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 diff --git a/LICENSE b/LICENSE index d3daa857..6258cc47 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,11 @@ Copyright (C) 2014 Google Inc. - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -213,3 +213,33 @@ Copyright (C) 2014 Google Inc. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +==================================================================================================== +This package uses MiniJSON + +Copyright (c) 2013 Calvin Rien + +Based on the JSON parser by Patrick van Bergen +http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +Simplified it so that it doesn't throw exceptions +and can be used in Unity iPhone with maximum code stripping. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index bf97a25f..a9aafe9f 100644 --- a/README.md +++ b/README.md @@ -1,197 +1,130 @@ -Play Services Resolver for Unity -======== +# External Dependency Manager for Unity -# Overview +[![openupm](https://img.shields.io/npm/v/com.google.external-dependency-manager?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.google.external-dependency-manager/) +[![openupm](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=downloads&query=%24.downloads&suffix=%2Fmonth&url=https%3A%2F%2Fpackage.openupm.com%2Fdownloads%2Fpoint%2Flast-month%2Fcom.google.external-dependency-manager)](https://openupm.com/packages/com.google.external-dependency-manager/) -This library is intended to be used by any Unity plugin that requires: +## Overview - * Android specific libraries (e.g - [AARs](https://developer.android.com/studio/projects/android-library.html)). - * iOS [CocoaPods](https://cocoapods.org/). - * Version management of transitive dependencies. +The External Dependency Manager for Unity (EDM4U) (formerly Play Services +Resolver/Jar Resolver) is intended to be used by any Unity package or user that +requires: -# Background +* Android specific libraries (e.g + [AARs](https://developer.android.com/studio/projects/android-library.html)) -Many Unity plugins have dependencies upon Android specific libraries, iOS -CocoaPods, and sometimes have transitive dependencies upon other Unity plugins. -This causes the following problems: +* iOS [CocoaPods](https://cocoapods.org/) - * Integrating platform specific (e.g Android and iOS) libraries within a - Unity project can be complex and a burden on a Unity plugin maintainer. - * The process of resolving conflicting dependencies on platform specific - libraries is pushed to the developer attempting to use a Unity plugin. - The developer trying to use you plugin is very likely to give up when - faced with Android or iOS specific build errors. - * The process of resolving conflicting Unity plugins (due to shared Unity - plugin components) is pushed to the developer attempting to use your Unity - plugin. In an effort to resolve conflicts, the developer will very likely - attempt to resolve problems by deleting random files in your plugin, - report bugs when that doesn't work and finally give up. - -The Play Services Resolver plugin (the name comes from its origin of just -handling -[Google Play Services](https://developers.google.com/android/guides/overview) -dependencies on Android) provides solutions for each of these problems. - -## Android Dependency Management +* Version management of transitive dependencies -The *Android Resolver* component of this plugin will download and integrate -Android library dependencies and handle any conflicts between plugins that share -the same dependencies. +* Management of Package Manager (PM) Registries -Without the Android Resolver, typically Unity plugins bundle their AAR and -JAR dependencies, e.g. a Unity plugin `SomePlugin` that requires the Google -Play Games Android library would redistribute the library and its transitive -dependencies in the folder `SomePlugin/Android/`. When a user imports -`SomeOtherPlugin` that includes the same libraries (potentially at different -versions) in `SomeOtherPlugin/Android/`, the developer using `SomePlugin` and -`SomeOtherPlugin` will see an error when building for Android that can be hard -to interpret. +If you want to add and use iOS/Android dependencies directly in your project, +then you should to install EDM4U in your project. -Using the Android Resolver to manage Android library dependencies: +If you are a package user and the plugin you are using depends on EDM4U, *and* +the package does not include EDM4U as a package dependency already, then you +should to install EDM4U in your project. - * Solves Android library conflicts between plugins. - * Handles all of the various processing steps required to use Android - libraries (AARs, JARs) in Unity 4.x and above projects. Almost all - versions of Unity have - at best - partial support for AARs. - * (Experimental) Supports minification of included Java components without - exporting a project. +If you are a UPM package maintainer and your package requires EDM4U, then you +should add EDM4U as a +[package dependency](https://docs.unity3d.com/2019.3/Documentation/Manual/upm-dependencies.html) +in your package manifest (`package.json`): -## iOS Dependency Management +```json +{ + "dependencies": { + "com.google.external-dependency-manager": "1.2.178" + } +} +``` -The *iOS Resolver* component of this plugin integrates with -[CocoaPods](https://cocoapods.org/) to download and integrate iOS libraries -and frameworks into the Xcode project Unity generates when building for iOS. -Using CocoaPods allows multiple plugins to utilize shared components without -forcing developers to fix either duplicate or incompatible versions of -libraries included through multiple Unity plugins in their project. +You should still install EDM4U to test out the package during development. -## Unity Plugin Version Management +If you are a legacy `.unitypackage` package maintainer and your package requires +EDM4U, please ask the user to install EDM4U separately. You should install EDM4U +to test out the package during development. -Finally, the *Version Handler* component of this plugin simplifies the process -of managing transitive dependencies of Unity plugins and each plugin's upgrade -process. +Updated releases are available on +[GitHub](https://github.com/googlesamples/unity-jar-resolver) -For example, without the Version Handler plugin, if: +## Requirements - * Unity plugin `SomePlugin` includes the `Play Services Resolver` plugin at - version 1.1. - * Unity plugin `SomeOtherPlugin` includes the `Play Services Resolver` - plugin at version 1.2. +The *Android Resolver* and *iOS Resolver* components of the plugin only work +with Unity version 4.6.8 or higher. -The version of `Play Services Resolver` included in the developer's project -depends upon the order the developer imports `SomePlugin` or `SomeOtherPlugin`. +The *Version Handler* component only works with Unity 5.x or higher as it +depends upon the `PluginImporter` UnityEditor API. -This results in: +The *Package Manager Resolver* component only works with Unity 2018.4 or above, +when [scoped registry](https://docs.unity3d.com/Manual/upm-scoped.html) support +was added to the Package Manager. - * `Play Services Resolver` at version 1.2, if `SomePlugin` is imported then - `SomeOtherPlugin` is imported. - * `Play Services Resolver` at version 1.1, if `SomeOtherPlugin` is imported - then `SomePlugin` is imported. +## Getting Started -The Version Handler solves the problem of managing transitive dependencies by: +Check out [troubleshooting](troubleshooting-faq.md) if you need help. - * Specifying a set of packaging requirements that enable a plugin at - different versions to be imported into a Unity project. - * Providing activation logic that selects the latest version of a plugin - within a project. +### Install via OpenUPM -When using the Version Handler to manage `Play Services Resolver` included in -`SomePlugin` and `SomeOtherPlugin`, from the prior example, version 1.2 will -always be the version activated in a developer's Unity project. +EDM4U is available on +[OpenUPM](https://openupm.com/packages/com.google.external-dependency-manager/): -Plugin creators are encouraged to adopt this library to ease integration for -their customers. For more information about integrating Play Services Resolver -into your own plugin, see the [Plugin Redistribution](#plugin-redistribution) -section of this document. +```shell +openupm add com.google.external-dependency-manager +``` -# Requirements +### Install via git URL +1. Open Package Manager +2. Click on the + icon on the top left corner of the "Package Manager" screen +3. Click on "Install package from git url..." +4. Paste: https://github.com/googlesamples/unity-jar-resolver.git?path=upm -The Android Resolver and iOS Resolver components of the plugin only work with -Unity version 4.6.8 or higher. +### Install via Google APIs for Unity -The *Version Handler* component only works with Unity 5.x or higher as it -depends upon the `PluginImporter` UnityEditor API. +EDM4U is available both in UPM and legacy `.unitypackage` formats on +[Google APIs for Unity](https://developers.google.com/unity/archive#external_dependency_manager_for_unity). -# Getting Started +You may install the UPM version (.tgz) as a +[local UPM package](https://docs.unity3d.com/Manual/upm-ui-local.html). -Before you import the Play Services Resolver into your plugin project, you first -need to consider whether you intend to *redistribute* Play Services Resolver -along with your own plugin. +You can also install EDM4U in your project as a `.unitypackage`. This is not +recommended due to potential conflicts. -Redistributing the `Play Services Resolver` inside your own plugin will ease -the integration process for your users, by resolving dependency conflicts -between your plugin and other plugins in a user's project. +### Conflict Resolution -If you wish to redistribute the `Play Services Resolver` inside your plugin, -you **must** follow these steps when importing the -`play-services-resolver-*.unitypackage`, and when exporting your own plugin -package: +For historical reasons, a package maintainer may choose to embed EDM4U in their +package for ease of installation. This will create a conflict when you try to +install EDM4U with the steps above, or with another package with embedded EDM4U. +If your project imported a `.unitypackage` that has a copy of EDM4U embedded in +it, you may safely delete it from your Assets folder. If your project depends on +another UPM package with EDM4U, please reach out to the package maintainer and +ask them to replace it with a dependency to this package. In the meantime, you +can workaround the issue by copying the package to your Packages folder (to +create an +[embedded package](https://docs.unity3d.com/Manual/upm-concepts.html#Embedded)) +and perform the steps yourself to avoid a dependency conflict. - 1. Import the `play-services-resolver-*.unitypackage` into your plugin - project by - [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), ensuring that - you add the `-gvh_disable` option. - 1. Export your plugin by [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), ensuring that - you: - - Include the contents of the `Assets/PlayServicesResolver` directory. - - Add the `-gvh_disable` option. +### Config file -You **must** specify the `-gvh_disable` option in order for the Version -Handler to work correctly! +To start adding dependencies to your project, copy and rename the +[SampleDependencies.xml](https://github.com/googlesamples/unity-jar-resolver/blob/master/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml) +file into your plugin and add the dependencies your project requires. -For example, the following command will import the -`play-services-resolver-1.2.46.0.unitypackage` into the project -`MyPluginProject` and export the entire Assets folder to -`MyPlugin.unitypackage`: +The XML file needs to be under an `Editor` directory and match the name +`*Dependencies.xml`. For example, `MyPlugin/Editor/MyPluginDependencies.xml`. -``` -Unity -gvh_disable \ - -batchmode \ - -importPackage play-services-resolver-1.2.46.0.unitypackage \ - -projectPath MyPluginProject \ - -exportPackage Assets MyPlugin.unitypackage \ - -quit -``` - -## Background - -The *Version Handler* component relies upon deferring the load of editor DLLs -so that it can run first and determine the latest version of a plugin component -to activate. The build of the `Play Services Resolver` plugin has Unity asset -metadata that is configured so that the editor components are not -initially enabled when it's imported into a Unity project. To maintain this -configuration when importing the `Play Services Resolver` .unitypackage -into a Unity plugin project, you *must* specify the command line option -`-gvh_disable` which will prevent the Version Handler component from running and -changing the Unity asset metadata. +## Usages -# Android Resolver Usage +### Android Resolver The Android Resolver copies specified dependencies from local or remote Maven repositories into the Unity project when a user selects Android as the build target in the Unity editor. - 1. Add the `play-services-resolver-*.unitypackage` to your plugin - project (assuming you are developing a plugin). If you are redistributing - the Play Services Resolver with your plugin, you **must** follow the - import steps in the [Getting Started](#getting-started) section! - - 2. Copy and rename the `SampleDependencies.xml` file into your - plugin and add the dependencies your plugin requires. - - The XML file just needs to be under an `Editor` directory and match the - name `*Dependencies.xml`. For example, - `MyPlugin/Editor/MyPluginDependencies.xml`. - - 3. Follow the steps in the [Getting Started](#getting-started) - section when you are exporting your plugin package. - For example, to add the Google Play Games library -(`com.google.android.gms:play-services-games` package) at version `9.8.0` to -the set of a plugin's Android dependencies: +(`com.google.android.gms:play-services-games` package) at version `9.8.0` to the +set of a plugin's Android dependencies: -``` +```xml @@ -205,20 +138,22 @@ the set of a plugin's Android dependencies: The version specification (last component) supports: - * Specific versions e.g `9.8.0` - * Partial matches e.g `9.8.+` would match 9.8.0, 9.8.1 etc. choosing the most - recent version. - * Latest version using `LATEST` or `+`. We do *not* recommend using this - unless you're 100% sure the library you depend upon will not break your - Unity plugin in future. +* Specific versions e.g `9.8.0` + +* Partial matches e.g `9.8.+` would match 9.8.0, 9.8.1 etc. choosing the most + recent version + +* Latest version using `LATEST` or `+`. We do *not* recommend using this + unless you're 100% sure the library you depend upon will not break your + Unity plugin in future The above example specifies the dependency as a component of the Android SDK manager such that the Android SDK manager will be executed to install the -package if it's not found. If your Android dependency is located on Maven +package if it's not found. If your Android dependency is located on Maven central it's possible to specify the package simply using the `androidPackage` element: -``` +```xml @@ -226,202 +161,743 @@ element: ``` -## Auto-resolution +#### Auto-resolution By default the Android Resolver automatically monitors the dependencies you have -specified and the `Plugins/Android` folder of your Unity project. The -resolution process runs when the specified dependencies are not present in your -project. +specified and the `Plugins/Android` folder of your Unity project. The resolution +process runs when the specified dependencies are not present in your project. -The *auto-resolution* process can be disabled via the -`Assets > Play Services Resolver > Android Resolver > Settings` menu. +The *auto-resolution* process can be disabled via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. Manual resolution can be performed using the following menu options: - * `Assets > Play Services Resolver > Android Resolver > Resolve` - * `Assets > Play Services Resolver > Android Resolver > Force Resolve` +* `Assets > External Dependency Manager > Android Resolver > Resolve` + +* `Assets > External Dependency Manager > Android Resolver > Force Resolve` -## Deleting libraries +#### Deleting libraries -Resolved packages are tracked via asset labels by the Android Resolver. -They can easily be deleted using the -`Assets > Play Services Resolver > Android Resolver > Delete Resolved Libraries` -menu item. +Resolved packages are tracked via asset labels by the Android Resolver. They can +easily be deleted using the `Assets > External Dependency Manager > Android +Resolver > Delete Resolved Libraries` menu item. -## Android Manifest Variable Processing +#### Android Manifest Variable Processing Some AAR files (for example play-services-measurement) contain variables that -are processed by the Android Gradle plugin. Unfortunately, Unity does not +are processed by the Android Gradle plugin. Unfortunately, Unity does not perform the same processing when using Unity's Internal Build System, so the -Android Resolver plugin handles known cases of this variable substitution -by exploding the AAR into a folder and replacing `${applicationId}` with the +Android Resolver plugin handles known cases of this variable substitution by +exploding the AAR into a folder and replacing `${applicationId}` with the `bundleID`. Disabling AAR explosion and therefore Android manifest processing can be done -via the `Assets > Play Services Resolver > Android Resolver > Settings` menu. -You may want to disable explosion of AARs if you're exporting a project to be -built with Gradle / Android Studio. +via the `Assets > External Dependency Manager > Android Resolver > Settings` +menu. You may want to disable explosion of AARs if you're exporting a project to +be built with Gradle/Android Studio. -## ABI Stripping +#### ABI Stripping -Some AAR files contain native libraries (.so files) for each ABI supported -by Android. Unfortunately, when targeting a single ABI (e.g x86), Unity does -not strip native libraries for unused ABIs. To strip unused ABIs, the Android -Resolver plugin explodes an AAR into a folder and removes unused ABIs to -reduce the built APK size. Furthermore, if native libraries are not stripped -from an APK (e.g you have a mix of Unity's x86 library and some armeabi-v7a -libraries) Android may attempt to load the wrong library for the current -runtime ABI completely breaking your plugin when targeting some architectures. +Some AAR files contain native libraries (.so files) for each ABI supported by +Android. Unfortunately, when targeting a single ABI (e.g x86), Unity does not +strip native libraries for unused ABIs. To strip unused ABIs, the Android +Resolver plugin explodes an AAR into a folder and removes unused ABIs to reduce +the built APK size. Furthermore, if native libraries are not stripped from an +APK (e.g you have a mix of Unity's x86 library and some armeabi-v7a libraries) +Android may attempt to load the wrong library for the current runtime ABI +completely breaking your plugin when targeting some architectures. -AAR explosion and therefore ABI stripping can be disabled via the -`Assets > Play Services Resolver > Android Resolver > Settings` menu. You may -want to disable explosion of AARs if you're exporting a project to be built -with Gradle / Android Studio. +AAR explosion and therefore ABI stripping can be disabled via the `Assets > +External Dependency Manager > Android Resolver > Settings` menu. You may want to +disable explosion of AARs if you're exporting a project to be built with +Gradle/Android Studio. -## Resolution Strategies +#### Resolution Strategies By default the Android Resolver will use Gradle to download dependencies prior -to integrating them into a Unity project. This works with Unity's internal -build system and Gradle / Android Studio project export. +to integrating them into a Unity project. This works with Unity's internal build +system and Gradle/Android Studio project export. + +It's possible to change the resolution strategy via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. + +##### Download Artifacts with Gradle + +Using the default resolution strategy, the Android resolver executes the +following operations: + +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project. -It's possible to change the resolution strategy via the -`Assets > Play Services Resolver > Android Resolver > Settings` menu. +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. -## Dependency Tracking +- Run `download_artifacts.gradle` with Gradle to resolve conflicts and, if + successful, download the set of resolved Android libraries (AARs, JARs). + +- Process each AAR/JAR so that it can be used with the currently selected + Unity build system (e.g Internal vs. Gradle, Export vs. No Export). This + involves patching each reference to `applicationId` in the + `AndroidManifest.xml` with the project's bundle ID. This means resolution + must be run again if the bundle ID has changed. + +- Move the processed AARs to `Plugins/Android` so they will be included when + Unity invokes the Android build. + +##### Integrate into mainTemplate.gradle + +Unity 5.6 introduced support for customizing the `build.gradle` used to build +Unity projects with Gradle. When the *Patch mainTemplate.gradle* setting is +enabled, rather than downloading artifacts before the build, Android resolution +results in the execution of the following operations: + +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project and + remove sections delimited with `// Android Resolver * Start` and `// Android + Resolver * End` lines. + +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. + +- Rename any `.srcaar` files in the build to `.aar` and exclude them from + being included directly by Unity in the Android build as + `mainTemplate.gradle` will be patched to include them instead from their + local maven repositories. + +- Inject the required Gradle repositories into `mainTemplate.gradle` at the + line matching the pattern `.*apply plugin: + 'com\.android\.(application|library)'.*` or the section starting at the line + `// Android Resolver Repos Start`. If you want to control the injection + point in the file, the section delimited by the lines `// Android Resolver + Repos Start` and `// Android Resolver Repos End` should be placed in the + global scope before the `dependencies` section. + +- Inject the required Android dependencies (libraries) into + `mainTemplate.gradle` at the line matching the pattern `***DEPS***` or the + section starting at the line `// Android Resolver Dependencies Start`. If + you want to control the injection point in the file, the section delimited + by the lines `// Android Resolver Dependencies Start` and `// Android + Resolver Dependencies End` should be placed in the `dependencies` section. + +- Inject the packaging options logic, which excludes architecture specific + libraries based upon the selected build target, into `mainTemplate.gradle` + at the line matching the pattern `android +{` or the section starting at the + line `// Android Resolver Exclusions Start`. If you want to control the + injection point in the file, the section delimited by the lines `// Android + Resolver Exclusions Start` and `// Android Resolver Exclusions End` should + be placed in the global scope before the `android` section. + +#### Dependency Tracking The Android Resolver creates the `ProjectSettings/AndroidResolverDependencies.xml` to quickly determine the set -of resolved dependencies in a project. This is used by the auto-resolution +of resolved dependencies in a project. This is used by the auto-resolution process to only run the expensive resolution process when necessary. -## Displaying Dependencies +#### Displaying Dependencies -It's possible to display the set of dependencies the Android Resolver -would download and process in your project via the -`Assets > Play Services Resolver > Android Resolver > Display Libraries` menu -item. +It's possible to display the set of dependencies the Android Resolver would +download and process in your project via the `Assets > External Dependency +Manager > Android Resolver > Display Libraries` menu item. -# iOS Resolver Usage +### iOS Resolver The iOS resolver component of this plugin manages -[CocoaPods](https://cocoapods.org/). A CocoaPods `Podfile` is generated and -the `pod` tool is executed as a post build process step to add dependencies -to the Xcode project exported by Unity. +[CocoaPods](https://cocoapods.org/). A CocoaPods `Podfile` is generated and the +`pod` tool is executed as a post build process step to add dependencies to the +Xcode project exported by Unity. Dependencies for iOS are added by referring to CocoaPods. - 1. Add the `play-services-resolver-*.unitypackage` to your plugin - project (assuming you are developing a plugin). If you are redistributing - the Play Services Resolver with your plugin, you **must** follow the - import steps in the [Getting Started](#getting-started) section! - - 2. Copy and rename the SampleDependencies.xml file into your - plugin and add the dependencies your plugin requires. - - The XML file just needs to be under an `Editor` directory and match the - name `*Dependencies.xml`. For example, - `MyPlugin/Editor/MyPluginDependencies.xml`. - - 3. Follow the steps in the [Getting Started](#getting-started) - section when you are exporting your plugin package. - For example, to add the AdMob pod, version 7.0 or greater with bitcode enabled: -``` +```xml + minTargetSdk="6.0" addToAllTargets="false" /> ``` -## Integration Strategies +#### Integration Strategies The `CocoaPods` are either: - * Downloaded and injected into the Xcode project file directly, rather than - creating a separate xcworkspace. We call this `Xcode project` integration. - * If the Unity version supports opening a xcworkspace file, the `pod` tool - is used as intended to generate a xcworkspace which references the - CocoaPods. We call this `Xcode workspace` integration. -The resolution strategy can be changed via the -`Assets > Play Services Resolver > iOS Resolver > Settings` menu. +* Downloaded and injected into the Xcode project file directly, rather than + creating a separate xcworkspace. We call this `Xcode project` integration. + +* If the Unity version supports opening a xcworkspace file, the `pod` tool is + used as intended to generate a xcworkspace which references the CocoaPods. + We call this `Xcode workspace` integration. + +The resolution strategy can be changed via the `Assets > External Dependency +Manager > iOS Resolver > Settings` menu. + +##### Appending text to generated Podfile + +In order to modify the generated Podfile you can create a script like this: + +```csharp +using System.IO; + +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEngine; + +public class PostProcessIOS : MonoBehaviour +{ + // Must be between 40 and 50 to ensure that it's not overriden by Podfile generation (40) and + // that it's added before "pod install" (50). + [PostProcessBuildAttribute(45)] + private static void PostProcessBuild_iOS(BuildTarget target, string buildPath) + { + if (target == BuildTarget.iOS) + { + using (StreamWriter sw = File.AppendText(buildPath + "/Podfile")) + { + // E.g. add an app extension + sw.WriteLine("\ntarget 'NSExtension' do\n pod 'Firebase/Messaging', '6.6.0'\nend"); + } + } + } +} +``` + +### Package Manager Resolver + +Adding registries to the +[Package Manager](https://docs.unity3d.com/Manual/Packages.html) (PM) is a +manual process. The Package Manager Resolver (PMR) component of this plugin +makes it easy for plugin maintainers to distribute new PM registry servers and +easy for plugin users to manage PM registry servers. + +#### Adding Registries + +For example, to add a registry for plugins in the scope `com.coolstuff`: + +```xml + + + + com.coolstuff + + + +``` + +When PMR is loaded it will prompt the developer to add the registry to their +project if it isn't already present in the `Packages/manifest.json` file. + +For more information, see Unity's documentation on +[scoped package registries](https://docs.unity3d.com/Manual/upm-scoped.html). + +#### Managing Registries + +It's possible to add and remove registries that are specified via PMR XML +configuration files via the following menu options: + +* `Assets > External Dependency Manager > Package Manager Resolver > Add + Registries` will prompt the user with a window which allows them to add + registries discovered in the project to the Package Manager. + +* `Assets > External Dependency Manager > Package Manager Resolver > Remove + Registries` will prompt the user with a window which allows them to remove + registries discovered in the project from the Package Manager. -# Version Handler Usage +* `Assets > External Dependency Manager > Package Manager Resolver > Modify + Registries` will prompt the user with a window which allows them to add or + remove registries discovered in the project. + +#### Migration + +PMR can migrate Version Handler packages installed in the `Assets` folder to PM +packages. This requires the plugins to implement the following: + +* `.unitypackage` must include a Version Handler manifests that describes the + components of the plugin. If the plugin has no dependencies the manifest + would just include the files in the plugin. + +* The PM package JSON provided by the registry must include a keyword (in the + `versions.VERSION.keyword` list) that maps the PM package to a Version + Handler package using the format `vh-name:VERSION_HANDLER_MANIFEST_NAME` + where `VERSION_HANDLER_MANIFEST_NAME` is the name of the manifest defined in + the `.unitypackage`. For more information see the description of the + `gvhp_manifestname` asset label in the [Version Handler](#version-handler) + section. + +When using the `Assets > External Dependency Manager > Package Manager +Resolver > Migrate Packages` menu option, PMR then will: + +* List all Version Handler manager packages in the project. + +* Search all available packages in the PM registries and fetch keywords + associated with each package parsing the Version Handler manifest names for + each package. + +* Map each installed Version Handler package to a PM package. + +* Prompt the user to migrate the discovered packages. + +* Perform package migration for all selected packages if the user clicks the + `Apply` button. + +#### Configuration + +PMR can be configured via the `Assets > External Dependency Manager > Package +Manager Resolver > Settings` menu option: + +* `Add package registries` when enabled, when the plugin loads or registry + configuration files change, this will prompt the user to add registries that + are not present in the Package Manager. + +* `Prompt to add package registries` will cause a developer to be prompted + with a window that will ask for confirmation before adding registries. When + this is disabled registries are added silently to the project. + +* `Prompt to migrate packages` will cause a developer to be prompted with a + window that will ask for confirmation before migrating packages installed in + the `Assets` directory to PM packages. + +* `Enable Analytics Reporting` when enabled, reports the use of the plugin to + the developers so they can make imrpovements. + +* `Verbose logging` when enabled prints debug information to the console which + can be useful when filing bug reports. + +### Version Handler The Version Handler component of this plugin manages: -* Shared Unity plugin dependencies. -* Upgrading Unity plugins by cleaning up old files from previous versions. + +* Shared Unity plugin dependencies. + +* Upgrading Unity plugins by cleaning up old files from previous versions. + +* Uninstallation of plugins that are distributed with manifest files. + +* Restoration of plugin assets to their original install locations if assets + are tagged with the `exportpath` label. + +Since the Version Handler needs to modify Unity asset metadata (`.meta` files), +to enable/disable components, rename and delete asset files it does not work +with Package Manager installed packages. It's still possible to include EDM4U in +Package Manager packages, the Version Handler component simply won't do anything +to PM plugins in this case. + +#### Using Version Handler Managed Plugins + +If a plugin is imported at multiple different versions into a project, if the +Version Handler is enabled, it will automatically check all managed assets to +determine the set of assets that are out of date and assets that should be +removed. To disable automatic checking managed assets disable the `Enable +version management` option in the `Assets > External Dependency Manager > +Version Handler > Settings` menu. + +If version management is disabled, it's possible to check managed assets +manually using the `Assets > External Dependency Manager > Version Handler > +Update` menu option. + +##### Listing Managed Plugins + +Plugins managed by the Version Handler, those that ship with manifest files, can +displayed using the `Assets > External Dependency Manager > Version Handler > +Display Managed Packages` menu option. The list of plugins are written to the +console window along with the set of files used by each plugin. + +##### Uninstalling Managed Plugins + +Plugins managed by the Version Handler, those that ship with manifest files, can +be removed using the `Assets > External Dependency Manager > Version Handler > +Uninstall Managed Packages` menu option. This operation will display a window +that allows a developer to select a set of plugins to remove which will remove +all files owned by each plugin excluding those that are in use by other +installed plugins. + +Files managed by the Version Handler, those labeled with the `gvh` asset label, +can be checked to see whether anything needs to be upgraded, disabled or removed +using the `Assets > External Dependency Manager > Version Handler > Update` menu +option. + +##### Restore Install Paths + +Some developers move assets around in their project which can make it harder for +plugin maintainers to debug issues if this breaks Unity's +[special folders](https://docs.unity3d.com/Manual/SpecialFolders.html) rules. If +assets are labeled with their original install/export path (see +`gvhp_exportpath` below), Version Handler can restore assets to their original +locations when using the `Assets > External Dependency Manager > Version +Handler > Move Files To Install Locations` menu option. + +##### Settings + +Some behavior of the Version Handler can be configured via the `Assets > +External Dependency Manager > Version Handler > Settings` menu option. + +* `Enable version management` controls whether the plugin should automatically + check asset versions and apply changes. If this is disabled the process + should be run manually when installing or upgrading managed plugins using + `Assets > External Dependency Manager > Version Handler > Update`. + +* `Rename to canonical filenames` is a legacy option that will rename files to + remove version numbers and other labels from filenames. + +* `Prompt for obsolete file deletion` enables the display of a window when + obsolete files are deleted allowing the developer to select which files to + delete and those to keep. + +* `Allow disabling files via renaming` controls whether obsolete or disabled + files should be disabled by renaming them to `myfilename_DISABLED`. Renaming + to disable files is required in some scenarios where Unity doesn't support + removing files from the build via the PluginImporter. + +* `Enable Analytics Reporting` enables/disables usage reporting to plugin + developers to improve the product. + +* `Verbose logging` enables *very* noisy log output that is useful for + debugging while filing a bug report or building a new managed plugin. + +* `Use project settings` saves settings for the plugin in the project rather + than system-wide. + +#### Redistributing a Managed Plugin + +The Version Handler employs a couple of methods for managing version selection, +upgrade and removal of plugins. + +* Each plugin can ship with a manifest file that lists the files it includes. + This makes it possible for Version Handler to calculate the difference in + assets between the most recent release of a plugin and the previous release + installed in a project. If a files are removed the Version Handler will + prompt the user to clean up obsolete files. + +* Plugins can ship using assets with unique names, unique GUIDs and version + number labels. Version numbers can be attached to assets using labels or + added to the filename (e.g `myfile.txt` would be `myfile_version-x.y.z.txt). + This allows the Version Handler to determine which set of files are the same + file at different versions, select the most recent version and prompt the + developer to clean up old versions. Unity plugins can be managed by the Version Handler using the following steps: - 1. Add the `gvh` asset label to each asset (file) you want Version Handler - to manage. - 1. Add the `gvh_version-VERSION` label to each asset where `VERSION` is the - version of the plugin you're releasing (e.g 1.2.3). - 1. Optional: Add `gvh_targets-editor` label to each editor DLL in your - plugin and disable `editor` as a target platform for the DLL. - The Version Handler will enable the most recent version of this DLL when - the plugin is imported. - 1. Optional: If your plugin is included in other Unity plugins, you should - add the version number to each filename and change the GUID of each asset. - This allows multiple versions of your plugin to be imported into a Unity - project, with the Version Handler component activating only the most - recent version. - 1. Create a manifest text file named `MY_UNIQUE_PLUGIN_NAME_VERSION.txt` - that lists all the files in your plugin relative to the project root. - Then add the `gvh_manifest` label to the asset to indicate this file is - a plugin manifest. - 1. Redistribute the `Play Services Resolver` Unity plugin with your plugin. - See the [Plugin Redistribution](#plugin-redistribution) for the details. +1. Add the `gvh` asset label to each asset (file) you want Version Handler to + manage. + +1. Add the `gvh_version-VERSION` label to each asset where `VERSION` is the + version of the plugin you're releasing (e.g 1.2.3). + +1. Add the `gvhp_exportpath-PATH` label to each asset where `PATH` is the + export path of the file when the `.unitypackage` is created. This is used to + track files if they're moved around in a project by developers. + +1. Optional: Add `gvh_targets-editor` label to each editor DLL in your plugin + and disable `editor` as a target platform for the DLL. The Version Handler + will enable the most recent version of this DLL when the plugin is imported. + +1. Optional: If your plugin is included in other Unity plugins, you should add + the version number to each filename and change the GUID of each asset. This + allows multiple versions of your plugin to be imported into a Unity project, + with the Version Handler component activating only the most recent version. + +1. Create a manifest text file named `MY_UNIQUE_PLUGIN_NAME_VERSION.txt` that + lists all the files in your plugin relative to the project root. Then add + the `gvh_manifest` label to the asset to indicate this file is a plugin + manifest. + +1. Optional: Add a `gvhp_manifestname-NAME` label to your manifest file to + provide a human readable name for your package. If this isn't provided the + name of the manifest file will be used as the package name. NAME can match + the pattern `[0-9]+[a-zA-Z -]` where a leading integer will set the priority + of the name where `0` is the highest priority and preferably used as the + display name. The lowest value (i.e highest priority name) will be used as + the display name and all other specified names will be aliases of the + display name. Aliases can refer to previous names of the package allowing + renaming across published versions. + +1. Redistribute EDM4U Unity plugin with your plugin. See the + [Plugin Redistribution](#plugin-redistribution) section for details. If you follow these steps: - * When users import a newer version of your plugin, files referenced by the - older version's manifest are cleaned up. - * The latest version of the plugin will be selected when users import - multiple packages that include your plugin, assuming the steps in - [Plugin Redistribution](#plugin-redistribution) are followed. +* When users import a newer version of your plugin, files referenced by the + older version's manifest are cleaned up. -## Building from Source +* The latest version of the plugin will be selected when users import multiple + packages that include your plugin, assuming the steps in + [Plugin Redistribution](#plugin-redistribution) are followed. -To build this plugin from source you need the following tools installed: - * Unity (with iOS and Android modules installed) +## Background -You can build the plugin by running the following from your shell -(Linux / OSX): +Many Unity plugins have dependencies upon Android specific libraries, iOS +CocoaPods, and sometimes have transitive dependencies upon other Unity plugins. +This causes the following problems: + +* Integrating platform specific (e.g Android and iOS) libraries within a Unity + project can be complex and a burden on a Unity plugin maintainer. +* The process of resolving conflicting dependencies on platform specific + libraries is pushed to the developer attempting to use a Unity plugin. The + developer trying to use your plugin is very likely to give up when faced + with Android or iOS specific build errors. +* The process of resolving conflicting Unity plugins (due to shared Unity + plugin components) is pushed to the developer attempting to use your Unity + plugin. In an effort to resolve conflicts, the developer will very likely + attempt to resolve problems by deleting random files in your plugin, report + bugs when that doesn't work and finally give up. + +EDM4U provides solutions for each of these problems. + +### Android Dependency Management + +The *Android Resolver* component of this plugin will download and integrate +Android library dependencies and handle any conflicts between plugins that share +the same dependencies. + +Without the Android Resolver, typically Unity plugins bundle their AAR and JAR +dependencies, e.g. a Unity plugin `SomePlugin` that requires the Google Play +Games Android library would redistribute the library and its transitive +dependencies in the folder `SomePlugin/Android/`. When a user imports +`SomeOtherPlugin` that includes the same libraries (potentially at different +versions) in `SomeOtherPlugin/Android/`, the developer using `SomePlugin` and +`SomeOtherPlugin` will see an error when building for Android that can be hard +to interpret. + +Using the Android Resolver to manage Android library dependencies: + +* Solves Android library conflicts between plugins. +* Handles all of the various processing steps required to use Android + libraries (AARs, JARs) in Unity 4.x and above projects. Almost all versions + of Unity have - at best - partial support for AARs. +* (Experimental) Supports minification of included Java components without + exporting a project. + +### iOS Dependency Management + +The *iOS Resolver* component of this plugin integrates with +[CocoaPods](https://cocoapods.org/) to download and integrate iOS libraries and +frameworks into the Xcode project Unity generates when building for iOS. Using +CocoaPods allows multiple plugins to utilize shared components without forcing +developers to fix either duplicate or incompatible versions of libraries +included through multiple Unity plugins in their project. +### Package Manager Registry Setup + +The [Package Manager](https://docs.unity3d.com/Manual/Packages.html) (PM) makes +use of [NPM](https://www.npmjs.com/) registry servers for package hosting and +provides ways to discover, install, upgrade and uninstall packages. This makes +it easier for developers to manage plugins within their projects. + +However, installing additional package registries requires a few manual steps +that can potentially be error prone. The *Package Manager Resolver* component of +this plugin integrates with [PM](https://docs.unity3d.com/Manual/Packages.html) +to provide a way to auto-install PM package registries when a `.unitypackage` is +installed which allows plugin maintainers to ship a `.unitypackage` that can +provide access to their own PM registry server to make it easier for developers +to manage their plugins. + +### Unity Plugin Version Management + +Finally, the *Version Handler* component of this plugin simplifies the process +of managing transitive dependencies of Unity plugins and each plugin's upgrade +process. + +For example, without the Version Handler plugin, if: + +* Unity plugin `SomePlugin` includes `EDM4U` plugin at version 1.1. +* Unity plugin `SomeOtherPlugin` includes `EDM4U` plugin at version 1.2. + +The version of `EDM4U` included in the developer's project depends upon the +order the developer imports `SomePlugin` or `SomeOtherPlugin`. + +This results in: + +* `EDM4U` at version 1.2, if `SomePlugin` is imported then `SomeOtherPlugin` + is imported. +* `EDM4U` at version 1.1, if `SomeOtherPlugin` is imported then `SomePlugin` + is imported. + +The Version Handler solves the problem of managing transitive dependencies by: + +* Specifying a set of packaging requirements that enable a plugin at different + versions to be imported into a Unity project. +* Providing activation logic that selects the latest version of a plugin + within a project. + +When using the Version Handler to manage `EDM4U` included in `SomePlugin` and +`SomeOtherPlugin`, from the prior example, version 1.2 will always be the +version activated in a developer's Unity project. + +Plugin creators are encouraged to adopt this library to ease integration for +their customers. For more information about integrating EDM4U into your own +plugin, see the [Plugin Redistribution](#plugin-redistribution) section of this +document. + +## Analytics + +The External Dependency Manager for Unity plugin by default logs usage to Google +Analytics. The purpose of the logging is to quantitatively measure the usage of +functionality, to gather reports on integration failures and to inform future +improvements to the developer experience of the External Dependency Manager +plugin. Note that the analytics collected are limited to the scope of the EDM4U +plugin’s usage. + +For details of what is logged, please refer to the usage of +`EditorMeasurement.Report()` in the source code. + +## Plugin Redistribution + +If you are a package maintainer and your package depends on EDM4U, it is highly +recommended to use the UPM format and add EDM4U as a dependency. If you must +include it in your `.unitypackage`, redistributing `EDM4U` inside your own +plugin might ease the integration process for your users. + +If you wish to redistribute `EDM4U` inside your plugin, you **must** follow +these steps when importing the `external-dependency-manager-*.unitypackage`, and +when exporting your own plugin package: + +1. Import the `external-dependency-manager-*.unitypackage` into your plugin + project by + [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), + ensuring that you add the `-gvh_disable` option. +1. Export your plugin by + [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), + ensuring that you: + - Include the contents of the `Assets/PlayServicesResolver` and + `Assets/ExternalDependencyManager` directory. + - Add the `-gvh_disable` option. + +You **must** specify the `-gvh_disable` option in order for the Version Handler +to work correctly! + +For example, the following command will import the +`external-dependency-manager-1.2.46.0.unitypackage` into the project +`MyPluginProject` and export the entire Assets folder to +`MyPlugin.unitypackage`: + +```shell +Unity -gvh_disable \ + -batchmode \ + -importPackage external-dependency-manager-1.2.46.0.unitypackage \ + -projectPath MyPluginProject \ + -exportPackage Assets MyPlugin.unitypackage \ + -quit ``` + +### Background + +The *Version Handler* component relies upon deferring the load of editor DLLs so +that it can run first and determine the latest version of a plugin component to +activate. The build of `EDM4U` plugin has Unity asset metadata that is +configured so that the editor components are not initially enabled when it's +imported into a Unity project. To maintain this configuration when importing the +`external-dependency-manager.unitypackage` into a Unity plugin project, you +*must* specify the command line option `-gvh_disable` which will prevent the +Version Handler component from running and changing the Unity asset metadata. + +## Building from Source + +To build this plugin from source you need the following tools installed: * Unity +2021 and below (with iOS and Android modules installed) * Java 11 + +You can build the plugin by running the following from your shell (Linux / OSX): + +```shell ./gradlew build + ``` or Windows: -``` +```shell ./gradlew.bat build ``` -### Releasing - -Each time a new build of this plugin is checked into the source tree you -need to do the following: - - * Bump the plugin version variable `pluginVersion` in `build.gradle` - * Update `CHANGELOG.md` with the new version number and changes included in - the release. - * Build the release using `./gradle release` which performs the following: - * Updates `play-services-resolver-*.unitypackage` - * Copies the unpacked plugin to the `exploded` directory. - * Updates template metadata files in the `plugin` directory. - The GUIDs of all asset metadata is modified due to the version number - change. Each file within the plugin is versioned to allow multiple - versions of the plugin to be imported into a Unity project which allows - the most recent version to be activated by the Version Handler - component. - * Create the release commit and tag the release using - `./gradle gitTagRelease` which performs the following: - * `git add -A` to pick up all modified, new and deleted files in the tree. - * `git commit --amend -a` to create a release commit with the release notes - in the change log. - * `git tag -a RELEASE -m "version RELEASE"` to tag the release. +If Java 11 is not your default Java command, add +`-Dorg.gradle.java.home=` to the command above. + +## Testing + +You can run the tests by running the following from your shell (Linux / OSX): + +```shell +./gradlew test +``` + +or Windows: + +```shell +./gradlew.bat test +``` + +The following properties can be set to narrow down the tests to run or change +the test run behavior. + +* `INTERACTIVE_MODE_TESTS_ENABLED` - Default to `1`. Set to `1` to enable + interactive mode tests, which requires GPU on the machine. Otherwise, only + run tests in the batch mode. +* `INCLUDE_TEST_TYPES` - Default to empty string, which means to include every + type of the test. To narrow down the types of test to run, set this + properties with a list of case-insensitive type strings separated by comma. + For instance, `-PINCLUDE_TEST_TYPES="Python,NUnit"` means to include only + Python tests and NUnit tests. See `TestTypeEnum` in `build.gradle` for + available options. +* `EXCLUDE_TEST_TYPES` - Default to empty string, which means to exclude none. + To add types of tests to exclude, set this properties with a list of + case-insensitive type strings separated by comma. For instance, + `-PEXCLUDE_TEST_TYPES="Python,NUnit"` means to exclude Python tests and + NUnit tests. See `TestTypeEnum` in `build.gradle` for available options. +* `INCLUDE_TEST_MODULES` - Default to empty string, which means to include the + tests for every modules. To narrow down modules to test, set this properties + with a list of case-insensitive module strings separated by comma. For + instance, `-PINCLUDE_TEST_MODULES="Tool,AndroidResolver"` means to run tests + for tools and Android Resolver only. See `TestModuleEnum` in `build.gradle` + for available options. +* `EXCLUDE_TEST_MODULES` - Default to empty string, which means to exclude + none. To add modules to exclude, set this properties with a list of + case-insensitive module strings separated by comma. For instance, + `-PEXCLUDE_TEST_MODULES="Tool,AndroidResolver"` means to run tests for any + modules other than tools and Android Resolver. See `TestModuleEnum` in + `build.gradle` for available options. +* `EXCLUDE_TESTS` - Default to empty string, which means to exclude none. To + add tests to exclude, set this properties with a list of case-insensitive + test names separated by comma. For instance, + `-PEXCLUDE_TESTS="testGenGuids,testDownloadArtifacts"` means to run tests + except the tests with name of `testGenGuids` and `testDownloadArtifacts`. +* `CONTINUE_ON_FAIL_FOR_TESTS_ENABLED` - Default to `1`. Set to `1` to + continue running the next test when the current one fails. Otherwise, the + build script stops whenever any test fails. + +For instance, by running the following command, it only runs the Unity +integration tests that does not requires GPU, but exclude tests for Android +Resolver module and iOS Resolver module. + +```shell +./gradlew test \ + -PINTERACTIVE_MODE_TESTS_ENABLED=0 \ + -PINCLUDE_TEST_TYPES="Integration" \ + -PEXCLUDE_TEST_MODULES="AndroidResolver,iOSResolver" +``` + +## Releasing + +Each time a new build of this plugin is checked into the source tree you need to +do the following: + +* Bump the plugin version variable `pluginVersion` in `build.gradle` +* Update `CHANGELOG.md` with the new version number and changes included in + the release. +* Build the release using `./gradlew release` which performs the following: + * Updates `external-dependency-manager-*.unitypackage` + * Copies the unpacked plugin to the `exploded` directory. + * Updates template metadata files in the `plugin` directory. The GUIDs of + all asset metadata is modified due to the version number change. Each + file within the plugin is versioned to allow multiple versions of the + plugin to be imported into a Unity project which allows the most recent + version to be activated by the Version Handler component. +* Create release commit using `./gradlew gitCreateReleaseCommit` which + performs `git commit -a -m "description from CHANGELOG.md"` +* Once the release commit is merge, tag the release using `./gradlew + gitTagRelease` which performs the following: + * `git tag -a pluginVersion -m "version RELEASE"` to tag the release. +* Update tags on remote branch using `git push --tag REMOTE HEAD:master` diff --git a/build.gradle b/build.gradle index fc062c11..cb824eac 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,14 @@ * Gradle file to build the Jar Resolver Unity plugin. */ buildscript { - repositories { - jcenter() - mavenLocal() - } + repositories { + mavenCentral() + mavenLocal() + } +} + +plugins { + id "com.jetbrains.python.envs" version "0.0.30" } /* @@ -20,8 +24,7 @@ project.ext { String unitySearchDirExcludesString = findProperty("UNITY_EXCLUDES") String[] unitySearchDirExcludes = unitySearchDirExcludesString ? - unitySearchDirExcludesString.tokenize(";") : - ["**/PackageManager/Editor"] + unitySearchDirExcludesString.tokenize(";") : [] // Save the current OS. operatingSystem = OperatingSystem.getOperatingSystem() @@ -32,7 +35,7 @@ project.ext { (OperatingSystem.MAC_OSX): ["/Applications/Unity/Unity.app/Contents/MacOS/Unity"] + (new FileNameFinder()).getFileNames( - "/", "Applications/UnityHub/*/Unity.app/Contents/MacOS/Unity"), + "/", "Applications/Unity/Hub/Editor/*/Unity.app/Contents/MacOS/Unity"), (OperatingSystem.WINDOWS): ["\\Program Files\\Unity\\Editor\\Unity.exe"] + (new FileNameFinder()).getFileNames( @@ -109,7 +112,7 @@ project.ext { "XBUILD_EXE", false, { unityRootDirTree.matching { include (operatingSystem == OperatingSystem.WINDOWS ? - "**/Mono/bin/xbuild.bat" : "**/xbuild") + "**/bin/xbuild.bat" : "**/xbuild") exclude unitySearchDirExcludes } }) @@ -118,38 +121,57 @@ project.ext { } saveProperty("XBUILD_EXE", xbuildExe, cacheProperties) - // Find the NUnit framework dll. - unityNUnitDll = getFileFromPropertyOrFileTree( - "UNITY_NUNIT_PATH", true, { + // Find mono to determine the distribution being used. + monoExe = getFileFromPropertyOrFileTree( + "MONO_EXE", false, { unityRootDirTree.matching { - include "**/nunit.framework.dll" + include (operatingSystem == OperatingSystem.WINDOWS ? + "**/bin/mono.bat" : "**/bin/mono") exclude unitySearchDirExcludes } }) - if (unityNUnitDll == null) { - logger.warn("Unity NUnit DLL not found, tests will not build.") - } - saveProperty("UNITY_NUNIT_PATH", unityNUnitDll, cacheProperties) + saveProperty("MONO_EXE", monoExe, cacheProperties) - // nunit-console is used to run tests. - nunitConsoleExe = getFileFromPropertyOrFileTree( - "NUNIT_CONSOLE_EXE", false, { - unityRootDirTree.matching { - include (operatingSystem == OperatingSystem.WINDOWS ? - "**/Mono/bin/nunit-console2.bat" : - "**/nunit-console2") - exclude unitySearchDirExcludes + // Get the mono distribution version. + def versionRegEx = /^.* version ([^ ]+) .*/ + def stdout = new ByteArrayOutputStream() + exec { + commandLine monoExe, "-V" + ignoreExitValue true + standardOutput = stdout + } + def monoVersionList = + stdout.toString().replace("\r\n", "\n").tokenize("\n").findResults { + def versionMatch = it =~ versionRegEx + if (versionMatch.matches()) { + return versionMatch.group(1) } - }) - if (nunitConsoleExe == null) { - logger.warn("nunit_console not found (NUNIT_CONSOLE_EXE) C# unit test " + - "execution will fail.") + return null + } + if (!monoVersionList) { + throw new StopActionException( + sprintf("Unable to determine mono version from %s", monoExe)) + } + monoVersion = monoVersionList[0] + + // Mono 5.x and above generate .pdb files that are compatible with visual + // studio as opposed to the mono-specific .pdb files. + pdbSupported = monoVersion.tokenize(".")[0].toInteger() >= 5 + + if (pdbSupported) { + logger.warn( + sprintf("Mono %s detected which will generate .pdb files " + + "that are not compatible with older versions of Unity. " + + "This can be fixed by compiling with Unity 5.6.", + monoVersion)) } - saveProperty("NUNIT_CONSOLE_EXE", nunitConsoleExe, cacheProperties) + + // Get the directory that contains this script. + scriptDirectory = buildscript.sourceFile.getParentFile() // It can take a while to search for build tools, so cache paths in the project // properties. - File projectPropertiesFile = new File("gradle.properties") + File projectPropertiesFile = new File(scriptDirectory,"gradle.properties") if (!projectPropertiesFile.exists()) { logger.info(sprintf("Saving %s to %s", cacheProperties.stringPropertyNames(), @@ -157,17 +179,6 @@ project.ext { cacheProperties.store(projectPropertiesFile.newWriter(), null) } - // Get the installed python version. - def pythonOutput = new ByteArrayOutputStream() - pythonVersion = "" - if (project.exec { - commandLine "python", "-V" - standardOutput = pythonOutput - errorOutput = pythonOutput - }.exitValue == 0) { - pythonVersion = pythonOutput.toString().trim().tokenize()[1] - } - // UnityAssetUploader required environment variables. unityUsername = findProperty("UNITY_USERNAME") unityPassword = findProperty("UNITY_PASSWORD") @@ -175,15 +186,76 @@ project.ext { unityPackagePath = findFileProperty("UNITY_PACKAGE_PATH", null) // Whether debug symbols should be included. - debugEnabled = findProperty("NDEBUG") == null - - // Get the directory that contains this script. - scriptDirectory = buildscript.sourceFile.getParentFile() + 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. @@ -192,38 +264,74 @@ project.ext { pluginStagingAreaDir = new File(buildDir, "staging") // Directory where the build plugin is unpacked to. pluginExplodedDir = new File(scriptDirectory, "exploded") + // Directory where the UPM package is unpacked to. + pluginUpmDir = new File(scriptDirectory, "upm") + // Base filename of the released plugin. + currentPluginBasename = "external-dependency-manager" + // Base UPM package name of the released plugin. + currentPluginUpmPackageName = "com.google.external-dependency-manager" // Where the exported plugin file is built before it's copied to the release // location. - pluginExportFile = new File(buildDir, "plugin.unitypackage") + pluginExportFile = new File(buildDir, currentPluginBasename + ".unitypackage") + // Where the exported UPM plugin file is built. + pluginUpmExportFile = new File(buildDir, + currentPluginUpmPackageName + "-" + pluginVersion + ".tgz") // Directory within the plugin staging area that just contains the plugin. - pluginAssetsDir = new File("Assets", "PlayServicesResolver") + pluginAssetsDir = new File("Assets", "ExternalDependencyManager") + // Directories within the staging area to export. + pluginExportDirs = [pluginAssetsDir, new File("Assets", "PlayServicesResolver")] // Directory within the plugin directory that contains the managed DLLs. pluginEditorDllDir = new File(pluginAssetsDir, "Editor") // Directory which contains the solution for all C# projects with a project in // each subdirectory. - pluginSourceDir = new File("source") + pluginSourceDir = new File(scriptDirectory, "source") // Solution which references all projects used by the plugin. - pluginSolutionFile = new File(pluginSourceDir, "JarResolver.sln") - // Version of the plugin (update this with CHANGELOG.md on each release). - pluginVersion = "1.2.128.0" - // Semantic version of the plugin. - pluginVersionSemVer = pluginVersion.tokenize(".")[0..2].join(".") - // Base filename of the released plugin. - currentPluginBasename = "play-services-resolver" + pluginSolutionFile = new File(pluginSourceDir, "ExternalDependencyManager.sln") // Versioned release plugin file. pluginReleaseFile = new File(scriptDirectory, sprintf("%s-%s.unitypackage", currentPluginBasename, pluginVersion)) + // Unversioned release plugin file. + pluginReleaseFileUnversioned = new File(scriptDirectory, + sprintf("%s-latest.unitypackage", + currentPluginBasename)) + + // Location of the Unity asset uploader application. + unityAssetUploaderDir = new File(pluginSourceDir, "UnityAssetUploader") + + // Location of the export_unity_package application. + exportUnityPackageDir = new File(pluginSourceDir, "ExportUnityPackage") + // Location of the import_unity_package application. + importUnityPackageDir = new File(pluginSourceDir, "ImportUnityPackage") // Common arguments used to execute Unity in batch mode. unityBatchModeArguments = ["-batchmode", "-nographics"] + // Common arguments used to execute Unity in interactive mode. + unityInteractiveModeArguments = ["-gvh_noninteractive"] // Extension for Unity asset metadata files. unityMetadataExtension = ".meta" - // Extension for debug files. - dllMdbExtension = ".mdb" + // Extensions for debug files. + symbolDatabaseExtension = pdbSupported ? ".pdb" : ".dll.mdb" // Changelog file. changelog = new File(scriptDirectory, "CHANGELOG.md") + pythonBootstrapDir = new File(externalToolsDir, "python_bootstrap") + pythonBinDir = new File(new File(pythonBootstrapDir, "python"), "bin") + // Python binary after it has been bootstrapped. + pythonExe = new File(pythonBinDir, "python3") + // Pip binary after it has been bootstrapped. + pipExe = new File(pythonBinDir, "pip3") + // Python packages required by export_unity_package.py + exportUnityPackageRequirements = ["absl-py", "PyYAML", "packaging"] + // Python packages required by gen_guids.py + genGuidRequirements = ["absl-py"] +} + +// Configure com.jetbrains.python.envs to bootstrap a Python install. +envs { + bootstrapDirectory = project.ext.pythonBootstrapDir + envsDirectory = new File(project.ext.buildDir, "python_envs") + python "python", "3.9.5" } /* @@ -250,6 +358,254 @@ public enum OperatingSystem { } } +/* + * Test Types + */ +public enum TestTypeEnum { + INTEGRATION, // Unity Integration Tests using IntegrationTester framework. + NUNIT, // Tests using NUnit framework + PYTHON, // Tests implemented in Python + GRADLE // Tests implemented in Gradle scripts + + // A complete set of all enums + private static HashSet completeSet; + + /* + * Get a complete set of all enums + * + * @returns A complete set of all enums + */ + private static HashSet getCompleteSet() { + if (completeSet == null) { + completeSet = new HashSet() + for (TestTypeEnum type : TestTypeEnum.values()) { + completeSet.add(type); + } + } + return completeSet.clone() + } + + /* + * Convert a list of strings to a set of enums + * + * @param values A list of case-insensitive strings to convert to enum. + * @param completeSetWhenEmpty Whether to return a complete set if the list + * is empty. + * + * @returns A set of enums + */ + public static HashSet toSet( + Collection values, Boolean completeSetWhenEmpty) { + def result = new HashSet() + if ( values == null) { + return completeSetWhenEmpty ? getCompleteSet() : result; + } + for (String value : values) { + def trimmed = value.trim().toUpperCase() + if (!trimmed.isEmpty()) { + result.add(TestTypeEnum.valueOf(trimmed)) + } + } + if (result.size() == 0) { + result = completeSetWhenEmpty ? getCompleteSet() : result; + } + return result + } +} + +/* + * Test Modules + */ +public enum TestModuleEnum { + ANDROIDRESOLVER, // Tests for Android Resolver + VERSIONHANDLER, // Tests for Version Handler + IOSRESOLVER, // Tests for iOS Resolver + PACKAGEMANAGER, // Tests for Package Manager + CORE, // Tests for reusable C# libraries + TOOL // Tests for build/packaging/release tools + + // A complete set of all enums + private static HashSet completeSet; + + /* + * Get a complete set of all enums + * + * @returns A complete set of all enums + */ + private static HashSet getCompleteSet() { + if (completeSet == null) { + completeSet = new HashSet() + for (TestModuleEnum type : TestModuleEnum.values()) { + completeSet.add(type); + } + } + return completeSet.clone() + } + + /* + * Convert a list of strings to a set of enums + * + * @param values A list of case-insensitive strings to convert to enum. + * @param completeSetWhenEmpty Whether to return a complete set if the list + * is empty. + * + * @returns A set of enums + */ + public static HashSet toSet( + Collection values, Boolean completeSetWhenEmpty) { + def result = new HashSet() + if ( values == null) { + return completeSetWhenEmpty ? getCompleteSet() : result; + } + for (String value : values) { + def trimmed = value.trim().toUpperCase() + if (!trimmed.isEmpty()) { + result.add(TestModuleEnum.valueOf(trimmed)) + } + } + if (result.size() == 0) { + result = completeSetWhenEmpty ? getCompleteSet() : result; + } + return result + } +} + +/* + * Determine whether the test should be run given the filter parameters, + * the current test type and the current test module + * + * @param type Type of the test. + * @param module Module of the test. + * + * @returns True if the test should be run by the given the filters. + */ +boolean shouldTestRunWithFilters(TestTypeEnum type, TestModuleEnum module) { + project.ext.actualIncludeTestTypes.contains(type) && + project.ext.actualIncludeTestModules.contains(module) +} + +/* + * Set the test type and module to the given task. + * + * @param task The task to set the properties to. + * @param type Type of the test. + * @param module Module of the test. + */ +void setTestProperties(Task task, TestTypeEnum type, TestModuleEnum module) { + task.ext.testType = type + task.ext.testModule = module +} + +/* + * Unit test for TestTypeEnum + */ +task(testTestTypeEnum) { task -> + setTestProperties(task, TestTypeEnum.GRADLE, TestModuleEnum.TOOL) + doFirst { + ReportTestStarted(task) + } + doLast { + def expectedTestTypeEnumCompleteSet = + new HashSet([ + TestTypeEnum.INTEGRATION, + TestTypeEnum.NUNIT, + TestTypeEnum.PYTHON, + TestTypeEnum.GRADLE]) + def expectedEmptySet = new HashSet() + def expectedPythonOnlySet = new HashSet([TestTypeEnum.PYTHON]) + def expectedPythonAndIntegrationSet = new HashSet([ + TestTypeEnum.PYTHON, + TestTypeEnum.INTEGRATION]) + + assert TestTypeEnum.getCompleteSet().equals( + expectedTestTypeEnumCompleteSet) + assert TestTypeEnum.toSet([], false).equals( + expectedEmptySet) + assert TestTypeEnum.toSet([], true).equals( + expectedTestTypeEnumCompleteSet) + assert TestTypeEnum.toSet(["python"], false).equals( + expectedPythonOnlySet) + assert TestTypeEnum.toSet(["python"], true).equals( + expectedPythonOnlySet) + assert TestTypeEnum.toSet(["PYTHON"], false).equals( + expectedPythonOnlySet) + assert TestTypeEnum.toSet(["PyThOn"], false).equals( + expectedPythonOnlySet) + assert TestTypeEnum.toSet(["Python", "Integration"], false).equals( + expectedPythonAndIntegrationSet) + assert TestTypeEnum.toSet(["Integration", "Python"], false).equals( + expectedPythonAndIntegrationSet) + assert TestTypeEnum.toSet( + ["Integration", "Python", "Gradle", "NUnit"], false).equals( + expectedTestTypeEnumCompleteSet) + + EvaluateTestResult(task) + } +} + +/* + * Unit test for TestModuleEnum + */ +task(testTestModuleEnum) { task -> + setTestProperties(task, TestTypeEnum.GRADLE, TestModuleEnum.TOOL) + doFirst { + ReportTestStarted(task) + } + doLast { + def expectedTestModuleEnumCompleteSet = + new HashSet([ + TestModuleEnum.ANDROIDRESOLVER, + TestModuleEnum.VERSIONHANDLER, + TestModuleEnum.IOSRESOLVER, + TestModuleEnum.PACKAGEMANAGER, + TestModuleEnum.CORE, + TestModuleEnum.TOOL]) + def expectedEmptySet = new HashSet() + def expectedToolOnlySet = new HashSet([TestModuleEnum.TOOL]) + def expectedToolAndAndroidResolverSet = new HashSet([ + TestModuleEnum.TOOL, + TestModuleEnum.ANDROIDRESOLVER]) + + assert TestModuleEnum.getCompleteSet().equals( + expectedTestModuleEnumCompleteSet) + assert TestModuleEnum.toSet([], false).equals( + expectedEmptySet) + assert TestModuleEnum.toSet([], true).equals( + expectedTestModuleEnumCompleteSet) + assert TestModuleEnum.toSet(["tool"], false).equals( + expectedToolOnlySet) + assert TestModuleEnum.toSet(["tool"], true).equals( + expectedToolOnlySet) + assert TestModuleEnum.toSet(["TOOL"], false).equals( + expectedToolOnlySet) + assert TestModuleEnum.toSet(["TooL"], false).equals( + expectedToolOnlySet) + assert TestModuleEnum.toSet(["Tool", "AndroidResolver"], false).equals( + expectedToolAndAndroidResolverSet) + assert TestModuleEnum.toSet(["AndroidResolver", "Tool"], false).equals( + expectedToolAndAndroidResolverSet) + assert TestModuleEnum.toSet([ + "AndroidResolver", + "VersionHandler", + "iOSResolver", + "PackageManager", + "Core", + "Tool"], false).equals( + expectedTestModuleEnumCompleteSet) + EvaluateTestResult(task) + } +} + +/* + * Test session + */ +public class TestSession { + public String name; + public TestTypeEnum type; + public TestModuleEnum module; + public Boolean isPassed; +} + /* * Search the path variable for an executable file. * @@ -359,24 +715,6 @@ void saveProperty(String name, value, Properties properties) { logger.info(sprintf("%s: %s", name, value)) } -/* - * Make sure the NUnit DLL property is set. - */ -void checkNUnitDllPath() { - if (project.ext.unityNUnitDll == null) { - throw new StopActionException("NUnit DLL not found.") - } -} - -/* - * Make sure the NUnit console property is set. - */ -void checkNUnitConsolePath() { - if (project.ext.nunitConsoleExe == null) { - throw new StopActionException("NUnit Console not found.") - } -} - /* * Removes the extension from a filename. * @@ -387,9 +725,14 @@ void checkNUnitConsolePath() { */ List splitFilenameExtension(File fileObj) { String filename = fileObj.name - int extensionIndex = - (filename - project.ext.unityMetadataExtension - - project.ext.dllMdbExtension).lastIndexOf(".") + String trimmedFilename = filename + if (trimmedFilename.endsWith(project.ext.unityMetadataExtension)) { + trimmedFilename -= project.ext.unityMetadataExtension + } + if (trimmedFilename.endsWith(".dll.mdb")) { + trimmedFilename -= ".mdb" + } + int extensionIndex = trimmedFilename.lastIndexOf(".") if (extensionIndex < 0) return [filename, ""] String basename = filename.substring(0, extensionIndex) String extension = filename.substring(extensionIndex) @@ -398,49 +741,83 @@ List splitFilenameExtension(File fileObj) { /* * Construct the name of a versioned asset from the source filename and version - * string. The encoded string takes the form + * string. If fullVersionPrefix is true, the encoded string takes the form + * ${filename}_version-${version}.${extension} + * if fullVersionPrefix is false, the string takes the form * ${filename}_v${version}.${extension} * where extension is derived from the specified filename. * * @param fileObj File to add version to. + * @param fullVersionPrefix if true uses the "_version-" otherwise uses "_v-". + * @param postfix Optional string to add before the extensioon. + * @param useVersionDir If true, place the file to be under a folder named after + * the version number, instead of changing the filename. * * @returns File which includes an encoded version. */ -File versionedAssetFile(File fileObj) { +File versionedAssetFile(File fileObj, Boolean fullVersionPrefix, + String postfix, Boolean useVersionDir) { String basename String extension (basename, extension) = splitFilenameExtension(fileObj) // Encode the DLL version and target names into the DLL in the form... - // ${dllname}_v${version}.dll + // ${dllname}_version-${version}.dll String targetName = basename String version = project.ext.pluginVersion + File dllDir = fileObj.parent != null ? new File(fileObj.parent) : + new File() if (!(version == null || version.isEmpty())) { - targetName += "_v" + version + if (useVersionDir) { + dllDir = new File(dllDir, version) + } else { + targetName += (fullVersionPrefix ? "_version-" : "_v") + version + } } - String filename = targetName + extension - return fileObj.parent != null ? new File(fileObj.parent, filename) : - new File(filename) + String filename = targetName + postfix + extension + return new File(dllDir, filename) } /* - * Remove the version component from a filename. + * Remove the version component from a path. (Both its filename and its parent + * folder) * * @param fileObj File to remove version from. * * @returns File with removed version string. */ File unversionedAssetFile(File fileObj) { + // Remove the version postfix. Ex. + // "ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.166.dll" -> + // "ExternalDependencyManager/Editor/Google.IOSResolver.dll" String basename String extension (basename, extension) = splitFilenameExtension(fileObj) - def versionRegEx = /^(.*)_(v[^v]+)$/ - def versionMatch = basename =~ versionRegEx + def versionRegExFull = /^(.*)_(version-[^-]+)$/ + def versionRegExShort = /^(.*)_(v[^v]+)$/ + def versionMatch = basename =~ versionRegExShort if (versionMatch.matches()) { basename = versionMatch.group(1) + } else { + versionMatch = basename =~ versionRegExFull + if (versionMatch.matches()) { + basename = versionMatch.group(1) + } } String filename = basename + extension - return fileObj.parent != null ? - new File(fileObj.parent, filename) : new File(filename) + + // Remove the version folder as well. Ex. + // "ExternalDependencyManager/Editor/1.2.166/Google.IOSResolver.dll" -> + // "ExternalDependencyManager/Editor/Google.IOSResolver.dll" + def versionFolderRegEx = /^[0-9]+\.[0-9]+\.[0-9]+$/ + File parent = fileObj.parent != null ? new File(fileObj.parent) : null + if (parent != null) { + String parentFolder = parent.name + def folderMatch = parentFolder =~ versionFolderRegEx + if (folderMatch.matches()) { + parent = parent.parent != null ? new File(parent.parent) : null + } + } + return parent != null ? new File(parent, filename) : new File(filename) } /* @@ -517,11 +894,12 @@ File copyAssetMetadataFile(File sourceFile, File targetFile) { if (!sourceFile.name.endsWith(project.ext.unityMetadataExtension)) { return copyFile(sourceFile, targetFile) } - String[] lines = sourceFile.text.split("\n") + String[] lines = sourceFile.text.tokenize("\n") // Parse the existing version from the asset metadata. def folderAssetRegEx = /^folderAsset:\s+yes\s*$/ def versionRegEx = /^(-\s+gvh_version-)([a-zA-Z0-9.]+)\s*$/ + def exportPathRegEx = /^(-\s+gvhp_exportpath-)(.*)$/ Boolean isFolder = false String currentVersion = "" lines.each { String line -> @@ -533,8 +911,11 @@ File copyAssetMetadataFile(File sourceFile, File targetFile) { isFolder = true } } + Boolean isNotVersioned = (isFolder || + targetFile.name.startsWith( + "play-services-resolver")) // Ignore folder assets, they don't need to be versioned. - if (isFolder) return copyFile(sourceFile, targetFile) + if (isNotVersioned) return copyFile(sourceFile, targetFile) Boolean versionChanged = currentVersion != project.ext.pluginVersion List outputLines = [] @@ -542,7 +923,9 @@ File copyAssetMetadataFile(File sourceFile, File targetFile) { if (versionChanged) { def guidMatch = (line =~ /^(guid:)\s+(.*)/) def versionLabelMatch = (line =~ versionRegEx) - if (guidMatch.matches() && sourceFile.name != targetFile.name) { + 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. @@ -550,11 +933,16 @@ File copyAssetMetadataFile(File sourceFile, File targetFile) { "%s %s", guidMatch.group(1), java.util.UUID.randomUUID().toString().replace("-", "")) - } - else if (versionLabelMatch.matches()) { + } else if (versionLabelMatch.matches()) { // Update the version metadata for the asset. line = sprintf("%s%s", versionLabelMatch.group(1), project.ext.pluginVersion) + } else if (exportPathMatch.matches()) { + // Update the export path of the asset. + line = sprintf("%s%s", exportPathMatch.group(1), + targetFile.path.replaceFirst(/.*\/Assets\//, "")) + line = line.substring(0, line.length() - + project.ext.unityMetadataExtension.length()) } } outputLines.add(line) @@ -592,7 +980,18 @@ Task createXbuildTask(String taskName, String taskDescription, Iterable outputFilesInBinaryOutputDir = outputFiles.collect { return new File(binaryOutputDir, it.path) } + + if (project.ext.debugEnabled) { + outputFilesInBinaryOutputDir += outputFilesInBinaryOutputDir.findResults { + return it.name.endsWith(".dll") ? + new File(it.parentFile.path, + splitFilenameExtension(it)[0] + + project.ext.symbolDatabaseExtension) : null + } + } + Iterable dependsOnTasks = dependsOn ? dependsOn : [] + Iterable compileTaskDependencies = dependsOnTasks.clone() Iterable patchVersionFilesTasks = inputFiles.findResults { if (it.name == "VersionNumber.cs") { File versionFile = it @@ -621,31 +1020,35 @@ Task createXbuildTask(String taskName, String taskDescription, return patchVersionTask } } + if (patchVersionFilesTasks) { + compileTaskDependencies += patchVersionFilesTasks + } + Task task = tasks.create(name: taskName, description: taskDescription, - type: Exec, - dependsOn: patchVersionFilesTasks ? - patchVersionFilesTasks : - dependsOnTasks).with { + type: Task, + dependsOn: compileTaskDependencies).with { inputs.files inputFiles outputs.files files(outputFilesInBinaryOutputDir) - executable project.ext.xbuildExe - workingDir projectToBuild.parentFile.absolutePath - args ([sprintf("/target:%s", target), - sprintf("/property:UnityHintPath=%s", - project.ext.unityDllPath.absolutePath), - sprintf("/property:UnityIosPath=%s", - project.ext.unityIosPath.absolutePath), - sprintf("/property:NUnityHintPath=%s", - project.ext.unityNUnitDll ? - project.ext.unityNUnitDll.absolutePath: ""), - sprintf("/property:BaseIntermediateOutputPath=%s%s", - intermediatesDir.absolutePath, - File.separator), - sprintf("/property:OutputPath=%s%s", - binaryOutputDir.absolutePath, - File.separator), - projectToBuild.absolutePath]) + doLast { + exec { + executable project.ext.xbuildExe + workingDir projectToBuild.parentFile.absolutePath + args ([sprintf("/target:%s", target), + sprintf("/property:UnityHintPath=%s", + project.ext.unityDllPath.absolutePath), + sprintf("/property:UnityIosPath=%s", + project.ext.unityIosPath.absolutePath), + sprintf("/property:BaseIntermediateOutputPath=%s%s", + intermediatesDir.absolutePath, + File.separator), + sprintf("/property:OutputPath=%s%s", + binaryOutputDir.absolutePath, + File.separator), + "/verbosity:quiet", + projectToBuild.absolutePath]) + } + } } task.ext.buildDir = outputDir return task @@ -675,25 +1078,21 @@ Task createBuildPluginDllTask(String componentName, File projectDir = new File(project.ext.pluginSourceDir, projectName) File projectBuildDir = new File(project.ext.buildDir, projectName) - List buildFiles = [new File(assemblyDllBasename)] - if (project.ext.debugEnabled) { - buildFiles += [new File(assemblyDllBasename + project.ext.dllMdbExtension)] - } - // Compile the C# project. Task compileTask = createXbuildTask( sprintf("compile%s", componentName), sprintf("Compile %s", projectName), project.ext.pluginSolutionFile, projectName, fileTree(new File(projectDir, "src")), projectBuildDir, - buildFiles, dependsOn) + [new File(assemblyDllBasename)], dependsOn) compileTask.ext.componentName = componentName // Template metadata for the built DLL. - Iterable assemblyDllMetaFiles = buildFiles.collect { - new File(new File(project.ext.pluginTemplateDir, - project.ext.pluginEditorDllDir.path), - it.name + project.ext.unityMetadataExtension) - } + Iterable assemblyDllMetaFiles = + compileTask.outputs.files.collect { + new File(new File(project.ext.pluginTemplateDir, + project.ext.pluginEditorDllDir.path), + it.name + project.ext.unityMetadataExtension) + } // Optionally map unversioned to versioned filenames. Iterable unversionedFiles = files(compileTask.outputs.files, assemblyDllMetaFiles) @@ -708,7 +1107,7 @@ Task createBuildPluginDllTask(String componentName, return [ unversionedFile.path, versionDll ? - versionedAssetFile(unversionedOutputFile) : + versionedAssetFile(unversionedOutputFile, false, "", true) : unversionedOutputFile] } @@ -732,7 +1131,7 @@ Task createBuildPluginDllTask(String componentName, copyTask.ext.componentName = componentName // Generate a clean target for this project. - Task cleanTask = tasks.create(name: sprintf("clean%s", projectName), + Task cleanTask = tasks.create(name: sprintf("clean%s", componentName), description: sprintf("Clean %s plugin DLL", projectName), type: Delete).with { delete (files(compileTask.outputs.files, @@ -752,33 +1151,92 @@ Task createBuildPluginDllTask(String componentName, } /* - * Create a Nunit test task. + * Creates a task which compiles and run an NUnit test. + * + * @param taskName Name of the test. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param unityProjectDir Directory containing a assets to copy into the + * integration test project. + * @param arguments Additional arguments for Unity when running the integration + * test. + * @param testType Type of the test + * @param testModule Module of the test + */ +Task createUnityNUnitTest(String taskName, + String description, + Iterable dependsOn, + File unityProjectDir, + Iterable arguments, + TestTypeEnum testType, + TestModuleEnum testModule) { + createUnityTestBatchAndNonBatch( + taskName, + description, + ["buildPlugin"], + unityProjectDir, + [], + arguments + [ + "-runTests", + "-batchmode", + "-testResults", "results.xml", + "-testPlatform", "EditMode" + ], null, testType, testModule, + true, + new File(new File(new File( + project.ext.scriptDirectory, + "test_resources"), + "nunit_upm"), + "manifest.json")) +} + +/* + * Create a task to run an executable. * * @param name Name of the task. * @param description Description of the task. - * @param testDll Test DLL to execute. The log file will be placed in the - * parent directory of this DLL path. + * @param dependsOn Tasks this depends upon. + * @param executable Executable to run. + * @param arguments Arguments for the executable. + * @param continueOnFail Whether to ignore non-zero return code and continue. + * + * @returns Task which runs the specified executable. + */ +Task createExecTask(String name, String description, + Iterable dependsOn, File executableToRun, + Iterable arguments, Boolean continueOnFail = false) { + Task execTask = tasks.create(name: name, + description: description, + type: Exec, + dependsOn: dependsOn) + execTask.with { + executable executableToRun + args arguments + ignoreExitValue continueOnFail + } + return execTask +} + +/* + * Create a task that does nothing. + * + * @param taskName Name of the task to create. + * @param summary Description of the task. * @param dependsOn Dependencies of the new task. * - * @returns Task which executes nunit-console. + * @returns Task that does nothing. */ -Task createNUnitTask(String name, String description, File testDll, +Task createEmptyTask(String taskName, String summary, Iterable dependsOn) { - File logFileDir = testDll.parentFile.parentFile - File logFile = new File(logFileDir, name + ".log") - File xmlLogFile = new File(logFileDir, name + ".xml") - return tasks.create(name: name, - description: description, - type: Exec, - dependsOn: dependsOn).with { - workingDir project.ext.pluginSourceDir - outputs.files logFile - executable project.ext.nunitConsoleExe - args ([sprintf("-output:%s", logFile.absolutePath), - sprintf("-xml:%s", xmlLogFile.absolutePath), - testDll.absolutePath]) - doFirst { checkNUnitConsolePath() } + Task emptyTask = tasks.create(name: taskName, + description: sprintf("(disabled) %s", summary), + dependsOn: dependsOn) + emptyTask.with { + doLast { + logger.info(sprintf("%s disabled", taskName)) + } } + return emptyTask } /* @@ -794,6 +1252,9 @@ Task createNUnitTask(String name, String description, File testDll, * @param projectContainerDir Directory that contains the project directory. * @param arguments Command line arguments to pass to Unity. * @param batchMode Whether to run Unity in batch mode. + * @param createTaskClosure Optional task used to start Unity, this must + * conform to createExecTask() + * @param continueOnFail Whether to ignore non-zero return code and continue. * * @returns Task which executes Unity. * The following extended properties are set on the task: @@ -805,29 +1266,53 @@ Task createNUnitTask(String name, String description, File testDll, Task createUnityTask(String taskName, String summary, Iterable dependsOn, String projectName, File projectContainerDir, Iterable arguments, - Boolean batchMode) { + Boolean batchMode, createTaskClosure, + Boolean continueOnFail = false) { Boolean createProject = summary == "create" File logFile = new File(projectContainerDir, sprintf("%s_%s.log", projectName, summary)) File projectDir = new File(projectContainerDir, projectName) List executeArguments = [] - if (batchMode) executeArguments += unityBatchModeArguments + + if (batchMode) { + executeArguments += project.ext.unityBatchModeArguments + } else { + executeArguments += project.ext.unityInteractiveModeArguments + } executeArguments += [ "-logFile", logFile.absolutePath, createProject ? "-createProject" : "-projectPath", projectDir.absolutePath] if (createProject) executeArguments += ["-quit"] executeArguments += arguments - Task unityTask = tasks.create(name: taskName, - description: sprintf( - "Run Unity to %s (project: %s)", - summary, projectName), - type: Exec, - dependsOn: dependsOn).with { - inputs.files fileTree(projectDir) + if (!createTaskClosure) { + createTaskClosure = { + String name, String description, Iterable depends, + File executable, Iterable args, Boolean contOnFail-> + return createExecTask(name, + description, + depends, + executable, + args, + contOnFail) + } + } + + Task unityTask + if (!batchMode && !project.ext.interactiveModeTestsEnabled) { + unityTask = createEmptyTask(taskName, summary, dependsOn) + } else { + unityTask = createTaskClosure(taskName, + sprintf( + "Run Unity to %s (project: %s)", + summary, projectName), + dependsOn, + project.ext.unityExe, + executeArguments, + continueOnFail) + } + unityTask.with { outputs.files files(logFile) - executable project.ext.unityExe - args executeArguments } unityTask.ext.projectDir = projectDir unityTask.ext.containerDir = projectContainerDir @@ -853,7 +1338,7 @@ Task createUnityTask(String taskName, String summary, Task createUnityProjectTask(String taskName, Iterable dependsOn, String projectName, File projectContainerDir) { return createUnityTask(taskName, "create", dependsOn, projectName, - projectContainerDir, [], true).with { + projectContainerDir, [], true, null).with { doFirst { // Clean / create the output directory. delete ext.containerDir @@ -862,35 +1347,6 @@ Task createUnityProjectTask(String taskName, Iterable dependsOn, } } -/* - * Setup a Unity project for a testing task and import the plugin in - * preparation for testing. - * - * @param taskName Name of the test task. - * @param dependsOn Dependencies of the new task. - * @param projectName Name of the Unity project to create. - * @param batchMode Whether to run Unity in batch mode. - * - * @returns Task which sets up a Unity project. - * The following extended properties are set on the task: - * - ext.projectDir: Directory of the created Unity project. - * - ext.containerDir: Directory which contains the Unity project and the logs. - * - ext.logFile: Unity log file for the import operation. - */ -Task createSetupUnityProjectTask(String taskName, Iterable dependsOn, - String projectName, Boolean batchMode) { - File outputDir = new File(project.ext.testDir, projectName) - Task createProject = createUnityProjectTask( - sprintf("create%s", taskName), dependsOn, projectName, outputDir) - createProject.with { - inputs.files files(project.ext.pluginExportFile) - } - return createUnityTask( - taskName, "import_plugin", [createProject], projectName, outputDir, - ["-importPackage", project.ext.pluginExportFile.absolutePath, - "-quit"], batchMode) -} - /* * Creates a task which runs a test with the Unity plugin. * @@ -902,36 +1358,147 @@ Task createSetupUnityProjectTask(String taskName, Iterable dependsOn, * @param additionalArguments Additional arguments to pass to Unity when * executing the test. * @param batchMode Whether to execute Unity in batch mode. + * @param editorAssets Files to copy into the Assets/Editor folder in the + * project. + * @param createTaskClosure Optional task used to start Unity, this must + * conform to tasks.create(name, description, type, dependsOn). + * @param testType Type of the test + * @param testModule Module of the test + * @param enableDlls Whether to enable dlls through Version Handlers before tests. + * @param upm_package_manifest Specify UPM package manifest (manifest.json) * * @returns Test task. */ Task createUnityTestTask(String taskName, String description, Iterable dependsOn, File testScriptsDir, + Iterable editorAssets, Iterable additionalArguments, - Boolean batchMode) { - List inputFiles = fileTree(testScriptsDir).collect { - new File(it.absolutePath - testScriptsDir.absolutePath) + Boolean batchMode, createTaskClosure, + TestTypeEnum testType, TestModuleEnum testModule, + Boolean enableDlls = false, + File upm_package_manifest = null) { + List testScripts = [] + if (testScriptsDir) { + testScripts = fileTree(testScriptsDir).collect { + new File(it.absolutePath.substring( + testScriptsDir.absolutePath.length() + 1)) + } } + // Setup the Unity project. - Task setupTestProject = createSetupUnityProjectTask( - sprintf("setup%s", taskName), dependsOn, taskName, batchMode) - setupTestProject.with { - inputs.files inputFiles + List setupTasks = [] + File testOutputDir = new File(project.ext.testDir, taskName) + Task createProject = createUnityProjectTask( + sprintf("createsetup%s", taskName), dependsOn, taskName, testOutputDir) + createProject.with { task -> + inputs.files files(project.ext.pluginExportFile) + setTestProperties(task, testType, testModule) + } + setupTasks += [createProject] + + Task setupTestProject = createUnityTask( + sprintf("setup%s", taskName), "import_plugin", [createProject], taskName, + testOutputDir, ["-importPackage", project.ext.pluginExportFile.absolutePath, + "-quit"], batchMode, null) + setupTestProject.with { task -> + inputs.files (testScripts + editorAssets) + setTestProperties(task, testType, testModule) + } + setupTasks += [setupTestProject] + + Task versionHandlerUpdate; + if (enableDlls) { + File updaterScriptDir = new File(new File(new File( + setupTestProject.ext.projectDir, + "Assets"), + "Editor"), + "VersionHandlerUpdater") + versionHandlerUpdate = createUnityTask( + sprintf("enableDlls%s", taskName), "enable_dlls", [setupTestProject], taskName, + testOutputDir, [], batchMode, null) + versionHandlerUpdate.with { task -> + setTestProperties(task, testType, testModule) + doFirst { + copy { + from new File(new File(new File( + project.ext.scriptDirectory, + "test_resources"), + "version_handler_update"), + "VersionHandlerUpdater.cs") + into updaterScriptDir + } + } + doLast { + delete updaterScriptDir + } + } + setupTasks += [versionHandlerUpdate] } + List copyTasks = [] + // Task which copies test scripts into the project. - Task copyTestScripts = createCopyFilesTask( - sprintf("copyScriptsFor%s", taskName), - sprintf("Copy the test scripts into the %s Unity project.", taskName), - inputFiles, testScriptsDir, setupTestProject.ext.projectDir, - [setupTestProject], null) + if (testScriptsDir) { + Task copyTestScripts = createCopyFilesTask( + sprintf("copyScriptsFor%s", taskName), + sprintf("Copy the test scripts into the %s Unity project.", taskName), + testScripts, testScriptsDir, setupTestProject.ext.projectDir, + setupTasks, null) + copyTestScripts.with { task -> + setTestProperties(task, testType, testModule) + } + copyTasks += [copyTestScripts] + } + + if (upm_package_manifest != null) { + Task copyPackageManifest = tasks.create( + name: sprintf("copyPackageManifestFor%s", taskName), + description: sprintf("Copy the package manifest into the %s Unity project.", + taskName), + type: Copy, + dependsOn: setupTasks) + copyPackageManifest.with { task -> + from upm_package_manifest + into new File(setupTestProject.ext.projectDir, "Packages") + setTestProperties(task, testType, testModule) + } + copyTasks += [copyPackageManifest] + } + + // Task which copies editor scripts into the project. + Task copyEditorAssets = tasks.create( + name: sprintf("copyEditorAssetsFor%s", taskName), + description: sprintf("Copy the editor assets into the %s Unity project.", + taskName), + type: Copy, + dependsOn: setupTasks) + copyEditorAssets.with { task -> + from editorAssets + into new File(new File(setupTestProject.ext.projectDir, "Assets"), + "Editor") + setTestProperties(task, testType, testModule) + } + copyTasks += [copyEditorAssets] // Create the test task. - Task testTask = createUnityTask(taskName, "test", [copyTestScripts], + Task testTask = createUnityTask(taskName, "test", + copyTasks, setupTestProject.ext.projectDir.name, setupTestProject.ext.containerDir, - additionalArguments, batchMode) + additionalArguments, batchMode, + createTaskClosure, + true) testTask.description = description + testTask.with { task -> + finalizedBy "reportAllTestsResult" + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + setTestProperties(task, testType, testModule) + } // Create a clean task Task cleanTestTask = tasks.create(name: sprintf("clean%s", taskName), @@ -952,98 +1519,373 @@ Task createUnityTestTask(String taskName, String description, * @param dependsOn Dependencies of the new task. * @param testScriptsDir Directory containing scripts to copy into the test * project and execute for testing. - * @param batchMode Whether to execute Unity in batch mode. + * @param editorAssets Files to copy into the Assets/Editor folder in the + * project. + * @param additionalArguments Additional arguments to pass to Unity. + * @param createTaskClosure Optional task used to start Unity, this must + * conform to tasks.create(name, description, type, dependsOn). + * @param testType Type of the test + * @param testModule Module of the test + * @param enableDlls Whether to enable dlls through Version Handlers before tests. + * @param upm_package_manifest Specify UPM package manifest (manifest.json) * * @returns Test task. */ Task createUnityTestBatchAndNonBatch(String taskName, String description, Iterable dependsOn, File testScriptsDir, - Iterable additionalArguments) { + Iterable editorAssets, + Iterable additionalArguments, + createTaskClosure, + TestTypeEnum testType, + TestModuleEnum testModule, + Boolean enableDlls = false, + File upm_package_manifest = null) { return tasks.create(name: taskName, description: description, dependsOn: [ createUnityTestTask( sprintf("%sBatchMode", taskName), sprintf("%s (Batch Mode)", description), - dependsOn, testScriptsDir, additionalArguments, true), + dependsOn, testScriptsDir, editorAssets, + additionalArguments, true, createTaskClosure, + testType, testModule, + enableDlls, upm_package_manifest), createUnityTestTask( sprintf("%sInteractiveMode", taskName), sprintf("%s (Interactive Mode)", description), - dependsOn, testScriptsDir, additionalArguments, - true)]) + dependsOn, testScriptsDir, editorAssets, + additionalArguments, false, createTaskClosure, + testType, testModule, + enableDlls, upm_package_manifest)]) } -Task compileResolverLibTests = createXbuildTask( - "compileResolverLibTests", - "Compile tests for the deprecated Jar Resolver library.", - project.ext.pluginSolutionFile, "JarResolverTests", - fileTree(new File(new File(project.ext.pluginSourceDir, - "JarResolverLib"), "src")), - new File(project.ext.testDir, "ResolverLibTests"), - [new File("JarResolverTests.dll")], []).with { - doFirst { checkNUnitDllPath() } +/* + * Install Python packages. + * + * @param taskName Name of the task to create. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param packages Packages to install. + * + * @returns Task which executes pip to install packages + */ +Task createInstallPythonPackageTask(String taskName, String description, + Iterable dependsOn, + Iterable packages) { + Task installPythonPackageTask = tasks.create( + name: taskName, + description: sprintf("Run Pip to %s", description), + type: Exec, + dependsOn: dependsOn + ["build_envs"]).with { + executable project.ext.pipExe + args (["-q", "install"] + packages) + } } -Task testResolverLibTests = createNUnitTask( - "testResolverLibTests", - "Runs the tests for the deprecated Jar Resolver library", - compileResolverLibTests.outputs.files[0], - [compileResolverLibTests]) - -task cleanResolverLibTests() { - description "Clean test output for the deprecated Jar Resolver library" - doLast { delete files(compileResolverLibTests.ext.buildDir, - testResolverLibTests.outputs.files) } +/* + * Create a task to execute Python. + * + * @param taskName Name of the task to create. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param script Python script to run. + * @param arguments Command line arguments to pass to the Python script. + * @param packages Optional Python packages to install. + * @param continueOnFail Whether to ignore non-zero return code and continue. + * + * @returns Task which executes Python. + */ +Task createPythonTask(String taskName, String description, + Iterable dependsOn, + File script, Iterable arguments, + Iterable packages, + Boolean continueOnFail = false) { + List installPackagesTask = [] + if (packages) { + installPackagesTask = [ + createInstallPythonPackageTask( + taskName + "InstallPipPackages", + sprintf("install packages %s for %s", packages.toString(), taskName), + [], + packages) + ] + } + Task pythonTask = tasks.create( + name: taskName, + description: sprintf("Run Python to %s", description), + type: Exec, + dependsOn: (dependsOn + installPackagesTask + ["build_envs"])).with { + ignoreExitValue continueOnFail + executable project.ext.pythonExe + args ([script.absolutePath] + arguments) + } + return pythonTask } -Task compilePackageManagerTests = createXbuildTask( - "compilePackageManagerTests", - "Compile tests for the package manager.", - project.ext.pluginSolutionFile, "PackageManagerTests", - fileTree(new File(new File(project.ext.pluginSourceDir, - "PackageManagerTests"), "src")), - new File(project.ext.testDir, "PackageManagerTests"), - [new File("PackageManagerTests.dll")], []).with { - doFirst { checkNUnitDllPath() } +/* + * 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 testPackageManagerTests = createNUnitTask( - "testPackageManagerTests", - "Runs tests for the Package Manager", - compilePackageManagerTests.outputs.files[0], - [compilePackageManagerTests]).with { - environment (["TEST_DATA_DIR": - (new File(new File(project.ext.pluginSourceDir, - "PackageManagerTests"), - "testData")).absolutePath]) +Task createGenGuidTask(String taskName, + String description, + File guidsFile, + String pluginVersion, + Iterable guidPath) { + File genGuidScript = new File(project.ext.exportUnityPackageDir, + "gen_guids.py") + Task genGuidTask = createPythonTask( + taskName, + description, + [], + genGuidScript, + ["--version", pluginVersion, + "--guids_file", guidsFile] + + guidPath, + genGuidRequirements) + genGuidTask.with { + inputs.files ([guidsFile] + + [genGuidScript]) + } + return genGuidTask } -task cleanPackageManagerTests() { - description "Clean Package Manager tests" - doLast { delete files(compilePackageManagerTests.ext.buildDir, - testPackageManagerTests.outputs.files) } -} -task testDownloadArtifacts(type: GradleBuild) { +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/PlayServicesResolver/scripts" + dir "source/AndroidResolver/scripts" + setTestProperties(task, TestTypeEnum.GRADLE, TestModuleEnum.ANDROIDRESOLVER) + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + finalizedBy "reportAllTestsResult" } -task testPackageUploader(type: Exec) { - if (project.ext.pythonVersion && - project.ext.pythonVersion.tokenize(".")[0].toInteger() >= 3) { - def outputDir = buildDir - def script = "source/UnityAssetUploader/unity_asset_uploader_test.py" - inputs.file script - outputs.dir outputDir - commandLine "python", script - workingDir = projectDir + +/* + * 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 { - commandLine "echo", "Skipping testPackageUploaderTests, requires Python 3." + throw new GradleException( + sprintf("Unsupported test class %s", testTask.class.simpleName)) + } + + if (succeeded) { + println sprintf("Test %s PASSED", testTask.name) + project.ext.testSessions.add(new TestSession( + name: testTask.name, + type: testTask.ext.testType, + module: testTask.ext.testModule, + isPassed: true)) + } else { + String errorMsg = sprintf("Test %s FAILED", testTask.name) + println sprintf("::error::%s", errorMsg) + project.ext.testSessions.add(new TestSession( + name: testTask.name, + type: testTask.ext.testType, + module: testTask.ext.testModule, + isPassed: false)) + if (!project.ext.continueOnFailForTestsEnabled) { + throw new GradleException(errorMsg) + } } } +Task reportAllTestsResult = tasks.create ( + name: "reportAllTestsResult", + description: "Report the result all every test that has been run", + type: Task +).with { + doLast { + if (project.ext.testSessions.isEmpty()) { + return + } + + println "\n\n[Test Summary]" + int failedCount = 0 + int totalCount = 0 + project.ext.testSessions.each { session -> + String resultStr + ++totalCount + String logType = "" + if (session.isPassed) { + resultStr = "PASSED" + } else { + resultStr = "FAILED" + logType = "::error::" + ++failedCount + } + println sprintf("%sTest %s %s [%s/%s]", + logType, + session.name, + resultStr, + session.type, + session.module) + } + println "--------------------------------------" + println sprintf("::notice::%s out of %d test(s) passed", totalCount - failedCount, totalCount) + if (failedCount > 0) { + throw new GradleException( + sprintf("%d out of %d test(s) failed", failedCount, totalCount)) + } + } +} + +Task testPackageUploader = createPythonTask( + "testPackageUploader", + "Test the unity_asset_uploader.py application.", + [], + new File(project.ext.unityAssetUploaderDir, "unity_asset_uploader_test.py"), + [], + [], + true).with { task -> + finalizedBy reportAllTestsResult + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + setTestProperties(task, TestTypeEnum.PYTHON, TestModuleEnum.TOOL) + } + +createPythonTask( + "testExportUnityPackage", + "Test the export_unity_package.py application", + [], + new File(project.ext.exportUnityPackageDir, "export_unity_package_test.py"), + [], + exportUnityPackageRequirements, + true).with { task -> + setTestProperties(task, TestTypeEnum.PYTHON, TestModuleEnum.TOOL) + finalizedBy reportAllTestsResult + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } +} + +createPythonTask( + "testGenGuids", + "Test the gen_guids.py application", + [], + new File(project.ext.exportUnityPackageDir, "gen_guids_test.py"), + [], + ["absl-py"], + true).with { task -> + setTestProperties(task, TestTypeEnum.PYTHON, TestModuleEnum.TOOL) + finalizedBy reportAllTestsResult + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + } + +createPythonTask( + "testImportUnityPackage", + "Test the import_unity_package.py application", + [], + new File(project.ext.importUnityPackageDir, "import_unity_package_test.py"), + [], + ["absl-py"], + true).with { task -> + setTestProperties(task, TestTypeEnum.PYTHON, TestModuleEnum.TOOL) + finalizedBy reportAllTestsResult + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + } + task updateEmbeddedGradleWrapper(type: Zip) { description "Update the gradle wrapper in gradle-template.zip" from project.ext.scriptDirectory @@ -1052,7 +1894,7 @@ task updateEmbeddedGradleWrapper(type: Zip) { include "gradle/**" archiveName "gradle-template.zip" destinationDir (new File(project.ext.scriptDirectory, - "source/PlayServicesResolver/scripts")) + "source/AndroidResolver/scripts")) } Task buildVersionHandler = createBuildPluginDllTask( @@ -1061,13 +1903,14 @@ Task buildVersionHandlerImpl = createBuildPluginDllTask( "VersionHandlerImpl", "VersionHandlerImpl", "Google.VersionHandlerImpl.dll", true, [buildVersionHandler]) Task buildAndroidResolver = createBuildPluginDllTask( - "AndroidResolver", "PlayServicesResolver", "Google.JarResolver.dll", true, + "AndroidResolver", "AndroidResolver", "Google.JarResolver.dll", true, [updateEmbeddedGradleWrapper, buildVersionHandlerImpl]) Task buildIosResolver = createBuildPluginDllTask( "IosResolver", "IOSResolver", "Google.IOSResolver.dll", true, [buildAndroidResolver]) -Task buildPackageManager = createBuildPluginDllTask( - "PackageManager", "PackageManager", "Google.PackageManager.dll", true, +Task buildPackageManagerResolver = createBuildPluginDllTask( + "PackageManagerResolver", "PackageManagerResolver", + "Google.PackageManagerResolver.dll", true, [buildVersionHandlerImpl]) task preparePluginStagingAreaDir(type: Task) { @@ -1091,17 +1934,42 @@ task preparePluginStagingAreaDir(type: Task) { Task copyPluginTemplateToStagingArea = createCopyFilesTask( "copyPluginTemplateToStagingArea", "Copy the template project into the Unity plugin packaging dir.", - [new File("Assets", "PlayServicesResolver.meta"), - new File(new File("Assets", "PlayServicesResolver"), "Editor.meta")], + [new File("Assets", "ExternalDependencyManager.meta"), + new File(new File("Assets", "ExternalDependencyManager"), "Editor.meta"), + new File(new File(new File("Assets", "ExternalDependencyManager"), "Editor"), + "CHANGELOG.md.meta"), + new File(new File(new File("Assets", "ExternalDependencyManager"), "Editor"), + "LICENSE.meta"), + new File(new File(new File("Assets", "ExternalDependencyManager"), "Editor"), + "README.md.meta"), + new File("Assets", "PlayServicesResolver.meta"), + new File(new File("Assets", "PlayServicesResolver"), "Editor.meta"), + new File(new File(new File("Assets", "PlayServicesResolver"), "Editor"), + "play-services-resolver_v1.2.137.0.txt"), + new File(new File(new File("Assets", "PlayServicesResolver"), "Editor"), + "play-services-resolver_v1.2.137.0.txt.meta")], project.ext.pluginTemplateDir, project.ext.pluginStagingAreaDir, [preparePluginStagingAreaDir], { sourceFile, targetFile -> copyAssetMetadataFile(sourceFile, targetFile) }) +Task copyDocumentationToStagingArea = createCopyFilesTask( + "copyDocumentationToStagingArea", + "Copy documentation into the Unity plugin packaging dir.", + [new File("CHANGELOG.md"), + new File("LICENSE"), + new File("README.md")], + project.ext.scriptDirectory, + new File(new File(new File(project.ext.pluginStagingAreaDir, "Assets"), + "ExternalDependencyManager"), "Editor"), + [preparePluginStagingAreaDir], + { sourceFile, targetFile -> copyAssetMetadataFile(sourceFile, targetFile) }) + Iterable copyComponentsToStagingAreaTasks = [ copyAndroidResolverStaging, copyVersionHandlerStaging, copyVersionHandlerImplStaging, - copyIosResolverStaging].collect { + copyIosResolverStaging, + copyPackageManagerResolverStaging].collect { task -> createCopyFilesTask( sprintf("copy%sToStagingArea", task.ext.componentName), @@ -1126,6 +1994,7 @@ task copyPluginComponentsToStagingArea( task generatePluginManifest(dependsOn: [preparePluginStagingAreaDir, copyPluginTemplateToStagingArea, + copyDocumentationToStagingArea, copyPluginComponentsToStagingArea]) { String unversionedManifestName = currentPluginBasename + ".txt" File outputDir = new File(project.ext.pluginStagingAreaDir, @@ -1135,9 +2004,15 @@ task generatePluginManifest(dependsOn: [preparePluginStagingAreaDir, project.ext.pluginEditorDllDir.path), unversionedManifestName + project.ext.unityMetadataExtension) File manifestFile = versionedAssetFile( - new File(outputDir, unversionedManifestName)) + new File(outputDir, unversionedManifestName), + true, + "_manifest", + false) File manifestMetadataFile = versionedAssetFile( - new File(outputDir, manifestMetadataTemplateFile.name)) + new File(outputDir, manifestMetadataTemplateFile.name), + true, + "_manifest", + false) description "Generate a manifest for the files in the plug-in." inputs.files files(manifestMetadataTemplateFile) @@ -1162,24 +2037,71 @@ task generatePluginManifest(dependsOn: [preparePluginStagingAreaDir, } } -Task buildPlugin = createUnityTask( - "buildPlugin", "build_plugin", [generatePluginManifest], +// Deprecated target for packaging the plugin. +Task buildPluginWithUnity = createUnityTask( + "buildPluginWithUnity", "build_plugin", [generatePluginManifest], project.ext.pluginStagingAreaDir.name, project.ext.buildDir, ["-g.building", "-buildTarget", "android", - "-exportPackage", project.ext.pluginAssetsDir.path, - project.ext.pluginExportFile.absolutePath, + "-exportPackage"] + (project.ext.pluginExportDirs.collect { it.path }) + + [project.ext.pluginExportFile.absolutePath, "-gvh_disable", - "-quit"], true) -buildPlugin.with { - description "Exports the plugin staging area directory as a Unity package." + "-quit"], true, null) +buildPluginWithUnity.with { + description ("(Deprecated) Exports the plugin staging area directory as " + + "a Unity package.") inputs.files files(copyPluginTemplateToStagingArea.outputs.files, copyPluginComponentsToStagingArea.outputs.files) outputs.files files(project.ext.pluginExportFile) } -task releasePlugin(dependsOn: buildPlugin) { +Task buildPlugin = createExportUnityPackageTask( + "buildPlugin", + "Package the .unitypackage with export_unity_package.py.", + [generatePluginManifest], + new File(project.ext.scriptDirectory, "export_unity_package_config.json"), + new File(project.ext.scriptDirectory, "export_unity_package_guids.json"), + new File(project.ext.pluginStagingAreaDir, "Assets"), + true, // Enable .unitypackage export. + false, // Disable UPM export. + project.ext.pluginVersion, + project.ext.pluginExportFile.parentFile, + ["--enabled_sections", "unitypackage documentation"]) +buildPlugin.with { + outputs.files project.ext.pluginExportFile.absolutePath +} + +// Guid paths for UPM package. +File upmPluginPackageDir = new File(currentPluginUpmPackageName, "ExternalDependencyManager") +File upmPluginEditorDir = new File(upmPluginPackageDir, "Editor") +File upmPluginDllDir = new File(upmPluginEditorDir, project.ext.pluginVersion) + +Task genGuidUpm = createGenGuidTask( + "genGuidUpm", + "Generate GUID for .tgz packaging.", + new File(project.ext.scriptDirectory, "export_unity_package_guids.json"), + project.ext.pluginVersion, + [upmPluginDllDir.path] +) + +Task buildUpmPlugin = createExportUnityPackageTask( + "buildUpmPlugin", + "Package the .tgz with export_unity_package.py.", + [generatePluginManifest, genGuidUpm], + new File(project.ext.scriptDirectory, "export_unity_package_config.json"), + new File(project.ext.scriptDirectory, "export_unity_package_guids.json"), + new File(project.ext.pluginStagingAreaDir, "Assets"), + false, // Disable .unitypackage export. + true, // Enable UPM export. + project.ext.pluginVersion, + project.ext.pluginUpmExportFile.parentFile, + []) +buildUpmPlugin.with { + outputs.files project.ext.pluginUpmExportFile.absolutePath +} + +task releasePlugin(dependsOn: [buildPlugin, buildUpmPlugin]) { Map pluginTemplateFilesMap = files( copyPluginTemplateToStagingArea.outputs.files, copyPluginComponentsToStagingArea.outputs.files).collectEntries { @@ -1202,6 +2124,7 @@ task releasePlugin(dependsOn: buildPlugin) { fileTree(dir: project.ext.pluginExplodedDir), pluginTemplateFilesMap.values()) doLast { + // Delete and regenerate built .unitypackage in the repo. delete fileTree( dir: project.ext.pluginReleaseFile.parentFile, includes: [project.ext.currentPluginBasename + "-*.unitypackage"]) @@ -1210,11 +2133,29 @@ task releasePlugin(dependsOn: buildPlugin) { into project.ext.pluginReleaseFile.parentFile rename { src_filename -> project.ext.pluginReleaseFile.name } } + copy { + from project.ext.pluginExportFile + into project.ext.pluginReleaseFileUnversioned.parentFile + rename { src_filename -> project.ext.pluginReleaseFileUnversioned.name } + } + // Delete and regenerate the exploded plugin folder in the repo. delete fileTree(dir: project.ext.pluginExplodedDir) copy { from project.ext.pluginStagingAreaDir into project.ext.pluginExplodedDir - include (project.ext.pluginAssetsDir.path + "/**/*") + include project.ext.pluginExportDirs.collect { + it.path + "/**/*" + } + } + // Delete and regenerate the UPM package in the repo. + delete fileTree(dir: project.ext.pluginUpmDir) + copy { + // Rename the top-level package folder to upm. + eachFile { + path = path.replaceFirst(/^.+?\//, "upm/") + } + from tarTree(project.ext.pluginUpmExportFile) + into project.ext.scriptDirectory } pluginTemplateFilesMap.each { sourceFile, targetFile -> copyFile(sourceFile, targetFile) @@ -1235,10 +2176,14 @@ task gitAddReleaseFilesToStaging(type: Exec, dependsOn: releasePlugin) { */ String readVersionSummaryFromChangelog() { String versionSummary = "" - for (String line in project.ext.changelog.text.split("\n")) { + Boolean foundVersion = false + for (String line in project.ext.changelog.text.tokenize("\n")) { String trimmedLine = line.trim() - if (line.isEmpty()) break - versionSummary += line + "\n" + if (line =~ /^# Version/) { + if (foundVersion) break + foundVersion = true + } + versionSummary += line.replace("#", "-") + "\n" } return versionSummary } @@ -1250,7 +2195,7 @@ String readVersionSummaryFromChangelog() { */ String createCommitMessage() { return sprintf("Version %s\n\n%s", - project.ext.pluginVersionSemVer, + project.ext.pluginVersion, readVersionSummaryFromChangelog()) } @@ -1276,17 +2221,17 @@ task gitTagRelease(type: Exec) { description ("Run git to tag a release. This should be performed " + "*after* a release commit has been be created.") commandLine "git", "tag", "-a", - sprintf("v%s", pluginVersionSemVer), "-m", + sprintf("v%s", pluginVersion), "-m", sprintf("%s\n\nDownload [here](%s)", createCommitMessage(), sprintf("https://github.com/googlesamples/" + "unity-jar-resolver/raw/v%s/" + - "play-services-resolver-%s.unitypackage", - project.ext.pluginVersionSemVer, + "external-dependency-manager-%s.unitypackage", + project.ext.pluginVersion, project.ext.pluginVersion)) } -// TODO: Version Handler tests to implement in both batch and interactive modes +// TODO: Version Handler tests // - Per platform targeting (iOS, Android, Editor, desktop) // - Version enablement, import newer plugin on top of older plugin and validate // new plugin is enabled, old plugin is removed. @@ -1296,7 +2241,7 @@ task gitTagRelease(type: Exec) { // - Import plugin into a project and wait for asset processor to enable it. // - Switch .NET version, validate DLLs are enabled / disabled as expected. -// TODO: Android Resolver tests to implement in both batch and interactive modes +// TODO: Android Resolver tests // - Resolve with: // - Conflicting dependencies // - FAT ABI vs. single ABI selection @@ -1309,28 +2254,163 @@ task gitTagRelease(type: Exec) { // - Project export // - Add a pod which changes the target SDK +createUnityNUnitTest( + "testVersionHandlerImplNUnitTests", + "Runs NUnit tests for the VersionHandlerImpl module.", + [], + new File(project.ext.scriptDirectory, + "source/VersionHandlerImpl/unit_tests"), [], + TestTypeEnum.NUNIT, TestModuleEnum.VERSIONHANDLER +) + +createUnityNUnitTest( + "testPackageManagerResolverNUnitTests", + "Runs NUnit tests for the PackageManagerResolver module.", + [], + new File(project.ext.scriptDirectory, + "source/PackageManagerResolver/unit_tests"), [], + TestTypeEnum.NUNIT, TestModuleEnum.PACKAGEMANAGER +) + createUnityTestBatchAndNonBatch( "testVersionHandlerActivation", ("Imports the plugin into a Unity project and verifies all " + "components can be activated by the Version Handler."), [buildPlugin], - new File("source/VersionHandlerImpl/test/activation"), []) + new File(project.ext.scriptDirectory, + "source/VersionHandlerImpl/test/activation"), + [], [], null, TestTypeEnum.INTEGRATION, TestModuleEnum.VERSIONHANDLER) + +// Launch the test via a script that runs a local web server. +createUnityTestBatchAndNonBatch( + "testVersionHandlerWebRequest", + ("Imports the plugin into a Unity project tests the PortableWebRequest " + + "class."), + [buildPlugin], + new File(project.ext.scriptDirectory, + "source/VersionHandlerImpl/test/webrequest"), + [], [], + { String name, String description, Iterable depends, + File executable, Iterable args, Boolean continueOnFail -> + Iterable runnerArgs = [executable.absolutePath] + args + return createPythonTask( + name, description, depends, + new File( + new File( + new File(project.ext.pluginSourceDir, + "VersionHandlerImpl"), + "test"), + "webrequest_launcher.py"), + runnerArgs, + [], continueOnFail) + }, TestTypeEnum.INTEGRATION, TestModuleEnum.CORE) createUnityTestBatchAndNonBatch( "testVersionHandlerReflection", ("Imports the plugin into a Unity project and tests reflection " + "methods."), [buildPlugin], - new File("source/VersionHandler/test/reflection"), []) + new File(project.ext.scriptDirectory, + "source/VersionHandler/test/reflection"), + [], + [], + null, + TestTypeEnum.INTEGRATION, TestModuleEnum.CORE) + +Task compileIntegrationTester = createXbuildTask( + "compileIntegrationTester", + "Compile Integration Tester module.", + project.ext.pluginSolutionFile, + "IntegrationTester", + fileTree(new File(project.ext.pluginSourceDir, + "IntegrationTester")), + new File(project.ext.buildDir, "IntegrationTester"), + [new File("Google.IntegrationTester.dll")], + [compileVersionHandler]) -createUnityTestBatchAndNonBatch( - "testAndroidResolverAsyncResolve", +/* + * Creates a task which compiles and run a Unity integration test. + * + * @param taskName Name of the test. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param integrationTestProject Project within + * project.ext.pluginSolutionFile that contains the integration test. + * @param integrationTestProjectSources Source files integrationTestProject + * requires. + * @param integrationTestProjectOutputs Assemblies generated by the + * integrationTestProject. + * @param unityProjectDir Directory containing a assets to copy into the + * integration test project. + * @param arguments Additional arguments for Unity when running the integration + * test. + * @param testType Type of the test + * @param testModule Module of the test + */ +Task createUnityIntegrationTest(String taskName, + String description, + Iterable dependsOn, + String integrationTestProject, + Iterable integrationTestProjectSources, + Iterable integrationTestProjectOutputs, + File unityProjectDir, + Iterable arguments, + TestTypeEnum testType, + TestModuleEnum testModule) { + Task compileIntegrationTest = createXbuildTask( + sprintf("compile%s", integrationTestProject), + sprintf("Compile %s for %s", integrationTestProject, taskName), + project.ext.pluginSolutionFile, + integrationTestProject, + integrationTestProjectSources, + new File(project.ext.buildDir, integrationTestProject), + integrationTestProjectOutputs, + [compileIntegrationTester] + dependsOn) + compileIntegrationTest.with { task -> + setTestProperties(task, testType, testModule) + } + + createUnityTestBatchAndNonBatch( + taskName, + description, + [buildPlugin], + unityProjectDir, + compileIntegrationTest.outputs.files + + compileIntegrationTester.outputs.files, + arguments, null, testType, testModule) +} + +createUnityIntegrationTest( + "testAndroidResolverIntegrationTests", ("Imports the plugin into a Unity project, runs Android resolution with " + "a combination of dependencies added via the programmatic API and via " + "XML files, verifying all dependencies resolve successfully."), - [buildPlugin], - new File("source/PlayServicesResolver/test/resolve_async"), - ["-buildTarget", "android"]) // TODO: Move this into the test!? + [compileAndroidResolver], + "AndroidResolverIntegrationTests", + fileTree(new File(new File(project.ext.pluginSourceDir, "AndroidResolver"), + "test")), + [new File("Google.AndroidResolverIntegrationTests.dll")], + new File( + project.ext.scriptDirectory, + "source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject"), + ["-buildTarget", "android"], + TestTypeEnum.INTEGRATION, + TestModuleEnum.ANDROIDRESOLVER) + +createUnityIntegrationTest( + "testPackageManagerClientIntegrationTests", + ("Imports the plugin into a Unity project and uses the Unity Package " + + "Manager client to list, search, install and remove packages. "), + [compilePackageManagerResolver], + "PackageManagerClientIntegrationTests", + fileTree(new File(new File(new File(project.ext.pluginSourceDir, + "PackageManagerResolver"), "test"), + "PackageManagerClientIntegrationTests")), + [new File("Google.PackageManagerClientIntegrationTests.dll"), + new File("Google.PackageManagerClientIntegrationTests.dll.mdb")], + null, [], + TestTypeEnum.INTEGRATION, + TestModuleEnum.PACKAGEMANAGER) task cleanTests(type: Delete) { description "Clean test directories." @@ -1352,3 +2432,18 @@ project.defaultTasks = ["build", "test", "release", "clean"].collect { task -> task.name.startsWith(topLevelTaskName) }) } + +// Disable tests by filters. +project.tasks.findAll { task -> + if (task.hasProperty("testType") && task.testType != null && + task.hasProperty("testModule") && task.testModule != null) { + if (!shouldTestRunWithFilters(task.testType, task.testModule)) { + println sprintf("DISABLED :%s", task.name) + task.enabled = false + } + if (project.ext.excludeTestsParam.contains(task.name.toLowerCase())) { + println sprintf("DISABLED :%s", task.name) + task.enabled = false + } + } +} diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.mdb.meta b/exploded/Assets/ExternalDependencyManager/Editor.meta similarity index 58% rename from plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.mdb.meta rename to exploded/Assets/ExternalDependencyManager/Editor.meta index 11fbbef2..4ef59616 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.mdb.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor.meta @@ -1,8 +1,7 @@ fileFormatVersion: 2 -guid: 42b257c30aa34035972469681b592ac3 -labels: -- gvh -timeCreated: 1538009133 +guid: b42aa8acaabecbf943da2892de5e6aeb +folderAsset: yes +timeCreated: 1448926516 licenseType: Pro DefaultImporter: userData: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll new file mode 100644 index 00000000..30030559 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta new file mode 100644 index 00000000..707c589b --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: e2d7ea0845de4cf984265d2a444b7aa4 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + validateReferences: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb new file mode 100644 index 00000000..493d3b28 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta new file mode 100644 index 00000000..e6b1e059 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: baf24db2bf904e729e7796721c09e8ad +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll new file mode 100644 index 00000000..645d5cd3 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll differ diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta similarity index 76% rename from plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta rename to exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta index c6955559..a1398e9f 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: 0d921e6d65a843abab7c9775a3d065cb +guid: fa49a85d4ba140a0ae21528ed12d174c labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll - gvh -- gvh_targets-editor +- gvhp_targets-editor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb new file mode 100644 index 00000000..c6eaef49 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta new file mode 100644 index 00000000..12826037 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d13c8602d5e14e43b0e92459754c4315 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll new file mode 100644 index 00000000..a4a6590c Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta new file mode 100644 index 00000000..3b4fa84b --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: d8bb10c56a0147bc855a6296778e025e +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb new file mode 100644 index 00000000..884ce212 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta new file mode 100644 index 00000000..ac071adc --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a695eb9f64fe49569a2db0c4246c877d +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll new file mode 100644 index 00000000..8562ef33 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll differ diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta similarity index 75% rename from plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.meta rename to exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta index b362930b..7456068f 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: 59f63fba63124b4a8d19bf039e14e11b +guid: 5980a684c61d42fbb6b74e2eb3477016 labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll - gvh -- gvh_targets-editor +- gvhp_targets-editor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb new file mode 100644 index 00000000..ed8cc976 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta new file mode 100644 index 00000000..da5b09b2 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9f56badf3ca84753b00163c3b632d4e5 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md b/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md new file mode 100644 index 00000000..e1294a3a --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md @@ -0,0 +1,1430 @@ +# Version 1.2.186 - May 19, 2025 +* iOS Resolver - Set `validateReferences` to off by default, + to prevent errors when running without iOS Support installed. + Fixes #412 and #622 + +# Version 1.2.185 - Feb 3, 2025 +* Android Resolver - Reverse conditional checker for `packaging` keyword in maintemplate based on android gradle plugin version. Fixes #715 + +# Version 1.2.184 - Jan 28, 2025 +* Android Resolver - Update and resolve `packaging` keyword in maintemplate + based on android gradle plugin version. + Fixes #715 + +# Version 1.2.183 - Sep 18, 2024 +* Android Resolver - Handle package paths that don't include a version hash, + which is no longer present with Unity 6. Fixes #697 +* Android Resolver - Handle packages referenced using local file paths. + Fixes #701 + +# Version 1.2.182 - Aug 2, 2024 +* General - Check for gradle version instead of Unity version when determining + the template files to modify. + +# Version 1.2.181 - Jun 26, 2024 +* General - Disable `EditorMeasurement` reporting that relied on the + Measurement Protocol for Universal Analytics. + +# Version 1.2.180 - Jun 4, 2024 +* General - Fix project settings resetting on domain reload. + Fixes #524 + +# Version 1.2.179 - Feb 12, 2024 +* Android Resolver - Added logic to automatically turn on `mainTemplate.gradle` + for new projects, and prompt users to enable it on projects that have previously + had the resolver run. + +# Version 1.2.178 - Dec 20, 2023 +* Added [OpenUPM support](https://openupm.com/packages/com.google.external-dependency-manager/). + +# Version 1.2.177 - Aug 14, 2023 +* iOS Resolver - Added `/opt/homebrew/bin` to Cocoapod executable search path. + Fixes #627 + +# Version 1.2.176 - Apr 27, 2023 +* Android Resolver - Added two Android Resolver settings to determine whether + EDM4U injects custom local Maven repo path as a relative path or full path. + Fixes #537 +* Android Resolver - Inject Maven Repo to `settingTemplate.gradle` from + Unity 2022.2+ + Fixes #594 +* Android Resolver - Jetifier option is enabled by default now. +* Android Resolver - `Explode Aar` option applies to all cases, whether the + project will be exported or not. + Fixes #584 + Fixes #287 + +# Version 1.2.175 - Nov 16, 2022 +* General - Added tvOS podfile support to the iOS resolver. + +# Version 1.2.174 - Oct 06, 2022 +* General - Added tvOS support to the iOS resolver. +* General - Fixed #484 - Changed `EditorMeasurement` to use secure connection. +* Android Resolver - Fixed Android Resolver unable to resolve + `mainTemplate.gradle` in Unity `2022.2+` or `2023.1+`. + +# Version 1.2.173 - Sep 28, 2022 +* General - Added tvOS library support to the export unity package scripts. + +# Version 1.2.172 - Jun 23, 2022 +* iOS Resolver - Stop forcing `ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES` to `YES`, + which seems to cause problem for some when submitting apps. See #526 for more + information. + +# Version 1.2.171 - May 11, 2022 +* iOS Resolver - Change `Enable Swift Framework Support Workaround` setting to + be `ON` by default since more pods are using Swift Framework now. + +# Version 1.2.170 - Apr 4, 2022 +* Android Resolver - Fixes #498 - Fix the path separator of the Maven repo + injected to `mainTemplate.gradle`. +* iOS Resolver - Fixes #470 - Switch default Cocoapods master repo from Github + to CDN. +* iOS Resolver - `Link Framework Statically` setting is now default to `true`. + That is, `use_frameworks! :linkage => static` will be added to `Podfile` by + default instead of `use_frameworks!`. This can be changed in iOS Resolver + settings. This fixes odd behaviors when pods include static libraries, ex. + Firebase Analytics. +* iOS Resolver - Added a workaround when app crashes on launch due to + `Library not loaded: @rpath/libswiftCore.dylib` when some pods includes Swift + framework. This is turned `OFF` by default and can be changed in iOS Resolver + settings. + +# Version 1.2.169 - Jan 20, 2022 +* General - Fixes #425 - Change to save `GvhProjectSettings.xml` without + Unicode byte order mark (BoM). +* Android Resolver - Remove reference to `jcenter()` +* iOS Resolver - Force setting `LANG` when executing Cocoapods in shell mode on + Mac. + +# Version 1.2.168 - Dec 9, 2021 +* All - Fixes #472 by removing the use of `System.Diagnostics.Debug.Assert` +* All - Fixed #477 by properly enabling EDM4U libraries for Unity 2021.2+ when + the package is installed through `.tgz` + +# Version 1.2.167 - Oct 6, 2021 +* All - Moved versioned `.dll` in EDM4U to a versioned folder and remove their + version postfix in their filename. For instance, `IOSResolver.dll` will be + placed at `ExternalDependencyManager/Editor/1.2.167/Google.IOSResolver.dll`. +* Android Resolver - Fixed #243 by only using the highest version in + `mainTemplate.gradle` when duplicated dependencies are presented. +* Android Resolver - Added supports to x86_64 to ABI list for Android apps on + Chrome OS. + +# Version 1.2.166 - Jun 30, 2021 +* All - Fixed #440 and fixed #447 by specifying the parameter type while calling + `GetApplicationIdentifier()` Unity API using reflection, due to a new + overloaded method introduced in Unity 2021.2. +* Android Resolver - Fixed #442 by patching `Dependency.IsGreater()` when the + version strings end '+'. + +# Version 1.2.165 - Apr 28, 2021 +## Bug Fixes +* Version Handler - Fixed #431 by replacing the use of `HttpUtility.UrlEncode()` + which causes NullReferenceException in certain version of Unity. +* Android Resolver - Check that androidSdkRootPath directory exists before using + as sdkPath. +* Android Resolver - Fixed Android Resolver integration tests with Unity + 2019.3+. + +# Version 1.2.164 - Feb 4, 2021 +## New Features +* Android Resolver - Added support for Android packages with classifier in their + namespaces. +* iOS Resolver - Added new settings in iOS Resolver to configure generated + Podfile. +* iOS Resolver - Added a new attribute `addToAllTargets` in Dependencies.xml. + +## Bug Fixes +* iOS Resolver - Fixed XML parsing for `bitcodeEnabled` attribute in + Dependencies.xml. + +# Version 1.2.163 - Dec 15, 2020 +## Bug Fixes +* Version Handler - Fixed measurement reporting + +# Version 1.2.162 - Nov 19, 2020 +## Bug Fixes +* Version Handler - Improved #413 by preventing Version Handler from running + from static constructor when it is disabled. +* Package Manager Resolver - Remove GPR + +# Version 1.2.161 - Oct 12, 2020 +## Bug Fixes +* Android Resolver - Fixed the issue that Android Resolver does not resolve + again before build in Unity 2020 if it failed to resolve previously. + +# Version 1.2.160 - Sep 30, 2020 +## Bug Fixes +* Android Resolver - Fixed a regression that gradleResolver can be null until + Initialize() is called. +* Android Resolver - Fixed a regression that Android Resolver failed in Unity + 2019.3+ due to `gradleTemplate.properties` not enabled when + `mainTemplate.gradle` is not enabled at all. + +# Version 1.2.159 - Sep 11, 2020 +## Bug Fixes +* Android Resolver - Fixed #322 where the Unity editor will lose its target SDK + setting between Unity restarts if `>28` is selected in 2019. This is due to + Unity AndroidSdkVersions enum does not contain values above 28. +* Android Resolver - Fixed #360 where building Android app with Untiy 2019.3+ + may fail due to Jetifier and AndroidX not enabled properly in generated + Gradle project. This fix requires the user to enable + `Custom Gradle Properties Template` found under + `Player Settings > Settings for Android > Publishing Settings`. + +# Version 1.2.158 - Sep 3, 2020 +## Bug Fixes +* Version Handler: Fixed editor freeze when `-executeMethod` is used in + non-batch mode. +* Android Resolver: Normalized file paths when generating local Maven repo + since the path may contains a mix of forward and backward slash on Windows. +* Export Unity Package: Fixed generation of .unitypackage with tarfile on + Windows. + +# Version 1.2.157 - Aug 6, 2020 +## Bug Fixes +* Android Resolver: Delay initialization until active build target is Android + and the editor is not in play mode. +* iOS Resolver: Delay initialization until active build target is iOS + and the editor is not in play mode. +* Export Unity Package: Workaround directory creation racy if multiple export + operations are spawned at the same time. + +# Version 1.2.156 - June 10, 2020 +## Bug Fixes +* Android Resolver: Fixed that the generated local repo assets contains + redundent labels which are causing Version Handler to failed while + uninstalling packages. +* Android Resolver: Fixed that the repo url injected into mainTemplate.gradle + is incorrect when Unity is configured to export gradle project. +* Android Resolver: Limited to only create local Maven repo when the source + repo contains ".srcaar" file. + +## Changes +* All: Described EDM4U analytics data usage in readme. + +# Version 1.2.155 - May 14, 2020 +## Bug Fixes +* All: Fixed compiler error when build with Unity 5.4 or below due to the + usage of Rect.zero. +* All: Ignore cases when checking command line arguments. + +# Version 1.2.154 - May 14, 2020 +## Bug Fixes +* All: Make each MultiSelectWindow for different purposes to have its own + unique window. + +## Changes +* All: Replace all dialog with DialogWindow which is implemented from + EditorWindow. +* Package Manager Resolver: Clarify how manifest.json will be changed in Package + Manager Resolver window. + +# Version 1.2.153 - Apr 24, 2020 +## Bug Fixes +* Android Resolver: Fixed an exception when repainting the Android resolution + window in Unity 2019.3.x. + +# Version 1.2.152 - Apr 17, 2020 +## Bug Fixes +* Version Handler: Fixed exception when waiting for enabled editor DLLs to + load. +* Android Resolver: Fixed regression when using a Custom Gradle Template + on Windows. + +# Version 1.2.151 - Apr 16, 2020 +## Bug Fixes +* Version Handler: When waiting for newly enabled editor DLLs to load, ignore + all DLLs that do not have a file-system location. +* Android Resolver: Fixed resolution when using a Custom Gradle Template with + libraries stored in a local maven repository distributed with a plugin + installed with the Unity Package Manager. + +# Version 1.2.150 - Apr 9, 2020 +## Bug Fixes +* All: The new packaging script when run on MacOS was generating a + .unitypackage archive that could not be read by Unity on Windows. + This release simply repackages the plugin with tar/gzip to fix the problem. + +# Version 1.2.149 - Apr 8, 2020 +## Bug Fixes +* Package Manager Resolver: Fixed spurious error message when resuming + migration after installing a UPM package. + +# Version 1.2.148 - Apr 8, 2020 +## Bug Fixes +* Package Manager Resolver: Fixed an exception when resuming migration + after installing a UPM package. + +# Version 1.2.147 - Apr 8, 2020 +## Bug Fixes +* Version Handler: Fixed alias traversal bug which caused problems when + migrating from installed .unitypackage files to UPM packages. + +# Version 1.2.146 - Apr 8, 2020 +## Bug Fixes +* Version Handler: Fixed exception in manifest parsing when a manifest is + detected with no aliases. + +# Version 1.2.145 - Apr 2, 2020 +## New Features +* Package Manager Resolver: Added a method to migrate Version Handler + managed packages installed via `.unitypackage` to Unity Package Manager + packages. This is initially used to migrate the External Dependency Manager + to UPM. + +## Changes +* All: Verbose logging is now no longer automatically enabled in batch mode + across all components. Instead logging can be configured using each + component's verbose logging setting or by using the `-gvh_log_debug` command + line flag when starting Unity. +* Version Handler: Sped up version handler updates when the app domain isn't + reloaded. + +## Bug Fixes +* Version Handler: Fixed the display of the obsolete files clean up dialog + when the asset database refreshes. +* Version Handler: Improved reliability of callback from + the VersionHandler.UpdateCompleteMethods event when an asset database + refresh occurs. +* Version Handler: Fixed duplicate exportPath labels when 'Assets/' is the + root of paths assigned to files. +* Version Handler: Handle empty lines in manifest files. + +# Version 1.2.144 - Mar 23, 2020 +## Changed +* iOS Resolver: Removed the ability to configure the Xcode target a Cocoapod + is added to. + +## Bug Fixes +* iOS Resolver: Reverted support for adding Cocoapods to multiple targets as + it caused a regression (exception thrown during post-build step) in some + versions of Unity. + +# Version 1.2.143 - Mar 20, 2020 +## Bug Fixes +* Android Resolver: Fixed caching of resolution state which was causing + the resolver to always run when no dependencies had changed. + +# Version 1.2.142 - Mar 19, 2020 +## Changes +* Package Manager Resolver: Enabled auto-add by default. + +# Version 1.2.141 - Mar 19, 2020 +## Bug Fixes +* Fixed a bug when retrieving project settings. If a plugin was configured + to fetch project settings, if a setting was fetched (e.g "foo") and this + setting existed in the system settings but not the project settings the + system value would override the default value leading to unexpected + behavior. +* Fixed a warning when caching web request classes in Unity 5.6. + +# Version 1.2.140 - Mar 19, 2020 +## Bug Fixes +* Fixed measurement reporting in Unity 5.x. +* Version Handler: Fixed NullReferenceException when an asset doesn't have + an AssetImporter. + +# Version 1.2.139 - Mar 18, 2020 +## Changed +* Added documentation to the built plugin. + +# Version 1.2.138 - Mar 17, 2020 +## New Features +* Package Manager Resolver: Added the Package Manager Resolver + component that allows developers to easily boostrap Unity Package Manager + (UPM) registry addition using unitypackage plugins. +* Version Handler: Added a window that allows plugins to managed by the + Version Handler to be uninstalled. +* Version Handler: Added support for displaying installed plugins. +* Version Handler: Added support for moving files in plugins to their install + locations (if the plugin has been configured to support this). +* iOS Resolver: Added the ability to configure the Xcode target a Cocoapod is + added to. + +## Bug Fixes +* Fixed upgrade from version 1.2.137 and below after the plugin rename to + EDM4U broke the upgrade process. +* Android Resolver: Worked around PlayerSettings.Android.targetSdkVersion + returning empty names for some values in 2019.x. +* Version Handler: Fixed the display of the obsolete files clean up window. +* Version Handler: Fixed managed file check when assets are modified in the + project after plugin import. + +# Version 1.2.137 - Mar 6, 2020 +## Changed +* Renamed package to External Package Manager for Unity (EDM4U). + We changed this to reflect what this plugin is doing today which is far more + than the original scope which just consisted of importing jar files from the + Android SDK maven repository. + Scripts that used to pull `play-services-resolver*.unitypackage` will now have + to request `external-dependency-manager*.unitypackage` instead. + We'll still be shipping a `play-services-resolver*_manifest.txt` file to + handle upgrading from older versions of the plugin. + +## New Features +* All Components: Added reporting of usage so that we can remotely detect + errors and target improvements. +* Android Resolver: Added support for *Dependencies.xml files in Unity Package + Manager packages. +* iOS Resolver: Added support for *Dependencies.xml files in Unity Package + Manager packages. + +## Bug Fixes +* Version Handler: Disabled attempts to disable asset metadata modification + when assets are in a Unity Package Manager managed package. + +# Version 1.2.136 - Feb 19, 2019 +## Bug Fixes +* Android Resolver: Fixed OpenJDK path discovery in Unity 2019.3.1. + +# Version 1.2.135 - Dec 5, 2019 +## Bug Fixes +* All Components: Fixed stack overflow when loading project settings. + +# Version 1.2.134 - Dec 4, 2019 +## Bug Fixes +* All Components: Fixed an issue which caused project settings to be cleared + when running in batch mode. + +# Version 1.2.133 - Nov 18, 2019 +## Bug Fixes +* All Components: Failure to save project settings will now report an error + to the log rather than throwing an exception. + +# Version 1.2.132 - Nov 11, 2019 +## Bug Fixes +* Android Resolver: Worked around expansion of DIR_UNITYPROJECT on Windows + breaking Gradle builds when used as part of a file URI. +* Android Resolver: mainTemplate.gradle is only written if it needs to be + modified. + +# Version 1.2.131 - Oct 29, 2019 +## Bug Fixes +* Version Handler: Improved execution of events on the main thread in batch + mode. +* Version Handler: Improved log level configuration at startup. +* Version Handler: Improved performance of class lookup in deferred method + calls. +* Version Handler: Fixed rename to enable / disable for editor assets. +* iOS Resolver: Improved log level configuration at startup. +* Android Resolver: Improved local maven repo path reference in + mainTemplate.gradle using DIR_UNITYPROJECT. DIR_UNITYPROJECT by Unity + to point to the local filesystem path of the Unity project when Unity + generates the Gradle project. + +# Version 1.2.130 - Oct 23, 2019 +## New Features +* iOS Resolver: Added support for modifying the Podfile before `pod install` + is executed. + +## Bug Fixes +* Version Handler: Fixed invalid classname error when calling + `VersionHandler.UpdateVersionedAssets()`. + +# Version 1.2.129 - Oct 2, 2019 +## Bug Fixes +* iOS Resolver: Changed Cocoapod integration in Unity 2019.3+ to + only add Pods to the UnityFramework target. + +# Version 1.2.128 - Oct 1, 2019 +## Bug Fixes +* iOS Resolver: Fixed Cocoapod project integration mode with Unity + 2019.3+. + +# Version 1.2.127 - Sep 30, 2019 +## Changes +* Android Resolver: All Android Resolver settings File paths are now + serialized with POSIX directory separators. + +# Version 1.2.126 - Sep 27, 2019 +## Changes +* Android Resolver: File paths are now serialized with POSIX directory + separators. +## Bug Fixes +* Android Resolver: Fixed resolution when the parent directory of a Unity + project contains a Gradle project (i.e `settings.gradle` file). + +# Version 1.2.125 - Sep 23, 2019 +## Bug Fixes +* All components: Silenced a warning about not being able to set the console + encoding to UTF8. +* Android Resolver: Worked around broken AndroidSDKTools class in some + versions of Unity. +* iOS Resolver: Fixed iOS target SDK version check +* Version Handler: Changed clean up obsolete files window so that it doesn't + exceed the screen size. + +# Version 1.2.124 - Jul 28, 2019 +## Bug Fixes +* All components: Fixed regression with source control integration when using + Unity 2019.1+. + +# Version 1.2.123 - Jul 23, 2019 +## New Features +* All components: Source control integration for project settings. +## Changes +* Android Resolver: Removed AAR cache as it now makes little difference to + incremental resolution performance. +* Android Resolver: Improved embedded resource management so that embedded + resources should upgrade when the plugin is updated without restarting + the Unity editor. +## Bug Fixes +* Version Handler: Fixed InvokeMethod() and InvokeStaticMethod() when calling + methods that have interface typed arguments. + +# Version 1.2.122 - Jul 2, 2019 +## Bug Fixes +* iOS Resolver: Worked around Unity not loading the iOS Resolver DLL as it + referenced the Xcode extension in a public interface. The iOS Resolver + DLL still references the Xcode extension internally and just handles + missing type exceptions dynamically. + +# Version 1.2.121 - Jun 27, 2019 +## Bug Fixes +* Android Resolver: Fixed warning about missing Packages folder when loading + XML dependencies files in versions of Unity without the package manager. +* Android Resolver: Fixed resolution window progress bar exceeding 100%. +* Android Resolver: If AndroidX is detected in the set of resolved libraries, + the user will be prompted to enable the Jetifier. +* Android Resolver: Improved text splitting in text area windows. +* iOS Resolver: Added support for Unity's breaking changes to the Xcode API + in 2019.3.+. Cocoapods are now added to build targets, Unity-iPhone and + UnityFramework in Unity 2019.3+. + +# Version 1.2.120 - Jun 26, 2019 +## New Features +* Android Resolver: Added support for loading *Dependencies.xml files from + Unity Package Manager packages. +* Android Resolver: Resolution window is now closed if resolution runs as + a pre-build step. +* iOS Resolver: Added support for loading *Dependencies.xml files from + Unity Package Manager packages. +## Bug Fixes +* Android Resolver: Fixed generation of relative repo paths when using + mainTemplate.gradle resolver. +* Android Resolver: Fixed copy of .srcaar to .aar files in repos embedded in a + project when a project path has characters (e.g whitespace) that are escaped + during conversion to URIs. +* Android Resolver: Fixed auto-resolution always running if the Android SDK + is managed by Unity Hub. + +# Version 1.2.119 - Jun 19, 2019 +## Bug Fixes +* Android Resolver: Fixed error reported when using Jetifier integration + in Unity 2018+ if the target SDK is set to "highest installed". + +# Version 1.2.118 - Jun 18, 2019 +## New Features +* Android Resolver: Added initial + [Jetifier](https://developer.android.com/studio/command-line/jetifier) + integration which simplifies + [migration](ttps://developer.android.com/jetpack/androidx/migrate) + to Jetpack ([AndroidX](https://developer.android.com/jetpack/androidx)) + libraries in cases where all dependencies are managed by the Android + Resolver. + This can be enabled via the `Use Jetifier` option in the + `Assets > Play Services Resolver > Android Resolver > Settings` menu. + Caveats: + - If your project contains legacy Android Support Library .jar and .aar + files, these files will need to be removed and replaced with references to + artifacts on Maven via `*Dependencies.xml` files so that the Jetifier + can map them to Jetpack (AndroidX) libraries. + For example, remove the file `support-v4-27.0.2.jar` and replace it with + `` in a + `*Dependencies.xml` file. + - If your project contains .jar or .aar files that use the legacy Android + Support Libraries, these will need to be moved into a local Maven repo + [See this guide](https://maven.apache.org/guides/mini/guide-3rd-party-jars-local.html) + and then these files should be removed from your Unity project and instead + referenced via `*Dependencies.xml` files so that the Jetifier can + patch them to reference the Jetpack lirbaries. + +## Bug Fixes +* Android Resolver: Disabled version locking of com.android.support:multidex + does not use the same versioning scheme as other legacy Android support + libraries. +* Version Handler: Made Google.VersionHandler.dll's asset GUID stable across + releases. This faciliates error-free import into projects where + Google.VersionHandler.dll is moved from the default install location. + +# Version 1.2.117 - Jun 12, 2019 +## Bug Fixes +* Android Resolver: Fix copying of .srcaar to .aar files for + mainTemplate.gradle resolution. PluginImporter configuration was previously + not being applied to .aar files unless the Unity project was saved. + +# Version 1.2.116 - Jun 7, 2019 +## Bug Fixes +* Android Resolver: Fixed resolution of Android dependencies without version + specifiers. +* Android Resolver: Fixed Maven repo not found warning in Android Resolver. +* Android Resolver: Fixed Android Player directory not found exception in + Unity 2019.x when the Android Player isn't installed. + +# Version 1.2.115 - May 28, 2019 +## Bug Fixes +* Android Resolver: Fixed exception due to Unity 2019.3.0a4 removing + x86 from the set of supported ABIs. + +# Version 1.2.114 - May 27, 2019 +## New Features +* Android Resolver: Added support for ABI stripping when using + mainTemplate.gradle. This only works with AARs stored in repos + on the local filesystem. + +# Version 1.2.113 - May 24, 2019 +## New Features +* Android Resolver: If local repos are moved, the plugin will search the + project for matching directories in an attempt to correct the error. +* Version Handler: Files can be now targeted to multiple build targets + using multiple "gvh_" asset labels. +## Bug Fixes +* Android Resolver: "implementation" or "compile" are now added correctly + to mainTemplate.gradle in Unity versions prior to 2019. + +# Version 1.2.112 - May 22, 2019 +## New Features +* Android Resolver: Added option to disable addition of dependencies to + mainTemplate.gradle. + See `Assets > Play Services Resolver > Android Resolver > Settings`. +* Android Resolver: Made paths to local maven repositories in + mainTemplate.gradle relative to the Unity project when a project is not + being exported. +## Bug Fixes +* Android Resolver: Fixed builds with mainTemplate.gradle integration in + Unity 2019. +* Android Resolver: Changed dependency inclusion in mainTemplate.gradle to + use "implementation" or "compile" depending upon the version of Gradle + included with Unity. +* Android Resolver: Gracefully handled exceptions if the console encoding + can't be modified. +* Android Resolver: Now gracefully fails if the AndroidPlayer directory + can't be found. + +# Version 1.2.111 - May 9, 2019 +## Bug Fixes +* Version Handler: Fixed invocation of methods with named arguments. +* Version Handler: Fixed occasional hang when the editor is compiling + while activating plugins. + +# Version 1.2.110 - May 7, 2019 +## Bug Fixes +* Android Resolver: Fixed inclusion of some srcaar artifacts in builds with + Gradle builds when using mainTemplate.gradle. + +# Version 1.2.109 - May 6, 2019 +## New Features: +* Added links to documentation from menu. +* Android Resolver: Added option to auto-resolve Android libraries on build. +* Android Resolver: Added support for packaging specs of Android libraries. +* Android Resolver: Pop up a window when displaying Android dependencies. + +## Bug Fixes +* Android Resolver: Support for Unity 2019 Android SDK and JDK install locations +* Android Resolver: e-enable AAR explosion if internal builds are enabled. +* Android Resolver: Gracefully handle exceptions on file deletion. +* Android Resolver: Fixed Android Resolver log spam on load. +* Android Resolver: Fixed save of Android Resolver PromptBeforeAutoResolution + setting. +* Android Resolver: Fixed AAR processing failure when an AAR without + classes.jar is found. +* Android Resolver: Removed use of EditorUtility.DisplayProgressBar which + was occasionally left displayed when resolution had completed. +* Version Handler: Fixed asset rename to disable when a disabled file exists. + +# Version 1.2.108 - May 3, 2019 +## Bug Fixes: +* Version Handler: Fixed occasional hang on startup. + +# Version 1.2.107 - May 3, 2019 +## New Features: +* Version Handler: Added support for enabling / disabling assets that do not + support the PluginImporter, based upon build target selection. +* Android Resolver: Added support for the global specification of maven repos. +* iOS Resolver: Added support for the global specification of Cocoapod sources. + +# Version 1.2.106 - May 1, 2019 +## New Features +* iOS Resolver: Added support for development pods in Xcode project integration + mode. +* iOS Resolver: Added support for source pods with resources in Xcode project + integration mode. + +# Version 1.2.105 - Apr 30, 2019 +## Bug fixes +* Android Resolver: Fixed reference to Java tool path in logs. +* Android and iOS Resolvers: Changed command line execution to emit a warning + rather than throwing an exception and failing, when it is not possible to + change the console input and output encoding to UTF-8. +* Android Resolver: Added menu option and API to delete resolved libraries. +* Android Resolver: Added menu option and API to log the repos and libraries + currently included in the project. +* Android Resolver: If Plugins/Android/mainTemplate.gradle file is present and + Gradle is selected as the build type, resolution will simply patch the file + with Android dependencies specified by plugins in the project. + +# Version 1.2.104 - Apr 10, 2019 +## Bug Fixes +* Android Resolver: Changed Android ABI selection method from using whitelisted + Unity versions to type availability. This fixes an exception on resolution + in some versions of Unity 2017.4. + +# Version 1.2.103 - Apr 2, 2019 +## Bug Fixes +* Android Resolver: Whitelisted Unity 2017.4 and above with ARM64 support. +* Android Resolver: Fixed Java version check to work with Java SE 12 and above. + +# Version 1.2.102 - Feb 13, 2019 +## Bug Fixes +* Android Resolver: Fixed the text overflow on the Android Resolver + prompt before initial run to fit inside the buttons for + smaller screens. + +# Version 1.2.101 - Feb 12, 2019 +## New Features +* Android Resolver: Prompt the user before the resolver runs for the + first time and allow the user to elect to disable from the prompt. +* Android Resolver: Change popup warning when resolver is disabled + to be a console warning. + +# Version 1.2.100 - Jan 25, 2019 +## Bug Fixes +* Android Resolver: Fixed AAR processing sometimes failing on Windows + due to file permissions. + +# Version 1.2.99 - Jan 23, 2019 +## Bug Fixes +* Android Resolver: Improved performance of project property polling. +* Version Handler: Fixed callback of VersionHandler.UpdateCompleteMethods + when the update process is complete. + +# Version 1.2.98 - Jan 9, 2019 +## New Features +* iOS Resolver: Pod declaration properties can now be set via XML pod + references. For example, this can enable pods for a subset of build + configurations. +## Bug Fixes +* iOS Resolver: Fixed incremental builds after local pods support caused + regression in 1.2.96. + +# Version 1.2.97 - Dec 17, 2018 +## Bug Fixes +* Android Resolver: Reduced memory allocation for logic that monitors build + settings when auto-resolution is enabled. If auto-resolution is disabled, + almost all build settings are no longer polled for changes. + +# Version 1.2.96 - Dec 17, 2018 +## Bug Fixes +* Android Resolver: Fixed repacking of AARs to exclude .meta files. +* Android Resolver: Only perform auto-resolution on the first scene while + building. +* Android Resolver: Fixed parsing of version ranges that include whitespace. +* iOS Resolver: Added support for local development pods. +* Version Handler: Fixed Version Handler failing to rename some files. + +# Version 1.2.95 - Oct 23, 2018 +## Bug Fixes: +* Android Resolver: Fixed auto-resolution running in a loop in some scenarios. + +# Version 1.2.94 - Oct 22, 2018 +## Bug Fixes +* iOS Resolver: Added support for PODS_TARGET_SRCROOT in source Cocoapods. + +# Version 1.2.93 - Oct 22, 2018 +## Bug Fixes +* Android Resolver: Fixed removal of Android libraries on auto-resolution when + `*Dependencies.xml` files are deleted. + +# Version 1.2.92 - Oct 2, 2018 +## Bug Fixes +* Android Resolver: Worked around auto-resolution hang on Windows if + resolution starts before compilation is finished. + +# Version 1.2.91 - Sep 27, 2018 +## Bug Fixes +* Android Resolver: Fixed Android Resolution when the selected build target + isn't Android. +* Added C# assembly symbols the plugin to simplify debugging bug reports. + +# Version 1.2.90 - Sep 21, 2018 +## Bug Fixes +* Android Resolver: Fixed transitive dependency selection of version locked + packages. + +# Version 1.2.89 - Aug 31, 2018 +## Bug Fixes +* Fixed FileLoadException in ResolveUnityEditoriOSXcodeExtension an assembly + can't be loaded. + +# Version 1.2.88 - Aug 29, 2018 +## Changed +* Improved reporting of resolution attempts and conflicts found in the Android + Resolver. +## Bug Fixes +* iOS Resolver now correctly handles sample code in CocoaPods. Previously it + would add all sample code to the project when using project level + integration. +* Android Resolver now correctly handles Gradle conflict resolution when the + resolution results in a package that is compatible with all requested + dependencies. + +# Version 1.2.87 - Aug 23, 2018 +## Bug Fixes +* Fixed Android Resolver "Processing AARs" dialog getting stuck in Unity 5.6. + +# Version 1.2.86 - Aug 22, 2018 +## Bug Fixes +* Fixed Android Resolver exception in OnPostProcessScene() when the Android + platform isn't selected. + +# Version 1.2.85 - Aug 17, 2018 +## Changes +* Added support for synchronous resolution in the Android Resolver. + PlayServicesResolver.ResolveSync() now performs resolution synchronously. +* Auto-resolution in the Android Resolver now results in synchronous resolution + of Android dependencies before the Android application build starts via + UnityEditor.Callbacks.PostProcessSceneAttribute. + +# Version 1.2.84 - Aug 16, 2018 +## Bug Fixes +* Fixed Android Resolver crash when the AndroidResolverDependencies.xml + file can't be written. +* Reduced log spam when a conflicting Android library is pinned to a + specific version. + +# Version 1.2.83 - Aug 15, 2018 +## Bug Fixes +* Fixed Android Resolver failures due to an in-accessible AAR / JAR explode + cache file. If the cache can't be read / written the resolver now continues + with reduced performance following recompilation / DLL reloads. +* Fixed incorrect version number in plugin manifest on install. + This was a minor issue since the version handler rewrote the metadata + after installation. + +# Version 1.2.82 - Aug 14, 2018 +## Changed +* Added support for alphanumeric versions in the Android Resolver. + +## Bug Fixes +* Fixed Android Resolver selection of latest duplicated library. +* Fixed Android Resolver conflict resolution when version locked and non-version + locked dependencies are specified. +* Fixed Android Resolver conflict resolution when non-existent artifacts are + referenced. + +# Version 1.2.81 - Aug 9, 2018 +## Bug Fixes +* Fixed editor error that would occur when when + `PlayerSettings.Android.targetArchitectures` was set to + `AndroidArchitecture.All`. + +# Version 1.2.80 - Jul 24, 2018 +## Bug Fixes +* Fixed project level settings incorrectly falling back to system wide settings + when default property values were set. + +# Version 1.2.79 - Jul 23, 2018 +## Bug Fixes +* Fixed AndroidManifest.xml patching on Android Resolver load in Unity 2018.x. + +# Version 1.2.78 - Jul 19, 2018 +## Changed +* Added support for overriding conflicting dependencies. + +# Version 1.2.77 - Jul 19, 2018 +## Changed +* Android Resolver now supports Unity's 2018 ABI filter (i.e arm64-v8a). +* Reduced Android Resolver build option polling frequency. +* Disabled Android Resolver auto-resolution in batch mode. Users now need + to explicitly kick off resolution through the API. +* All Android Resolver and Version Handler dialogs are now disabled in batch + mode. +* Verbose logging for all plugins is now enabled by default in batch mode. +* Version Handler bootstrapper has been improved to no longer call + UpdateComplete multiple times. However, since Unity can still reload the + app domain after plugins have been enabled, users still need to store their + plugin state to persistent storage to handle reloads. + +## Bug Fixes +* Android Resolver no longer incorrectly adds MANIFEST.MF files to AARs. +* Android Resolver auto-resolution jobs are now unscheduled when an explicit + resolve job is started. + +# Version 1.2.76 - Jul 16, 2018 +## Bug Fixes +* Fixed variable replacement in AndroidManifest.xml files in the Android + Resolver. + Version 1.2.75 introduced a regression which caused all variable replacement + to replace the *entire* property value rather than the component of the + property that referenced a variable. For example, + given "applicationId = com.my.app", "${applicationId}.foo" would be + incorrectly expanded as "com.my.app" rather than "com.my.app.foo". This + resulted in numerous issues for Android builds where content provider + initialization would fail and services may not start. + +## Changed +* Gradle prebuild experimental feature has been removed from the Android + Resolver. The feature has been broken for some time and added around 8MB + to the plugin size. +* Added better support for execution of plugin components in batch mode. + In batch mode UnityEditor.update is sometimes never called - like when a + single method is executed - so the new job scheduler will execute all jobs + synchronously from the main thread. + +# Version 1.2.75 - Jun 20, 2018 +## New Features +* Android Resolver now monitors the Android SDK path when + auto-resolution is enabled and triggers resolution when the path is + modified. + +## Changed +* Android auto-resolution is now delayed by 3 seconds when the following build + settings are changed: + - Target ABI. + - Gradle build vs. internal build. + - Project export. +* Added a progress bar display when AARs are being processed during Android + resolution. + +## Bug Fixes +* Fixed incorrect Android package version selection when a mix of + version-locked and non-version-locked packages are specified. +* Fixed non-deterministic Android package version selection to select + the highest version of a specified package rather than the last + package specification passed to the Gradle resolution script. + +# Version 1.2.74 - Jun 19, 2018 +## New Features +* Added workaround for broken AndroidManifest.xml variable replacement in + Unity 2018.x. By default ${applicationId} variables will be replaced by + the bundle ID in the Plugins/Android/AndroidManifest.xml file. The + behavior can be disabled via the Android Resolver settings menu. + +# Version 1.2.73 - May 30, 2018 +## Bug Fixes +* Fixed spurious warning message about missing Android plugins directory on + Windows. + +# Version 1.2.72 - May 23, 2018 +## Bug Fixes +* Fixed spurious warning message about missing Android plugins directory. + +# Version 1.2.71 - May 10, 2018 +## Bug Fixes +* Fixed resolution of Android dependencies when the `Assets/Plugins/Android` + directory is named in a different case e.g `Assets/plugins/Android`. + +# Version 1.2.70 - May 7, 2018 +## Bug Fixes +* Fixed bitcode flag being ignored for iOS pods. + +# Version 1.2.69 - May 7, 2018 +## Bug Fixes +* Fixed escaping of local repository paths in Android Resolver. + +# Version 1.2.68 - May 3, 2018 +## Changes +* Added support for granular builds of Google Play Services. + +# Version 1.2.67 - May 1, 2018 +## Changes +* Improved support for iOS source-only pods in Unity 5.5 and below. + +# Version 1.2.66 - April 27, 2018 +## Bug Fixes +* Fixed Version Handler renaming of Linux libraries with hyphens in filenames. + Previously, libraries named Foo-1.2.3.so were not being renamed to + libFoo-1.2.3.so on Linux which could break native library loading on some + versions of Unity. + +# Version 1.2.65 - April 26, 2018 +## Bug Fixes +* Fix CocoaPods casing in logs and comments. + +# Version 1.2.64 - Mar 16, 2018 +## Bug Fixes +* Fixed bug in download_artifacts.gradle (used by Android Resolver) which + reported a failure if required artifacts already exist. + +# Version 1.2.63 - Mar 15, 2018 +## Bug Fixes +* Fixed iOS Resolver include search paths taking precedence over system headers + when using project level resolution. +* Fixed iOS Resolver includes relative to library root, when using project level + resolution. + +# Version 1.2.62 - Mar 12, 2018 +## Changes +* Improved error reporting when a file can't be moved to trash by the + Version Handler. +## Bug Fixes +* Fixed Android Resolver throwing NullReferenceException when the Android SDK + path isn't set. +* Fixed Version Handler renaming files with underscores if the + "Rename to Canonical Filenames" setting is enabled. + +# Version 1.2.61 - Jan 22, 2018 +## Bug Fixes +* Fixed Android Resolver reporting non-existent conflicting dependencies when + Gradle build system is enabled. + +# Version 1.2.60 - Jan 12, 2018 +## Changes +* Added support for Maven / Ivy version specifications for Android packages. +* Added support for Android SNAPSHOT packages. + +## Bug Fixes +* Fixed Openjdk version check. +* Fixed non-deterministic Android package resolution when two packages contain + an artifact with the same name. + +# Version 1.2.59 - Oct 19, 2017 +## Bug Fixes +* Fixed execution of Android Gradle resolution script when it's located + in a path with whitespace. + +# Version 1.2.58 - Oct 19, 2017 +## Changes +* Removed legacy resolution method from Android Resolver. + It is now only possible to use the Gradle or Gradle prebuild resolution + methods. + +# Version 1.2.57 - Oct 18, 2017 +## Bug Fixes +* Updated Gradle wrapper to 4.2.1 to fix issues using Gradle with the + latest Openjdk. +* Android Gradle resolution now also uses gradle.properties to pass + parameters to Gradle in an attempt to workaround problems with + command line argument parsing on Windows 10. + +# Version 1.2.56 - Oct 12, 2017 +## Bug Fixes +* Fixed Gradle artifact download with non-version locked artifacts. +* Changed iOS resolver to only load dependencies at build time. + +# Version 1.2.55 - Oct 4, 2017 +## Bug Fixes +* Force Android Resolution when the "Install Android Packages" setting changes. + +# Version 1.2.54 - Oct 4, 2017 +## Bug Fixes +* Fixed execution of command line tools on Windows when the path to the tool + contains a single quote (apostrophe). In this case we fallback to executing + the tool via the system shell. + +# Version 1.2.53 - Oct 2, 2017 +## New Features +* Changed Android Resolver "resolution complete" dialog so that it now displays + failures. +* Android Resolver now detects conflicting libraries that it does not manage + warning the user if they're newer than the managed libraries and prompting + the user to clean them up if they're older or at the same version. + +## Bug Fixes +* Improved Android Resolver auto-resolution speed. +* Fixed bug in the Gradle Android Resolver which would result in resolution + succeeding when some dependencies are not found. + +# Version 1.2.52 - Sep 25, 2017 +## New Features +* Changed Android Resolver's Gradle resolution to resolve conflicting + dependencies across Google Play services and Android Support library packages. + +# Version 1.2.51 - Sep 20, 2017 +## Changes +* Changed iOS Resolver to execute the CocoaPods "pod" command via the shell + by default. Some developers customize their shell environment to use + custom ssh certs to access internal git repositories that host pods so + executing "pod" via the shell will work for these scenarios. + The drawback of executing "pod" via the shell could potentially cause + users problems if they break their shell environment. Though users who + customize their shell environments will be able to resolve these issues. + +# Version 1.2.50 - Sep 18, 2017 +## New Features +* Added option to disable the Gradle daemon in the Android Resolver. + This daemon is now disabled by default as some users are getting into a state + where multiple daemon instances are being spawned when changing dependencies + which eventually results in Android resolution failing until all daemon + processes are manually killed. + +## Bug Fixes +* Android resolution is now always executed if the user declines the update + of their Android SDK. This ensure users can continue to use out of date + Android SDK packages if they desire. + +# Version 1.2.49 - Sep 18, 2017 +## Bug Fixes +* Removed modulemap parsing in iOS Resolver. + The framework *.modulemap did not need to be parsed by the iOS Resolver + when injecting Cocoapods into a Xcode project. Simply adding a modular + framework to a Xcode project results in Xcode's Clang parsing the associated + modulemap and injecting any compile and link flags into the build process. + +# Version 1.2.48 - Sep 12, 2017 +## New Features +* Changed settings to be per-project by default. + +## Bug Fixes +* Added Google maven repository to fix GradlePrebuild resolution with Google + components. +* Fixed Android Resolution failure with spaces in paths. + +# Version 1.2.47 - Aug 29, 2017 +## New Features +* Android and iOS dependencies can now be specified using *Dependencies.xml + files. This is now the preferred method for registering dependencies, + we may remove the API for dependency addition in future. +* Added "Reset to Defaults" button to each settings dialog to restore default + settings. +* Android Resolver now validates the configured JDK is new enough to build + recently released Android libraries. +## Bug Fixes +* Fixed a bug that caused dependencies with the "LATEST" version specification + to be ignored when using the Gradle mode of the Android Resolver. +* Fixed a race condition when running Android Resolution. +* Fixed Android Resolver logging if a PlayServicesSupport instance is created + with no logging enabled before the Android Resolver is initialized. +* Fixed iOS resolver dialog in Unity 4. +* Fixed iOS Cocoapod Xcode project integration in Unity 4. + +# Version 1.2.46 - Aug 22, 2017 +## Bug Fixes +* GradlePrebuild Android resolver on Windows now correctly locates dependent + data files. + +# Version 1.2.45 - Aug 22, 2017 +## Bug Fixes +* Improved Android package auto-resolution and fixed clean up of stale + dependencies when using Gradle dependency resolution. + +# Version 1.2.44 - Aug 21, 2017 +## Bug Fixes +* Enabled autoresolution for Gradle Prebuild. +* Made the command line dialog windows have selectable text. +* Fixed incorrect "Android Settings" dialog disabled groups. +* Updated PlayServicesResolver android platform detection to use the package + manager instead of the 'android' tool. +* UnityCompat reflection methods 'GetAndroidPlatform' and + 'GetAndroidBuildToolsVersion' are now Obsolete due to dependence on the + obsolete 'android' build tool. + +# Version 1.2.43 - Aug 18, 2017 +## Bug Fixes +* Fixed Gradle resolution in the Android Resolver when running + PlayServicesResolver.Resolve() in parallel or spawning multiple + resolutions before the previous resolve completed. + +# Version 1.2.42 - Aug 17, 2017 +## Bug Fixes +* Fixed Xcode project level settings not being applied by IOS Resolver when + Xcode project pod integration is enabled. + +# Version 1.2.41 - Aug 15, 2017 +## Bug Fixes +* IOS Resolver's Xcode workspace pod integration is now disabled when Unity + Cloud Build is detected. Unity Cloud Build does not follow the same build + process as the Unity editor and fails to open the generated xcworkspace at + this time. + +# Version 1.2.40 - Aug 15, 2017 +## Bug Fixes +* Moved Android Resolver Gradle Prebuild scripts into Google.JarResolver.dll. + They are now extracted from the DLL when required. +* AARs / JARs are now cleaned up when switching the Android resolution + strategy. + +# Version 1.2.39 - Aug 10, 2017 +## New Features +* Android Resolver now supports resolution with Gradle. This enables support + for non-local artifacts. +## Bug Fixes +* Android Resolver's Gradle Prebuild now uses Android build tools to determine + the Android platform tools version rather than relying upon internal Unity + APIs. +* Android Resolver's Gradle Prebuild now correctly strips binaries that are + not required for the target ABI. + +# Version 1.2.38 - Aug 7, 2017 +## Bug Fixes +* Fixed an issue in VersionHandler where disabled targets are ignored if + the "Any Platform" flag is set on a plugin DLL. + +# Version 1.2.37 - Aug 3, 2017 +## New Features +* Exposed GooglePlayServices.PlayServicesResolver.Resolve() so that it's + possible for a script to be notified when AAR / Jar resolution is complete. + This makes it easier to setup a project to build from the command line. + +# Version 1.2.36 - Aug 3, 2017 +## New Features +* VersionHandler.UpdateCompleteMethods allows a user to provide a list of + methods to be called when VersionHandlerImpl has completed an update. + This makes it easier to import a plugin and wait for VersionHandler to + execute prior executing a build. + +# Version 1.2.35 - Jul 28, 2017 +## New Features +* VersionHandler will now rename Linux libraries so they can target Unity + versions that require different file naming. Libraries need to be labelled + gvh_linuxlibname-${basename} in order to be considered for renaming. + e.g gvh\_linuxlibname-MyLib will be named MyLib.so in Unity 5.5 and below and + libMyLib.so in Unity 5.6 and above. + +# Version 1.2.34 - Jul 28, 2017 +## Bug Fixes +* Made VersionHandler bootstrap module more robust when calling static + methods before the implementation DLL is loaded. + +# Version 1.2.33 - Jul 27, 2017 +## New Features +* Added a bootstrap module for VersionHandler so the implementation + of the VersionHandler module can be versioned without resulting in + a compile error when imported at different versions across multiple + plugins. + +# Version 1.2.32 - Jul 20, 2017 +## New Features +* Added support for build target selection based upon .NET framework + version in the VersionHandler. + When applying either gvh\_dotnet-3.5 or gvh\_dotnet-4.5 labels to + assets, the VersionHandler will only enable the asset for the + specified set of build targets when the matching .NET framework version + is selected in Unity 2017's project settings. This allows assets + to be provided in a plugin that need to differ based upon .NET version. + +# Version 1.2.31 - Jul 5, 2017 +## Bug Fixes +* Force expansion of AARs with native components when using Unity 2017 + with the internal build system. In contrast to Unity 5.x, Unity 2017's + internal build system does not include native libraries included in AARs. + Forcing expansion of AARs with native components generates an + Ant / Eclipse project for each AAR which is correctly included by Unity + 2017's internal build system. + +# Version 1.2.30 - Jul 5, 2017 +## Bug Fixes +* Fixed Cocoapods being installed when the build target isn't iOS. +* Added support for malformed AARs with missing classes.jar. + +# Version 1.2.29 - Jun 16, 2017 +## New Features +* Added support for the Android sdkmanager tool. + +# Version 1.2.28 - Jun 8, 2017 +## Bug Fixes +* Fixed non-shell command line execution (regression from + Cocoapod installation patch). + +# Version 1.2.27 - Jun 7, 2017 +## Bug Fixes +* Added support for stdout / stderr redirection when executing + commands in shell mode. + This fixes CocoaPod tool installation when shell mode is + enabled. +* Fixed incremental builds when additional sources are specified + in the Podfile. + +# Version 1.2.26 - Jun 7, 2017 +## Bug Fixes +* Fixed a crash when importing Version Handler into Unity 4.7.x. + +# Version 1.2.25 - Jun 7, 2017 +## Bug Fixes +* Fixed an issue in the Jar Resolver which incorrectly notified + event handlers of bundle ID changes when the currently selected + (not active) build target changed in Unity 5.6 and above. + +# Version 1.2.24 - Jun 6, 2017 +## New Features +* Added option to control file renaming in Version Handler settings. + Disabling file renaming (default option) significantly increases + the speed of file version management operations with the downside + that any files that are referenced directly by canonical filename + rather than asset ID will no longer be valid. +* Improved logging in the Version Handler. +## Bug Fixes +* Fixed an issue in the Version Handler which caused it to not + re-enable plugins when re-importing a custom package with disabled + version managed files. + +# Version 1.2.23 - May 26, 2017 +## Bug Fixes +* Fixed a bug with gradle prebuild resolver on windows. + +# Version 1.2.22 - May 19, 2017 +## Bug Fixes +* Fixed a bug in the iOS resolver with incremental builds. +* Fixed misdetection of Cocoapods support with Unity beta 5.6. + +# Version 1.2.21 - May 8, 2017 +## Bug Fixes +* Fix for https://github.com/googlesamples/unity-jar-resolver/issues/48 + Android dependency version number parsing when "-alpha" (etc.) are + included in dependency (AAR / JAR) versions. + +# Version 1.2.20 - May 8, 2017 +## Bug Fixes +* Attempted to fix + https://github.com/googlesamples/unity-jar-resolver/issues/48 + where a NullReferenceException could occur if a target file does not + have a valid version string. + +# Version 1.2.19 - May 4, 2017 +## Bug Fixes +* Fixed Jar Resolver exploding and deleting AAR files it isn't managing. + +# Version 1.2.18 - May 4, 2017 +## New Features +* Added support for preserving Unity pods such as when GVR is enabled. + +# Version 1.2.17 - Apr 20, 2017 +## Bug Fixes +* Fixed auto-resolution when an Android application ID is modified. + +# Version 1.2.16 - Apr 17, 2017 +## Bug Fixes +* Fixed Unity version number parsing on machines with a locale that uses + "," for decimal points. +* Fixed null reference exception if JDK path isn't set. + +# Version 1.2.15 - Mar 17, 2017 +## New Features +* Added warning when the Jar Resolver's background resolution is disabled. +## Bug Fixes +* Fixed support of AARs with native libraries when using Gradle. +* Fixed extra repository paths when resolving dependencies. + +# Version 1.2.14 - Mar 7, 2017 +## New Features +* Added experimental Android resolution using Gradle. + This alternative resolver supports proguard stripping with Unity's + internal build system. +* Added Android support for single ABI builds when using AARs include + native libraries. +* Disabled Android resolution on changes to all .cs and .js files. + File patterns that are monitored for auto-resolution can be added + using PlayServicesResolver.AddAutoResolutionFilePatterns(). +* Added tracking of resolved AARs and JARs so they can be cleaned up + if they're no longer referenced by a project. +* Added persistence of AAR / JAR version replacement for each Unity + session. +* Added settings dialog to the iOS resolver. +* Integrated Cocoapod tool installation in the iOS resolver. +* Added option to run pod tool via the shell. +## Bug Fixes +* Fixed build of some source Cocoapods (e.g Protobuf). +* VersionHandler no longer prompts to delete obsolete manifests. +* iOS resolver handles Cocoapod installation when using Ruby < 2.2.2. +* Added workaround for package version selection when including + Google Play Services on Android. +* Fixed support for pods that reference static libraries. +* Fixed support for resource-only pods. + +# Version 1.2.12 - Feb 14, 2017 +## Bug Fixes +* Fixed re-explosion of AARs when the bundle ID is modified. + +# Version 1.2.11 - Jan 30, 2017 +## New Features +* Added support for Android Studio builds. +* Added support for native (C/C++) shared libraries in AARs. + +# Version 1.2.10 - Jan 11, 2017 +## Bug Fixes +* Fixed SDK manager path retrieval. +* Also, report stderr when it's not possible to run the "pod" tool. +* Handle exceptions thrown by Unity.Cecil on asset rename +* Fixed IOSResolver to handle PlayerSettings.iOS.targetOSVersionString + +# Version 1.2.9 - Dec 7, 2016 +## Bug Fixes +* Improved error reporting when "pod repo update" fails. +* Added detection of xml format xcode projects generated by old Cocoapods + installations. + +# Version 1.2.8 - Dec 6, 2016 +## Bug Fixes +* Increased speed of JarResolver resolution. +* Fixed JarResolver caches getting out of sync with requested dependencies + by removing the caches. +* Fixed JarResolver explode cache always being rewritten even when no + dependencies change. + +# Version 1.2.7 - Dec 2, 2016 +## Bug Fixes +* Fixed VersionHandler build errors with Unity 5.5, due to the constantly + changing BuildTarget enum. +* Added support for Unity configured JDK Path rather than requiring + JAVA_HOME to be set in the Jar Resolver. + +# Version 1.2.6 - Nov 15, 2016 +## Bug Fixes +* Fixed IOSResolver errors when iOS support is not installed. +* Added fallback to "pod" executable search which queries the Ruby Gems + package manager for the binary install location. + +# Version 1.2.5 - Nov 3, 2016 +## Bug Fixes +* Added crude support for source only Cocoapods to the IOSResolver. + +# Version 1.2.4 - Oct 27, 2016 +## Bug Fixes +* Automated resolution of out of date pod repositories. + +# Version 1.2.3 - Oct 25, 2016 +## Bug Fixes +* Fixed exception when reporting conflicting dependencies. + +# Version 1.2.2 - Oct 17, 2016 +## Bug Fixes +* Fixed issue working with Unity 5.5 +* Fixed issue with PlayServicesResolver corrupting other iOS dependencies. +* Updated build script to use Unity distributed tools for building. + +# Version 1.2.1 - Jul 25, 2016 +## Bug Fixes +* Removed 1.2 Resolver and hardcoded whitelist of AARs to expand. +* Improved error reporting when the "jar" executable can't be found. +* Removed the need to set JAVA_HOME if "jar" is in the user's path. +* Fixed spurious copying of partially matching AARs. +* Changed resolver to only copy / expand when source AARs change. +* Auto-resolution of dependencies is now performed when the Android + build target is selected. + +## New Features +* Expand AARs that contain manifests with variable expansion like + ${applicationId}. +* Added optional logging in the JarResolverLib module. +* Integration with the Android SDK manager for dependencies that + declare required Android SDK packages. + +# Version 1.2.0 - May 11 2016 +## Bug Fixes +* Handles resolving dependencies when the artifacts are split across 2 repos. +* #4 Misdetecting version for versions like 1.2-alpha. These are now string + compared if alphanumeric +* Removed resolver creation via reflection since it did not work all the time. + Now a resolver needs to be loaded externally (which is existing behavior). + +## New Features +* Expose PlayServicesResolver properties to allow for script access. +* Explodes firebase-common and firebase-measurement aar files to support + ${applicationId} substitution. + +# Version 1.1.1 - 25 Feb 2016 +## Bug Fixes +* #1 Spaces in project path not handled when exploding Aar file. +* #2 Script compilation error: TypeLoadException. + +# Version 1.1.0 - 5 Feb 2016 +## New Features +* Adds friendly alert when JAVA_HOME is not set on Windows platforms. +* Adds flag for disabling background resolution. +* Expands play-services-measurement and replaces ${applicationId} with the + bundle Id. + + ## Bug Fixes +* Fixes infinite loop of resolution triggered by resolution. diff --git a/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta b/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta new file mode 100644 index 00000000..e5662a98 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: aba4ee01c6d145f7bf2d944d892f709a +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/CHANGELOG.md +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll new file mode 100644 index 00000000..12c150e2 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta similarity index 77% rename from exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta rename to exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta index 2b2485ac..3babd47f 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: bb6999c8a5ce4ba99688ec579babe5b7 +guid: f7632a50b10045458c53a5ddf7b6d238 labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.dll - gvh -- gvh_targets-editor +- gvhp_targets-editor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb new file mode 100644 index 00000000..7dd02af5 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.mdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta similarity index 51% rename from exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.mdb.meta rename to exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta index beca11d4..0b461abd 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.mdb.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 64b62afe000c4b78962d39a83a231778 +guid: 57f5a82a79ab4b098f09326c8f3c73a6 labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.pdb - gvh timeCreated: 1538009133 licenseType: Pro diff --git a/exploded/Assets/ExternalDependencyManager/Editor/LICENSE b/exploded/Assets/ExternalDependencyManager/Editor/LICENSE new file mode 100644 index 00000000..6258cc47 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/LICENSE @@ -0,0 +1,245 @@ +Copyright (C) 2014 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==================================================================================================== +This package uses MiniJSON + +Copyright (c) 2013 Calvin Rien + +Based on the JSON parser by Patrick van Bergen +http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +Simplified it so that it doesn't throw exceptions +and can be used in Unity iPhone with maximum code stripping. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/exploded/Assets/ExternalDependencyManager/Editor/LICENSE.meta b/exploded/Assets/ExternalDependencyManager/Editor/LICENSE.meta new file mode 100644 index 00000000..30482451 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/LICENSE.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ae8b2bc8d1ac4ad48f0ab2b2e7ac75fb +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/LICENSE +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/README.md b/exploded/Assets/ExternalDependencyManager/Editor/README.md new file mode 100644 index 00000000..a9aafe9f --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/README.md @@ -0,0 +1,903 @@ +# External Dependency Manager for Unity + +[![openupm](https://img.shields.io/npm/v/com.google.external-dependency-manager?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.google.external-dependency-manager/) +[![openupm](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=downloads&query=%24.downloads&suffix=%2Fmonth&url=https%3A%2F%2Fpackage.openupm.com%2Fdownloads%2Fpoint%2Flast-month%2Fcom.google.external-dependency-manager)](https://openupm.com/packages/com.google.external-dependency-manager/) + +## Overview + +The External Dependency Manager for Unity (EDM4U) (formerly Play Services +Resolver/Jar Resolver) is intended to be used by any Unity package or user that +requires: + +* Android specific libraries (e.g + [AARs](https://developer.android.com/studio/projects/android-library.html)) + +* iOS [CocoaPods](https://cocoapods.org/) + +* Version management of transitive dependencies + +* Management of Package Manager (PM) Registries + +If you want to add and use iOS/Android dependencies directly in your project, +then you should to install EDM4U in your project. + +If you are a package user and the plugin you are using depends on EDM4U, *and* +the package does not include EDM4U as a package dependency already, then you +should to install EDM4U in your project. + +If you are a UPM package maintainer and your package requires EDM4U, then you +should add EDM4U as a +[package dependency](https://docs.unity3d.com/2019.3/Documentation/Manual/upm-dependencies.html) +in your package manifest (`package.json`): + +```json +{ + "dependencies": { + "com.google.external-dependency-manager": "1.2.178" + } +} +``` + +You should still install EDM4U to test out the package during development. + +If you are a legacy `.unitypackage` package maintainer and your package requires +EDM4U, please ask the user to install EDM4U separately. You should install EDM4U +to test out the package during development. + +Updated releases are available on +[GitHub](https://github.com/googlesamples/unity-jar-resolver) + +## Requirements + +The *Android Resolver* and *iOS Resolver* components of the plugin only work +with Unity version 4.6.8 or higher. + +The *Version Handler* component only works with Unity 5.x or higher as it +depends upon the `PluginImporter` UnityEditor API. + +The *Package Manager Resolver* component only works with Unity 2018.4 or above, +when [scoped registry](https://docs.unity3d.com/Manual/upm-scoped.html) support +was added to the Package Manager. + +## Getting Started + +Check out [troubleshooting](troubleshooting-faq.md) if you need help. + +### Install via OpenUPM + +EDM4U is available on +[OpenUPM](https://openupm.com/packages/com.google.external-dependency-manager/): + +```shell +openupm add com.google.external-dependency-manager +``` + +### Install via git URL +1. Open Package Manager +2. Click on the + icon on the top left corner of the "Package Manager" screen +3. Click on "Install package from git url..." +4. Paste: https://github.com/googlesamples/unity-jar-resolver.git?path=upm + +### Install via Google APIs for Unity + +EDM4U is available both in UPM and legacy `.unitypackage` formats on +[Google APIs for Unity](https://developers.google.com/unity/archive#external_dependency_manager_for_unity). + +You may install the UPM version (.tgz) as a +[local UPM package](https://docs.unity3d.com/Manual/upm-ui-local.html). + +You can also install EDM4U in your project as a `.unitypackage`. This is not +recommended due to potential conflicts. + +### Conflict Resolution + +For historical reasons, a package maintainer may choose to embed EDM4U in their +package for ease of installation. This will create a conflict when you try to +install EDM4U with the steps above, or with another package with embedded EDM4U. +If your project imported a `.unitypackage` that has a copy of EDM4U embedded in +it, you may safely delete it from your Assets folder. If your project depends on +another UPM package with EDM4U, please reach out to the package maintainer and +ask them to replace it with a dependency to this package. In the meantime, you +can workaround the issue by copying the package to your Packages folder (to +create an +[embedded package](https://docs.unity3d.com/Manual/upm-concepts.html#Embedded)) +and perform the steps yourself to avoid a dependency conflict. + +### Config file + +To start adding dependencies to your project, copy and rename the +[SampleDependencies.xml](https://github.com/googlesamples/unity-jar-resolver/blob/master/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml) +file into your plugin and add the dependencies your project requires. + +The XML file needs to be under an `Editor` directory and match the name +`*Dependencies.xml`. For example, `MyPlugin/Editor/MyPluginDependencies.xml`. + +## Usages + +### Android Resolver + +The Android Resolver copies specified dependencies from local or remote Maven +repositories into the Unity project when a user selects Android as the build +target in the Unity editor. + +For example, to add the Google Play Games library +(`com.google.android.gms:play-services-games` package) at version `9.8.0` to the +set of a plugin's Android dependencies: + +```xml + + + + + extra-google-m2repository + + + + +``` + +The version specification (last component) supports: + +* Specific versions e.g `9.8.0` + +* Partial matches e.g `9.8.+` would match 9.8.0, 9.8.1 etc. choosing the most + recent version + +* Latest version using `LATEST` or `+`. We do *not* recommend using this + unless you're 100% sure the library you depend upon will not break your + Unity plugin in future + +The above example specifies the dependency as a component of the Android SDK +manager such that the Android SDK manager will be executed to install the +package if it's not found. If your Android dependency is located on Maven +central it's possible to specify the package simply using the `androidPackage` +element: + +```xml + + + + + +``` + +#### Auto-resolution + +By default the Android Resolver automatically monitors the dependencies you have +specified and the `Plugins/Android` folder of your Unity project. The resolution +process runs when the specified dependencies are not present in your project. + +The *auto-resolution* process can be disabled via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. + +Manual resolution can be performed using the following menu options: + +* `Assets > External Dependency Manager > Android Resolver > Resolve` + +* `Assets > External Dependency Manager > Android Resolver > Force Resolve` + +#### Deleting libraries + +Resolved packages are tracked via asset labels by the Android Resolver. They can +easily be deleted using the `Assets > External Dependency Manager > Android +Resolver > Delete Resolved Libraries` menu item. + +#### Android Manifest Variable Processing + +Some AAR files (for example play-services-measurement) contain variables that +are processed by the Android Gradle plugin. Unfortunately, Unity does not +perform the same processing when using Unity's Internal Build System, so the +Android Resolver plugin handles known cases of this variable substitution by +exploding the AAR into a folder and replacing `${applicationId}` with the +`bundleID`. + +Disabling AAR explosion and therefore Android manifest processing can be done +via the `Assets > External Dependency Manager > Android Resolver > Settings` +menu. You may want to disable explosion of AARs if you're exporting a project to +be built with Gradle/Android Studio. + +#### ABI Stripping + +Some AAR files contain native libraries (.so files) for each ABI supported by +Android. Unfortunately, when targeting a single ABI (e.g x86), Unity does not +strip native libraries for unused ABIs. To strip unused ABIs, the Android +Resolver plugin explodes an AAR into a folder and removes unused ABIs to reduce +the built APK size. Furthermore, if native libraries are not stripped from an +APK (e.g you have a mix of Unity's x86 library and some armeabi-v7a libraries) +Android may attempt to load the wrong library for the current runtime ABI +completely breaking your plugin when targeting some architectures. + +AAR explosion and therefore ABI stripping can be disabled via the `Assets > +External Dependency Manager > Android Resolver > Settings` menu. You may want to +disable explosion of AARs if you're exporting a project to be built with +Gradle/Android Studio. + +#### Resolution Strategies + +By default the Android Resolver will use Gradle to download dependencies prior +to integrating them into a Unity project. This works with Unity's internal build +system and Gradle/Android Studio project export. + +It's possible to change the resolution strategy via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. + +##### Download Artifacts with Gradle + +Using the default resolution strategy, the Android resolver executes the +following operations: + +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project. + +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. + +- Run `download_artifacts.gradle` with Gradle to resolve conflicts and, if + successful, download the set of resolved Android libraries (AARs, JARs). + +- Process each AAR/JAR so that it can be used with the currently selected + Unity build system (e.g Internal vs. Gradle, Export vs. No Export). This + involves patching each reference to `applicationId` in the + `AndroidManifest.xml` with the project's bundle ID. This means resolution + must be run again if the bundle ID has changed. + +- Move the processed AARs to `Plugins/Android` so they will be included when + Unity invokes the Android build. + +##### Integrate into mainTemplate.gradle + +Unity 5.6 introduced support for customizing the `build.gradle` used to build +Unity projects with Gradle. When the *Patch mainTemplate.gradle* setting is +enabled, rather than downloading artifacts before the build, Android resolution +results in the execution of the following operations: + +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project and + remove sections delimited with `// Android Resolver * Start` and `// Android + Resolver * End` lines. + +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. + +- Rename any `.srcaar` files in the build to `.aar` and exclude them from + being included directly by Unity in the Android build as + `mainTemplate.gradle` will be patched to include them instead from their + local maven repositories. + +- Inject the required Gradle repositories into `mainTemplate.gradle` at the + line matching the pattern `.*apply plugin: + 'com\.android\.(application|library)'.*` or the section starting at the line + `// Android Resolver Repos Start`. If you want to control the injection + point in the file, the section delimited by the lines `// Android Resolver + Repos Start` and `// Android Resolver Repos End` should be placed in the + global scope before the `dependencies` section. + +- Inject the required Android dependencies (libraries) into + `mainTemplate.gradle` at the line matching the pattern `***DEPS***` or the + section starting at the line `// Android Resolver Dependencies Start`. If + you want to control the injection point in the file, the section delimited + by the lines `// Android Resolver Dependencies Start` and `// Android + Resolver Dependencies End` should be placed in the `dependencies` section. + +- Inject the packaging options logic, which excludes architecture specific + libraries based upon the selected build target, into `mainTemplate.gradle` + at the line matching the pattern `android +{` or the section starting at the + line `// Android Resolver Exclusions Start`. If you want to control the + injection point in the file, the section delimited by the lines `// Android + Resolver Exclusions Start` and `// Android Resolver Exclusions End` should + be placed in the global scope before the `android` section. + +#### Dependency Tracking + +The Android Resolver creates the +`ProjectSettings/AndroidResolverDependencies.xml` to quickly determine the set +of resolved dependencies in a project. This is used by the auto-resolution +process to only run the expensive resolution process when necessary. + +#### Displaying Dependencies + +It's possible to display the set of dependencies the Android Resolver would +download and process in your project via the `Assets > External Dependency +Manager > Android Resolver > Display Libraries` menu item. + +### iOS Resolver + +The iOS resolver component of this plugin manages +[CocoaPods](https://cocoapods.org/). A CocoaPods `Podfile` is generated and the +`pod` tool is executed as a post build process step to add dependencies to the +Xcode project exported by Unity. + +Dependencies for iOS are added by referring to CocoaPods. + +For example, to add the AdMob pod, version 7.0 or greater with bitcode enabled: + +```xml + + + + + +``` + +#### Integration Strategies + +The `CocoaPods` are either: + +* Downloaded and injected into the Xcode project file directly, rather than + creating a separate xcworkspace. We call this `Xcode project` integration. + +* If the Unity version supports opening a xcworkspace file, the `pod` tool is + used as intended to generate a xcworkspace which references the CocoaPods. + We call this `Xcode workspace` integration. + +The resolution strategy can be changed via the `Assets > External Dependency +Manager > iOS Resolver > Settings` menu. + +##### Appending text to generated Podfile + +In order to modify the generated Podfile you can create a script like this: + +```csharp +using System.IO; + +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEngine; + +public class PostProcessIOS : MonoBehaviour +{ + // Must be between 40 and 50 to ensure that it's not overriden by Podfile generation (40) and + // that it's added before "pod install" (50). + [PostProcessBuildAttribute(45)] + private static void PostProcessBuild_iOS(BuildTarget target, string buildPath) + { + if (target == BuildTarget.iOS) + { + using (StreamWriter sw = File.AppendText(buildPath + "/Podfile")) + { + // E.g. add an app extension + sw.WriteLine("\ntarget 'NSExtension' do\n pod 'Firebase/Messaging', '6.6.0'\nend"); + } + } + } +} +``` + +### Package Manager Resolver + +Adding registries to the +[Package Manager](https://docs.unity3d.com/Manual/Packages.html) (PM) is a +manual process. The Package Manager Resolver (PMR) component of this plugin +makes it easy for plugin maintainers to distribute new PM registry servers and +easy for plugin users to manage PM registry servers. + +#### Adding Registries + +For example, to add a registry for plugins in the scope `com.coolstuff`: + +```xml + + + + com.coolstuff + + + +``` + +When PMR is loaded it will prompt the developer to add the registry to their +project if it isn't already present in the `Packages/manifest.json` file. + +For more information, see Unity's documentation on +[scoped package registries](https://docs.unity3d.com/Manual/upm-scoped.html). + +#### Managing Registries + +It's possible to add and remove registries that are specified via PMR XML +configuration files via the following menu options: + +* `Assets > External Dependency Manager > Package Manager Resolver > Add + Registries` will prompt the user with a window which allows them to add + registries discovered in the project to the Package Manager. + +* `Assets > External Dependency Manager > Package Manager Resolver > Remove + Registries` will prompt the user with a window which allows them to remove + registries discovered in the project from the Package Manager. + +* `Assets > External Dependency Manager > Package Manager Resolver > Modify + Registries` will prompt the user with a window which allows them to add or + remove registries discovered in the project. + +#### Migration + +PMR can migrate Version Handler packages installed in the `Assets` folder to PM +packages. This requires the plugins to implement the following: + +* `.unitypackage` must include a Version Handler manifests that describes the + components of the plugin. If the plugin has no dependencies the manifest + would just include the files in the plugin. + +* The PM package JSON provided by the registry must include a keyword (in the + `versions.VERSION.keyword` list) that maps the PM package to a Version + Handler package using the format `vh-name:VERSION_HANDLER_MANIFEST_NAME` + where `VERSION_HANDLER_MANIFEST_NAME` is the name of the manifest defined in + the `.unitypackage`. For more information see the description of the + `gvhp_manifestname` asset label in the [Version Handler](#version-handler) + section. + +When using the `Assets > External Dependency Manager > Package Manager +Resolver > Migrate Packages` menu option, PMR then will: + +* List all Version Handler manager packages in the project. + +* Search all available packages in the PM registries and fetch keywords + associated with each package parsing the Version Handler manifest names for + each package. + +* Map each installed Version Handler package to a PM package. + +* Prompt the user to migrate the discovered packages. + +* Perform package migration for all selected packages if the user clicks the + `Apply` button. + +#### Configuration + +PMR can be configured via the `Assets > External Dependency Manager > Package +Manager Resolver > Settings` menu option: + +* `Add package registries` when enabled, when the plugin loads or registry + configuration files change, this will prompt the user to add registries that + are not present in the Package Manager. + +* `Prompt to add package registries` will cause a developer to be prompted + with a window that will ask for confirmation before adding registries. When + this is disabled registries are added silently to the project. + +* `Prompt to migrate packages` will cause a developer to be prompted with a + window that will ask for confirmation before migrating packages installed in + the `Assets` directory to PM packages. + +* `Enable Analytics Reporting` when enabled, reports the use of the plugin to + the developers so they can make imrpovements. + +* `Verbose logging` when enabled prints debug information to the console which + can be useful when filing bug reports. + +### Version Handler + +The Version Handler component of this plugin manages: + +* Shared Unity plugin dependencies. + +* Upgrading Unity plugins by cleaning up old files from previous versions. + +* Uninstallation of plugins that are distributed with manifest files. + +* Restoration of plugin assets to their original install locations if assets + are tagged with the `exportpath` label. + +Since the Version Handler needs to modify Unity asset metadata (`.meta` files), +to enable/disable components, rename and delete asset files it does not work +with Package Manager installed packages. It's still possible to include EDM4U in +Package Manager packages, the Version Handler component simply won't do anything +to PM plugins in this case. + +#### Using Version Handler Managed Plugins + +If a plugin is imported at multiple different versions into a project, if the +Version Handler is enabled, it will automatically check all managed assets to +determine the set of assets that are out of date and assets that should be +removed. To disable automatic checking managed assets disable the `Enable +version management` option in the `Assets > External Dependency Manager > +Version Handler > Settings` menu. + +If version management is disabled, it's possible to check managed assets +manually using the `Assets > External Dependency Manager > Version Handler > +Update` menu option. + +##### Listing Managed Plugins + +Plugins managed by the Version Handler, those that ship with manifest files, can +displayed using the `Assets > External Dependency Manager > Version Handler > +Display Managed Packages` menu option. The list of plugins are written to the +console window along with the set of files used by each plugin. + +##### Uninstalling Managed Plugins + +Plugins managed by the Version Handler, those that ship with manifest files, can +be removed using the `Assets > External Dependency Manager > Version Handler > +Uninstall Managed Packages` menu option. This operation will display a window +that allows a developer to select a set of plugins to remove which will remove +all files owned by each plugin excluding those that are in use by other +installed plugins. + +Files managed by the Version Handler, those labeled with the `gvh` asset label, +can be checked to see whether anything needs to be upgraded, disabled or removed +using the `Assets > External Dependency Manager > Version Handler > Update` menu +option. + +##### Restore Install Paths + +Some developers move assets around in their project which can make it harder for +plugin maintainers to debug issues if this breaks Unity's +[special folders](https://docs.unity3d.com/Manual/SpecialFolders.html) rules. If +assets are labeled with their original install/export path (see +`gvhp_exportpath` below), Version Handler can restore assets to their original +locations when using the `Assets > External Dependency Manager > Version +Handler > Move Files To Install Locations` menu option. + +##### Settings + +Some behavior of the Version Handler can be configured via the `Assets > +External Dependency Manager > Version Handler > Settings` menu option. + +* `Enable version management` controls whether the plugin should automatically + check asset versions and apply changes. If this is disabled the process + should be run manually when installing or upgrading managed plugins using + `Assets > External Dependency Manager > Version Handler > Update`. + +* `Rename to canonical filenames` is a legacy option that will rename files to + remove version numbers and other labels from filenames. + +* `Prompt for obsolete file deletion` enables the display of a window when + obsolete files are deleted allowing the developer to select which files to + delete and those to keep. + +* `Allow disabling files via renaming` controls whether obsolete or disabled + files should be disabled by renaming them to `myfilename_DISABLED`. Renaming + to disable files is required in some scenarios where Unity doesn't support + removing files from the build via the PluginImporter. + +* `Enable Analytics Reporting` enables/disables usage reporting to plugin + developers to improve the product. + +* `Verbose logging` enables *very* noisy log output that is useful for + debugging while filing a bug report or building a new managed plugin. + +* `Use project settings` saves settings for the plugin in the project rather + than system-wide. + +#### Redistributing a Managed Plugin + +The Version Handler employs a couple of methods for managing version selection, +upgrade and removal of plugins. + +* Each plugin can ship with a manifest file that lists the files it includes. + This makes it possible for Version Handler to calculate the difference in + assets between the most recent release of a plugin and the previous release + installed in a project. If a files are removed the Version Handler will + prompt the user to clean up obsolete files. + +* Plugins can ship using assets with unique names, unique GUIDs and version + number labels. Version numbers can be attached to assets using labels or + added to the filename (e.g `myfile.txt` would be `myfile_version-x.y.z.txt). + This allows the Version Handler to determine which set of files are the same + file at different versions, select the most recent version and prompt the + developer to clean up old versions. + +Unity plugins can be managed by the Version Handler using the following steps: + +1. Add the `gvh` asset label to each asset (file) you want Version Handler to + manage. + +1. Add the `gvh_version-VERSION` label to each asset where `VERSION` is the + version of the plugin you're releasing (e.g 1.2.3). + +1. Add the `gvhp_exportpath-PATH` label to each asset where `PATH` is the + export path of the file when the `.unitypackage` is created. This is used to + track files if they're moved around in a project by developers. + +1. Optional: Add `gvh_targets-editor` label to each editor DLL in your plugin + and disable `editor` as a target platform for the DLL. The Version Handler + will enable the most recent version of this DLL when the plugin is imported. + +1. Optional: If your plugin is included in other Unity plugins, you should add + the version number to each filename and change the GUID of each asset. This + allows multiple versions of your plugin to be imported into a Unity project, + with the Version Handler component activating only the most recent version. + +1. Create a manifest text file named `MY_UNIQUE_PLUGIN_NAME_VERSION.txt` that + lists all the files in your plugin relative to the project root. Then add + the `gvh_manifest` label to the asset to indicate this file is a plugin + manifest. + +1. Optional: Add a `gvhp_manifestname-NAME` label to your manifest file to + provide a human readable name for your package. If this isn't provided the + name of the manifest file will be used as the package name. NAME can match + the pattern `[0-9]+[a-zA-Z -]` where a leading integer will set the priority + of the name where `0` is the highest priority and preferably used as the + display name. The lowest value (i.e highest priority name) will be used as + the display name and all other specified names will be aliases of the + display name. Aliases can refer to previous names of the package allowing + renaming across published versions. + +1. Redistribute EDM4U Unity plugin with your plugin. See the + [Plugin Redistribution](#plugin-redistribution) section for details. + +If you follow these steps: + +* When users import a newer version of your plugin, files referenced by the + older version's manifest are cleaned up. + +* The latest version of the plugin will be selected when users import multiple + packages that include your plugin, assuming the steps in + [Plugin Redistribution](#plugin-redistribution) are followed. + +## Background + +Many Unity plugins have dependencies upon Android specific libraries, iOS +CocoaPods, and sometimes have transitive dependencies upon other Unity plugins. +This causes the following problems: + +* Integrating platform specific (e.g Android and iOS) libraries within a Unity + project can be complex and a burden on a Unity plugin maintainer. +* The process of resolving conflicting dependencies on platform specific + libraries is pushed to the developer attempting to use a Unity plugin. The + developer trying to use your plugin is very likely to give up when faced + with Android or iOS specific build errors. +* The process of resolving conflicting Unity plugins (due to shared Unity + plugin components) is pushed to the developer attempting to use your Unity + plugin. In an effort to resolve conflicts, the developer will very likely + attempt to resolve problems by deleting random files in your plugin, report + bugs when that doesn't work and finally give up. + +EDM4U provides solutions for each of these problems. + +### Android Dependency Management + +The *Android Resolver* component of this plugin will download and integrate +Android library dependencies and handle any conflicts between plugins that share +the same dependencies. + +Without the Android Resolver, typically Unity plugins bundle their AAR and JAR +dependencies, e.g. a Unity plugin `SomePlugin` that requires the Google Play +Games Android library would redistribute the library and its transitive +dependencies in the folder `SomePlugin/Android/`. When a user imports +`SomeOtherPlugin` that includes the same libraries (potentially at different +versions) in `SomeOtherPlugin/Android/`, the developer using `SomePlugin` and +`SomeOtherPlugin` will see an error when building for Android that can be hard +to interpret. + +Using the Android Resolver to manage Android library dependencies: + +* Solves Android library conflicts between plugins. +* Handles all of the various processing steps required to use Android + libraries (AARs, JARs) in Unity 4.x and above projects. Almost all versions + of Unity have - at best - partial support for AARs. +* (Experimental) Supports minification of included Java components without + exporting a project. + +### iOS Dependency Management + +The *iOS Resolver* component of this plugin integrates with +[CocoaPods](https://cocoapods.org/) to download and integrate iOS libraries and +frameworks into the Xcode project Unity generates when building for iOS. Using +CocoaPods allows multiple plugins to utilize shared components without forcing +developers to fix either duplicate or incompatible versions of libraries +included through multiple Unity plugins in their project. + +### Package Manager Registry Setup + +The [Package Manager](https://docs.unity3d.com/Manual/Packages.html) (PM) makes +use of [NPM](https://www.npmjs.com/) registry servers for package hosting and +provides ways to discover, install, upgrade and uninstall packages. This makes +it easier for developers to manage plugins within their projects. + +However, installing additional package registries requires a few manual steps +that can potentially be error prone. The *Package Manager Resolver* component of +this plugin integrates with [PM](https://docs.unity3d.com/Manual/Packages.html) +to provide a way to auto-install PM package registries when a `.unitypackage` is +installed which allows plugin maintainers to ship a `.unitypackage` that can +provide access to their own PM registry server to make it easier for developers +to manage their plugins. + +### Unity Plugin Version Management + +Finally, the *Version Handler* component of this plugin simplifies the process +of managing transitive dependencies of Unity plugins and each plugin's upgrade +process. + +For example, without the Version Handler plugin, if: + +* Unity plugin `SomePlugin` includes `EDM4U` plugin at version 1.1. +* Unity plugin `SomeOtherPlugin` includes `EDM4U` plugin at version 1.2. + +The version of `EDM4U` included in the developer's project depends upon the +order the developer imports `SomePlugin` or `SomeOtherPlugin`. + +This results in: + +* `EDM4U` at version 1.2, if `SomePlugin` is imported then `SomeOtherPlugin` + is imported. +* `EDM4U` at version 1.1, if `SomeOtherPlugin` is imported then `SomePlugin` + is imported. + +The Version Handler solves the problem of managing transitive dependencies by: + +* Specifying a set of packaging requirements that enable a plugin at different + versions to be imported into a Unity project. +* Providing activation logic that selects the latest version of a plugin + within a project. + +When using the Version Handler to manage `EDM4U` included in `SomePlugin` and +`SomeOtherPlugin`, from the prior example, version 1.2 will always be the +version activated in a developer's Unity project. + +Plugin creators are encouraged to adopt this library to ease integration for +their customers. For more information about integrating EDM4U into your own +plugin, see the [Plugin Redistribution](#plugin-redistribution) section of this +document. + +## Analytics + +The External Dependency Manager for Unity plugin by default logs usage to Google +Analytics. The purpose of the logging is to quantitatively measure the usage of +functionality, to gather reports on integration failures and to inform future +improvements to the developer experience of the External Dependency Manager +plugin. Note that the analytics collected are limited to the scope of the EDM4U +plugin’s usage. + +For details of what is logged, please refer to the usage of +`EditorMeasurement.Report()` in the source code. + +## Plugin Redistribution + +If you are a package maintainer and your package depends on EDM4U, it is highly +recommended to use the UPM format and add EDM4U as a dependency. If you must +include it in your `.unitypackage`, redistributing `EDM4U` inside your own +plugin might ease the integration process for your users. + +If you wish to redistribute `EDM4U` inside your plugin, you **must** follow +these steps when importing the `external-dependency-manager-*.unitypackage`, and +when exporting your own plugin package: + +1. Import the `external-dependency-manager-*.unitypackage` into your plugin + project by + [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), + ensuring that you add the `-gvh_disable` option. +1. Export your plugin by + [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), + ensuring that you: + - Include the contents of the `Assets/PlayServicesResolver` and + `Assets/ExternalDependencyManager` directory. + - Add the `-gvh_disable` option. + +You **must** specify the `-gvh_disable` option in order for the Version Handler +to work correctly! + +For example, the following command will import the +`external-dependency-manager-1.2.46.0.unitypackage` into the project +`MyPluginProject` and export the entire Assets folder to +`MyPlugin.unitypackage`: + +```shell +Unity -gvh_disable \ + -batchmode \ + -importPackage external-dependency-manager-1.2.46.0.unitypackage \ + -projectPath MyPluginProject \ + -exportPackage Assets MyPlugin.unitypackage \ + -quit +``` + +### Background + +The *Version Handler* component relies upon deferring the load of editor DLLs so +that it can run first and determine the latest version of a plugin component to +activate. The build of `EDM4U` plugin has Unity asset metadata that is +configured so that the editor components are not initially enabled when it's +imported into a Unity project. To maintain this configuration when importing the +`external-dependency-manager.unitypackage` into a Unity plugin project, you +*must* specify the command line option `-gvh_disable` which will prevent the +Version Handler component from running and changing the Unity asset metadata. + +## Building from Source + +To build this plugin from source you need the following tools installed: * Unity +2021 and below (with iOS and Android modules installed) * Java 11 + +You can build the plugin by running the following from your shell (Linux / OSX): + +```shell +./gradlew build + +``` + +or Windows: + +```shell +./gradlew.bat build +``` + +If Java 11 is not your default Java command, add +`-Dorg.gradle.java.home=` to the command above. + +## Testing + +You can run the tests by running the following from your shell (Linux / OSX): + +```shell +./gradlew test +``` + +or Windows: + +```shell +./gradlew.bat test +``` + +The following properties can be set to narrow down the tests to run or change +the test run behavior. + +* `INTERACTIVE_MODE_TESTS_ENABLED` - Default to `1`. Set to `1` to enable + interactive mode tests, which requires GPU on the machine. Otherwise, only + run tests in the batch mode. +* `INCLUDE_TEST_TYPES` - Default to empty string, which means to include every + type of the test. To narrow down the types of test to run, set this + properties with a list of case-insensitive type strings separated by comma. + For instance, `-PINCLUDE_TEST_TYPES="Python,NUnit"` means to include only + Python tests and NUnit tests. See `TestTypeEnum` in `build.gradle` for + available options. +* `EXCLUDE_TEST_TYPES` - Default to empty string, which means to exclude none. + To add types of tests to exclude, set this properties with a list of + case-insensitive type strings separated by comma. For instance, + `-PEXCLUDE_TEST_TYPES="Python,NUnit"` means to exclude Python tests and + NUnit tests. See `TestTypeEnum` in `build.gradle` for available options. +* `INCLUDE_TEST_MODULES` - Default to empty string, which means to include the + tests for every modules. To narrow down modules to test, set this properties + with a list of case-insensitive module strings separated by comma. For + instance, `-PINCLUDE_TEST_MODULES="Tool,AndroidResolver"` means to run tests + for tools and Android Resolver only. See `TestModuleEnum` in `build.gradle` + for available options. +* `EXCLUDE_TEST_MODULES` - Default to empty string, which means to exclude + none. To add modules to exclude, set this properties with a list of + case-insensitive module strings separated by comma. For instance, + `-PEXCLUDE_TEST_MODULES="Tool,AndroidResolver"` means to run tests for any + modules other than tools and Android Resolver. See `TestModuleEnum` in + `build.gradle` for available options. +* `EXCLUDE_TESTS` - Default to empty string, which means to exclude none. To + add tests to exclude, set this properties with a list of case-insensitive + test names separated by comma. For instance, + `-PEXCLUDE_TESTS="testGenGuids,testDownloadArtifacts"` means to run tests + except the tests with name of `testGenGuids` and `testDownloadArtifacts`. +* `CONTINUE_ON_FAIL_FOR_TESTS_ENABLED` - Default to `1`. Set to `1` to + continue running the next test when the current one fails. Otherwise, the + build script stops whenever any test fails. + +For instance, by running the following command, it only runs the Unity +integration tests that does not requires GPU, but exclude tests for Android +Resolver module and iOS Resolver module. + +```shell +./gradlew test \ + -PINTERACTIVE_MODE_TESTS_ENABLED=0 \ + -PINCLUDE_TEST_TYPES="Integration" \ + -PEXCLUDE_TEST_MODULES="AndroidResolver,iOSResolver" +``` + +## Releasing + +Each time a new build of this plugin is checked into the source tree you need to +do the following: + +* Bump the plugin version variable `pluginVersion` in `build.gradle` +* Update `CHANGELOG.md` with the new version number and changes included in + the release. +* Build the release using `./gradlew release` which performs the following: + * Updates `external-dependency-manager-*.unitypackage` + * Copies the unpacked plugin to the `exploded` directory. + * Updates template metadata files in the `plugin` directory. The GUIDs of + all asset metadata is modified due to the version number change. Each + file within the plugin is versioned to allow multiple versions of the + plugin to be imported into a Unity project which allows the most recent + version to be activated by the Version Handler component. +* Create release commit using `./gradlew gitCreateReleaseCommit` which + performs `git commit -a -m "description from CHANGELOG.md"` +* Once the release commit is merge, tag the release using `./gradlew + gitTagRelease` which performs the following: + * `git tag -a pluginVersion -m "version RELEASE"` to tag the release. +* Update tags on remote branch using `git push --tag REMOTE HEAD:master` diff --git a/exploded/Assets/ExternalDependencyManager/Editor/README.md.meta b/exploded/Assets/ExternalDependencyManager/Editor/README.md.meta new file mode 100644 index 00000000..6bcc2245 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/README.md.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 77919e84cef8419ab4b725fc16e83d52 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/README.md +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt b/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt new file mode 100644 index 00000000..81c97ed6 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt @@ -0,0 +1,13 @@ +Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll +Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb +Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll +Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb +Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll +Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb +Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll +Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb +Assets/ExternalDependencyManager/Editor/CHANGELOG.md +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb +Assets/ExternalDependencyManager/Editor/LICENSE +Assets/ExternalDependencyManager/Editor/README.md diff --git a/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta b/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta new file mode 100644 index 00000000..a36e708a --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: c9a3138961c74d99b7046b783112fceb +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt +- gvh +- gvh_manifest +- gvhp_manifestname-0External Dependency Manager +- gvhp_manifestname-play-services-resolver +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll deleted file mode 100644 index 36c75522..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.mdb b/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.mdb deleted file mode 100644 index e7036312..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.mdb and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll deleted file mode 100644 index 7740b287..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.mdb b/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.mdb deleted file mode 100644 index ef3ac721..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.mdb and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll deleted file mode 100644 index b888fc52..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb deleted file mode 100644 index bc725f3f..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll deleted file mode 100644 index 804332db..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.mdb b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.mdb deleted file mode 100644 index 8e8d9a9b..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.mdb and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.128.0.txt b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.128.0.txt deleted file mode 100644 index cbcaf2a7..00000000 --- a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.128.0.txt +++ /dev/null @@ -1,8 +0,0 @@ -Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll -Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.mdb -Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll -Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.mdb -Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll -Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb -Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll -Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.mdb diff --git a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt new file mode 100644 index 00000000..a0268fcc --- /dev/null +++ b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt @@ -0,0 +1,2 @@ +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb diff --git a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.128.0.txt.meta b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta similarity index 72% rename from exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.128.0.txt.meta rename to exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta index 0df143e4..af4c6c44 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.128.0.txt.meta +++ b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 -guid: 4370c5c4a530458c907b513d2b893f6d +guid: ba6f911c6f9d4d9ea269756e9dafb641 labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.137.0 - gvh - gvh_manifest timeCreated: 1474401009 diff --git a/export_unity_package_config.json b/export_unity_package_config.json new file mode 100644 index 00000000..78b83ecb --- /dev/null +++ b/export_unity_package_config.json @@ -0,0 +1,96 @@ +{ + "packages": [ + { + "name": "external-dependency-manager.unitypackage", + "imports": [ + { + "importer": "PluginImporter", + "platforms": ["Editor"], + "paths": [ + "ExternalDependencyManager/Editor/Google.VersionHandler.*" + ] + }, + { + "importer": "PluginImporter", + "platforms": [], + "labels": ["gvhp_targets-editor"], + "paths": [ + "ExternalDependencyManager/Editor/*/Google.IOSResolver.*", + "ExternalDependencyManager/Editor/*/Google.JarResolver.*", + "ExternalDependencyManager/Editor/*/Google.VersionHandlerImpl.*", + "ExternalDependencyManager/Editor/*/Google.PackageManagerResolver.*" + ], + "override_metadata_upm": { + "PluginImporter": { + "platformData": [ { + "first" : { + "Editor": "Editor" + }, + "second": { + "enabled": 1 + } + } + ] + } + } + }, + { + "sections": ["documentation"], + "importer": "DefaultImporter", + "paths": [ + "ExternalDependencyManager/Editor/README.md", + "ExternalDependencyManager/Editor/CHANGELOG.md", + "ExternalDependencyManager/Editor/LICENSE" + ] + }, + { + "importer": "DefaultImporter", + "paths": [ + "ExternalDependencyManager/Editor/external-dependency-manager*_manifest.txt" + ] + }, + { + "sections": ["unitypackage"], + "importer": "DefaultImporter", + "paths": [ + "PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt" + ] + } + ], + "manifest_path": "ExternalDependencyManager/Editor", + + "readme": "ExternalDependencyManager/Editor/README.md", + "license": "ExternalDependencyManager/Editor/LICENSE", + "changelog": "ExternalDependencyManager/Editor/CHANGELOG.md", + "documentation": "ExternalDependencyManager/Editor/README.md", + + "common_manifest" : { + "name": "com.google.external-dependency-manager", + "display_name": "External Dependency Manager for Unity", + "description": [ + "External Dependency Manager for Unity (EDM4U) can be used by any ", + "Unity plugin that requires Android specific libraries (e.g. AARs), ", + "iOS CocoaPods, version management of transitive dependencies, ", + "and/or management of Unity Package Manager registries." + ], + "keywords": [ + "Google", "Android", "Gradle", "Cocoapods", "Dependency", + "Unity Package Manager", "Unity", + "vh-name:play-services-resolver", + "vh-name:unity-jar-resolver" + ], + "author": { + "name" : "Google LLC", + "url": "https://github.com/googlesamples/unity-jar-resolver" + } + }, + + "export_upm" : 1, + "upm_package_config" : { + "manifest" : { + "unity": "2019.1" + } + } + } + ] +} diff --git a/export_unity_package_guids.json b/export_unity_package_guids.json new file mode 100644 index 00000000..59c045f4 --- /dev/null +++ b/export_unity_package_guids.json @@ -0,0 +1,73 @@ +{ + "1.2.137": { + "com.google.external-dependency-manager/CHANGELOG.md": "dd6a29a412594aadb37d9698db325eca", + "com.google.external-dependency-manager/ExternalDependencyManager": "46f5870ddbde4a6091f50656dcd5573e", + "com.google.external-dependency-manager/ExternalDependencyManager/Editor": "1f23cd25474841f6ad7555705b4807e9", + "com.google.external-dependency-manager/LICENSE.md": "f61a1c8e753b496bb696e77d7eedfb95", + "com.google.external-dependency-manager/README.md": "cbbebcaa6ecb4b9582dce440a386de75", + "com.google.external-dependency-manager/package.json": "9bed450d5c03481d87e61b61431cf00a" + }, + "1.2.166": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.166": "9dfec94683154487ab08de0c50179674" + }, + "1.2.167": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.167": "75c93e39c49442c9a6c4aea09cc5b982" + }, + "1.2.168": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.168": "224d14327d134b0ea5ba8ce2deaa6768" + }, + "1.2.169": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.169": "02ea964fdc804b098a302e49a57d8cd3" + }, + "1.2.170": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.170": "cd46d4272b824509b5b5544ec9b34870" + }, + "1.2.171": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.171": "8281940745814cc5b50954561f0e2582" + }, + "1.2.172": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.172": "3f9604d812054a74a5726176ad683fb3" + }, + "1.2.173": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.173": "fdeeb5a9eafe4d2bbd8a94a41eb5302e" + }, + "1.2.174": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.174": "2a82085e2b06441b96c360b7fe97f98c" + }, + "1.2.175": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.175": "ba0c697ad191428fac6b53f4ca8f8d4a" + }, + "1.2.176": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.176": "864a72f7c7cf4153a532cc1277dbfbdb" + }, + "1.2.177": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.177": "a768f4126d3b41ac900586e5a3b2d4a1" + }, + "1.2.178": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.178": "3d894de101f34a4b9a7f42e13c7aa3a3" + }, + "1.2.179": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.179": "e4b2f028f2e949cfac3ba5bbb128d139" + }, + "1.2.180": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.180": "e8f303eb74064b86b1308e81afd52ce1" + }, + "1.2.181": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.181": "d27fe564f10147d1bfded8244b25b494" + }, + "1.2.182": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.182": "60e9ce1be3474de5bdded37d1462e09b" + }, + "1.2.183": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.183": "5c127f68af44472d8099a80364a25718" + }, + "1.2.184": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.184": "297013161bc34ce4ac7b8ffba2384862" + }, + "1.2.185": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.185": "eb66eed64a99451d81abd63ff417f837" + }, + "1.2.186": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.186": "8a6a9bd2649d4370b43be75e1689748c" + } +} diff --git a/external-dependency-manager-1.2.186.unitypackage b/external-dependency-manager-1.2.186.unitypackage new file mode 100644 index 00000000..37e58cd2 Binary files /dev/null and b/external-dependency-manager-1.2.186.unitypackage differ diff --git a/external-dependency-manager-latest.unitypackage b/external-dependency-manager-latest.unitypackage new file mode 100644 index 00000000..37e58cd2 Binary files /dev/null and b/external-dependency-manager-latest.unitypackage differ diff --git a/gha/build_setup/action.yml b/gha/build_setup/action.yml new file mode 100644 index 00000000..aaacd456 --- /dev/null +++ b/gha/build_setup/action.yml @@ -0,0 +1,72 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reusable cross-platform workflow to setup for the build environment. +# * Install Python and required Python packages +# * Install Unity, platform build support, and register license if +# username, password and serial id are provided +name: 'Build Setup' + +inputs: + unity_version: + required: true + platform: + description: 'Platform to install Unity on (Windows,macOS,Linux)' + type: choice + options: + - Windows + - macOS + - Linux + required: true + unity_username: + required: false + unity_password: + required: false + unity_serial_id: + required: false + python_version: + required: true + +runs: + using: 'composite' + steps: + # Download GHA tools and requirements from Firebase Unity SDK repo + - uses: actions/checkout@v3 + with: + repository: firebase/firebase-unity-sdk + path: external/firebase-unity-sdk + sparse-checkout: | + scripts/gha/requirements.txt + sparse-checkout-cone-mode: false + + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python_version }} + + - name: Install python deps + shell: bash + run: | + pip install -r ./external/firebase-unity-sdk/scripts/gha/requirements.txt + + - name: Install Unity and get a license + uses: firebase/firebase-unity-sdk/gha/unity@main + with: + version: ${{ inputs.unity_version }} + # iOS build support is always required to build EDM4U + platforms: "${{ inputs.platform }},iOS,Android" + username: ${{ inputs.unity_username }} + password: ${{ inputs.unity_password }} + serial_ids: ${{ inputs.unity_serial_id }} + diff --git a/play-services-resolver-1.2.128.0.unitypackage b/play-services-resolver-1.2.128.0.unitypackage deleted file mode 100644 index d4f82e33..00000000 Binary files a/play-services-resolver-1.2.128.0.unitypackage and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager.meta similarity index 52% rename from exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager.meta index f83a0a80..1a04a8e0 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager.meta @@ -1,9 +1,7 @@ fileFormatVersion: 2 -guid: 3aa17cd5f43042a1a7381759dc1258a7 -labels: -- gvh_version-1.2.128.0 -- gvh -timeCreated: 1538009133 +guid: e7f679112961a0f7f11fdb3f983aed77 +folderAsset: yes +timeCreated: 1448926447 licenseType: Pro DefaultImporter: userData: diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor.meta similarity index 52% rename from exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager/Editor.meta index e1e6bb9c..4ef59616 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor.meta @@ -1,9 +1,7 @@ fileFormatVersion: 2 -guid: 3fb866bdba4f410996179b1966bd415d -labels: -- gvh_version-1.2.128.0 -- gvh -timeCreated: 1538009133 +guid: b42aa8acaabecbf943da2892de5e6aeb +folderAsset: yes +timeCreated: 1448926516 licenseType: Pro DefaultImporter: userData: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta b/plugin/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta new file mode 100644 index 00000000..e5662a98 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: aba4ee01c6d145f7bf2d944d892f709a +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/CHANGELOG.md +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta similarity index 50% rename from plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta index e1e6bb9c..3e39629c 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 3fb866bdba4f410996179b1966bd415d +guid: adacdf2f31cf474c99788c9454063fed labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.IOSResolver.dll.mdb - gvh timeCreated: 1538009133 licenseType: Pro diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.meta new file mode 100644 index 00000000..707c589b --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: e2d7ea0845de4cf984265d2a444b7aa4 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + validateReferences: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.pdb.meta new file mode 100644 index 00000000..e6b1e059 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: baf24db2bf904e729e7796721c09e8ad +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta similarity index 50% rename from plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta index beca11d4..e5cd206a 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 64b62afe000c4b78962d39a83a231778 +guid: c613343662334614b65918fa6cf9c17e labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.JarResolver.dll.mdb - gvh timeCreated: 1538009133 licenseType: Pro diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta similarity index 76% rename from exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.meta rename to plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta index c9c306ee..a1398e9f 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.128.0.dll.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: 519a52f7f22e4953a1f8eb29922145e1 +guid: fa49a85d4ba140a0ae21528ed12d174c labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll - gvh -- gvh_targets-editor +- gvhp_targets-editor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.pdb.meta new file mode 100644 index 00000000..12826037 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d13c8602d5e14e43b0e92459754c4315 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.mdb.meta new file mode 100644 index 00000000..9c80a012 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.mdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b60e304f823d423da748809d088d67b1 +labels: +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.PackageManagerResolver.dll.mdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.meta new file mode 100644 index 00000000..3b4fa84b --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: d8bb10c56a0147bc855a6296778e025e +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.pdb.meta new file mode 100644 index 00000000..ac071adc --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a695eb9f64fe49569a2db0c4246c877d +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta similarity index 51% rename from plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta index f83a0a80..442c422b 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 3aa17cd5f43042a1a7381759dc1258a7 +guid: 5855ffeab65945dc8f9cb3dc063f9eba labels: -- gvh_version-1.2.128.0 +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb - gvh timeCreated: 1538009133 licenseType: Pro diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta new file mode 100644 index 00000000..3babd47f --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: f7632a50b10045458c53a5ddf7b6d238 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta new file mode 100644 index 00000000..0b461abd --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 57f5a82a79ab4b098f09326c8f3c73a6 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.mdb.meta new file mode 100644 index 00000000..6569dce4 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.mdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bd203ff120ed4c69bfdeffa466beec72 +labels: +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.VersionHandlerImpl.dll.mdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.meta new file mode 100644 index 00000000..7456068f --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: 5980a684c61d42fbb6b74e2eb3477016 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.pdb.meta new file mode 100644 index 00000000..da5b09b2 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9f56badf3ca84753b00163c3b632d4e5 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/LICENSE.meta b/plugin/Assets/ExternalDependencyManager/Editor/LICENSE.meta new file mode 100644 index 00000000..30482451 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/LICENSE.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ae8b2bc8d1ac4ad48f0ab2b2e7ac75fb +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/LICENSE +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/README.md.meta b/plugin/Assets/ExternalDependencyManager/Editor/README.md.meta new file mode 100644 index 00000000..6bcc2245 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/README.md.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 77919e84cef8419ab4b725fc16e83d52 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/README.md +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/external-dependency-manager.txt.meta b/plugin/Assets/ExternalDependencyManager/Editor/external-dependency-manager.txt.meta new file mode 100644 index 00000000..0a44bf71 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/external-dependency-manager.txt.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: 20a3aeaa6495bf32b31b6980c7977b87 +labels: +- gvh_version-0.0.0.0 +- gvhp_exportpath-ExternalDependencyManager/Editor/external-dependency-manager.txt +- gvh +- gvh_manifest +- gvhp_manifestname-0External Dependency Manager +- gvhp_manifestname-play-services-resolver +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.meta b/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.meta deleted file mode 100644 index 82c309a5..00000000 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.meta +++ /dev/null @@ -1,24 +0,0 @@ -fileFormatVersion: 2 -guid: 1d2d3ae96d464e3a87349618b43303e3 -labels: -- gvh_version-0.0.0.0 -- gvh -- gvh_targets-editor -timeCreated: 1473798236 -licenseType: Pro -PluginImporter: - serializedVersion: 1 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - platformData: - Any: - enabled: 0 - settings: {} - Editor: - enabled: 1 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.mdb.meta b/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.mdb.meta deleted file mode 100644 index c7261431..00000000 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.mdb.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b0c0bcc139fb4135aa08213dbad27b2a -labels: -- gvh_version-1.2.128.0 -- gvh -timeCreated: 1538009133 -licenseType: Pro -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta index 47f5ad9d..af4c6c44 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta +++ b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: ba6f911c6f9d4d9ea269756e9dafb641 labels: -- gvh_version-0.0.0.0 +- gvh_version-1.2.137.0 - gvh - gvh_manifest timeCreated: 1474401009 diff --git a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt new file mode 100644 index 00000000..a0268fcc --- /dev/null +++ b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt @@ -0,0 +1,2 @@ +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb diff --git a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta new file mode 100644 index 00000000..af4c6c44 --- /dev/null +++ b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ba6f911c6f9d4d9ea269756e9dafb641 +labels: +- gvh_version-1.2.137.0 +- gvh +- gvh_manifest +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/registry/com.google.unity.example/gpm-example-plugin/1.0.0/description.xml b/registry/com.google.unity.example/gpm-example-plugin/1.0.0/description.xml deleted file mode 100644 index a0fcd226..00000000 --- a/registry/com.google.unity.example/gpm-example-plugin/1.0.0/description.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - GPM Example Plugin - A demonstration of a Google Package Manager compatable plugin. - This plugin demonstrates how a Unity plugin can be created with the Google Package Manager Packager and be available from a Google Package Manager registry. - - - diff --git a/registry/com.google.unity.example/gpm-example-plugin/1.0.0/gpm-example-plugin.unitypackage b/registry/com.google.unity.example/gpm-example-plugin/1.0.0/gpm-example-plugin.unitypackage deleted file mode 100644 index b89606e1..00000000 Binary files a/registry/com.google.unity.example/gpm-example-plugin/1.0.0/gpm-example-plugin.unitypackage and /dev/null differ diff --git a/registry/com.google.unity.example/package-manifest.xml b/registry/com.google.unity.example/package-manifest.xml deleted file mode 100644 index bc845273..00000000 --- a/registry/com.google.unity.example/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.example - gpm-example-plugin - unitypackage - 1.0.0 - - 1.0.0 - - 1.0.0 - - - 0 - diff --git a/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/description.xml b/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/description.xml deleted file mode 100755 index 9e9f819a..00000000 --- a/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/description.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Google Firebase Analytics - Official Google Firebase Analytics plugin for Unity - TODO: add full description - - - \ No newline at end of file diff --git a/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/firebase-analytics-plugin.unitypackage b/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/firebase-analytics-plugin.unitypackage deleted file mode 100755 index 16122b92..00000000 Binary files a/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/firebase-analytics-plugin.unitypackage and /dev/null differ diff --git a/registry/com.google.unity.firebase.analytics/package-manifest.xml b/registry/com.google.unity.firebase.analytics/package-manifest.xml deleted file mode 100755 index ab9902bf..00000000 --- a/registry/com.google.unity.firebase.analytics/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.firebase.analytics - firebase-analytics-plugin - 1.1.0 - unitypackage - - 1.1.0 - - 1.1.0 - - - 0 - \ No newline at end of file diff --git a/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/description.xml b/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/description.xml deleted file mode 100755 index e414088b..00000000 --- a/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/description.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Google Play Games Services - Official Google Play Games plugin for Unity - The Google Play Games plugin for Unity® is an open-source project whose goal is to provide a plugin that allows game developers to integrate with the Google Play Games API from a game written in Unity®. However, this project is not in any way endorsed or supervised by Unity Technologies. Unity® is a trademark of Unity Technologies. iOS is a trademark of Apple, Inc. - - - \ No newline at end of file diff --git a/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/google-playgames-plugin.unitypackage b/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/google-playgames-plugin.unitypackage deleted file mode 100755 index a4583c89..00000000 Binary files a/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/google-playgames-plugin.unitypackage and /dev/null differ diff --git a/registry/com.google.unity.playgames/package-manifest.xml b/registry/com.google.unity.playgames/package-manifest.xml deleted file mode 100755 index dc142d5e..00000000 --- a/registry/com.google.unity.playgames/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.playgames - google-playgames-plugin - unitypackage - 0.9.36 - - 0.9.36 - - 0.9.36 - - - 0 - diff --git a/registry/registry.xml b/registry/registry.xml deleted file mode 100644 index ff49d0e7..00000000 --- a/registry/registry.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - registry.google.unity - jarresolver-google-registry - 0.0.1.1 - 1482171836 - - com.google.unity.example - com.google.unity.playgames - com.google.unity.firebase.analytics - - diff --git a/sample/Assets/PlayServicesResolver/Editor/ResolutionRunner.cs b/sample/Assets/ExternalDependencyManager/Editor/ResolutionRunner.cs similarity index 100% rename from sample/Assets/PlayServicesResolver/Editor/ResolutionRunner.cs rename to sample/Assets/ExternalDependencyManager/Editor/ResolutionRunner.cs diff --git a/sample/Assets/PlayServicesResolver/Editor/Resolver.cs b/sample/Assets/ExternalDependencyManager/Editor/Resolver.cs similarity index 100% rename from sample/Assets/PlayServicesResolver/Editor/Resolver.cs rename to sample/Assets/ExternalDependencyManager/Editor/Resolver.cs diff --git a/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.cs b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.cs similarity index 100% rename from sample/Assets/PlayServicesResolver/Editor/SampleDependencies.cs rename to sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.cs diff --git a/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.xml b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml similarity index 94% rename from sample/Assets/PlayServicesResolver/Editor/SampleDependencies.xml rename to sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml index de11de0b..97c27dec 100644 --- a/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.xml +++ b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml @@ -67,6 +67,9 @@ generated Xcode project. This is "true" by default. * "minTargetSdk" (optional) The minimum iOS SDK required by this Cocoapod. + * "addToAllTargets" (optional) + Whether to add this pod to all targets when multiple target is + supported. This is "false" by default. * "configurations" (optional) Podfile formatted list of configurations to include this pod in. * "modular_headers" (optional) @@ -77,7 +80,7 @@ Subspecs to include for the pod. --> + minTargetSdk="6.0" addToAllTargets="false"> + + + + + + + + com.mycompany + + + diff --git a/source/PlayServicesResolver/PlayServicesResolver.csproj b/source/AndroidResolver/AndroidResolver.csproj similarity index 99% rename from source/PlayServicesResolver/PlayServicesResolver.csproj rename to source/AndroidResolver/AndroidResolver.csproj index 004d71c8..08059780 100644 --- a/source/PlayServicesResolver/PlayServicesResolver.csproj +++ b/source/AndroidResolver/AndroidResolver.csproj @@ -53,7 +53,6 @@ - diff --git a/source/PlayServicesResolver/Properties/AssemblyInfo.cs b/source/AndroidResolver/Properties/AssemblyInfo.cs similarity index 93% rename from source/PlayServicesResolver/Properties/AssemblyInfo.cs rename to source/AndroidResolver/Properties/AssemblyInfo.cs index b4787d66..2d9fbd82 100644 --- a/source/PlayServicesResolver/Properties/AssemblyInfo.cs +++ b/source/AndroidResolver/Properties/AssemblyInfo.cs @@ -55,4 +55,6 @@ // Uses XmlDependencies class. [assembly: InternalsVisibleTo("Google.IOSResolver")] - +// Uses all classes for testing. +[assembly: InternalsVisibleTo("Google.AndroidResolverIntegrationTests")] +[assembly: InternalsVisibleTo("Google.AndroidResolverTests")] diff --git a/source/PlayServicesResolver/scripts/build.gradle b/source/AndroidResolver/scripts/build.gradle similarity index 100% rename from source/PlayServicesResolver/scripts/build.gradle rename to source/AndroidResolver/scripts/build.gradle diff --git a/source/PlayServicesResolver/scripts/download_artifacts.gradle b/source/AndroidResolver/scripts/download_artifacts.gradle similarity index 98% rename from source/PlayServicesResolver/scripts/download_artifacts.gradle rename to source/AndroidResolver/scripts/download_artifacts.gradle index c7a49245..7d99ec35 100644 --- a/source/PlayServicesResolver/scripts/download_artifacts.gradle +++ b/source/AndroidResolver/scripts/download_artifacts.gradle @@ -42,7 +42,7 @@ USE_MAVEN_LOCAL_REPO (optional project property): By default local maven repositories are enabled. USE_REMOTE_MAVEN_REPOS (optional project property): Optional property which, when not set to 1, disables the implicit use of - remote repositories (maven.google.com, jcenter and maven central) when + remote repositories (maven.google.com and maven central) when fetching artifacts. By default remote repositories are enabled. USE_JETIFIER (optional project property): Optional project which, when not set to 1, disables the Jetifier tool to @@ -70,7 +70,6 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() google() } dependencies { @@ -439,6 +438,8 @@ public class PackageSpecifier implements Comparable { 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 = "" @@ -446,24 +447,39 @@ public class PackageSpecifier implements Comparable { * Extract the components of a package specifier string. * * @param packageSpecifier Package specification. - * Package specification should match agroup:apackage:version@artifacttype - * e.g a.b.c:d.e:1.2.3@aar + * Package specification should match one of the following formats, + * - agroup:apackage:version@artifacttype + * e.g a.b.c:d.e:1.2.3@aar + * - agroup:apackage:version:classifier@artifacttype + * e.g a.b.c:d.e:1.2.3:f@aar * - * @returns [group, artifact, version, artifactType] list with the components + * @returns [group, artifact, version, artifactType, classifier] list with the components * of the package spec. If a component is not present the entry in the list * is null. */ public static PackageSpecifier fromString(String packageSpecifierString) { List components = packageSpecifierString ? packageSpecifierString.split(/[:@]/) : [] + if (components.size() == 5) { + // Special case to handle group:artifact:version:classifier@artifactType, + // We want to maintain the components list as, + // [group, artifact, version, artifactType, classifier] + // because classifiers are a rare case and more often than not, the parser + // will find artifactType in the second to last position. + // Hence, if there are 5 components, swap the last two to make sure + // artifactType is second to last. + components.swap(3,4) + } // Fill the list of components with null elements. - components += ([""] * Math.min(4, (4 - components.size()))) + components += ([""] * Math.min(5, (5 - components.size()))) + return new PackageSpecifier( group: components[0], artifact: components[1], versionExpression: components[2] ? (VersionRange.fromExpression( components[2])).expression : "", - artifactType: components[3]) + artifactType: components[3], + classifier: components[4]) } /* @@ -508,6 +524,7 @@ public class PackageSpecifier implements Comparable { return resolvedArtifact.with { PackageSpecifier pkg = fromModuleVersionIdentifier(moduleVersion.id) pkg.artifactType = type + pkg.classifier = classifier return pkg } } @@ -613,7 +630,11 @@ public class PackageSpecifier implements Comparable { components.add(artifact) if (versionExpression) { if (artifactType) { - components.add(versionExpression + "@" + artifactType) + if (classifier) { + components.add(versionExpression + ':' + classifier + "@" + artifactType) + } else { + components.add(versionExpression + "@" + artifactType) + } } else { components.add(versionExpression) } @@ -665,6 +686,9 @@ public class PackageSpecifier implements Comparable { if (versionExpression) { hypenSeparatedComponents += [versionExpression] } + if (classifier) { + hypenSeparatedComponents += [classifier] + } String filename = hypenSeparatedComponents.join("-") if (artifactType) { filename += "." + (artifactType == "srcaar" ? "aar" : artifactType) @@ -1345,6 +1369,7 @@ Tuple2> loosenVersionContraintsForConflicts( new PackageSpecifier( group: pkg.group, artifact: pkg.artifact, + classifier: pkg.classifier, versionExpression: ( isConflicting ? pkg.versionRange.matchType == VersionExpressionMatchType.RANGE ? @@ -2495,7 +2520,6 @@ project.ext { } if (useMavenLocalRepo) mavenLocal() if (useRemoteMavenRepos) { - jcenter() mavenCentral() } } diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test.gradle b/source/AndroidResolver/scripts/download_artifacts_test.gradle similarity index 98% rename from source/PlayServicesResolver/scripts/download_artifacts_test.gradle rename to source/AndroidResolver/scripts/download_artifacts_test.gradle index a9c3ced8..02d8cd54 100644 --- a/source/PlayServicesResolver/scripts/download_artifacts_test.gradle +++ b/source/AndroidResolver/scripts/download_artifacts_test.gradle @@ -418,6 +418,21 @@ createTestTask( ["Copied artifacts:\n" + "com.android.support.support-annotations-23.0.1.magic"]) + createTestTask( + "testDownloadAvailableWithClassifier", + "Downloads artifacts with one of them having a classifier in the name.", + "org.test.psr:classifier:1.0.1:foo@aar;" + + "com.android.support:support-annotations:26.1.0;", + ["org.test.psr.classifier-1.0.1-foo.aar": + "org/test/psr/classifier/1.0.1/" + + "org.test.psr.classifier-1.0.1-foo.aar", + "com.android.support.support-annotations-26.1.0.jar": + "com/android/support/support-annotations/26.1.0/" + + "support-annotations-26.1.0.jar"], + ["Copied artifacts:\n" + + "com.android.support.support-annotations-26.1.0.jar\n" + + "org.test.psr.classifier-1.0.1-foo.aar"]) + createTestTask( "testDownloadAvailableTwice", "Downloads a single artifact and it's dependencies from maven.", diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.jar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.jar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.jar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-ui/1.0.0/legacy-support-core-ui-1.0.0.aar rename to 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 diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-ui/1.0.0/legacy-support-core-ui-1.0.0.pom rename to 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 diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-utils/1.0.0/legacy-support-core-utils-1.0.0.aar rename to 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 diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-utils/1.0.0/legacy-support-core-utils-1.0.0.pom rename to 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 diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.jar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.aar diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.pom diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/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 rename to 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 diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.srcaar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.srcaar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.srcaar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.srcaar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml diff --git a/source/PlayServicesResolver/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 similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/mirror_from_gradle_cache.sh rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/mirror_from_gradle_cache.sh diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom new file mode 100644 index 00000000..9e3d37d0 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + classifier + 1.0.1 + aar + + + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/maven-metadata.xml new file mode 100644 index 00000000..ba1496c5 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/maven-metadata.xml @@ -0,0 +1,13 @@ + + org.test.psr + classifier + + 1.0.1 + + 1.0.1 + + + + + + diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh diff --git a/source/PlayServicesResolver/scripts/file_to_maven_package.gradle b/source/AndroidResolver/scripts/file_to_maven_package.gradle similarity index 99% rename from source/PlayServicesResolver/scripts/file_to_maven_package.gradle rename to source/AndroidResolver/scripts/file_to_maven_package.gradle index e753c29b..1629a70b 100644 --- a/source/PlayServicesResolver/scripts/file_to_maven_package.gradle +++ b/source/AndroidResolver/scripts/file_to_maven_package.gradle @@ -64,7 +64,6 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() } dependencies { classpath "commons-io:commons-io:2.6" diff --git a/source/PlayServicesResolver/scripts/generate_test_maven_package.sh b/source/AndroidResolver/scripts/generate_test_maven_package.sh similarity index 100% rename from source/PlayServicesResolver/scripts/generate_test_maven_package.sh rename to source/AndroidResolver/scripts/generate_test_maven_package.sh diff --git a/source/PlayServicesResolver/scripts/gradle-template.zip b/source/AndroidResolver/scripts/gradle-template.zip similarity index 98% rename from source/PlayServicesResolver/scripts/gradle-template.zip rename to source/AndroidResolver/scripts/gradle-template.zip index e26323b9..db181f66 100644 Binary files a/source/PlayServicesResolver/scripts/gradle-template.zip and b/source/AndroidResolver/scripts/gradle-template.zip differ diff --git a/source/PlayServicesResolver/scripts/settings.gradle b/source/AndroidResolver/scripts/settings.gradle similarity index 100% rename from source/PlayServicesResolver/scripts/settings.gradle rename to source/AndroidResolver/scripts/settings.gradle diff --git a/source/PlayServicesResolver/src/AndroidAbis.cs b/source/AndroidResolver/src/AndroidAbis.cs similarity index 93% rename from source/PlayServicesResolver/src/AndroidAbis.cs rename to source/AndroidResolver/src/AndroidAbis.cs index 4ca7e17e..20b12c3d 100644 --- a/source/PlayServicesResolver/src/AndroidAbis.cs +++ b/source/AndroidResolver/src/AndroidAbis.cs @@ -34,7 +34,7 @@ private class PropertyConfiguration { public enum Mode { FatOnly, // Only supports fat (armeabi-v7a & x86) builds. OneOfArmX86Fat, // Supports one of armeabi-v7a, x86 or fat builds. - AnyOfArmX86Arm64, // Supports any combination of armeabi-v7a, x86 or arm64 builds. + AnyOfArmX86Arm64, // Supports any combination of armeabi-v7a, x86, x86_64 or arm64 builds. } /// @@ -74,7 +74,8 @@ public PropertyConfiguration() { foreach (var abi in new Dictionary() { {"armeabi-v7a", "ARMv7"}, {"arm64-v8a", "ARM64"}, - {"x86", "X86"} + {"x86", "X86"}, + {"x86_64", "X86_64"} }) { try { EnumValueStringToULong(EnumType, abi.Value); @@ -143,7 +144,7 @@ public AndroidAbis(IEnumerable abisSet) { /// /// Create a set of ABIs from a comma separated set of ABI strings. /// - /// Set of ABI strings. + /// Comma separated set of ABI strings. public AndroidAbis(string abiString) { if (String.IsNullOrEmpty(abiString)) { abis = new HashSet(Supported); @@ -190,7 +191,7 @@ public override bool Equals(System.Object obj) { /// Get the supported set of Android ABIs for the current Unity version. /// The dictionary maps the official Android ABI name (i.e the directory name looked up by the /// operating system) to the UnityEditor.AndroidTargetDevice (Unity 5.x & 2017.x) or - // UnityEditor.AndroidArchitecture enumeration value name. + /// UnityEditor.AndroidArchitecture enumeration value name. /// private static Dictionary SupportedAbiToAbiEnumValue { get { return PropertyConfiguration.Instance.SupportedAbiToAbiEnumValue; } @@ -221,10 +222,10 @@ public static IEnumerable AllSupported { /// /// Enum object to convert. private static ulong EnumValueObjectToULong(object enumValueObject) { - /// Flags enum values can't be cast directly to an integral type, however it is possible to - /// print the value as an integer string so convert to a string and then parse as an int. - /// Enums are considered unsigned by the formatter, so if an enum is defined as -1 it will - /// be formatted as UInt32.MaxValue, i.e. 4294967295. + // Flags enum values can't be cast directly to an integral type, however it is possible to + // print the value as an integer string so convert to a string and then parse as an int. + // Enums are considered unsigned by the formatter, so if an enum is defined as -1 it will + // be formatted as UInt32.MaxValue, i.e. 4294967295. return UInt64.Parse(String.Format("{0:D}", enumValueObject)); } @@ -239,8 +240,9 @@ private static ulong EnumValueStringToULong(Type enumType, string enumValueStrin /// /// Get / set the target device ABI (Unity >= 5.0.x) + /// Unity >= 2019.4 supports armeabi-v7a, arm64-v8a, x86, x86_64 & fat (i.e armeabi-v7a, arm64, x86, x86_64) /// Unity >= 2017.4 supports armeabi-v7a, arm64-v8a, x86 & fat (i.e armeabi-v7a, arm64, x86) - /// Unity >= 5.0.x & <= 2017.3 only support armeabi-v7a, x86 & fat (i.e armeabi-v7a & x86) + /// Unity >= 5.0.x & <= 2017.3 only support armeabi-v7a, x86 & fat (i.e armeabi-v7a & x86) /// public static AndroidAbis Current { set { diff --git a/source/PlayServicesResolver/src/AndroidSdkManager.cs b/source/AndroidResolver/src/AndroidSdkManager.cs similarity index 94% rename from source/PlayServicesResolver/src/AndroidSdkManager.cs rename to source/AndroidResolver/src/AndroidSdkManager.cs index 7b9311c6..28df9b04 100644 --- a/source/PlayServicesResolver/src/AndroidSdkManager.cs +++ b/source/AndroidResolver/src/AndroidSdkManager.cs @@ -404,6 +404,8 @@ internal class SdkManagerUtil { /// Called when the query is complete. public static void QueryPackages(string toolPath, string toolArguments, Action complete) { + PlayServicesResolver.analytics.Report("/androidsdkmanager/querypackages", + "Android SDK Manager: Query Packages"); var window = CommandLineDialog.CreateCommandLineDialog( "Querying Android SDK packages"); PlayServicesResolver.Log(String.Format("Query Android SDK packages\n" + @@ -420,7 +422,14 @@ public static void QueryPackages(string toolPath, string toolArguments, toolPath, toolArguments, (CommandLine.Result result) => { window.Close(); - if (result.exitCode != 0) { + if (result.exitCode == 0) { + PlayServicesResolver.analytics.Report( + "/androidsdkmanager/querypackages/success", + "Android SDK Manager: Query Packages Succeeded"); + } else { + PlayServicesResolver.analytics.Report( + "/androidsdkmanager/querypackages/failed", + "Android SDK Manager: Query Packages Failed"); PlayServicesResolver.Log(String.Format(PACKAGES_MISSING, result.message)); } complete(result); @@ -446,6 +455,8 @@ public static void InstallPackages( HashSet packages, string licenseQuestion, string licenseAgree, string licenseDecline, Regex licenseTextHeader, Action complete) { + PlayServicesResolver.analytics.Report("/androidsdkmanager/installpackages", + "Android SDK Manager: Install Packages"); PlayServicesResolver.Log(String.Format("Install Android SDK packages\n" + "\n" + "{0} {1}\n", @@ -498,7 +509,7 @@ public static void InstallPackages( /// Tool that was executed. /// Arguments to passed to the tool. /// Whether the command is retrieving licenses. - /// List of package versions to install / upgrade.. + /// List of package versions to install / upgrade. /// Result of the tool's execution. private static void LogInstallLicenseResult( string toolPath, string toolArguments, bool retrievingLicenses, @@ -520,6 +531,20 @@ private static void LogInstallLicenseResult( AndroidSdkPackageNameVersion.ListToString(packages), toolResult.message), level: succeeded ? LogLevel.Info : LogLevel.Warning); + var analyticsParameters = new KeyValuePair[] { + new KeyValuePair( + "numPackages", + new List(packages).Count.ToString()) + }; + if (succeeded) { + PlayServicesResolver.analytics.Report( + "/androidsdkmanager/installpackages/success", analyticsParameters, + "Android SDK Manager: Install Packages Successful"); + } else { + PlayServicesResolver.analytics.Report( + "/androidsdkmanager/installpackages/failed", analyticsParameters, + "Android SDK Manager: Install Packages Failed"); + } } } @@ -912,7 +937,7 @@ public void InstallPackages(HashSet packages, var packagesString = AndroidSdkPackageNameVersion.ListToString(packages); // TODO: Remove this dialog when the package manager provides feedback while // downloading. - bool installPackage = UnityEditor.EditorUtility.DisplayDialog( + DialogWindow.Display( "Missing Android SDK packages", String.Format( "Android SDK packages need to be installed:\n" + @@ -922,19 +947,22 @@ public void InstallPackages(HashSet packages, "which may lead you to think Unity has hung / crashed. Would you like " + "to wait for these package to be installed?", packagesString), - "Yes", cancel: "No"); - if (!installPackage) { - PlayServicesResolver.Log( - "User cancelled installation of Android SDK tools package.", - level: LogLevel.Warning); - complete(false); - return; - } - var packageNames = new List(); - foreach (var pkg in packages) packageNames.Add(pkg.Name); - SdkManagerUtil.InstallPackages(toolPath, String.Join(" ", packageNames.ToArray()), - packages, "Accept? (y/N):", "y", "N", - new Regex("^License\\W+[^ ]+:"), complete); + DialogWindow.Option.Selected0, "Yes", "No", + complete: (selectedOption) => { + if (selectedOption == DialogWindow.Option.Selected0) { + PlayServicesResolver.Log( + "User cancelled installation of Android SDK tools package.", + level: LogLevel.Warning); + complete(false); + return; + } + var packageNames = new List(); + foreach (var pkg in packages) packageNames.Add(pkg.Name); + SdkManagerUtil.InstallPackages(toolPath, + String.Join(" ", packageNames.ToArray()), + packages, "Accept? (y/N):", "y", "N", + new Regex("^License\\W+[^ ]+:"), complete); + }); } } @@ -947,7 +975,7 @@ internal class AndroidSdkManager { /// /// Name of the tool to search for. /// SDK path to search for the tool. If this is null or empty, the - // system path is searched instead. + /// system path is searched instead. /// String with the path to the tool if found, null otherwise. private static string FindAndroidSdkTool(string toolName, string sdkPath = null) { if (String.IsNullOrEmpty(sdkPath)) { diff --git a/source/PlayServicesResolver/src/AndroidXmlDependencies.cs b/source/AndroidResolver/src/AndroidXmlDependencies.cs similarity index 92% rename from source/PlayServicesResolver/src/AndroidXmlDependencies.cs rename to source/AndroidResolver/src/AndroidXmlDependencies.cs index f4f5c31e..5af00803 100644 --- a/source/PlayServicesResolver/src/AndroidXmlDependencies.cs +++ b/source/AndroidResolver/src/AndroidXmlDependencies.cs @@ -58,6 +58,7 @@ protected override bool Read(string filename, Logger logger) { string group = null; string artifact = null; string versionSpec = null; + string classifier = null; List repositories = null; logger.Log( String.Format("Reading Android dependency XML file {0}", filename), @@ -79,12 +80,15 @@ protected override bool Read(string filename, Logger logger) { group = null; artifact = null; versionSpec = null; + classifier = null; repositories = new List(); // Parse a package specification in the form: // group:artifact:version_spec + // (or) + // group:artifact:version_spec:classifier var spec = reader.GetAttribute("spec") ?? ""; var specComponents = spec.Split(new [] { ':' }); - if (specComponents.Length != 3) { + if (specComponents.Length != 3 && specComponents.Length != 4) { logger.Log( String.Format( "Ignoring invalid package specification '{0}' " + @@ -96,11 +100,15 @@ protected override bool Read(string filename, Logger logger) { group = specComponents[0]; artifact = specComponents[1]; versionSpec = specComponents[2]; + if (specComponents.Length == 4) + classifier = specComponents[3]; + return true; } else if (!(String.IsNullOrEmpty(group) || String.IsNullOrEmpty(artifact) || - String.IsNullOrEmpty(versionSpec))) { - svcSupport.DependOn(group, artifact, versionSpec, + String.IsNullOrEmpty(versionSpec) + )) { + svcSupport.DependOn(group, artifact, versionSpec, classifier: classifier, packageIds: androidSdkPackageIds.ToArray(), repositories: repositories.ToArray(), createdBy: String.Format("{0}:{1}", filename, @@ -139,7 +147,9 @@ protected override bool Read(string filename, Logger logger) { } return true; } - return false; + // Ignore unknown tags so that different configurations can be stored in the + // same file. + return true; })) { return false; } diff --git a/source/PlayServicesResolver/src/CommandLine.cs b/source/AndroidResolver/src/CommandLine.cs similarity index 97% rename from source/PlayServicesResolver/src/CommandLine.cs rename to source/AndroidResolver/src/CommandLine.cs index 2e50da98..8ec83985 100644 --- a/source/PlayServicesResolver/src/CommandLine.cs +++ b/source/AndroidResolver/src/CommandLine.cs @@ -375,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; @@ -397,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) { @@ -536,13 +535,15 @@ public static Result Run(string toolPath, string arguments, string workingDirect /// executing a command via the shell. This requires: /// * cmd.exe (on Windows) or bash (on OSX / Linux) are in the path. /// * Arguments containing whitespace are quoted. + /// Causes environment variable LANG to be explicitly set + /// when executing a command via the shell. This is only applicable to OSX. /// CommandLineTool result if successful, raises an exception if it's not /// possible to execute the tool. public static Result RunViaShell( string toolPath, string arguments, string workingDirectory = null, Dictionary envVars = null, IOHandler ioHandler = null, bool useShellExecution = false, - bool stdoutRedirectionInShellMode = true) { + bool stdoutRedirectionInShellMode = true, bool setLangInShellMode = false) { bool consoleConfigurationWarning = false; Encoding inputEncoding = Console.InputEncoding; Encoding outputEncoding = Console.OutputEncoding; @@ -592,6 +593,7 @@ public static Result RunViaShell( string shellArgPrefix; string shellArgPostfix; string escapedToolPath = toolPath; + string additionalCommands = ""; if (UnityEngine.RuntimePlatform.WindowsEditor == UnityEngine.Application.platform) { shellCmd = "cmd.exe"; shellArgPrefix = "/c \""; @@ -601,9 +603,13 @@ public static Result RunViaShell( shellArgPrefix = "-l -c '"; shellArgPostfix = "'"; escapedToolPath = toolPath.Replace("'", "'\\''"); + if (UnityEngine.RuntimePlatform.OSXEditor == UnityEngine.Application.platform + && envVars != null && envVars.ContainsKey("LANG") && setLangInShellMode) { + additionalCommands = String.Format("export {0}={1}; ", "LANG", envVars["LANG"]); + } } - arguments = String.Format("{0}\"{1}\" {2} 1> {3} 2> {4}{5}", shellArgPrefix, - escapedToolPath, arguments, stdoutFileName, + arguments = String.Format("{0}{1}\"{2}\" {3} 1> {4} 2> {5}{6}", shellArgPrefix, + additionalCommands, escapedToolPath, arguments, stdoutFileName, stderrFileName, shellArgPostfix); toolPath = shellCmd; } diff --git a/source/PlayServicesResolver/src/CommandLineDialog.cs b/source/AndroidResolver/src/CommandLineDialog.cs similarity index 100% rename from source/PlayServicesResolver/src/CommandLineDialog.cs rename to source/AndroidResolver/src/CommandLineDialog.cs diff --git a/source/PlayServicesResolver/src/EmbeddedResource.cs b/source/AndroidResolver/src/EmbeddedResource.cs similarity index 98% rename from source/PlayServicesResolver/src/EmbeddedResource.cs rename to source/AndroidResolver/src/EmbeddedResource.cs index c57ab325..f9f64be2 100644 --- a/source/PlayServicesResolver/src/EmbeddedResource.cs +++ b/source/AndroidResolver/src/EmbeddedResource.cs @@ -132,7 +132,7 @@ public bool Extract(Logger logger) { /// directories if they're required. /// /// Assembly to extract resources from. - /// Each Key is the resource to extract and each + /// 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. /// diff --git a/source/PlayServicesResolver/src/GradleResolver.cs b/source/AndroidResolver/src/GradleResolver.cs similarity index 79% rename from source/PlayServicesResolver/src/GradleResolver.cs rename to source/AndroidResolver/src/GradleResolver.cs index 40260662..c9f4eac1 100644 --- a/source/PlayServicesResolver/src/GradleResolver.cs +++ b/source/AndroidResolver/src/GradleResolver.cs @@ -88,14 +88,32 @@ private void ParseDownloadGradleArtifactsGradleOutput( 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. @@ -107,8 +125,7 @@ internal static Dictionary DependenciesToPackageSpecs( var sourcesByPackageSpec = new Dictionary(); foreach (var dependency in dependencies) { // Convert the legacy "LATEST" version spec to a Gradle version spec. - var packageSpec = dependency.Version.ToUpper() == "LATEST" ? - dependency.VersionlessKey + ":+" : dependency.Key; + var packageSpec = DependencyToPackageSpec(dependency); var source = CommandLine.SplitLines(dependency.CreatedBy)[0]; string sources; if (sourcesByPackageSpec.TryGetValue(packageSpec, out sources)) { @@ -125,9 +142,12 @@ internal static Dictionary DependenciesToPackageSpecs( /// 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. @@ -144,25 +164,79 @@ internal static string RepoPathToUri(string repoPath, string sourceLocation=null if (repoPath.StartsWith(scheme)) return GradleWrapper.EscapeUri(repoPath); } - // If the directory isn't found, it is possible the user has moved the repository - // in the project, so try searching for it. - string searchDir = "Assets" + Path.DirectorySeparatorChar; - if (!Directory.Exists(repoPath) && - FileUtils.NormalizePathSeparators(repoPath.ToLower()).StartsWith( - searchDir.ToLower())) { - var foundPath = FileUtils.FindPathUnderDirectory( - searchDir, repoPath.Substring(searchDir.Length)); - string warningMessage; + 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)) { - repoPath = searchDir + foundPath; - warningMessage = String.Format( - "{0}: Repo path '{1}' does not exist, will try using '{2}' instead.", - sourceLocation, repoPath, 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 { - warningMessage = String.Format( - "{0}: Repo path '{1}' does not exist.", sourceLocation, repoPath); + PlayServicesResolver.Log(String.Format( + "{0}: Repo path '{1}' does not exist.", sourceLocation, repoPath), + level: LogLevel.Warning); } - PlayServicesResolver.Log(warningMessage, level: LogLevel.Warning); } return GradleWrapper.EscapeUri(GradleWrapper.PathToFileUri(repoPath)); } @@ -245,6 +319,10 @@ private void GradleResolution( 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, @@ -265,6 +343,8 @@ private void GradleResolution( 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, @@ -373,6 +453,8 @@ private void GradleResolution( 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}", @@ -403,37 +485,51 @@ private void GradleResolution( // 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. - if (PlayServicesResolver.CanEnableJetifierOrPromptUser( - "Jetpack (AndroidX) libraries detected, ")) { - 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; - } - } else { - // 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; - } + 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(); } - // Successful, proceed with processing libraries. - processAars(); }, synchronous: false, progressUpdate: (progress, message) => { @@ -498,7 +594,9 @@ private void GradleResolution( /// and play-services-base-9.2.4.aar (unmanaged)) /// The user is warned about the unmanaged conflicting libraries and, if they're /// older than the managed library, prompted to delete the unmanaged libraries. - private void FindAndResolveConflicts() { + /// + /// Called when the operation is complete. + private void FindAndResolveConflicts(Action complete) { Func getVersionlessArtifactFilename = (filename) => { var basename = Path.GetFileName(filename); int split = basename.LastIndexOf("-"); @@ -570,6 +668,9 @@ private void FindAndResolveConflicts() { 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. @@ -593,64 +694,114 @@ private void FindAndResolveConflicts() { string basename = Path.GetFileNameWithoutExtension(Path.GetFileName(filename)); return basename.Substring(getVersionlessArtifactFilename(basename).Length + 1); }; - foreach (var conflict in conflicts) { - var currentVersion = getVersionFromFilename(conflict.Key); - var conflictingVersionsSet = new HashSet(); - foreach (var conflictFilename in conflict.Value) { - conflictingVersionsSet.Add(getVersionFromFilename(conflictFilename)); + + // 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 conflictingVersions = new List(conflictingVersionsSet); - conflictingVersions.Sort(Dependency.versionComparer); - - var warningMessage = String.Format( - "Found conflicting Android library {0}\n" + - "\n" + - "{1} (managed by the Android Resolver) conflicts with:\n" + - "{2}\n", - getVersionlessArtifactFilename(conflict.Key), - conflict.Key, String.Join("\n", conflict.Value.ToArray())); - - // If the conflicting versions are older than the current version we can - // possibly clean up the old versions automatically. - if (Dependency.versionComparer.Compare(conflictingVersions[0], - currentVersion) >= 0) { - if (EditorUtility.DisplayDialog( + }; + + 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?", - "Yes", "No")) { - var deleteFailures = new List(); - foreach (var filename in conflict.Value) { - deleteFailures.AddRange( - FileUtils.DeleteExistingFileOrDirectory(filename)); - } - var deleteError = FileUtils.FormatError("Unable to delete old libraries", - deleteFailures); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Error); - } - warningMessage = null; + 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 (!String.IsNullOrEmpty(warningMessage)) { - PlayServicesResolver.Log( - warningMessage + - "\n" + - "Your application is unlikely to build in the current state.\n" + - "\n" + - "To resolve this problem you can try one of the following:\n" + - "* Updating the dependencies managed by the Android Resolver\n" + - " to remove references to old libraries. Be careful to not\n" + - " include conflicting versions of Google Play services.\n" + - "* Contacting the plugin vendor(s) with conflicting\n" + - " dependencies and asking them to update their plugin.\n", - level: LogLevel.Warning); + 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(); } /// @@ -667,8 +818,7 @@ public void DoResolution(string destinationDirectory, bool closeWindowOnCompleti RunOnMainThread.Run(() => { DoResolutionUnsafe(destinationDirectory, closeWindowOnCompletion, () => { - FindAndResolveConflicts(); - resolutionComplete(); + FindAndResolveConflicts(resolutionComplete); }); }); } @@ -688,6 +838,8 @@ private void DoResolutionUnsafe(string destinationDirectory, bool closeWindowOnC 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" + @@ -881,9 +1033,12 @@ private string DetermineExplodedAarPath(string aarPath) { /// /// Processes the aars. /// - /// Each aar copied is inspected and determined if it should be + /// + /// + /// 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 @@ -891,6 +1046,7 @@ private string DetermineExplodedAarPath(string aarPath) { /// 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. @@ -908,6 +1064,15 @@ private void ProcessAars(string dir, HashSet updatedFiles, 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; @@ -930,11 +1095,31 @@ private void ProcessAars(string dir, HashSet updatedFiles, "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(); } } @@ -976,7 +1161,7 @@ internal static bool ShouldProcess(string aarDirectory) { // To work around this when Gradle builds are enabled, explosion is enabled for all // AARs that require variable expansion unless this behavior is explicitly disabled // in the settings dialog. - if (PlayServicesResolver.GradleProjectExportEnabled && !SettingsDialog.ExplodeAars) { + if (!SettingsDialog.ExplodeAars) { return false; } // If this version of Unity doesn't support AAR files, always explode. 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/PlayServicesResolver/src/GradleWrapper.cs b/source/AndroidResolver/src/GradleWrapper.cs similarity index 99% rename from source/PlayServicesResolver/src/GradleWrapper.cs rename to source/AndroidResolver/src/GradleWrapper.cs index 4a463234..4c667d8d 100644 --- a/source/PlayServicesResolver/src/GradleWrapper.cs +++ b/source/AndroidResolver/src/GradleWrapper.cs @@ -133,7 +133,7 @@ public bool Extract(Logger logger) { /// 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, diff --git a/source/PlayServicesResolver/src/JavaUtilities.cs b/source/AndroidResolver/src/JavaUtilities.cs similarity index 86% rename from source/PlayServicesResolver/src/JavaUtilities.cs rename to source/AndroidResolver/src/JavaUtilities.cs index eb896bd6..54b87beb 100644 --- a/source/PlayServicesResolver/src/JavaUtilities.cs +++ b/source/AndroidResolver/src/JavaUtilities.cs @@ -41,7 +41,7 @@ public ToolNotFoundException(string message) : base(message) {} /// /// Environment variable used to specify the Java distribution directory. /// - private const string JAVA_HOME = "JAVA_HOME"; + internal const string JAVA_HOME = "JAVA_HOME"; /// /// Minimum JDK version required to build with recently released Android libraries. @@ -52,9 +52,14 @@ public ToolNotFoundException(string message) : base(message) {} /// Find the JDK path (JAVA_HOME) either configured in the Unity editor or via the JAVA_HOME /// environment variable. /// - private static string JavaHome { + internal static string JavaHome { get { - var javaHome = UnityEditor.EditorPrefs.GetString("JdkPath"); + string javaHome = null; + // Unity 2019.3 added AndroidExternalToolsSettings which contains the JDK path so + // try to use that first. + var javaRootPath = UnityCompat.AndroidExternalToolsSettingsJdkRootPath; + if (!String.IsNullOrEmpty(javaRootPath)) javaHome = javaRootPath; + // Unity 2019.x added installation of the JDK in the AndroidPlayer directory // so fallback to searching for it there. if (String.IsNullOrEmpty(javaHome) || EditorPrefs.GetBool("JdkUseEmbedded")) { @@ -64,9 +69,20 @@ private static string JavaHome { "Editor", "").Replace("OSX", "MacOS"); var openJdkDir = Path.Combine(Path.Combine(Path.Combine( androidPlayerDir, "Tools"), "OpenJDK"), platformDir); - if (Directory.Exists(openJdkDir)) javaHome = openJdkDir; + if (Directory.Exists(openJdkDir)) { + javaHome = openJdkDir; + } else { + openJdkDir = Path.Combine(androidPlayerDir, "OpenJDK"); + if (Directory.Exists(openJdkDir)) javaHome = openJdkDir; + } } } + + // Pre Unity 2019, use the JDK path in the preferences. + if (String.IsNullOrEmpty(javaHome)) { + javaHome = UnityEditor.EditorPrefs.GetString("JdkPath"); + } + // If the JDK stil isn't found, check the environment. if (String.IsNullOrEmpty(javaHome)) { javaHome = Environment.GetEnvironmentVariable(JAVA_HOME); @@ -116,7 +132,7 @@ private static string JavaHomeBinaryPath(string javaTool) { /// /// Find a Java tool. /// - /// Name of the tool to search for. + /// Name of the tool to search for. /// Path to the tool if it's found, throws a ToolNotFoundException /// otherwise. private static string FindJavaTool(string javaTool) @@ -126,12 +142,12 @@ private static string FindJavaTool(string javaTool) if (!String.IsNullOrEmpty(javaHome)) { toolPath = JavaHomeBinaryPath(javaTool); if (String.IsNullOrEmpty(toolPath)) { - EditorUtility.DisplayDialog( + DialogWindow.Display( "Android Resolver", String.Format("{0} environment references a directory ({1}) that does " + "not contain {2} which is required to process Android " + "libraries.", JAVA_HOME, javaHome, javaTool), - "OK"); + DialogWindow.Option.Selected0, "OK"); throw new ToolNotFoundException( String.Format("{0} not found, {1} references incomplete Java distribution.", javaTool, javaHome)); @@ -140,14 +156,14 @@ private static string FindJavaTool(string javaTool) } else { toolPath = CommandLine.FindExecutable(javaTool); if (!File.Exists(toolPath)) { - EditorUtility.DisplayDialog( + DialogWindow.Display( "Android Resolver", String.Format("Unable to find {0} in the system path. This tool is " + "required to process Android libraries. Please configure " + "your JDK location under the " + "'Unity Preferences > External Tools' menu.", javaTool), - "OK"); + DialogWindow.Option.Selected0, "OK"); throw new ToolNotFoundException(javaTool + " not found."); } } @@ -163,7 +179,7 @@ private static void LogJdkVersionFailedWarning(string javaPath, string commandLi PlayServicesResolver.Log( String.Format( "Failed to get Java version when running {0}\n" + - "It is not be possible to verify your Java installation is new enough to " + + "It is not possible to verify your Java installation is new enough to " + "compile with the latest Android SDK\n\n" + "{1}", javaPath, commandLineSummary), level: LogLevel.Warning); @@ -210,6 +226,7 @@ internal static void CheckJdkForApiLevel() { } // If the user's installed JDK is too old, report an error. if (foundVersion < MinimumJdkVersion) { + PlayServicesResolver.analytics.Report("jdk/outofdate", "JDK out of date"); PlayServicesResolver.Log( String.Format("The configured JDK {0} is too old to build Android " + "applications with recent libraries.\n" + diff --git a/source/PlayServicesResolver/src/LocalMavenRepository.cs b/source/AndroidResolver/src/LocalMavenRepository.cs similarity index 82% rename from source/PlayServicesResolver/src/LocalMavenRepository.cs rename to source/AndroidResolver/src/LocalMavenRepository.cs index 675c3849..b4467ccf 100644 --- a/source/PlayServicesResolver/src/LocalMavenRepository.cs +++ b/source/AndroidResolver/src/LocalMavenRepository.cs @@ -82,9 +82,9 @@ public static List FindAarsInLocalRepos(ICollection dependen /// /// Get a path without a filename extension. /// - /// Path to a file. + /// Path to a file. /// Path (including directory) without a filename extension. - private static string PathWithoutExtension(string path) { + internal static string PathWithoutExtension(string path) { return Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); } @@ -95,16 +95,37 @@ private static string PathWithoutExtension(string path) { /// file. /// /// artifactFilename + /// If artifactFilename is copied from a different location, + /// pass the original location where POM file lives. /// true if successful, false otherwise. - public static bool PatchPomFile(string artifactFilename) { + public static bool PatchPomFile(string artifactFilename, string sourceFilename) { + if (sourceFilename == null) { + sourceFilename = artifactFilename; + } + if (FileUtils.IsUnderPackageDirectory(artifactFilename)) { + // File under Packages folder is immutable. + PlayServicesResolver.Log( + String.Format("Cannot patch POM from Packages directory since it is immutable" + + " ({0})", artifactFilename), level: LogLevel.Error); + return false; + } + var failureImpact = String.Format("{0} may not be included in your project", Path.GetFileName(artifactFilename)); var pomFilename = PathWithoutExtension(artifactFilename) + ".pom"; - if (!File.Exists(pomFilename)) { - PlayServicesResolver.Log( - String.Format("Maven POM {0} for {1} does not exist. " + failureImpact, - pomFilename, artifactFilename), level: LogLevel.Warning); - return false; + // Copy POM file if artifact has been copied from a different location as well. + if (String.Compare(sourceFilename, artifactFilename) != 0 && + !File.Exists(pomFilename)) { + var sourcePomFilename = PathWithoutExtension(sourceFilename) + ".pom"; + var error = PlayServicesResolver.CopyAssetAndLabel( + sourcePomFilename, pomFilename); + if (!String.IsNullOrEmpty(error)) { + PlayServicesResolver.Log( + String.Format("Failed to copy POM from {0} to {1} due to:\n{2}", + sourcePomFilename, pomFilename, error), + level: LogLevel.Error); + return false; + } } var artifactPackaging = Path.GetExtension(artifactFilename).ToLower().Substring(1); var pom = new XmlDocument(); @@ -168,6 +189,10 @@ public static bool PatchPomFilesInLocalRepos(ICollection dependencie // Filename extensions by the basename of each file path. var extensionsByBasenames = new Dictionary>(); foreach (var filename in FindAarsInLocalRepos(dependencies)) { + // No need to patch POM under package folder. + if (FileUtils.IsUnderPackageDirectory(filename)) { + continue; + } var pathWithoutExtension = PathWithoutExtension(filename); HashSet extensions; if (!extensionsByBasenames.TryGetValue(pathWithoutExtension, out extensions)) { @@ -191,7 +216,8 @@ public static bool PatchPomFilesInLocalRepos(ICollection dependencie } if (foundFile) break; } - successful &= PatchPomFile(kv.Key + filePackagingToUse); + var artifect = kv.Key + filePackagingToUse; + successful &= PatchPomFile(artifect, artifect); } return successful; } diff --git a/source/PlayServicesResolver/src/PlayServicesPreBuild.cs b/source/AndroidResolver/src/PlayServicesPreBuild.cs similarity index 90% rename from source/PlayServicesResolver/src/PlayServicesPreBuild.cs rename to source/AndroidResolver/src/PlayServicesPreBuild.cs index fd5012f3..21c28cbd 100644 --- a/source/PlayServicesResolver/src/PlayServicesPreBuild.cs +++ b/source/AndroidResolver/src/PlayServicesPreBuild.cs @@ -23,11 +23,11 @@ private static void WarnIfAutoResolveDisabled() { "Ensure you have run the resolver manually." + "\n\nWith auto-resolution of Android dependencies disabled you " + "must manually resolve dependencies using the " + - "\"Assets > Play Services Resolver > Android Resolver > " + + "\"Assets > External Dependency Manager > Android Resolver > " + "Resolve\" menu item.\n\nFailure to resolve Android " + "dependencies will result in an non-functional " + "application.\nTo enable auto-resolution, navigate to " + - "\"Assets > Play Services Resolver > Android Resolver > " + + "\"Assets > External Dependency Manager > Android Resolver > " + "Settings\" and check \"Enable Auto-resolution\""); } diff --git a/source/PlayServicesResolver/src/PlayServicesResolver.cs b/source/AndroidResolver/src/PlayServicesResolver.cs similarity index 70% rename from source/PlayServicesResolver/src/PlayServicesResolver.cs rename to source/AndroidResolver/src/PlayServicesResolver.cs index 407e299a..e4f7d40e 100644 --- a/source/PlayServicesResolver/src/PlayServicesResolver.cs +++ b/source/AndroidResolver/src/PlayServicesResolver.cs @@ -18,6 +18,7 @@ namespace GooglePlayServices { using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Xml; @@ -201,7 +202,7 @@ public static DependencyState ReadFromFile() { return true; } } - return false; + return true; })) { return null; } @@ -261,7 +262,7 @@ private static string SetToString(HashSet setToConvert) { /// /// Convert a dictionary to a sorted comma separated string. /// - /// Comma separated string in the form key=value. + /// 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)) { @@ -319,7 +320,7 @@ internal class PropertyPoller { /// Time to wait before signalling that the value /// has changed. /// Time to check the value of the property for - /// changes. + /// changes. public PropertyPoller(string propertyName, int delayTimeInSeconds = 3, int checkIntervalInSeconds = 1) { @@ -383,8 +384,22 @@ public void Poll(Func getCurrentValue, Changed changed) { /// /// 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 gradleResolver; + 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. @@ -461,7 +476,7 @@ public class BundleIdChangedEventArgs : EventArgs { /// /// Asset label applied to files managed by this plugin. /// - private const string ManagedAssetLabel = "gpsr"; + internal const string ManagedAssetLabel = "gpsr"; /// /// Get a boolean property from UnityEditor.EditorUserBuildSettings. @@ -498,6 +513,25 @@ public static bool GradleTemplateEnabled { } } + /// + /// Whether the Gradle Properties template is enabled. (Unity 2019.3 and above) + /// + public static bool GradlePropertiesTemplateEnabled { + get { + return GradleBuildEnabled && + File.Exists(GradleTemplateResolver.GradlePropertiesTemplatePath); + } + } + + /// + /// Whether the Gradle Settings template is enabled. + /// + public static bool GradleSettingsTemplateEnabled { + get { + return GradleBuildEnabled && + File.Exists(GradleTemplateResolver.GradleSettingsTemplatePath); + } + } // Backing store for GradleVersion property. private static string gradleVersion = null; @@ -513,12 +547,22 @@ public static string GradleVersion { set { gradleVersion = value; } get { if (!String.IsNullOrEmpty(gradleVersion)) return gradleVersion; - var engineDir = AndroidPlaybackEngineDirectory; - if (String.IsNullOrEmpty(engineDir)) return null; + 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"); + } - var gradleLibDir = - Path.Combine(Path.Combine(Path.Combine(engineDir, "Tools"), "gradle"), "lib"); - if (Directory.Exists(gradleLibDir)) { + 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)); @@ -538,8 +582,17 @@ public static string GradleVersion { // 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\.tools\.build:gradle:([^']+)['""]$"); + @"['""]com\.android\.application['""] version ['""]([^'""]+)['""]"); /// /// Get the Android Gradle Plugin version used by Unity. @@ -563,10 +616,7 @@ public static string AndroidGradlePluginVersion { return androidGradlePluginVersion; } // Search the gradle templates for the plugin version. - var engineDir = AndroidPlaybackEngineDirectory; - if (String.IsNullOrEmpty(engineDir)) return null; - var gradleTemplateDir = - Path.Combine(Path.Combine(engineDir, "Tools"), "GradleTemplates"); + var gradleTemplateDir = GradleTemplateResolver.UnityGradleTemplatesDir; if (Directory.Exists(gradleTemplateDir)) { var gradleTemplates = new List(); if (File.Exists(mainTemplateGradlePath)) { @@ -576,7 +626,12 @@ public static string AndroidGradlePluginVersion { SearchOption.TopDirectoryOnly)); foreach (var path in gradleTemplates) { foreach (var line in File.ReadAllLines(path)) { - var match = androidGradlePluginVersionExtract.Match(line); + 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; @@ -618,7 +673,7 @@ public static bool GradleProjectExportEnabled { /// private struct AndroidBuildSystemSettings { /// - // Whether the Gradle build is enabled. + /// Whether the Gradle build is enabled. /// public bool GradleBuildEnabled { get; private set; } @@ -627,6 +682,11 @@ private struct AndroidBuildSystemSettings { /// public bool GradleTemplateEnabled { get; private set; } + /// + /// Whether the Gradle properties template is enabled. + /// + public bool GradlePropertiesTemplateEnabled { get; private set; } + /// // Whether project export is enabled. /// @@ -640,13 +700,14 @@ public static AndroidBuildSystemSettings Current { return new AndroidBuildSystemSettings { GradleBuildEnabled = PlayServicesResolver.GradleBuildEnabled, GradleTemplateEnabled = PlayServicesResolver.GradleTemplateEnabled, + GradlePropertiesTemplateEnabled = PlayServicesResolver.GradlePropertiesTemplateEnabled, ProjectExportEnabled = PlayServicesResolver.ProjectExportEnabled }; } } /// - // Compare with another AndroidBuildSystemSettings. + /// Compare with another AndroidBuildSystemSettings. /// /// Object to compare with. /// true if the object is the same as this, false otherwise. @@ -654,6 +715,7 @@ public override bool Equals(System.Object obj) { var other = (AndroidBuildSystemSettings)obj; return other.GradleBuildEnabled == GradleBuildEnabled && other.GradleTemplateEnabled == GradleTemplateEnabled && + other.GradlePropertiesTemplateEnabled == GradlePropertiesTemplateEnabled && other.ProjectExportEnabled == ProjectExportEnabled; } @@ -663,7 +725,8 @@ public override bool Equals(System.Object obj) { /// Hash of this object. public override int GetHashCode() { return GradleBuildEnabled.GetHashCode() ^ GradleTemplateEnabled.GetHashCode() ^ - ProjectExportEnabled.GetHashCode(); + GradlePropertiesTemplateEnabled.GetHashCode() ^ + ProjectExportEnabled.GetHashCode(); } @@ -673,9 +736,9 @@ public override int GetHashCode() { /// String representation. public override string ToString() { return String.Format("[GradleBuildEnabled={0} GradleTemplateEnabled={1} " + - "ProjectExportEnabled={2}]", + "GradlePropertiesTemplateEnabled={2} ProjectExportEnabled={2}]", GradleBuildEnabled, GradleTemplateEnabled, - ProjectExportEnabled); + GradlePropertiesTemplateEnabled, ProjectExportEnabled); } } @@ -710,6 +773,22 @@ public class AndroidBuildSystemChangedArgs : EventArgs { /// public bool PreviousGradleTemplateEnabled { get; set; } + /// + /// Project export was selected when this event was fired. + /// + + /// + /// Whether a custom Gradle Properties template is enabled. + /// This will only be true if GradleBuildEnabled is also true. + /// + public bool GradlePropertiesTemplateEnabled { get; set; } + + /// + /// Whether a custom Gradle Properties Template + /// was enabled the last time this event was fired. + /// + public bool PreviousGradlePropertiesTemplateEnabled { get; set; } + /// /// Project export was selected when this event was fired. /// @@ -737,6 +816,19 @@ public class AndroidBuildSystemChangedArgs : EventArgs { /// 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. /// @@ -768,7 +860,14 @@ public class AndroidAbisChangedArgs : EventArgs { /// public static string AndroidSdkRoot { get { - var sdkPath = EditorPrefs.GetString("AndroidSdkRoot"); + string sdkPath = null; + // Unity 2019.3 added AndroidExternalToolsSettings which contains the Android SDK + // path so try to use that first. + var androidSdkRootPath = UnityCompat.AndroidExternalToolsSettingsSdkRootPath; + if (!String.IsNullOrEmpty(androidSdkRootPath)) { + if (Directory.Exists(androidSdkRootPath)) sdkPath = androidSdkRootPath; + } + // Unity 2019.x added installation of the Android SDK in the AndroidPlayer directory // so fallback to searching for it there. if (String.IsNullOrEmpty(sdkPath) || EditorPrefs.GetBool("SdkUseEmbedded")) { @@ -778,6 +877,11 @@ public static string AndroidSdkRoot { if (Directory.Exists(androidPlayerSdkDir)) sdkPath = androidPlayerSdkDir; } } + + // Pre Unity 2019 Android SDK path. + if (String.IsNullOrEmpty(sdkPath)) { + sdkPath = EditorPrefs.GetString("AndroidSdkRoot"); + } return sdkPath; } } @@ -868,18 +972,35 @@ public static bool AutomaticResolutionEnabled { /// Initializes the class. /// static PlayServicesResolver() { - // Create the resolver. - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { - gradleResolver = new GradleResolver(); - // Monitor Android dependency XML files to perform auto-resolution. - AddAutoResolutionFilePatterns(xmlDependencies.fileRegularExpressions); - - svcSupport = PlayServicesSupport.CreateInstance( - "PlayServicesResolver", - AndroidSdkRoot, - "ProjectSettings", - logMessageWithLevel: LogDelegate); + // Delay initialization until the build target is Android and the editor is not in play + // mode. + EditorInitializer.InitializeOnMainThread(condition: () => { + return EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android && + !EditorApplication.isPlayingOrWillChangePlaymode; + }, initializer: Initialize, name: "PlayServicesResolver", logger: logger); + + } + + /// + /// Initialize the module. This should be called on the main thread only if + /// current active build target is Android and not in play mode. + /// + private static bool Initialize() { + if ( EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android ) { + throw new Exception("PlayServiceResolver.Initialize() is called when active " + + "build target is not Android. This should never happen. If it does, " + + "please report to the developer."); } + + // Monitor Android dependency XML files to perform auto-resolution. + AddAutoResolutionFilePatterns(xmlDependencies.fileRegularExpressions); + + svcSupport = PlayServicesSupport.CreateInstance( + "PlayServicesResolver", + AndroidSdkRoot, + "ProjectSettings", + logMessageWithLevel: LogDelegate); + RunOnMainThread.OnUpdate -= PollBundleId; RunOnMainThread.OnUpdate += PollBundleId; @@ -887,16 +1008,15 @@ static PlayServicesResolver() { OnSettingsChanged(); // Setup events for auto resolution. - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { - BundleIdChanged += ResolveOnBundleIdChanged; - AndroidBuildSystemChanged += ResolveOnBuildSystemChanged; - AndroidAbisChanged += ResolveOnAndroidAbisChanged; - AndroidSdkRootChanged += ResolveOnAndroidSdkRootChange; - Reresolve(); + BundleIdChanged += ResolveOnBundleIdChanged; + AndroidBuildSystemChanged += ResolveOnBuildSystemChanged; + AndroidAbisChanged += ResolveOnAndroidAbisChanged; + AndroidSdkRootChanged += ResolveOnAndroidSdkRootChange; + Reresolve(); - if (SettingsDialogObj.EnableAutoResolution) LinkAutoResolution(); - } + if (SettingsDialogObj.EnableAutoResolution) LinkAutoResolution(); + return true; } // Unregister events to monitor build system changes for the Android Resolver and other @@ -1007,7 +1127,7 @@ private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { - if (gradleResolver != null) { + if (GradleResolverInstance != null) { // If the manifest changed, try patching it. var manifestPath = FileUtils.NormalizePathSeparators( SettingsDialogObj.AndroidManifestPath); @@ -1056,6 +1176,18 @@ private static void OnPostprocessAllAssets(string[] importedAssets, /// private static int scenesProcessed = 0; + /// + /// Reset static variable after every successful build. + /// + [UnityEditor.Callbacks.PostProcessBuildAttribute(0)] + private static void OnPostProcessBuild(UnityEditor.BuildTarget target, string path) { + // Since certain versions of Unity do not reload the module, we want + // to reset this variable in order to allow an attempt to resolve in OnPostProcessScene(). + // Caveat: Unity calls this only after a successful build. We should really be calling + // this after every build (successful or not). + scenesProcessed = 0; + } + /// /// If auto-resolution is enabled, run resolution synchronously before building the /// application. @@ -1066,7 +1198,7 @@ private static void OnPostProcessScene() { if (UnityEngine.Application.isPlaying) return; // If the Android resolver isn't enabled or automatic resolution is disabled, // do nothing. - if (gradleResolver == null || !SettingsDialogObj.AutoResolveOnBuild) { + if (GradleResolverInstance == null || !SettingsDialogObj.AutoResolveOnBuild) { return; } // If post-processing has already been executed since this module was loaded, don't @@ -1075,6 +1207,8 @@ private static void OnPostProcessScene() { 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); } @@ -1133,11 +1267,11 @@ private static void AutoResolve(Action resolutionComplete) { "Ensure you have manually run the resolver before building." + "\n\nWith auto-resolution of Android dependencies disabled you " + "must manually resolve dependencies using the " + - "\"Assets > Play Services Resolver > Android Resolver > " + + "\"Assets > External Dependency Manager > Android Resolver > " + "Resolve\" menu item.\n\nFailure to resolve Android " + "dependencies will result in an non-functional " + "application.\n\nTo enable auto-resolution, navigate to " + - "\"Assets > Play Services Resolver > Android Resolver > " + + "\"Assets > External Dependency Manager > Android Resolver > " + "Settings\" and check \"Enable Auto-resolution\""); resolutionComplete(); } @@ -1147,7 +1281,7 @@ private static void AutoResolve(Action resolutionComplete) { /// Auto-resolve if any packages need to be resolved. /// private static void Reresolve() { - if (AutomaticResolutionEnabled && gradleResolver != null) { + if (AutomaticResolutionEnabled && GradleResolverInstance != null) { ScheduleAutoResolve(); } } @@ -1166,7 +1300,7 @@ private static void Reresolve() { /// * foo.com.my.app.service --> foo.com.my.app.service (unchanged) /// /// Path of this node in the hierarchy of nodes. For example: - /// given node is "" in "" this should be "a/b/c". If this + /// given node is "<c>" in "<a><b><c>" this should be "a/b/c". If this /// value is null the name of the current node is used. /// true if any replacements are applied, false otherwise. private static bool ReplaceVariablesInXmlElementTree( @@ -1288,6 +1422,8 @@ internal static void ReplaceVariablesInAndroidManifest( /// 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(); @@ -1345,6 +1481,8 @@ private static void PollBuildSystem() { PreviousGradleBuildEnabled = previousValue.GradleBuildEnabled, GradleTemplateEnabled = currentValue.GradleTemplateEnabled, PreviousGradleTemplateEnabled = previousValue.GradleTemplateEnabled, + GradlePropertiesTemplateEnabled = currentValue.GradlePropertiesTemplateEnabled, + PreviousGradlePropertiesTemplateEnabled = previousValue.GradlePropertiesTemplateEnabled, ProjectExportEnabled = currentValue.ProjectExportEnabled, PreviousProjectExportEnabled = previousValue.ProjectExportEnabled, }); @@ -1402,12 +1540,23 @@ private static void ResolveOnAndroidSdkRootChange( /// /// Files or directories to delete. /// true if files are deleted, false otherwise. - private static bool DeleteFiles(IEnumerable filenames) - { + private static bool DeleteFiles(IEnumerable filenames) { if (filenames == null) return false; var failedToDelete = new List(); foreach (string artifact in filenames) { failedToDelete.AddRange(FileUtils.DeleteExistingFileOrDirectory(artifact)); + // Attempt to remove empty parent folders. + var folder = Path.GetDirectoryName(artifact); + while (!String.IsNullOrEmpty(folder) && + !Path.IsPathRooted(folder) && + String.Compare(folder, FileUtils.ASSETS_FOLDER) != 0) { + if (Directory.GetFiles(folder).Length != 0 || + Directory.GetDirectories(folder).Length != 0) { + break; + } + failedToDelete.AddRange(FileUtils.DeleteExistingFileOrDirectory(folder)); + folder = Path.GetDirectoryName(folder); + } } var deleteError = FileUtils.FormatError("Failed to delete files:", failedToDelete); if (!String.IsNullOrEmpty(deleteError)) { @@ -1522,12 +1671,21 @@ public static bool ResolveSync(bool forceResolution) { /// Remove libraries references from Gradle template files and patch POM files to work /// with the Gradle template. /// - private static void DeleteResolvedLibrariesFromGradleTemplate() { + /// 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 (GradleTemplateEnabled) { + if (updateMainTemplate && GradleTemplateEnabled) { GradleTemplateResolver.InjectDependencies(new List()); } + if (updateSettingsTemplate && GradleSettingsTemplateEnabled) { + string lastError; + GradleTemplateResolver.InjectSettings(new List(), out lastError); + } } /// @@ -1541,8 +1699,11 @@ public static void DeleteResolvedLibraries(System.Action complete = null) { "resolved after deletion.", level: LogLevel.Warning); SettingsDialogObj.EnableAutoResolution = false; } + PlayServicesResolver.analytics.Report("/deleteresolved", + "Delete Resolved Packages"); DeleteLabeledAssets(); - DeleteResolvedLibrariesFromGradleTemplate(); + DeleteResolvedLibrariesFromGradleTemplate(updateMainTemplate: true, + updateSettingsTemplate: true); if (complete != null) complete(); }, 0); } @@ -1587,7 +1748,7 @@ private static void ScheduleResolve(bool forceResolution, bool closeWindowOnComp new ResolutionJob( isAutoResolveJob, () => { - ResolveUnsafe( + ResolveUnsafeAfterMainTemplateCheck( (success) => { SignalResolveJobComplete(() => { if (resolutionCompleteWithResult != null) { @@ -1604,7 +1765,17 @@ private static void ScheduleResolve(bool forceResolution, bool closeWindowOnComp } /// - /// Resolve dependencies. + /// Ensures that the mainTemplate.gradle and gradle.properties files are present in the project, + /// creating them via the Unity Editor's template files if needed. + /// + /// True if both files are present. + private static bool EnableGradleTemplates() { + return GradleTemplateResolver.EnsureGradleTemplateEnabled(GradleTemplateResolver.GradleTemplateFilename) && + GradleTemplateResolver.EnsureGradleTemplateEnabled(GradleTemplateResolver.GradlePropertiesTemplateFilename); + } + + /// + /// Resolve dependencies after checking if mainTemplate.gradle is enabled (or previously disabled). /// /// Delegate called when resolution is complete /// with a parameter that indicates whether it succeeded or failed. @@ -1614,12 +1785,92 @@ private static void ScheduleResolve(bool forceResolution, bool closeWindowOnComp /// Whether this is an auto-resolution job. /// Whether to unconditionally close the resolution /// window when complete. - private static void ResolveUnsafe(Action resolutionComplete, - bool forceResolution, bool isAutoResolveJob, - bool closeWindowOnCompletion) { + 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(""); + 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 && @@ -1639,7 +1890,9 @@ private static void ResolveUnsafe(Action resolutionComplete, Log("Stale dependencies exist. Deleting assets...", level: LogLevel.Verbose); DeleteLabeledAssets(); } - DeleteResolvedLibrariesFromGradleTemplate(); + DeleteResolvedLibrariesFromGradleTemplate(updateMainTemplate: true, + updateSettingsTemplate: true); + if (resolutionComplete != null) { resolutionComplete(true); } @@ -1652,47 +1905,58 @@ private static void ResolveUnsafe(Action resolutionComplete, if (SettingsDialogObj.PromptBeforeAutoResolution && isAutoResolveJob && !ExecutionEnvironment.InBatchMode) { - bool shouldResolve = false; - AlertModal alert = new AlertModal { - Title = "Enable Android Auto-resolution?", - Message = "The Play Services Resolver has detected a change " + - " and would to resolve conflicts and download Android dependencies." + - "\n\n\"Disable Auto-Resolution\" will require manually " + - "running resolution using \"Assets > Play Services Resolver " + - "> Android Resolver > Resolve\" menu item. Failure to " + - "resolve Android dependencies will result " + - "in an non-functional application." + - "\n\nEnable auto-resolution again via " + - "\"Assets > Play Services Resolver " + - "> Android Resolver > Settings.", - Ok = new AlertModal.LabeledAction { - Label = "Enable", - DelegateAction = () => { - shouldResolve = true; - SettingsDialogObj.PromptBeforeAutoResolution = false; + DialogWindow.Display( + "Enable Android Auto-resolution?", + "Android Resolver has detected a change " + + " and would to resolve conflicts and download Android dependencies." + + "\n\n\"Disable Auto-Resolution\" will require manually " + + "running resolution using \"Assets > External Dependency Manager " + + "> Android Resolver > Resolve\" menu item. Failure to " + + "resolve Android dependencies will result " + + "in an non-functional application." + + "\n\nEnable auto-resolution again via " + + "\"Assets > External Dependency Manager " + + "> Android Resolver > Settings.", + DialogWindow.Option.Selected0, "Enable", "Disable", + complete: (selectedOption) => { + bool shouldResolve = true; + switch (selectedOption) { + case DialogWindow.Option.Selected0: // Enable + shouldResolve = true; + SettingsDialogObj.PromptBeforeAutoResolution = false; + break; + case DialogWindow.Option.Selected1: // Disable + SettingsDialogObj.EnableAutoResolution = false; + SettingsDialogObj.PromptBeforeAutoResolution = false; + shouldResolve = false; + break; } - }, - Cancel = new AlertModal.LabeledAction { - Label = "Disable", - DelegateAction = () => { - SettingsDialogObj.EnableAutoResolution = false; - SettingsDialogObj.PromptBeforeAutoResolution = false; - shouldResolve = false; + if (shouldResolve) { + ResolveUnsafe(resolutionComplete, forceResolution, + closeWindowOnCompletion); + } else { + if (resolutionComplete != null) { + resolutionComplete(false); + } } - } - }; - - alert.Display(); - - if (!shouldResolve) { - if (resolutionComplete != null) { - resolutionComplete(false); - } - - return; - } + }); + } else { + ResolveUnsafe(resolutionComplete, forceResolution, closeWindowOnCompletion); } + } + /// + /// Resolve dependencies. + /// + /// Delegate called when resolution is complete + /// with a parameter that indicates whether it succeeded or failed. + /// Whether resolution should be executed when no dependencies + /// have changed. This is useful if a dependency specifies a wildcard in the version + /// expression. + /// Whether to unconditionally close the resolution + /// window when complete. + private static void ResolveUnsafe(Action resolutionComplete, + bool forceResolution, bool closeWindowOnCompletion) { if (forceResolution) { Log("Forcing resolution...", level: LogLevel.Verbose); DeleteLabeledAssets(); @@ -1724,19 +1988,31 @@ private static void ResolveUnsafe(Action resolutionComplete, } } + var requestedDependencies = PlayServicesSupport.GetAllDependencies(); System.IO.Directory.CreateDirectory(SettingsDialogObj.PackageDir); Log(String.Format("Resolving the following dependencies:\n{0}\n", String.Join("\n", (new List( - PlayServicesSupport.GetAllDependencies().Keys)).ToArray())), + requestedDependencies.Keys)).ToArray())), level: LogLevel.Verbose); + var resolutionParameters = GetResolutionMeasurementParameters(null); + PlayServicesResolver.analytics.Report("/resolve", resolutionParameters, "Resolve"); + // Writes the current dependency state and reports whether resolution was successful. Action finishResolution = (bool succeeded, string error) => { AssetDatabase.Refresh(); DependencyState.GetState().WriteToFile(); - Log(String.Format("Resolution {0}.\n\n{1}", - succeeded ? "Succeeded" : "Failed", - error), level: LogLevel.Verbose); + if (succeeded) { + PlayServicesResolver.analytics.Report("/resolve/success", resolutionParameters, + "Resolve Successful"); + Log(String.Format("Resolution Succeeded.\n\n{0}", error), + level: LogLevel.Verbose); + } else { + PlayServicesResolver.analytics.Report("/resolve/failed", resolutionParameters, + "Resolve Failed"); + Log(String.Format("Resolution Failed.\n\n{0}", error), + level: LogLevel.Error); + } if (resolutionComplete != null) { RunOnMainThread.Run(() => { resolutionComplete(succeeded); }); } @@ -1744,27 +2020,85 @@ private static void ResolveUnsafe(Action resolutionComplete, // If a gradle template is present but patching is disabled, remove managed libraries // from the template. - if (GradleTemplateEnabled && - !SettingsDialogObj.PatchMainTemplateGradle) { - DeleteResolvedLibrariesFromGradleTemplate(); - } + DeleteResolvedLibrariesFromGradleTemplate( + updateMainTemplate: GradleTemplateEnabled && + !SettingsDialogObj.PatchMainTemplateGradle, + updateSettingsTemplate: GradleSettingsTemplateEnabled && + !SettingsDialogObj.PatchSettingsTemplateGradle); + + Func patchGradleProperties = () => { + // For Unity 2019.3 and above, warn the user if jetifier is enabled + // and custom gradle properties template is not enabled in build settings. + if (GradleBuildEnabled && + Google.VersionHandler.GetUnityVersionMajorMinor() >= 2019.3f && + SettingsDialogObj.UseJetifier && + SettingsDialogObj.PatchPropertiesTemplateGradle && + GradleTemplateEnabled && + !GradlePropertiesTemplateEnabled) { + lastError = String.Format( + "Resolution failed because EDM4U could not enable Jetifier " + + "in Unity {0} without Custom Gradle Properties Template. " + + "Please enable 'Custom Gradle Properties Template' found under " + + "'Player Settings > Settings for Android -> Publishing Settings' menu. " + + "Due to changes in Gradle project generated by Unity 2019.3 and "+ + "above, our recommended way of enabling Jetifier is by injecting "+ + "properties to Assets/Plugins/Android/gradleTemplate.properties.\n" + + "If you like to patch this yourself, simply disable 'Patch " + + "gradleTemplate.properties' in Android Resolver settings.", + Google.VersionHandler.GetUnityVersionMajorMinor() + ); + return false; + } + + // If there is a custom gradle properties template file and jetifier + // is enabled, inject the required properties in that file. + // This is the recommended way of doing things in Unity 2019.3 and above + // because the structure of gradle projects built by Unity has changed. + if (GradlePropertiesTemplateEnabled && + SettingsDialogObj.UseJetifier && + SettingsDialogObj.PatchPropertiesTemplateGradle) { + return GradleTemplateResolver.InjectProperties(); + } + return true; + }; + + Func, bool> patchSettingsTemplateGradle = (dependencies) => { + if (GradleBuildEnabled && GradleTemplateEnabled && + SettingsDialogObj.PatchSettingsTemplateGradle) { + return GradleTemplateResolver.InjectSettings(dependencies, out lastError); + } + return true; + }; if (GradleTemplateEnabled && SettingsDialogObj.PatchMainTemplateGradle) { + lastError = ""; RunOnMainThread.Run(() => { - finishResolution(GradleTemplateResolver.InjectDependencies( - PlayServicesSupport.GetAllDependencies().Values), ""); + var dependencies = PlayServicesSupport.GetAllDependencies().Values; + finishResolution( + GradleTemplateResolver.InjectDependencies(dependencies) && + patchGradleProperties() && + patchSettingsTemplateGradle(dependencies), lastError); }); } else { lastError = ""; - gradleResolver.DoResolution( - SettingsDialogObj.PackageDir, - closeWindowOnCompletion, - () => { - RunOnMainThread.Run(() => { - finishResolution(String.IsNullOrEmpty(lastError), lastError); - }); + if (GradleResolverInstance != null) { + GradleResolverInstance.DoResolution( + SettingsDialogObj.PackageDir, + closeWindowOnCompletion, + () => { + RunOnMainThread.Run(() => { + finishResolution(String.IsNullOrEmpty(lastError) && + patchGradleProperties(), lastError); + }); + }); + } else { + // Fail the resolution if gradleResolver is not initialized. + RunOnMainThread.Run(() => { + finishResolution(false, "GradleResolver is not created. Is your " + + "current build target set to Android?"); }); + } } } @@ -1772,17 +2106,16 @@ private static void ResolveUnsafe(Action 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"); + DialogWindow.Display("Android Resolver.", + "Resolver not enabled. Android platform must be selected.", + DialogWindow.Option.Selected0, "OK"); } /// /// Link to the documentation. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Documentation")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Documentation")] public static void OpenDocumentation() { Application.OpenURL(VersionHandlerImpl.DocumentationUrl("#android-resolver-usage")); } @@ -1790,7 +2123,7 @@ public static void OpenDocumentation() { /// /// Add a menu item for resolving the jars manually. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Settings")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Settings")] public static void SettingsDialog() { SettingsDialog window = (SettingsDialog)EditorWindow.GetWindow( @@ -1803,24 +2136,25 @@ public static void SettingsDialog() /// Interactive resolution of dependencies. /// private static void ExecuteMenuResolve(bool forceResolution) { - if (gradleResolver == null) { + if (GradleResolverInstance == null) { NotAvailableDialog(); return; } ScheduleResolve( forceResolution, false, (success) => { - EditorUtility.DisplayDialog( + DialogWindow.Display( "Android Dependencies", String.Format("Resolution {0}", success ? "Succeeded" : "Failed!\n\nYour application will not run, see " + - "the log for details."), "OK"); + "the log for details."), + DialogWindow.Option.Selected0, "OK"); }, false); } /// /// Add a menu item for resolving the jars manually. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Resolve")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Resolve")] public static void MenuResolve() { ExecuteMenuResolve(false); } @@ -1828,7 +2162,7 @@ public static void MenuResolve() { /// /// Add a menu item to force resolve the jars manually. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Force Resolve")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Force Resolve")] public static void MenuForceResolve() { ExecuteMenuResolve(true); } @@ -1836,7 +2170,7 @@ public static void MenuForceResolve() { /// /// Add a menu item to clear all resolved libraries. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Delete Resolved Libraries")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Delete Resolved Libraries")] public static void MenuDeleteResolvedLibraries() { DeleteResolvedLibrariesSync(); } @@ -1882,38 +2216,15 @@ public static IList> GetRepos( internal static IList GradleMavenReposLines(ICollection dependencies) { var lines = new List(); if (dependencies.Count > 0) { - var exportEnabled = GradleProjectExportEnabled; var projectPath = FileUtils.PosixPathSeparators(Path.GetFullPath(".")); var projectFileUri = GradleResolver.RepoPathToUri(projectPath); lines.Add("([rootProject] + (rootProject.subprojects as List)).each { project ->"); lines.Add(" project.repositories {"); - // 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. - lines.Add(String.Format( - " def unityProjectPath = \"{0}\" + " + - "file(rootProject.projectDir.path + \"/../../\").absolutePath", - GradleWrapper.FILE_SCHEME)); - lines.Add(" maven {"); - lines.Add(" url \"https://maven.google.com/""); - lines.Add(" }"); - foreach (var repoAndSources in GetRepos(dependencies: dependencies)) { - string repoUri; - if (repoAndSources.Key.StartsWith(projectFileUri) && !exportEnabled) { - repoUri = String.Format( - "(unityProjectPath + \"/{0}\")", - repoAndSources.Key.Substring(projectFileUri.Length + 1)); - } else { - repoUri = String.Format("\"{0}\"", repoAndSources.Key); - } - lines.Add(" maven {"); - lines.Add(String.Format(" url {0} // {1}", repoUri, - repoAndSources.Value)); - lines.Add(" }"); - } - lines.Add(" mavenLocal()"); - lines.Add(" jcenter()"); - lines.Add(" mavenCentral()"); + lines.AddRange(GradleTemplateResolver.GradleMavenReposLinesFromDependencies( + dependencies: dependencies, + addMavenGoogle: true, + addMavenCentral: true, + addMavenLocal: true)); lines.Add(" }"); lines.Add("}"); } @@ -1937,15 +2248,56 @@ internal static IList GradleDependenciesLines( // 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) && - (new Dependency.VersionComparer()).Compare("3.4", version) >= 0 ? + versionComparer.Compare("3.4", version) >= 0 ? "implementation" : "compile"; if (includeDependenciesBlock) lines.Add("dependencies {"); + // Build a map of dependencies with the max version of that dependency. + // If different packages have different versions of the same dependency, + // we want to activate only the highest version but still include the + // other versions as commented out dependency lines. + var dependenciesMaxVersions = new Dictionary(); + // Maintain a set of packages which we want to comment out. + HashSet packageSpecStringsToComment = new HashSet(); + foreach( var dependency in dependencies) { + Dependency dependencyMaxVersion; + if (dependenciesMaxVersions.TryGetValue(dependency.VersionlessKey, out dependencyMaxVersion)){ + if(versionComparer.Compare(dependency.Version, dependencyMaxVersion.Version) < 0) { + dependenciesMaxVersions[dependency.VersionlessKey] = dependency; + // We found a new larger version. Comment out older one + // Build a single item list since `GetPackageSpecs` + // accepts an IEnumerable type. + var packageSpecString = GradleResolver.DependencyToPackageSpec(dependencyMaxVersion); + packageSpecStringsToComment.Add(packageSpecString); + } + else { + // Dependency version is smaller than current max. + var packageSpecString = GradleResolver.DependencyToPackageSpec(dependency); + packageSpecStringsToComment.Add(packageSpecString); + } + } + else { + // First time encountering this dependency. + dependenciesMaxVersions[dependency.VersionlessKey] = dependency; + } + } foreach (var packageSpecAndSources in GetPackageSpecs(dependencies: dependencies)) { - lines.Add(String.Format( - " {0} '{1}' // {2}", includeStatement, packageSpecAndSources.Key, - packageSpecAndSources.Value)); + string line = " "; + if (packageSpecStringsToComment.Contains(packageSpecAndSources.Key)) { + PlayServicesResolver.Log( + String.Format( + "Ignoring duplicate package {0} with older version.", + packageSpecAndSources.Key), + level: LogLevel.Info + ); + line += "// "; + } + line += String.Format( + "{0} '{1}' // {2}", includeStatement, packageSpecAndSources.Key, + packageSpecAndSources.Value); + lines.Add(line); } if (includeDependenciesBlock) lines.Add("}"); } @@ -2000,7 +2352,13 @@ internal static IList PackagingOptionsLines(ICollection depe var sortedExcludeFiles = new List(excludeFiles); sortedExcludeFiles.Sort(); lines.Add("android {"); - lines.Add(" packagingOptions {"); + + // `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 @@ -2022,7 +2380,7 @@ internal static IList PackagingOptionsLines(ICollection depe /// script. This does not resolve dependency conflicts, it simply displays what is included /// by plugins in the project. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Display Libraries")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Display Libraries")] public static void MenuDisplayLibraries() { xmlDependencies.ReadAll(logger); var dependencies = PlayServicesSupport.GetAllDependencies().Values; @@ -2128,6 +2486,69 @@ internal static void LabelAssets(IEnumerable assetPaths, }, 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. /// @@ -2148,11 +2569,9 @@ internal static void DeleteLabeledAssets() { /// Called when settings change. /// internal static void OnSettingsChanged() { - PlayServicesSupport.verboseLogging = - SettingsDialogObj.VerboseLogging || - ExecutionEnvironment.InBatchMode; + PlayServicesSupport.verboseLogging = SettingsDialogObj.VerboseLogging; logger.Verbose = SettingsDialogObj.VerboseLogging; - if (gradleResolver != null) { + if (GradleResolverInstance != null) { PatchAndroidManifest(GetAndroidApplicationId(), null); Reresolve(); } @@ -2171,15 +2590,71 @@ internal static Dictionary GetResolutionSettings() { {"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)$"); @@ -2197,20 +2672,24 @@ internal static bool FilesContainJetpackLibraries(IEnumerable filenames) /// /// Prompt the user to change Unity's build settings, if the Jetifier is enabled but not - /// supported with Unity's current build settings. + /// supported by the version of Gradle in use. /// + /// Whether the user wants to use the jetifier. /// Prefix added to dialogs shown by this method. - /// true if the Jetifier is enabled, false otherwise - internal static bool CanEnableJetifierOrPromptUser(string titlePrefix) { - bool useJetifier = SettingsDialogObj.UseJetifier; - if (!useJetifier || ExecutionEnvironment.InBatchMode) return useJetifier; + /// 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) { - switch (EditorUtility.DisplayDialogComplex( + displayedEnableJetifierDialog = true; + DialogWindow.Display( titlePrefix + "Enable Jetifier?", String.Format( "Jetifier for Jetpack (AndroidX) libraries is only " + @@ -2222,24 +2701,53 @@ internal static bool CanEnableJetifierOrPromptUser(string titlePrefix) { "It's possible to use the Jetifier on Android Resolver managed " + "dependencies by disabling mainTemplate.gradle patching.", MinimumAndroidGradlePluginVersionForJetifier, version), - "Disable Jetifier", "Ignore", "Disable mainTemplate.gradle patching")) { - case 0: // Disable Jetifier - useJetifier = false; - break; - case 1: // Ignore - break; - case 2: // Disable mainTemplate.gradle patching - SettingsDialogObj.PatchMainTemplateGradle = false; - break; - } + 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) { - switch (EditorUtility.DisplayDialogComplex( + DialogWindow.Display( titlePrefix + "Enable Jetpack?", String.Format( "Jetpack (AndroidX) libraries are only supported when targeting Android " + @@ -2247,39 +2755,82 @@ internal static bool CanEnableJetifierOrPromptUser(string titlePrefix) { "Would you like to set the project's target API level to {0}?", MinimumApiLevelForJetpack, apiLevel > 0 ? apiLevel.ToString() : "auto (max. installed)"), - "Yes", "No", "Disable Jetifier")) { - case 0: // 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; + 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; } - break; - case 1: // No - // Don't change the settings but report that AndroidX will not work. - return false; - case 2: // Disable Jetifier - useJetifier = false; - 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; } - SettingsDialogObj.UseJetifier = useJetifier; - return useJetifier; + + 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); + }); + }); } /// diff --git a/source/PlayServicesResolver/src/SettingsDialog.cs b/source/AndroidResolver/src/SettingsDialog.cs similarity index 59% rename from source/PlayServicesResolver/src/SettingsDialog.cs rename to source/AndroidResolver/src/SettingsDialog.cs index 43e2add2..33bea57d 100644 --- a/source/PlayServicesResolver/src/SettingsDialog.cs +++ b/source/AndroidResolver/src/SettingsDialog.cs @@ -16,6 +16,7 @@ namespace GooglePlayServices { using System; + using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; @@ -37,11 +38,18 @@ private class Settings { 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. @@ -55,11 +63,18 @@ internal Settings() { 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); } /// @@ -74,11 +89,18 @@ internal void Save() { 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(); } } @@ -90,6 +112,11 @@ internal void Save() { 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 = @@ -97,6 +124,7 @@ internal void Save() { 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[] { @@ -107,11 +135,17 @@ internal void Save() { ExplodeAarsKey, PatchAndroidManifestKey, PatchMainTemplateGradleKey, + PatchPropertiesTemplateGradleKey, + PatchSettingsTemplateGradleKey, + UseFullCustomMavenRepoPathWhenExportKey, + UseFullCustomMavenRepoPathWhenNotExportKey, + LocalMavenRepoDirKey, UseJetifierKey, VerboseLoggingKey, AutoResolutionDisabledWarningKey, PromptBeforeAutoResolutionKey, - UseGradleDaemonKey + UseGradleDaemonKey, + UserRejectedGradleUpgradeKey }; internal const string AndroidPluginsDir = "Assets/Plugins/Android"; @@ -121,10 +155,11 @@ internal void Save() { // with a workaround - this can be enabled. static bool ConfigurablePackageDir = false; static string DefaultPackageDir = AndroidPluginsDir; + static string DefaultLocalMavenRepoDir = "Assets/GeneratedLocalRepo"; private Settings settings; - private static ProjectSettings projectSettings = new ProjectSettings(Namespace); + internal static ProjectSettings projectSettings = new ProjectSettings(Namespace); // Previously validated package directory. private static string previouslyValidatedPackageDir; @@ -136,6 +171,7 @@ internal void Save() { /// internal static void RestoreDefaultSettings() { projectSettings.DeleteKeys(PreferenceKeys); + PlayServicesResolver.analytics.RestoreDefaultSettings(); } internal static bool EnableAutoResolution { @@ -224,9 +260,37 @@ internal static bool PatchMainTemplateGradle { 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, false); } + get { return projectSettings.GetBool(UseJetifierKey, true); } } internal static bool VerboseLogging { @@ -234,6 +298,11 @@ internal static bool VerboseLogging { 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 @@ -267,8 +336,31 @@ internal static string ValidatePackageDir(string directory) { 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, 455); + minSize = new Vector2(425, 510); position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, minSize.x, minSize.y); } @@ -285,6 +377,8 @@ public void OnEnable() { /// 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, @@ -358,8 +452,8 @@ public void OnGUI() { "AndroidManifest.xml or a single target ABI is selected " + "without a compatible build system."); } else { - GUILayout.Label("AAR explosion will be disabled in exported Gradle builds " + - "(Unity 5.5 and above). You will need to set " + + 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."); } @@ -406,6 +500,29 @@ public void OnGUI() { 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 = @@ -424,23 +541,96 @@ public void OnGUI() { settings.packageDir)); } - GUILayout.BeginHorizontal(); - GUILayout.Label("Use Jetifier.", EditorStyles.boldLabel); - settings.useJetifier = EditorGUILayout.Toggle(settings.useJetifier); - GUILayout.EndHorizontal(); + 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( - "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 { + "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( - "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."); + "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); @@ -451,6 +641,10 @@ public void OnGUI() { settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); GUILayout.EndHorizontal(); + GUILayout.EndVertical(); + EditorGUILayout.EndScrollView(); + + GUILayout.BeginVertical(); GUILayout.Space(10); if (GUILayout.Button("Reset to Defaults")) { @@ -458,23 +652,80 @@ public void OnGUI() { // 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(); - EditorGUILayout.EndScrollView(); + GUILayout.EndVertical(); } } diff --git a/source/PlayServicesResolver/src/TextAreaDialog.cs b/source/AndroidResolver/src/TextAreaDialog.cs similarity index 98% rename from source/PlayServicesResolver/src/TextAreaDialog.cs rename to source/AndroidResolver/src/TextAreaDialog.cs index ad9c2b24..93eadb13 100644 --- a/source/PlayServicesResolver/src/TextAreaDialog.cs +++ b/source/AndroidResolver/src/TextAreaDialog.cs @@ -132,8 +132,10 @@ public virtual void Initialize() // Add to body text. public void AddBodyText(string text) { - bodyText += text; - Repaint(); + RunOnMainThread.Run(() => { + bodyText += text; + Repaint(); + }); } /// diff --git a/source/PlayServicesResolver/src/UnityCompat.cs b/source/AndroidResolver/src/UnityCompat.cs similarity index 72% rename from source/PlayServicesResolver/src/UnityCompat.cs rename to source/AndroidResolver/src/UnityCompat.cs index 3644e44e..265e19fb 100644 --- a/source/PlayServicesResolver/src/UnityCompat.cs +++ b/source/AndroidResolver/src/UnityCompat.cs @@ -39,6 +39,8 @@ public class UnityCompat { 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 = @@ -53,8 +55,21 @@ private static int AndroidPlatformVersionFallback { } // Parses a UnityEditor.AndroidSDKVersion enum for a value. - private static int VersionFromAndroidSDKVersionsEnum(string enumName, string fallbackPrefKey, + private static int VersionFromAndroidSDKVersionsEnum(object enumValue, string fallbackPrefKey, int fallbackValue) { + string enumName = null; + try { + enumName = Enum.GetName(typeof(AndroidSdkVersions), enumValue); + } catch (ArgumentException) { + //Fall back on auto if the enum value is not parsable + return -1; + } + + // If the enumName is empty then enumValue was not represented in the enum, + // most likely because Unity has not yet added the new version, + // fall back on the raw enumValue + if (String.IsNullOrEmpty(enumName)) return (int)enumValue; + if (enumName.StartsWith(UNITY_ANDROID_VERSION_ENUM_PREFIX)) { enumName = enumName.Substring(UNITY_ANDROID_VERSION_ENUM_PREFIX.Length); } @@ -85,7 +100,7 @@ private static int VersionFromAndroidSDKVersionsEnum(string enumName, string fal /// the sdk value (ie. 24 for Android 7.0 Nouget) public static int GetAndroidMinSDKVersion() { int minSdkVersion = VersionFromAndroidSDKVersionsEnum( - PlayerSettings.Android.minSdkVersion.ToString(), + (object)PlayerSettings.Android.minSdkVersion, ANDROID_MIN_SDK_FALLBACK_KEY, MinSDKVersionFallback); if (minSdkVersion == -1) return MinSDKVersionFallback; @@ -115,7 +130,7 @@ public static int GetAndroidTargetSDKVersion() { var property = typeof(UnityEditor.PlayerSettings.Android).GetProperty("targetSdkVersion"); int apiLevel = property == null ? -1 : VersionFromAndroidSDKVersionsEnum( - Enum.GetName(property.PropertyType, property.GetValue(null, null)), + property.GetValue(null, null), ANDROID_PLATFORM_FALLBACK_KEY, AndroidPlatformVersionFallback); if (apiLevel >= 0) return apiLevel; return FindNewestInstalledAndroidSDKVersion(); @@ -151,6 +166,64 @@ private static bool SetAndroidSDKVersion(int sdkVersion, string propertyName) { [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( @@ -248,54 +321,70 @@ private static int WarnOnAndroidSdkFallbackVersion() { public static int FindNewestInstalledAndroidSDKVersion() { var androidSdkToolsClass = AndroidSDKToolsClass; if (androidSdkToolsClass != null) { - 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) { + // 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 2018+ + // Unity 2019+ platformStrings = (IEnumerable)listTargetPlatforms.Invoke( - androidSdkToolsInstance, new object[] { AndroidJavaToolsInstance }); + androidSdkToolsInstance, null); } 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); - } + if (platformStrings != null) { + try { + // Unity 2018+ + platformStrings = (IEnumerable)listTargetPlatforms.Invoke( + androidSdkToolsInstance, new object[] { AndroidJavaToolsInstance }); + } catch (Exception) { } } - sdkVersionList.Sort(); - var numberOfSdks = sdkVersionList.Count; - if (numberOfSdks > 0) { - return sdkVersionList[numberOfSdks - 1]; + 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); + 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); } } @@ -319,7 +408,7 @@ public static int FindNewestInstalledAndroidSDKVersion() { /// /// Unfortunately the Unity API does not provide a way to get the current BuildTargetGroup from /// the currently active BuildTarget. - /// BuildTarget to convert. + /// BuildTarget to convert. /// BuildTargetGroup enum value. private static BuildTargetGroup ConvertBuildTargetToBuildTargetGroup(BuildTarget buildTarget) { var buildTargetToGroup = new Dictionary() { @@ -355,7 +444,7 @@ private static BuildTargetGroup ConvertBuildTargetToBuildTargetGroup(BuildTarget /// Application identifier if it can be retrieved, null otherwise. private static string GetUnity56AndAboveApplicationIdentifier(BuildTarget buildTarget) { var getApplicationIdentifierMethod = - typeof(UnityEditor.PlayerSettings).GetMethod("GetApplicationIdentifier"); + typeof(UnityEditor.PlayerSettings).GetMethod("GetApplicationIdentifier", new[]{typeof(BuildTargetGroup)}); if (getApplicationIdentifierMethod == null) return null; var buildTargetGroup = ConvertBuildTargetToBuildTargetGroup(buildTarget); if (buildTargetGroup == BuildTargetGroup.Unknown) return null; @@ -373,7 +462,9 @@ private static string GetUnity56AndAboveApplicationIdentifier(BuildTarget buildT private static bool SetUnity56AndAboveApplicationIdentifier(BuildTarget buildTarget, string applicationIdentifier) { var setApplicationIdentifierMethod = - typeof(UnityEditor.PlayerSettings).GetMethod("SetApplicationIdentifier"); + typeof(UnityEditor.PlayerSettings).GetMethod( + "SetApplicationIdentifier", + new[]{typeof(BuildTargetGroup), typeof(string)}); if (setApplicationIdentifierMethod == null) return false; var buildTargetGroup = ConvertBuildTargetToBuildTargetGroup(buildTarget); if (buildTargetGroup == BuildTargetGroup.Unknown) return false; diff --git a/source/PlayServicesResolver/src/VersionNumber.cs b/source/AndroidResolver/src/VersionNumber.cs similarity index 95% rename from source/PlayServicesResolver/src/VersionNumber.cs rename to source/AndroidResolver/src/VersionNumber.cs index 8b1ffe58..5309b99f 100644 --- a/source/PlayServicesResolver/src/VersionNumber.cs +++ b/source/AndroidResolver/src/VersionNumber.cs @@ -27,7 +27,7 @@ public class AndroidResolverVersionNumber { /// /// Version number, patched by the build process. /// - private const string VERSION_STRING = "1.2.128.0"; + private const string VERSION_STRING = "1.2.186"; /// /// Cached version structure. diff --git a/source/PlayServicesResolver/src/XmlDependencies.cs b/source/AndroidResolver/src/XmlDependencies.cs similarity index 100% rename from source/PlayServicesResolver/src/XmlDependencies.cs rename to source/AndroidResolver/src/XmlDependencies.cs diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTests.csproj b/source/AndroidResolver/test/AndroidResolverIntegrationTests.csproj new file mode 100644 index 00000000..4e6d28a1 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTests.csproj @@ -0,0 +1,74 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {6CB19B5A-371A-5E50-A3F7-E24F1696D501} + Library + Google + Google.AndroidResolverIntegrationTests + 1.2 + v3.5 + + + True + full + False + bin\Debug + DEBUG;UNITY_EDITOR + prompt + 4 + False + + + True + full + True + bin\Release + DEBUG;UNITY_EDITOR + prompt + 4 + False + + + ..\..\unity_dlls + + + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + + + + + + + + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040} + IntegrationTester + + + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} + AndroidResolver + + + diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestAdditionalDependencies.template b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.template similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestAdditionalDependencies.template rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.template diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.template b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.template new file mode 100644 index 00000000..ab1b4973 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.template @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestDependencies.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml similarity index 84% rename from source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestDependencies.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml index 0f51534a..d44ed8d4 100644 --- a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestDependencies.xml +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml @@ -8,6 +8,8 @@ Assets/Firebase/m2repository + + file:///my/nonexistant/test/repo diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar index b0fedd63..23794ccb 100755 Binary files a/source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar differ diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/gradleTemplateDISABLED.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/gradleTemplateDISABLED.properties new file mode 100644 index 00000000..de2dd054 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/gradleTemplateDISABLED.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M +org.gradle.parallel=true +unityStreamingAssets=**STREAMING_ASSETS** +**ADDITIONAL_PROPERTIES** diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateDISABLED.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle index 18384ec7..4dce823e 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar index b0fedd63..23794ccb 100644 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar index b0fedd63..23794ccb 100644 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar index b0fedd63..23794ccb 100644 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar index b0fedd63..23794ccb 100644 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar new file mode 100644 index 00000000..23794ccb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle similarity index 70% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle index e55df46d..5a09a8be 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } @@ -22,21 +21,20 @@ allprojects { // Android Resolver Repos Start ([rootProject] + (rootProject.subprojects as List)).each { project -> project.repositories { - def unityProjectPath = "file:///" + file(rootProject.projectDir.path + "/../../").absolutePath + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") maven { url "https://maven.google.com/" } maven { - url "file:///my/nonexistant/test/repo" // Assets/PlayServicesResolver/Editor/TestDependencies.xml:15 + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 } maven { - url (unityProjectPath + "/project_relative_path/repo") // Assets/PlayServicesResolver/Editor/TestDependencies.xml:15 + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 } maven { - url (unityProjectPath + "/Assets/Firebase/m2repository") // Assets/PlayServicesResolver/Editor/TestDependencies.xml:10 + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 } mavenLocal() - jcenter() mavenCentral() } } @@ -46,12 +44,20 @@ apply plugin: 'com.android.application' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start - compile 'com.android.support:support-annotations:26.1.0' // Assets/PlayServicesResolver/Editor/TestDependencies.xml:4 - compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/PlayServicesResolver/Editor/TestDependencies.xml:10 - compile 'com.google.firebase:firebase-common:16.0.0' // TestResolveAsync.SetupDependencies() + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 // Android Resolver Dependencies End **DEPS**} +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End android { compileSdkVersion **APIVERSION** buildToolsVersion '**BUILDTOOLS**' diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplateDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplateDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplate.gradle new file mode 100644 index 00000000..438e779b --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplate.gradle @@ -0,0 +1,102 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +// Android Resolver Repos Start +([rootProject] + (rootProject.subprojects as List)).each { project -> + project.repositories { + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "https://maven.google.com/" + } + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() + mavenCentral() + } +} +// Android Resolver Repos End +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + // compile 'com.google.android.gms:play-services-base:12.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:6 + // compile 'com.google.android.gms:play-services-base:15.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.xml:3 + compile 'com.google.android.gms:play-services-base:18.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:4 + // compile 'com.google.firebase:firebase-app-unity:5.+' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:8 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplateDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplateDISABLED.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplateDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..71d3fdee --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplate.gradle @@ -0,0 +1,81 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + // compile 'com.google.android.gms:play-services-base:12.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:6 + // compile 'com.google.android.gms:play-services-base:15.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.xml:3 + compile 'com.google.android.gms:play-services-base:18.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:4 + // compile 'com.google.firebase:firebase-app-unity:5.+' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:8 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplateDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplateDISABLED.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplateDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..d23796f5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplate.gradle @@ -0,0 +1,36 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() +// Android Resolver Repos Start + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() +// Android Resolver Repos End + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplate.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplate.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle similarity index 71% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle index 3aae306d..cac35627 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } @@ -28,21 +27,20 @@ allprojects { } ([rootProject] + (rootProject.subprojects as List)).each { project -> project.repositories { - def unityProjectPath = "file:///" + file(rootProject.projectDir.path + "/../../").absolutePath + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") maven { url "https://maven.google.com/" } maven { - url "file:///my/nonexistant/test/repo" // Assets/PlayServicesResolver/Editor/TestDependencies.xml:15 + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 } maven { - url (unityProjectPath + "/project_relative_path/repo") // Assets/PlayServicesResolver/Editor/TestDependencies.xml:15 + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 } maven { - url (unityProjectPath + "/Assets/Firebase/m2repository") // Assets/PlayServicesResolver/Editor/TestDependencies.xml:10 + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 } mavenLocal() - jcenter() mavenCentral() } } @@ -52,12 +50,20 @@ apply plugin: 'com.android.application' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start - compile 'com.android.support:support-annotations:26.1.0' // Assets/PlayServicesResolver/Editor/TestDependencies.xml:4 - compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/PlayServicesResolver/Editor/TestDependencies.xml:10 - compile 'com.google.firebase:firebase-common:16.0.0' // TestResolveAsync.SetupDependencies() + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 // Android Resolver Dependencies End **DEPS**} +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End android { compileSdkVersion **APIVERSION** buildToolsVersion '**BUILDTOOLS**' diff --git a/source/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/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle similarity index 70% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle index 93dc6e10..84def10b 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } @@ -22,21 +21,20 @@ allprojects { // Android Resolver Repos Start ([rootProject] + (rootProject.subprojects as List)).each { project -> project.repositories { - def unityProjectPath = "file:///" + file(rootProject.projectDir.path + "/../../").absolutePath + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") maven { url "https://maven.google.com/" } maven { - url "file:///my/nonexistant/test/repo" // Assets/PlayServicesResolver/Editor/TestDependencies.xml:15 + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 } maven { - url (unityProjectPath + "/project_relative_path/repo") // Assets/PlayServicesResolver/Editor/TestDependencies.xml:15 + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 } maven { - url (unityProjectPath + "/Assets/Firebase/m2repository") // Assets/PlayServicesResolver/Editor/TestDependencies.xml:10 + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 } mavenLocal() - jcenter() mavenCentral() } } @@ -46,12 +44,20 @@ 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/PlayServicesResolver/Editor/TestDependencies.xml:4 - compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/PlayServicesResolver/Editor/TestDependencies.xml:10 - compile 'com.google.firebase:firebase-common:16.0.0' // TestResolveAsync.SetupDependencies() + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 // Android Resolver Dependencies End **DEPS**} +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End android { compileSdkVersion **APIVERSION** buildToolsVersion '**BUILDTOOLS**' diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle index 18384ec7..4dce823e 100644 --- a/source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..b93a44f8 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplate.gradle @@ -0,0 +1,77 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplateLibraryDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplateLibraryDISABLED.gradle new file mode 100644 index 00000000..4dce823e --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplateLibraryDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..d23796f5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplate.gradle @@ -0,0 +1,36 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() +// Android Resolver Repos Start + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() +// Android Resolver Repos End + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..ddfc07b3 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplate.gradle @@ -0,0 +1,77 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..d23796f5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplate.gradle @@ -0,0 +1,36 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() +// Android Resolver Repos Start + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() +// Android Resolver Repos End + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar new file mode 100644 index 00000000..23794ccb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/R.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/R.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/proguard.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/project.properties similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF new file mode 100644 index 00000000..cfe75e41 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: 1.8.0_221 (Oracle Corporation) + diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/R.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/R.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/x86/libFirebaseCppApp-5.1.1.so b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/libFirebaseCppApp-5.1.1.so similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/x86/libFirebaseCppApp-5.1.1.so rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/libFirebaseCppApp-5.1.1.so diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/project.properties similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/R.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/R.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/proguard.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/project.properties similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.annotation.annotation-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.annotation.annotation-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-common-2.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-common-2.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-runtime-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-runtime-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.collection.collection-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.collection.collection-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.core.core-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.core.core-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.customview.customview-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.customview.customview-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.documentfile.documentfile-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.documentfile.documentfile-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.fragment.fragment-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.fragment.fragment-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.interpolator.interpolator-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.interpolator.interpolator-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.loader.loader-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.loader.loader-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.media.media-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.media.media-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.print.print-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.print.print-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.viewpager.viewpager-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.viewpager.viewpager-1.0.0.aar 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/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml 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/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/R.txt diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/x86/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/x86/libFirebaseCppApp-5.1.1.so rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/libFirebaseCppApp-5.1.1.so diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/R.txt 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/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt 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/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.annotation.annotation-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.annotation.annotation-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-common-2.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-common-2.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-runtime-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-runtime-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.collection.collection-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.collection.collection-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.core.core-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.core.core-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.customview.customview-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.customview.customview-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.documentfile.documentfile-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.documentfile.documentfile-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.fragment.fragment-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.fragment.fragment-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.interpolator.interpolator-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.interpolator.interpolator-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.loader.loader-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.loader.loader-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.media.media-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.media.media-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.print.print-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.print.print-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.viewpager.viewpager-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.viewpager.viewpager-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar 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/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/R.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json diff --git a/source/PlayServicesResolver/test/resolve_async/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 similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ProjectSettings/GvhProjectSettings.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ProjectSettings/GvhProjectSettings.xml new file mode 100644 index 00000000..20f2d7b5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ProjectSettings/GvhProjectSettings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom new file mode 100644 index 00000000..9e3d37d0 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + classifier + 1.0.1 + aar + + + + + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/maven-metadata.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/maven-metadata.xml new file mode 100644 index 00000000..ba1496c5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/maven-metadata.xml @@ -0,0 +1,13 @@ + + org.test.psr + classifier + + 1.0.1 + + 1.0.1 + + + + + + diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestResolveAsync.cs b/source/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs similarity index 60% rename from source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestResolveAsync.cs rename to source/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs index 382db56f..cc7a6e66 100644 --- a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestResolveAsync.cs +++ b/source/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (C) 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,96 +21,12 @@ using System.Linq; using System.Reflection; -[UnityEditor.InitializeOnLoad] -public class TestResolveAsync { +using Google.JarResolver; +using GooglePlayServices; - /// - /// Test case class. - /// - /// This specifies a test case to execute. Each test case has a name which is used to - /// log the result of the test and the method to execute as part of the test case. - /// - class TestCase { - - /// - /// Test case delegate. - /// - /// Object executing this method. - /// Called when the test case is complete. - public delegate void MethodDelegate(TestCase testCase, - Action testCaseComplete); - - /// - /// Name of the test case. - /// - public string Name { get; set; } - - /// - /// Delegate that runs the test case logic. - /// - public MethodDelegate Method { get; set; } - } - - /// - /// Result of a test. - /// - class TestCaseResult { - - /// - /// Initialize the class. - /// - public TestCaseResult(TestCase testCase) { - TestCaseName = testCase.Name; - ErrorMessages = new List(); - Skipped = false; - } - - /// - /// Name of the test case. This does not need to be set by the test case. - /// - public string TestCaseName { private get; set; } - - /// - /// Error messages reported by a test case failure. - /// - public List ErrorMessages { get; set; } - - /// - /// Whether the test case was skipped. - /// - public bool Skipped { get; set; } - - /// - /// Whether the test case succeeded. - /// - public bool Succeeded { - get { - return Skipped || ErrorMessages == null || ErrorMessages.Count == 0; - } - } - - /// - /// 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()) : ""); - } - } - - /// - /// Executed test case names and failure messages (if any). - /// - private static List testCaseResults = new List(); +namespace Google { - /// - /// Set of test cases to execute. - /// - private static List testCases = new List(); +public class AndroidResolverIntegrationTests { /// /// EditorUserBuildSettings property which controls the Android build system. @@ -128,6 +44,12 @@ public string FormatString(bool includeFailureMessages) { /// 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. /// @@ -140,124 +62,252 @@ public string FormatString(bool includeFailureMessages) { 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"; /// - /// Major / minor Unity version numbers. + /// + /// Enabled Gradle template properties file. /// - private static float unityVersion; + private const string GRADLE_TEMPLATE_PROPERTIES_ENABLED = "Assets/Plugins/Android/gradleTemplate.properties"; /// - /// This module can be executed multiple times when the Version Handler is enabling - /// so this method uses a temporary file to determine whether the module has been executed - /// once in a Unity session. + /// + /// Enabled Gradle settings properties file. /// - /// true if the module was previously initialized, false otherwise. - private static bool SetInitialized() { - const string INITIALIZED_PATH = "Temp/TestEnabledCallbackInitialized"; - if (File.Exists(INITIALIZED_PATH)) return true; - File.WriteAllText(INITIALIZED_PATH, "Ready"); - return false; - } + private const string GRADLE_TEMPLATE_SETTINGS_ENABLED = "Assets/Plugins/Android/settingsTemplate.gradle"; /// - /// Register a method to call when the Version Handler has enabled all plugins in the project. + /// Configure tests to run. /// - static TestResolveAsync() { - unityVersion = Google.VersionHandler.GetUnityVersionMajorMinor(); - // Disable stack traces for more condensed logs. - UnityEngine.Application.stackTraceLogType = UnityEngine.StackTraceLogType.None; + [IntegrationTester.Initializer] + public static void ConfigureTestCases() { + // The default application name is different in different versions of Unity. Set the value + // in the beginning of the test to ensure all AndroidManifest.xml are using the same + // application name across different versions of Unity. + UnityCompat.SetApplicationId(UnityEditor.BuildTarget.Android, "com.Company.ProductName"); // Set of files to ignore (relative to the Assets/Plugins/Android directory) in all tests // that do not use the Gradle template. var nonGradleTemplateFilesToIgnore = new HashSet() { Path.GetFileName(GRADLE_TEMPLATE_DISABLED), - Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED) }; + // Set of files to ignore (relative to the Assets/Plugins/Android directory) in all tests + // that do not use the Gradle template. + var unity2022WithoutJetifierGradleTemplateFilesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + }; + var defaultGradleTemplateFilesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED), + }; + + UnityEngine.Debug.Log("Setting up test cases for execution."); - testCases.AddRange(new [] { + IntegrationTester.Runner.ScheduleTestCases(new [] { // This *must* be the first test case as other test cases depend upon it. - new TestCase { + new IntegrationTester.TestCase { Name = "ValidateAndroidTargetSelected", Method = ValidateAndroidTargetSelected, }, - new TestCase { + new IntegrationTester.TestCase { Name = "SetupDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); - var testCaseResult = new TestCaseResult(testCase); + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); ValidateDependencies(testCaseResult); testCaseComplete(testCaseResult); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplate", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); + string expectedAssetsDir = null; + string gradleTemplateSettings = null; + HashSet filesToIgnore = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplate_2022_2"; + gradleTemplateSettings = GRADLE_TEMPLATE_SETTINGS_DISABLED; + filesToIgnore = unity2022WithoutJetifierGradleTemplateFilesToIgnore; + } else { + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplate"; + filesToIgnore = defaultGradleTemplateFilesToIgnore; + } + + ResolveWithGradleTemplate( + GRADLE_TEMPLATE_DISABLED, + expectedAssetsDir, + testCase, testCaseComplete, + otherExpectedFiles: new [] { + "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: true, + gradleTemplateSettings: gradleTemplateSettings); + } + }, + new IntegrationTester.TestCase { + Name = "ResolveForGradleBuildSystemWithDuplicatePackages", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + // Add 2 additional dependency files (each file contains a single package + // but with different versions). + UpdateAdditionalDependenciesFile(true, ADDITIONAL_DEPENDENCIES_FILENAME); + UpdateAdditionalDependenciesFile(true, ADDITIONAL_DUPLICATE_DEPENDENCIES_FILENAME); + + string expectedAssetsDir = null; + string gradleTemplateSettings = null; + HashSet filesToIgnore = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2"; + gradleTemplateSettings = GRADLE_TEMPLATE_SETTINGS_DISABLED; + filesToIgnore = unity2022WithoutJetifierGradleTemplateFilesToIgnore; + } else { + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages"; + filesToIgnore = defaultGradleTemplateFilesToIgnore; + } + ResolveWithGradleTemplate( GRADLE_TEMPLATE_DISABLED, - "ExpectedArtifacts/NoExport/GradleTemplate", + expectedAssetsDir, testCase, testCaseComplete, otherExpectedFiles: new [] { - "Assets/Firebase/m2repository/com/google/firebase/" + - "firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, - filesToIgnore: new HashSet { - Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) - }); + "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: true, + gradleTemplateSettings: gradleTemplateSettings); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolverForGradleBuildSystemWithTemplateUsingJetifier", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); - UseJetifier = true; + 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, - "ExpectedArtifacts/NoExport/GradleTemplateJetifier", + expectedAssetsDir, testCase, testCaseComplete, otherExpectedFiles: new [] { - "Assets/Firebase/m2repository/com/google/firebase/" + - "firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, - filesToIgnore: new HashSet { - Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) - }); + "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 TestCase { + 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, - "ExpectedArtifacts/NoExport/GradleTemplateLibrary", + expectedAssetsDir, testCase, testCaseComplete, otherExpectedFiles: new [] { - "Assets/Firebase/m2repository/com/google/firebase/" + - "firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, - filesToIgnore: new HashSet { - Path.GetFileName(GRADLE_TEMPLATE_DISABLED) - }); + "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: true, + gradleTemplateSettings: gradleTemplateSettings); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplateEmpty", Method = (testCase, testCaseComplete) => { string enabledDependencies = - "Assets/PlayServicesResolver/Editor/TestDependencies.xml"; + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml"; string disabledDependencies = - "Assets/PlayServicesResolver/Editor/TestDependenciesDISABLED.xml"; + "Assets/ExternalDependencyManager/Editor/TestDependenciesDISABLED.xml"; Action enableDependencies = () => { UnityEditor.AssetDatabase.MoveAsset(disabledDependencies, enabledDependencies); @@ -267,7 +317,7 @@ static TestResolveAsync() { var error = UnityEditor.AssetDatabase.MoveAsset(enabledDependencies, disabledDependencies); if (!String.IsNullOrEmpty(error)) { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List() { error } }); return; } @@ -280,14 +330,16 @@ static TestResolveAsync() { testCaseComplete(testCaseResult); }, filesToIgnore: new HashSet { - Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED) }); } finally { enableDependencies(); } } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystem", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -296,7 +348,7 @@ static TestResolveAsync() { null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemSync", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -306,32 +358,7 @@ static TestResolveAsync() { synchronous: true); } }, - new TestCase { - Name = "ResolveForInternalBuildSystem", - Method = (testCase, testCaseComplete) => { - ClearAllDependencies(); - SetupDependencies(); - Resolve("Internal", false, - AarsWithNativeLibrariesSupported ? - "ExpectedArtifacts/NoExport/InternalNativeAars" : - "ExpectedArtifacts/NoExport/InternalNativeAarsExploded", - null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); - } - }, - new TestCase { - Name = "ResolveForInternalBuildSystemUsingJetifier", - Method = (testCase, testCaseComplete) => { - ClearAllDependencies(); - SetupDependencies(); - UseJetifier = true; - Resolve("Internal", false, - AarsWithNativeLibrariesSupported ? - "ExpectedArtifacts/NoExport/InternalNativeAarsJetifier" : - "ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier", - null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); - } - }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemAndExport", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -340,7 +367,7 @@ static TestResolveAsync() { null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveAddedDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -350,7 +377,7 @@ static TestResolveAsync() { null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveRemovedDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -362,7 +389,7 @@ static TestResolveAsync() { null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "DeleteResolvedLibraries", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -370,8 +397,7 @@ static TestResolveAsync() { Resolve("Gradle", true, "ExpectedArtifacts/Export/Gradle", null, nonGradleTemplateFilesToIgnore, testCase, (testCaseResult) => { - Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "DeleteResolvedLibrariesSync", null); + PlayServicesResolver.DeleteResolvedLibrariesSync(); var unexpectedFilesMessage = new List(); var resolvedFiles = ListFiles("Assets/Plugins/Android", nonGradleTemplateFilesToIgnore); @@ -387,123 +413,145 @@ static TestResolveAsync() { synchronous: true); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplateDeleteLibraries", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); - var filesToIgnore = new HashSet { - Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED) - }; + + 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, - "ExpectedArtifacts/NoExport/GradleTemplate", + expectedAssetsDir, testCase, (testCaseResult) => { - Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "DeleteResolvedLibrariesSync", null); + PlayServicesResolver.DeleteResolvedLibrariesSync(); + string expectedAssetsDirEmpty = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDirEmpty = "ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2"; + } else { + expectedAssetsDirEmpty = "ExpectedArtifacts/NoExport/GradleTemplateEmpty"; + } testCaseResult.ErrorMessages.AddRange(CompareDirectoryContents( - "ExpectedArtifacts/NoExport/GradleTemplateEmpty", + 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); + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: false, + gradleTemplateSettings: gradleTemplateSettings); } }, }); - // Test resolution with Android ABI filtering. - if (unityVersion >= 2018.0f) { - testCases.AddRange(new [] { - new TestCase { - Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7aAndArm64", + // Internal build system for Android is removed in Unity 2019, even + // UnityEditor.AndroidBuildSystem.Internal still exist. + if (IntegrationTester.Runner.UnityVersion < 2019.0f) { + IntegrationTester.Runner.ScheduleTestCases(new [] { + new IntegrationTester.TestCase { + Name = "ResolveForInternalBuildSystem", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); - Resolve("Gradle", false, - "ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64", - "armeabi-v7a, arm64-v8a", nonGradleTemplateFilesToIgnore, - testCase, testCaseComplete); + SetupDependencies(); + Resolve("Internal", false, AarsWithNativeLibrariesSupported ? + "ExpectedArtifacts/NoExport/InternalNativeAars" : + "ExpectedArtifacts/NoExport/InternalNativeAarsExploded", + null, nonGradleTemplateFilesToIgnore, testCase, + testCaseComplete); } - } - }); - } else if (unityVersion >= 5.0f) { - testCases.AddRange(new [] { - new TestCase { - Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7a", + }, + new IntegrationTester.TestCase { + Name = "ResolveForInternalBuildSystemUsingJetifier", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); - Resolve("Gradle", false, - "ExpectedArtifacts/NoExport/GradleArmeabiv7a", - "armeabi-v7a", nonGradleTemplateFilesToIgnore, - testCase, testCaseComplete); + SetupDependencies(); + GooglePlayServices.SettingsDialog.UseJetifier = true; + Resolve("Internal", false, AarsWithNativeLibrariesSupported ? + "ExpectedArtifacts/NoExport/InternalNativeAarsJetifier" : + "ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier", + null, nonGradleTemplateFilesToIgnore, testCase, + testCaseComplete); } - } + }, }); } - UnityEngine.Debug.Log("Set up callback on Version Handler completion."); - Google.VersionHandler.UpdateCompleteMethods = new [] { - ":TestResolveAsync:VersionHandlerReady" - }; - UnityEngine.Debug.Log("Enable plugin using the Version Handler."); - Google.VersionHandler.UpdateNow(); - } - - /// - /// Whether the Gradle builds are supported by the current version of Unity. - /// - private static bool GradleBuildSupported { - get { return unityVersion >= 5.5f; } + // Test resolution with Android ABI filtering. + if (IntegrationTester.Runner.UnityVersion >= 2018.0f) { + IntegrationTester.Runner.ScheduleTestCase( + new IntegrationTester.TestCase { + Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7aAndArm64", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + Resolve("Gradle", false, + "ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64", + "armeabi-v7a, arm64-v8a", nonGradleTemplateFilesToIgnore, + testCase, testCaseComplete); + } + }); + } else if (IntegrationTester.Runner.UnityVersion >= 5.0f) { + IntegrationTester.Runner.ScheduleTestCase( + new IntegrationTester.TestCase { + Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7a", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + Resolve("Gradle", false, + "ExpectedArtifacts/NoExport/GradleArmeabiv7a", + "armeabi-v7a", nonGradleTemplateFilesToIgnore, + testCase, testCaseComplete); + } + }); + } } /// - /// Whether the current version of Unity requires AARs with native artifacts to be converted - /// to ant / eclipse projects. + /// Whether Current Unity Version requires Maven Repo in Gradle Settings Template. /// - private static bool AarsWithNativeLibrariesSupported { - get { return unityVersion < 2017.0f; } + private static bool UnityChangeMavenInSettings_2022_2 { + get { return IntegrationTester.Runner.UnityVersion >= 2022.2f; } } /// - /// Get property that gets and sets Android ABIs. + /// Whether Current Unity Version requires Jetifier enabling in Gradle Properties Template. /// - private static PropertyInfo AndroidAbisCurrentStringProperty { - get { - return Google.VersionHandler.FindClass( - "Google.JarResolver", "GooglePlayServices.AndroidAbis").GetProperty( - "CurrentString"); - } + private static bool UnityChangeJetifierInProperties_2019_3 { + get { return IntegrationTester.Runner.UnityVersion >= 2019.3f; } } /// - /// Set Android ABIs. - /// - private static string AndroidAbisCurrentString { - set { AndroidAbisCurrentStringProperty.SetValue(null, value, null); } - get { return (string)AndroidAbisCurrentStringProperty.GetValue(null, null); } - } - - /// - /// Get the property that gets and sets whether the Jetifier is enabled. + /// Whether the Gradle builds are supported by the current version of Unity. /// - private static PropertyInfo UseJetifierBoolProperty { - get { - return Google.VersionHandler.FindClass( - "Google.JarResolver", "GooglePlayServices.SettingsDialog").GetProperty( - "UseJetifier", BindingFlags.Static | BindingFlags.NonPublic); - } + private static bool GradleBuildSupported { + get { return IntegrationTester.Runner.UnityVersion >= 5.5f; } } /// - /// Get / set jetifier setting. + /// Whether the current version of Unity requires AARs with native artifacts to be converted + /// to ant / eclipse projects. /// - private static bool UseJetifier { - set { UseJetifierBoolProperty.SetValue(null, value, null); } - get { return (bool)UseJetifierBoolProperty.GetValue(null, null); } + private static bool AarsWithNativeLibrariesSupported { + get { return IntegrationTester.Runner.UnityVersion < 2017.0f; } } /// @@ -547,144 +595,38 @@ private static object StringToAndroidBuildSystemValue(string value) { return Enum.Parse(androidBuildSystemType, value); } - - /// - /// Log test result summary and quit the application. - /// - private static void LogSummaryAndExit() { - bool passed = true; - var testSummaryLines = new List(); - foreach (var testCaseResult in testCaseResults) { - testSummaryLines.Add(testCaseResult.FormatString(false)); - passed &= testCaseResult.Succeeded; - } - UnityEngine.Debug.Log(String.Format("Test(s) {0}.\n{1}", passed ? "PASSED" : "FAILED", - String.Join("\n", testSummaryLines.ToArray()))); - UnityEditor.EditorApplication.Exit(passed ? 0 : 1); - } - - /// - /// Log a test case result with error details. - /// - /// Result to log. - private static void LogTestCaseResult(TestCaseResult testCaseResult) { - testCaseResults.Add(testCaseResult); - UnityEngine.Debug.Log(testCaseResult.FormatString(true)); - } - - /// - /// Execute a function for a test case catching any exceptions and logging the result. - /// - /// Object executing this method. - /// Action to execute. - /// Whether to execute the next test case if the specified action - /// fails. - /// true if the action executed without any exceptions, false otherwise. - private static bool ExecuteTestCase(TestCase testCase, Action testCaseAction, - bool executeNext) { - bool succeeded = true; - try { - testCaseAction(); - } catch (Exception e) { - LogTestCaseResult(new TestCaseResult(testCase) { - ErrorMessages = new List { e.ToString() } - }); - succeeded = false; - } - if (!succeeded && executeNext) ExecuteNextTestCase(); - return succeeded; - } - - /// - /// Execute the next queued test case. - /// - private static void ExecuteNextTestCase() { - bool executeNext; - do { - executeNext = false; - if (testCases.Count > 0) { - var testCase = testCases[0]; - testCases.RemoveAt(0); - UnityEngine.Debug.Log(String.Format("Test {0} starting...", testCase.Name)); - // If the test threw an exception on this thread, execute the next test case - // in a loop. - executeNext = !ExecuteTestCase( - testCase, - () => { - testCase.Method(testCase, (testCaseResult) => { - UnityEngine.Debug.Log(String.Format("Test {0} complete", - testCase.Name)); - testCaseResult.TestCaseName = testCase.Name; - LogTestCaseResult(testCaseResult); - ExecuteNextTestCase(); - }); - }, false); - } else { - LogSummaryAndExit(); - } - } while (executeNext); - } - - /// - /// Called when the Version Handler has enabled all managed plugins in a project. - /// - public static void VersionHandlerReady() { - UnityEngine.Debug.Log("VersionHandler is ready."); - Google.VersionHandler.UpdateCompleteMethods = null; - // If this has already been initialize this session, do not start tests again. - if (SetInitialized()) return; - // Start executing tests. - ExecuteNextTestCase(); - } - /// /// Make sure the Android platform is selected for testing. /// - private static void ValidateAndroidTargetSelected(TestCase testCase, - Action testCaseComplete) { + private static void ValidateAndroidTargetSelected( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { if (UnityEditor.EditorUserBuildSettings.activeBuildTarget != UnityEditor.BuildTarget.Android) { - LogTestCaseResult(new TestCaseResult(testCase) { + IntegrationTester.Runner.LogTestCaseResult( + new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List() { "Target platform must be Android" } }); - LogSummaryAndExit(); + IntegrationTester.Runner.LogSummaryAndExit(); } - // Also, set the internal Gradle version to a deterministic version number. This controls - // how gradle template snippets are generated by GradleTemplateResolver. - var resolver = Google.VersionHandler.FindClass("Google.JarResolver", - "GooglePlayServices.PlayServicesResolver"); - resolver.GetProperty("GradleVersion").SetValue(null, "2.14", null); - testCaseComplete(new TestCaseResult(testCase)); - } - /// - /// Get the Android Resolver support instance. - /// NOTE: This is deprecated and only exposed for testing. - /// - private static object AndroidResolverSupport { - get { - // Get the deprecated dependency management API. - return Google.VersionHandler.InvokeStaticMethod( - Google.VersionHandler.FindClass( - "Google.JarResolver", "Google.JarResolver.PlayServicesSupport"), - "CreateInstance", new object[] { "Test", null, "ProjectSettings" }); - } - } + // Verify if PlayServicesResolver properties are working properly. + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); - /// - /// Cached Android Resolver class. - /// - private static Type androidResolverClass = null; + if (String.IsNullOrEmpty(PlayServicesResolver.AndroidGradlePluginVersion)) { + testCaseResult.ErrorMessages.Add(String.Format( + "PlayServicesResolver.AndroidGradlePluginVersion is empty or null")); + } - /// - /// Get the Android Resolver class. - /// - private static Type AndroidResolverClass { - get { - androidResolverClass = androidResolverClass ?? Google.VersionHandler.FindClass( - "Google.JarResolver", "GooglePlayServices.PlayServicesResolver"); - return androidResolverClass; + if (String.IsNullOrEmpty(PlayServicesResolver.GradleVersion)) { + testCaseResult.ErrorMessages.Add(String.Format( + "PlayServicesResolver.GradleVersion is empty or null")); } + + // Also, set the internal Gradle version to a deterministic version number. This controls + // how gradle template snippets are generated by GradleTemplateResolver. + PlayServicesResolver.GradleVersion = "2.14"; + testCaseComplete(testCaseResult); } /// @@ -696,12 +638,15 @@ private static Type AndroidResolverClass { /// private static void ClearAllDependencies() { UnityEngine.Debug.Log("Clear all loaded dependencies"); - UseJetifier = false; - AndroidResolverSupport.GetType().GetMethod( - "ResetDependencies", - BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); + GooglePlayServices.SettingsDialog.UseJetifier = false; + GooglePlayServices.SettingsDialog.PatchPropertiesTemplateGradle = false; + GooglePlayServices.SettingsDialog.PatchSettingsTemplateGradle = false; + + GooglePlayServices.SettingsDialog.UserRejectedGradleUpgrade = true; - UpdateAdditionalDependenciesFile(false); + PlayServicesSupport.ResetDependencies(); + UpdateAdditionalDependenciesFile(false, ADDITIONAL_DEPENDENCIES_FILENAME); + UpdateAdditionalDependenciesFile(false, ADDITIONAL_DUPLICATE_DEPENDENCIES_FILENAME); } /// @@ -710,9 +655,8 @@ private static void ClearAllDependencies() { /// future. /// private static void SetupDependencies() { - Google.VersionHandler.InvokeInstanceMethod( - AndroidResolverSupport, "DependOn", - new object[] { "com.google.firebase", "firebase-common", "16.0.0" }); + PlayServicesSupport.CreateInstance("Test", null, "ProjectSettings").DependOn( + "com.google.firebase", "firebase-common", "16.0.0"); } /// @@ -720,39 +664,40 @@ private static void SetupDependencies() { /// /// TestCaseResult instance to add errors to if this method /// fails. - private static void ValidateDependencies(TestCaseResult testCaseResult) { + private static void ValidateDependencies(IntegrationTester.TestCaseResult testCaseResult) { // Validate set dependencies are present. CompareKeyValuePairLists( new List>() { new KeyValuePair( "com.android.support:support-annotations:26.1.0", - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:4"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4"), new KeyValuePair( "com.google.firebase:firebase-app-unity:5.1.1", - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:10"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10"), new KeyValuePair( "com.google.firebase:firebase-common:16.0.0", - "TestResolveAsync.SetupDependencies()") + "Google.AndroidResolverIntegrationTests.SetupDependencies"), + new KeyValuePair( + "org.test.psr:classifier:1.0.1:foo@aar", + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12"), }, - (IList>)Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "GetPackageSpecs", null), + PlayServicesResolver.GetPackageSpecs(), "Package Specs", testCaseResult); // Validate configured repos are present. CompareKeyValuePairLists( new List>() { new KeyValuePair( "file:///my/nonexistant/test/repo", - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:15"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17"), new KeyValuePair( "file:///" + Path.GetFullPath("project_relative_path/repo").Replace("\\", "/"), - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:15"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17"), new KeyValuePair( "file:///" + Path.GetFullPath( "Assets/Firebase/m2repository").Replace("\\", "/"), - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:10") + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10") }, - (IList>)Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "GetRepos", null), + PlayServicesResolver.GetRepos(), "Repos", testCaseResult); } @@ -767,7 +712,7 @@ private static void ValidateDependencies(TestCaseResult testCaseResult) { private static void CompareKeyValuePairLists( IList> expectedList, IList> testList, string listDescription, - TestCaseResult testCaseResult) { + IntegrationTester.TestCaseResult testCaseResult) { if (expectedList.Count != testList.Count) { testCaseResult.ErrorMessages.Add(String.Format( "Returned list of {0} is an unexpected size {1} vs {2}", @@ -792,13 +737,18 @@ private static void CompareKeyValuePairLists( /// /// If true, will copy the template file to an XML file if it /// doesn't exist. If false, delete the XML file if it exists. - private static void UpdateAdditionalDependenciesFile(bool addDependencyFile) { + /// Name of the template file (without extension) to + /// create an XML from. + private static void UpdateAdditionalDependenciesFile( + bool addDependencyFile, + string filename=ADDITIONAL_DEPENDENCIES_FILENAME) { string currentDirectory = Directory.GetCurrentDirectory(); - string editorPath = Path.Combine(currentDirectory, "Assets/PlayServicesResolver/Editor/"); + string editorPath = Path.Combine(currentDirectory, + "Assets/ExternalDependencyManager/Editor/"); - string templateFilePath = Path.Combine(editorPath, ADDITIONAL_DEPENDENCIES_FILENAME + + string templateFilePath = Path.Combine(editorPath, filename+ ".template"); - string xmlFilePath = Path.Combine(editorPath, ADDITIONAL_DEPENDENCIES_FILENAME + ".xml"); + string xmlFilePath = Path.Combine(editorPath, filename+ ".xml"); if (addDependencyFile && !File.Exists(xmlFilePath)) { if (!File.Exists(templateFilePath)) { UnityEngine.Debug.LogError("Could not find file: " + templateFilePath); @@ -833,13 +783,14 @@ private static void UpdateAdditionalDependenciesFile(bool addDependencyFile) { private static void Resolve(string androidBuildSystem, bool exportProject, string expectedAssetsDir, string targetAbis, ICollection filesToIgnore, - TestCase testCase, Action testCaseComplete, + IntegrationTester.TestCase testCase, + Action testCaseComplete, bool synchronous = false) { // Set the Android target ABIs. - AndroidAbisCurrentString = targetAbis; + GooglePlayServices.AndroidAbis.CurrentString = targetAbis; // Try setting the build system if this version of Unity supports it. if (!GradleBuildSupported && androidBuildSystem == "Gradle") { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { Skipped = true, ErrorMessages = new List { "Unity version does not support Gradle builds." @@ -851,7 +802,7 @@ private static void Resolve(string androidBuildSystem, bool exportProject, ANDROID_BUILD_SYSTEM, StringToAndroidBuildSystemValue(androidBuildSystem)) && GetEditorUserBuildSettingsProperty( ANDROID_BUILD_SYSTEM, androidBuildSystem).ToString() == androidBuildSystem)) { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List { String.Format("Unable to set AndroidBuildSystem to {0}.", androidBuildSystem) @@ -863,7 +814,7 @@ private static void Resolve(string androidBuildSystem, bool exportProject, if (!(SetEditorUserBuildSettingsProperty(EXPORT_ANDROID_PROJECT, exportProject) && (bool)GetEditorUserBuildSettingsProperty(EXPORT_ANDROID_PROJECT, exportProject) == exportProject)) { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List { String.Format("Unable to set Android export project to {0}.", exportProject) @@ -873,45 +824,20 @@ private static void Resolve(string androidBuildSystem, bool exportProject, // Resolve dependencies. Action completeWithResult = (bool complete) => { - ExecuteTestCase( + IntegrationTester.Runner.ExecuteTestCase( testCase, () => { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = ValidateAndroidResolution(expectedAssetsDir, complete, filesToIgnore) }); }, true); }; if (synchronous) { - bool success = (bool)Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "ResolveSync", args: new object[] { true }, - namedArgs: null); + bool success = PlayServicesResolver.ResolveSync(true); completeWithResult(success); } else { - Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "Resolve", args: null, - namedArgs: new Dictionary() { - {"resolutionCompleteWithResult", completeWithResult} - }); - } - } - - /// - /// Accesses the PatchMainTemplateGradle setting. - /// - private static bool PatchMainTemplateGradle { - get { - return (bool)Google.VersionHandler.FindClass( - null, "GooglePlayServices.SettingsDialog").GetProperty( - "PatchMainTemplateGradle", - BindingFlags.Static | BindingFlags.NonPublic).GetValue(null, null); - } - - set { - Google.VersionHandler.FindClass( - null, "GooglePlayServices.SettingsDialog").GetProperty( - "PatchMainTemplateGradle", - BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, value, null); + PlayServicesResolver.Resolve(resolutionCompleteWithResult: completeWithResult); } } @@ -928,27 +854,47 @@ private static bool PatchMainTemplateGradle { /// Whether to delete the gradle template before /// testCaseComplete is called. /// Set of files to relative to the generatedAssetsDir. - private static void ResolveWithGradleTemplate(string gradleTemplate, - string expectedAssetsDir, - TestCase testCase, - Action testCaseComplete, - IEnumerable otherExpectedFiles = null, - bool deleteGradleTemplate = true, - ICollection filesToIgnore = null) { + /// 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 = () => { - PatchMainTemplateGradle = false; foreach (var filename in cleanUpFiles) { if (File.Exists(filename)) File.Delete(filename); } }; try { - PatchMainTemplateGradle = true; + 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, - (TestCaseResult testCaseResult) => { + (IntegrationTester.TestCaseResult testCaseResult) => { if (otherExpectedFiles != null) { foreach (var expectedFile in otherExpectedFiles) { if (!File.Exists(expectedFile)) { @@ -961,7 +907,7 @@ private static void ResolveWithGradleTemplate(string gradleTemplate, testCaseComplete(testCaseResult); }, synchronous: true); } catch (Exception ex) { - var testCaseResult = new TestCaseResult(testCase); + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); testCaseResult.ErrorMessages.Add(ex.ToString()); cleanUpTestCase(); testCaseComplete(testCaseResult); @@ -973,6 +919,7 @@ private static void ResolveWithGradleTemplate(string gradleTemplate, /// 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. @@ -1011,9 +958,7 @@ private static string ExtractZip(string zipFile, List failureMessages) { Directory.CreateDirectory(outputDir); // This uses reflection to access an internal method for testing purposes. // ExtractZip is not part of the public API. - bool successful = (bool)AndroidResolverClass.GetMethod( - "ExtractZip", BindingFlags.Static | BindingFlags.NonPublic).Invoke( - null, new object[]{ zipFile, null, outputDir, false }); + bool successful = PlayServicesResolver.ExtractZip(zipFile, null, outputDir, false); if (!successful) { failureMessages.Add(String.Format("Unable to extract {0} to {1}", zipFile, outputDir)); @@ -1105,17 +1050,18 @@ private static List CompareDirectoryContents(string expectedAssetsDir, string expectedContentsAsString = "(binary)"; string resolvedContentsAsString = expectedContentsAsString; string resolvedExtension = Path.GetExtension(resolvedFile).ToLower(); - foreach (var extension in new[] { ".xml", ".txt", ".gradle" }) { + foreach (var extension in new[] { ".xml", ".txt", ".gradle", ".properties" }) { if (resolvedExtension == extension) { displayContents = true; break; } } if (displayContents) { + // Compare ignoring leading and trailing whitespace. expectedContentsAsString = - System.Text.Encoding.Default.GetString(expectedContents); + System.Text.Encoding.Default.GetString(expectedContents).Trim(); resolvedContentsAsString = - System.Text.Encoding.Default.GetString(resolvedContents); + System.Text.Encoding.Default.GetString(resolvedContents).Trim(); differs = expectedContentsAsString != resolvedContentsAsString; } if (differs) { @@ -1156,3 +1102,5 @@ private static List ValidateAndroidResolution(string expectedAssetsDir, return failureMessages; } } + +} diff --git a/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/AndroidResolverTests.asmdef b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/AndroidResolverTests.asmdef new file mode 100644 index 00000000..1df88e66 --- /dev/null +++ b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/AndroidResolverTests.asmdef @@ -0,0 +1,25 @@ +{ + "name": "Google.AndroidResolverTests", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Google.VersionHandler.dll", + "Google.VersionHandlerImpl.dll", + "Google.JarResolver.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/source/JarResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs similarity index 100% rename from source/JarResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs diff --git a/source/JarResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs similarity index 100% rename from source/JarResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/maven-metadata.xml b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/maven-metadata.xml similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/maven-metadata.xml rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/maven-metadata.xml diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/maven-metadata.xml b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/maven-metadata.xml similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/maven-metadata.xml rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/maven-metadata.xml diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/transdep/maven-metadata.xml b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/maven-metadata.xml similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/transdep/maven-metadata.xml rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/maven-metadata.xml diff --git a/source/ExportUnityPackage/export_unity_package.py b/source/ExportUnityPackage/export_unity_package.py new file mode 100755 index 00000000..b8f16b5a --- /dev/null +++ b/source/ExportUnityPackage/export_unity_package.py @@ -0,0 +1,3559 @@ +#!/usr/bin/python +# +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +r"""A script to build Unity packages without Unity. + +This script enables plugins and assets created for Unity to be packaged into +a Unity package which can be loaded in Unity and supports the appropriate meta +data. The script takes a config file and the root directory of the assets +to be packed. + +The config supports multiple Unity package definitions, and each contains +an inclusive set of files with wildcard support grouped by platform settings. + +Example usage: + export_unity_package.py --config_file=exports.json \ + --guids_file=guids.json \ + --plugins_version="1.0.0" \ + --assets_dir="/tmp/unityBundle" + +The json file should have the following format: +{ + "packages": [ + { + # Name of the Unity package to export. + "name": "yourpackage.unitypackage", + + # Whether this package should be exported for the sections enabled. + # If this is empty the package will always be built. If this + # specifies a list of sections it will only be built if the + # enabled_sections flag contains the enabled sections in this list. + "sections": ["some_section"], + + # Files to import into the package. + "imports": [ + { + # Whether this package should be exported for the sections + # enabled. + # If this is empty the package will always be built. If this + # specifies a list of sections it will only be built if the + # enabled_sections flag contains the enabled sections in + # this list. + "sections": ["some_section"], + + # How / when to import (load) the file in Unity. + # * PluginImporter specifies the file should be imported as + # a C# DLL for the platforms specified by the "platforms" + # field. + # * DefaultImporter specifies that the file should be + # imported using Unity's default settings for the file + # type derived from the file extension. + # This field defaults to "DefaultImporter". + "importer": "PluginImporter", + + # Platforms targeted when the PluginImporter is used. + # Can be a list containing any Unity platform name e.g: + # * Any: Meta platform that targets all platforms. + # * Editor: Unity editor. + # * Standalone: Meta platform that targets all desktop + # platforms including the editor. + # * Android + # * iOS + # * tvOS + "platforms": ["Editor", "Standalone", "Android", "iOS"], + + # CPUs supported by standalone or editor platforms when the + # "PluginImporter" is the importer and platforms contains + # one of "Standalone", "LinuxUniversal", "OSXUniversal" + # or "Editor". + "cpu": "AnyCPU", # (or "x86" or "x86_64") + + # Labels to apply to the asset. These are used to find + # assets quickly in the asset database and change import + # setting via plugins like the Play Services Resolver. + "labels": [ + "gvh", + ... + ], + + # Asset metadata YAML to override the existing metadata for + # the file. This should either be a string containing YAML + # or a JSON dictionary. + # For example, the following uses a JSON dictionary to + # disable the plugin for the "Any" platform. + "override_metadata": { + "PluginImporter": { + "platformData": { + "Any": { + "enabled": 0 + } + } + }, + + # Asset metadata YAML to override the existing metadata for + # the file for Unity Package Manager package. This should + # either be a string containing YAML or a JSON dictionary. + # For example, the following uses a JSON dictionary to + # enable the plugin for the "Editor" platform for UPM + # package. + "override_metadata_upm": { + "PluginImporter": { + "platformData": { + "Editor": { + "enabled": 1 + } + } + }, + + # Files to import with the importer and label settings + # applied. + # Each item in this list can be one of the following: + # - Filename: Includes just this file. + # - Directory: Recursively includes the directory. + # - Unix shell-style wildcard (glob): Includes all files + # matching the pattern. + "paths": [ + "Firebase/Plugins/App.dll", + ... + ] + }, + ... + ], + # Transitively includes all files from the set of packages specified + # by this list. + "includes": [ "anotherpackage.unitypackage" ], + + # List of regular expression strings which exclude files included + # in this plugin. This applies to this plugin if it's exported and + # all plugins that depend upon it. + "exclude_paths": [ + "Firebase/Samples/Auth/.*", + ], + + # Whether to export this package (enabled by default). + "export": 1, + + # Path of the manifest in the package with the basename of the + # manifest file. If a path isn't specified, a manifest isn't + # generated. + # e.g + # My/Cool/ShaderToolkit + # would be expanded to... + # My/Cool/${package_name}_v${version}_manifest.txt + # + # ${package_name} is derived from the output filename and + # ${version} is specified via the command line --plugins_version + # argument. + "manifest_path": "Firebase/Editor/FirebaseAnalytics", + + # Path to the readme document. The file must be included through + # FLAGS.assets_dir, FLAGS.assets_zip or FLAG.asset_file, and is not + # required to be in "imports" section. + "readme": "path/to/a/Readme.md", + + # Path to the changelog document. The file must be included through + # FLAGS.assets_dir, FLAGS.assets_zip or FLAG.asset_file, and is not + # required to be in "imports" section. + "changelog": "path/to/a/Changelog.md", + + # Path to the license document. The file must be included through + # FLAGS.assets_dir, FLAGS.assets_zip or FLAG.asset_file, and is not + # required to be in "imports" section. + "license": "path/to/a/License.md", + + # Path to the documents. The path can be a specific file or a folder + # containing index.md. The file/folder must be included through + # FLAGS.assets_dir, FLAGS.assets_zip or FLAG.asset_file, and is not + # required to be in "imports" section. + "documentaiton": "path/to/a/Document.md", + + # Common package information used to generate package manifest. + # Required if "export_upm" is 1 + "common_manifest": { + # Package name used in the manifest file. Required if + # "export_upm" is 1. + "name": "com.google.firebase.app", + + # Display name for the package. Optional. + "display_name": "Firebase App (Core)", + + # Description for the package. Optional. + # This can be a single string or a list of strings which will be + # joined into single string for manifest. + "description": "This is core library for Firebase", + "description": [ "This is core library ", "for Firebase" ], + + # A list of keywords for the package. Potentially used for + # filtering or searching. Optional. + # Add "vh-name:legacy_manifest_name" to link this package to + # a renamed package imported as an asset package. + # Note that this script will automatically add + # "vh-name:current_package_name" to keywords. + "keywords": [ "Google", "Firebase", "vh-name:MyOldName"], + + # Author information for the package. Optional. + "author": { + "name" : "Google Inc", + "email" : "someone@google.com", + "url": "https://firebase.google.com/" + } + }, + + # Whether to export this package for Unity Package Manager, i.e. + # .tgz tarball (disabled by default) + "export_upm": 0, + + # Package configuration for Unity Package Manager package. Optional. + "upm_package_config": { + # Manifest information for package.json used by Unity Package + # Manager. Optional. + "manifest" : { + # This defines the package's minimum supported Unity version + # in the form "major.minor", for example "2019.1". The + # minimum valid version here is "2017.1". Optional. + "unity": "2017.1", + + # A map containing this package's additional dependencies + # where the keys are package names and the values are + # specific versions, e.g. "1.2.3". This script will also + # automatically includes packages listed in "includes", if + # it is set to export for UPM. + "dependencies": { + "com.some.third-party-package": "1.2.3" + } + } + }, + }, + ... + ], + + # Optional build configurations for the project. + # All packages in the project are exported for each build configuration + # listed in this section. + "builds": [ + { + # Name of this build config for logging purposes. + "name": "debug", + + # Whether this build config should be executed for the sections enabled. + # If this is empty, it will always be executed. + "sections": ["debug"], + + # Sections that should be enabled when exporting packages with this + # build config. This set of sections are added to the sections + # specified on the command line before packages are exported. + "enabled_sections": ["early_access"], + + # List of regular expressions and replacement strings applied to + # package names before they're exported. + # For example: + # { "match": "foo(.*)\\.bar", "replacement": "foo\\1Other.bar" } + # Changes the package name "foo123.bar" to "foo123Other.bar". + "package_name_replacements": [ + { + "match": "(.*)(\\.unitypackage)", + "replacement": "\\1EarlyAccess\\2" + }, + ] + }, + ... + ] +} +""" + +import collections +import copy +import glob +import gzip +import json +import os +import platform +import re +import shutil +import stat +import subprocess +import sys +import tarfile +import tempfile +import traceback +import zipfile +from absl import app +from absl import flags +from absl import logging +import packaging.version +import yaml + +FLAGS = flags.FLAGS + +flags.DEFINE_string("config_file", None, ("Config file that describes how to " + "pack the unity assets.")) +flags.DEFINE_string("guids_file", None, "Json file with stable guids cache.") +flags.DEFINE_string("plugins_version", None, "Version of the plugins to " + "package.") +flags.DEFINE_boolean("use_tar", True, "Whether to use the tar command line " + "application, when available, to generate archives rather " + "than Python's tarfile module. NOTE: On macOS tar / gzip " + "generate Unity compatible but non-reproducible archives.") +flags.DEFINE_boolean( + "enforce_semver", True, "Whether to enforce semver (major.minor.patch) for" + "plugins_version. This is required to build UPM package.") +flags.DEFINE_multi_string("assets_dir", ".", "Directory containing assets to " + "package.") +flags.DEFINE_multi_string("assets_zip", None, "Zip files containing assets to " + "package.") +flags.DEFINE_multi_string("asset_file", None, + "File to copy in a directory to search for assets. " + "This is in the format " + "'input_filename:asset_filename' where " + "input_filename if the path to the file to copy and " + "asset_filename is the path to copy to in directory " + "to stage assets.") +flags.DEFINE_integer("timestamp", 1480838400, # 2016-12-04 + "Timestamp to use for each file. " + "Set to 0 to use the current time.") +flags.DEFINE_string("owner", "root", + "Username of file owner in each generated package.") +flags.DEFINE_string("group", "root", + "Username of file group in each generated package.") +flags.DEFINE_string("output_dir", "output", + "Directory to write the resulting Unity package files.") +flags.DEFINE_string("output_zip", None, "Zip file to archive the output Unity " + "packages.") +flags.DEFINE_boolean( + "output_upm", False, "Whether output packages as tgz for" + "Unity Package Manager.") +flags.DEFINE_boolean("output_unitypackage", True, "Whether output packages as " + "asset packages.") +flags.DEFINE_multi_string("additional_file", None, + "Additional file in the format " + "'input_filename:output_filename', which copies the " + "specified input_filename to output_filename under " + "the output_dir. This can be used to store " + "additional files in the output directory or zip " + "file. If the ':output_filename' portion of the " + "argument isn't specified, the file will be written " + "to the same path as the specified input_filename " + "under the output_dir.") +flags.DEFINE_spaceseplist( + "enabled_sections", None, + ("List of sections to include in the set of packages. " + "Package specifications that do not specify any sections are always " + "included.")) + +# Default metadata for all Unity 5.3+ assets. +DEFAULT_METADATA_TEMPLATE = collections.OrderedDict( + [("fileFormatVersion", 2), + ("guid", None), # A unique GUID *must* be specified for all assets. + ("labels", None), # Can optionally specific a list of asset label strings. + ("timeCreated", 0)]) + +# A minimal set of Importer meta data. +# +# This importer is used if nothing more specific is needed and Unity can often +# infer the correct meta data using this by directory structure. This method is +# used if the json import group's "importer" field is "DefaultImporter". +DEFAULT_IMPORTER_DATA = [("userData", None), + ("assetBundleName", None), + ("assetBundleVariant", None)] +DEFAULT_IMPORTER_METADATA_TEMPLATE = collections.OrderedDict( + [("DefaultImporter", collections.OrderedDict(DEFAULT_IMPORTER_DATA))]) + +DEFAULT_FOLDER_METADATA_TEMPLATE = collections.OrderedDict([ + ("folderAsset", True), + ("DefaultImporter", collections.OrderedDict(DEFAULT_IMPORTER_DATA)) +]) + +PLATFORM_SETTINGS_DISABLED = [("enabled", 0)] +DEFAULT_PLATFORM_SETTINGS_EMPTY_DISABLED = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", {})]) + +DEFAULT_PLATFORM_SETTINGS_DISABLED = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]) + +DEFAULT_PLATFORM_SETTINGS_EDITOR = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CPU", "AnyCPU"), + ("DefaultValueInitialized", True), + ("OS", "AnyOS")]))]) + +# When desktop platforms are disabled Unity expects the CPU to be set to None. +DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CPU", "None")]))]) + +DEFAULT_PLATFORM_SETTINGS_DISABLED_IOS = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CompileFlags", None), + ("FrameworkDependencies", None)]))]) + +DEFAULT_PLATFORM_SETTINGS_DISABLED_TVOS = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CompileFlags", None), + ("FrameworkDependencies", None)]))]) + +PLUGIN_IMPORTER_METADATA_TEMPLATE = collections.OrderedDict( + [("PluginImporter", collections.OrderedDict( + [("serializedVersion", 1), + ("iconMap", {}), + ("executionOrder", {}), + ("isPreloaded", 0), + ("platformData", collections.OrderedDict( + [("Android", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED)), + ("Any", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_EMPTY_DISABLED)), + ("Editor", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_EDITOR)), + ("Linux", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("Linux64", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("LinuxUniversal", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("OSXIntel", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("OSXIntel64", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("OSXUniversal", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("Web", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_EMPTY_DISABLED)), + ("WebStreamed", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_EMPTY_DISABLED)), + ("Win", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("Win64", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("WindowsStoreApps", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED)), + ("iOS", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_IOS)), + ("tvOS", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_TVOS)), + ])) + ] + DEFAULT_IMPORTER_DATA)) + ]) + +# Map of platforms to targets. +# Unity 5.6+ metadata requires a tuple of (target, name) for each platform. +# This ignores targets like "Facebook" which overlap with more common targets +# like "Standalone". +PLATFORM_TARGET_BY_PLATFORM = { + "Any": "Any", + "Editor": "Editor", + "Android": "Android", + "Linux": "Standalone", + "Linux64": "Standalone", + "LinuxUniversal": "Standalone", + "OSXIntel": "Standalone", + "OSXIntel64": "Standalone", + "OSXUniversal": "Standalone", + "Web": "WebGL", + "WebStreamed": "", + "Win": "Standalone", + "Win64": "Standalone", + "WindowsStoreApps": "Windows Store Apps", + "iOS": "iPhone", + "tvOS": "tvOS", +} + +# Alias for standalone platforms specified by the keys of +# CPU_BY_DESKTOP_PLATFORM. +STANDALONE_PLATFORM_ALIAS = "Standalone" +# Maps architecture specific platform selections to "universal" +# platforms. Universal in Unity doesn't really mean that it can target any +# architecture, instead it is master flag that controls whether the asset is +# enabled for export. +ARCH_SPECIFIC_TO_UNIVERSAL_PLATFORM = { + "Linux": "LinuxUniversal", + "Linux64": "LinuxUniversal", + "OSXIntel": "OSXUniversal", + "OSXIntel64": "OSXUniversal", +} + +# Set of supported platforms for each shared library extension. +PLATFORMS_BY_SHARED_LIBRARY_EXTENSION = { + ".so": set(["Any", "Editor", "Linux", "Linux64", "LinuxUniversal"]), + ".bundle": set(["Any", "Editor", "OSXIntel", "OSXIntel64", "OSXUniversal"]), + ".dll": set(["Any", "Editor", "Win", "Win64"]) +} + +# Desktop platform to CPU mapping. +CPU_BY_DESKTOP_PLATFORM = { + "Linux": "x86", + "OSXIntel": "x86", + "Win": "x86", + "Linux64": "x86_64", + "OSXIntel64": "x86_64", + "Win64": "x86_64", + "LinuxUniversal": "AnyCPU", + "OSXUniversal": "AnyCPU", +} +# CPU to desktop platform mapping. +DESKTOP_PLATFORMS_BY_CPU = { + "x86": [p for p, c in CPU_BY_DESKTOP_PLATFORM.items() if c == "x86"], + "x86_64": [p for p, c in CPU_BY_DESKTOP_PLATFORM.items() if c == "x86_64"], + "AnyCPU": CPU_BY_DESKTOP_PLATFORM.keys(), +} + +# Unity 5.6 and beyond modified the PluginImporter format such that platforms +# are enabled using a list of dictionaries with the keys "first" and "second" +# controlling platform settings. This constant matches the keys in entries of +# the PluginImporter.platformData list. +UNITY_5_6_PLATFORM_DATA_KEYS = ["first", "second"] + +# Prefix for labels that are applied to files managed by the VersionHandler +# module. +VERSION_HANDLER_LABEL_PREFIX = "gvh" +# Prefix for version numbers in VersionHandler filenames and labels. +VERSION_HANDLER_VERSION_FIELD_PREFIX = "version-" +VERSION_HANDLER_MANIFEST_FIELD_PREFIX = "manifest" +# Separator for filenames and fields parsed by the VersionHandler. +VERSION_HANDLER_FIELD_SEPARATOR = "_" +# Prefix for labels that are applied to files managed by the Unity Package +# Manager module. +UPM_RESOLVER_LABEL_PREFIX = "gupmr" +UPM_RESOLVER_MANIFEST_FIELD_PREFIX = "manifest" +# Separator for filenames and fields parsed by the UPM Resolver. +UPM_RESOLVER_FIELD_SEPARATOR = "_" +# Prefix for latest labels that are applied to files managed by the +# VersionHandler module. +VERSION_HANDLER_PRESERVE_LABEL_PREFIX = "gvhp" +VERSION_HANDLER_PRESERVE_MANIFEST_NAME_FIELD_PREFIX = "manifestname-" +VERSION_HANDLER_PRESERVE_EXPORT_PATH_FIELD_PREFIX = "exportpath-" +VERSION_HANDLER_MANIFEST_TYPE_LEGACY = 0 +VERSION_HANDLER_MANIFEST_TYPE_UPM = 1 +# Prefix for canonical Linux library names. +VERSION_HANDLER_LINUXLIBNAME_FIELD_PREFIX = "linuxlibname-" +# Canonical prefix of Linux shared libraries. +LINUX_SHARED_LIBRARY_PREFIX = "lib" +# Extension used by Linux shared libraries. +LINUX_SHARED_LIBRARY_EXTENSION = ".so" +# Path relative to the "Assets" dir of native Linux plugin libraries. +LINUX_SHARED_LIBRARY_PATH = re.compile( + r"^Plugins/(x86|x86_64)/(.*{ext})".format( + ext=LINUX_SHARED_LIBRARY_EXTENSION.replace(".", r"\."))) +# Path components required for native desktop libraries. +SHARED_LIBRARY_PATH = re.compile( + r"(^|/)Plugins/(x86|x86_64)/(.*/|)[^/]+\.(so|dll|bundle)$") +# Prefix of the keywords to be added to UPM manifest to link to legacy manifest. +UPM_KEYWORDS_MANIFEST_PREFIX = "vh-name:" +# Everything in a Unity plugin - at the moment - lives under the Assets +# directory +ASSETS_DIRECTORY = "Assets" +# Extension for asset metadata files. +ASSET_METADATA_FILE_EXTENSION = ".meta" +# Valid version for asset package in form of major.minor.patch(-preview) +VALID_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(-preview)?$") + +# Documentation folder and filename for UPM package. +UPM_DOCUMENTATION_DIRECTORY = "Documentation~" +UPM_DOCUMENTATION_FILENAME = "index.md" + +# String and unicode classes used to check types with safe_dict_get_value() +try: + unicode("") # See whether unicode class is available (Python < 3) + STR_OR_UNICODE = [str, unicode] +except NameError: + STR_OR_UNICODE = [str] + unicode = str # pylint: disable=redefined-builtin,invalid-name + +def posix_path(path): + """Convert path separators to POSIX style. + + Args: + path: Path to convert. + + Returns: + Path with POSIX separators, i.e / rather than \\. + """ + return path.replace('\\', '/') + + +class MissingGuidsError(Exception): + """Raised when GUIDs are missing for input files in export_package(). + + Attributes: + missing_guid_paths: List of files missing GUIDs. + """ + + def __init__(self, missing_guid_paths): + """Initialize the instance. + + Args: + missing_guid_paths: List of files missing GUIDs. + """ + self.missing_guid_paths = sorted(list(set(missing_guid_paths))) + super(MissingGuidsError, self).__init__(self.__str__()) + + def __str__(self): + """Retrieves a description of this error.""" + guids_file = FLAGS.guids_file if FLAGS.guids_file else "" + plugins_version = FLAGS.plugins_version if FLAGS.plugins_version else "" + return (("There were asset paths without a known guid. " + "generate guids for these assets:\n\n" + "{gen_guids} " + "--guids_file=\"{guids_file}\" " + "--version=\"{plugins_version}\" \"").format( + gen_guids=os.path.realpath( + os.path.join(os.path.dirname(__file__), "gen_guids.py")), + guids_file=guids_file, plugins_version=plugins_version) + + "\" \"".join(self.missing_guid_paths) + "\"") + + +class DuplicateGuidsError(Exception): + """Raised when GUIDs are duplicated for multiple export paths. + + Attributes: + paths_by_guid: GUIDs that have multiple paths associated with them. + """ + + def __init__(self, paths_by_guid): + self.paths_by_guid = paths_by_guid + super(DuplicateGuidsError, self).__init__(self.__str__()) + + def __str__(self): + """Retrieves a description of this error.""" + return ("Found duplicate GUIDs that map to multiple paths.\n%s" % + "\n".join(["%s --> %s" % (guid, str(sorted(paths))) + for guid, paths in self.paths_by_guid.items()])) + + +class DuplicateGuidsChecker(object): + """Ensures no duplicate GUIDs are present in the project. + + Attributes: + _paths_by_guid: Set of file paths by GUID. + """ + + def __init__(self): + """Initialize this instance.""" + self._paths_by_guid = collections.defaultdict(set) + + def add_guid_and_path(self, guid, path): + """Associate an export path with a GUID. + + Args: + guid: GUID to add to this instance. + path: Path associated with this GUID. + """ + self._paths_by_guid[guid].add(posix_path(path)) + + def check_for_duplicates(self): + """Check the set of GUIDs for duplicate paths. + + Raises: + DuplicateGuidsError: If multiple paths are found for the same GUID. + """ + conflicting_paths_by_guid = dict( + [(guid, paths) + for guid, paths in self._paths_by_guid.items() if len(paths) > 1]) + if conflicting_paths_by_guid: + raise DuplicateGuidsError(conflicting_paths_by_guid) + + +class YamlSerializer(object): + """Loads and saves YAML files preserving the order of elements.""" + + class OrderedLoader(yaml.Loader): + """Overrides the default YAML loader to construct nodes as OrderedDict.""" + _initialized = False + + @classmethod + def initialize(cls): + """Installs the construct_mapping constructor on the Loader.""" + if not cls._initialized: + cls.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + cls._construct_mapping) + cls._initialized = True + + @staticmethod + def _construct_mapping(loader, node): + """Constructs an OrderedDict from a YAML node. + + Args: + loader: yaml.Loader loading the file. + node: Node being mapped to a python data structure. + + Returns: + OrderedDict for the YAML node. + """ + loader.flatten_mapping(node) + return collections.OrderedDict(loader.construct_pairs(node)) + + class OrderedDumper(yaml.Dumper): + """Overrides the default YAML serializer. + + By default maps items to the OrderedDict structure, None to an empty + strings and disables aliases. + """ + _initialized = False + + @classmethod + def initialize(cls): + """Installs the representers on this class.""" + if not cls._initialized: + # By default map data structures to OrderedDict. + cls.add_representer(collections.OrderedDict, cls._represent_map) + # By default map None to empty strings. + cls.add_representer(type(None), cls._represent_none) + # By default map unicode to strings. + cls.add_representer(unicode, cls._represent_unicode) + cls._initialized = True + + @staticmethod + def _represent_unicode(dumper, data): + """Strip the unicode tag from a yaml dump. + + Args: + dumper: Generates the mapping. + data: Data to generate the representer for. + + Returns: + String mapping for unicode data. + """ + return dumper.represent_scalar(u"tag:yaml.org,2002:str", data) + + @staticmethod + def _represent_map(dumper, data): + """Return a default representer for a map. + + Args: + dumper: Generates the mapping. + data: Data to generate a representor for. + + Returns: + The default mapping for a map. + """ + return dumper.represent_mapping( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items()) + + @staticmethod + def _represent_none(dumper, unused_data): + """Return a representer for None that emits an empty string. + + Args: + dumper: Generates the mapping. + unused_data: Unused. + + Returns: + A mapping that returns an empty string for None entries. + """ + return dumper.represent_scalar(u"tag:yaml.org,2002:null", "") + + def ignore_aliases(self, unused_data): + """Disable data structure aliases. + + Returns: + True always. + """ + return True + + def __init__(self, *unused_argv): + """Create the serializer.""" + YamlSerializer.OrderedLoader.initialize() + YamlSerializer.OrderedDumper.initialize() + + def load(self, yaml_string): + """Load yaml from a string into this class. + + Args: + yaml_string: String to load YAML from. + + Returns: + OrderedDict loaded from YAML. + """ + return yaml.load(yaml_string, Loader=YamlSerializer.OrderedLoader) + + def dump(self, data): + """Generate a YAML string from the data in this class. + + Args: + data: Set of Python data structures to dump to YAML. + + Returns: + YAML string representation of this class. + """ + return yaml.dump(data, Dumper=YamlSerializer.OrderedDumper, + default_flow_style=False) + + +def merge_ordered_dicts(merge_into, merge_from): + """Merge ordered dicts. + + Merge nodes of merge_from into merge_into. + + - If a node exists in merge_into and merge_from and they're both a dictionary, + merge them together. + - If a node exists in merge_into and merge_from and both values are lists of + dictionaries where each dictionary contains the keys "first" and "second", + merge the lists using the value of "first" in each dictionary as the merge + key. This allows modification of the platform targeting data structure in + Unity asset metadata. + + In all other cases, replace the node in merge_info with the value from + merge_from. + + Args: + merge_into: OrderedDict instance to merge values into. + merge_from: OrderedDict instance to merge values from. + + Returns: + Value of merge_into. + """ + + def list_contains_dictionaries_with_keys(list_to_query, expected_keys): + """Check a list for dictionaries with exactly the specified keys. + + Args: + list_to_query: List to query. + expected_keys: Keys to search for in each dictionary in the list. + + Returns: + True if the list contains dictionaries with exactly the specified + keys, False otherwise. + """ + list_matches = False + if issubclass(list_to_query.__class__, list): + list_matches = list_to_query and True + for item in list_to_query: + if not (issubclass(item.__class__, dict) and + sorted(item.keys()) == expected_keys): + list_matches = False + break + return list_matches + + if (issubclass(merge_from.__class__, dict) and + issubclass(merge_into.__class__, dict)): + for merge_from_key, merge_from_value in merge_from.items(): + merge_into_value = merge_into.get(merge_from_key) + if merge_into_value is not None: + if (issubclass(merge_into_value.__class__, dict) and + issubclass(merge_from_value.__class__, dict)): + merge_ordered_dicts(merge_into_value, merge_from_value) + continue + if (list_contains_dictionaries_with_keys( + merge_into_value, UNITY_5_6_PLATFORM_DATA_KEYS) and + list_contains_dictionaries_with_keys( + merge_from_value, UNITY_5_6_PLATFORM_DATA_KEYS)): + for merge_from_list_item in merge_from_value: + # Try finding the dictionary to merge based upon the hash of the + # "first" item value. + merged = None + key = str(merge_from_list_item["first"]) + for merge_into_list_item in merge_into_value: + if str(merge_into_list_item["first"]) == key: + merge_ordered_dicts(merge_into_list_item, merge_from_list_item) + merged = merge_into_list_item + break + # If the dictionary wasn't merged, add it to the list. + if not merged: + merge_into_value.append(merge_from_list_item) + continue + merge_into[merge_from_key] = merge_from_value + return merge_into + + +def safe_dict_get_value(tree_node, key, default_value=None, value_classes=None): + """Safely retrieve a value from a node in a tree read from JSON or YAML. + + The JSON and YAML parsers returns nodes that can be container or non-container + types. This method internally checks the node to make sure it's a dictionary + before querying for the specified key. If a default value or value_class are + specified, this method also ensures the returned value is derived from the + same type as default_value or value_type. + + Args: + tree_node: Node to query. + key: Key to retrieve from the node. + default_value: Default value if the key doesn't exist in the node or the + node isn't derived from dictionary. + value_classes: List of expected classes of the key value. If the returned + type does not match one of these classes the default value is returned. + If this is not specified, the class of the default_value is used instead. + + Returns: + Value corresponding to key in the tree_node or default_value if the key does + not exist or doesn't match the expected class. + """ + if not issubclass(tree_node.__class__, dict): + return default_value + + value = tree_node.get(key) + if value is None: + value = default_value + elif default_value is not None or value_classes: + if not value_classes: + value_classes = [default_value.__class__] + if default_value.__class__ == str: + value_classes.append(unicode) + + matches_class = False + for value_class in value_classes: + if issubclass(value.__class__, value_class): + matches_class = True + break + + if not matches_class: + logging.warning("Expected class %s instead of class %s while reading key " + "%s from %s. Will use value %s instead of %s.\n%s", + value_classes, value.__class__, key, tree_node, + default_value, value, "".join(traceback.format_stack())) + value = default_value + return value + + +def safe_dict_set_value(tree_node, key, value): + """Safely set a value to a node in a tree read from JSON or YAML. + + The JSON and YAML parsers returns nodes that can be container or non-container + types. This method internally checks the node to make sure it's a dictionary + before setting for the specified key. If value is None, try to remove key + from tree_node. + + Args: + tree_node: Node to set. + key: Key of the entry to be added to the node. + value: Value of the entry to be added to the node. If None, try to remove + the entry from tree_node. + + Returns: + Return tree_node + """ + if not issubclass(tree_node.__class__, dict): + return tree_node + + if value is None: + if key in tree_node: + del tree_node[key] + else: + tree_node[key] = value + + return tree_node + + +class GuidDatabase(object): + """Reads GUIDs from .meta files and a GUID cache. + + Attributes: + _guids_by_path: Cache of GUIDs by path. + _duplicate_guids_checker: Instance of DuplicateGuidsChecker to ensure no + duplicate GUIDs are present. + """ + + def __init__(self, duplicate_guids_checker, guids_json, + plugin_version): + """Initialize the database with data from the GUIDs database. + + Args: + duplicate_guids_checker: Instance of DuplicateGuidsChecker. + guids_json: JSON dictionary that contains the GUIDs to search for. + See firebase/app/client/unity/gen_guids.py for the format. + This can be None to not initialize the database. + plugin_version: Version to use for GUID selection in the specified JSON. + """ + self._guids_by_path = {} + self._duplicate_guids_checker = duplicate_guids_checker + + if guids_json: + guid_map = safe_dict_get_value(guids_json, plugin_version, + default_value={}) + for filename, guid in guid_map.items(): + self.add_guid(posix_path(filename), guid) + + if plugin_version: + # Aggregate guids for older versions of files. + current_version = packaging.version.Version(plugin_version) + for version in sorted(guids_json, key=packaging.version.Version, + reverse=True): + # Skip all versions after and including the current version. + if packaging.version.Version(version) >= current_version: + continue + # Add all guids for files to the current version. + guids_by_filename = guids_json[version] + for filename in guids_by_filename: + if filename not in guid_map: + self.add_guid(filename, guids_by_filename[filename]) + + def add_guid(self, path, guid): + """Add a GUID for the specified path to the guid_map and GUID checker. + + Args: + path: Path associated with the GUID. + guid: GUID for the asset at the path. + """ + path = posix_path(path) + self._guids_by_path[path] = guid + self._duplicate_guids_checker.add_guid_and_path(guid, path) + + def read_guids_from_assets(self, assets): + """Read GUIDs from a set of metadata files into the database. + + Args: + assets: List of Asset instances to read GUIDs from. + + Raises: + MissingGuidsError: If GUIDs are missing for any of the specified assets. + DuplicateGuidsError: If any of the read GUIDs are duplicates. + """ + missing_guid_paths = [] + for asset in assets: + existing_guid = None + metadata_guid = safe_dict_get_value(asset.importer_metadata, "guid", + value_classes=STR_OR_UNICODE) + try: + existing_guid = self.get_guid(asset.filename_guid_lookup) + except MissingGuidsError: + pass + guid = metadata_guid if metadata_guid else existing_guid + if guid: + self.add_guid(asset.filename_guid_lookup, guid) + else: + missing_guid_paths.append(asset.filename_guid_lookup) + if missing_guid_paths: + raise MissingGuidsError(missing_guid_paths) + self._duplicate_guids_checker.check_for_duplicates() + + def get_guid(self, path): + """Get a GUID for the specified path. + + Args: + path: Asset export path to retrieve the GUID of. + + Returns: + GUID of the asset. + + Raises: + MissingGuidsError: If the GUID isn't found. + """ + path = posix_path(path) + guid = self._guids_by_path.get(path) + if not guid: + raise MissingGuidsError([path]) + return guid + + +def copy_and_set_rwx(source_path, target_path): + """Copy a file/folder and set the target to readable / writeable & executable. + + Args: + source_path: File to copy from. + target_path: Path to copy to. + """ + logging.debug("Copying %s --> %s", source_path, target_path) + file_mode = stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH + + if os.path.isfile(source_path): + target_dir = os.path.dirname(target_path) + if not os.path.exists(target_dir): + os.makedirs(target_dir) + shutil.copy(source_path, target_path) + os.chmod(target_path, file_mode) + elif os.path.isdir(source_path): + shutil.copytree(source_path, target_path) + os.chmod(target_path, file_mode) + for current_dir, directories, filenames in os.walk(target_path): + for directory in [os.path.join(current_dir, d) for d in directories]: + os.chmod(directory, file_mode) + for filename in [os.path.join(current_dir, f) for f in filenames]: + os.chmod(filename, file_mode) + +def version_handler_tag(islabel=True, field=None, value=None): + """Generate a VersionHandler filename or label. + + For more information, see + third_party/unity/unity_jar_resolver/source/VersionHandler + + Args: + islabel: Whether the generated field is a label. If this is false this + simply returns the field and value. + field: Type of the field. + value: Value of the field. + + Returns: + Label string. + """ + label_components = [VERSION_HANDLER_LABEL_PREFIX] if islabel else [] + if field: + if value: + label_components.append( + field + value.replace(VERSION_HANDLER_FIELD_SEPARATOR, "-")) + else: + label_components.append(field) + return VERSION_HANDLER_FIELD_SEPARATOR.join(label_components) + + +def version_handler_filename(filename, field_value_list): + """Generate a VersionHandler filename. + + For more information, see + third_party/unity/unity_jar_resolver/source/VersionHandler + + Args: + filename: Base filename VersionHandler fields are injected into. + field_value_list: List of (field, value) tuples added to the end of the + filename. + + Returns: + Filename for a file managed by the VersionHandler. + """ + filename_prefix, extension = os.path.splitext(filename) + directory, basename = os.path.split(filename_prefix) + components = [os.path.join(directory, + basename.replace(VERSION_HANDLER_FIELD_SEPARATOR, + "-"))] + fields = [] + for field, value in field_value_list: + fields.append(version_handler_tag(islabel=False, field=field, value=value)) + if fields: + components.extend([VERSION_HANDLER_FIELD_SEPARATOR, + VERSION_HANDLER_FIELD_SEPARATOR.join(fields)]) + components.append(extension) + return posix_path("".join(components)) + + +class Asset(object): + """Asset to export. + + Attributes: + _filename: Relative path of the file referenced by this asset. + _filename_guid_lookup: Filename to reference GUID. + _filename_absolute: Absolute path of the file referenced by this asset. + _importer_metadata: OrderedDict of Unity asset metadata used to construct + this class. + _is_folder: Whether this asser is for a folder. + """ + + def __init__(self, + filename, + filename_absolute, + importer_metadata, + filename_guid_lookup=None, + is_folder=False): + """Initialize an asset. + + Args: + filename: Name of the file referenced by this asset. + filename_absolute: Absolute path of the file referenced by this + asset. If this is None, the filename argument is used instead. + importer_metadata: OrderedDict of Unity importer metadata for the file. + filename_guid_lookup: Filename to reference GUID. If this is None, the + filename argument is used instead. + is_folder: Whether this asser is for a folder. + """ + self._filename = filename + self._filename_guid_lookup = filename_guid_lookup or filename + self._filename_absolute = filename_absolute or filename + self._importer_metadata = importer_metadata + self._is_folder = is_folder + + def __eq__(self, other): + """Overrides == operator.""" + if isinstance(other, Asset): + return (self.filename == other.filename and + self.filename_guid_lookup == other.filename_guid_lookup and + self.filename_absolute == other.filename_absolute and + self.importer_metadata == other.importer_metadata and + self.is_folder == other.is_folder) + return False + + def __ne__(self, other): + """Overrides != operator (Required in Python2).""" + return not self.__eq__(other) + + @property + def filename(self): + """Get the name of the file referenced by this asset. + + Returns: + Filename string. + """ + return posix_path(self._filename) + + @property + def filename_absolute(self): + """Get the absolute path of the file referenced by this asset. + + Returns: + Filename string. + """ + return posix_path(self._filename_absolute) + + @property + def filename_guid_lookup(self): + """Get the filename to reference GUID. + + Returns: + Filename string. + """ + return posix_path(self._filename_guid_lookup) + + @property + def is_folder(self): + """Get whether this asset is for a folder. + + Returns: + Boolean whether this asset is for a folder + """ + return self._is_folder + + def __repr__(self): + """Returns a human readable string. + + Returns: + A human readable representation of this object. + """ + return "" % (self.filename, + self.importer_metadata) + + @staticmethod + def add_labels_to_metadata(importer_metadata, labels): + """Add to the labels field of Unity asset metadata OrderedDict. + + Args: + importer_metadata: OrderedDict to modify. + labels: Set of labels to add to asset_metadata. + + Returns: + Modified importer_metadata. + """ + existing_labels = safe_dict_get_value(importer_metadata, "labels", + value_classes=[list]) + new_labels = set(existing_labels or []).union(labels) + if new_labels: + importer_metadata["labels"] = sorted(new_labels) + elif existing_labels is not None: + del importer_metadata["labels"] + return importer_metadata + + @staticmethod + def disable_unsupported_platforms(importer_metadata, filename): + """Disable all platforms that are not supported by a shared library asset. + + If the asset references a shared library, disable all platforms that are + not supported by the asset based upon the asset's filename extension. + + Args: + importer_metadata: Metadata to modify. This is modified in-place. + filename: Name of the asset file. + + Returns: + Modified importer_metadata. + """ + filename = posix_path(os.path.normpath(filename)) + is_shared_library = SHARED_LIBRARY_PATH.search(filename) + if not is_shared_library: + return importer_metadata + + supported_platforms = PLATFORMS_BY_SHARED_LIBRARY_EXTENSION.get( + os.path.splitext(filename)[1]) + if not supported_platforms: + return importer_metadata + + plugin_importer = safe_dict_get_value(importer_metadata, "PluginImporter", + default_value={}) + serialized_version = safe_dict_get_value( + plugin_importer, "serializedVersion", default_value=1) + if serialized_version != 1: + logging.warning("Unsupported platformData version %d. " + "Unable to configure platforms for shared library " + "%s", serialized_version, filename) + return importer_metadata + + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value={}) + disable_platforms = sorted(set(platform_data).difference( + set(supported_platforms))) + # Disable the Any platform if any platforms are disabled. + if disable_platforms: + any_config = platform_data.get("Any", collections.OrderedDict()) + any_config["enabled"] = 0 + platform_data["Any"] = any_config + # Disable all platforms in the set. + for current_platform in disable_platforms: + platform_data[current_platform] = copy.deepcopy( + PLUGIN_IMPORTER_METADATA_TEMPLATE[ + "PluginImporter"]["platformData"][current_platform]) + logging.debug("Disabled platforms %s for %s", disable_platforms, filename) + return importer_metadata + + @staticmethod + def platform_data_get_entry(platform_data_item): + """Retrieve a platform entry from an item in the platformData list. + + The PluginImporter.platformData is a list when + PluginImporter.serializedVersion is 2. This list can either contain a + dictionary of platform information dictionaries indexed by "first" / + "second" (Unity 2017+) or a list of dictionaries containing just a "data" + entry (Unity 5.6) which in turn contain a platform information entry with + "first" / "second". This method retrieves the values of the + "first" / "second" tuples from an entry in the platformData list. + + Args: + platform_data_item: Entry in the platformData list. + + Returns: + Tuple of values retrieved from the "first" and "second" entries of the + platform information dictionary. + """ + entry = platform_data_item.get("data", platform_data_item) + return (safe_dict_get_value(entry, "first", + default_value=collections.OrderedDict()), + safe_dict_get_value(entry, "second", + default_value=collections.OrderedDict())) + + @staticmethod + def set_cpu_for_desktop_platforms(importer_metadata): + """Enable CPU(s) for each enabled desktop platform in the metadata. + + Args: + importer_metadata: Metadata to modify. + + Returns: + Modified importer_metadata. + """ + plugin_importer = safe_dict_get_value( + importer_metadata, "PluginImporter", default_value={}) + serialized_version = safe_dict_get_value( + plugin_importer, "serializedVersion", default_value=1) + + if serialized_version == 1: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value={}) + for platform_name, options in platform_data.items(): + if not safe_dict_get_value(options, "enabled", default_value=0): + continue + # Override the CPU of the appropriate platforms. + cpu = CPU_BY_DESKTOP_PLATFORM.get(platform_name) + if not cpu: + continue + settings = options.get("settings", collections.OrderedDict()) + if settings.get("CPU", "None") == "None": + settings["CPU"] = cpu + options["settings"] = settings + else: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value=[]) + for entry in platform_data: + # Parse the platform name tuple from the "first" dictionary. + first, second = Asset.platform_data_get_entry(entry) + platform_tuple = list(first.items())[0] + if len(platform_tuple) < 2: + continue + unused_platform_target, platform_name = platform_tuple + if not second.get("enabled", 0): + continue + # Override the CPU of the appropriate platforms. + cpu = CPU_BY_DESKTOP_PLATFORM.get(platform_name) + if not cpu: + continue + settings = safe_dict_get_value(second, "settings", + default_value=collections.OrderedDict()) + if settings.get("CPU", "None") == "None": + settings["CPU"] = cpu + second["settings"] = settings + return importer_metadata + + @staticmethod + def set_cpu_for_android(importer_metadata, cpu_string): + """Sets the CPU for Android in the metadata if enabled. + + Args: + importer_metadata: Metadata to modify. + cpu_string: The desired CPU string value. + + Returns: + Modified importer_metadata. + """ + plugin_importer = safe_dict_get_value( + importer_metadata, "PluginImporter", default_value={}) + serialized_version = safe_dict_get_value( + plugin_importer, "serializedVersion", default_value=1) + + if serialized_version == 1: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value={}) + for platform_name, options in platform_data.items(): + if not safe_dict_get_value(options, "enabled", default_value=0): + continue + if not cpu_string: + continue + if platform_name == "Android": + settings = options.get("settings", collections.OrderedDict()) + settings["CPU"] = cpu_string + options["settings"] = settings + else: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value=[]) + for entry in platform_data: + # Parse the platform name tuple from the "first" dictionary. + first, second = Asset.platform_data_get_entry(entry) + platform_tuple = list(first.items())[0] + if len(platform_tuple) < 2: + continue + unused_platform_target, platform_name = platform_tuple + if not second.get("enabled", 0): + continue + if not cpu_string: + continue + settings = safe_dict_get_value(second, "settings", + default_value=collections.OrderedDict()) + if platform_name == "Android": + settings["CPU"] = cpu_string + second["settings"] = settings + return importer_metadata + + @staticmethod + def apply_any_platform_selection(importer_metadata): + """Enable / disable all platforms if the "Any" platform is enabled. + + Args: + importer_metadata: Metadata to modify. This is modified in-place. + + Returns: + Modified importer_metadata. + """ + plugin_importer = safe_dict_get_value( + importer_metadata, "PluginImporter", default_value={}) + serialized_version = safe_dict_get_value( + plugin_importer, "serializedVersion", default_value=1) + + if serialized_version == 1: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value={}) + # Check PluginImporter.platformData.Any.enabled for the Any platform + # enabled in Unity 4 & early 5 metadata. + any_enabled = platform_data.get("Any", {}).get("enabled", 0) + if not any_enabled: + return importer_metadata + # If the Any platform is present and either enabled or disabled, enable + # for disable all platforms. + for platform_name, default_config in PLUGIN_IMPORTER_METADATA_TEMPLATE[ + "PluginImporter"]["platformData"].items(): + config = platform_data.get(platform_name) + if config is None: + config = copy.deepcopy(default_config) + config["enabled"] = any_enabled + platform_data[platform_name] = config + else: + # Search for the Any platform and retrieve if it's present and + # enabled / disabled if Unity 5.4+ metadata. + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value=[]) + any_enabled = 0 + for entry in platform_data: + first, second = Asset.platform_data_get_entry(entry) + if "Any" in first: + any_enabled = second.get("enabled", 0) + break + if not any_enabled: + return importer_metadata + remaining_platforms = [platform_name for platform_name in ( + PLUGIN_IMPORTER_METADATA_TEMPLATE[ + "PluginImporter"]["platformData"]) if platform_name != "Any"] + new_platform_data = [] + unity_5_6_format = False + # Modify the "enabled" field of each platform in the metadata. + for entry in platform_data: + unity_5_6_format = "data" in entry + # Parse the platform name tuple from the "first" dictionary. + first, second = Asset.platform_data_get_entry(entry) + platform_tuple = list(first.items())[0] + if len(platform_tuple) >= 2: + unused_platform_target, platform_name = platform_tuple + if platform_name in remaining_platforms: + remaining_platforms.remove(platform_name) + entry = copy.deepcopy(entry) + _, second = Asset.platform_data_get_entry(entry) + second["enabled"] = any_enabled + new_platform_data.append(entry) + + # Add all platforms that were not present in the default metadata. + for platform_name in remaining_platforms: + platform_target = PLATFORM_TARGET_BY_PLATFORM.get(platform_name) + entry = collections.OrderedDict([ + ("first", collections.OrderedDict([ + (platform_target, platform_name)])), + ("second", collections.OrderedDict([ + ("enabled", any_enabled)]))]) + if unity_5_6_format: + entry = collections.OrderedDict([("data", entry)]) + new_platform_data.append(entry) + plugin_importer["platformData"] = new_platform_data + return importer_metadata + + @property + def importer_metadata_original(self): + """Get the original metadata section used to import this asset. + + Returns: + Importer section of Unity asset metadata as an OrderedDict. + """ + return self._importer_metadata + + @property + def importer_metadata(self): + """Get the Unity metadata section used to import this asset. + + Returns: + Importer section of Unity asset metadata as an OrderedDict. + """ + # If this is a linux library label the asset with the basename of + # the library so that it can be renamed to work with different versions + # of Unity. + linuxlibname_label_prefix = version_handler_tag( + field=VERSION_HANDLER_LINUXLIBNAME_FIELD_PREFIX) + labels = set() + if not [l for l in labels if l.startswith(linuxlibname_label_prefix)]: + match = LINUX_SHARED_LIBRARY_PATH.match(self._filename) + if match: + basename = os.path.basename(match.group(2)) + # Strip prefix and extension. + if basename.startswith(LINUX_SHARED_LIBRARY_PREFIX): + basename = basename[len(LINUX_SHARED_LIBRARY_PREFIX):] + if basename.endswith(LINUX_SHARED_LIBRARY_EXTENSION): + basename = basename[:-len(LINUX_SHARED_LIBRARY_EXTENSION)] + labels.add(version_handler_tag( + field=VERSION_HANDLER_LINUXLIBNAME_FIELD_PREFIX, + value=basename)) + + # Add gvhp_exportpath- label + labels.add(VERSION_HANDLER_PRESERVE_LABEL_PREFIX + + VERSION_HANDLER_FIELD_SEPARATOR + + VERSION_HANDLER_PRESERVE_EXPORT_PATH_FIELD_PREFIX + + self.filename) + + metadata = copy.deepcopy(self._importer_metadata) + metadata = Asset.add_labels_to_metadata(metadata, labels) + metadata = Asset.disable_unsupported_platforms(metadata, self._filename) + metadata = Asset.apply_any_platform_selection(metadata) + metadata = Asset.set_cpu_for_desktop_platforms(metadata) + return metadata + + @staticmethod + def write_metadata(filename, metadata_list): + """Write asset metadata to a file. + + Args: + filename: Name of the file to write. + metadata_list: List of OrderedDict instances to combine, in the + specified order, to generate the data structure to serialize in YAML to + the metadata file. + """ + output_metadata = collections.OrderedDict() + for metadata in metadata_list: + merge_ordered_dicts(output_metadata, metadata) + # Unity does throws exceptions when encountering empty lists of labels, + # so filter them from the metadata. + if not output_metadata.get("labels") and "labels" in output_metadata: + del output_metadata["labels"] + with open(filename, "wt", encoding='utf-8') as metadata_file: + metadata_file.write(YamlSerializer().dump(output_metadata)) + + def write(self, output_dir, guid, timestamp=-1): + """Write a asset and it's metadata to output_dir. + + Given an asset file, path and metadata, this method creates a Unity plugin + directory structure that facilitates the creation of a .unitypackage + archive. + + This method generates: + * /asset + Copy of the file referenced by asset_filename. + * /asset.meta + Metadata for this asset including `importer_metadata`. + * /pathname + Text file which contains export_path. + + Args: + output_dir: Output directory where the unity package can be staged for + archiving. The final .unitypackage archive can be built from the files + placed here. + guid: The guid to use to pack the asset. This will override the GUID in + any existing metadata. + timestamp: Timestamp to write into the metadata file if a timestamp + does not already exist in importer_metadata. If this argument < 0 the + timestamp is set to the creation time of the source file. + + Returns: + Directory containing the asset, asset.meta and pathname files. + Return None if the asset is for a folder. + + Raises: + RuntimeError: If the asset is exported again with a different path or + asset contents. + """ + # Ignore folder asset when writing to unitypackage + if self.is_folder: + return None + + # Create the output directory. + output_asset_dir = os.path.join(output_dir, guid) + if not os.path.exists(output_asset_dir): + os.makedirs(output_asset_dir) + + # Copy the asset to the output folder. + output_asset_filename = os.path.join(output_asset_dir, "asset") + copy_and_set_rwx(self.filename_absolute, output_asset_filename) + + # Create the "asset.meta" file. + output_asset_metadata_filename = (output_asset_filename + + ASSET_METADATA_FILE_EXTENSION) + + self.create_metadata(output_asset_metadata_filename, guid, timestamp) + + # Create the "pathname" file. + # export_filename is the path of the file when it's imported into a Unity + # project. + with open(os.path.join(output_asset_dir, "pathname"), "wt", + encoding='utf-8') as (pathname_file): + pathname_file.write(posix_path(os.path.join(ASSETS_DIRECTORY, + self.filename))) + return output_asset_dir + + def write_upm(self, output_dir, guid, timestamp=-1): + """Write a asset and it's metadata to output_dir for UPM package. + + Given an asset file, path and metadata, this method creates a Unity custom + package structure that facilitates the creation of a .tgz archive. + + This method generates: + * path/to/asset/asset_filename + Copy of the file referenced by asset_filename, or create folders if this + asset is for a folder. + * path/to/asset/asset_filename.meta + Metadata for this asset. + + Args: + output_dir: Output directory where the unity package can be staged for + archiving. The final .unitypackage archive can be built from the files + placed here. + guid: The guid to use to pack the asset. This will override the GUID in + any existing metadata. + timestamp: Timestamp to write into the metadata file if a timestamp does + not already exist in importer_metadata. If this argument < 0 the + timestamp is set to the creation time of the source file. + + Returns: + Directory containing the asset and asset.meta. + + Raises: + RuntimeError: If the asset is exported again with a different path or + asset contents. + """ + # Create the output directory. + output_asset = os.path.join(output_dir, "package", self.filename) + output_asset_dir = os.path.dirname(output_asset) + + if self.is_folder: + os.makedirs(output_asset) + else: + # Copy the asset to the output folder. + copy_and_set_rwx(self.filename_absolute, output_asset) + + # Create the "path/to/asset/asset_filename.meta" file. + output_asset_metadata_filename = ( + output_asset + ASSET_METADATA_FILE_EXTENSION) + + self.create_metadata(output_asset_metadata_filename, guid, timestamp) + + return output_asset_dir + + def create_metadata(self, filename, guid, timestamp=-1): + """Create metadata file for the asset. + + Args: + filename: Filename for the metadata. + guid: The guid to use to pack the asset. This will override the GUID in + any existing metadata. + timestamp: Timestamp to write into the metadata file if a timestamp does + not already exist in importer_metadata. If this argument < 0 the + timestamp is set to the creation time of the source file, or 0 if this + asset is a folder. + + Raises: + RuntimeError: If the asset is exported again with a different path or + asset contents. + """ + if self.is_folder: + importer_metadata = copy.deepcopy(DEFAULT_FOLDER_METADATA_TEMPLATE) + else: + importer_metadata = self.importer_metadata + + # If a timestamp is specified on the command line override the timestamp + # in the metadata if it isn't set. + if timestamp < 0: + if self.is_folder: + timestamp = 0 + else: + timestamp = int(os.path.getctime(self.filename_absolute)) + timestamp = safe_dict_get_value( + importer_metadata, "timeCreated", default_value=timestamp) + + Asset.write_metadata(filename, [ + DEFAULT_METADATA_TEMPLATE, importer_metadata, + collections.OrderedDict([("guid", guid), ("timeCreated", timestamp)]) + ]) + + @staticmethod + def sorted_by_filename(assets): + """Sort a sequence of assets by filename. + + Args: + assets: Sequence of assets to sort. + + Returns: + List of Asset instances sorted by filename. + """ + return sorted([asset for asset in assets], + key=lambda asset: asset.filename) + + +class ProjectConfigurationError(Exception): + """Raised when there is an error parsing the project configuration.""" + pass + + +class ConfigurationBlock(object): + """Common attributes for all export configuration blocks. + + Attributes: + _json: JSON data for the configuration block. + """ + + def __init__(self, json_data): + """Initialize the configuration block. + + Args: + json_data: JSON dictionary to read common configuration from. + """ + self._json = json_data + + @property + def sections(self): + """Get the sections that enable this block. + + Returns: + List of strings indicating when this block should be enabled. + """ + return set(safe_dict_get_value(self._json, "sections", default_value=[])) + + def get_enabled(self, sections): + """Determine whether this block is enabled given `sections`. + + Args: + sections: Set of sections that are enabled for the block. + + Returns: + True if this block specifies no sections or the specified sections + overlap this block's sections whitelist, False otherwise. + """ + block_sections = self.sections + return ((not block_sections) or + block_sections.intersection(sections)) + + +class AssetConfiguration(ConfigurationBlock): + """Export configuration for a set of assets. + + Attributes: + _package: PackageConfiguration instance this asset group was parsed from. + _json: Dictionary containing the raw asset group configuration. + """ + + def __init__(self, package, asset_json): + """Initialize this asset configuration from JSON. + + Args: + package: PackageConfiguration instance `asset_json` was parsed from. + asset_json: JSON dictionary for this set of assets. + + Raises: + ProjectConfigurationError: If an invalid field is specified. + """ + super(AssetConfiguration, self).__init__(asset_json) + self._package = package + self._asset_json = asset_json + + @property + def importer_metadata(self): + """Get the Unity metadata section used to import this asset. + + Returns: + Importer section of Unity asset metadata as an OrderedDict. + + Raises: + ProjectConfigurationError: If the importer type or the cpu string for a + PluginImporter is invalid. + """ + importer_type = safe_dict_get_value(self._json, "importer", + default_value="DefaultImporter") + importer_metadata = None + if importer_type == "DefaultImporter": + importer_metadata = copy.deepcopy(DEFAULT_IMPORTER_METADATA_TEMPLATE) + elif importer_type == "PluginImporter": + platforms = set(safe_dict_get_value( + self._json, "platforms", default_value=["Editor", "Android", "iOS", + "tvOS", + STANDALONE_PLATFORM_ALIAS])) + cpu_string = safe_dict_get_value(self._json, "cpu", + default_value="AnyCPU") + + # Select standalone platforms. + standalone_platforms = [] + if STANDALONE_PLATFORM_ALIAS in platforms: + standalone_platforms = DESKTOP_PLATFORMS_BY_CPU.get(cpu_string) + if not standalone_platforms: + raise ProjectConfigurationError( + "Unknown cpu type %s for package %s, paths %s" % ( + cpu_string, self._package.name, str(self.paths))) + platforms.remove(STANDALONE_PLATFORM_ALIAS) + # Enable "universal" platforms where applicable. + universal_platforms = set() + for standalone in standalone_platforms: + universal_platform = ARCH_SPECIFIC_TO_UNIVERSAL_PLATFORM.get( + standalone) + if universal_platform: + universal_platforms.add(universal_platform) + platforms = platforms.union(standalone_platforms).union( + universal_platforms) + + # Enable selected platforms. + importer_metadata = copy.deepcopy(PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = importer_metadata["PluginImporter"]["platformData"] + for target_platform in platforms: + platform_data_options = platform_data.get(target_platform, + collections.OrderedDict()) + platform_data[target_platform] = platform_data_options + platform_data_options["enabled"] = 1 + importer_metadata = Asset.set_cpu_for_desktop_platforms( + importer_metadata) + if "Android" in platforms and cpu_string != "AnyCPU": + importer_metadata = Asset.set_cpu_for_android( + importer_metadata, cpu_string) + # Set validateReferences, if requested, which should be either 0 or 1 + validateRef = safe_dict_get_value(self._json, "validateReferences", default_value=2) + if validateRef == 0 or validateRef == 1: + importer_metadata["PluginImporter"]["validateReferences"] = validateRef + else: + raise ProjectConfigurationError( + "Unknown importer type %s for package %s, paths %s" % ( + importer_type, self._package.name, str(self.paths))) + Asset.add_labels_to_metadata(importer_metadata, self.labels) + return importer_metadata + + @property + def labels(self): + """Get the set of asset labels that should be applied to this asset. + + Returns: + Set of asset label strings to apply to the assets. + """ + return set(safe_dict_get_value( + self._json, "labels", default_value=[])).union(self._package.labels) + + @property + def paths(self): + """Get the set of paths to include in this group of assets. + + Returns: + Set of paths to include in this group. + """ + return set(safe_dict_get_value(self._json, "paths", default_value=[])) + + @property + def override_metadata(self): + """Get the metadata used override. + + Returns: + OrderedDict metadata to merge over the metadata in each asset referenced + by this AssetConfiguration instance. + """ + return safe_dict_get_value(self._json, "override_metadata", + default_value=collections.OrderedDict(), + value_classes=[dict]) + + @property + def override_metadata_upm(self): + """Get the metadata used override for UPM package. + + Returns: + OrderedDict metadata to merge over the metadata in each asset referenced + by this AssetConfiguration instance. + """ + return safe_dict_get_value( + self._json, + "override_metadata_upm", + default_value=collections.OrderedDict(), + value_classes=[dict]) + + def find_assets(self, assets_dirs, for_upm=False): + """Find the assets referenced by the `paths` attribute. + + Args: + assets_dirs: List of root directories to search for paths referenced by + this asset group. + for_upm: Whether this is for packaging for Unity Package Manager + + Returns: + List of Asset instances referencing file paths found under root_dir + matching the patterns in the `paths` attribute. All returned paths are + relative to the specified assets_dir. + """ + matching_files = set() + assets_dir_by_matching_file = {} + paths_matching_no_files = [] + for wildcard_path in sorted(self.paths): + found_assets = [] + for assets_dir in assets_dirs: + assets_dir = os.path.normpath(assets_dir) + for path in glob.glob(os.path.join(assets_dir, wildcard_path)): + if os.path.isdir(path): + for current_root, _, files in os.walk(path): + for filename in files: + if not AssetConfiguration._is_metadata_file(filename): + relative_path = os.path.relpath(os.path.join( + current_root, filename), assets_dir) + found_assets.append(relative_path) + matching_files.add(relative_path) + assets_dir_by_matching_file[relative_path] = assets_dir + elif not AssetConfiguration._is_metadata_file(path): + relative_path = os.path.relpath(path, assets_dir) + found_assets.append(relative_path) + matching_files.add(relative_path) + assets_dir_by_matching_file[relative_path] = assets_dir + if not found_assets: + paths_matching_no_files.append(wildcard_path) + if paths_matching_no_files: + logging.warning( + "Package %s references paths that match no files %s", + self._package.name, str(paths_matching_no_files)) + importer_metadata = self.importer_metadata + + assets = [] + # If metadata exists, read it for each file in this group creating a list + # Asset instances that references each filename and associated metadata. + for filename in sorted(matching_files): + assets_dir = assets_dir_by_matching_file[filename] + asset_metadata_filename = os.path.join( + assets_dir, filename + ASSET_METADATA_FILE_EXTENSION) + asset_metadata = copy.deepcopy(importer_metadata) + if os.path.exists(asset_metadata_filename): + existing_asset_metadata = collections.OrderedDict() + with open(asset_metadata_filename, "rt", encoding='utf-8') as ( + asset_metadata_file): + existing_asset_metadata = YamlSerializer().load( + asset_metadata_file.read()) + if existing_asset_metadata: + # If the file already has metadata use it, preserving the labels from + # this instance. + Asset.add_labels_to_metadata(existing_asset_metadata, self.labels) + asset_metadata = existing_asset_metadata + + merge_ordered_dicts(asset_metadata, self.override_metadata) + # Override metadata again using "override_metadata_upm" + if for_upm: + merge_ordered_dicts(asset_metadata, self.override_metadata_upm) + + assets.append(Asset(filename, os.path.join(assets_dir, filename), + asset_metadata)) + + return Asset.sorted_by_filename(assets) + + @staticmethod + def _is_metadata_file(path): + """Returns true if the path references a Unity asset metadata file. + + Args: + path: Path of the file to query. + + Returns: + True if the file is an asset metadata file, False otherwise. + """ + return os.path.splitext(path)[1] == ".meta" + + +class PackageConfiguration(ConfigurationBlock): + """Export configuration for a package. + + Attributes: + _project: ProjectConfiguration instance this package was parsed from. + _json: Dictionary containing the raw package configuration. + """ + + def __init__(self, project, package_json): + """Initialize this object with JSON package data. + + Args: + project: ProjectConfiguration instance this package was parsed from. + package_json: Package dictionary parsed from JSON project data. + + Raises: + ProjectConfigurationError: If the package has no name. + """ + super(PackageConfiguration, self).__init__(package_json) + self._project = project + self._json = package_json + if not safe_dict_get_value(self._json, "name", + value_classes=STR_OR_UNICODE): + raise ProjectConfigurationError("Package found with no name") + + @property + def name(self): + """Get the name of the exported package. + + Returns: + Name of package as string. + """ + return self._json["name"] + + @property + def version(self): + """Get the version of this project. + + Returns: + Version as a string in form of "major.minor.revision" + """ + + return self._project.version + + @property + def tarball_name(self): + """Get the tarball filename for Unity Package Manager package. + + Returns: + Tarball filename as string. + """ + + if not self.common_package_name or not self.version: + return None + return self.common_package_name + "-" + self.version + ".tgz" + + @property + def export_upm(self): + """Get whether to export this package as a Unity Package Manager package. + + Returns: + True if the package should be exported. + """ + return safe_dict_get_value(self._json, "export_upm", default_value=0) == 1 + + @property + def upm_package_config(self): + """Get the package configuration for Unity Package Manager package. + + Returns: + Package configuration as dict. + """ + + return safe_dict_get_value(self._json, "upm_package_config") + + @property + def upm_manifest(self): + """Get the package manifest for Unity Package Manager package. + + Returns: + UPM manifest as dict. + """ + package_info = self.upm_package_config + if not package_info: + return None + + return safe_dict_get_value(package_info, "manifest") + + @property + def common_manifest(self): + """Get common package manifest. + + Returns: + Manifest as dict. + """ + return safe_dict_get_value(self._json, "common_manifest") + + @property + def common_package_name(self): + """Get the common name of this package. + + Returns: + Name of package as string. + """ + package_info = self.common_manifest + if not package_info: + return None + + return safe_dict_get_value(package_info, "name") + + @property + def common_package_display_name(self): + """Get the common display name of this package. + + Returns: + Name of package as string. + """ + package_info = self.common_manifest + if not package_info: + return self.package_name + + return safe_dict_get_value(package_info, "display_name", + default_value=self.package_name) + + @property + def common_package_description(self): + """Get the common description of this package. + + Returns: + Name of package as string. + """ + package_info = self.common_manifest + if not package_info: + return None + + description = safe_dict_get_value(package_info, "description") + if description: + if issubclass(description.__class__, list): + description = "".join(description) + else: + description = str(description) + return description + + def _get_includes(self, recursive=True): + """Get transitively included packages of this package. + + Args: + recursive: Whether to recursively traverse includes. + + Returns: + List of PackageConfiguration instances. + + Raises: + ProjectConfigurationError: If any referenced packages are not present + in the project. + """ + packages_by_name = self._project.packages_by_name + included_packages_by_name = {} + missing_package_names = [] + for include_package_name in safe_dict_get_value(self._json, "includes", + default_value=[]): + package = packages_by_name.get(include_package_name) + if package: + included_packages_by_name[package.name] = package + if recursive: + included_packages_by_name.update( + dict([(pkg.name, pkg) for pkg in package.includes])) + else: + missing_package_names.append(include_package_name) + if missing_package_names: + raise ProjectConfigurationError( + "%s includes missing packages %s" % ( + self.name, missing_package_names)) + return list(included_packages_by_name.values()) + + @property + def includes(self): + """Get transitively included packages of this package. + + Returns: + List of PackageConfiguration instances. + + Raises: + ProjectConfigurationError: If any referenced packages are not present + in the project. + """ + return self._get_includes() + + def check_circular_references_in_includes(self, parents): + """Searches for circular references in includes. + + Args: + parents: List of parents packages including this package. + + Raises: + ProjectConfigurationError: If a circular reference is detected. + """ + parent_names = [parent.name for parent in parents] + if self.name in parent_names: + raise ProjectConfigurationError( + ("Circular package inclusion detected when checking %s which is " + "included by [%s]") % (self.name, " --> ".join(parent_names))) + for include in self._get_includes(recursive=False): + parents.append(self) + include.check_circular_references_in_includes(parents) + del parents[-1] + + @property + def export(self): + """Get whether this package should be exported. + + Returns: + True if the package should be exported to `name`, False otherwise. + """ + return safe_dict_get_value(self._json, "export", default_value=1) == 1 + + @property + def exclude_paths(self): + """Get the set of paths that should be excluded from this package. + + Returns: + Set of regular expressions that match paths which should be excluded from + this package. + """ + return [re.compile(path) + for path in safe_dict_get_value(self._json, "exclude_paths", + default_value=[])] + + def find_assets(self, assets_dirs, check_for_duplicates=True, for_upm=False): + """Find all assets referenced by this package. + + Args: + assets_dirs: List of paths to directories to search for files referenced + by this package. + check_for_duplicates: Whether to raise an exception when duplicate assets + are found with different import settings. + for_upm: Whether this is for packaging for UPM package. + + Returns: + List of Asset instances for all files imported by this package. + + Raises: + ProjectConfigurationError: If more than one file has been included with + different import settings. + """ + # Dictionary of asset lists indexed by package name. + assets_by_package_name = collections.defaultdict(list) + for asset_config in self.imports: + if asset_config.get_enabled(self._project.selected_sections): + assets_by_package_name[self.name].extend( + asset_config.find_assets(assets_dirs, for_upm)) + + # Only add asset from "includes" package if this is not for UPM package. + if not for_upm: + for package in self.includes: + logging.debug("%s including assets from %s", self.name, package.name) + assets_by_package_name[package.name].extend( + package.find_assets(assets_dirs, check_for_duplicates=False)) + + package_and_assets_by_filename = collections.defaultdict(list) + for package_name, assets in assets_by_package_name.items(): + for asset in assets: + package_and_assets_by_filename[asset.filename].append( + (package_name, asset)) + + # Check for duplicate assets with different import settings. + duplicate_assets_errors = [] + for filename, package_and_assets in package_and_assets_by_filename.items(): + if len(package_and_assets) <= 1: + continue + # Look for assets with different import settings for this file. + differing_metadata_packages = set() + previous_package_name = None + previous_asset = None + for package_and_asset in package_and_assets: + package_name, asset = package_and_asset + if previous_package_name and (previous_asset.importer_metadata != + asset.importer_metadata): + differing_metadata_packages.add(previous_package_name) + differing_metadata_packages.add(package_name) + previous_package_name = package_name + previous_asset = asset + + if differing_metadata_packages: + duplicate_assets_errors.append( + ("File %s imported with different import settings in " + "packages %s") % (filename, sorted(differing_metadata_packages))) + if check_for_duplicates and duplicate_assets_errors: + raise ProjectConfigurationError("\n".join(duplicate_assets_errors)) + + # Deduplicate the list of assets that were found. + found_assets = Asset.sorted_by_filename( + [package_and_assets[0][1] for package_and_assets in ( + package_and_assets_by_filename.values())]) + + # Filter out excluded assets. + exclude_paths = self.exclude_paths + if exclude_paths: + assets_with_exclusions_removed = [] + for asset in found_assets: + include = True + for exclude_path in exclude_paths: + if exclude_path.match(asset.filename): + include = False + break + if include: + assets_with_exclusions_removed.append(asset) + found_assets = assets_with_exclusions_removed + + logging.debug("Found assets for package %s: %s", self.name, + [asset.filename for asset in found_assets]) + return found_assets + + @property + def manifest_path(self): + """Get the manifest path of the package in the exported archive. + + Returns: + Path of the directory that will contain the manifest in the exported + archive or None if no manifest should be created. + """ + return safe_dict_get_value(self._json, "manifest_path", + value_classes=STR_OR_UNICODE) + + @property + def package_name(self): + """Get the name of this package configuration. + + Returns: + String as the name without extension. + """ + + return os.path.splitext(self.name)[0] + + @property + def manifest_filename(self): + """Get the filename of the manifest that will be generated by this package. + + Returns: + Filename of the manifest in the exported archive or None if no manifest + should be created. + """ + path = self.manifest_path + version = self._project.version + if path and version: + return version_handler_filename( + os.path.join(path, self.package_name + ".txt"), + [[VERSION_HANDLER_VERSION_FIELD_PREFIX, version], + [VERSION_HANDLER_MANIFEST_FIELD_PREFIX, None]]) + return None + + def get_manifest_metadata(self, + manifest_type=VERSION_HANDLER_MANIFEST_TYPE_LEGACY): + """Get the importer metadata for the manifest generated by this package. + + Args: + manifest_type: Type of the manifest, ex. legacy (.txt) or upm + (package.json) + + Returns: + OrderedDict of importer metadata if a manifest is generated by this + package, None otherwise. + """ + + if not self._project.version: + return None + + if (manifest_type == VERSION_HANDLER_MANIFEST_TYPE_LEGACY and + not self.manifest_filename): + return None + metadata = copy.deepcopy(DEFAULT_METADATA_TEMPLATE) + labels = self.labels + if manifest_type == VERSION_HANDLER_MANIFEST_TYPE_LEGACY: + labels.add( + version_handler_tag(field=VERSION_HANDLER_MANIFEST_FIELD_PREFIX)) + + # Add gvhp_manifestname-0DisplayName + priority = 0 + if self.common_package_display_name: + labels.add(VERSION_HANDLER_PRESERVE_LABEL_PREFIX + + VERSION_HANDLER_FIELD_SEPARATOR + + VERSION_HANDLER_PRESERVE_MANIFEST_NAME_FIELD_PREFIX + + str(priority) + self.common_package_display_name) + priority += 1 + if self.package_name != self.common_package_display_name: + labels.add(VERSION_HANDLER_PRESERVE_LABEL_PREFIX + + VERSION_HANDLER_FIELD_SEPARATOR + + VERSION_HANDLER_PRESERVE_MANIFEST_NAME_FIELD_PREFIX + + str(priority) + self.package_name) + + elif manifest_type == VERSION_HANDLER_MANIFEST_TYPE_UPM: + # gupmr_manifest + labels.add(UPM_RESOLVER_LABEL_PREFIX + UPM_RESOLVER_FIELD_SEPARATOR + + UPM_RESOLVER_MANIFEST_FIELD_PREFIX) + Asset.add_labels_to_metadata(metadata, labels) + + return metadata + + def write_manifest(self, output_dir, assets): + """Write the manifest for this package to the specified directory. + + Args: + output_dir: Directory to write the manifest into. + assets: Assets to write to the manifest, typically returned by + find_assets(). + + Returns: + Asset instance that references the generated manifest. + """ + manifest_filename = self.manifest_filename + if not manifest_filename: + return None + manifest_absolute_path = os.path.join(output_dir, manifest_filename) + manifest_directory = os.path.dirname(manifest_absolute_path) + if not os.path.exists(manifest_directory): + os.makedirs(manifest_directory) + with open(manifest_absolute_path, "wt", encoding='utf-8') as manifest_file: + manifest_file.write( + "%s\n" % "\n".join([posix_path(os.path.join(ASSETS_DIRECTORY, + asset.filename)) + for asset in Asset.sorted_by_filename(assets)])) + # Retrieve a template manifest asset if it exists. + manifest_asset = [asset for asset in assets + if asset.filename == manifest_filename] + return manifest_asset[0] if manifest_asset else ( + Asset(manifest_filename, manifest_absolute_path, + self.get_manifest_metadata(VERSION_HANDLER_MANIFEST_TYPE_LEGACY))) + + def write_upm_manifest(self, output_dir): + """Write UPM manifest for this package to the specified directory. + + Args: + output_dir: Directory to write the manifest into. + + Returns: + Asset instance that references the generated manifest. + + Raises: + ProjectConfigurationError: If this package or its dependencies does not + have package name under common_manifest. + """ + + manifest_filename = "package.json" + manifest_absolute_path = os.path.join(output_dir, manifest_filename) + manifest_directory = os.path.dirname(manifest_absolute_path) + if not os.path.exists(manifest_directory): + os.makedirs(manifest_directory) + + # Compose package.json + package_manifest = {} + package_manifest["name"] = self.common_package_name + if not package_manifest["name"]: + raise ProjectConfigurationError( + "Detected package %s has missing package name under common_manifest" % + (self.name)) + package_manifest["version"] = self.version + + common_manifest = self.common_manifest + # Add manifest info from common_manifest to package.json + if common_manifest: + for common_manifest_key, upm_manifest_key in (("display_name", + "displayName"), + ("keywords", "keywords"), + ("author", "author")): + common_manifest_value = safe_dict_get_value(common_manifest, + common_manifest_key) + safe_dict_set_value(package_manifest, upm_manifest_key, + common_manifest_value) + + # Add description. + safe_dict_set_value(package_manifest, "description", + self.common_package_description) + + # Add additonal keywords to link back to legacy manifest. + if self.export and self.manifest_path: + keywords = safe_dict_get_value(package_manifest, "keywords", + default_value=[]) + keywords.append(UPM_KEYWORDS_MANIFEST_PREFIX + self.package_name) + if self.common_package_display_name != self.package_name: + keywords.append(UPM_KEYWORDS_MANIFEST_PREFIX + + self.common_package_display_name) + package_manifest["keywords"] = keywords + + # Add minimum Unity version + if self.upm_manifest: + safe_dict_set_value(package_manifest, "unity", + safe_dict_get_value(self.upm_manifest, "unity")) + dependencies = safe_dict_get_value( + self.upm_manifest, "dependencies", default_value={}) + else: + dependencies = {} + + # Add additional dependencies from "includes" + missing_deps = [] + include_packages = [ + pkg for pkg in self._get_includes(recursive=False) if pkg.export_upm + ] + for include in include_packages: + if include.common_package_name: + dependencies[include.common_package_name] = include.version + else: + missing_deps.append(include.name) + if missing_deps: + raise ProjectConfigurationError( + ("Detected multiple dependencies by %s has missing package name" + + "\n%s") % (self.name, "\n".join(missing_deps))) + package_manifest["dependencies"] = dependencies + + with open(manifest_absolute_path, "wt", encoding='utf-8') as manifest_file: + json.dump(package_manifest, manifest_file, indent=2) + + return Asset( + manifest_filename, + manifest_absolute_path, + self.get_manifest_metadata(VERSION_HANDLER_MANIFEST_TYPE_UPM), + filename_guid_lookup=os.path.join(self.common_package_name, + manifest_filename)) + + @property + def imports(self): + """Get the AssetConfiguration instances from this package. + + Returns: + List of AssetConfiguration instances. + """ + return [AssetConfiguration(self, import_json) + for import_json in safe_dict_get_value(self._json, "imports", + default_value=[])] + + @property + def labels(self): + """Get the set of labels that should be added to assets in this package. + + Returns: + Set of asset label strings to apply to assets in the package. + """ + label_set = set() + version = self._project.version + if version: + label_set = label_set.union( + [version_handler_tag(), + version_handler_tag(field=VERSION_HANDLER_VERSION_FIELD_PREFIX, + value=version)]) + return label_set + + @staticmethod + def create_archive(archive_filename, input_directory, timestamp): + """Create a .unitypackage archive from a directory. + + Args: + archive_filename: Name of the archive file to create. + input_directory: Directory to archive. + timestamp: Timestamp to apply to the archive and all files in the archive + or -1 to use the current time. + """ + archive_filename = os.path.realpath(archive_filename) + cwd = os.getcwd() + try: + os.chdir(input_directory) + # Create a deterministically ordered set of filesystem entries. + input_filenames = [] + for current_dir, directories, filenames in os.walk(os.path.curdir): + for directory in directories: + input_filenames.append(os.path.join(current_dir, directory)) + for filename in filenames: + input_filenames.append(os.path.join(current_dir, filename)) + input_filenames = sorted( + [os.path.normpath(filename) for filename in input_filenames]) + + archive_dir = os.path.dirname(archive_filename) + if not os.path.exists(archive_dir): + os.makedirs(archive_dir) + + # Create a tar.gz archive. + tar_available = (platform.system() == "Linux" or + platform.system() == "Darwin") + gnu_tar_available = platform.system() == "Linux" + # Whether a reproducible tar.gz is required. + if tar_available and FLAGS.use_tar: + # tarfile is 10x slower than the tar command so use the command line + # tool where it's available and can generate a reproducible archive. + list_filename = os.path.join(tempfile.mkdtemp(), "input_files.txt") + try: + # Create a list of input files to workaround command line length + # limits. + with open(list_filename, "wt", encoding='utf-8') as list_file: + list_file.write("%s\n" % "\n".join(input_filenames)) + + tar_args = ["tar"] + tar_args.extend(["-c", "-z", "-f", archive_filename]) + if gnu_tar_available: + if FLAGS.timestamp: + tar_args.append("--mtime=@%d" % FLAGS.timestamp) + # Hard code the user and group of files in the tar file so that + # the process is reproducible. + tar_args.extend(["--owner=%s" % FLAGS.owner, + "--group=%s" % FLAGS.group]) + tar_args.append("--no-recursion") + else: # Assume BSD tar. + # Set the modification time of each file since BSD tar doesn't have + # an option to override this. + if FLAGS.timestamp: + for filename in input_filenames: + os.utime(filename, (FLAGS.timestamp, FLAGS.timestamp)) + # Don't recurse directories. + tar_args.append("-n") + # Avoid creating mac metadata files with name started with "." + if platform.system() == "Darwin": + tar_args.append("--no-mac-metadata") + tar_args.extend(["-T", list_filename]) + # Disable timestamp in the gzip header. + tar_env = os.environ.copy() + tar_env["GZIP"] = "-n" + subprocess.check_call(tar_args, cwd=input_directory, env=tar_env) + finally: + shutil.rmtree(os.path.dirname(list_filename)) + else: + with open(archive_filename, "wb") as gzipped_tar_file: + with gzip.GzipFile( + # The filename in the archive must end with .tar or .tar.gz for + # Unity to open it on Windows. + os.path.splitext(archive_filename)[0] + ".tar.gz", + fileobj=gzipped_tar_file, mode="wb", + mtime=(timestamp if timestamp >= 0 else None)) as gzip_file: + with tarfile.open(mode="w|", fileobj=gzip_file, + format=tarfile.USTAR_FORMAT, dereference=True, + errorlevel=2) as tar_file: + def reproducible_tarinfo(tarinfo): + """Patch TarInfo so that it generates a reproducible archive. + + Args: + tarinfo: TarInfo to modify. + + Returns: + Modified tarinfo. + """ + tarinfo.mtime = timestamp if timestamp >= 0 else tarinfo.mtime + tarinfo.uid = 0 + tarinfo.gid = 0 + tarinfo.uname = FLAGS.owner + tarinfo.gname = FLAGS.group + return tarinfo + + for filename in input_filenames: + tar_file.add(filename, recursive=False, + filter=reproducible_tarinfo) + + finally: + os.chdir(cwd) + + def write(self, guid_database, assets_dirs, output_dir, timestamp, + package_filename=None): + """Creates a .unitypackage file from a package dictionary. + + Creates a Unity package given the import directory and the package + dictionary containing the groups of imports. + + Args: + guid_database: GuidDatabase instance which contains GUIDs for each + exported asset. + assets_dirs: List of paths to directories to search for files referenced + by this package. + output_dir: Directory where to write the exported .unitypackage. + timestamp: Timestamp to apply to all packaged assets in the archive. + If this value is less than 0, the creation time of each input file is + used instead. + package_filename: Filename to write package to in the output_dir. + + Returns: + Path to the created .unitypackage file. + + Raises: + MissingGuidsError: If GUIDs are missing for input files. + DuplicateGuidsError: If any duplicate GUIDs are present. + ProjectConfigurationError: If files are imported multiple times with + different metadata. + """ + package_filename = package_filename or self.name + unity_package_file = os.path.join(output_dir, package_filename) + + # Create a directory to stage the source assets and assemble the package. + temporary_dir = tempfile.mkdtemp() + try: + generated_assets_dir = os.path.join(temporary_dir, "generated_assets") + os.makedirs(generated_assets_dir) + staging_dir = os.path.join(temporary_dir, "plugin") + os.makedirs(staging_dir) + + logging.info("Packaging %s to %s...", self.name, unity_package_file) + + assets = self.find_assets(assets_dirs) + # If a manifest is enabled, write it into the assets directory. + manifest_asset = self.write_manifest(generated_assets_dir, assets) + if manifest_asset: + assets.append(manifest_asset) + + # Add manifest files for the include packages + for include_package in self.includes: + include_manifest_asset = include_package.write_manifest( + generated_assets_dir, include_package.find_assets(assets_dirs)) + if include_manifest_asset: + assets.append(include_manifest_asset) + + # Populate the GUID database and check for any duplicates. + guid_database.read_guids_from_assets(assets) + + # Process all assets and stage all files for packaging in the staging + # area. + for asset in Asset.sorted_by_filename(assets): + asset_file = asset.write( + staging_dir, guid_database.get_guid(asset.filename_guid_lookup), + timestamp) + logging.info("- Processed %s --> %s", asset.filename, asset_file) + + # Create the .unitypackage file. + PackageConfiguration.create_archive(unity_package_file, staging_dir, + timestamp) + logging.info("Created %s for %s", unity_package_file, self.name) + finally: + shutil.rmtree(temporary_dir) + + return unity_package_file + + def write_upm(self, + guid_database, + assets_dirs, + output_dir, + timestamp, + package_filename=None): + """Creates a .tgz file from a package dictionary. + + Creates a UPM package given the import directory and the package + dictionary containing the groups of imports. + + Args: + guid_database: GuidDatabase instance which contains GUIDs for each + exported asset. + assets_dirs: List of paths to directories to search for files referenced + by this package. + output_dir: Directory where to write the exported .tgz. + timestamp: Timestamp to apply to all packaged assets in the archive. If + this value is less than 0, the creation time of each input file is used + instead. + package_filename: Filename to write package to in the output_dir. + + Returns: + Path to the created .tgz file. + + Raises: + MissingGuidsError: If GUIDs are missing for input files. + DuplicateGuidsError: If any duplicate GUIDs are present. + ProjectConfigurationError: If files are imported multiple times with + different metadata. + """ + package_filename = package_filename or self.tarball_name + unity_package_file = os.path.join(output_dir, package_filename) + + # Create a directory to stage the source assets and assemble the package. + temporary_dir = tempfile.mkdtemp() + try: + generated_assets_dir = os.path.join(temporary_dir, "generated_assets") + os.makedirs(generated_assets_dir) + staging_dir = os.path.join(temporary_dir, "plugin") + os.makedirs(staging_dir) + + logging.info("Packaging %s to %s...", self.name, unity_package_file) + + assets = self.find_assets(assets_dirs, for_upm=True) + + # Create package.json + manifest_asset = self.write_upm_manifest(generated_assets_dir) + if manifest_asset: + assets.append(manifest_asset) + + manifest_asset = self.write_manifest(generated_assets_dir, assets) + if manifest_asset: + assets.append(manifest_asset) + + # Move README.md, CHANGELOG.md and LICENSE.md to root folder. + for config_name, to_location in (("readme", "README.md"), + ("changelog", "CHANGELOG.md"), + ("license", "LICENSE.md")): + from_location = safe_dict_get_value(self._json, config_name) + if from_location: + abs_from_location = find_in_dirs(from_location, assets_dirs) + if abs_from_location: + # Create default metadata + metadata = copy.deepcopy(DEFAULT_METADATA_TEMPLATE) + labels = self.labels + Asset.add_labels_to_metadata(metadata, labels) + + assets.append(Asset( + to_location, + abs_from_location, + metadata, + filename_guid_lookup=os.path.join(self.common_package_name, + to_location), + is_folder=False)) + else: + raise ProjectConfigurationError( + "Cannot find '%s' at '%s' for package '%s'. Perhaps it " + "is not included in assets_dir or assets_zip?" % ( + config_name, from_location, self.name)) + + # Add all folder assets to generate .meta + folders = set() + for asset in assets: + filepath = os.path.os.path.dirname(asset.filename) + while filepath: + folders.add(filepath) + filepath = os.path.os.path.dirname(filepath) + + for folder in folders: + # Same folder from each package needs to have an unique GUID. Therefore, + # filename_guid_lookup is set to "package-name/path/to/folder" + assets.append( + Asset( + folder, + folder, + DEFAULT_METADATA_TEMPLATE, + filename_guid_lookup=os.path.join(self.common_package_name, + folder), + is_folder=True)) + + # Populate the GUID database and check for any duplicates. + guid_database.read_guids_from_assets(assets) + + # Process all assets and stage all files for packaging in the staging + # area. + for asset in Asset.sorted_by_filename(assets): + asset_file = asset.write_upm( + staging_dir, guid_database.get_guid(asset.filename_guid_lookup), + timestamp) + logging.info("- Processed %s --> %s", asset.filename, asset_file) + + # Copy documents to "Documentation~" folder. + # See https://docs.unity3d.com/Manual/cus-layout.html + # All documentation folders and files must not include a .meta file + # otherwise the documentation links in the Unity Package Manager window + # will not work + source_doc = safe_dict_get_value(self._json, "documentation") + if source_doc: + # Try to find the source doc from assets directory + source_doc = find_in_dirs(source_doc, assets_dirs) + if os.path.isfile(source_doc): + # Copy file + target_doc = os.path.join(staging_dir, "package", + UPM_DOCUMENTATION_DIRECTORY, + UPM_DOCUMENTATION_FILENAME) + logging.info("- Copying doc file %s --> %s", source_doc, target_doc) + copy_and_set_rwx(source_doc, target_doc) + elif os.path.isdir(source_doc): + target_doc_dir = os.path.join(staging_dir, "package", + UPM_DOCUMENTATION_DIRECTORY) + # Check if index.md exists + if not os.path.exists(os.path.join(source_doc, + UPM_DOCUMENTATION_FILENAME)): + raise ProjectConfigurationError( + "Cannot find index.md under '%s' for package '%s'. Perhaps it " + "is not included in assets_dir or assets_zip?" % ( + source_doc, self.name)) + + logging.info("- Copying doc folder %s --> %s", + source_doc, target_doc_dir) + copy_and_set_rwx(source_doc, target_doc_dir) + else: + raise ProjectConfigurationError( + "Cannot find documentation at '%s' for package '%s'. Perhaps the " + "file/folder is not included in assets_dir or assets_zip?" % ( + from_location, self.name)) + + # Create the .tgz file. + PackageConfiguration.create_archive(unity_package_file, staging_dir, + timestamp) + logging.info("Created %s for %s", unity_package_file, self.name) + finally: + shutil.rmtree(temporary_dir) + + return unity_package_file + + +class BuildConfiguration(ConfigurationBlock): + """Build configuration used to modify enabled sections and export settings. + + Attributes: + _json: JSON dictionary this configuration is read from. + _replacements: List of (regular_expression, replacement_string) tuples where + regular_expression is a RegexObject instance and replacement_string is + a string that can be passed to re.sub() as part of a replacement. + """ + + def __init__(self, build_json): + """Parse a build configuration. + + Args: + build_json: Dictionary with the configuration to parse. + + Raises: + ProjectConfigurationError: If a package name replacement regular + expression fails to compile. + """ + super(BuildConfiguration, self).__init__(build_json) + self._json = build_json + self._replacements = [] + for match_replacement in (safe_dict_get_value( + self._json, "package_name_replacements", default_value=[])): + match_regexp_string = safe_dict_get_value(match_replacement, "match", + value_classes=STR_OR_UNICODE) + replacement_string = safe_dict_get_value(match_replacement, "replacement", + value_classes=STR_OR_UNICODE) + if match_regexp_string and replacement_string is not None: + try: + self._replacements.append((re.compile(match_regexp_string), + replacement_string)) + except re.error as error: + raise ProjectConfigurationError( + "Failed to compile package name replacement regular expression " + "'%s' for build config %s (%s)" % (match_regexp_string, self.name, + str(error))) + + @property + def name(self): + """Get the name of the build configuration. + + Returns: + Name of build configuration for logging. + """ + return safe_dict_get_value(self._json, "name", default_value="") + + @property + def enabled_sections(self): + """Get the set of sections that should be enabled when exporting. + + Returns: + Set of export section strings that should be enabled when exporting with + this build configuration. + """ + return set(safe_dict_get_value(self._json, "enabled_sections", + default_value=[])) + + @property + def package_name_replacements(self): + """Get the set of replacements to apply to package names. + + Returns: + List of (regular_expression, replacement_string) tuples where + regular_expression is a RegexObject instance and replacement_string is + a string that can be passed to re.sub() as part of a replacement. + """ + return list(self._replacements) + + def apply_package_name_replacements(self, package_name): + """Apply package name replacements to the specified string. + + Args: + package_name: String modified by package name replacements. + + Returns: + Package name modified by the replacements returned by + the package_name_replacements property. + + Raises: + ProjectConfigurationError: If a package name replacement fails. + """ + new_package_name = package_name + for match_regexp, replacement_string in self._replacements: + try: + new_package_name = match_regexp.sub(replacement_string, + new_package_name) + except re.error as error: + raise ProjectConfigurationError( + "Failed to apply package name replacement '%s' --> '%s' from build " + "config %s to package name %s (%s)" % ( + match_regexp.pattern, replacement_string, self.name, + package_name, str(error))) + return new_package_name + + def create_package_name_map(self, package_names_map): + """Create a dictionary which maps existing to replaced package names. + + Args: + package_names_map: Map of package name to filename to replace to generate + a dictionary from. + + Returns: + Dictionary of replacement package names keyed by the original package + name. + + Raises: + ProjectConfigurationError: If multiple package names are mapped to the + same target package name or a package name replacement fails. + """ + new_by_old_package_names = collections.defaultdict(list) + for package_name, filename_to_replace in package_names_map.items(): + new_by_old_package_names[package_name].append( + self.apply_package_name_replacements(filename_to_replace)) + + duplicate_new_names_strings = [] + for old_name, new_names in new_by_old_package_names.items(): + if len(new_names) > 1: + duplicate_new_names_strings.append("%s --> %s" % (old_name, new_names)) + if duplicate_new_names_strings: + raise ProjectConfigurationError( + "Multiple packages map in build config %s (sections %s) map to the " + "same export name %s" % (self.name, self.enabled_sections, + ", ".join(duplicate_new_names_strings))) + return dict([(old, new[0]) + for old, new in new_by_old_package_names.items()]) + + +class ProjectConfiguration(object): + """Reads a project configuration JSON file. + + Attributes: + _packages: All packages parsed from the configuration. + _json: JSON dictionary this configuration is read from. + _packages_by_name: Dictionary of enabled PackageConfiguration instances + indexed by name. + _selected_sections: Export sections used to enable this project. + _version: Version of the project or None if no version was specified on + initialization. + _all_builds: All available build configuration instances. + _builds: Set of builds filtered by enabled export sections. + """ + + def __init__(self, export_configuration_dict, selected_sections, version): + """Parse an project configuration string. + + Args: + export_configuration_dict: Dictionary with the configuration to parse. + selected_sections: Set of enabled export section strings. This is used to + filter the loaded package configurations. + See PackageConfiguration.get_enabled(). + version: Version number of this project. Can be None if this is + not versioned. + + Raises: + ProjectConfigurationError: If any project data contains errors. + """ + self._json = export_configuration_dict + self._packages = [PackageConfiguration(self, package_json) + for package_json in safe_dict_get_value( + self._json, "packages", default_value=[])] + self._packages_by_name = collections.OrderedDict() + self._version = version + if FLAGS.enforce_semver: + if version is None: + raise ProjectConfigurationError( + "Version number is required to export to Unity Package Manager " + + "packages.") + elif not VALID_VERSION_RE.match(version): + raise ProjectConfigurationError( + "Invalid version '%s'. Should be 'major.minor.patch(-preview)' in " + "numbers" % version) + + self._all_builds = [BuildConfiguration(build_json) + for build_json in safe_dict_get_value( + self._json, "builds", default_value=[{}])] + self._builds = [] + self._selected_sections = None + + # pylint: disable=g-missing-from-attributes + self.selected_sections = selected_sections + + @property + def packages_by_name(self): + """Get the list of packages from the configuration indexed by name. + + Returns: + Dictionary of PackageConfiguration instances indexed by name. + """ + return collections.OrderedDict(self._packages_by_name) + + @property + def packages(self): + """Get the list of packages from the configuration. + + Returns: + List of PackageConfiguration instances. + """ + return list(self._packages_by_name.values()) + + @property + def version(self): + """Get the version of this project. + + Returns: + Version string or None if no version was specified on initialization. + """ + return self._version + + @property + def selected_sections(self): + """Get the sections used to initialize this instance. + + Returns: + Set of currently selected section strings enabled for this project. + """ + return set(self._selected_sections) + + @selected_sections.setter + def selected_sections(self, sections): + """Filter the set of packages and build configurations by export sections. + + This method changes the set of selected packages returned by the + packages and packages_by_name properties and build configurations by the + builds property. + + Args: + sections: Set of enabled export section strings. This is used to + filter the loaded package and build configurations. + + Raises: + ProjectConfigurationError: If any project data contains errors. + """ + # If the set of selected sections hasn't changed, do nothing. + sections = set(sections) + if sections == self._selected_sections: + return + + # Filter by enabled packages and bucket by name. + package_list_by_name = collections.OrderedDict() + for package in self._packages: + if package.get_enabled(sections): + packages_list = package_list_by_name.get(package.name, []) + packages_list.append(package) + package_list_by_name[package.name] = packages_list + else: + logging.debug("Package %s not enabled for sections %s (supports %s)", + package.name, sections, package.sections) + + # Find any duplicate packages. + duplicate_package_names = ( + [name for name, pkgs in package_list_by_name.items() if len(pkgs) > 1]) + if duplicate_package_names: + raise ProjectConfigurationError( + ("Package(s) %s configured to export to the same path with " + "enabled sections %s") % (sorted(duplicate_package_names), + sorted(sections))) + + previous_selected_sections = self._selected_sections + previous_packages_by_name = self._packages_by_name + try: + self._selected_sections = sections + self._packages_by_name = collections.OrderedDict([ + (name, pkgs[0]) for name, pkgs in package_list_by_name.items()]) + + # Check for circular references in includes. + for package in self.packages: + package.check_circular_references_in_includes([]) + except ProjectConfigurationError as error: + self._selected_sections = previous_selected_sections + self._packages_by_name = previous_packages_by_name + raise error + + # Create a filtered set of build configuration instances. + self._builds = [] + for build in self._all_builds: + if build.get_enabled(sections): + self._builds.append(build) + else: + logging.debug("Build %s not enabled for sections %s (supports %s)", + build.name, sections, build.sections) + + @property + def builds(self): + """Get selected build configurations for this project. + + Returns: + List of BuildConfiguration instances associated with this project. + """ + return list(self._builds) + + def write(self, + guid_database, + assets_dirs, + output_dir, + timestamp, + for_upm=False): + """Export all enabled packages using the project build configs. + + Args: + guid_database: GuidDatabase instance which contains GUIDs for each + exported asset. + assets_dirs: List of paths to directories containing assets to import. + This is combined with the path of each asset referenced by the project. + output_dir: Directory where to write the exported .unitypackage. + timestamp: Timestamp to apply to all packaged assets in each archive. If + this value is less than 0, the creation time of each input file is used + instead. + for_upm: Whether write for Unity Package Manager package. + + Returns: + Dictionary of BuildConfiguration instances indexed by the exported + package filename generated by the build config. + + Raises: + ProjectConfigurationError: If an error occurs while exporting the project. + MissingGuidsError: If any asset GUIDs are missing. + """ + selected_sections = self.selected_sections + build_by_package_filename = {} + + try: + build_sections_and_package_name_maps = [] + build_indices_by_package_filename = collections.defaultdict(list) + # Generate package name maps for each build configuration. + builds = self.builds + for build_index, build in enumerate(builds): + # Apply the build config. + build_sections = selected_sections.union(build.enabled_sections) + self.selected_sections = build_sections + + # Create the package filename mapping. + packages_by_name = self.packages_by_name + package_name_map = None + if for_upm: + package_name_to_output_filename = { + pkg.name: pkg.tarball_name + for pkg in packages_by_name.values() + if pkg.export_upm + } + else: + package_name_to_output_filename = { + pkg.name: pkg.name + for pkg in packages_by_name.values() + if pkg.export + } + package_name_map = build.create_package_name_map( + package_name_to_output_filename) + + # Store the build configuration. + build_sections_and_package_name_maps.append( + (build, build_sections, package_name_map)) + + # Map each filename to a set of build indices so it's possible to + # check for duplicates later. + for filename in package_name_map.values(): + build_indices_by_package_filename[filename].append(build_index) + + # Check for multiple build configurations exporting to the same filenames. + duplicate_filename_errors = [] + for filename, build_indices in build_indices_by_package_filename.items(): + if len(build_indices) > 1: + duplicate_filename_errors.append( + "%s exported by multiple builds %s" % ( + filename, + str([builds[index].name for index in build_indices]))) + if duplicate_filename_errors: + raise ProjectConfigurationError( + ("Detected multiple builds exporting packages to the same " + "file(s).\n" + "%s") % "\n".join(duplicate_filename_errors)) + + missing_guid_paths = [] + for build, build_sections, package_name_map in ( + build_sections_and_package_name_maps): + # Apply the build config. + logging.info("Building %s using sections %s", build.name, + str(build_sections)) + self.selected_sections = build_sections + packages_by_name = self.packages_by_name + + # Export all packages. + for package_name, package_filename in package_name_map.items(): + package = packages_by_name[package_name] + try: + if for_upm: + filename = package.write_upm( + guid_database, + assets_dirs, + output_dir, + timestamp, + package_filename=package_filename) + else: + filename = package.write( + guid_database, + assets_dirs, + output_dir, + timestamp, + package_filename=package_filename) + build_by_package_filename[filename] = build + except MissingGuidsError as missing_guids_error: + logging.error("Missing GUIDs while writing %s (%s)", + package.name, missing_guids_error.missing_guid_paths) + missing_guid_paths.extend(missing_guids_error.missing_guid_paths) + except DuplicateGuidsError as error: + raise ProjectConfigurationError( + "Duplicate GUIDs detecting while writing package %s to %s " + "(%s)" % (package.name, package_filename, str(error))) + + if missing_guid_paths: + raise MissingGuidsError(missing_guid_paths) + finally: + self.selected_sections = selected_sections + return build_by_package_filename + + +def read_json_file_into_ordered_dict(json_filename): + """Load JSON into an OrderedDict. + + By default the JSON parser reads data into a tree of Python dictionaries that + do not preserve order. This method reads JSON into a tree of OrderedDict + instances that preserves order and provides some compatibility with YAML. + + Args: + json_filename: File to read JSON from. + + Returns: + OrderedDict read from json_filename. + + Raises: + IOError: If the file can't be read. + ValueError: If there is a parse error while reading the file. + """ + json_dict = None + with open(json_filename, "rt", encoding='utf-8') as json_file: + try: + json_dict = json.loads(json_file.read(), + object_pairs_hook=collections.OrderedDict) + except ValueError as error: + raise ValueError("Failed to load JSON file %s (%s)" % (json_filename, + str(error))) + return json_dict + + +def create_directory_from_zip(zip_filename): + """Optionally creates a directory from a zip file. + + Args: + zip_filename: Zip file to extract. + + Returns: + Temporary directory containing the contents of the zip file. + + Raises: + IOError: If an error occurs while extracting the zip file. + """ + temporary_dir = tempfile.mkdtemp() + try: + logging.debug("Unpacking zip file %s to %s...", zip_filename, temporary_dir) + with zipfile.ZipFile(zip_filename, "r") as zip_file: + zip_file.extractall(temporary_dir) + logging.debug("Unpacked zip file %s to %s", zip_filename, temporary_dir) + except IOError as error: + shutil.rmtree(temporary_dir) + raise error + return temporary_dir + + +def write_zipfile(zip_filename, source_directory): + """Write the contents of a directory to a zip file. + + Args: + zip_filename: Zip filename to create. + source_directory: Directory to archive. + + Raises: + IOError: If an error occurs while archiving. + """ + if os.path.exists(zip_filename): + os.unlink(zip_filename) + + logging.debug("Archiving directory %s to %s...", source_directory, + zip_filename) + with zipfile.ZipFile(zip_filename, "w", allowZip64=True) as zip_file: + for current_root, _, filenames in os.walk(source_directory): + for filename in filenames: + fullpath = os.path.join(current_root, filename) + zip_file.write(fullpath, os.path.relpath(fullpath, source_directory)) + logging.debug("Archived directory %s to %s", source_directory, zip_filename) + + +def copy_files_to_dir(colon_separated_input_output_filenames, + output_dir): + """Copy any additional files to the output directory. + + Args: + colon_separated_input_output_filenames: List of colon separated strings + that specifies "input_filename:output_filename" paths. + input_filename is the path to copy. output_filename can be optionally + specified to copy input_filename into a different path under output_dir. + If output_filename isn't specified, this method uses the input_filename. + output_dir: Directory to copy the files into. + + Returns: + List of files copied to the output directory. + """ + copied_files = [] + for additional_file in colon_separated_input_output_filenames: + additional_file_args = additional_file.split(":") + input_filename = posix_path(additional_file_args[0]) + + # Get the output filename. + output_filename = input_filename + if len(additional_file_args) > 1: + output_filename = additional_file_args[1] + output_filename = os.path.splitdrive(output_filename)[1] + + # Remove the drive or root directory from the output filename. + if os.path.normpath(output_filename).startswith(os.path.sep): + output_filename = output_filename[len(os.path.sep):] + output_filename = posix_path(os.path.join(output_dir, output_filename)) + + # Copy the file to the output directory. + copy_and_set_rwx(input_filename, output_filename) + copied_files.append(posix_path(output_filename)) + return copied_files + + +def find_in_dirs(filename, directories): + """Find the file under given directories. + + Args: + filename: File/Folder name to find. + directories: List of directories to search for. + + Returns: + If found, return the combined path with directory and filename. + Return None, otherwise. + """ + for directory in directories: + candidate = os.path.join(directory, filename) + if os.path.exists(candidate): + return candidate + return None + + +def main(unused_argv): + """Builds Unity packages from sets of files within an assets folder. + + Args: + unused_argv: List of arguments passed on the command line after the command. + + Returns: + The exit code status; 1 for error, 0 for success. + """ + if not FLAGS.config_file: + print >> sys.stderr, main.__doc__ + return 1 + + enabled_sections = set(FLAGS.enabled_sections or []) + assets_dirs = list(FLAGS.assets_dir or []) + temporary_assets_dirs = [] + output_dir = FLAGS.output_dir + + try: + if FLAGS.output_zip: + output_dir = tempfile.mkdtemp() + elif not os.path.exists(output_dir): + try: + os.makedirs(output_dir) + except FileExistsError: + # This can be racy with other build scripts. + pass + elif not os.path.isdir(output_dir): + logging.error("output_dir %s is not a directory", output_dir) + return 1 + + if FLAGS.output_upm and not FLAGS.enforce_semver: + logging.error("enforce_semver flag should be True when output_upm flag " + "is set to True") + return 1 + + if FLAGS.assets_zip: + try: + for asset_zip_file in FLAGS.assets_zip: + temporary_assets_dirs.append( + create_directory_from_zip(asset_zip_file)) + except IOError as error: + logging.error("Failed to extract assets zip file %s (%s)", + FLAGS.assets_zip, str(error)) + return 1 + + if FLAGS.asset_file: + asset_files_dir = tempfile.mkdtemp() + temporary_assets_dirs.append(asset_files_dir) + try: + copy_files_to_dir(FLAGS.asset_file, asset_files_dir) + except IOError as error: + logging.error("Failed while copying input files (%s)", str(error)) + return 1 + + duplicate_guids_checker = DuplicateGuidsChecker() + guids_json = {} + if FLAGS.guids_file: + try: + guids_json = read_json_file_into_ordered_dict(FLAGS.guids_file) + except (IOError, ValueError) as error: + logging.error("Failed to load GUIDs JSON from %s (%s)", + FLAGS.guids_file, str(error)) + return 1 + guid_database = GuidDatabase(duplicate_guids_checker, guids_json, + FLAGS.plugins_version) + try: + duplicate_guids_checker.check_for_duplicates() + except DuplicateGuidsError as duplicate_guids_error: + logging.error(str(duplicate_guids_error)) + return 1 + + try: + project = ProjectConfiguration( + read_json_file_into_ordered_dict(FLAGS.config_file), enabled_sections, + FLAGS.plugins_version) + except (IOError, ValueError, ProjectConfigurationError) as error: + logging.error("Error while parsing project configuration from %s (%s)", + FLAGS.config_file, str(error)) + return 1 + + assets_dirs.extend(temporary_assets_dirs) + + if FLAGS.output_unitypackage: + try: + project.write( + guid_database, + assets_dirs, + output_dir, + FLAGS.timestamp, + for_upm=False) + except ProjectConfigurationError as error: + logging.error(str(error)) + return 1 + + # Copy any additional files to the output directory. + try: + copy_files_to_dir(FLAGS.additional_file or [], output_dir) + except IOError as error: + logging.error("Failed while copying additional output files (%s)", + str(error)) + return 1 + + # Generate tgz packages for Unity Package Manager + if FLAGS.output_upm: + try: + project.write( + guid_database, + assets_dirs, + output_dir, + FLAGS.timestamp, + for_upm=True) + except ProjectConfigurationError as error: + logging.error(str(error)) + return 1 + + # Generate the output zip file if one is requested. + if FLAGS.output_zip: + try: + write_zipfile(FLAGS.output_zip, output_dir) + except IOError as error: + logging.error("Failed when writing output zip file %s (%s)", + FLAGS.output_zip, str(error)) + return 1 + + finally: + for temporary_dir in temporary_assets_dirs: + shutil.rmtree(temporary_dir) + if output_dir != FLAGS.output_dir: + shutil.rmtree(output_dir) + + return 0 + +if __name__ == "__main__": + flags.mark_flag_as_required("config_file") + app.run(main) diff --git a/source/ExportUnityPackage/export_unity_package_test.py b/source/ExportUnityPackage/export_unity_package_test.py new file mode 100755 index 00000000..8e9eeb5e --- /dev/null +++ b/source/ExportUnityPackage/export_unity_package_test.py @@ -0,0 +1,3472 @@ +#!/usr/bin/python +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for Unity packager export_unity_package.py.""" + +import collections +import copy +import filecmp +import json +import os +import platform +import re +import shutil +import stat +import sys +import tarfile +import time +from absl import flags +from absl.testing import absltest + +# pylint: disable=C6204 +# pylint: disable=W0403 +sys.path.append(os.path.dirname(__file__)) +import export_unity_package +# pylint: enable=C6204 +# pylint: enable=W0403 + +FLAGS = flags.FLAGS + +# Location of test data. +TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), "test_data") + +try: + unicode("") # See whether unicode class is available (Python < 3) +except NameError: + unicode = str # pylint: disable=redefined-builtin,invalid-name + + +class DuplicateGuidsCheckerTest(absltest.TestCase): + """Test the DuplicateGuidsChecker class.""" + + def test_add_guid_and_path(self): + checker = export_unity_package.DuplicateGuidsChecker() + checker.add_guid_and_path("816270c2a2a348e59cb9b7b096a24f50", + "Firebase/Plugins/Firebase.Analytics.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.App.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.Auth.dll") + self.assertEqual( + set(["Firebase/Plugins/Firebase.Analytics.dll"]), + checker._paths_by_guid.get("816270c2a2a348e59cb9b7b096a24f50")) + self.assertEqual( + set(["Firebase/Plugins/Firebase.App.dll", + "Firebase/Plugins/Firebase.Auth.dll"]), + checker._paths_by_guid.get("7311924048bd457bac6d713576c952da")) + + def test_check_for_duplicate_guids(self): + """Ensure an exception is raised if multiple files are found per GUID.""" + checker = export_unity_package.DuplicateGuidsChecker() + checker.add_guid_and_path("816270c2a2a348e59cb9b7b096a24f50", + "Firebase/Plugins/Firebase.Analytics.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.App.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.Auth.dll") + with self.assertRaises(export_unity_package.DuplicateGuidsError) as ( + context): + checker.check_for_duplicates() + self.assertEqual( + {"7311924048bd457bac6d713576c952da": set([ + "Firebase/Plugins/Firebase.App.dll", + "Firebase/Plugins/Firebase.Auth.dll"])}, + context.exception.paths_by_guid) + + def test_check_for_duplicate_guids_no_duplicates(self): + """Ensure an exception is not raised if there are no duplicate GUIDs.""" + checker = export_unity_package.DuplicateGuidsChecker() + checker.add_guid_and_path("816270c2a2a348e59cb9b7b096a24f50", + "Firebase/Plugins/Firebase.Analytics.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.App.dll") + checker.add_guid_and_path("275bd6b96a28470986154b9a995e191c", + "Firebase/Plugins/Firebase.Auth.dll") + checker.check_for_duplicates() + + +def delete_temporary_directory_contents(): + """Delete the contents of the temporary directory.""" + # If the temporary directory is populated, delete everything in there. + directory = FLAGS.test_tmpdir + for path in os.listdir(directory): + full_path = os.path.join(directory, path) + if os.path.isdir(full_path): + shutil.rmtree(full_path) + else: + os.unlink(full_path) + + +class SafeDictGetValueTest(absltest.TestCase): + """Test reading from a dictionary with error checking.""" + + def test_safe_dict_get_value_empty(self): + """Get a value from an empty dictionary.""" + self.assertEqual(None, export_unity_package.safe_dict_get_value({}, "test")) + + def test_safe_dict_get_value_empty_with_default(self): + """Get a value from an empty dictionary with a default value.""" + self.assertEqual("hello", export_unity_package.safe_dict_get_value( + {}, "test", default_value="hello")) + + def test_safe_dict_get_value_any_class(self): + """Get a value from a dictionary of any class.""" + self.assertEqual("hello", export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test")) + self.assertEqual(["hello"], export_unity_package.safe_dict_get_value( + {"test": ["hello"]}, "test")) + + def test_safe_dict_get_value_matching_class(self): + """Get a value from a dictionary with a matching class.""" + self.assertEqual("hello", export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test", value_classes=[str])) + self.assertEqual(u"hello", export_unity_package.safe_dict_get_value( + {"test": u"hello"}, "test", value_classes=[str, unicode])) + self.assertEqual(["hello"], export_unity_package.safe_dict_get_value( + {"test": ["hello"]}, "test", value_classes=[list])) + + def test_safe_dict_get_value_matching_class_from_default(self): + """Get a value from a dictionary with a matching class.""" + self.assertEqual("hello", export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test", default_value="goodbye")) + self.assertEqual(u"hello", export_unity_package.safe_dict_get_value( + {"test": u"hello"}, "test", default_value="goodbye")) + self.assertEqual(["hello"], export_unity_package.safe_dict_get_value( + {"test": ["hello"]}, "test", default_value=["goodbye"])) + + def test_safe_dict_get_value_mismatching_class(self): + """Get a value from a dictionary with a mismatching class.""" + self.assertEqual(None, export_unity_package.safe_dict_get_value( + {"bish": ["hello"]}, "bish", value_classes=[str])) + self.assertEqual(None, export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test", value_classes=[list])) + + def test_safe_dict_get_value_mismatching_class_from_default(self): + """Get a value from a dictionary with a mismatching class.""" + self.assertEqual("goodbye", export_unity_package.safe_dict_get_value( + {"test": ["hello"]}, "test", default_value="goodbye")) + self.assertEqual(["goodbye"], export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test", default_value=["goodbye"])) + + +class SafeDictSetValueTest(absltest.TestCase): + """Test write to a dictionary with error checking.""" + + def test_safe_dict_set_value_non_dict_type(self): + """Set a value to a non-dict type.""" + # Set a value to None + self.assertEqual( + None, export_unity_package.safe_dict_set_value(None, "test", None)) + self.assertEqual( + None, export_unity_package.safe_dict_set_value(None, "test", "value")) + + # Set a value to a list + self.assertEqual([], + export_unity_package.safe_dict_set_value([], "test", None)) + self.assertEqual([], + export_unity_package.safe_dict_set_value([], "test", + "value")) + + # Set a value to an integer + self.assertEqual(1, + export_unity_package.safe_dict_set_value(1, "test", None)) + self.assertEqual( + 1, export_unity_package.safe_dict_set_value(1, "test", "value")) + + # Set a value to a string + self.assertEqual( + "node", export_unity_package.safe_dict_set_value("node", "test", None)) + self.assertEqual( + "node", + export_unity_package.safe_dict_set_value("node", "test", "value")) + + # Set a value to a set + self.assertEqual({"item1"}, + export_unity_package.safe_dict_set_value({"item1"}, "test", + None)) + self.assertEqual({"item1"}, + export_unity_package.safe_dict_set_value({"item1"}, "test", + "value")) + + def test_safe_dict_set_value_to_dict(self): + """Set a value to a dict.""" + empty_dict = {} + empty_dict_return = export_unity_package.safe_dict_set_value( + empty_dict, "test", "value") + empty_dict_expected = {"test": "value"} + self.assertEqual(empty_dict_expected, empty_dict) + self.assertEqual(empty_dict_expected, empty_dict_return) + + dict_with_other_key = {"some_key": "some_value"} + dict_with_other_key_return = export_unity_package.safe_dict_set_value( + dict_with_other_key, "test", "value") + dict_with_other_key_expected = {"some_key": "some_value", "test": "value"} + self.assertEqual(dict_with_other_key_expected, dict_with_other_key) + self.assertEqual(dict_with_other_key_expected, dict_with_other_key_return) + + dict_with_existing_key = {"test": "other_value"} + dict_with_existing_key_return = export_unity_package.safe_dict_set_value( + dict_with_existing_key, "test", "value") + dict_with_existing_key_expected = {"test": "value"} + self.assertEqual(dict_with_existing_key_expected, dict_with_existing_key) + self.assertEqual(dict_with_existing_key_expected, + dict_with_existing_key_return) + + def test_safe_dict_set_value_none_to_dict(self): + """Set None to a dict.""" + empty_dict = {} + empty_dict_return = export_unity_package.safe_dict_set_value( + empty_dict, "test", None) + self.assertEqual({}, empty_dict) + self.assertEqual({}, empty_dict_return) + + dict_with_other_key = {"some_key": "some_value"} + dict_with_other_key_return = export_unity_package.safe_dict_set_value( + dict_with_other_key, "test", None) + self.assertEqual({"some_key": "some_value"}, dict_with_other_key) + self.assertEqual({"some_key": "some_value"}, dict_with_other_key_return) + + dict_with_existing_key = {"test": "some_value"} + dict_with_existing_key_return = export_unity_package.safe_dict_set_value( + dict_with_existing_key, "test", None) + self.assertEqual({}, dict_with_existing_key) + self.assertEqual({}, dict_with_existing_key_return) + + +class GuidDatabaseTest(absltest.TestCase): + """Test reading GUIDs from .meta files and the GUID cache.""" + + def test_init_and_query_guids(self): + """Read GUIDs from JSON string.""" + database = export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), + { + "1.0.0": { + "A/B.cs": "ba9f9118207d46248936105077947525", + "C/D.dll": "84bde502cd4a4a98add4c90441d7e158" + }, + "1.2.3": { + "A/B.cs": "df2d7d4d6f6345609df6159fe468b61f" + } + }, + "1.2.3") + + self.assertEqual("df2d7d4d6f6345609df6159fe468b61f", + database.get_guid("A/B.cs")) + self.assertEqual("84bde502cd4a4a98add4c90441d7e158", + database.get_guid("C/D.dll")) + with self.assertRaises(export_unity_package.MissingGuidsError) as context: + unused_guid = database.get_guid("E/F.png") + self.assertEqual(["E/F.png"], context.exception.missing_guid_paths) + + def test_init_duplicate_guids(self): + """Initialize the GUID database with duplicate GUIDs.""" + duplicate_guids_checker = export_unity_package.DuplicateGuidsChecker() + unused_database = export_unity_package.GuidDatabase( + duplicate_guids_checker, + { + "1.0.0": { + "A/B.cs": "ba9f9118207d46248936105077947525", + "C/D.cs": "ba9f9118207d46248936105077947525" + } + }, + "1.0.0") + with self.assertRaises(export_unity_package.DuplicateGuidsError) as context: + duplicate_guids_checker.check_for_duplicates() + self.assertEqual({"ba9f9118207d46248936105077947525": set(["A/B.cs", + "C/D.cs"])}, + context.exception.paths_by_guid) + + def test_read_guids_from_assets(self): + """Read GUIDs from a tree of Unity assets.""" + database = export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), "", "1.0.0") + + with self.assertRaises(export_unity_package.MissingGuidsError) as context: + database.read_guids_from_assets([ + export_unity_package.Asset( + "PlayServicesResolver/Editor/Google.VersionHandler.dll", None, + collections.OrderedDict( + [("guid", "06f6f385a4ad409884857500a3c04441")])), + export_unity_package.Asset( + "Firebase/Plugins/Firebase.Analytics.dll", None, + collections.OrderedDict()), + export_unity_package.Asset( + "Firebase/Plugins/Firebase.App.dll", None, + collections.OrderedDict())]) + self.assertEqual(["Firebase/Plugins/Firebase.Analytics.dll", + "Firebase/Plugins/Firebase.App.dll"], + context.exception.missing_guid_paths) + self.assertEqual( + "06f6f385a4ad409884857500a3c04441", + database.get_guid( + "PlayServicesResolver/Editor/Google.VersionHandler.dll")) + + +class YamlSerializerTest(absltest.TestCase): + """Test reading / writing YAML.""" + + def test_read_yaml(self): + """Read YAML into an ordered dictionary.""" + serializer = export_unity_package.YamlSerializer() + yaml_dict = serializer.load("Object: [1, 2, 3]\n" + "AnotherObject:\n" + " someData: foo\n" + " moreData: bar\n" + " otherData:\n") + self.assertEqual([(0, "Object"), + (1, "AnotherObject")], + list(enumerate(yaml_dict))) + self.assertEqual([1, 2, 3], yaml_dict["Object"]) + self.assertEqual([(0, "someData"), (1, "moreData"), (2, "otherData")], + list(enumerate(yaml_dict["AnotherObject"]))) + self.assertEqual("foo", yaml_dict["AnotherObject"]["someData"]) + self.assertEqual("bar", yaml_dict["AnotherObject"]["moreData"]) + self.assertEqual(None, yaml_dict["AnotherObject"]["otherData"]) + + def test_write_yaml(self): + """Write YAML from an ordered dictionary.""" + serializer = export_unity_package.YamlSerializer() + object_list = [1, 2, 3] + yaml_string = serializer.dump(collections.OrderedDict( + [("Object", object_list), + ("AnotherObject", + collections.OrderedDict( + # Also, ensure unicode strings are serialized as plain strings. + [("someData", u"foo"), + ("moreData", "bar"), + ("otherData", None)])), + ("Object2", object_list)])) + self.assertEqual("Object:\n" + "- 1\n" + "- 2\n" + "- 3\n" + "AnotherObject:\n" + " someData: foo\n" + " moreData: bar\n" + " otherData:\n" + "Object2:\n" + "- 1\n" + "- 2\n" + "- 3\n", + yaml_string) + + +class MergeOrderedDictsTest(absltest.TestCase): + """Test merging ordered dictionaries.""" + + def test_merge_with_empty(self): + """"Merge a dictionary with an empty dictionary.""" + merge_into = collections.OrderedDict() + merge_from = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [1, 2, 3]), + ("c", "hello")])), + ("d", "bye")]) + self.assertEqual(merge_into, + export_unity_package.merge_ordered_dicts( + merge_into, + copy.deepcopy(merge_from))) + self.assertEqual(merge_from, merge_into) + + def test_merge_from_empty(self): + """"Merge an empty dictionary with a dictionary.""" + expected = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [1, 2, 3]), + ("c", "hello")])), + ("d", "bye")]) + merge_into = copy.deepcopy(expected) + merge_from = collections.OrderedDict() + self.assertEqual(merge_into, + export_unity_package.merge_ordered_dicts(merge_into, + merge_from)) + self.assertEqual(expected, merge_into) + + def test_merge_non_dictionaries(self): + """Try merging non-dictionary objects.""" + self.assertEqual(["do not merge"], + export_unity_package.merge_ordered_dicts(["do not merge"], + {"something"})) + self.assertEqual({"something"}, + export_unity_package.merge_ordered_dicts({"something"}, + ["do not merge"])) + + def test_merge_nested(self): + """Merge nested items in a dictionary with another dictionary.""" + merge_into = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [1, 2, 3]), + ("c", collections.OrderedDict( + [("hello", "goodbye"), + ("bonjour", "au revior")]))])), + ("d", "foo")]) + merge_from = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [4, 5, 6]), + ("c", collections.OrderedDict( + [("bonjour", "is french")]))]))]) + expected = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [4, 5, 6]), + ("c", collections.OrderedDict( + [("hello", "goodbye"), + ("bonjour", "is french")]))])), + ("d", "foo")]) + self.assertEqual(merge_into, + export_unity_package.merge_ordered_dicts( + merge_into, merge_from)) + self.assertEqual(expected, merge_into) + + def test_merge_first_second(self): + """Merge nested items in a list of dictionaries.""" + merge_into = collections.OrderedDict( + [("PluginImporter", collections.OrderedDict( + [("platformData", [ + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Any", None)])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Standalone", "Linux")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "x86")]))]))]), + ])])) + ]) + merge_from = collections.OrderedDict( + [("PluginImporter", collections.OrderedDict( + [("platformData", [ + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Any", None)])), + ("second", collections.OrderedDict( + [("enabled", 0), + ("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Standalone", "Windows")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "x86_64")]))]))]), + ])])) + ]) + expected = collections.OrderedDict( + [("PluginImporter", collections.OrderedDict( + [("platformData", [ + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Any", None)])), + ("second", collections.OrderedDict( + [("enabled", 0), + ("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Standalone", "Linux")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "x86")]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Standalone", "Windows")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "x86_64")]))]))]), + ])])) + ]) + self.assertEqual(merge_into, + export_unity_package.merge_ordered_dicts( + merge_into, merge_from)) + self.assertEqual(expected, merge_into) + + +class ConfigurationBlockTest(absltest.TestCase): + """Test parsing common configuration options from JSON.""" + + def test_block_with_sections(self): + """Test parsing a block with conditionally enabled sections.""" + block = export_unity_package.ConfigurationBlock(json.loads( + """ + { + "sections": ["foo", "bar"] + } + """)) + self.assertEqual(set(["bar", "foo"]), block.sections) + self.assertTrue(block.get_enabled(set(["foo"]))) + self.assertTrue(block.get_enabled(set(["bar"]))) + self.assertTrue(block.get_enabled(set(["bar", "baz"]))) + self.assertFalse(block.get_enabled(set(["baz"]))) + self.assertFalse(block.get_enabled(set())) + + def test_block_with_no_sections(self): + """Test parsing a block with no conditionally enabled sections.""" + block = export_unity_package.ConfigurationBlock(json.loads("{}")) + self.assertEqual(set(), block.sections) + self.assertTrue(block.get_enabled(set(["foo", "bar"]))) + self.assertTrue(block.get_enabled(set())) + + +class ProjectConfigurationTest(absltest.TestCase): + """Test parsing the project configuration from JSON.""" + + def setUp(self): + """Setup a common configuration for a subset of tests.""" + super(ProjectConfigurationTest, self).setUp() + self.old_flag = FLAGS.enforce_semver + # Turn off the enforce for most of the test + FLAGS.enforce_semver = False + + def tearDown(self): + """Clean up the temporary directory.""" + super(ProjectConfigurationTest, self).tearDown() + FLAGS.enforce_semver = self.old_flag + + def test_no_packages(self): + """Test parsing a project with no packages.""" + config = export_unity_package.ProjectConfiguration({"packages": []}, + set(), None) + self.assertEqual([], list(config.packages)) + self.assertEqual({}, config.packages_by_name) + self.assertEqual(set(), config.selected_sections) + self.assertEqual([""], [build.name for build in config.builds]) + + def test_no_packages_with_version(self): + """Test parsing a project with no packages with version.""" + config = export_unity_package.ProjectConfiguration({"packages": []}, set(), + "1.22.333") + self.assertEqual([], list(config.packages)) + self.assertEqual({}, config.packages_by_name) + self.assertEqual(set(), config.selected_sections) + self.assertEqual([""], [build.name for build in config.builds]) + + def test_version_no_enforce(self): + """Test parsing a project version with no SemVer enforcement.""" + FLAGS.enforce_semver = False + self.assertEqual(None, + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), None).version) + + self.assertEqual("", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "").version) + + self.assertEqual("1", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1").version) + + self.assertEqual("1.2", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1.2").version) + + self.assertEqual("1a.2.3", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1a.2.3").version) + + self.assertEqual(".2.3", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), ".2.3").version) + + def test_version_with_enforce(self): + """Test parsing a project version with SemVer enforcement.""" + FLAGS.enforce_semver = True + + self.assertEqual("0.0.0", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "0.0.0").version) + self.assertEqual("1.2.3", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1.2.3").version) + self.assertEqual("111.22.3333", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "111.22.3333").version) + self.assertEqual("1.0.0-preview", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1.0.0-preview").version) + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), None) + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), "") + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), "1") + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), "1.2") + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), + "1a.2.3") + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), ".2.3") + + def test_enabled_package(self): + """Test parsing a project with an enabled package.""" + config = export_unity_package.ProjectConfiguration( + {"packages": [{"name": "FirebaseApp.unitypackage"}]}, set(), None) + self.assertEqual(["FirebaseApp.unitypackage"], + list(config.packages_by_name.keys())) + self.assertLen(config.packages, 1) + self.assertEqual("FirebaseApp.unitypackage", config.packages[0].name) + self.assertEqual(set(), config.selected_sections) + + def test_enable_package_and_builds_by_section(self): + """Test parsing a project with conditionally enabled packages.""" + config = export_unity_package.ProjectConfiguration( + { + "packages": [ + {"name": "FirebaseApp.unitypackage"}, + {"name": "FirebaseAppExperimental.unitypackage", + "sections": ["experimental"]}, + {"name": "FirebaseAppTest.unitypackage", + "sections": ["debug", "test"]} + ], + "builds": [ + {"name": "production"}, + {"name": "experimental", "sections": ["experimental"]}, + {"name": "debug", "sections": ["debug", "test"]}, + ], + }, set(), None) + self.assertCountEqual(["FirebaseApp.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["production"], [b.name for b in config.builds]) + self.assertCountEqual(set(), config.selected_sections) + + config.selected_sections = set(["experimental"]) + self.assertCountEqual(["FirebaseApp.unitypackage", + "FirebaseAppExperimental.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["experimental", "production"], + [b.name for b in config.builds]) + self.assertCountEqual(set(["experimental"]), config.selected_sections) + + config.selected_sections = set(["debug"]) + self.assertCountEqual(["FirebaseApp.unitypackage", + "FirebaseAppTest.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["debug", "production"], + [b.name for b in config.builds]) + self.assertCountEqual(set(["debug"]), config.selected_sections) + + config.selected_sections = set(["test"]) + self.assertCountEqual(["FirebaseApp.unitypackage", + "FirebaseAppTest.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["debug", "production"], + [b.name for b in config.builds]) + self.assertCountEqual(set(["test"]), config.selected_sections) + + config.selected_sections = set(["experimental", "debug"]) + self.assertCountEqual(["FirebaseApp.unitypackage", + "FirebaseAppExperimental.unitypackage", + "FirebaseAppTest.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["debug", "experimental", "production"], + [b.name for b in config.builds]) + self.assertCountEqual(set(["debug", "experimental"]), + config.selected_sections) + + def test_duplicate_packages(self): + """Test parsing a project with duplicate packages.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage", "sections": ["public"]}, + {"name": "FirebaseApp.unitypackage", "sections": ["experimental"]}, + {"name": "FirebaseApp.unitypackage", "sections": ["debug", "test"]} + ] + } + + config = export_unity_package.ProjectConfiguration(config_json, + set(["public"]), None) + self.assertEqual(["FirebaseApp.unitypackage"], + list(config.packages_by_name.keys())) + + config = export_unity_package.ProjectConfiguration(config_json, + set(["experimental"]), + None) + self.assertEqual(["FirebaseApp.unitypackage"], + list(config.packages_by_name.keys())) + + # Enable conflicting packages for export. + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + config = export_unity_package.ProjectConfiguration( + config_json, set(["debug", "experimental"]), None) + self.assertRegexMatch( + str(context.exception), + [r"Package.*FirebaseApp\.unitypackage.*'debug', 'experimental'"]) + + def test_package_includes(self): + """Test parsing a project with packages that include each other.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage"}, + {"name": "Parse.unitypackage"}, + {"name": "FirebaseAnalytics.unitypackage", + "includes": ["FirebaseApp.unitypackage", "Parse.unitypackage"]}, + {"name": "PlayServicesResolver.unitypackage"} + ] + } + + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + self.assertCountEqual( + ["FirebaseApp.unitypackage", "Parse.unitypackage"], + [include.name for include in ( + config.packages_by_name[ + "FirebaseAnalytics.unitypackage"].includes)]) + self.assertEqual([], config.packages_by_name[ + "PlayServicesResolver.unitypackage"].includes) + + def test_package_include_circular_reference(self): + """Test parsing a project with circular reference in includes.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage", + "includes": ["CommonUtilities.unitypackage"]}, + {"name": "Parse.unitypackage", + "includes": ["PlayServicesResolver.unitypackage"]}, + {"name": "CommonUtilities.unitypackage", + "includes": ["Parse.unitypackage", + "FirebaseAnalytics.unitypackage"]}, + {"name": "FirebaseAnalytics.unitypackage", + "includes": ["FirebaseApp.unitypackage", "Parse.unitypackage"]}, + {"name": "PlayServicesResolver.unitypackage"} + ] + } + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration(config_json, set(), None) + + def test_package_include_missing(self): + """Test parsing a project with a missing reference in includes.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage", + "includes": ["CommonUtilities.unitypackage"]}, + {"name": "CommonUtilities.unitypackage", + "includes": ["Parse.unitypackage"]}, + {"name": "FirebaseAnalytics.unitypackage", + "includes": ["FirebaseApp.unitypackage"]} + ] + } + + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + export_unity_package.ProjectConfiguration(config_json, set(), None) + self.assertIn("Parse.unitypackage", str(context.exception)) + + def test_write_failure_due_to_duplicate_output_files(self): + """Test writing a project that maps multiple packages to the same files.""" + config = export_unity_package.ProjectConfiguration( + {"packages": [ + {"name": "FirebaseApp.unitypackage"}, + {"name": "FirebaseAnalytics.unitypackage", + "sections": ["experimental"]}, + {"name": "FirebaseAuth.unitypackage", + "sections": ["public"]}, + ], + "builds": [ + {"name": "public", "enabled_sections": ["public"]}, + {"name": "experimental", "enabled_sections": ["experimental"]}, + ]}, set(), None) + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + config.write( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), {}, "1.2.3"), + ["assets"], "output", 0) + self.assertRegexMatch( + str(context.exception), + ["FirebaseApp.unitypackage .* ['public', 'experimental']"]) + + def test_write_with_build_configs(self): + """Test writing the contents of a project with build configs.""" + + expected_guid_database = export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), {}, "1.2.3") + expected_assets_dirs = ["some/assets"] + expected_output_dir = "an/output/dir" + expected_timestamp = 123456789 + test_case_instance = self + + package_configuration_class = export_unity_package.PackageConfiguration + + class FakeWritePackageConfiguration( + export_unity_package.PackageConfiguration): + """PackageConfiguration with write()/write_upm() replaced with a fake.""" + + def __init__(self, project, package_json): + """Initialize this object with JSON package data. + + Args: + project: ProjectConfiguration instance this package was parsed from. + package_json: Package dictionary parsed from JSON project data. + + Raises: + ProjectConfigurationError: If the package has no name. + """ + # Restore the class before init so super() functions correctly. + export_unity_package.PackageConfiguration = package_configuration_class + super(FakeWritePackageConfiguration, self).__init__(project, + package_json) + export_unity_package.PackageConfiguration = ( + FakeWritePackageConfiguration) + + def write(self, guid_database, assets_dirs, output_dir, timestamp, + package_filename=None): + """Stubbed out implementation of write(). + + Args: + guid_database: Must equal expected_guid_database. + assets_dirs: Must equal expected_assets_dir. + output_dir: Must equal expected_output_dir. + timestamp: Must equal expected_timestamp. + package_filename: Returned by this method. + + Returns: + Value of package_filename. + """ + test_case_instance.assertEqual(expected_guid_database, guid_database) + test_case_instance.assertEqual(expected_assets_dirs, assets_dirs) + test_case_instance.assertEqual(expected_output_dir, output_dir) + test_case_instance.assertEqual(expected_timestamp, timestamp) + return package_filename + + def write_upm(self, + guid_database, + assets_dirs, + output_dir, + timestamp, + package_filename=None): + """Stubbed out implementation of write_upm(). + + Args: + guid_database: Must equal expected_guid_database. + assets_dirs: Must equal expected_assets_dir. + output_dir: Must equal expected_output_dir. + timestamp: Must equal expected_timestamp. + package_filename: Returned by this method. + + Returns: + Value of package_filename. + """ + test_case_instance.assertEqual(expected_guid_database, guid_database) + test_case_instance.assertEqual(expected_assets_dirs, assets_dirs) + test_case_instance.assertEqual(expected_output_dir, output_dir) + test_case_instance.assertEqual(expected_timestamp, timestamp) + return package_filename + + try: + export_unity_package.PackageConfiguration = FakeWritePackageConfiguration + config = export_unity_package.ProjectConfiguration( + { + "packages": [ + { + "name": "FirebaseApp.unitypackage", + "common_manifest": { + "name": "com.firebase.app" + }, + "export_upm": 1 + }, + { + "name": "FirebaseSpecialSauce.unitypackage", + "sections": ["experimental"], + "common_manifest": { + "name": "com.firebase.special_sauce" + }, + "export_upm": 1 + }, + ], + "builds": [ + { + "name": "public", + "enabled_sections": ["public"] + }, + { + "name": + "experimental", + "enabled_sections": ["experimental"], + "package_name_replacements": [ + { + "match": r"^(Firebase)([^.]+)(\.unitypackage)$", + "replacement": r"\1Secret\2Experiment\3" + }, + { + "match": r"^(com\.firebase\..+)(\.tgz)$", + "replacement": r"\1-preview\2" + }, + ] + }, + ] + }, set(), "1.2.3") + self.assertCountEqual( + [("FirebaseApp.unitypackage", "public"), + ("FirebaseSecretAppExperiment.unitypackage", "experimental"), + ("FirebaseSecretSpecialSauceExperiment.unitypackage", + "experimental")], + [(filename, build.name) for filename, build in config.write( + expected_guid_database, expected_assets_dirs, + expected_output_dir, expected_timestamp).items()]) + self.assertCountEqual( + [("com.firebase.app-1.2.3.tgz", "public"), + ("com.firebase.app-1.2.3-preview.tgz", "experimental"), + ("com.firebase.special_sauce-1.2.3-preview.tgz", "experimental")], + [(filename, build.name) for filename, build in config.write( + expected_guid_database, expected_assets_dirs, expected_output_dir, + expected_timestamp, for_upm=True).items()]) + finally: + export_unity_package.PackageConfiguration = package_configuration_class + + +class BuildConfigurationTest(absltest.TestCase): + """Test parsing a build configuration.""" + + def setUp(self): + """Setup a common configuration for a subset of tests.""" + super(BuildConfigurationTest, self).setUp() + self.config_json = { + "name": "Debug", + "sections": ["internal"], + "enabled_sections": ["debug", "test"], + "package_name_replacements": [ + {"match": "Firebase", + "replacement": "Fire"}, + {"match": r"([^.]+)\.unitypackage", + "replacement": r"\1Debug.unitypackage"}, + ], + } + + def test_create_empty(self): + """Create an empty build config.""" + config = export_unity_package.BuildConfiguration({}) + self.assertEqual(set(), config.sections) + self.assertEqual(set(), config.enabled_sections) + self.assertEqual("", config.name) + self.assertEqual([], config.package_name_replacements) + + def test_create(self): + """Create a named build config.""" + config = export_unity_package.BuildConfiguration(self.config_json) + self.assertEqual(set(["internal"]), config.sections) + self.assertEqual(set(["debug", "test"]), config.enabled_sections) + self.assertEqual("Debug", config.name) + self.assertEqual([("Firebase", "Fire"), + (r"([^.]+)\.unitypackage", + r"\1Debug.unitypackage")], + [(pattern_re.pattern, repl) + for pattern_re, repl in config.package_name_replacements]) + + def test_create_invalid_regex(self): + """Create a build config with an invalid regular expression.""" + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + export_unity_package.BuildConfiguration({ + "package_name_replacements": [{ + "match": r"(invalid", + "replacement": "ignored" + }] + }) + self.assertRegexMatch(str(context.exception), [r"\(invalid"]) + + def test_apply_package_name_replacements(self): + """Replace a package name with build config replacements.""" + config = export_unity_package.BuildConfiguration(self.config_json) + self.assertEqual("HotFireMagicExDebug.unitypackage", + config.apply_package_name_replacements( + "HotFirebaseMagicEx.unitypackage")) + + def test_apply_invalid_package_name_replacement(self): + """Try to apply an invalid replacement.""" + config = export_unity_package.BuildConfiguration({ + "package_name_replacements": [{ + "match": r"([^.]+)\.ext", + "replacement": r"\2something\1" + }] + }) + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + config.apply_package_name_replacements("test.ext") + self.assertRegexMatch(str(context.exception), [r"\\2something\\1"]) + + def test_create_package_name_map(self): + """Create a map of renamed package names.""" + config = export_unity_package.BuildConfiguration(self.config_json) + self.assertEqual( + { + "HotFirebaseMagicEx.unitypackage": + "HotFireMagicExDebug.unitypackage", + "FirebasePerf.unitypackage": + "FirePerformanceDebug.unitypackage" + }, + config.create_package_name_map({ + "HotFirebaseMagicEx.unitypackage": + "HotFirebaseMagicEx.unitypackage", + "FirebasePerf.unitypackage": + "FirebasePerformance.unitypackage" + })) + + +class PackageConfigurationTest(absltest.TestCase): + """Test parsing a package configuration.""" + + def setUp(self): + """Create an empty project config.""" + super(PackageConfigurationTest, self).setUp() + self.project = export_unity_package.ProjectConfiguration({}, set(), + "1.2.3") + # Metadata before write() is called. + self.expected_manifest_metadata_prebuild = copy.deepcopy( + export_unity_package.DEFAULT_METADATA_TEMPLATE) + self.expected_manifest_metadata_prebuild["labels"] = [ + "gvh", "gvh_manifest", "gvh_version-1.2.3", + "gvhp_manifestname-0NiceName", "gvhp_manifestname-1Test"] + # Metadata when write() is called. + self.expected_manifest_metadata = copy.deepcopy( + export_unity_package.DEFAULT_METADATA_TEMPLATE) + self.expected_manifest_metadata["labels"] = [ + "gvh", "gvh_manifest", "gvh_version-1.2.3", + "gvhp_exportpath-Foo/Bar/Test_version-1.2.3_manifest.txt", + "gvhp_manifestname-0Test"] + # Metadata before write_upm() is called. + self.expected_upm_manifest_metadata_prebuild = copy.deepcopy( + export_unity_package.DEFAULT_METADATA_TEMPLATE) + self.expected_upm_manifest_metadata_prebuild["labels"] = [ + "gupmr_manifest", "gvh", "gvh_version-1.2.3"] + # Metadata when write_upm() is called. + self.expected_upm_manifest_metadata = copy.deepcopy( + export_unity_package.DEFAULT_METADATA_TEMPLATE) + self.expected_upm_manifest_metadata["labels"] = [ + "gupmr_manifest", "gvh", "gvh_version-1.2.3", + "gvhp_exportpath-package.json"] + + def test_create_no_name(self): + """Create a package config with no name.""" + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.PackageConfiguration(self.project, {}) + + def test_create_defaults_no_version(self): + """Create a package config with default values.""" + old_flag = FLAGS.enforce_semver + FLAGS.enforce_semver = False + config = export_unity_package.PackageConfiguration( + export_unity_package.ProjectConfiguration({}, set(), None), + {"name": "Test.unitypackage"}) + self.assertEqual("Test.unitypackage", config.name) + self.assertTrue(config.export) + self.assertEqual(None, config.manifest_path) + self.assertEqual(None, config.manifest_filename) + self.assertEqual( + None, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_LEGACY)) + self.assertEqual( + None, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_UPM)) + self.assertEqual([], config.imports) + self.assertEqual([], config.includes) + self.assertEqual([], config.exclude_paths) + self.assertEqual(set([]), config.labels) + self.assertEqual(None, config.version) + self.assertEqual("Test", config.package_name) + self.assertEqual("Test", config.common_package_display_name) + self.assertEqual(None, config.common_manifest) + self.assertEqual(None, config.common_package_name) + self.assertEqual(None, config.common_package_description) + self.assertFalse(config.export_upm) + self.assertEqual(None, config.tarball_name) + self.assertEqual(None, config.upm_package_config) + self.assertEqual(None, config.upm_manifest) + + FLAGS.enforce_semver = old_flag + + def test_create_defaults(self): + """Create a package config with default values.""" + config = export_unity_package.PackageConfiguration( + self.project, {"name": "Test.unitypackage"}) + self.assertEqual("Test.unitypackage", config.name) + self.assertTrue(config.export) + self.assertEqual(None, config.manifest_path) + self.assertEqual(None, config.manifest_filename) + self.assertEqual( + None, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_LEGACY)) + self.assertEqual( + self.expected_upm_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_UPM)) + self.assertEqual([], config.imports) + self.assertEqual([], config.includes) + self.assertEqual([], config.exclude_paths) + self.assertEqual(set(["gvh", "gvh_version-1.2.3"]), config.labels) + self.assertEqual("1.2.3", config.version) + self.assertEqual("Test", config.package_name) + self.assertEqual("Test", config.common_package_display_name) + self.assertEqual(None, config.common_manifest) + self.assertEqual(None, config.common_package_name) + self.assertEqual(None, config.common_package_description) + self.assertFalse(config.export_upm) + self.assertEqual(None, config.tarball_name) + self.assertEqual(None, config.upm_package_config) + self.assertEqual(None, config.upm_manifest) + + def test_create_non_defaults(self): + """Create a package config with non-default values.""" + config = export_unity_package.PackageConfiguration( + self.project, { + "name": "Test.unitypackage", + "export": 0, + "manifest_path": "Foo/Bar", + "exclude_paths": ["a/b/c"], + "common_manifest": { + "name": "com.company.test", + "description": "Desc", + "display_name": "NiceName" + }, + "export_upm": 1, + "upm_package_config": { + "manifest": { + "unity": "2017.1" + } + } + }) + self.assertEqual("Test.unitypackage", config.name) + self.assertFalse(config.export) + self.assertEqual("Foo/Bar", config.manifest_path) + self.assertEqual("Foo/Bar/Test_version-1.2.3_manifest.txt", + config.manifest_filename) + self.assertEqual( + self.expected_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_LEGACY)) + self.assertEqual( + self.expected_upm_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_UPM)) + self.assertEqual([], config.imports) + self.assertEqual([], config.includes) + self.assertEqual([re.compile("a/b/c")], config.exclude_paths) + self.assertEqual(set(["gvh", "gvh_version-1.2.3"]), config.labels) + self.assertEqual("1.2.3", config.version) + self.assertEqual("Test", config.package_name) + self.assertEqual("NiceName", config.common_package_display_name) + self.assertEqual( + {"name": "com.company.test", "description": "Desc", + "display_name": "NiceName"}, + config.common_manifest) + self.assertEqual("com.company.test", config.common_package_name) + self.assertEqual("Desc", config.common_package_description) + self.assertTrue(config.export_upm) + self.assertEqual("com.company.test-1.2.3.tgz", config.tarball_name) + self.assertEqual({"manifest": { + "unity": "2017.1" + }}, config.upm_package_config) + self.assertEqual({"unity": "2017.1"}, config.upm_manifest) + + def test_create_non_defaults_desc_list(self): + """Create a package config with description as a list.""" + config = export_unity_package.PackageConfiguration( + self.project, { + "name": "Test.unitypackage", + "export": 0, + "manifest_path": "Foo/Bar", + "exclude_paths": ["a/b/c"], + "common_manifest": { + "name": "com.company.test", + "description": ["Desc", "123"], + "display_name": "NiceName" + }, + "export_upm": 1, + "upm_package_config": { + "manifest": { + "unity": "2017.1" + } + } + }) + self.assertEqual("Test.unitypackage", config.name) + self.assertFalse(config.export) + self.assertEqual("Foo/Bar", config.manifest_path) + self.assertEqual("Foo/Bar/Test_version-1.2.3_manifest.txt", + config.manifest_filename) + self.assertEqual( + self.expected_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_LEGACY)) + self.assertEqual( + self.expected_upm_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_UPM)) + self.assertEqual([], config.imports) + self.assertEqual([], config.includes) + self.assertEqual([re.compile("a/b/c")], config.exclude_paths) + self.assertEqual(set(["gvh", "gvh_version-1.2.3"]), config.labels) + self.assertEqual("1.2.3", config.version) + self.assertEqual("Test", config.package_name) + self.assertEqual("NiceName", config.common_package_display_name) + self.assertEqual( + {"name": "com.company.test", "description": ["Desc", "123"], + "display_name": "NiceName"}, + config.common_manifest) + self.assertEqual("com.company.test", config.common_package_name) + self.assertEqual("Desc123", config.common_package_description) + self.assertTrue(config.export_upm) + self.assertEqual("com.company.test-1.2.3.tgz", config.tarball_name) + self.assertEqual({"manifest": { + "unity": "2017.1" + }}, config.upm_package_config) + self.assertEqual({"unity": "2017.1"}, config.upm_manifest) + + def test_imports(self): + """Create a package configuration that includes other configs.""" + config = export_unity_package.PackageConfiguration( + self.project, + { + "name": "Test.unitypackage", + "imports": [ + { + "importer": "PluginImporter", + "paths": ["PlayServicesResolver/Editor/" + "Google.VersionHandler.*"] + }, + { + "importer": "DefaultImporter", + "paths": ["PlayServicesResolver/Editor/*_manifest*.txt"], + } + ] + }) + self.assertEqual( + [set(["PlayServicesResolver/Editor/Google.VersionHandler.*"]), + set(["PlayServicesResolver/Editor/*_manifest*.txt"])], + [asset_config.paths for asset_config in config.imports]) + + def test_write_manifest(self): + """Write a package manifest.""" + config = export_unity_package.PackageConfiguration( + self.project, + {"name": "Test.unitypackage", + "export": 0, + "manifest_path": "Foo/Bar"}) + output_directory = os.path.join(FLAGS.test_tmpdir, "manifest") + try: + os.makedirs(output_directory) + manifest_asset = config.write_manifest( + output_directory, + [export_unity_package.Asset( + "zebra/head.fbx", None, + export_unity_package.DEFAULT_METADATA_TEMPLATE), + export_unity_package.Asset( + "moose/tail.png", None, + export_unity_package.DEFAULT_METADATA_TEMPLATE), + export_unity_package.Asset( + "bear/paw.fplmesh", None, + export_unity_package.DEFAULT_METADATA_TEMPLATE)]) + self.assertEqual("Foo/Bar/Test_version-1.2.3_manifest.txt", + manifest_asset.filename) + self.assertEqual("Foo/Bar/Test_version-1.2.3_manifest.txt", + manifest_asset.filename_guid_lookup) + manifest_absolute_path = export_unity_package.posix_path(os.path.join( + output_directory, "Foo/Bar/Test_version-1.2.3_manifest.txt")) + self.assertEqual(manifest_absolute_path, + manifest_asset.filename_absolute) + self.assertFalse(manifest_asset.is_folder) + self.assertEqual(self.expected_manifest_metadata, + manifest_asset.importer_metadata) + with open(manifest_absolute_path, "rt") as manifest: + self.assertEqual( + "Assets/bear/paw.fplmesh\n" + "Assets/moose/tail.png\n" + "Assets/zebra/head.fbx\n", + manifest.read()) + finally: + shutil.rmtree(output_directory) + + def test_write_upm_manifest(self): + """Write a package manifest for UPM package.""" + config = export_unity_package.PackageConfiguration( + self.project, { + "name": "Test.unitypackage", + "export": 0, + "manifest_path": "Foo/Bar", + "common_manifest": { + "name": "com.company.test", + "display_name": "Test", + "description": "Test description", + "keywords": ["test keyword"], + "author": { + "name": "company", + "email": "someone@email.com", + "url": "https://test.company.com/" + } + }, + "export_upm": 1, + "upm_package_config": { + "manifest": { + "unity": "2017.1", + "dependencies": { + "com.company.dep": "1.6.8" + } + } + } + }) + expected_manifest = { + "name": "com.company.test", + "version": "1.2.3", + "displayName": "Test", + "description": "Test description", + "keywords": ["test keyword"], + "author": { + "name": "company", + "email": "someone@email.com", + "url": "https://test.company.com/" + }, + "unity": "2017.1", + "dependencies": { + "com.company.dep": "1.6.8" + } + } + output_directory = os.path.join(FLAGS.test_tmpdir, "manifest") + try: + os.makedirs(output_directory) + manifest_asset = config.write_upm_manifest(output_directory) + self.assertEqual("package.json", manifest_asset.filename) + self.assertEqual("com.company.test/package.json", + manifest_asset.filename_guid_lookup) + manifest_absolute_path = export_unity_package.posix_path( + os.path.join(output_directory, "package.json")) + self.assertEqual(manifest_absolute_path, manifest_asset.filename_absolute) + self.assertFalse(manifest_asset.is_folder) + self.assertEqual(self.expected_upm_manifest_metadata, + manifest_asset.importer_metadata) + with open(manifest_absolute_path, "rt") as manifest: + self.assertEqual(expected_manifest, json.loads(manifest.read())) + finally: + shutil.rmtree(output_directory) + + +class AssetTest(absltest.TestCase): + """Test the Asset class.""" + + def setUp(self): + """Create expected metadata.""" + super(AssetTest, self).setUp() + self.default_metadata = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.default_metadata["labels"] = ["gvh", "gvh_version-1.2.3"] + self.staging_dir = os.path.join(FLAGS.test_tmpdir, "staging") + os.makedirs(self.staging_dir) + + self.assets_dir = os.path.join(TEST_DATA_PATH, "Assets") + self.asset_list = [ + export_unity_package.Asset("bar", "bar", self.default_metadata), + export_unity_package.Asset("foo/bar", "foo/bar", self.default_metadata), + ] + + def tearDown(self): + """Clean up the temporary directory.""" + super(AssetTest, self).tearDown() + delete_temporary_directory_contents() + + def test_init(self): + """Initialize an Asset instance.""" + asset = export_unity_package.Asset("libFooBar.so", "a/path/to/libFooBar.so", + collections.OrderedDict()) + self.assertEqual("libFooBar.so", asset.filename) + self.assertEqual("a/path/to/libFooBar.so", asset.filename_absolute) + self.assertEqual( + collections.OrderedDict([("labels", ["gvhp_exportpath-libFooBar.so"])]), + asset.importer_metadata) + self.assertEqual("libFooBar.so", asset.filename_guid_lookup) + self.assertEqual(False, asset.is_folder) + self.assertEqual(collections.OrderedDict(), + asset.importer_metadata_original) + + def test_init_override_guid_lookup(self): + """Initialize an Asset instance with overridden GUID Lookup filename.""" + asset = export_unity_package.Asset( + "libFooBar.so", + "a/path/to/libFooBar.so", + collections.OrderedDict(), + filename_guid_lookup="foo.bar/libFooBar.so") + self.assertEqual("libFooBar.so", asset.filename) + self.assertEqual("a/path/to/libFooBar.so", asset.filename_absolute) + self.assertEqual("foo.bar/libFooBar.so", asset.filename_guid_lookup) + self.assertEqual(False, asset.is_folder) + self.assertEqual( + collections.OrderedDict([("labels", ["gvhp_exportpath-libFooBar.so"])]), + asset.importer_metadata) + self.assertEqual(collections.OrderedDict(), + asset.importer_metadata_original) + + def test_init_folder(self): + """Initialize an folder Asset instance.""" + asset = export_unity_package.Asset( + "foo/bar", "foo/bar", collections.OrderedDict(), is_folder=True) + self.assertEqual("foo/bar", asset.filename) + self.assertEqual("foo/bar", asset.filename_absolute) + self.assertEqual("foo/bar", asset.filename_guid_lookup) + self.assertEqual(True, asset.is_folder) + self.assertEqual( + collections.OrderedDict([("labels", ["gvhp_exportpath-foo/bar"])]), + asset.importer_metadata) + self.assertEqual(collections.OrderedDict(), + asset.importer_metadata_original) + + def test_repr(self): + """Convert an asset to a string.""" + self.assertRegexMatch( + repr(export_unity_package.Asset("libFooBar.so", None, + self.default_metadata)), + ["filename=libFooBar.so metadata=.*gvh_version-1.2.3.*"]) + + def test_importer_metadata(self): + """Generate importer metadata for a path.""" + expected_metadata = copy.deepcopy(self.default_metadata) + expected_metadata["labels"] = sorted( + expected_metadata["labels"] + + ["gvhp_exportpath-Plugins/noarch/libFooBar.so"]) + asset = export_unity_package.Asset("Plugins/noarch/libFooBar.so", None, + self.default_metadata) + self.assertEqual(self.default_metadata, asset.importer_metadata_original) + self.assertEqual(expected_metadata, asset.importer_metadata) + + metadata_linuxlibname = copy.deepcopy(self.default_metadata) + metadata_linuxlibname["labels"] = sorted(metadata_linuxlibname["labels"] + + ["gvh_linuxlibname-FooBar"]) + + expected_metadata = copy.deepcopy(metadata_linuxlibname) + expected_metadata["labels"] = sorted( + expected_metadata["labels"] + + ["gvhp_exportpath-Plugins/x86/libFooBar.so"]) + asset = export_unity_package.Asset("Plugins/x86/libFooBar.so", None, + metadata_linuxlibname) + self.assertEqual(metadata_linuxlibname, asset.importer_metadata_original) + self.assertEqual(expected_metadata, asset.importer_metadata) + + expected_metadata = copy.deepcopy(metadata_linuxlibname) + expected_metadata["labels"] = sorted( + expected_metadata["labels"] + + ["gvhp_exportpath-Plugins/x86_64/libFooBar.so"]) + asset = export_unity_package.Asset("Plugins/x86_64/libFooBar.so", None, + metadata_linuxlibname) + self.assertEqual(metadata_linuxlibname, asset.importer_metadata_original) + self.assertEqual(expected_metadata, asset.importer_metadata) + + def test_add_labels_to_metadata(self): + """Add labels to importer metadata.""" + metadata = export_unity_package.Asset.add_labels_to_metadata( + self.default_metadata, set(["foo", "bar"])) + self.assertEqual( + ["bar", "foo", "gvh", "gvh_version-1.2.3"], + metadata["labels"]) + self.assertEqual(metadata, self.default_metadata) + + def test_add_labels_to_metadata_empty(self): + """Add an empty list of labels to empty metadata.""" + metadata = export_unity_package.Asset.add_labels_to_metadata( + collections.OrderedDict([("labels", [])]), set([])) + self.assertEqual(None, metadata.get("labels")) + + def test_disable_unsupported_platforms(self): + """Disable unsupported platforms for shared libraries.""" + all_platforms_enabled = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = all_platforms_enabled["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 1 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + platform_data["Editor"]["enabled"] = 1 + platform_data["Editor"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel"]["settings"]["CPU"] = "x86" + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXIntel64"]["settings"]["CPU"] = "x86_64" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux"]["settings"]["CPU"] = "x86" + platform_data["Linux64"]["enabled"] = 1 + platform_data["Linux64"]["settings"]["CPU"] = "x86_64" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win"]["enabled"] = 1 + platform_data["Win"]["settings"]["CPU"] = "x86" + platform_data["Win64"]["enabled"] = 1 + platform_data["Win64"]["settings"]["CPU"] = "x86_64" + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 0 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + platform_data["Editor"]["enabled"] = 1 + platform_data["Editor"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel"]["settings"]["CPU"] = "x86" + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXIntel64"]["settings"]["CPU"] = "x86_64" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + filename = "Plugins/x86/Foo/bar.bundle" + metadata = export_unity_package.Asset.disable_unsupported_platforms( + copy.deepcopy(all_platforms_enabled), filename) + self.assertEqual(expected_metadata, metadata) + expected_metadata["labels"] = [ + "gvhp_exportpath-Plugins/x86/Foo/bar.bundle"] + self.assertEqual(expected_metadata, export_unity_package.Asset( + filename, None, all_platforms_enabled).importer_metadata) + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 0 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + platform_data["Editor"]["enabled"] = 1 + platform_data["Editor"]["settings"]["CPU"] = "AnyCPU" + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux"]["settings"]["CPU"] = "x86" + platform_data["Linux64"]["enabled"] = 1 + platform_data["Linux64"]["settings"]["CPU"] = "x86_64" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + filename = "Assets/Plugins/x86_64/Foo/bar.so" + metadata = export_unity_package.Asset.disable_unsupported_platforms( + copy.deepcopy(all_platforms_enabled), filename) + self.assertEqual(expected_metadata, metadata) + expected_metadata["labels"] = [ + "gvhp_exportpath-Assets/Plugins/x86_64/Foo/bar.so"] + self.assertEqual(expected_metadata, export_unity_package.Asset( + filename, None, all_platforms_enabled).importer_metadata) + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 0 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + platform_data["Editor"]["enabled"] = 1 + platform_data["Editor"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win"]["enabled"] = 1 + platform_data["Win"]["settings"]["CPU"] = "x86" + platform_data["Win64"]["enabled"] = 1 + platform_data["Win64"]["settings"]["CPU"] = "x86_64" + filename = "A/Path/To/Assets/Plugins/x86_64/Foo/bar.dll" + metadata = export_unity_package.Asset.disable_unsupported_platforms( + copy.deepcopy(all_platforms_enabled), filename) + self.assertEqual(expected_metadata, metadata) + expected_metadata["labels"] = [ + "gvhp_exportpath-A/Path/To/Assets/Plugins/x86_64/Foo/bar.dll"] + self.assertEqual(expected_metadata, export_unity_package.Asset( + filename, None, all_platforms_enabled).importer_metadata) + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 0 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + filename = "Plugins/Plugin.dll" + metadata = export_unity_package.Asset.disable_unsupported_platforms( + copy.deepcopy(expected_metadata), filename) + self.assertEqual(expected_metadata, metadata) + expected_metadata["labels"] = ["gvhp_exportpath-Plugins/Plugin.dll"] + self.assertEqual(expected_metadata, export_unity_package.Asset( + filename, None, expected_metadata).importer_metadata) + + def test_platform_data_get_entry(self): + """Retrieve an entry from a PluginImporter.platformData list.""" + unity_5_6_format = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", [ + collections.OrderedDict([ + ("data", collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Standalone", "Linux")])), + ("second", collections.OrderedDict([ + ("enabled", 1)]))]) + )]) + ]) + ])) + ]) + first, second = export_unity_package.Asset.platform_data_get_entry( + unity_5_6_format["PluginImporter"]["platformData"][0]) + self.assertEqual(collections.OrderedDict([("Standalone", "Linux")]), first) + self.assertEqual(collections.OrderedDict([("enabled", 1)]), second) + + unity_2017_format = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", [ + collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Standalone", "Linux")])), + ("second", collections.OrderedDict([ + ("enabled", 1)]))]) + ]) + ])) + ]) + first, second = export_unity_package.Asset.platform_data_get_entry( + unity_2017_format["PluginImporter"]["platformData"][0]) + self.assertEqual(collections.OrderedDict([("Standalone", "Linux")]), first) + self.assertEqual(collections.OrderedDict([("enabled", 1)]), second) + + def test_set_cpu_for_desktop_platforms_serializationv1(self): + """Set CPU field for enabled desktop platforms in v1 metadata format.""" + linux_enabled = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + linux_enabled["PluginImporter"]["platformData"]["Linux"]["enabled"] = 1 + expected_metadata = copy.deepcopy(linux_enabled) + expected_metadata["PluginImporter"]["platformData"]["Linux"]["settings"][ + "CPU"] = "x86" + linux_enabled_with_cpu = ( + export_unity_package.Asset.set_cpu_for_desktop_platforms(linux_enabled)) + self.assertEqual(expected_metadata, linux_enabled_with_cpu) + + def test_set_cpu_for_desktop_platforms_serializationv2(self): + """Set CPU field for enabled desktop platforms in v2 metadata format.""" + linux_enabled = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", [ + collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Standalone", "Linux")])), + ("second", collections.OrderedDict([ + ("enabled", 1)]))]) + ]) + ])) + ]) + expected_metadata = copy.deepcopy(linux_enabled) + expected_metadata["PluginImporter"]["platformData"][0]["second"][ + "settings"] = collections.OrderedDict([("CPU", "x86")]) + linux_enabled_with_cpu = ( + export_unity_package.Asset.set_cpu_for_desktop_platforms(linux_enabled)) + self.assertEqual(expected_metadata, linux_enabled_with_cpu) + + def test_set_cpu_for_android_serializationv1(self): + """Set CPU field for the enabled Android platform in v1 metadata format.""" + android_enabled = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + android_enabled["PluginImporter"]["platformData"]["Android"]["enabled"] = 1 + expected_metadata = copy.deepcopy(android_enabled) + expected_metadata["PluginImporter"]["platformData"]["Android"]["settings"][ + "CPU"] = "ARMv7" + android_enabled_with_cpu = ( + export_unity_package.Asset.set_cpu_for_android(android_enabled, "ARMv7")) + self.assertEqual(expected_metadata, android_enabled_with_cpu) + + def test_set_cpu_for_android_serializationv2(self): + """Set CPU field for the enabled Android platform in v2 metadata format.""" + android_enabled = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", [ + collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Android", "Android")])), + ("second", collections.OrderedDict([ + ("enabled", 1)]))]) + ]) + ])) + ]) + expected_metadata = copy.deepcopy(android_enabled) + expected_metadata["PluginImporter"]["platformData"][0]["second"][ + "settings"] = collections.OrderedDict([("CPU", "ARMv7")]) + android_enabled_with_cpu = ( + export_unity_package.Asset.set_cpu_for_android(android_enabled, "ARMv7")) + self.assertEqual(expected_metadata, android_enabled_with_cpu) + + def test_apply_any_platform_selection_serializationv1(self): + """Modify v1 importer metadata to enable all platforms.""" + # Enable all platforms. + any_platform_enabled = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = any_platform_enabled["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 1 + # Remove some platforms, these should be re-added to the metadata and + # enabled. + del platform_data["Win"] + del platform_data["Win64"] + del platform_data["WindowsStoreApps"] + del platform_data["iOS"] + del platform_data["tvOS"] + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Android"]["enabled"] = 1 + platform_data["Any"]["enabled"] = 1 + platform_data["Editor"]["enabled"] = 1 + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux64"]["enabled"] = 1 + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["Web"]["enabled"] = 1 + platform_data["WebStreamed"]["enabled"] = 1 + platform_data["Win"]["enabled"] = 1 + platform_data["Win64"]["enabled"] = 1 + platform_data["WindowsStoreApps"]["enabled"] = 1 + platform_data["iOS"]["enabled"] = 1 + platform_data["tvOS"]["enabled"] = 1 + + all_platforms_enabled = ( + export_unity_package.Asset.apply_any_platform_selection( + any_platform_enabled)) + self.assertEqual(expected_metadata, all_platforms_enabled) + + # If Any is disabled, do not modify any platform states. + unmodified_metadata = ( + export_unity_package.Asset.apply_any_platform_selection( + copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE))) + self.assertEqual(export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE, + unmodified_metadata) + + def test_apply_any_platform_selection_serializationv2(self): + """Modify v2 importer metadata to enable all platforms.""" + def create_default_platform_data(target, name): + """Create a platform data entry. + + Args: + target: Name of the build target. + name: Name of the platform. + + Returns: + Ordered dictionary with the platformData metadata entry. + """ + return collections.OrderedDict([ + ("first", collections.OrderedDict([(target, name)])), + ("second", collections.OrderedDict([("enabled", 0)]))]) + + all_platform_data = [create_default_platform_data("Any", None)] + for platform in export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE[ + "PluginImporter"]["platformData"]: + if platform != "Any": + all_platform_data.append(create_default_platform_data( + export_unity_package.PLATFORM_TARGET_BY_PLATFORM[platform], + platform)) + + all_disabled = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", copy.deepcopy(all_platform_data)), + ])) + ]) + + # If "Any" isn't enabled, make sure the data isn't modified. + unmodified_metadata = ( + export_unity_package.Asset.apply_any_platform_selection( + copy.deepcopy(all_disabled))) + self.assertEqual(all_disabled, unmodified_metadata) + + # Enable the "Any" platform (first in the list) and remove some platforms + # from the list then verify all platforms are enabled. + any_enabled = copy.deepcopy(all_disabled) + platform_data = any_enabled["PluginImporter"]["platformData"] + platform_data[0]["second"]["enabled"] = 1 + any_enabled["PluginImporter"]["platformData"] = platform_data[:-3] + + all_enabled = export_unity_package.Asset.apply_any_platform_selection( + any_enabled) + expected_metadata = copy.deepcopy(all_disabled) + for config in expected_metadata["PluginImporter"]["platformData"]: + config["second"]["enabled"] = 1 + self.assertEqual(expected_metadata, all_enabled) + + def test_create_metadata(self): + """Test Asset.create_metadata().""" + asset = export_unity_package.Asset( + "Google.VersionHandler.dll", + os.path.join(self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll"), + self.default_metadata) + + expected_metadata_path = os.path.join(self.staging_dir, + "Google.VersionHandler.dll.meta") + + asset.create_metadata(expected_metadata_path, + "06f6f385a4ad409884857500a3c04441", + timestamp=123456789) + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 06f6f385a4ad409884857500a3c04441\n" + "labels:\n" + "- gvh\n" + "- gvh_version-1.2.3\n" + "- gvhp_exportpath-Google.VersionHandler.dll\n" + "timeCreated: 123456789\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + def test_create_metadata_folder(self): + """Test Asset.create_metadata().""" + asset = export_unity_package.Asset( + "foo/bar", "foo/bar", self.default_metadata, is_folder=True) + + expected_metadata_path = os.path.join(self.staging_dir, + "Google.VersionHandler.dll.meta") + + asset.create_metadata(expected_metadata_path, + "5187848eea9240faaec2deb7d66107db") + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 5187848eea9240faaec2deb7d66107db\n" + "timeCreated: 0\n" + "folderAsset: true\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + def test_write(self): + """Test Asset.write().""" + asset_path = os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll") + + asset = export_unity_package.Asset("foo/bar/Google.VersionHandler.dll", + asset_path, self.default_metadata) + + expected_asset_path = os.path.join( + self.staging_dir, "06f6f385a4ad409884857500a3c04441/asset") + expected_metadata_path = os.path.join( + self.staging_dir, "06f6f385a4ad409884857500a3c04441/asset.meta") + expected_pathname_path = os.path.join( + self.staging_dir, "06f6f385a4ad409884857500a3c04441/pathname") + + asset.write(self.staging_dir, "06f6f385a4ad409884857500a3c04441", + timestamp=123456789) + + # Compare asset + self.assertTrue(filecmp.cmp(asset_path, expected_asset_path)) + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 06f6f385a4ad409884857500a3c04441\n" + "labels:\n" + "- gvh\n" + "- gvh_version-1.2.3\n" + "- gvhp_exportpath-foo/bar/Google.VersionHandler.dll\n" + "timeCreated: 123456789\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + # Check pathname file + with open(expected_pathname_path) as (pathname): + self.assertEqual("Assets/foo/bar/Google.VersionHandler.dll", + pathname.read()) + + def test_write_folder(self): + """Test Asset.write() for folder asset.""" + + asset = export_unity_package.Asset( + "foo/bar", "foo/bar", self.default_metadata, is_folder=True) + + output_dir = asset.write(self.staging_dir, + "5187848eea9240faaec2deb7d66107db") + + # Should do nothing when writing a folder asset. + self.assertIsNone(output_dir) + + def test_write_upm(self): + """Test Asset.write_upm().""" + asset_path = os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll") + + asset = export_unity_package.Asset("foo/bar/Google.VersionHandler.dll", + asset_path, self.default_metadata) + + expected_asset_path = os.path.join( + self.staging_dir, "package/foo/bar/Google.VersionHandler.dll") + expected_metadata_path = os.path.join( + self.staging_dir, "package/foo/bar/Google.VersionHandler.dll.meta") + + asset.write_upm(self.staging_dir, "06f6f385a4ad409884857500a3c04441", + timestamp=123456789) + + # Compare asset + self.assertTrue(filecmp.cmp(asset_path, expected_asset_path)) + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 06f6f385a4ad409884857500a3c04441\n" + "labels:\n" + "- gvh\n" + "- gvh_version-1.2.3\n" + "- gvhp_exportpath-foo/bar/Google.VersionHandler.dll\n" + "timeCreated: 123456789\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + def test_write_upm_folder(self): + """Test Asset.write_upm() for folder asset.""" + asset = export_unity_package.Asset( + "foo/bar", "foo/bar", self.default_metadata, is_folder=True) + + expected_folder_path = os.path.join(self.staging_dir, "package/foo/bar") + expected_metadata_path = os.path.join(self.staging_dir, + "package/foo/bar.meta") + + asset.write_upm(self.staging_dir, "5187848eea9240faaec2deb7d66107db") + + # Compare asset + self.assertTrue(os.path.isdir(expected_folder_path)) + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 5187848eea9240faaec2deb7d66107db\n" + "timeCreated: 0\n" + "folderAsset: true\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + +class AssetConfigurationTest(absltest.TestCase): + """Test the AssetConfiguration class.""" + + def setUp(self): + """Create an empty package config and expected metadata.""" + super(AssetConfigurationTest, self).setUp() + self.package = export_unity_package.PackageConfiguration( + export_unity_package.ProjectConfiguration({}, set(), "1.2.3"), + {"name": "Test.unitypackage", "manifest_path": "Foo/Bar"}) + self.labels = set(["gvh", "gvh_version-1.2.3"]) + self.default_metadata = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.default_metadata["labels"] = sorted(list(self.labels)) + self.plugin_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + self.plugin_metadata["labels"] = sorted(list(self.labels)) + self.override_metadata = { + "PluginImporter": { + "platformData": { + "Editor": { + "enabled": 1 + } + } + } + } + + def test_create_empty(self): + """Create an empty config.""" + config = export_unity_package.AssetConfiguration(self.package, {}) + self.assertEqual(self.default_metadata, config.importer_metadata) + self.assertEqual(set(self.labels), config.labels) + self.assertEqual(set(), config.paths) + self.assertEqual({}, config.override_metadata) + + def test_labels(self): + """Test labels property.""" + self.assertEqual( + self.labels.union(set(["fun-label"])), + export_unity_package.AssetConfiguration(self.package, { + "importer": "DefaultImporter", + "labels": ["fun-label"] + }).labels) + + def test_paths(self): + """Test paths property.""" + self.assertEqual( + set([ + "foo/bar", + "bar", + ]), + export_unity_package.AssetConfiguration(self.package, { + "importer": "DefaultImporter", + "paths": [ + "bar", + "foo/bar", + ] + }).paths) + + def test_override_metadata(self): + """Test override_metadata property.""" + self.assertEqual( + self.override_metadata, + export_unity_package.AssetConfiguration( + self.package, { + "importer": "DefaultImporter", + "override_metadata": self.override_metadata + }).override_metadata) + + def test_override_metadata_upm(self): + """Test override_metadata_upm property.""" + self.assertEqual( + self.override_metadata, + export_unity_package.AssetConfiguration( + self.package, { + "importer": "DefaultImporter", + "override_metadata_upm": self.override_metadata + }).override_metadata_upm) + + def test_importer_metadata_default(self): + """Create default metadata.""" + self.assertEqual( + self.default_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "DefaultImporter"}).importer_metadata) + + def test_importer_metadata_invalid(self): + """Try to create metadata with an invalid importer.""" + with self.assertRaises(export_unity_package.ProjectConfigurationError): + unused_metadata = export_unity_package.AssetConfiguration( + self.package, {"importer": "InvalidImporter"}).importer_metadata + + def test_create_importer_metadata_editor_only(self): + """Create metadata that only targets the editor.""" + self.plugin_metadata["PluginImporter"]["platformData"]["Editor"][ + "enabled"] = 1 + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Editor"]}).importer_metadata) + + def test_importer_metadata_android_only(self): + """Create metadata that only targets Android.""" + self.plugin_metadata["PluginImporter"]["platformData"]["Android"][ + "enabled"] = 1 + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Android"]}).importer_metadata) + + def test_importer_metadata_android_only_armv7(self): + """Create metadata with ARMv7 CPU set.""" + self.plugin_metadata["PluginImporter"]["platformData"]["Android"][ + "enabled"] = 1 + self.plugin_metadata["PluginImporter"]["platformData"]["Android"][ + "settings"]["CPU"] = "ARMv7" + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Android"], + "cpu": "ARMv7"}).importer_metadata) + + def test_importer_metadata_ios_only(self): + """Create metadata that only targets iOS.""" + self.plugin_metadata["PluginImporter"]["platformData"]["iOS"]["enabled"] = 1 + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["iOS"]}).importer_metadata) + + def test_importer_metadata_tvos_only(self): + """Create metadata that only targets tvOS.""" + self.plugin_metadata["PluginImporter"]["platformData"]["tvOS"]["enabled"] = 1 + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["tvOS"]}).importer_metadata) + + def test_importer_metadata_standalone_invalid_cpu(self): + """Create metadata with an invalid CPU.""" + with self.assertRaises(export_unity_package.ProjectConfigurationError): + unused_metadata = export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Standalone"], + "cpu": "Crusoe"}).importer_metadata + + def test_importer_metadata_standalone_only_any_cpu(self): + """Create metadata that only targets standalone (desktop).""" + platform_data = self.plugin_metadata["PluginImporter"]["platformData"] + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux"]["settings"]["CPU"] = "x86" + platform_data["Linux64"]["enabled"] = 1 + platform_data["Linux64"]["settings"]["CPU"] = "x86_64" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel"]["settings"]["CPU"] = "x86" + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXIntel64"]["settings"]["CPU"] = "x86_64" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win"]["enabled"] = 1 + platform_data["Win"]["settings"]["CPU"] = "x86" + platform_data["Win64"]["enabled"] = 1 + platform_data["Win64"]["settings"]["CPU"] = "x86_64" + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Standalone"]}).importer_metadata) + + def test_importer_metadata_standalone_only_x86(self): + """Create metadata that only targets standalone (desktop) x86.""" + platform_data = self.plugin_metadata["PluginImporter"]["platformData"] + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux"]["settings"]["CPU"] = "x86" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel"]["settings"]["CPU"] = "x86" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win"]["enabled"] = 1 + platform_data["Win"]["settings"]["CPU"] = "x86" + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Standalone"], + "cpu": "x86"}).importer_metadata) + + def test_importer_metadata_standalone_only_x86_64(self): + """Create metadata that only targets standalone (desktop) x86_64.""" + platform_data = self.plugin_metadata["PluginImporter"]["platformData"] + platform_data["Linux64"]["enabled"] = 1 + platform_data["Linux64"]["settings"]["CPU"] = "x86_64" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXIntel64"]["settings"]["CPU"] = "x86_64" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win64"]["enabled"] = 1 + platform_data["Win64"]["settings"]["CPU"] = "x86_64" + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Standalone"], + "cpu": "x86_64"}).importer_metadata) + + +class AssetPackageAndProjectFileOperationsTest(absltest.TestCase): + """Tests for file operation methods.""" + + def setUp(self): + """Unpack resources to a temporary directory.""" + super(AssetPackageAndProjectFileOperationsTest, self).setUp() + self.old_flag = FLAGS.enforce_semver + FLAGS.enforce_semver = False + self.assets_dir = os.path.join(TEST_DATA_PATH, "Assets") + self.staging_dir = os.path.join(FLAGS.test_tmpdir, "staging") + self.package = export_unity_package.PackageConfiguration( + export_unity_package.ProjectConfiguration({}, set(), None), + {"name": "Test.unitypackage"}) + os.makedirs(self.staging_dir) + self.version_handler_dll_metadata = collections.OrderedDict( + [("fileFormatVersion", 2), + ("guid", "06f6f385a4ad409884857500a3c04441"), + ("labels", ["gvh", "gvh_teditor", "gvh_v1.2.86.0", + "gvhp_exportpath-PlayServicesResolver/Editor/" + + "Google.VersionHandler.dll"]), + ("PluginImporter", collections.OrderedDict( + [("externalObjects", collections.OrderedDict()), + ("serializedVersion", 2), + ("iconMap", collections.OrderedDict()), + ("executionOrder", collections.OrderedDict()), + ("isPreloaded", 0), + ("isOverridable", 0), + ("platformData", [ + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Any", None)])), + ("second", collections.OrderedDict( + [("enabled", 0), + ("settings", collections.OrderedDict())]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Editor", "Editor")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("DefaultValueInitialized", True)]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Windows Store Apps", "WindowsStoreApps")])), + ("second", collections.OrderedDict( + [("enabled", 0), + ("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]))])]), + ("userData", None), + ("assetBundleName", None), + ("assetBundleVariant", None)]))]) + + self.expected_metadata_analytics = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.expected_metadata_analytics["labels"] = [ + "gvhp_exportpath-Firebase/Plugins/Firebase.Analytics.dll"] + self.expected_metadata_app = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.expected_metadata_app["labels"] = [ + "gvhp_exportpath-Firebase/Plugins/Firebase.App.dll"] + self.expected_metadata_auth = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.expected_metadata_auth["labels"] = [ + "gvhp_exportpath-Firebase/Plugins/Firebase.Auth.dll"] + + def tearDown(self): + """Clean up the temporary directory.""" + super(AssetPackageAndProjectFileOperationsTest, self).tearDown() + FLAGS.enforce_semver = self.old_flag + delete_temporary_directory_contents() + + def test_find_files_no_wildcards(self): + """Walk a set of paths with no wildcards.""" + config = export_unity_package.AssetConfiguration( + self.package, + {"paths": ["Firebase/Plugins/Firebase.App.dll", + "Firebase/Plugins/Firebase.Analytics.dll"]}) + found_assets = config.find_assets([self.assets_dir]) + + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.App.dll", self.expected_metadata_app)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_files_non_existant_file(self): + """Walk a set of paths with no wildcards.""" + config = export_unity_package.AssetConfiguration( + self.package, + {"paths": ["Firebase/Plugins/Firebase.Analytics.dll", + "Firebase/AFileThatDoesNotExist"]}) + found_assets = config.find_assets([self.assets_dir]) + self.assertEqual( + ["Firebase/Plugins/Firebase.Analytics.dll"], + [asset.filename for asset in found_assets]) + + def test_find_assets_using_directory(self): + """Walk a set of paths using a directory.""" + config = export_unity_package.AssetConfiguration( + self.package, {"paths": ["Firebase"]}) + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.App.dll", self.expected_metadata_app), + ("Firebase/Plugins/Firebase.Auth.dll", self.expected_metadata_auth)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_in_multiple_directories(self): + """Search multiple directories for assets.""" + config = export_unity_package.AssetConfiguration( + self.package, {"paths": ["Plugins/Firebase.Analytics.dll", + "Editor/Google.VersionHandler.dll"]}) + found_assets = config.find_assets( + [os.path.join(self.assets_dir, "Firebase"), + os.path.join(self.assets_dir, "PlayServicesResolver")]) + self.assertCountEqual( + [export_unity_package.posix_path(os.path.join( + self.assets_dir, "Firebase/Plugins/Firebase.Analytics.dll")), + export_unity_package.posix_path(os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll"))], + [asset.filename_absolute for asset in found_assets]) + + def test_find_assets_using_wildcard(self): + """Walk a set of paths using a wildcard.""" + config = export_unity_package.AssetConfiguration( + self.package, {"paths": ["Firebase/Plugins/Firebase.A*t*.dll"]}) + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.Auth.dll", self.expected_metadata_auth)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_with_metadata(self): + """Walk a set of paths using a wildcard with metadata.""" + config = export_unity_package.AssetConfiguration( + self.package, + {"paths": ["PlayServicesResolver/Editor/Google.VersionHandler.*"]}) + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + self.version_handler_dll_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_with_override_metadata(self): + """Find an asset and override parts of its metadata.""" + config = export_unity_package.AssetConfiguration( + self.package, + collections.OrderedDict([ + ("paths", ["PlayServicesResolver/Editor/Google.VersionHandler.*"]), + ("override_metadata", collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("platformData", [collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Editor", "Editor")])), + ("second", collections.OrderedDict([ + ("enabled", 0)])) + ])]) + ])) + ])) + ])) + expected_metadata = copy.deepcopy(self.version_handler_dll_metadata) + expected_metadata["PluginImporter"]["platformData"][1]["second"][ + "enabled"] = 0 + + # Metadata with find_assets(for_upm=False) should be overridden. + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + expected_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + # Metadata with find_assets(for_upm=True) should be overridden. + found_assets_upm = config.find_assets([self.assets_dir], for_upm=True) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + expected_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets_upm + ]) + + def test_find_assets_with_override_metadata_upm(self): + """Find an asset and override parts of its metadata.""" + config = export_unity_package.AssetConfiguration( + self.package, + collections.OrderedDict([ + ("paths", ["PlayServicesResolver/Editor/Google.VersionHandler.*"]), + ("override_metadata_upm", + collections.OrderedDict([ + ("PluginImporter", + collections.OrderedDict([("platformData", [ + collections.OrderedDict([ + ("first", + collections.OrderedDict([("Editor", "Editor")])), + ("second", collections.OrderedDict([("enabled", 0)])) + ]) + ])])) + ])) + ])) + + # Metadata with find_assets(for_upm=False) should remain unchanged. + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + self.version_handler_dll_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + # Metadata with find_assets(for_upm=True) should be overridden. + expected_metadata = copy.deepcopy(self.version_handler_dll_metadata) + expected_metadata["PluginImporter"]["platformData"][1]["second"][ + "enabled"] = 0 + found_assets_upm = config.find_assets([self.assets_dir], for_upm=True) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + expected_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets_upm + ]) + + def test_find_assets_with_exclusions(self): + """Find assets for a package with a subset of files excluded.""" + config_json = { + "packages": [ + {"name": "FirebaseAppAndAuth.unitypackage", + "imports": [ + {"paths": [ + "Firebase/Plugins/Firebase.App.dll", + "Firebase/Plugins/Firebase.Auth.dll" + ]} + ]}, + {"name": "PlayServicesResolver.unitypackage", + "imports": [ + {"paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ]} + ]}, + {"name": "FirebaseAnalytics.unitypackage", + "imports": [ + {"paths": [ + "Firebase/Plugins/Firebase.Analytics.dll" + ]} + ], + "exclude_paths": [r".*\.Auth\.dll$"], + "includes": ["FirebaseAppAndAuth.unitypackage", + "PlayServicesResolver.unitypackage"] + } + ] + } + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + package = config.packages_by_name["FirebaseAnalytics.unitypackage"] + found_assets = package.find_assets([self.assets_dir]) + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.App.dll", self.expected_metadata_app), + ("PlayServicesResolver/Editor/Google.VersionHandler.dll", + self.version_handler_dll_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_via_includes(self): + """Find assets by transitively searching included packages.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage", + "imports": [{"paths": ["Firebase/Plugins/Firebase.App.dll"]}]}, + {"name": "PlayServicesResolver.unitypackage", + "imports": [ + {"paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ]} + ]}, + {"name": "FirebaseAnalytics.unitypackage", + "imports": [ + {"paths": [ + "Firebase/Plugins/Firebase.Analytics.dll" + ]} + ], + "includes": ["FirebaseApp.unitypackage", + "PlayServicesResolver.unitypackage"] + } + ] + } + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + package = config.packages_by_name["FirebaseAnalytics.unitypackage"] + found_assets = package.find_assets([self.assets_dir]) + + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.App.dll", self.expected_metadata_app), + ("PlayServicesResolver/Editor/Google.VersionHandler.dll", + self.version_handler_dll_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_via_includes_with_conflicting_metadata(self): + """Find assets with conflicting metadata.""" + config_json = { + "packages": [ + {"name": "PlayServicesResolver.unitypackage", + "imports": [ + {"paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ]} + ]}, + {"name": "PlayServicesResolverConflicting.unitypackage", + "imports": [ + {"labels": ["conflicting"], + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ] + } + ] + }, + {"name": "FirebaseAnalytics.unitypackage", + "imports": [ + {"paths": ["Firebase/Plugins/Firebase.Analytics.dll"]} + ], + "includes": [ + "PlayServicesResolver.unitypackage", + "PlayServicesResolverConflicting.unitypackage" + ] + } + ] + } + + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + package = config.packages_by_name["FirebaseAnalytics.unitypackage"] + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + package.find_assets([self.assets_dir]) + self.assertRegexMatch( + str(context.exception), + [r"File .*Google\.VersionHandler\.dll imported with different import " + r"settings in .*PlayServicesResolver\.unitypackage', " + r".*PlayServicesResolverConflicting\.unitypackage'"]) + + def test_find_assets_for_upm(self): + """Find assets for UPM package. + + This should exclude assets from included packages. + """ + config_json = { + "packages": [{ + "name": "FirebaseApp.unitypackage", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.App.dll"] + }] + }, { + "name": + "PlayServicesResolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ] + }] + }, { + "name": + "FirebaseAnalytics.unitypackage", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.Analytics.dll"] + }], + "includes": [ + "FirebaseApp.unitypackage", "PlayServicesResolver.unitypackage" + ] + }] + } + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + package = config.packages_by_name["FirebaseAnalytics.unitypackage"] + found_assets = package.find_assets([self.assets_dir], for_upm=True) + + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_asset_write_metadata(self): + """Write asset metadata to a file.""" + metadata_filename = os.path.join(FLAGS.test_tmpdir, "metadata") + export_unity_package.Asset.write_metadata( + metadata_filename, + [collections.OrderedDict( + [("someMetadata", None), + ("otherData", "foo") + ]), + collections.OrderedDict( + [("packageInfo", + collections.OrderedDict( + [("version", 1), + ("format", "max")])) + ]), + collections.OrderedDict([("someMetadata", 1)]), + collections.OrderedDict( + [("packageInfo", + collections.OrderedDict( + [("format", "ultra"), + ("enabled", 1)])) + ]), + ]) + with open(metadata_filename) as metadata_file: + self.assertEqual("someMetadata: 1\n" + "otherData: foo\n" + "packageInfo:\n" + " version: 1\n" + " format: ultra\n" + " enabled: 1\n", + metadata_file.read()) + + def test_asset_write(self): + """Add an asset to a plugin archive staging area.""" + asset = export_unity_package.Asset( + "Firebase/Plugins/Firebase.App.dll", + os.path.join(self.assets_dir, "Firebase/Plugins/Firebase.App.dll"), + collections.OrderedDict( + [("someMetadata", 1), + ("otherData", "foo"), + ("packageInfo", + collections.OrderedDict( + [("version", 1)]))])) + asset_dir = asset.write(self.staging_dir, + "fa42a2182ad64806b8f78e9de4cc4f78", 123456789) + self.assertTrue(os.path.join(self.staging_dir, + "fa42a2182ad64806b8f78e9de4cc4f78"), + asset_dir) + self.assertTrue(filecmp.cmp(os.path.join(asset_dir, "asset"), + asset.filename_absolute)) + + with open(os.path.join(asset_dir, "pathname")) as ( + asset_pathname_file): + self.assertEqual("Assets/Firebase/Plugins/Firebase.App.dll", + asset_pathname_file.read()) + + with open(os.path.join(asset_dir, "asset.meta")) as ( + asset_metadata_file): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: fa42a2182ad64806b8f78e9de4cc4f78\n" + "labels:\n" + "- gvhp_exportpath-Firebase/Plugins/Firebase.App.dll\n" + "timeCreated: 123456789\n" + "someMetadata: 1\n" + "otherData: foo\n" + "packageInfo:\n" + " version: 1\n", + asset_metadata_file.read()) + + def test_package_create_archive(self): + """Create a unitypackage archive.""" + test_case_dir = os.path.join(FLAGS.test_tmpdir, "test_create_archive") + os.makedirs(test_case_dir) + use_tar = export_unity_package.FLAGS.use_tar + try: + # Disable use of tar command line application until this is reproducible on + # macOS. + export_unity_package.FLAGS.use_tar = (platform.system() != "Darwin") + + archive_dir = os.path.join(test_case_dir, "archive_dir") + os.makedirs(archive_dir) + # Create some files to archive. + test_files = {"a/b/c.txt": "hello", + "d/e.txt": "world"} + for filename, contents in test_files.items(): + input_filename = os.path.join(os.path.join(archive_dir, filename)) + os.makedirs(os.path.dirname(input_filename)) + with open(input_filename, "wt") as input_file: + input_file.write(contents) + + # Create an archive. + archive_filename = os.path.join(test_case_dir, "archive.unitypackage") + export_unity_package.PackageConfiguration.create_archive(archive_filename, + archive_dir, 0) + + # Unpack the archive and make sure the expected files were stored. + with tarfile.open(archive_filename, "r:gz") as archive_file: + self.assertCountEqual( + ["a", "a/b", "a/b/c.txt", "d", "d/e.txt"], + archive_file.getnames()) + for filename in test_files: + embedded_file = archive_file.extractfile(filename) + self.assertEqual(test_files[filename], + embedded_file.read().decode("utf8")) + + # Touch all files in the archive. + for filename in test_files: + input_filename = os.path.join(os.path.join(archive_dir, filename)) + file_stat = os.stat(input_filename) + os.utime(input_filename, (file_stat.st_atime, file_stat.st_mtime - 60)) + + # Create another archive. + other_archive_filename = os.path.join(test_case_dir, + "archive2.unitypackage") + os.rename(archive_filename, other_archive_filename) + # Wait before writing another archive so that any potential changes to + # timestamps can be written. + time.sleep(1) + # We create the archive with the original filename as the filename is + # embedded in the archive. + export_unity_package.PackageConfiguration.create_archive(archive_filename, + archive_dir, 0) + self.assertTrue(filecmp.cmp(archive_filename, other_archive_filename)) + + finally: + shutil.rmtree(test_case_dir) + export_unity_package.FLAGS.use_tar = use_tar + + def test_package_write(self): + """Write a .unitypackage file.""" + project = export_unity_package.ProjectConfiguration( + {"packages": [ + {"name": "FirebaseApp.unitypackage", + "manifest_path": "Firebase/Editor", + "imports": [ + {"paths": [ + "Firebase/Plugins/Firebase.App.dll", + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ]} + ]} + ]}, set(), "1.0.0") + package = project.packages_by_name["FirebaseApp.unitypackage"] + unitypackage = package.write( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), + { + "1.0.0": { + "Firebase/Editor/FirebaseApp_version-1.0.0_manifest.txt": + "08d62f799cbd4b02a3ff77313706a3c0", + "Firebase/Plugins/Firebase.App.dll": + "7311924048bd457bac6d713576c952da" + } + }, "1.0.0"), + [self.assets_dir], self.staging_dir, 0) + + self.assertEqual(os.path.join(self.staging_dir, "FirebaseApp.unitypackage"), + unitypackage) + + with tarfile.open(unitypackage, "r:gz") as unitypackage_file: + self.assertCountEqual( + ["06f6f385a4ad409884857500a3c04441", + "06f6f385a4ad409884857500a3c04441/asset", + "06f6f385a4ad409884857500a3c04441/asset.meta", + "06f6f385a4ad409884857500a3c04441/pathname", + "08d62f799cbd4b02a3ff77313706a3c0", + "08d62f799cbd4b02a3ff77313706a3c0/asset", + "08d62f799cbd4b02a3ff77313706a3c0/asset.meta", + "08d62f799cbd4b02a3ff77313706a3c0/pathname", + "7311924048bd457bac6d713576c952da", + "7311924048bd457bac6d713576c952da/asset", + "7311924048bd457bac6d713576c952da/asset.meta", + "7311924048bd457bac6d713576c952da/pathname"], + unitypackage_file.getnames()) + unitypackage_file.extractall(self.staging_dir) + self.assertTrue(filecmp.cmp( + os.path.join(self.assets_dir, "Firebase/Plugins/Firebase.App.dll"), + os.path.join(self.staging_dir, + "7311924048bd457bac6d713576c952da/asset"))) + with open(os.path.join(self.staging_dir, + "08d62f799cbd4b02a3ff77313706a3c0/asset"), + "rt") as manifest: + self.assertEqual( + "Assets/Firebase/Plugins/Firebase.App.dll\n" + "Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll\n", + manifest.read()) + with open(os.path.join( + self.staging_dir, "06f6f385a4ad409884857500a3c04441/asset.meta")) as ( + metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 06f6f385a4ad409884857500a3c04441\n" + "labels:\n" + "- gvh\n" + "- gvh_teditor\n" + "- gvh_v1.2.86.0\n" + "- gvh_version-1.0.0\n" + "- gvhp_exportpath-PlayServicesResolver/Editor/" + "Google.VersionHandler.dll\n" + "timeCreated: 0\n" + "PluginImporter:\n" + " externalObjects: {}\n" + " serializedVersion: 2\n" + " iconMap: {}\n" + " executionOrder: {}\n" + " isPreloaded: 0\n" + " isOverridable: 0\n" + " platformData:\n" + " - first:\n" + " Any:\n" + " second:\n" + " enabled: 0\n" + " settings: {}\n" + " - first:\n" + " Editor: Editor\n" + " second:\n" + " enabled: 1\n" + " settings:\n" + " DefaultValueInitialized: true\n" + " - first:\n" + " Windows Store Apps: WindowsStoreApps\n" + " second:\n" + " enabled: 0\n" + " settings:\n" + " CPU: AnyCPU\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + def test_package_write_with_includes_and_export(self): + """Write a .unitypackage file.""" + # This is a slighty more complicated case + # FirebaseAuth.unitypackage: + # export=1, generate manifest, includes=[FirebaseApp, FirebaseAnalytics] + # FirebaseAnalytics.unitypackage: + # export=1, generate manifest, includes=[FirebaseApp] + # FirebaseApp.unitypackage: + # export=0, generate manifest, includes=[VersionHandler] + # VersionHandler.unitypackage: + # export=0, no manifest, includes=[] + project = export_unity_package.ProjectConfiguration( + { + "packages": [{ + "name": "FirebaseApp.unitypackage", + "manifest_path": "Firebase/Editor", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.App.dll",] + }], + "includes": ["VersionHandler.unitypackage"], + "export": 0 + }, { + "name": "VersionHandler.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ] + }], + "export": 0 + }, { + "name": "FirebaseAnalytics.unitypackage", + "manifest_path": "Firebase/Editor", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.Analytics.dll",] + }], + "includes": ["FirebaseApp.unitypackage"] + }, { + "name": + "FirebaseAuth.unitypackage", + "manifest_path": + "Firebase/Editor", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.Auth.dll",] + }], + "includes": [ + "FirebaseApp.unitypackage", "FirebaseAnalytics.unitypackage" + ] + }] + }, set(), "1.0.0") + package = project.packages_by_name["FirebaseAuth.unitypackage"] + unitypackage = package.write( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), { + "1.0.0": { + "Firebase/Editor/FirebaseApp_version-1.0.0_manifest.txt": + "08d62f799cbd4b02a3ff77313706a3c0", + ("Firebase/Editor/" + "FirebaseAnalytics_version-1.0.0_manifest.txt"): + "4a3f361c622e4b88b6f61a126cc8083d", + "Firebase/Editor/FirebaseAuth_version-1.0.0_manifest.txt": + "2b2a3eb537894428a96778fef31996e2", + "Firebase/Plugins/Firebase.App.dll": + "7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.Analytics.dll": + "816270c2a2a348e59cb9b7b096a24f50", + "Firebase/Plugins/Firebase.Auth.dll": + "275bd6b96a28470986154b9a995e191c" + } + }, "1.0.0"), [self.assets_dir], self.staging_dir, 0) + + self.assertEqual( + os.path.join(self.staging_dir, "FirebaseAuth.unitypackage"), + unitypackage) + + with tarfile.open(unitypackage, "r:gz") as unitypackage_file: + self.assertCountEqual([ + "06f6f385a4ad409884857500a3c04441", + "06f6f385a4ad409884857500a3c04441/asset", + "06f6f385a4ad409884857500a3c04441/asset.meta", + "06f6f385a4ad409884857500a3c04441/pathname", + "08d62f799cbd4b02a3ff77313706a3c0", + "08d62f799cbd4b02a3ff77313706a3c0/asset", + "08d62f799cbd4b02a3ff77313706a3c0/asset.meta", + "08d62f799cbd4b02a3ff77313706a3c0/pathname", + "4a3f361c622e4b88b6f61a126cc8083d", + "4a3f361c622e4b88b6f61a126cc8083d/asset", + "4a3f361c622e4b88b6f61a126cc8083d/asset.meta", + "4a3f361c622e4b88b6f61a126cc8083d/pathname", + "2b2a3eb537894428a96778fef31996e2", + "2b2a3eb537894428a96778fef31996e2/asset", + "2b2a3eb537894428a96778fef31996e2/asset.meta", + "2b2a3eb537894428a96778fef31996e2/pathname", + "7311924048bd457bac6d713576c952da", + "7311924048bd457bac6d713576c952da/asset", + "7311924048bd457bac6d713576c952da/asset.meta", + "7311924048bd457bac6d713576c952da/pathname", + "816270c2a2a348e59cb9b7b096a24f50", + "816270c2a2a348e59cb9b7b096a24f50/asset", + "816270c2a2a348e59cb9b7b096a24f50/asset.meta", + "816270c2a2a348e59cb9b7b096a24f50/pathname", + "275bd6b96a28470986154b9a995e191c", + "275bd6b96a28470986154b9a995e191c/asset", + "275bd6b96a28470986154b9a995e191c/asset.meta", + "275bd6b96a28470986154b9a995e191c/pathname" + ], unitypackage_file.getnames()) + unitypackage_file.extractall(self.staging_dir) + self.assertTrue( + filecmp.cmp( + os.path.join(self.assets_dir, + "Firebase/Plugins/Firebase.App.dll"), + os.path.join(self.staging_dir, + "7311924048bd457bac6d713576c952da/asset"))) + self.assertTrue( + filecmp.cmp( + os.path.join(self.assets_dir, + "Firebase/Plugins/Firebase.Auth.dll"), + os.path.join(self.staging_dir, + "275bd6b96a28470986154b9a995e191c/asset"))) + self.assertTrue( + filecmp.cmp( + os.path.join(self.assets_dir, + "Firebase/Plugins/Firebase.Analytics.dll"), + os.path.join(self.staging_dir, + "816270c2a2a348e59cb9b7b096a24f50/asset"))) + # Verify FirebaseApp_version-1.0.0_manifest.txt + with open( + os.path.join(self.staging_dir, + "08d62f799cbd4b02a3ff77313706a3c0/asset"), + "rt") as manifest: + self.assertEqual( + "Assets/Firebase/Plugins/Firebase.App.dll\n" + "Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll\n", + manifest.read()) + # Verify FirebaseAnalytics_version-1.0.0_manifest.txt + with open( + os.path.join(self.staging_dir, + "4a3f361c622e4b88b6f61a126cc8083d/asset"), + "rt") as manifest: + self.assertEqual( + "Assets/Firebase/Plugins/Firebase.Analytics.dll\n" + "Assets/Firebase/Plugins/Firebase.App.dll\n" + "Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll\n", + manifest.read()) + # Verify FirebaseAuth_version-1.0.0_manifest.txt + with open( + os.path.join(self.staging_dir, + "2b2a3eb537894428a96778fef31996e2/asset"), + "rt") as manifest: + self.assertEqual( + "Assets/Firebase/Plugins/Firebase.Analytics.dll\n" + "Assets/Firebase/Plugins/Firebase.App.dll\n" + "Assets/Firebase/Plugins/Firebase.Auth.dll\n" + "Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll\n", + manifest.read()) + + def test_package_write_upm(self): + """Write a .tgz file.""" + # This is a slightly complicated case + # play-services-resolver: + # export_upm=1, includes=[ios-resolver, jar-resolver] + # Google.VersionHandlerImpl_*.dll overridden to be enabled in editor + # ios-resolver: + # export_upm=1 + # jar-resolver: + # export_upm=0 + + expected_override_metadata = {"Editor": {"enabled": 1}} + + project = export_unity_package.ProjectConfiguration( + { + "packages": [{ + "name": "jar-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.JarResolver_*.dll", + ] + }], + "common_manifest": { + "name": "com.google.jar-resolver", + }, + }, { + "name": "ios-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.IOSResolver_*.dll", + ] + }], + "common_manifest": { + "name": "com.google.ios-resolver", + }, + "export_upm": 1, + }, { + "name": "play-services-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ] + }, { + "paths": [ + "PlayServicesResolver/Editor/" + "Google.VersionHandlerImpl_*.dll", + ], + "override_metadata_upm": { + "PluginImporter": { + "platformData": expected_override_metadata, + } + }, + }], + "manifest_path": "PlayServicesResolver/Editor", + "readme": "PlayServicesResolver/Editor/README.md", + "changelog": "PlayServicesResolver/Editor/CHANGELOG.md", + "license": "PlayServicesResolver/Editor/LICENSE", + "documentation": "PlayServicesResolver/Doc", + "includes": + ["ios-resolver.unitypackage", "jar-resolver.unitypackage"], + "common_manifest": { + "name": "com.google.play-services-resolver", + "display_name": "Play Services Resolver", + }, + "export_upm": 1, + "upm_package_config": { + "manifest": { + "unity": "2017.1", + "dependencies": { + "com.some.third-party-package": "1.2.3" + }, + } + } + }] + }, set(), "1.0.0") + package = project.packages_by_name["play-services-resolver.unitypackage"] + + expected_tarball_name = "com.google.play-services-resolver-1.0.0.tgz" + + self.assertEqual(expected_tarball_name, package.tarball_name) + + unitypackage = package.write_upm( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), { + "1.0.0": { + "com.google.play-services-resolver/README.md": + "baa27a4c0385454899a759d9852966b7", + "com.google.play-services-resolver/CHANGELOG.md": + "000ce82791494e44b04c7a6f9a31151c", + "com.google.play-services-resolver/LICENSE.md": + "94717c1d977f445baed18e00605e3d7c", + "PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt": + "353f6aace2cd42adb1343fc6a808f62e", + "com.google.play-services-resolver/package.json": + "782a38c5f19e4bb99e927976c8daa9ac", + "com.google.play-services-resolver/PlayServicesResolver": + "fa7daf703ad1430dad0cd8b764e5e6d2", + "com.google.play-services-resolver/PlayServicesResolver/" + "Editor": + "2334cd7684164851a8a53db5bd5923ca", + } + }, "1.0.0"), [self.assets_dir], self.staging_dir, 0) + + expected_manifest = { + "name": "com.google.play-services-resolver", + "displayName": "Play Services Resolver", + "version": "1.0.0", + "unity": "2017.1", + "keywords": [ + "vh-name:play-services-resolver", + "vh-name:Play Services Resolver" + ], + "dependencies": { + "com.some.third-party-package": "1.2.3", + "com.google.ios-resolver": "1.0.0" + }, + } + self.assertEqual( + os.path.join(self.staging_dir, expected_tarball_name), unitypackage) + + with tarfile.open(unitypackage, "r:gz") as unitypackage_file: + # Check included files. + self.assertCountEqual([ + "package", + "package/package.json", + "package/package.json.meta", + "package/README.md", + "package/README.md.meta", + "package/CHANGELOG.md", + "package/CHANGELOG.md.meta", + "package/LICENSE.md", + "package/LICENSE.md.meta", + "package/PlayServicesResolver", + "package/PlayServicesResolver.meta", + "package/PlayServicesResolver/Editor", + "package/PlayServicesResolver/Editor.meta", + "package/PlayServicesResolver/Editor/Google.VersionHandler.dll", + "package/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta", + "package/PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt", + "package/PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt.meta", + "package/PlayServicesResolver/Editor/" + "Google.VersionHandlerImpl_v1.2.87.0.dll", + "package/PlayServicesResolver/Editor/" + "Google.VersionHandlerImpl_v1.2.87.0.dll.meta", + "package/Documentation~", + "package/Documentation~/index.md", + ], unitypackage_file.getnames()) + unitypackage_file.extractall(self.staging_dir) + + self.assertTrue( + filecmp.cmp( + os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll"), + os.path.join( + self.staging_dir, "package/PlayServicesResolver/Editor/" + "Google.VersionHandler.dll"))) + + self.assertTrue( + filecmp.cmp( + os.path.join( + self.assets_dir, + "PlayServicesResolver/Doc/index.md"), + os.path.join( + self.staging_dir, "package/Documentation~/index.md"))) + + # Check package.json + with open(os.path.join(self.staging_dir, "package/package.json"), + "rt") as manifest: + self.assertEqual(expected_manifest, json.loads(manifest.read())) + + # Check folder metadata + with open( + os.path.join(self.staging_dir, + "package/PlayServicesResolver.meta")) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: fa7daf703ad1430dad0cd8b764e5e6d2\n" + "timeCreated: 0\n" + "folderAsset: true\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + # Check overridden metadata + with open( + os.path.join( + self.staging_dir, "package/PlayServicesResolver/Editor/" + "Google.VersionHandlerImpl_v1.2.87.0.dll.meta")) as (metadata): + serializer = export_unity_package.YamlSerializer() + yaml_dict = serializer.load(metadata.read()) + self.assertEqual(expected_override_metadata, + yaml_dict["PluginImporter"]["platformData"]) + + def test_package_write_upm_documentation_as_file(self): + """Test write_upm() with documentation path as a file.""" + project = export_unity_package.ProjectConfiguration( + { + "packages": [{ + "name": "play-services-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ] + }], + "manifest_path": "PlayServicesResolver/Editor", + # Use README.md as documentation. + "documentation": "PlayServicesResolver/Editor/README.md", + "common_manifest": { + "name": "com.google.play-services-resolver", + }, + "export_upm": 1 + }] + }, set(), "1.0.0") + package = project.packages_by_name["play-services-resolver.unitypackage"] + + upm_package = package.write_upm( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), { + "1.0.0": { + "PlayServicesResolver/Editor/README.md": + "baa27a4c0385454899a759d9852966b7", + "PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt": + "353f6aace2cd42adb1343fc6a808f62e", + "com.google.play-services-resolver/package.json": + "782a38c5f19e4bb99e927976c8daa9ac", + "com.google.play-services-resolver/PlayServicesResolver": + "fa7daf703ad1430dad0cd8b764e5e6d2", + "com.google.play-services-resolver/PlayServicesResolver/" + "Editor": + "2334cd7684164851a8a53db5bd5923ca", + } + }, "1.0.0"), [self.assets_dir], self.staging_dir, 0) + + with tarfile.open(upm_package, "r:gz") as upm_package_file: + # Check included files. + self.assertCountEqual([ + "package", + "package/package.json", + "package/package.json.meta", + "package/PlayServicesResolver", + "package/PlayServicesResolver.meta", + "package/PlayServicesResolver/Editor", + "package/PlayServicesResolver/Editor.meta", + "package/PlayServicesResolver/Editor/Google.VersionHandler.dll", + "package/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta", + "package/PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt", + "package/PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt.meta", + "package/Documentation~", + "package/Documentation~/index.md", + ], upm_package_file.getnames()) + upm_package_file.extractall(self.staging_dir) + + self.assertTrue( + filecmp.cmp( + os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/README.md"), + os.path.join( + self.staging_dir, "package/Documentation~/index.md"))) + + def test_package_write_upm_missing_readme(self): + """Test write_upm() with misconfigured readme path.""" + project = export_unity_package.ProjectConfiguration( + { + "packages": [{ + "name": "play-services-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ] + }], + "manifest_path": "PlayServicesResolver/Editor", + "readme": "a/nonexist/path/README.md", + "common_manifest": { + "name": "com.google.play-services-resolver", + }, + "export_upm": 1 + }] + }, set(), "1.0.0") + package = project.packages_by_name["play-services-resolver.unitypackage"] + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + package.write_upm( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), { + "1.0.0": { + "PlayServicesResolver/Editor/README.md": + "baa27a4c0385454899a759d9852966b7", + "PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt": + "353f6aace2cd42adb1343fc6a808f62e", + "com.google.play-services-resolver/package.json": + "782a38c5f19e4bb99e927976c8daa9ac", + "com.google.play-services-resolver/PlayServicesResolver": + "fa7daf703ad1430dad0cd8b764e5e6d2", + "com.google.play-services-resolver/PlayServicesResolver/" + "Editor": + "2334cd7684164851a8a53db5bd5923ca", + } + }, "1.0.0"), [self.assets_dir], self.staging_dir, 0) + + +class TestVersionHandler(absltest.TestCase): + """Test methods that generate Version Handler labels and filenames.""" + + def test_version_handler_tag(self): + """Generate a label or filename field for the Version Handler.""" + self.assertEqual( + "gvh_manifest", + export_unity_package.version_handler_tag( + islabel=True, + field=export_unity_package.VERSION_HANDLER_MANIFEST_FIELD_PREFIX, + value=None)) + self.assertEqual( + "manifest", + export_unity_package.version_handler_tag( + islabel=False, + field=export_unity_package.VERSION_HANDLER_MANIFEST_FIELD_PREFIX, + value=None)) + self.assertEqual( + "gvh_version-1.2.3-beta2", + export_unity_package.version_handler_tag( + islabel=True, + field=export_unity_package.VERSION_HANDLER_VERSION_FIELD_PREFIX, + value="1.2.3_beta2")) + self.assertEqual( + "version-1.2.3-beta2", + export_unity_package.version_handler_tag( + islabel=False, + field=export_unity_package.VERSION_HANDLER_VERSION_FIELD_PREFIX, + value="1.2.3_beta2")) + self.assertEqual( + "", export_unity_package.version_handler_tag( + islabel=False, field=None, value=None)) + + def test_version_handler_filename(self): + """Generate a filename for the Version Handler.""" + self.assertEqual( + "a/b/c/myplugin_version-1.2.3_manifest.txt", + export_unity_package.version_handler_filename( + "a/b/c/myplugin.txt", + [(export_unity_package.VERSION_HANDLER_VERSION_FIELD_PREFIX, + "1.2.3"), + (export_unity_package.VERSION_HANDLER_MANIFEST_FIELD_PREFIX, + None)])) + + +class FileOperationsTest(absltest.TestCase): + """Test file utility methods.""" + + def setUp(self): + """Unpack resources to a temporary directory.""" + super(FileOperationsTest, self).setUp() + self.assets_dir = os.path.join(TEST_DATA_PATH, "Assets") + self.temp_dir = os.path.join(FLAGS.test_tmpdir, "copy_temp") + self.expected_mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR + if platform.system() == 'Windows': + # Windows doesn't support the executable mode so ignore it in tests. + self.expected_mode = self.expected_mode & ~stat.S_IXUSR + os.makedirs(self.temp_dir) + + def tearDown(self): + """Clean up the temporary directory.""" + super(FileOperationsTest, self).tearDown() + shutil.rmtree(self.temp_dir) + + def test_copy_and_set_rwx(self): + """Copy a file and set it to readable / writeable and executable.""" + source_path = os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt") + target_path = os.path.join(self.temp_dir, + "play-services-resolver_v1.2.87.0.txt") + self.assertFalse(os.path.exists(target_path)) + + export_unity_package.copy_and_set_rwx(source_path, target_path) + self.assertTrue(os.path.exists(target_path)) + self.assertTrue(filecmp.cmp(source_path, target_path)) + self.assertEqual(self.expected_mode, + os.stat(target_path).st_mode & stat.S_IRWXU) + + def test_copy_and_set_rwx_new_dir(self): + """Copy a file into a non-existent directory.""" + source_path = os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt") + target_path = os.path.join( + self.temp_dir, + "a/nonexistent/directory/play-services-resolver_v1.2.87.0.txt") + self.assertFalse(os.path.exists(target_path)) + + export_unity_package.copy_and_set_rwx(source_path, target_path) + self.assertTrue(os.path.exists(target_path)) + self.assertTrue(filecmp.cmp(source_path, target_path)) + self.assertEqual(self.expected_mode, + os.stat(target_path).st_mode & stat.S_IRWXU) + + def test_copy_files_to_dir(self): + """Test copying files into a directory using a variety of target paths.""" + original_copy_and_set_rwx = export_unity_package.copy_and_set_rwx + try: + copied_files = [] + export_unity_package.copy_and_set_rwx = ( + lambda source, target: copied_files.append((source, target))) + self.assertEqual( + ["an/output/dir/something/to/copy.txt", + "an/output/dir/a/target/path.txt", + "an/output/dir/some/root/path.txt"], + export_unity_package.copy_files_to_dir( + ["something/to/copy.txt", + "something/else/to_copy.txt:a/target/path.txt", + "yet/another/file.txt:/some/root/path.txt"], + "an/output/dir")) + self.assertEqual( + [("something/to/copy.txt", "an/output/dir/something/to/copy.txt"), + ("something/else/to_copy.txt", "an/output/dir/a/target/path.txt"), + ("yet/another/file.txt", "an/output/dir/some/root/path.txt")], + copied_files) + finally: + export_unity_package.copy_and_set_rwx = original_copy_and_set_rwx + + def test_copy_dir_to_dir(self): + """Test copying directory into a directory recursively.""" + source_path = os.path.join( + self.assets_dir, + "PlayServicesResolver") + target_path = os.path.join( + FLAGS.test_tmpdir, + "a/nonexistent/directory") + self.assertFalse(os.path.exists(target_path)) + + export_unity_package.copy_and_set_rwx(source_path, target_path) + self.assertTrue(os.path.exists(target_path)) + cmp_result = filecmp.dircmp(source_path, target_path) + self.assertFalse(cmp_result.left_only or cmp_result.right_only or + cmp_result.diff_files) + # NOTE: Folders have the executable bit set on Windows. + self.assertEqual( + stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR, + os.stat(os.path.join(target_path, "Editor")).st_mode & stat.S_IRWXU) + self.assertEqual( + self.expected_mode, + (os.stat(os.path.join(target_path, "Editor.meta")).st_mode & + stat.S_IRWXU)) + + def test_find_in_dirs(self): + """Test find_in_dirs.""" + self.assertEqual( + export_unity_package.find_in_dirs( + "PlayServicesResolver", [self.assets_dir]), + os.path.join(self.assets_dir, "PlayServicesResolver")) + self.assertEqual( + export_unity_package.find_in_dirs( + "PlayServicesResolver/Editor.meta", [self.assets_dir]), + os.path.join(self.assets_dir, "PlayServicesResolver/Editor.meta")) + self.assertEqual( + export_unity_package.find_in_dirs("PlayServicesResolver", []), None) + self.assertEqual( + export_unity_package.find_in_dirs( + "a/nonexisting/file", [self.assets_dir]), + None) + +class ReadJsonFileTest(absltest.TestCase): + """Test reading a JSON file.""" + + def setUp(self): + """Create a temporary directory.""" + super(ReadJsonFileTest, self).setUp() + self.temp_dir = os.path.join(FLAGS.test_tmpdir, "json_temp") + os.makedirs(self.temp_dir) + + def tearDown(self): + """Clean up the temporary directory.""" + super(ReadJsonFileTest, self).tearDown() + shutil.rmtree(self.temp_dir) + + def test_read_json_file_into_ordered_dict(self): + """Read JSON into an OrderedDict.""" + json_filename = os.path.join(self.temp_dir, "test.json") + with open(json_filename, "wt") as json_file: + json_file.write("{\n" + " \"this\": \"is\",\n" + " \"just\": \"a\",\n" + " \"test\": \"of\",\n" + " \"ordered\": \"json\",\n" + " \"parsing\": 1\n" + "}\n") + dictionary = export_unity_package.read_json_file_into_ordered_dict( + json_filename) + self.assertEqual( + collections.OrderedDict([("this", "is"), + ("just", "a"), + ("test", "of"), + ("ordered", "json"), + ("parsing", 1)]), dictionary) + + def test_read_json_file_into_ordered_dict_missing_file(self): + """Try to read a non-existent JSON file.""" + with self.assertRaises(IOError) as context: + export_unity_package.read_json_file_into_ordered_dict( + os.path.join(self.temp_dir, "missing.json")) + self.assertRegexMatch(str(context.exception), + [r".*missing\.json"]) + + def test_read_json_file_into_ordered_dict_malformed_json(self): + """Try to read invalid JSON.""" + json_filename = os.path.join(self.temp_dir, "test.json") + with open(json_filename, "wt") as json_file: + json_file.write("{\n" + " \"json\": \"dislikes\",\n" + " \"trailing\": \"commas\",\n" + "}\n") + with self.assertRaises(ValueError) as context: + export_unity_package.read_json_file_into_ordered_dict( + json_filename) + self.assertRegexMatch(str(context.exception), + [r".*test\.json"]) + + +if __name__ == "__main__": + absltest.main() diff --git a/source/ExportUnityPackage/gen_guids.py b/source/ExportUnityPackage/gen_guids.py new file mode 100755 index 00000000..77e322f9 --- /dev/null +++ b/source/ExportUnityPackage/gen_guids.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +r"""This script generates stable guids for Unity asset paths. + +Since the command for this is typically generated from a blaze script, the paths +passed in are typically relative to the root google3 dir. So we'll use the +fact that this script is in google3 somewhere to find the root and fix the +paths to be fully qualified. + +The json file is expected to already exist as a precaution to avoid accidentally +creating a different one in the wrong location. The guids json file has a simple +format: +{ + "": { + "": "", + ... + }, + ... +} + +Example usage (output from blaze): + +python firebase/app/client/unity/gen_guids.py \ + --guids_file="firebase/app/client/unity/unity_packer/guids.json" \ + --version="1.0.0" \ + "Firebase/Plugins/Firebase.Database.dll" \ + "Firebase/Plugins/Firebase.Database.Unity.dll" + +""" + +import json +import os +import re +import uuid + +from absl import app +from absl import flags +from absl import logging +import distutils.version + +FLAGS = flags.FLAGS + +flags.DEFINE_string("guids_file", None, + "Path to the guids.json file to modify.") +flags.DEFINE_string("version", "1.0.0", + "The plugin version the GUID will be created for.") +flags.DEFINE_boolean("generate_new_guids", False, + "Whether to generate new GUIDs for command line " + "specified files (arguments following the flags). " + "If this is disabled, GUIDs will only be generated for " + "files that are not referenced by any package version.") + +GUID_REGEX = re.compile(r"^[0-9a-fA-F]{32}$") + + +def validate_guid_data(guid_data): + """Validate the specified matches the expected format for plugin asset GUIDs. + + Args: + guid_data: Dictionary in the following form to validate... + { + "": { + "": "", + ... + }, + ... + } + where is a plugin version, is the path of the asset + within a plugin and is the GUID assigned to the asset. + + Raises: + ValueError: If the dictionary doesn't match the expected format. + """ + # Validate the dictionary. + for version, guids_by_asset_paths in guid_data.items(): + if not isinstance(guids_by_asset_paths, dict): + raise ValueError("Version %s contains invalid GUID object %s" % + (version, guids_by_asset_paths)) + # version can be anything that can be converted to a string. + for asset_path, guid in guids_by_asset_paths.items(): + if not GUID_REGEX.match(guid): + raise ValueError("Version %s, asset path %s references invalid " + "GUID %s" % (version, asset_path, guid)) + + +def read_guid_data(filename): + """Read GUIDs from JSON file. + + Args: + filename: File to read asset GUIDs from. + + Returns: + Dictionary in the form expected by validate_guid_data(). + + Raises: + ValueError: If the JSON file doesn't match the expected format. + """ + with open(filename, "r") as guids_file: + guid_data = json.load(guids_file) + validate_guid_data(guid_data) + return guid_data + + +def remove_duplicate_guids(guid_data): + """Remove duplicate GUIDs that are present for prior versions of a plugin. + + Args: + guid_data: Dictionary in the form expected by validate_guid_data(). + This dictionary is modified in-place. + """ + # Compress map by removing duplicate GUIDs. + sorted_versions = sorted(guid_data, key=distutils.version.LooseVersion) + for version_index, version in enumerate(sorted_versions): + current_guids_by_filename = guid_data[version] + for filename in list(current_guids_by_filename.keys()): + current_guid = current_guids_by_filename[filename] + # Iterate through all versions prior to the current version. + for previous_version_index in range(version_index - 1, 0, -1): + # If the previous version contains the current GUID, remove it from the + # current map. + previous_guids_by_filename = ( + guid_data[sorted_versions[previous_version_index]]) + if previous_guids_by_filename.get(filename) == current_guid: + del current_guids_by_filename[filename] + break + + +def get_guids_by_asset_paths_for_version(guid_data, version): + """Get asset GUIDs by path for a plugin version. + + Args: + guid_data: Data to query for asset GUIDs. This should be a dictionary in + the form expected by validate_guid_data(). + version: Version dictionary to find in guid_data. + + Returns: + Dictionary that is referenced by the "version" section of the guid_data + dictionary. The returned value can be mutated to extend the guid_data + object. + """ + guids_by_asset_paths = guid_data.get(version, {}) + guid_data[version] = guids_by_asset_paths + return guids_by_asset_paths + + +def get_all_asset_paths(guid_data, version): + """Get all asset paths and their newest associated GUIDs. + + Args: + guid_data: Dictionary in the form expected by validate_guid_data(). + version: Maximum version to search for assets. + + Returns: + Dictionary of asset path to GUID. + """ + all_guids_by_asset_paths = {} + # Aggregate guids for older versions of files. + max_version = distutils.version.LooseVersion(version) + for current_version in sorted(guid_data, key=distutils.version.LooseVersion, + reverse=True): + # Skip all versions after the current version. + if distutils.version.LooseVersion(current_version) > max_version: + continue + # Add all guids for files to the current version. + guids_by_asset_paths = guid_data[current_version] + for asset_path, guid in guids_by_asset_paths.items(): + if asset_path not in all_guids_by_asset_paths: + all_guids_by_asset_paths[asset_path] = guid + return all_guids_by_asset_paths + + +def generate_guids_for_asset_paths(guid_data, version, asset_paths, + generate_new_guids): + """Generate GUIDs for a set of asset paths. + + Args: + guid_data: Dictionary in the form expected by validate_guid_data() to insert + the GUIDs into. + version: Plugin version the asset paths were introduced. + asset_paths: Asset paths to generate GUIDs for. These paths should be the + location of the assets when imported in a Unity plugin. + generate_new_guids: Whether to generate new GUIDs for files that have GUIDs + in prior versions of the plugin. + """ + all_guids_by_asset_paths = get_all_asset_paths(guid_data, version) + guids_by_asset_paths = get_guids_by_asset_paths_for_version(guid_data, + version) + for asset_path in set(asset_paths): + new_guid = uuid.uuid4().hex + if generate_new_guids: + guids_by_asset_paths[asset_path] = new_guid + elif asset_path not in all_guids_by_asset_paths: + guids_by_asset_paths[asset_path] = ( + guids_by_asset_paths.get(asset_path, new_guid)) + + +def write_guid_data(filename, guid_data): + """Write a GUIDs JSON file. + + Args: + filename: File to write data to. + guid_data: Dictionary in the form expected by validate_guid_data() to write + to the file. + """ + output = json.dumps(guid_data, indent=4, sort_keys=True) + with open(filename, "wt") as guids_file: + for line in output.splitlines(): + guids_file.write(line.rstrip() + "\n") + + +def main(argv_paths): + """Generates stable guids for Unity package assets. + + Since the command for this is typically generated from a blaze rule, the paths + passed in are typically relative to the root google3 dir. So we'll use the + fact that this script is in google3 somewhere to find the root and fix the + paths to be fully qualified. + + Args: + argv_paths: List of google3 relative paths to generate new guids for. The + relative script path is included in index 0, and is ignored. + + Returns: + The exit code status; 1 for error, 0 for success. + """ + # if it's not a cwd relative path, check if it's a google3 relative path. + guids_file_path = FLAGS.guids_file + if not os.path.exists(guids_file_path): + logging.error("Could not find the guids file (%s) to modify. If you wish " + "to start a new guids file at this path, please create the " + "empty file first.", FLAGS.guids_file) + return 1 + + guid_data = read_guid_data(guids_file_path) + remove_duplicate_guids(guid_data) + generate_guids_for_asset_paths(guid_data, FLAGS.version, set(argv_paths[1:]), + FLAGS.generate_new_guids) + write_guid_data(guids_file_path, guid_data) + return 0 + + +if __name__ == "__main__": + flags.mark_flag_as_required("guids_file") + app.run(main) diff --git a/source/ExportUnityPackage/gen_guids_test.py b/source/ExportUnityPackage/gen_guids_test.py new file mode 100755 index 00000000..c098b936 --- /dev/null +++ b/source/ExportUnityPackage/gen_guids_test.py @@ -0,0 +1,168 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for gen_guids.py script.""" + +import copy +import json +import os +import shutil +import sys + +from absl import flags +from absl.testing import absltest + +# pylint: disable=C6204 +# pylint: disable=W0403 +sys.path.append(os.path.dirname(__file__)) +import gen_guids +# pylint: enable=C6204 +# pylint: enable=W0403 + +FLAGS = flags.FLAGS + + +TEST_JSON = """\ +{ + "0.0.1": { + "pea/nut": "7311924048bd457bac6d713576c952da" + }, + "1.2.3": { + "foo/bar": "ba9f9118207d46248936105077947525", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1" + }, + "2.3.4": { + "foo/bar": "ae88c0972b7448b5b36def1716f1d711", + "a/new/file": "816270c2a2a348e59cb9b7b096a24f50" + } +} +""" + +# Dictionary representation of TEST_JSON +TEST_DICT = { + "0.0.1": { + "pea/nut": "7311924048bd457bac6d713576c952da", + }, + "1.2.3": { + "foo/bar": "ba9f9118207d46248936105077947525", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1" + }, + "2.3.4": { + "foo/bar": "ae88c0972b7448b5b36def1716f1d711", + "a/new/file": "816270c2a2a348e59cb9b7b096a24f50" + } +} + + +def delete_temporary_directory_contents(): + """Delete the contents of the temporary directory.""" + # If the temporary directory is populated, delete everything in there. + directory = FLAGS.test_tmpdir + for path in os.listdir(directory): + full_path = os.path.join(directory, path) + if os.path.isdir(full_path): + shutil.rmtree(full_path) + else: + os.unlink(full_path) + + +class GenGuidsTest(absltest.TestCase): + """Test the gen_guids module.""" + + def tearDown(self): + """Clean up the temporary directory.""" + delete_temporary_directory_contents() + super(GenGuidsTest, self).tearDown() + + def test_validate_guid_data(self): + """Ensure validate_guid_data() catches invalid JSON data.""" + gen_guids.validate_guid_data({}) + gen_guids.validate_guid_data({"1.2.3": {}}) + gen_guids.validate_guid_data( + {"1.2.3": {"foo/bar": "0123456789abcdef0123456789abcdef"}}) + with self.assertRaises(ValueError): + gen_guids.validate_guid_data({"1.2.3": {"foo/bar": "notaguid"}}) + + def test_read_guid_data(self): + """Read GUIDs from a JSON file.""" + guids_filename = os.path.join(FLAGS.test_tmpdir, "guids.json") + with open(guids_filename, "w") as guids_file: + guids_file.write(TEST_JSON) + guids_data = gen_guids.read_guid_data(guids_filename) + self.assertDictEqual(TEST_DICT, guids_data) + + def test_write_guid_data(self): + """Write GUIDs to a JSON file.""" + guids_filename = os.path.join(FLAGS.test_tmpdir, "guids.json") + gen_guids.write_guid_data(guids_filename, TEST_DICT) + with open(guids_filename, "rt") as guids_file: + guids_json = guids_file.read() + self.assertDictEqual(TEST_DICT, json.loads(guids_json)) + + def test_remove_duplicate_guids(self): + """Ensure duplicate GUIDs are removed from a dictionary of asset GUIDs.""" + duplicate_data = copy.deepcopy(TEST_DICT) + duplicate_data["2.3.4"]["bish/bosh"] = "a308bff8c54a4c8d987dad79e96ed5e1" + gen_guids.remove_duplicate_guids(duplicate_data) + self.assertDictEqual(TEST_DICT, duplicate_data) + + def test_get_guids_by_asset_paths_for_version(self): + """Get GUIDs for each asset path for a specific version.""" + guids_by_asset_paths = gen_guids.get_guids_by_asset_paths_for_version( + copy.deepcopy(TEST_DICT), "1.2.3") + self.assertDictEqual({ + "foo/bar": "ba9f9118207d46248936105077947525", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1" + }, guids_by_asset_paths) + self.assertDictEqual({}, gen_guids.get_guids_by_asset_paths_for_version( + copy.deepcopy(TEST_DICT), "100.0.0")) + + def test_get_all_asset_paths(self): + """Get GUIDs for all assets up to the specified version.""" + guids_by_asset_paths = gen_guids.get_all_asset_paths(TEST_DICT, "1.2.3") + self.assertDictEqual({ + "pea/nut": "7311924048bd457bac6d713576c952da", + "foo/bar": "ba9f9118207d46248936105077947525", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1" + }, guids_by_asset_paths) + guids_by_asset_paths = gen_guids.get_all_asset_paths(TEST_DICT, "2.3.4") + self.assertDictEqual({ + "pea/nut": "7311924048bd457bac6d713576c952da", + "foo/bar": "ae88c0972b7448b5b36def1716f1d711", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1", + "a/new/file": "816270c2a2a348e59cb9b7b096a24f50" + }, guids_by_asset_paths) + + def test_generate_guids_for_asset_paths(self): + """Generate GUIDs for a set of asset paths.""" + guid_data = copy.deepcopy(TEST_DICT) + gen_guids.generate_guids_for_asset_paths( + guid_data, "2.3.4", ("pea/nut", "another/file", "more/things"), False) + self.assertEqual(TEST_DICT["0.0.1"]["pea/nut"], + guid_data["0.0.1"].get("pea/nut")) + self.assertNotEmpty(guid_data["2.3.4"].get("another/file")) + self.assertNotEmpty(guid_data["2.3.4"].get("more/things")) + + guid_data = copy.deepcopy(TEST_DICT) + gen_guids.generate_guids_for_asset_paths( + guid_data, "2.3.4", ("pea/nut", "another/file", "more/things"), True) + self.assertNotEqual(TEST_DICT["0.0.1"]["pea/nut"], + guid_data["2.3.4"].get("pea/nut")) + self.assertNotEmpty(guid_data["2.3.4"].get("pea/nut")) + self.assertNotEmpty(guid_data["2.3.4"].get("another/file")) + self.assertNotEmpty(guid_data["2.3.4"].get("more/things")) + + +if __name__ == "__main__": + absltest.main() diff --git a/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Analytics.dll b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Analytics.dll new file mode 100644 index 00000000..0399d610 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Analytics.dll @@ -0,0 +1 @@ +Firebase.Analytics.dll diff --git a/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.App.dll b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.App.dll new file mode 100644 index 00000000..0ccb132d --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.App.dll @@ -0,0 +1 @@ +Firebase.App.dll diff --git a/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Auth.dll b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Auth.dll new file mode 100644 index 00000000..26d4f1c7 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Auth.dll @@ -0,0 +1 @@ +Firebase.Auth.dll diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Doc/index.md b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Doc/index.md new file mode 100644 index 00000000..e89fa552 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Doc/index.md @@ -0,0 +1 @@ +A documentation file diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.mdb.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta similarity index 52% rename from exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.mdb.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta index c7261431..457843c6 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.mdb.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta @@ -1,9 +1,7 @@ fileFormatVersion: 2 -guid: b0c0bcc139fb4135aa08213dbad27b2a -labels: -- gvh_version-1.2.128.0 -- gvh -timeCreated: 1538009133 +guid: e105e00cdce8456482d26b1fcd1ca47d +folderAsset: yes +timeCreated: 1448926516 licenseType: Pro DefaultImporter: userData: diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/CHANGELOG.md b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/CHANGELOG.md new file mode 100644 index 00000000..5568b66b --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/CHANGELOG.md @@ -0,0 +1 @@ +A changelog file diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll new file mode 100644 index 00000000..15e7ecd0 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll @@ -0,0 +1 @@ +Google.IOSResolver_v1.2.87.0.dll diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta similarity index 86% rename from exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta index c6955559..367d2e9e 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.128.0.dll.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: 0d921e6d65a843abab7c9775a3d065cb +guid: 5889e1be319e46899d75db5bdf372fb9 labels: -- gvh_version-1.2.128.0 +- gvh_v1.2.87.0 - gvh -- gvh_targets-editor +- gvh_teditor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll new file mode 100644 index 00000000..99e97b7c --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll @@ -0,0 +1 @@ +Google.JarResolver_v1.2.87.0.dll diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta similarity index 86% rename from plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta index c9c306ee..9a357d5b 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: 519a52f7f22e4953a1f8eb29922145e1 +guid: 380e8ddf4f4a46bfa31759d4ad1e82bc labels: -- gvh_version-1.2.128.0 +- gvh_v1.2.87.0 - gvh -- gvh_targets-editor +- gvh_teditor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll new file mode 100644 index 00000000..0eee039d --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll @@ -0,0 +1 @@ +Google.VersionHandler.dll diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta similarity index 86% rename from plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta index 2b2485ac..43e26f6a 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: bb6999c8a5ce4ba99688ec579babe5b7 +guid: 06f6f385a4ad409884857500a3c04441 labels: -- gvh_version-1.2.128.0 +- gvh_v1.2.86.0 - gvh -- gvh_targets-editor +- gvh_teditor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll new file mode 100644 index 00000000..3afddb47 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll @@ -0,0 +1 @@ +Google.VersionHandlerImpl_v1.2.87.0.dll diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta similarity index 86% rename from exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta index b362930b..fc4128d4 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.128.0.dll.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: 59f63fba63124b4a8d19bf039e14e11b +guid: dc0556b86e9e4751a1553508cd89142f labels: -- gvh_version-1.2.128.0 +- gvh_v1.2.87.0 - gvh -- gvh_targets-editor +- gvh_teditor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/LICENSE b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/LICENSE new file mode 100644 index 00000000..d0a85ab1 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/LICENSE @@ -0,0 +1 @@ +A license file diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/README.md b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/README.md new file mode 100644 index 00000000..378dddb9 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/README.md @@ -0,0 +1 @@ +A readme file diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt new file mode 100644 index 00000000..c3495665 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt @@ -0,0 +1,4 @@ +Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll +Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll +Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta new file mode 100644 index 00000000..4b034385 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4a1d3acb6c7545ffbd0b6601e3f57aee +labels: +- gvh_v1.2.87.0 +- gvh +- gvh_manifest +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/ExternalDependencyManager.sln b/source/ExternalDependencyManager.sln new file mode 100644 index 00000000..9dda1103 --- /dev/null +++ b/source/ExternalDependencyManager.sln @@ -0,0 +1,89 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JarResolverLib", "JarResolverLib\JarResolverLib.csproj", "{CC4F239D-3C7F-4164-830F-9215AE15B32A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JarResolverTests", "JarResolverTests\JarResolverTests.csproj", "{593254D7-6358-40A6-B0C8-F0616BBF499D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTester", "IntegrationTester\IntegrationTester.csproj", "{DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidResolver", "AndroidResolver\AndroidResolver.csproj", "{82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidResolverIntegrationTests", "AndroidResolver\test\AndroidResolverIntegrationTests.csproj", "{6CB19B5A-371A-5E50-A3F7-E24F1696D501}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandler", "VersionHandler\VersionHandler.csproj", "{5378B37A-887E-49ED-A8AE-42FA843AA9DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandlerImpl", "VersionHandlerImpl\VersionHandlerImpl.csproj", "{1E162334-8EA2-440A-9B3A-13FD8FE5C22E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandlerImplTests", "VersionHandlerImpl\unit_tests\VersionHandlerImplTests.csproj", "{9FD33878-5B6B-411D-A3C7-D2A5E7E63182}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOSResolver", "IOSResolver\IOSResolver.csproj", "{5B581BAE-D432-41AB-AEED-FD269AEA081D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerResolver", "PackageManagerResolver\PackageManagerResolver.csproj", "{77EBE819-CBE6-4CA8-A791-ED747EA29D30}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerResolverTests", "PackageManagerResolver\unit_tests\PackageManagerResolverTests.csproj", "{FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerClientIntegrationTests", "PackageManagerResolver\test\PackageManagerClientIntegrationTests.csproj", "{00A0DB5E-1120-24C9-331A-BE692C1F7C01}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageMigratorIntegrationTests", "PackageManagerResolver\test\PackageMigratorIntegrationTests.csproj", "{4DBDEE33-4B6C-A866-93FE-04C15486BB03}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {593254D7-6358-40A6-B0C8-F0616BBF499D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {593254D7-6358-40A6-B0C8-F0616BBF499D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {593254D7-6358-40A6-B0C8-F0616BBF499D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {593254D7-6358-40A6-B0C8-F0616BBF499D}.Release|Any CPU.Build.0 = Release|Any CPU + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}.Release|Any CPU.Build.0 = Release|Any CPU + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Release|Any CPU.Build.0 = Release|Any CPU + {6CB19B5A-371A-5E50-A3F7-E24F1696D501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CB19B5A-371A-5E50-A3F7-E24F1696D501}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CB19B5A-371A-5E50-A3F7-E24F1696D501}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CB19B5A-371A-5E50-A3F7-E24F1696D501}.Release|Any CPU.Build.0 = Release|Any CPU + {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Release|Any CPU.Build.0 = Release|Any CPU + {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Release|Any CPU.Build.0 = Release|Any CPU + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Release|Any CPU.Build.0 = Release|Any CPU + {9FD33878-5B6B-411D-A3C7-D2A5E7E63182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FD33878-5B6B-411D-A3C7-D2A5E7E63182}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FD33878-5B6B-411D-A3C7-D2A5E7E63182}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FD33878-5B6B-411D-A3C7-D2A5E7E63182}.Release|Any CPU.Build.0 = Release|Any CPU + {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Release|Any CPU.Build.0 = Release|Any CPU + {77EBE819-CBE6-4CA8-A791-ED747EA29D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77EBE819-CBE6-4CA8-A791-ED747EA29D30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77EBE819-CBE6-4CA8-A791-ED747EA29D30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77EBE819-CBE6-4CA8-A791-ED747EA29D30}.Release|Any CPU.Build.0 = Release|Any CPU + {FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}.Release|Any CPU.Build.0 = Release|Any CPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01}.Release|Any CPU.Build.0 = Release|Any CPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/source/IOSResolver/IOSResolver.csproj b/source/IOSResolver/IOSResolver.csproj index a4a0864b..e16e2abf 100644 --- a/source/IOSResolver/IOSResolver.csproj +++ b/source/IOSResolver/IOSResolver.csproj @@ -51,7 +51,7 @@ - ..\PlayServicesResolver\bin\Release\Google.JarResolver.dll + ..\AndroidResolver\bin\Release\Google.JarResolver.dll ..\VersionHandler\bin\Release\Google.VersionHandler.dll @@ -72,9 +72,9 @@ - + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} - PlayServicesResolver + AndroidResolver {5378B37A-887E-49ED-A8AE-42FA843AA9DC} diff --git a/source/IOSResolver/src/IOSResolver.cs b/source/IOSResolver/src/IOSResolver.cs index a3640493..a272810e 100644 --- a/source/IOSResolver/src/IOSResolver.cs +++ b/source/IOSResolver/src/IOSResolver.cs @@ -72,12 +72,12 @@ private class Pod { /// Since order is important sources specified in this list /// are interleaved across each Pod added to the resolver. /// e.g Pod1.source[0], Pod2.source[0] ... - //// Pod1.source[1], Pod2.source[1] etc. + /// Pod1.source[1], Pod2.source[1] etc. /// /// See: https://guides.cocoapods.org/syntax/podfile.html#source /// public List sources = new List() { - "https://github.com/CocoaPods/Specs.git" + "https://cdn.cocoapods.org/" }; /// @@ -86,6 +86,11 @@ private class Pod { /// public string minTargetSdk = null; + /// + /// Whether to add this pod to all targets when multiple + /// + public bool addToAllTargets = false; + /// /// Tag that indicates where this was created. /// @@ -162,6 +167,8 @@ public string PodFilePodLine { /// bitcode. /// Minimum target SDK revision required by /// this pod. + /// Whether to add this pod to all targets when multiple + /// target is supported. /// List of sources to search for all pods. /// Each source is a URL that is injected in the source section of a Podfile /// See https://guides.cocoapods.org/syntax/podfile.html#source for the description of @@ -169,7 +176,8 @@ public string PodFilePodLine { /// Dictionary of additional properties for the pod /// reference. public Pod(string name, string version, bool bitcodeEnabled, string minTargetSdk, - IEnumerable sources, Dictionary propertiesByName) { + bool addToAllTargets, IEnumerable sources, + Dictionary propertiesByName) { this.name = name; this.version = version; if (propertiesByName != null) { @@ -177,6 +185,7 @@ public Pod(string name, string version, bool bitcodeEnabled, string minTargetSdk } this.bitcodeEnabled = bitcodeEnabled; this.minTargetSdk = minTargetSdk; + this.addToAllTargets = addToAllTargets; if (sources != null) { var allSources = new List(sources); allSources.AddRange(this.sources); @@ -186,9 +195,9 @@ public Pod(string name, string version, bool bitcodeEnabled, string minTargetSdk /// /// Convert min target SDK to an integer in the form - // (major * 10) + minor. + /// (major * 10) + minor. /// - /// Numeric minimum SDK revision required by this pod. + /// Numeric minimum SDK revision required by this pod. public int MinTargetSdkToVersion() { string sdkString = String.IsNullOrEmpty(minTargetSdk) ? "0.0" : minTargetSdk; @@ -305,6 +314,7 @@ protected override bool Read(string filename, Logger logger) { string versionSpec = null; bool bitcodeEnabled = true; string minTargetSdk = null; + bool addToAllTargets = false; var propertiesByName = new Dictionary(); if (!XmlUtilities.ParseXmlTextFileElements( filename, logger, @@ -328,10 +338,15 @@ protected override bool Read(string filename, Logger logger) { } versionSpec = reader.GetAttribute("version"); var bitcodeEnabledString = - (reader.GetAttribute("bitcode") ?? "").ToLower(); + (reader.GetAttribute("bitcodeEnabled") ?? "").ToLower(); bitcodeEnabled |= trueStrings.Contains(bitcodeEnabledString); bitcodeEnabled &= !falseStrings.Contains(bitcodeEnabledString); + var addToAllTargetsString = + (reader.GetAttribute("addToAllTargets") ?? "").ToLower(); + addToAllTargets |= trueStrings.Contains(addToAllTargetsString); + addToAllTargets &= !falseStrings.Contains(addToAllTargetsString); minTargetSdk = reader.GetAttribute("minTargetSdk"); + sources = new List(); if (podName == null) { logger.Log( @@ -344,6 +359,7 @@ protected override bool Read(string filename, Logger logger) { AddPodInternal(podName, preformattedVersion: versionSpec, bitcodeEnabled: bitcodeEnabled, minTargetSdk: minTargetSdk, + addToAllTargets: addToAllTargets, sources: sources, overwriteExistingPod: false, createdBy: String.Format("{0}:{1}", @@ -375,7 +391,9 @@ protected override bool Read(string filename, Logger logger) { } return true; } - return false; + // Ignore unknown tags so that different configurations can be stored in the + // same file. + return true; })) { return false; } @@ -388,12 +406,12 @@ protected override bool Read(string filename, Logger logger) { new SortedDictionary(); // Order of post processing operations. - private const int BUILD_ORDER_REFRESH_DEPENDENCIES = 1; - private const int BUILD_ORDER_CHECK_COCOAPODS_INSTALL = 2; - private const int BUILD_ORDER_PATCH_PROJECT = 3; - private const int BUILD_ORDER_GEN_PODFILE = 4; - private const int BUILD_ORDER_INSTALL_PODS = 5; - private const int BUILD_ORDER_UPDATE_DEPS = 6; + private const int BUILD_ORDER_REFRESH_DEPENDENCIES = 10; + private const int BUILD_ORDER_CHECK_COCOAPODS_INSTALL = 20; + private const int BUILD_ORDER_PATCH_PROJECT = 30; + private const int BUILD_ORDER_GEN_PODFILE = 40; + private const int BUILD_ORDER_INSTALL_PODS = 50; + private const int BUILD_ORDER_UPDATE_DEPS = 60; // This is appended to the Podfile filename to store a backup of the original Podfile. // ie. "Podfile_Unity". @@ -405,6 +423,21 @@ protected override bool Read(string filename, Logger logger) { " > sudo gem install -n /usr/local/bin cocoapods\n" + " > pod setup"); + // Dialog box text when the podfile version exceeds the currently configured + // target iOS or tvOS sdk version. + private const string TARGET_SDK_NEEDS_UPDATE_STRING = ( + "Target SDK selected in the {0} Player Settings ({1}) is not supported by " + + "the Cocoapods included in this project. The build will very " + + "likely fail. The minimum supported version is \"{2}\" required " + + "by pods ({3})\n" + + "Would you like to update the target SDK version?"); + + // Dialog box text when the IOSResolver has updated the target sdk version on behalf + // of the user. + private const string UPDATED_TARGET_SDK_STRING = ( + "Target {0} SDK has been updated from {1} to {2}. " + + "You must restart the build for this change to take effect."); + // Pod executable filename. private static string POD_EXECUTABLE = "pod"; // Default paths to search for the "pod" command before falling back to @@ -412,6 +445,7 @@ protected override bool Read(string filename, Logger logger) { private static string[] POD_SEARCH_PATHS = new string[] { "/usr/local/bin", "/usr/bin", + "/opt/homebrew/bin", }; // Ruby Gem executable filename. private static string GEM_EXECUTABLE = "gem"; @@ -444,6 +478,9 @@ protected override bool Read(string filename, Logger logger) { // Whether execution of the pod tool is performed via the shell. private const string PREFERENCE_POD_TOOL_EXECUTION_VIA_SHELL_ENABLED = PREFERENCE_NAMESPACE + "PodToolExecutionViaShellEnabled"; + // Whether environment variables should be set when execution is performed via the shell. + private const string PREFERENCE_POD_TOOL_SHELL_EXECUTION_SET_LANG = + PREFERENCE_NAMESPACE + "PodToolShellExecutionSetLang"; // Whether to try to install Cocoapods tools when iOS is selected as the target platform. private const string PREFERENCE_AUTO_POD_TOOL_INSTALL_IN_EDITOR = PREFERENCE_NAMESPACE + "AutoPodToolInstallInEditor"; @@ -453,6 +490,26 @@ protected override bool Read(string filename, Logger logger) { // Whether to skip pod install when using workspace integration. private const string PREFERENCE_SKIP_POD_INSTALL_WHEN_USING_WORKSPACE_INTEGRATION = PREFERENCE_NAMESPACE + "SkipPodInstallWhenUsingWorkspaceIntegration"; + // Whether to add "use_frameworks!" in the Podfile. + private const string PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS = + PREFERENCE_NAMESPACE + "PodfileAddUseFrameworks"; + // Whether to statically link framework in the Podfile. + private const string PREFERENCE_PODFILE_STATIC_LINK_FRAMEWORKS = + PREFERENCE_NAMESPACE + "PodfileStaticLinkFrameworks"; + // Whether to add Dummy.swift to the generated Xcode project as a workaround to support pods + // with Swift Framework. + private const string PREFERENCE_SWIFT_FRAMEWORK_SUPPORT_WORKAROUND = + PREFERENCE_NAMESPACE + "SwiftFrameworkSupportWorkaroundEnabled"; + // The Swift Language Version (SWIFT_VERSION) to update to the generated Xcode Project. + private const string PREFERENCE_SWIFT_LANGUAGE_VERSION = + PREFERENCE_NAMESPACE + "SwiftLanguageVersion"; + // Whether to add an main target to Podfile for Unity 2019.3+. + private const string PREFERENCE_PODFILE_ALWAYS_ADD_MAIN_TARGET = + PREFERENCE_NAMESPACE + "PodfileAlwaysAddMainTarget"; + // Whether to allow the same pods to be in multiple targets, if specified in Dependecies.xml. + private const string PREFERENCE_PODFILE_ALLOW_PODS_IN_MULTIPLE_TARGETS = + PREFERENCE_NAMESPACE + "PodfileAllowPodsInMultipleTargets"; + // List of preference keys, used to restore default settings. private static string[] PREFERENCE_KEYS = new [] { PREFERENCE_COCOAPODS_INSTALL_ENABLED, @@ -462,7 +519,13 @@ protected override bool Read(string filename, Logger logger) { PREFERENCE_POD_TOOL_EXECUTION_VIA_SHELL_ENABLED, PREFERENCE_AUTO_POD_TOOL_INSTALL_IN_EDITOR, PREFERENCE_WARN_UPGRADE_WORKSPACE, - PREFERENCE_SKIP_POD_INSTALL_WHEN_USING_WORKSPACE_INTEGRATION + PREFERENCE_SKIP_POD_INSTALL_WHEN_USING_WORKSPACE_INTEGRATION, + PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS, + PREFERENCE_PODFILE_STATIC_LINK_FRAMEWORKS, + PREFERENCE_SWIFT_FRAMEWORK_SUPPORT_WORKAROUND, + PREFERENCE_SWIFT_LANGUAGE_VERSION, + PREFERENCE_PODFILE_ALWAYS_ADD_MAIN_TARGET, + PREFERENCE_PODFILE_ALLOW_PODS_IN_MULTIPLE_TARGETS }; // Whether the xcode extension was successfully loaded. @@ -492,8 +555,12 @@ protected override bool Read(string filename, Logger logger) { private static string PODFILE_GENERATED_COMMENT = "# IOSResolver Generated Podfile"; // Default iOS target SDK if the selected version is invalid. - private const int DEFAULT_TARGET_SDK = 82; - // Valid iOS target SDK version. + private const int DEFAULT_IOS_TARGET_SDK = 82; + + // Default tvOS target SDK if the selected version is invalid. + private const string DEFAULT_TVOS_TARGET_SDK = "12.0"; + + // Valid iOS / tvOS target SDK version. private static Regex TARGET_SDK_REGEX = new Regex("^[0-9]+\\.[0-9]$"); // Current window being used for a long running shell command. @@ -532,6 +599,9 @@ protected override bool Read(string filename, Logger logger) { // Parses a source URL from a Podfile. private static Regex PODFILE_SOURCE_REGEX = new Regex(@"^\s*source\s+'([^']*)'"); + // Parses comments from a Podfile + private static Regex PODFILE_COMMENT_REGEX = new Regex(@"^\s*\#"); + // Parses dependencies from XML dependency files. private static IOSXmlDependencies xmlDependencies = new IOSXmlDependencies(); @@ -539,11 +609,17 @@ protected override bool Read(string filename, Logger logger) { private static ProjectSettings settings = new ProjectSettings(PREFERENCE_NAMESPACE); /// - /// Polls for changes to TargetSdk. + /// Polls for changes to target iOS SDK version. /// - private static PlayServicesResolver.PropertyPoller targetSdkPoller = + 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( @@ -647,11 +723,36 @@ private static Assembly ResolveUnityEditoriOSXcodeExtension( /// Initialize the module. /// static IOSResolver() { + // Load log preferences. + UpdateLoggerLevel(VerboseLoggingEnabled); + // NOTE: We can't reference the UnityEditor.iOS.Xcode module in this // method as the Mono runtime in Unity 4 and below requires all // dependencies of a method are loaded before the method is executed // so we install the DLL loader first then try using the Xcode module. RemapXcodeExtension(); + + // Delay initialization until the build target is iOS and the editor is not in play + // mode. + EditorInitializer.InitializeOnMainThread(condition: () => { + return (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) && + !EditorApplication.isPlayingOrWillChangePlaymode; + }, initializer: Initialize, name: "IOSResolver", logger: logger); + } + + /// + /// Initialize the module. This should be called on the main thread only if + /// current active build target is iOS and not in play mode. + /// + private static bool Initialize() { + if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.iOS && + EditorUserBuildSettings.activeBuildTarget != BuildTarget.tvOS) { + throw new Exception("IOSResolver.Initialize() is called when active build target " + + "is not iOS+. This should never happen. If it does, please report to the " + + "developer."); + } + // NOTE: It's not possible to catch exceptions a missing reference // to the UnityEditor.iOS.Xcode assembly in this method as the runtime // will attempt to load the assembly before the method is executed so @@ -659,73 +760,74 @@ static IOSResolver() { try { InitializeTargetName(); } catch (Exception exception) { - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { - Log("Failed: " + exception.ToString(), level: LogLevel.Error); - if (exception is FileNotFoundException || - exception is TypeInitializationException || - exception is TargetInvocationException) { - // It's likely we failed to load the iOS Xcode extension. - Debug.LogWarning( - "Failed to load the " + - "UnityEditor.iOS.Extensions.Xcode dll. " + - "Is iOS support installed?"); - } else { - throw exception; - } + Log("Failed: " + exception.ToString(), level: LogLevel.Error); + if (exception is FileNotFoundException || + exception is TypeInitializationException || + exception is TargetInvocationException) { + // It's likely we failed to load the iOS Xcode extension. + Debug.LogWarning( + "Failed to load the " + + "UnityEditor.iOS.Extensions.Xcode dll. " + + "Is iOS support installed?"); + } else { + throw exception; } } // If Cocoapod tool auto-installation is enabled try installing on the first update of // the editor when the editor environment has been initialized. - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && - AutoPodToolInstallInEditorEnabled && CocoapodsIntegrationEnabled && + if (AutoPodToolInstallInEditorEnabled && CocoapodsIntegrationEnabled && !ExecutionEnvironment.InBatchMode) { - RunOnMainThread.Run(() => { AutoInstallCocoapods(); }, runNow: false); + AutoInstallCocoapods(); } - // Install / remove target SDK property poller. - SetEnablePollTargetSdk(PodfileGenerationEnabled); + // Install / remove target SDK property pollers. + SetEnablePollTargetSdks(PodfileGenerationEnabled); // Load XML dependencies on the next editor update. if (PodfileGenerationEnabled) { - RunOnMainThread.Run(RefreshXmlDependencies, runNow: false); + RefreshXmlDependencies(); } // Prompt the user to use workspaces if they aren't at least using project level // integration. - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && - (CocoapodsIntegrationMethod)settings.GetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, + if ((CocoapodsIntegrationMethod)settings.GetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, CocoapodsIntegrationUpgradeDefault) == CocoapodsIntegrationMethod.None && - !ExecutionEnvironment.InBatchMode && !UpgradeToWorkspaceWarningDisabled) { + !ExecutionEnvironment.InBatchMode && !UpgradeToWorkspaceWarningDisabled) { - switch (EditorUtility.DisplayDialogComplex( + DialogWindow.Display( "Warning: CocoaPods integration is disabled!", "Would you like to enable CocoaPods integration with workspaces?\n\n" + "Unity 5.6+ now supports loading workspaces generated from CocoaPods.\n" + "If you enable this, and still use Unity less than 5.6, it will fallback " + "to integrating CocoaPods with the .xcodeproj file.\n", - "Yes", "Not Now", "Silence Warning")) { - case 0: // Yes - settings.SetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, - (int)CocoapodsIntegrationMethod.Workspace); - break; - case 1: // Not now - break; - case 2: // Ignore - UpgradeToWorkspaceWarningDisabled = true; - break; - } + DialogWindow.Option.Selected0, "Yes", "Not Now", "Silence Warning", + complete: (selectedOption) => { + switch (selectedOption) { + case DialogWindow.Option.Selected0: // Yes + settings.SetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, + (int)CocoapodsIntegrationMethod.Workspace); + break; + case DialogWindow.Option.Selected1: // Not now + break; + case DialogWindow.Option.Selected2: // Ignore + UpgradeToWorkspaceWarningDisabled = true; + break; + } + }); } + + return true; } /// /// Link to the documentation. /// - [MenuItem("Assets/Play Services Resolver/iOS Resolver/Documentation")] + [MenuItem("Assets/External Dependency Manager/iOS Resolver/Documentation")] public static void OpenDocumentation() { - Application.OpenURL(VersionHandlerImpl.DocumentationUrl("#ios-resolver-usage")); + analytics.OpenUrl(VersionHandlerImpl.DocumentationUrl("#ios-resolver-usage"), "Usage"); } // Display the iOS resolver settings menu. - [MenuItem("Assets/Play Services Resolver/iOS Resolver/Settings")] + [MenuItem("Assets/External Dependency Manager/iOS Resolver/Settings")] public static void SettingsDialog() { IOSResolverSettingsDialog window = (IOSResolverSettingsDialog) EditorWindow.GetWindow(typeof(IOSResolverSettingsDialog), true, @@ -739,10 +841,61 @@ public static void SettingsDialog() { /// version of Unity supports multiple Xcode build targets, for example if Unity supports use /// as a library or framework. /// - private static bool MultipleXcodeTargetsSupported { + 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 typeof(UnityEditor.iOS.Xcode.PBXProject).GetMethod( - "GetUnityMainTargetGuid", Type.EmptyTypes) != null; + return MultipleXcodeTargetsSupported ? + XcodeUnityFrameworkTargetName : XcodeMainTargetName; } } @@ -756,9 +909,7 @@ 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 = MultipleXcodeTargetsSupported ? "Unity-iPhone" : - (string)VersionHandler.InvokeStaticMethod(typeof(UnityEditor.iOS.Xcode.PBXProject), - "GetUnityTargetName", null); + TARGET_NAME = XcodeMainTargetName; return TARGET_NAME; } @@ -775,6 +926,7 @@ public static void RemapXcodeExtension() { /// internal static void RestoreDefaultSettings() { settings.DeleteKeys(PREFERENCE_KEYS); + analytics.RestoreDefaultSettings(); } /// @@ -839,18 +991,24 @@ public static bool PodfileGenerationEnabled { defaultValue: true); } set { settings.SetBool(PREFERENCE_PODFILE_GENERATION_ENABLED, value); - SetEnablePollTargetSdk(value); + SetEnablePollTargetSdks(value); } } /// - /// Enable / disable target SDK polling. + /// Enable / disable polling of target iOS and tvOS SDK project setting values. /// - private static void SetEnablePollTargetSdk(bool enable) { + private static void SetEnablePollTargetSdks(bool enable) { if (enable && EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { - RunOnMainThread.OnUpdate += PollTargetSdk; + RunOnMainThread.OnUpdate += PollTargetIosSdk; + } else { + RunOnMainThread.OnUpdate -= PollTargetIosSdk; + } + + if (enable && EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) { + RunOnMainThread.OnUpdate += PollTargetTvosSdk; } else { - RunOnMainThread.OnUpdate -= PollTargetSdk; + RunOnMainThread.OnUpdate -= PollTargetTvosSdk; } } @@ -863,6 +1021,15 @@ public static bool PodToolExecutionViaShellEnabled { set { settings.SetBool(PREFERENCE_POD_TOOL_EXECUTION_VIA_SHELL_ENABLED, value); } } + /// + /// Enable / disable setting of environment variables when executing pod tool via the shell. + /// + public static bool PodToolShellExecutionSetLang { + get { return settings.GetBool(PREFERENCE_POD_TOOL_SHELL_EXECUTION_SET_LANG, + defaultValue: true); } + set { settings.SetBool(PREFERENCE_POD_TOOL_SHELL_EXECUTION_SET_LANG, value); } + } + /// /// Enable automated pod tool installation in the editor. This is only performed when the /// editor isn't launched in batch mode. @@ -886,7 +1053,14 @@ public static bool UpgradeToWorkspaceWarningDisabled { /// public static bool VerboseLoggingEnabled { get { return settings.GetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, defaultValue: false); } - set { settings.SetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, value); } + set { + settings.SetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, value); + UpdateLoggerLevel(value); + } + } + + private static void UpdateLoggerLevel(bool verboseLoggingEnabled) { + logger.Level = verboseLoggingEnabled ? LogLevel.Verbose : LogLevel.Info; } /// @@ -899,6 +1073,92 @@ public static bool SkipPodInstallWhenUsingWorkspaceIntegration { value); } } + /// + /// Whether to add "use_frameworks!" in the Podfile. True by default. + /// If true, iOS Resolver adds the following line to Podfile. + /// + /// use_frameworks! + /// + public static bool PodfileAddUseFrameworks { + get { return settings.GetBool(PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS, value); + } + } + + /// + /// Whether to statically link framework in the Podfile. False by default. + /// If true and PodfileAddUseFrameworks is true, iOS Resolver adds the following line to Podfile. + /// + /// use_frameworks! :linkage => :static + /// + public static bool PodfileStaticLinkFrameworks { + get { return settings.GetBool(PREFERENCE_PODFILE_STATIC_LINK_FRAMEWORKS, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_STATIC_LINK_FRAMEWORKS, value); + } + } + + /// + /// Whether to enable Swift Framework support workaround. + /// If enabled, iOS Resolver adds a Dummy.swift to the generated Xcode project, and change build + // properties in order to properly include Swift Standard Libraries. + /// + public static bool SwiftFrameworkSupportWorkaroundEnabled { + get { return settings.GetBool(PREFERENCE_SWIFT_FRAMEWORK_SUPPORT_WORKAROUND, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_SWIFT_FRAMEWORK_SUPPORT_WORKAROUND, value); + } + } + + /// + /// The value used to set Xcode build property: Swift Language Version (SWIFT_VERSION). + /// It is default to "5" but Xcode may add or remove options over time. + /// If blank, iOS Resolver will not override the build property. + /// Please check the build property "Swift Language Version" options in Xcode project first + /// before changing this value. + /// + public static string SwiftLanguageVersion { + get { return settings.GetString(PREFERENCE_SWIFT_LANGUAGE_VERSION, + defaultValue: "5.0"); } + set { + settings.SetString(PREFERENCE_SWIFT_LANGUAGE_VERSION, value); + } + } + + /// + /// Whether to add the main target to Podfile for Unity 2019.3+. True by default. + /// If true, iOS Resolver will add the following lines to Podfile, on top of 'UnityFramework' + /// target. + /// + /// target 'Unity-iPhone' do + /// end + /// + /// This target will empty by default. + /// + public static bool PodfileAlwaysAddMainTarget { + get { return settings.GetBool(PREFERENCE_PODFILE_ALWAYS_ADD_MAIN_TARGET, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_ALWAYS_ADD_MAIN_TARGET, value); + } + } + + /// + /// Whether to allow the same pods to be in multiple targets, if specified in Dependecies.xml. + /// + public static bool PodfileAllowPodsInMultipleTargets { + get { return settings.GetBool(PREFERENCE_PODFILE_ALLOW_PODS_IN_MULTIPLE_TARGETS, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_ALLOW_PODS_IN_MULTIPLE_TARGETS, value); + } + } + + /// /// Whether to use project level settings. /// @@ -932,7 +1192,7 @@ private static bool UnityCanLoadWorkspace { } // If Unity was launched from Unity Cloud Build the build pipeline does not // open the xcworkspace so we need to force project level integration of frameworks. - if (System.Environment.CommandLine.Contains("-bvrbuildtarget")) { + if (System.Environment.CommandLine.ToLower().Contains("-bvrbuildtarget")) { return false; } return (VersionHandler.GetUnityVersionMajorMinor() >= 5.6f - epsilon); @@ -968,7 +1228,8 @@ private static bool CocoapodsProjectIntegrationEnabled { /// public static bool CocoapodsIntegrationEnabled { get { - return EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && + return (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) && CocoapodsIntegrationMethodPref != CocoapodsIntegrationMethod.None; } } @@ -978,6 +1239,18 @@ private delegate void LogMessageDelegate(string message, bool verbose = false, private static Google.Logger logger = new Google.Logger(); + // Analytics reporter. + internal static EditorMeasurement analytics = new EditorMeasurement( + settings, logger, VersionHandlerImpl.GA_TRACKING_ID, + VersionHandlerImpl.MEASUREMENT_ID, VersionHandlerImpl.PLUGIN_SUITE_NAME, "", + VersionHandlerImpl.PRIVACY_POLICY) { + BasePath = "/iosresolver/", + BaseQuery = String.Format("version={0}", IOSResolverVersionNumber.Value.ToString()), + BaseReportName = "iOS Resolver: ", + InstallSourceFilename = Assembly.GetAssembly(typeof(IOSResolver)).Location, + DataUsageUrl = VersionHandlerImpl.DATA_USAGE_URL + }; + /// /// Log a message. /// @@ -987,8 +1260,6 @@ private delegate void LogMessageDelegate(string message, bool verbose = false, /// Severity of the message. internal static void Log(string message, bool verbose = false, LogLevel level = LogLevel.Info) { - logger.Level = (VerboseLoggingEnabled || ExecutionEnvironment.InBatchMode) ? - LogLevel.Verbose : LogLevel.Info; logger.Log(message, level: verbose ? LogLevel.Verbose : level); } @@ -997,7 +1268,9 @@ internal static void Log(string message, bool verbose = false, /// internal static void LogToDialog(string message, bool verbose = false, LogLevel level = LogLevel.Info) { - if (!verbose) EditorUtility.DisplayDialog("iOS Resolver", message, "OK"); + if (!verbose) { + DialogWindow.Display("iOS Resolver", message, DialogWindow.Option.Selected0, "OK"); + } Log(message, verbose: verbose, level: level); } @@ -1013,7 +1286,8 @@ public static bool PodPresent(string pod) { /// project. /// private static bool InjectDependencies() { - return EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && + return (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) && Enabled && pods.Count > 0; } @@ -1066,7 +1340,38 @@ public static void AddPod(string podName, string version = null, AddPodInternal(podName, preformattedVersion: PodVersionExpressionFromVersionDep(version), bitcodeEnabled: bitcodeEnabled, minTargetSdk: minTargetSdk, - sources: sources); + addToAllTargets: false, sources: sources); + } + + /// + /// Tells the app what pod dependencies are needed. + /// This is called from a deps file in each API to aggregate all of the + /// dependencies to automate the Podfile generation. + /// + /// pod path, for example "Google-Mobile-Ads-SDK" to + /// be included + /// Version specification. + /// See PodVersionExpressionFromVersionDep for how the version string is processed. + /// Whether the pod was compiled with bitcode + /// enabled. If this is set to false on a pod, the entire project will + /// be configured with bitcode disabled. + /// Minimum SDK revision required by this + /// pod. + /// Whether to add this pod to all targets when multiple + /// target is supported. + /// List of sources to search for all pods. + /// Each source is a URL that is injected in the source section of a Podfile + /// See https://guides.cocoapods.org/syntax/podfile.html#source for the description of + /// a source. + public static void AddPod(string podName, string version = null, + bool bitcodeEnabled = true, + string minTargetSdk = null, + bool addToAllTargets = false, + IEnumerable sources = null) { + AddPodInternal(podName, + preformattedVersion: PodVersionExpressionFromVersionDep(version), + bitcodeEnabled: bitcodeEnabled, minTargetSdk: minTargetSdk, + addToAllTargets: addToAllTargets, sources: sources); } /// @@ -1082,6 +1387,8 @@ public static void AddPod(string podName, string version = null, /// be configured with bitcode disabled. /// Minimum SDK revision required by this /// pod. + /// Whether to add this pod to all targets when multiple + /// target is supported. /// List of sources to search for all pods. /// Each source is a URL that is injected in the source section of a Podfile /// See https://guides.cocoapods.org/syntax/podfile.html#source for the description of @@ -1095,22 +1402,24 @@ private static void AddPodInternal(string podName, string preformattedVersion = null, bool bitcodeEnabled = true, string minTargetSdk = null, + bool addToAllTargets = false, IEnumerable sources = null, bool overwriteExistingPod = true, string createdBy = null, bool fromXmlFile = false, Dictionary propertiesByName = null) { var pod = new Pod(podName, preformattedVersion, bitcodeEnabled, minTargetSdk, - sources, propertiesByName); + addToAllTargets, sources, propertiesByName); pod.createdBy = createdBy ?? pod.createdBy; pod.fromXmlFile = fromXmlFile; Log(String.Format( - "AddPod - name: {0} version: {1} bitcode: {2} sdk: {3} sources: {4}, " + - "properties: {5}\n" + - "createdBy: {6}\n\n", + "AddPod - name: {0} version: {1} bitcode: {2} sdk: {3} alltarget: {4} " + + "sources: {5} properties: {6}\n" + + "createdBy: {7}\n\n", podName, preformattedVersion ?? "null", bitcodeEnabled.ToString(), minTargetSdk ?? "null", + addToAllTargets.ToString(), sources != null ? String.Join(", ", (new List(sources)).ToArray()) : "(null)", Pod.PropertyDictionaryToString(pod.propertiesByName), createdBy ?? pod.createdBy), @@ -1129,68 +1438,125 @@ private static void AddPodInternal(string podName, return; } pods[podName] = pod; - ScheduleCheckTargetSdk(); + ScheduleCheckTargetIosSdkVersion(); + ScheduleCheckTargetTvosSdkVersion(); + } + + /// + /// Determine whether the target iOS SDK has changed. + /// + private static void PollTargetIosSdk() { + iosTargetSdkPoller.Poll(() => TargetIosSdkVersionString, + (previousValue, currentValue) => { ScheduleCheckTargetIosSdkVersion(); }); } /// - /// Determine whether the target SDK has changed. + /// Determine whether the target tvOS SDK has changed. /// - private static void PollTargetSdk() { - targetSdkPoller.Poll(() => TargetSdk, - (previousValue, currentValue) => { ScheduleCheckTargetSdk(); }); + private static void PollTargetTvosSdk() { + tvosTargetSdkPoller.Poll(() => TargetTvosSdkVersionString, + (previousValue, currentValue) => { ScheduleCheckTargetTvosSdkVersion(); }); } - // ID of the job which checks that the target SDK is correct for the currently selected set of - // Cocoapods. - private static int checkTargetSdkJobId = 0; + // 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 SDK is configured correctly given the set of selected - /// Cocoapods. + /// Schedule a check to ensure target iOS SDK is configured correctly given the + /// set of selected Cocoapods. /// - private static void ScheduleCheckTargetSdk() { - RunOnMainThread.Cancel(checkTargetSdkJobId); - checkTargetSdkJobId = RunOnMainThread.Schedule(() => { - UpdateTargetSdk(false); + 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 target SDK if it's required. + /// Update the iOS target SDK if it's required. /// /// Whether the build is being processed. - /// true if the SDK was updated, false otherwise. - public static bool UpdateTargetSdk(bool runningBuild) { - var minVersionAndPodNames = TargetSdkNeedsUpdate(); + public static void UpdateTargetTvosSdkVersion(bool runningBuild) { + var minVersionAndPodNames = TargetSdkNeedsUpdate(TargetTvosSdkVersionNum); if (minVersionAndPodNames.Value != null) { var minVersionString = TargetSdkVersionToString(minVersionAndPodNames.Key); - var update = EditorUtility.DisplayDialog( - "Unsupported Target SDK", - "Target SDK selected in the iOS Player Settings (" + - TargetSdk + ") is not supported by the Cocoapods " + - "included in this project. " + - "The build will very likely fail. The minimum supported " + - "version is \"" + minVersionString + "\" " + - "required by pods (" + - String.Join(", ", minVersionAndPodNames.Value.ToArray()) + - ").\n" + - "Would you like to update the target SDK version?", - "Yes", cancel: "No"); - if (update) { - TargetSdkVersion = minVersionAndPodNames.Key; - if (runningBuild) { - string errorString = ( - "Target SDK has been updated from " + TargetSdk + - " to " + minVersionString + ". You must restart the " + - "build for this change to take effect."); - EditorUtility.DisplayDialog( - "Target SDK updated.", errorString, "OK"); - } - return true; - } + DialogWindow.Display("Unsupported Target tvOS SDK", + String.Format( + TARGET_SDK_NEEDS_UPDATE_STRING, /*platformName=*/"tvOS", + TargetTvosSdkVersionNum, minVersionString, + String.Join(", ", minVersionAndPodNames.Value.ToArray())), + DialogWindow.Option.Selected1 /* No */, "Yes", "No", + complete: (selectedOption) => { + analytics.Report( + "UpdateTargetTvosSdkVersion/" + + (selectedOption == DialogWindow.Option.Selected0 ? "apply" : "cancel"), + "Update Target tvOS SDK"); + if (selectedOption == DialogWindow.Option.Selected0) { + TargetTvosSdkVersionNum = minVersionAndPodNames.Key; + if (runningBuild) { + string errorString = String.Format( + UPDATED_TARGET_SDK_STRING, /*platform=*/"tvOS", + TargetTvosSdkVersionString, minVersionString); + DialogWindow.Display("Target tvOS SDK updated.", errorString, + DialogWindow.Option.Selected0, "OK"); + } + } + }); } - return false; } /// @@ -1201,7 +1567,8 @@ public static bool UpdateTargetSdk(bool runningBuild) { /// 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() { + private static KeyValuePair> + TargetSdkNeedsUpdate(int targetSdkVersionNum) { var emptyVersionAndPodNames = new KeyValuePair>(0, null); var minVersionAndPodNames = emptyVersionAndPodNames; int maxOfMinRequiredVersions = 0; @@ -1213,7 +1580,7 @@ private static KeyValuePair> TargetSdkNeedsUpdate() { } // 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 TargetSdkVersion >= maxOfMinRequiredVersions ? emptyVersionAndPodNames : + return targetSdkVersionNum >= maxOfMinRequiredVersions ? emptyVersionAndPodNames : minVersionAndPodNames; } @@ -1236,42 +1603,19 @@ public static string GetProjectPath(string relativeTo) { /// /// Get or set the Unity iOS target SDK version string (e.g "7.1") - /// build setting. + /// build setting dependeding on the current active build target. /// - static string TargetSdk { + static string TargetIosSdkVersionString { get { string name = null; var iosSettingsType = typeof(UnityEditor.PlayerSettings.iOS); - // Read the version (Unity 5.5 and above). - var osVersionProperty = iosSettingsType.GetProperty( - "targetOSVersionString"); + var osVersionProperty = + iosSettingsType.GetProperty("targetOSVersionString"); if (osVersionProperty != null) { name = (string)osVersionProperty.GetValue(null, null); } - if (name == null) { - // Read the version (deprecated in Unity 5.5). - osVersionProperty = iosSettingsType.GetProperty( - "targetOSVersion"); - if (osVersionProperty != null) { - var osVersionValue = - osVersionProperty.GetValue(null, null); - if (osVersionValue != null) { - name = Enum.GetName(osVersionValue.GetType(), - osVersionValue); - } - } - } if (String.IsNullOrEmpty(name)) { - // Versions 8.2 and above do not have enum symbols - // The values in Unity 5.4.1f1: - // 8.2 == 32 - // 8.3 == 34 - // 8.4 == 36 - // 9.0 == 38 - // 9.1 == 40 - // Since these are undocumented just report - // 8.2 as selected for the moment. - return TargetSdkVersionToString(DEFAULT_TARGET_SDK); + return TargetSdkVersionToString(DEFAULT_IOS_TARGET_SDK); } return name.Trim().Replace("iOS_", "").Replace("_", "."); } @@ -1289,24 +1633,60 @@ static string TargetSdk { osVersionProperty.SetValue( null, Enum.Parse(osVersionProperty.PropertyType, - "iOS_" + value.Replace(".", "_")), + "iOS_" + value.Replace(".", "_")), null); } } } + /// + /// Get or set the Unity tvOS target SDK version string (e.g "7.1") + /// build setting dependeding on the current active build target. + /// + static string TargetTvosSdkVersionString { + get { + string name = null; + var tvosSettingsType = typeof(UnityEditor.PlayerSettings.tvOS); + var osVersionProperty = + tvosSettingsType.GetProperty("targetOSVersionString"); + if (osVersionProperty != null) { + name = (string)osVersionProperty.GetValue(null, null); + } + if (String.IsNullOrEmpty(name)) { + return DEFAULT_TVOS_TARGET_SDK; + } + return name.Trim().Replace("tvOS_", "").Replace("_", "."); + } + + set { + var tvosSettingsType = typeof(UnityEditor.PlayerSettings.tvOS); + var osVersionProperty = + tvosSettingsType.GetProperty("targetOSVersionString"); + osVersionProperty.SetValue(null, value, null); + } + } + + /// + /// Get or set the Unity iOS target SDK using a version number (e.g 71 + /// is equivalent to "7.1"). + /// + static int 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 TargetSdkVersion { - get { return TargetSdkStringToVersion(TargetSdk); } - set { TargetSdk = TargetSdkVersionToString(value); } + static int TargetTvosSdkVersionNum { + get { return TargetSdkStringToVersion(TargetTvosSdkVersionString); } + set { TargetTvosSdkVersionString = TargetSdkVersionToString(value); } } /// /// Convert a target SDK string into a value of the form - // (major * 10) + minor. + /// (major * 10) + minor. /// /// Integer representation of the SDK. internal static int TargetSdkStringToVersion(string targetSdk) { @@ -1323,9 +1703,9 @@ internal static int TargetSdkStringToVersion(string targetSdk) { "Please change this to a valid SDK version (e.g {1}) in:\n" + " Player Settings -> Other Settings --> " + "Target Minimum iOS Version\n", - targetSdk, TargetSdkVersionToString(DEFAULT_TARGET_SDK)), + targetSdk, TargetSdkVersionToString(DEFAULT_IOS_TARGET_SDK)), level: LogLevel.Warning); - return DEFAULT_TARGET_SDK; + return DEFAULT_IOS_TARGET_SDK; } @@ -1342,7 +1722,7 @@ internal static string TargetSdkVersionToString(int version) { /// /// Determine whether any pods need bitcode disabled. /// - /// List of pod names with bitcode disabled. + /// List of pod names with bitcode disabled. private static List FindPodsWithBitcodeDisabled() { var disabled = new List(); foreach (var pod in pods.Values) { @@ -1356,7 +1736,7 @@ private static List FindPodsWithBitcodeDisabled() { /// /// Menu item that installs CocoaPods if it's not already installed. /// - [MenuItem("Assets/Play Services Resolver/iOS Resolver/Install Cocoapods")] + [MenuItem("Assets/External Dependency Manager/iOS Resolver/Install Cocoapods")] public static void InstallCocoapodsMenu() { InstallCocoapodsInteractive(); } @@ -1448,7 +1828,9 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, var podToolPath = FindPodTool(); if (!String.IsNullOrEmpty(podToolPath)) { var installationFoundMessage = "CocoaPods installation detected " + podToolPath; - if (displayAlreadyInstalled) logMessage(installationFoundMessage); + if (displayAlreadyInstalled) { + logMessage(installationFoundMessage, level: LogLevel.Verbose); + } cocoapodsToolsInstallPresent = true; return; } @@ -1461,7 +1843,9 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, "For more information see:\n" + " https://guides.cocoapods.org/using/getting-started.html\n\n"; - // Log the set of install pods. + analytics.Report("installpodtool/querygems", "Install Pod Tool Query Ruby Gems"); + + // Log the set of install gems. RunCommand(GEM_EXECUTABLE, "list"); // Gem is being executed in an RVM directory it's already configured to perform a @@ -1485,6 +1869,7 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, installArgs += " --verbose"; } + analytics.Report("installpodtool/installgems", "Install Pod Tool Ruby Gem"); var commandList = new List(); if (!QueryGemInstalled("activesupport", logMessage: logMessage)) { // Workaround activesupport (dependency of the CocoaPods gem) requiring @@ -1509,6 +1894,9 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, var lastCommand = commands[commandIndex]; commandIndex += 1; if (result.exitCode != 0) { + analytics.Report("installpodtool/failed", + String.Format("Install Pod Tool Ruby Gem Failed {0}", + result.exitCode)); logMessage(String.Format( "Failed to install CocoaPods for the current user.\n\n" + "{0}\n" + @@ -1529,15 +1917,20 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, "'{0} {1}' succeeded but the {2} tool cannot be found.\n\n" + "{3}\n", lastCommand.Command, lastCommand.Arguments, POD_EXECUTABLE, commonInstallErrorMessage), level: LogLevel.Error); + analytics.Report("installpodtool/failedmissing", + "Install Pod Tool Ruby Gem Succeeded, Missing Tool"); complete.Set(); return -1; } if (dialog != null) { + analytics.Report("installpodtool/downloadrepo", + "Install Pod Tool Download Cocoapods Repo"); dialog.bodyText += ("\n\nDownloading CocoaPods Master Repository\n" + "(this can take a while)\n"); } commands[commandIndex].Command = podToolPath; } else if (commandIndex == commands.Length) { + analytics.Report("installpodtool/success", "Install Pod Tool Succeeded"); complete.Set(); logMessage("CocoaPods tools successfully installed."); cocoapodsToolsInstallPresent = true; @@ -1608,6 +2001,21 @@ public static void OnPostProcessPatchProject(BuildTarget buildTarget, PatchProject(buildTarget, pathToBuiltProject); } + /// + /// Post-processing build step to add dummy swift file + /// + [PostProcessBuildAttribute(BUILD_ORDER_PATCH_PROJECT)] + public static void OnPostProcessAddDummySwiftFile(BuildTarget buildTarget, + string pathToBuiltProject) { + if (!InjectDependencies() || + !PodfileGenerationEnabled || + !PodfileAddUseFrameworks || + !SwiftFrameworkSupportWorkaroundEnabled) { + return; + } + AddDummySwiftFile(buildTarget, pathToBuiltProject); + } + /// /// Get Xcode target names using a method that works across all Unity versions. /// @@ -1617,7 +2025,7 @@ public static IEnumerable XcodeTargetNames { get { // Return hard coded names in the UnityEditor.iOS.Xcode.PBXProject DLL. return MultipleXcodeTargetsSupported ? - new List() { "Unity-iPhone", "UnityFramework" } : + new List() { XcodeUnityFrameworkTargetName } : new List() { InitializeTargetName() }; } } @@ -1627,6 +2035,22 @@ public static IEnumerable XcodeTargetNames { /// 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) { @@ -1634,8 +2058,11 @@ public static IEnumerable GetXcodeTargetGuids(object xcodeProject) { // is requested so we need to use instance methods to fetch the GUIDs of each target. // NOTE: The test target is not exposed. try { - foreach (var guidMethod in - new[] { "GetUnityMainTargetGuid", "GetUnityFrameworkTargetGuid" }) { + var guidMethods = new List() {"GetUnityFrameworkTargetGuid"}; + if (includeAllTargets) { + guidMethods.Add("GetUnityMainTargetGuid"); + } + foreach (var guidMethod in guidMethods) { targets.Add((string)VersionHandler.InvokeInstanceMethod(project, guidMethod, null)); } @@ -1686,6 +2113,41 @@ internal static void PatchProject( File.WriteAllText(pbxprojPath, project.WriteToString()); } + internal static void AddDummySwiftFile( + BuildTarget buildTarget, string pathToBuiltProject) { + string pbxprojPath = GetProjectPath(pathToBuiltProject); + var project = new UnityEditor.iOS.Xcode.PBXProject(); + project.ReadFromString(File.ReadAllText(pbxprojPath)); + + string DUMMY_SWIFT_FILE_NAME = "Dummy.swift"; + string DUMMY_SWIFT_FILE_CONTENT = + "// Generated by External Dependency Manager for Unity\n" + + "import Foundation"; + string dummySwiftPath = Path.Combine(pathToBuiltProject, DUMMY_SWIFT_FILE_NAME); + if (!File.Exists(dummySwiftPath)) { + File.WriteAllText(dummySwiftPath, DUMMY_SWIFT_FILE_CONTENT); + } + + foreach (var target in GetXcodeTargetGuids(project, includeAllTargets: false)) { + project.AddFileToBuild( + target, + project.AddFile(DUMMY_SWIFT_FILE_NAME, + DUMMY_SWIFT_FILE_NAME, + UnityEditor.iOS.Xcode.PBXSourceTree.Source)); + if(!string.IsNullOrEmpty(SwiftLanguageVersion)) { + project.SetBuildProperty(target, "SWIFT_VERSION", SwiftLanguageVersion); + } + + // These build properties are only required for multi-target Xcode project, which is + // generated from 2019.3+. + if(MultipleXcodeTargetsSupported) { + project.SetBuildProperty(target, "CLANG_ENABLE_MODULES", "YES"); + } + } + + File.WriteAllText(pbxprojPath, project.WriteToString()); + } + /// /// Post-processing build step to generate the podfile for ios. /// @@ -1741,12 +2203,17 @@ private static void ParseUnityDeps(string unityPodfilePath) { var sources = new List(); while ((line = unityPodfile.ReadLine()) != null) { line = line.Trim(); + if (PODFILE_COMMENT_REGEX.IsMatch(line)) { + continue; + } var sourceLineMatch = PODFILE_SOURCE_REGEX.Match(line); if (sourceLineMatch.Groups.Count > 1) { sources.Add(sourceLineMatch.Groups[1].Value); continue; } - if (line.StartsWith("target 'Unity-iPhone' do")) { + // TODO: Properly support multiple targets. + if (line.StartsWith(String.Format("target '{0}' do", + XcodeTargetWithUnityLibraries))) { capturingPodsDepth++; continue; } @@ -1802,7 +2269,7 @@ private static void ParseUnityDeps(string unityPodfilePath) { /// this returns the string... /// /// source 'http://myrepo.com/Specs.git' - /// source 'http://anotherrepo.com/Specs.git' + /// source 'http://anotherrepo.com/Specs.git' private static string GeneratePodfileSourcesSection() { var interleavedSourcesLines = new List(); var processedSources = new HashSet(); @@ -1833,6 +2300,7 @@ private static string GeneratePodfileSourcesSection() { // processing step. public static void GenPodfile(BuildTarget buildTarget, string pathToBuiltProject) { + analytics.Report("generatepodfile", "Generate Podfile"); string podfilePath = GetPodfilePath(pathToBuiltProject); string unityPodfile = FindExistingUnityPodfile(podfilePath); @@ -1852,9 +2320,24 @@ public static void GenPodfile(BuildTarget buildTarget, (CocoapodsWorkspaceIntegrationEnabled ? "Xcode workspace" : (CocoapodsProjectIntegrationEnabled ? "Xcode project" : "no target"))), verbose: true); + using (StreamWriter file = new StreamWriter(podfilePath)) { - file.WriteLine(GeneratePodfileSourcesSection() + - String.Format("platform :ios, '{0}'\n", TargetSdk)); + 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) { @@ -1862,7 +2345,63 @@ public static void GenPodfile(BuildTarget buildTarget, } 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"); } /// @@ -1993,7 +2532,7 @@ private class DelegateContainer { /// Delegate method associated with the container. This enables the /// following pattern: /// - /// var container = new DelegateContainer(); + /// var container = new DelegateContainer<CommandLine.CompletionHandler>(); /// container.Handler = (CommandLine.Result result) => { RunNext(container.Handler); }; /// public T Handler { get; set; } @@ -2083,7 +2622,8 @@ private static void RunCommandsAsync(CommandItem[] commands, Log(command.ToString(), verbose: true); var result = CommandLine.RunViaShell( command.Command, command.Arguments, workingDirectory: command.WorkingDirectory, - envVars: envVars, useShellExecution: PodToolExecutionViaShellEnabled); + envVars: envVars, useShellExecution: PodToolExecutionViaShellEnabled, + setLangInShellMode: PodToolShellExecutionSetLang); LogCommandLineResult(command.ToString(), result); index = completionDelegate(index, commands, result, null); } @@ -2195,7 +2735,15 @@ private static CommandLine.Result RunPodCommand( public static void OnPostProcessInstallPods(BuildTarget buildTarget, string pathToBuiltProject) { if (!InjectDependencies() || !PodfileGenerationEnabled) return; - if (UpdateTargetSdk(true)) 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" + @@ -2213,6 +2761,7 @@ public static void OnPostProcessInstallPods(BuildTarget buildTarget, if (UnityCanLoadWorkspace && CocoapodsIntegrationMethodPref == CocoapodsIntegrationMethod.Workspace && SkipPodInstallWhenUsingWorkspaceIntegration) { + analytics.Report("installpods/disabled", "Pod Install Disabled"); Log("Skipping pod install.", level: LogLevel.Warning); return; } @@ -2221,9 +2770,14 @@ public static void OnPostProcessInstallPods(BuildTarget buildTarget, CommandLine.Result result; result = RunPodCommand("--version", pathToBuiltProject); if (result.exitCode == 0) podsVersion = result.stdout.Trim(); + var cocoapodsVersionParameters = new KeyValuePair[] { + new KeyValuePair("cocoapodsVersion", podsVersion) + }; if (result.exitCode != 0 || (!String.IsNullOrEmpty(podsVersion) && podsVersion[0] == '0')) { + analytics.Report("installpods/outofdate", cocoapodsVersionParameters, + "Pod Install, Tool Out Of Date"); Log("Error running CocoaPods. Please ensure you have at least " + "version 1.0.0. " + COCOAPOD_INSTALL_INSTRUCTIONS + "\n\n" + "'" + POD_EXECUTABLE + " --version' returned status: " + @@ -2233,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. @@ -2251,6 +2810,8 @@ public static void OnPostProcessInstallPods(BuildTarget buildTarget, // If the repo update still didn't fix the problem... if (result2.exitCode != 0) { + analytics.Report("installpods/failed", cocoapodsVersionParameters, + "Pod Install Failed"); Log("iOS framework addition failed due to a " + "CocoaPods installation failure. This will will likely " + "result in an non-functional Xcode project.\n\n" + @@ -2299,6 +2860,7 @@ public static void UpdateProjectDeps( // failed. var podsDir = Path.Combine(pathToBuiltProject, PODS_DIR); if (!Directory.Exists(podsDir)) return; + analytics.Report("injectpodsintoxcproj", "Inject Pods Into xcproj"); // If Unity can load workspaces, and one has been generated, yet we're still // trying to patch the project file, then we have to actually get rid of the workspace @@ -2313,7 +2875,7 @@ public static void UpdateProjectDeps( "from CocoaPods integration, however the IOSResolver Settings are configured " + "to use project level integration. It's recommended that you use workspace " + "integration instead.\n" + - "You can manage this setting from: Assets > Play Services Resolver > " + + "You can manage this setting from: Assets > External Dependency Manager > " + "iOS Resolver > Settings, using the CocoaPods Integration drop down menu.", level: LogLevel.Warning); Directory.Delete(workspacePath, true); diff --git a/source/IOSResolver/src/IOSResolverSettingsDialog.cs b/source/IOSResolver/src/IOSResolverSettingsDialog.cs index 5a7dedf9..e8f7001d 100644 --- a/source/IOSResolver/src/IOSResolverSettingsDialog.cs +++ b/source/IOSResolver/src/IOSResolverSettingsDialog.cs @@ -17,6 +17,7 @@ namespace Google { using System; +using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; @@ -32,10 +33,18 @@ public class IOSResolverSettingsDialog : EditorWindow 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. @@ -43,11 +52,20 @@ private class Settings { 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); } /// @@ -56,11 +74,20 @@ internal Settings() { 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(); } } @@ -80,6 +107,8 @@ internal void Save() { IOSResolver.CocoapodsIntegrationMethod.None, }; + private Vector2 scrollPosition = new Vector2(0, 0); + // enum to index (linear search because there's no point in creating a reverse mapping // with such a small list). private static int FindIndexFromCocoapodsIntegrationMethod( @@ -91,7 +120,7 @@ private static int FindIndexFromCocoapodsIntegrationMethod( } public void Initialize() { - minSize = new Vector2(400, 360); + minSize = new Vector2(400, 715); position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, minSize.x, minSize.y); } @@ -118,6 +147,8 @@ public void OnGUI() { IOSResolverVersionNumber.Value.Minor, IOSResolverVersionNumber.Value.Build)); + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + GUILayout.BeginHorizontal(); GUILayout.Label("Podfile Generation", EditorStyles.boldLabel); settings.podfileGenerationEnabled = @@ -152,9 +183,16 @@ public void OnGUI() { settings.podToolExecutionViaShellEnabled = EditorGUILayout.Toggle(settings.podToolExecutionViaShellEnabled); GUILayout.EndHorizontal(); + GUILayout.Label("Shell execution is useful when configuration in the shell " + + "environment (e.g ~/.profile) is required to execute Cocoapods tools."); + if (settings.podToolExecutionViaShellEnabled) { - GUILayout.Label("Shell execution is useful when configuration in the shell " + - "environment (e.g ~/.profile) is required to execute Cocoapods tools."); + GUILayout.BeginHorizontal(); + GUILayout.Label("Set LANG When Using Shell to Execute Cocoapod Tool", EditorStyles.boldLabel); + settings.podToolShellExecutionSetLang = + EditorGUILayout.Toggle(settings.podToolShellExecutionSetLang); + GUILayout.EndHorizontal(); + GUILayout.Label("Useful for versions of cocoapods that depend on the value of LANG."); } GUILayout.BeginHorizontal(); @@ -166,10 +204,95 @@ public void OnGUI() { GUILayout.Label("Automatically installs the Cocoapod tool if the editor isn't " + "running in batch mode"); } else { - GUILayout.Label("Cocoapod tool installation can be performed via the menu option: " + - "Assets > Play Services Resolver > iOS Resolver > Install Cocoapods"); + GUILayout.Label( + "Cocoapod tool installation can be performed via the menu option: " + + "Assets > External Dependency Manager > iOS Resolver > Install Cocoapods"); } + if (settings.podfileGenerationEnabled) { + GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); + GUILayout.Label("Podfile Configurations", EditorStyles.largeLabel); + EditorGUILayout.Separator(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Add use_frameworks! to Podfile", EditorStyles.boldLabel); + settings.podfileAddUseFrameworks = + EditorGUILayout.Toggle(settings.podfileAddUseFrameworks); + GUILayout.EndHorizontal(); + + GUILayout.Label("Add the following line to Podfile. Required if any third-party " + + "Unity packages depends on Swift frameworks."); + if (settings.podfileStaticLinkFrameworks) { + GUILayout.Label(" use_frameworks! :linkage => :static"); + } else { + GUILayout.Label(" use_frameworks!"); + } + + if (settings.podfileAddUseFrameworks) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Link frameworks statically", EditorStyles.boldLabel); + settings.podfileStaticLinkFrameworks = + EditorGUILayout.Toggle(settings.podfileStaticLinkFrameworks); + GUILayout.EndHorizontal(); + GUILayout.Label("Link frameworks statically is recommended just in case any pod " + + "framework includes static libraries."); + } + + if (IOSResolver.MultipleXcodeTargetsSupported) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Always add the main target to Podfile", EditorStyles.boldLabel); + settings.podfileAlwaysAddMainTarget = + EditorGUILayout.Toggle(settings.podfileAlwaysAddMainTarget); + GUILayout.EndHorizontal(); + + GUILayout.Label("Add the following lines to Podfile."); + GUILayout.Label(String.Format(" target '{0}' do\n" + + " end", IOSResolver.XcodeMainTargetName)); + + if (settings.podfileAlwaysAddMainTarget) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Allow the same pod to be in multiple targets", + EditorStyles.boldLabel); + settings.podfileAllowPodsInMultipleTargets = + EditorGUILayout.Toggle(settings.podfileAllowPodsInMultipleTargets); + GUILayout.EndHorizontal(); + + GUILayout.Label("Allow to add the same pod to multiple targets, if specified in " + + "Dependencies.xml with 'addToAllTargets' attribute."); + } + } + + if (settings.podfileAddUseFrameworks) { + GUILayout.BeginHorizontal(); + GUILayout.Label("(Recommended) Enable Swift Framework Support Workaround", + EditorStyles.boldLabel); + settings.swiftFrameworkSupportWorkaroundEnabled = + EditorGUILayout.Toggle(settings.swiftFrameworkSupportWorkaroundEnabled); + GUILayout.EndHorizontal(); + GUILayout.Label("This workround patches the Xcode project to properly link Swift " + + "Standard Library when some plugins depend on Swift Framework " + + "pods by:"); + GUILayout.Label("1. Add a dummy Swift file to Xcode project."); + GUILayout.Label("2. Enable 'CLANG_ENABLE_MODULES' build settings and set " + + "'SWIFT_VERSION' to the value below."); + + if (settings.swiftFrameworkSupportWorkaroundEnabled) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Swift Framework Version", + EditorStyles.boldLabel); + settings.swiftLanguageVersion = + EditorGUILayout.TextField(settings.swiftLanguageVersion); + GUILayout.EndHorizontal(); + GUILayout.Label("Used to override 'SWIFT_VERSION' build setting in Xcode. " + + "Leave it blank to prevent override."); + } + } + + GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); + } + + settings.analyticsSettings.RenderGui(); + GUILayout.BeginHorizontal(); GUILayout.Label("Verbose Logging", EditorStyles.boldLabel); settings.verboseLoggingEnabled = EditorGUILayout.Toggle(settings.verboseLoggingEnabled); @@ -180,22 +303,70 @@ public void OnGUI() { settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); GUILayout.EndHorizontal(); - GUILayout.Space(10); + EditorGUILayout.EndScrollView(); + GUILayout.EndVertical(); + GUILayout.BeginVertical(); + GUILayout.Space(10); if (GUILayout.Button("Reset to Defaults")) { // Load default settings into the dialog but preserve the state in the user's // saved preferences. var backupSettings = new Settings(); IOSResolver.RestoreDefaultSettings(); + IOSResolver.analytics.Report("settings/reset", "Settings Reset"); LoadSettings(); backupSettings.Save(); } GUILayout.BeginHorizontal(); bool closeWindow = GUILayout.Button("Cancel"); + if (closeWindow) IOSResolver.analytics.Report("settings/cancel", "Settings Cancel"); bool ok = GUILayout.Button("OK"); closeWindow |= ok; - if (ok) settings.Save(); + if (ok) { + IOSResolver.analytics.Report( + "settings/save", + new KeyValuePair[] { + new KeyValuePair( + "podfileGenerationEnabled", + IOSResolver.PodfileGenerationEnabled.ToString()), + new KeyValuePair( + "podToolExecutionViaShellEnabled", + IOSResolver.PodToolExecutionViaShellEnabled.ToString()), + new KeyValuePair( + "podToolShellExecutionSetLang", + IOSResolver.PodToolShellExecutionSetLang.ToString()), + new KeyValuePair( + "autoPodToolInstallInEditorEnabled", + IOSResolver.AutoPodToolInstallInEditorEnabled.ToString()), + new KeyValuePair( + "verboseLoggingEnabled", + IOSResolver.VerboseLoggingEnabled.ToString()), + new KeyValuePair( + "cocoapodsIntegrationMethod", + IOSResolver.CocoapodsIntegrationMethodPref.ToString()), + new KeyValuePair( + "podfileAddUseFrameworks", + IOSResolver.PodfileAddUseFrameworks.ToString()), + new KeyValuePair( + "podfileStaticLinkFrameworks", + IOSResolver.PodfileStaticLinkFrameworks.ToString()), + new KeyValuePair( + "podfileAlwaysAddMainTarget", + IOSResolver.PodfileAlwaysAddMainTarget.ToString()), + new KeyValuePair( + "podfileAllowPodsInMultipleTargets", + IOSResolver.PodfileAllowPodsInMultipleTargets.ToString()), + new KeyValuePair( + "swiftFrameworkSupportWorkaroundEnabled", + IOSResolver.SwiftFrameworkSupportWorkaroundEnabled.ToString()), + new KeyValuePair( + "swiftLanguageVersion", + IOSResolver.SwiftLanguageVersion.ToString()), + }, + "Settings Save"); + settings.Save(); + } if (closeWindow) Close(); GUILayout.EndHorizontal(); GUILayout.EndVertical(); diff --git a/source/IOSResolver/src/VersionNumber.cs b/source/IOSResolver/src/VersionNumber.cs index 7743670a..c9f3afa8 100644 --- a/source/IOSResolver/src/VersionNumber.cs +++ b/source/IOSResolver/src/VersionNumber.cs @@ -27,7 +27,7 @@ public class IOSResolverVersionNumber { /// /// Version number, patched by the build process. /// - private const string VERSION_STRING = "1.2.128.0"; + private const string VERSION_STRING = "1.2.186"; /// /// Cached version structure. diff --git a/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-311.pyc b/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-311.pyc new file mode 100644 index 00000000..f1c36f67 Binary files /dev/null and b/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-311.pyc differ diff --git a/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-39.pyc b/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-39.pyc new file mode 100644 index 00000000..0d771aa0 Binary files /dev/null and b/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-39.pyc differ diff --git a/source/ImportUnityPackage/import_unity_package.py b/source/ImportUnityPackage/import_unity_package.py new file mode 100755 index 00000000..5ecc17a2 --- /dev/null +++ b/source/ImportUnityPackage/import_unity_package.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +r"""A script to import a .unitypackage into a project without Unity. + +Example usage: + import_unity_package.py --projects=path/to/unity/project \ + --packages=mypackage.unitypackage +""" + +import os +import shutil +import tarfile +import tempfile +from absl import app +from absl import flags +from absl import logging + +FLAGS = flags.FLAGS + +flags.DEFINE_multi_string( + "projects", None, "Paths to Unity project directories to unpack packages " + "into. This should be the directory that contains the Assets directory, " + "i.e my/project not my/project/Assets") +flags.DEFINE_multi_string( + "packages", None, "Set of packages to unpack into a project. Packages are " + "unpacked in the order they're specified.") + + +def files_exist(paths_to_check): + """Determine whether the specified files exist. + + Args: + paths_to_check: List of files to check whether they exist. + + Returns: + List of files that do not exist. + """ + return [p for p in paths_to_check if not os.path.isfile(os.path.realpath(p))] + + +def directories_exist(paths_to_check): + """Determine whether the specified directories exist. + + Args: + paths_to_check: List of directories to check whether they exist. + + Returns: + List of directories that do not exist. + """ + return [p for p in paths_to_check if not os.path.isdir(os.path.realpath(p))] + + +def unpack_to_directory(directory, packages): + """Unpack a set of .unitypackage files to a directory. + + Args: + directory: Directory to unpack into. + packages: List of .unitypackage filesname to unpack. + + Returns: + Dictionary containing a list of files that could not be extracted, keyed by + package archive filename. + """ + ignored_files_by_package = {} + for unitypackage in packages: + with tarfile.open(unitypackage) as unitypackage_file: + member_names = unitypackage_file.getnames() + guid_to_path = {} + extracted_files = set() + + # Map each asset GUID to an extract path the path of each extracted asset. + for filename in member_names: + if os.path.basename(filename) == "pathname": + guid = os.path.dirname(filename) + with unitypackage_file.extractfile(filename) as pathname_file: + pathname = pathname_file.read().decode("utf8").strip() + if guid and pathname: + extracted_files.add(filename) + guid_to_path[guid] = pathname + + # Extract each asset to the appropriate path in the output directory. + for filename in member_names: + basename = os.path.basename(filename) + if basename == "asset" or basename == "asset.meta": + guid = os.path.dirname(filename) + if guid: + pathname = guid_to_path.get(guid) + if pathname: + with unitypackage_file.extractfile(filename) as member_file: + extension = os.path.splitext(basename)[1] + output_filename = os.path.join(directory, pathname + extension) + os.makedirs(os.path.dirname(output_filename), exist_ok=True) + with open(output_filename, "wb") as output_file: + shutil.copyfileobj(member_file, output_file) + extracted_files.add(filename) + + # Returns the list of files that could not be extracted in the archive's + # order. + ignored_files = [] + for member in member_names: + if member not in extracted_files: + if unitypackage_file.getmember(member).isfile(): + ignored_files.append(member) + if ignored_files: + ignored_files_by_package[unitypackage] = ignored_files + return ignored_files_by_package + + +def main(unused_argv): + """Unpacks a set of .unitypackage files into a set of Unity projects. + + Args: + unused_argv: Not used. + + Returns: + 0 if successful, 1 otherwise. + """ + # Make sure all input files and output directories exist. + missing_packages = files_exist(FLAGS.packages) + missing_projects = directories_exist(FLAGS.projects) + if missing_packages: + logging.error("Specified packages %s not found.", missing_packages) + if missing_projects: + logging.error("Specified projects %s not found.", missing_projects) + if missing_packages or missing_projects: + return 1 + + with tempfile.TemporaryDirectory() as unpack_directory: + # Unpack all packages into a single directory. + for package, files in unpack_to_directory(unpack_directory, FLAGS.packages): + logging.error("Failed to unpack files %s from package %s", package, files) + + # Copy unpacked packages into each project. + for project in FLAGS.projects: + for dirname, _, filenames in os.walk(unpack_directory): + for filename in filenames: + source_filename = os.path.join(dirname, filename) + relative_filename = source_filename[len(unpack_directory) + 1:] + if os.path.isfile(source_filename): + target_filename = os.path.join(project, relative_filename) + os.makedirs(os.path.dirname(target_filename), exist_ok=True) + shutil.copyfile(source_filename, target_filename) + return 0 + + +if __name__ == "__main__": + flags.mark_flag_as_required("projects") + flags.mark_flag_as_required("packages") + app.run(main) diff --git a/source/ImportUnityPackage/import_unity_package_test.py b/source/ImportUnityPackage/import_unity_package_test.py new file mode 100755 index 00000000..146cf258 --- /dev/null +++ b/source/ImportUnityPackage/import_unity_package_test.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for import_unity_package.py.""" + +import os +import shutil +import sys +from absl import flags +from absl.testing import absltest + +# pylint: disable=C6204 +# pylint: disable=W0403 +sys.path.append(os.path.dirname(__file__)) +import import_unity_package +# pylint: enable=C6204 +# pylint: enable=W0403 + +FLAGS = flags.FLAGS + +# Location of test data. +TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), "test_data") + +class ImportUnityPackageTest(absltest.TestCase): + """Test import_unity_package.py.""" + + def setUp(self): + """Create a temporary directory.""" + self.temp_dir = os.path.join(FLAGS.test_tmpdir, "temp") + os.makedirs(self.temp_dir) + + def tearDown(self): + """Clean up the temporary directory.""" + shutil.rmtree(self.temp_dir) + + def test_files_exist(self): + """Test file existance check.""" + non_existent_file = os.path.join(FLAGS.test_tmpdir, "foo/bar.txt") + existent_file = os.path.join(FLAGS.test_tmpdir, "a_file.txt") + with open(existent_file, "wt") as test_file: + test_file.write("hello") + self.assertEqual( + import_unity_package.files_exist([existent_file, non_existent_file]), + [non_existent_file]) + + def test_directories_exist(self): + """Test directory existence check.""" + non_existent_dir = os.path.join(FLAGS.test_tmpdir, "foo/bar") + existent_dir = os.path.join(FLAGS.test_tmpdir, "an/available/dir") + os.makedirs(existent_dir, exist_ok=True) + self.assertEqual( + import_unity_package.directories_exist([non_existent_dir, + existent_dir]), + [non_existent_dir]) + + def read_contents_file(self, test_package_filename): + """Read the contents file for the specified test package. + + Args: + test_package_filename: File to read the expected contents of. + + Returns: + Sorted list of filenames read from + (test_package_filename + ".contents.txt"). + """ + contents = [] + with open(test_package_filename + ".contents.txt", "rt") as contents_file: + return [l.strip() for l in contents_file.readlines() if l.strip()] + + def list_files_in_temp_dir(self): + """List files in the temporary directory. + + Returns: + Sorted list of files relative to the temporary directory. + """ + files = [] + for dirpath, _, filenames in os.walk(self.temp_dir): + for basename in list(filenames): + filename = os.path.join(dirpath, basename) + if os.path.isfile(filename): + files.append(filename[len(self.temp_dir) + 1:]) + return sorted(files) + + def test_unpack_to_directory_valid_archive(self): + """Unpack a valid unitypackage into a directory.""" + packages = [ + os.path.join(TEST_DATA_PATH, + "external-dependency-manager-1.2.144.unitypackage"), + os.path.join(TEST_DATA_PATH, + "external-dependency-manager-1.2.153.unitypackage") + ] + self.assertEqual(import_unity_package.unpack_to_directory(self.temp_dir, + packages), {}) + + expected_files = set(self.read_contents_file(packages[0])) + expected_files = expected_files.union(self.read_contents_file(packages[1])) + + self.maxDiff = None + self.assertEqual(self.list_files_in_temp_dir(), + sorted(expected_files)) + + def test_unpack_to_directory_invalid_archive(self): + """Unpack a broken unitypackage into a directory.""" + # This archive has been modified so that 9b7b6f84d4eb4f549252df73305e17c8 + # does not have a path. + packages = [ + os.path.join( + TEST_DATA_PATH, + "external-dependency-manager-1.2.144-broken.unitypackage") + ] + self.assertEqual( + import_unity_package.unpack_to_directory(self.temp_dir, packages), + {packages[0]: [ + "9b7b6f84d4eb4f549252df73305e17c8/asset.meta", + "9b7b6f84d4eb4f549252df73305e17c8/asset"]}) + + +if __name__ == "__main__": + absltest.main() + diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144-broken.unitypackage b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144-broken.unitypackage new file mode 100644 index 00000000..f352cd95 Binary files /dev/null and b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144-broken.unitypackage differ diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage new file mode 100644 index 00000000..a940d314 Binary files /dev/null and b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage differ diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage.contents.txt b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage.contents.txt new file mode 100644 index 00000000..294949ff --- /dev/null +++ b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage.contents.txt @@ -0,0 +1,36 @@ +Assets/ExternalDependencyManager.meta +Assets/ExternalDependencyManager/Editor.meta +Assets/ExternalDependencyManager/Editor/CHANGELOG.md +Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.144.dll +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.144.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.144.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.144.dll.meta +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.144.dll +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.144.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.144.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.144.dll.meta +Assets/ExternalDependencyManager/Editor/Google.UnityPackageManagerResolver_v1.2.144.dll +Assets/ExternalDependencyManager/Editor/Google.UnityPackageManagerResolver_v1.2.144.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.UnityPackageManagerResolver_v1.2.144.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.UnityPackageManagerResolver_v1.2.144.dll.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.144.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.144.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.144.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.144.dll.meta +Assets/ExternalDependencyManager/Editor/GoogleRegistries.xml +Assets/ExternalDependencyManager/Editor/GoogleRegistries.xml.meta +Assets/ExternalDependencyManager/Editor/LICENSE +Assets/ExternalDependencyManager/Editor/LICENSE.meta +Assets/ExternalDependencyManager/Editor/README.md +Assets/ExternalDependencyManager/Editor/README.md.meta +Assets/ExternalDependencyManager/Editor/external-dependency-manager_v1.2.144.txt +Assets/ExternalDependencyManager/Editor/external-dependency-manager_v1.2.144.txt.meta +Assets/PlayServicesResolver.meta +Assets/PlayServicesResolver/Editor.meta +Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt +Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage new file mode 100644 index 00000000..8e466704 Binary files /dev/null and b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage differ diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage.contents.txt b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage.contents.txt new file mode 100644 index 00000000..289574ea --- /dev/null +++ b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage.contents.txt @@ -0,0 +1,32 @@ +Assets/ExternalDependencyManager/Editor/CHANGELOG.md +Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.153.dll +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.153.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.153.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.153.dll.meta +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.153.dll +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.153.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.153.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.153.dll.meta +Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver_v1.2.153.dll +Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver_v1.2.153.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver_v1.2.153.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver_v1.2.153.dll.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.153.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.153.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.153.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.153.dll.meta +Assets/ExternalDependencyManager/Editor/GoogleRegistries.xml +Assets/ExternalDependencyManager/Editor/GoogleRegistries.xml.meta +Assets/ExternalDependencyManager/Editor/LICENSE +Assets/ExternalDependencyManager/Editor/LICENSE.meta +Assets/ExternalDependencyManager/Editor/README.md +Assets/ExternalDependencyManager/Editor/README.md.meta +Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.153_manifest.txt +Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.153_manifest.txt.meta +Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt +Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta diff --git a/source/PackageManager/PackageManager.csproj b/source/IntegrationTester/IntegrationTester.csproj similarity index 80% rename from source/PackageManager/PackageManager.csproj rename to source/IntegrationTester/IntegrationTester.csproj index 27ffa43b..6faa768d 100644 --- a/source/PackageManager/PackageManager.csproj +++ b/source/IntegrationTester/IntegrationTester.csproj @@ -5,10 +5,10 @@ AnyCPU 8.0.30703 2.0 - {8B0A2564-01ED-426B-AF33-33EED4A81828} + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040} Library Google - Google.PackageManager + Google.IntegrationTester 1.2 v3.5 @@ -23,7 +23,8 @@ False - none + True + full True bin\Release DEBUG;UNITY_EDITOR @@ -51,21 +52,19 @@ + + + - - - - - - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} VersionHandler - - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} - PlayServicesResolver + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl diff --git a/source/PackageManager/Properties/AssemblyInfo.cs b/source/IntegrationTester/Properties/AssemblyInfo.cs similarity index 87% rename from source/PackageManager/Properties/AssemblyInfo.cs rename to source/IntegrationTester/Properties/AssemblyInfo.cs index 23b4e109..d2f2e8b1 100644 --- a/source/PackageManager/Properties/AssemblyInfo.cs +++ b/source/IntegrationTester/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ -// -// Copyright (C) 2014 Google Inc. All Rights Reserved. +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle("Google.PackageManager")] +[assembly: AssemblyTitle("Google.IntegrationTester")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Google Inc.")] @@ -37,7 +37,7 @@ // if desired. See the Mono documentation for more information about signing. // [assembly: AssemblyDelaySign(false)] -// // +// // // Copyright (C) 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,4 +52,5 @@ // See the License for the specific language governing permissions and // limitations under the License. // [assembly: AssemblyKeyFile("")] -[assembly: InternalsVisibleToAttribute("ControllerTests")] \ No newline at end of file + +[assembly: InternalsVisibleTo("Google.AndroidResolverTests")] diff --git a/source/IntegrationTester/src/Runner.cs b/source/IntegrationTester/src/Runner.cs new file mode 100644 index 00000000..a7e8c9a9 --- /dev/null +++ b/source/IntegrationTester/src/Runner.cs @@ -0,0 +1,445 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; + +using Google; + +namespace Google.IntegrationTester { + + /// + /// Should be applied to "static void Method()" methods that will be called when the Runner is + /// ready to be initialized with test cases. + /// + public class InitializerAttribute : Attribute {} + + /// + /// Can be applied to static methods that conform to TestCase.MethodDelegate which will add the + /// methods to the set of tests cases to execute. + /// + public class TestCaseAttribute : Attribute {} + + /// + /// Runs a series of asynchronous test cases in the Unity Editor. + /// + [UnityEditor.InitializeOnLoad] + public static class Runner { + + /// + /// Executed test case names and failure messages (if any). + /// + private static List testCaseResults = new List(); + + /// + /// Set of test cases to execute. + /// + private static List testCases = new List(); + + /// + /// Backing store for UnityVersion. + /// + private static float unityVersion; + + /// + /// Get the current Unity version. + /// + public static float UnityVersion { get { return unityVersion; } } + + /// + /// Whether the default initializer was called. + /// + private static bool defaultInitializerCalled = false; + + /// + /// Whether the default test case was called. + /// + private static bool defaultTestCaseCalled = false; + + /// + /// File to store snapshot of test case results. + /// + private static string TestCaseResultsFilename = "Temp/GvhRunnerTestCaseResults.xml"; + + /// + /// Register a method to call when the Version Handler has enabled all plugins in the + /// project. + /// + static Runner() { + // Disable stack traces for more condensed logs. + try { + foreach (var logType in + new [] { UnityEngine.LogType.Log, UnityEngine.LogType.Warning }) { + // Unity 2017 and above have the Application.SetStackTraceLogType to configure + // stack traces per log level. + VersionHandler.InvokeStaticMethod( + typeof(UnityEngine.Application), "SetStackTraceLogType", + new object[] { logType, UnityEngine.StackTraceLogType.None }); + } + } catch (Exception) { + // Fallback to the legacy method. + UnityEngine.Application.stackTraceLogType = UnityEngine.StackTraceLogType.None; + } + + UnityEngine.Debug.Log("Set up callback on Version Handler completion."); + Google.VersionHandler.UpdateCompleteMethods = new [] { + "Google.IntegrationTester:Google.IntegrationTester.Runner:VersionHandlerReady" + }; + UnityEngine.Debug.Log("Enable plugin using the Version Handler."); + Google.VersionHandler.UpdateNow(); + } + + /// + /// Add a set of test cases to the list to be executed. + /// + /// Test cases to add to the list to execute. + public static void ScheduleTestCases(IEnumerable tests) { + testCases.AddRange(tests); + } + + /// + /// Add a single test case to the list to be executed. + /// + /// Test case to add to the list to execute. + public static void ScheduleTestCase(TestCase test) { + testCases.Add(test); + } + + /// + /// Called when the Version Handler has enabled all managed plugins in a project. + /// + public static void VersionHandlerReady() { + UnityEngine.Debug.Log("VersionHandler is ready."); + Google.VersionHandler.UpdateCompleteMethods = null; + // Start executing tests. + ConfigureTestCases(); + RunOnMainThread.Run(() => { ExecuteNextTestCase(); }, runNow: false); + } + + /// + /// Configure tests to run. + /// + /// + /// Finds and calls all initialization methods with the InitializerAttribute. + /// + private static void ConfigureTestCases() { + unityVersion = Google.VersionHandler.GetUnityVersionMajorMinor(); + + // Gather test initializers and test case methods. + var initializerMethods = new List(); + var testCaseMethods = new List(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + foreach (var type in assembly.GetTypes()) { + IEnumerable methods; + try { + methods = type.GetMethods(); + } catch (Exception) { + // TargetInvocationException, TypeLoadException and others can be thrown + // when retrieving the methods of some .NET assemblies + // (e.g System.Web.UI.WebControls.ModelDataSourceView) so ignore them. + continue; + } + foreach (var method in methods) { + foreach (var attribute in method.GetCustomAttributes(true)) { + if (attribute is InitializerAttribute) { + initializerMethods.Add(method); + break; + } else if (attribute is TestCaseAttribute) { + testCaseMethods.Add(method); + break; + } + } + } + } + } + + bool initializationSuccessful = true; + foreach (var initializer in initializerMethods) { + try { + initializer.Invoke(null, null); + } catch (Exception e) { + UnityEngine.Debug.Log(String.Format("FAILED: Unable to initialize {0} ({1})", + initializer.Name, e)); + initializationSuccessful = false; + } + } + + // Try adding test cases to the list to execute. + foreach (var testCaseMethod in testCaseMethods) { + try { + var testCaseMethodDelegate = (TestCase.MethodDelegate)Delegate.CreateDelegate( + typeof(TestCase.MethodDelegate), null, testCaseMethod, true); + ScheduleTestCase(new TestCase() { + Name = testCaseMethod.Name, + Method = testCaseMethodDelegate + }); + } catch (Exception e) { + UnityEngine.Debug.Log(String.Format( + "FAILED: Test case {0} does not implement TestCase.MethodDelegate ({1})", + testCaseMethod.Name, e)); + initializationSuccessful = false; + } + } + + // Restore for all executed test cases, restore results and remove all pending test + // cases that are complete. + var executedTestCaseNames = new HashSet(); + foreach (var executedTestCase in ReadTestCaseResults()) { + testCaseResults.Add(executedTestCase); + executedTestCaseNames.Add(executedTestCase.TestCaseName); + } + var filteredTestCases = new List(); + foreach (var testCase in testCases) { + if (!executedTestCaseNames.Contains(testCase.Name)) { + filteredTestCases.Add(testCase); + } + } + defaultTestCaseCalled = executedTestCaseNames.Contains("DefaultTestCase"); + testCases = filteredTestCases; + + if (!defaultInitializerCalled) { + UnityEngine.Debug.Log("FAILED: Default Initializer not called."); + initializationSuccessful = false; + } + + if (!initializationSuccessful) Exit(false); + } + + /// + /// Default initializer to test the Initializer attribute. + /// + [Initializer] + public static void DefaultInitializer() { + defaultInitializerCalled = true; + } + + /// + /// Default test case to test the TestCase attribute. + /// + /// Object executing this method. + /// Called when the test case is complete. + [TestCase] + public static void DefaultTestCase(TestCase testCase, + Action testCaseComplete) { + defaultTestCaseCalled = true; + testCaseComplete(new TestCaseResult(testCase)); + } + + /// + /// Exit the application if the -gvh_noexitontestcompletion command line flag isn't set. + /// + /// Whether the tests passed. + private static void Exit(bool passed) { + if (!Environment.CommandLine.ToLower().Contains("-gvh_noexitontestcompletion")) { + UnityEditor.EditorApplication.Exit(passed ? 0 : 1); + } + } + + /// + /// Log test result summary and quit the application. + /// + public static void LogSummaryAndExit() { + bool passed = true; + var testSummaryLines = new List(); + if (!defaultTestCaseCalled) { + testSummaryLines.Add("Default test case not called"); + passed = false; + } + foreach (var testCaseResult in testCaseResults) { + testSummaryLines.Add(testCaseResult.FormatString(false)); + passed &= testCaseResult.Succeeded; + } + UnityEngine.Debug.Log(String.Format("Test(s) {0}.\n{1}", passed ? "PASSED" : "FAILED", + String.Join("\n", testSummaryLines.ToArray()))); + Exit(passed); + } + + /// + /// Read test case results from the journal. + /// + /// List of TestCaseResults. + private static List ReadTestCaseResults() { + var readTestCaseResults = new List(); + if (!File.Exists(TestCaseResultsFilename)) return readTestCaseResults; + + bool successful = XmlUtilities.ParseXmlTextFileElements( + TestCaseResultsFilename, new Logger(), + (XmlTextReader reader, string elementName, bool isStart, string parentElementName, + List elementNameStack) => { + TestCaseResult currentTestCaseResult = null; + int testCaseResultsCount = readTestCaseResults.Count; + if (testCaseResultsCount > 0) { + currentTestCaseResult = readTestCaseResults[testCaseResultsCount - 1]; + } + if (elementName == "TestCaseResults" && parentElementName == "") { + if (isStart) { + readTestCaseResults.Clear(); + } + return true; + } else if (elementName == "TestCaseResult" && + parentElementName == "TestCaseResults") { + if (isStart) { + readTestCaseResults.Add(new TestCaseResult(new TestCase())); + } + return true; + } else if (elementName == "TestCaseName" && + parentElementName == "TestCaseResult") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentTestCaseResult.TestCaseName = reader.ReadContentAsString(); + } + return true; + } else if (elementName == "Skipped" && parentElementName == "TestCaseResult") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentTestCaseResult.Skipped = reader.ReadContentAsBoolean(); + } + return true; + } else if (elementName == "ErrorMessages" && + parentElementName == "TestCaseResult") { + return true; + } else if (elementName == "ErrorMessage" && + parentElementName == "ErrorMessages") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentTestCaseResult.ErrorMessages.Add(reader.ReadContentAsString()); + } + return true; + } + return false; + }); + if (!successful) { + UnityEngine.Debug.LogWarning( + String.Format("Failed while reading {0}, test execution will restart if the " + + "app domain is reloaded.", TestCaseResultsFilename)); + } + return readTestCaseResults; + } + + /// + /// Log a test case result to the journal so that it isn't executed again if the app + /// domain is reloaded. + /// + private static bool WriteTestCaseResult(TestCaseResult testCaseResult) { + var existingTestCaseResults = ReadTestCaseResults(); + existingTestCaseResults.Add(testCaseResult); + try { + Directory.CreateDirectory(Path.GetDirectoryName(TestCaseResultsFilename)); + using (var writer = new XmlTextWriter(new StreamWriter(TestCaseResultsFilename)) { + Formatting = Formatting.Indented + }) { + writer.WriteStartElement("TestCaseResults"); + foreach (var result in existingTestCaseResults) { + writer.WriteStartElement("TestCaseResult"); + if (!String.IsNullOrEmpty(result.TestCaseName)) { + writer.WriteStartElement("TestCaseName"); + writer.WriteValue(result.TestCaseName); + writer.WriteEndElement(); + } + writer.WriteStartElement("Skipped"); + writer.WriteValue(result.Skipped); + writer.WriteEndElement(); + if (result.ErrorMessages.Count > 0) { + writer.WriteStartElement("ErrorMessages"); + foreach (var errorMessage in result.ErrorMessages) { + writer.WriteStartElement("ErrorMessage"); + writer.WriteValue(errorMessage); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.Flush(); + writer.Close(); + } + } catch (Exception e) { + UnityEngine.Debug.LogWarning( + String.Format("Failed while writing {0} ({1}), test execution will restart " + + "if the app domain is reloaded.", TestCaseResultsFilename, e)); + return false; + } + return true; + } + + /// + /// Log a test case result with error details. + /// + /// Result to log. + public static void LogTestCaseResult(TestCaseResult testCaseResult) { + testCaseResults.Add(testCaseResult); + UnityEngine.Debug.Log(testCaseResult.FormatString(true)); + WriteTestCaseResult(testCaseResult); + } + + /// + /// Execute a function for a test case catching any exceptions and logging the result. + /// + /// Object executing this method. + /// Action to execute. + /// Whether to execute the next test case if the specified action + /// fails. + /// true if the action executed without any exceptions, false otherwise. + public static bool ExecuteTestCase(TestCase testCase, Action testCaseAction, + bool executeNext) { + bool succeeded = true; + try { + testCaseAction(); + } catch (Exception e) { + LogTestCaseResult(new TestCaseResult(testCase) { + ErrorMessages = new List { e.ToString() } + }); + succeeded = false; + } + if (!succeeded && executeNext) { + RunOnMainThread.Run(() => { ExecuteNextTestCase(); }); + } + return succeeded; + } + + /// + /// Execute the next queued test case. + /// + private static void ExecuteNextTestCase() { + bool executeNext; + do { + executeNext = false; + UnityEngine.Debug.Log(String.Format("Remaining test cases {0}", testCases.Count)); + if (testCases.Count > 0) { + var testCase = testCases[0]; + testCases.RemoveAt(0); + UnityEngine.Debug.Log(String.Format("Test {0} starting...", testCase.Name)); + // If the test threw an exception on this thread, execute the next test case + // in a loop. + executeNext = !ExecuteTestCase( + testCase, + () => { + testCase.Method(testCase, (testCaseResult) => { + UnityEngine.Debug.Log(String.Format("Test {0} complete", + testCase.Name)); + testCaseResult.TestCaseName = testCase.Name; + LogTestCaseResult(testCaseResult); + RunOnMainThread.Run(() => { ExecuteNextTestCase(); }); + }); + }, false); + } else { + LogSummaryAndExit(); + } + } while (executeNext); + } + } +} diff --git a/source/IntegrationTester/src/TestCase.cs b/source/IntegrationTester/src/TestCase.cs new file mode 100644 index 00000000..2d87fa4d --- /dev/null +++ b/source/IntegrationTester/src/TestCase.cs @@ -0,0 +1,47 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; + +namespace Google.IntegrationTester { + + /// + /// Test case class. + /// + /// This specifies a test case to execute. Each test case has a name which is used to + /// log the result of the test and the method to execute as part of the test case. + /// + public class TestCase { + + /// + /// Test case delegate. + /// + /// Object executing this method. + /// Called when the test case is complete. + public delegate void MethodDelegate(TestCase testCase, + Action testCaseComplete); + + /// + /// Name of the test case. + /// + public string Name { get; set; } + + /// + /// Delegate that runs the test case logic. + /// + public MethodDelegate Method { get; set; } + } +} diff --git a/source/IntegrationTester/src/TestCaseResult.cs b/source/IntegrationTester/src/TestCaseResult.cs new file mode 100644 index 00000000..461b9c2b --- /dev/null +++ b/source/IntegrationTester/src/TestCaseResult.cs @@ -0,0 +1,72 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; + +namespace Google.IntegrationTester { + + /// + /// Result of a test. + /// + public class TestCaseResult { + + /// + /// Initialize the class. + /// + public TestCaseResult(TestCase testCase) { + TestCaseName = testCase.Name; + ErrorMessages = new List(); + Skipped = false; + } + + /// + /// Name of the test case. This does not need to be set by the test case. + /// + public string TestCaseName { get; set; } + + /// + /// Error messages reported by a test case failure. + /// + public List ErrorMessages { get; set; } + + /// + /// Whether the test case was skipped. + /// + public bool Skipped { get; set; } + + /// + /// Whether the test case succeeded. + /// + public bool Succeeded { + get { + return Skipped || ErrorMessages == null || ErrorMessages.Count == 0; + } + } + + /// + /// Format the result as a string. + /// + /// Include failure messages in the list. + public string FormatString(bool includeFailureMessages) { + return String.Format("Test {0}: {1}{2}", TestCaseName, + Skipped ? "SKIPPED" : Succeeded ? "PASSED" : "FAILED", + includeFailureMessages && ErrorMessages != null && + ErrorMessages.Count > 0 ? + "\n" + String.Join("\n", ErrorMessages.ToArray()) : ""); + } + } +} diff --git a/source/JarResolver.sln b/source/JarResolver.sln deleted file mode 100644 index c4ce3495..00000000 --- a/source/JarResolver.sln +++ /dev/null @@ -1,239 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JarResolverLib", "JarResolverLib\JarResolverLib.csproj", "{CC4F239D-3C7F-4164-830F-9215AE15B32A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JarResolverTests", "JarResolverTests\JarResolverTests.csproj", "{593254D7-6358-40A6-B0C8-F0616BBF499D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayServicesResolver", "PlayServicesResolver\PlayServicesResolver.csproj", "{82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandler", "VersionHandler\VersionHandler.csproj", "{5378B37A-887E-49ED-A8AE-42FA843AA9DC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandlerImpl", "VersionHandlerImpl\VersionHandlerImpl.csproj", "{1E162334-8EA2-440A-9B3A-13FD8FE5C22E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOSResolver", "IOSResolver\IOSResolver.csproj", "{5B581BAE-D432-41AB-AEED-FD269AEA081D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManager", "PackageManager\PackageManager.csproj", "{8B0A2564-01ED-426B-AF33-33EED4A81828}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerTests", "PackageManagerTests\PackageManagerTests.csproj", "{7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {593254D7-6358-40A6-B0C8-F0616BBF499D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {593254D7-6358-40A6-B0C8-F0616BBF499D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {593254D7-6358-40A6-B0C8-F0616BBF499D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {593254D7-6358-40A6-B0C8-F0616BBF499D}.Release|Any CPU.Build.0 = Release|Any CPU - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Release|Any CPU.Build.0 = Release|Any CPU - {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Release|Any CPU.Build.0 = Release|Any CPU - {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Release|Any CPU.Build.0 = Release|Any CPU - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Release|Any CPU.Build.0 = Release|Any CPU - {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Release|Any CPU.Build.0 = Release|Any CPU - {8B0A2564-01ED-426B-AF33-33EED4A81828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B0A2564-01ED-426B-AF33-33EED4A81828}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B0A2564-01ED-426B-AF33-33EED4A81828}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B0A2564-01ED-426B-AF33-33EED4A81828}.Release|Any CPU.Build.0 = Release|Any CPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = PlayServicesResolver\PlayServicesResolver.csproj - Policies = $0 - $0.DotNetNamingPolicy = $1 - $1.DirectoryNamespaceAssociation = Hierarchical - $1.ResourceNamePolicy = FileFormatDefault - $0.TextStylePolicy = $2 - $2.inheritsSet = VisualStudio - $2.inheritsScope = text/plain - $2.scope = text/plain - $2.FileWidth = 100 - $0.CSharpFormattingPolicy = $3 - $3.IndentSwitchBody = True - $3.AnonymousMethodBraceStyle = NextLine - $3.PropertyBraceStyle = NextLine - $3.PropertyGetBraceStyle = NextLine - $3.PropertySetBraceStyle = NextLine - $3.EventBraceStyle = NextLine - $3.EventAddBraceStyle = NextLine - $3.EventRemoveBraceStyle = NextLine - $3.StatementBraceStyle = NextLine - $3.ElseNewLinePlacement = NewLine - $3.CatchNewLinePlacement = NewLine - $3.FinallyNewLinePlacement = NewLine - $3.WhileNewLinePlacement = DoNotCare - $3.ArrayInitializerWrapping = DoNotChange - $3.ArrayInitializerBraceStyle = NextLine - $3.BeforeMethodDeclarationParentheses = False - $3.BeforeMethodCallParentheses = False - $3.BeforeConstructorDeclarationParentheses = False - $3.BeforeDelegateDeclarationParentheses = False - $3.NewParentheses = False - $3.SpacesBeforeBrackets = False - $3.inheritsSet = Mono - $3.inheritsScope = text/x-csharp - $3.scope = text/x-csharp - $3.NewLinesForBracesInTypes = False - $3.NewLinesForBracesInMethods = False - $3.SpacingAfterMethodDeclarationName = False - $3.SpaceAfterMethodCallName = False - $3.SpaceBeforeOpenSquareBracket = False - $0.TextStylePolicy = $4 - $4.inheritsSet = VisualStudio - $4.inheritsScope = text/plain - $4.scope = text/plain - $0.StandardHeader = $5 - $5.Text = @\r\n Copyright ${Year} ${CopyrightHolder}\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License. - $5.IncludeInNewFiles = True - $0.NameConventionPolicy = $6 - $6.Rules = $7 - $7.NamingRule = $8 - $8.Name = Type Parameters - $8.AffectedEntity = TypeParameter - $8.VisibilityMask = VisibilityMask - $8.NamingStyle = PascalCase - $8.IncludeInstanceMembers = True - $8.IncludeStaticEntities = True - $8.RequiredPrefixes = $30 - $30.String = T - $8.RequiredSuffixes = $31 - $31.String = Exception - $7.NamingRule = $9 - $9.Name = Types - $9.AffectedEntity = Class, Struct, Enum, Delegate - $9.VisibilityMask = Public - $9.NamingStyle = PascalCase - $9.IncludeInstanceMembers = True - $9.IncludeStaticEntities = True - $7.NamingRule = $10 - $10.Name = Interfaces - $10.RequiredPrefixes = $11 - $11.String = I - $10.AffectedEntity = Interface - $10.VisibilityMask = Public - $10.NamingStyle = PascalCase - $10.IncludeInstanceMembers = True - $10.IncludeStaticEntities = True - $7.NamingRule = $12 - $12.Name = Attributes - $12.RequiredSuffixes = $13 - $13.String = Attribute - $12.AffectedEntity = CustomAttributes - $12.VisibilityMask = Public - $12.NamingStyle = PascalCase - $12.IncludeInstanceMembers = True - $12.IncludeStaticEntities = True - $7.NamingRule = $14 - $14.Name = Event Arguments - $14.RequiredSuffixes = $15 - $15.String = EventArgs - $14.AffectedEntity = CustomEventArgs - $14.VisibilityMask = Public - $14.NamingStyle = PascalCase - $14.IncludeInstanceMembers = True - $14.IncludeStaticEntities = True - $7.NamingRule = $16 - $16.Name = Exceptions - $16.RequiredSuffixes = $17 - $17.String = Exception - $16.AffectedEntity = CustomExceptions - $16.VisibilityMask = VisibilityMask - $16.NamingStyle = PascalCase - $16.IncludeInstanceMembers = True - $16.IncludeStaticEntities = True - $7.NamingRule = $18 - $18.Name = Methods - $18.AffectedEntity = Methods - $18.VisibilityMask = Protected, Public - $18.NamingStyle = PascalCase - $18.IncludeInstanceMembers = True - $18.IncludeStaticEntities = True - $7.NamingRule = $19 - $19.Name = Static Readonly Fields - $19.AffectedEntity = ReadonlyField - $19.VisibilityMask = Protected, Public - $19.NamingStyle = PascalCase - $19.IncludeInstanceMembers = False - $19.IncludeStaticEntities = True - $7.NamingRule = $20 - $20.Name = Fields - $20.AffectedEntity = Field - $20.VisibilityMask = Protected, Public - $20.NamingStyle = PascalCase - $20.IncludeInstanceMembers = True - $20.IncludeStaticEntities = True - $7.NamingRule = $21 - $21.Name = ReadOnly Fields - $21.AffectedEntity = ReadonlyField - $21.VisibilityMask = Protected, Public - $21.NamingStyle = PascalCase - $21.IncludeInstanceMembers = True - $21.IncludeStaticEntities = False - $7.NamingRule = $22 - $22.Name = Constant Fields - $22.AffectedEntity = ConstantField - $22.VisibilityMask = Protected, Public - $22.NamingStyle = PascalCase - $22.IncludeInstanceMembers = True - $22.IncludeStaticEntities = True - $7.NamingRule = $23 - $23.Name = Properties - $23.AffectedEntity = Property - $23.VisibilityMask = Protected, Public - $23.NamingStyle = PascalCase - $23.IncludeInstanceMembers = True - $23.IncludeStaticEntities = True - $7.NamingRule = $24 - $24.Name = Events - $24.AffectedEntity = Event - $24.VisibilityMask = Protected, Public - $24.NamingStyle = PascalCase - $24.IncludeInstanceMembers = True - $24.IncludeStaticEntities = True - $7.NamingRule = $25 - $25.Name = Enum Members - $25.AffectedEntity = EnumMember - $25.VisibilityMask = VisibilityMask - $25.NamingStyle = PascalCase - $25.IncludeInstanceMembers = True - $25.IncludeStaticEntities = True - $7.NamingRule = $26 - $26.Name = Parameters - $26.AffectedEntity = Parameter - $26.VisibilityMask = VisibilityMask - $26.NamingStyle = CamelCase - $26.IncludeInstanceMembers = True - $26.IncludeStaticEntities = True - $7.NamingRule = $27 - $27.Name = Type Parameters - $27.RequiredPrefixes = $28 - $28.String = T - $27.AffectedEntity = TypeParameter - $27.VisibilityMask = VisibilityMask - $27.NamingStyle = PascalCase - $27.IncludeInstanceMembers = True - $27.IncludeStaticEntities = True - $0.VersionControlPolicy = $29 - $29.inheritsSet = Mono - version = 1.2 - EndGlobalSection -EndGlobal diff --git a/source/JarResolverLib/src/Google.JarResolver/Dependency.cs b/source/JarResolverLib/src/Google.JarResolver/Dependency.cs index 3462565c..f740e9c5 100644 --- a/source/JarResolverLib/src/Google.JarResolver/Dependency.cs +++ b/source/JarResolverLib/src/Google.JarResolver/Dependency.cs @@ -49,13 +49,15 @@ public class Dependency { /// Group ID /// Artifact ID /// Version constraint. + /// Artifact classifier. /// Android SDK package identifiers required for this /// artifact. /// List of additional repository directories to search for /// this artifact. /// Human readable string that describes where this dependency /// originated. - public Dependency(string group, string artifact, string version, string[] packageIds=null, + public Dependency(string group, string artifact, string version, + string classifier = null, string[] packageIds=null, string[] repositories=null, string createdBy=null) { // If the dependency was added programmatically, strip out stack frames from inside the // library since the developer is likely interested in where in their code the @@ -75,6 +77,12 @@ public Dependency(string group, string artifact, string version, string[] packag continue; } filterFrames = false; + + // From Unity 2019, System.Environment.StackTrace stops returning parentheses. + // Remove the parentheses here to keep result consistent. + if (frameString.EndsWith("()")) { + frameString = frameString.Substring(0, frameString.Length - 2); + } usefulFrames.Add(frameString); } createdBy = String.Join("\n", usefulFrames.ToArray()); @@ -82,6 +90,7 @@ public Dependency(string group, string artifact, string version, string[] packag Group = group; Artifact = artifact; Version = version; + Classifier = classifier; PackageIds = packageIds; Repositories = repositories; CreatedBy = createdBy; @@ -95,6 +104,7 @@ public Dependency(Dependency dependency) { Group = dependency.Group; Artifact = dependency.Artifact; Version = dependency.Version; + Classifier = dependency.Classifier; if (dependency.PackageIds != null) { PackageIds = (string[])dependency.PackageIds.Clone(); } @@ -127,6 +137,14 @@ public Dependency(Dependency dependency) { /// The version. public string Version { get; set; } + /// + /// Gets the classifier. + /// + /// The classifier. + public string Classifier { get; set; } + + /// + /// /// Array of Android SDK identifiers for packages that are required for this /// artifact. @@ -152,7 +170,11 @@ public Dependency(Dependency dependency) { /// group, artifact and version constraint. /// /// The key. - public string Key { get { return Group + ":" + Artifact + ":" + Version; } } + public string Key { get { + string key = Group + ":" + Artifact + ":" + Version; + if (!String.IsNullOrEmpty(Classifier)) + key += ":" + Classifier; + return key; } } /// /// Returns a that represents @@ -171,7 +193,7 @@ public Dependency(Dependency dependency) { private static bool IsGreater(string version1, string version2) { version1 = version1.EndsWith("+") ? version1.Substring(0, version1.Length - 1) : version1; - version2 = version1.EndsWith("+") ? + version2 = version2.EndsWith("+") ? version2.Substring(0, version2.Length - 1) : version2; string[] version1Components = version1.Split('.'); string[] version2Components = version2.Split('.'); diff --git a/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs b/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs index d210fa14..673b8915 100644 --- a/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs +++ b/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs @@ -246,7 +246,8 @@ private static Dependency AddCommonPackageIds(Dependency dep) { } } if (packageNames.Count > 0) packageIds = packageNames.ToArray(); - return new Dependency(dep.Group, dep.Artifact, dep.Version, packageIds: packageIds, + return new Dependency(dep.Group, dep.Artifact, dep.Version, + classifier: dep.Classifier, packageIds: packageIds, repositories: dep.Repositories); } @@ -275,17 +276,20 @@ private static Dependency AddCommonPackageIds(Dependency dep) { /// Group - the Group Id of the artifact /// Artifact - Artifact Id /// Version - the version constraint + /// Classifier - the artifact classifer. /// Optional list of Android SDK package identifiers. /// List of additional repository directories to search for /// this artifact. /// Human readable string that describes where this dependency /// originated. public void DependOn(string group, string artifact, string version, - string[] packageIds = null, string[] repositories = null, - string createdBy = null) { + string classifier = null, string[] packageIds = null, + string[] repositories = null, string createdBy = null) { Log("DependOn - group: " + group + " artifact: " + artifact + " version: " + version + + " classifier: " + + (classifier!= null ? classifier : "null") + " packageIds: " + (packageIds != null ? String.Join(", ", packageIds) : "null") + " repositories: " + @@ -296,7 +300,8 @@ public void DependOn(string group, string artifact, string version, var depRepoList = new List(repositories); depRepoList.AddRange(repositoryPaths); var dep = AddCommonPackageIds(new Dependency( - group, artifact, version, packageIds: packageIds, + group, artifact, version, classifier: classifier, + packageIds: packageIds, repositories: UniqueList(depRepoList).ToArray(), createdBy: createdBy)); clientDependenciesMap[dep.Key] = dep; diff --git a/source/JarResolverTests/JarResolverTests.csproj b/source/JarResolverTests/JarResolverTests.csproj deleted file mode 100644 index 4a9acd0d..00000000 --- a/source/JarResolverTests/JarResolverTests.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - Debug - AnyCPU - {593254D7-6358-40A6-B0C8-F0616BBF499D} - Library - JarResolverTests - JarResolverTests - v2.0 - 1.2 - 12.0.0 - 2.0 - - - True - full - False - bin\Debug - DEBUG; - prompt - 4 - False - - - none - True - bin\Release - prompt - 4 - False - - - ..\packages\NUnit.2.6.3\lib\ - - - - - - $(NUnityHintPath)/nunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - {CC4F239D-3C7F-4164-830F-9215AE15B32A} - JarResolverLib - - - - - - diff --git a/source/JarResolverTests/packages.config b/source/JarResolverTests/packages.config deleted file mode 100644 index d4e241a2..00000000 --- a/source/JarResolverTests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/source/PackageManager/src/Constants.cs b/source/PackageManager/src/Constants.cs deleted file mode 100644 index ad07bfe2..00000000 --- a/source/PackageManager/src/Constants.cs +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - /// - /// Collection of constants relating to the PackageManager. - /// - public class Constants { - /// - /// The name of the plugin package manifest file. - /// - public const string MANIFEST_FILE_NAME = "package-manifest.xml"; - /// - /// The name of the plugin description file. - /// - public const string DESCRIPTION_FILE_NAME = "description.xml"; - /// - /// A marker used to prefix asset strings. - /// - public const string GPM_LABEL_MARKER = "gpm"; - /// - /// Key value used to get/set Unity editor user preferences for registry - /// location data. - /// - public const string KEY_REGISTRIES = "gpm_registries"; - /// - /// Key value for get/set Unity editor user preferences for location of - /// the package manager download cache location. - /// - public const string KEY_DOWNLOAD_CACHE = "gpm_local_cache"; - /// - /// The constant value of the download cache directory name. - /// - public const string GPM_CACHE_NAME = "GooglePackageManagerCache"; - - // TODO(krispy): when final location is settled it should go here - public const string DEFAULT_REGISTRY_LOCATION = - "https://raw.githubusercontent.com/kuccello/gpm_test/master/registry.xml"; - /// - /// The verbose package mananger logging key. - /// - public const string VERBOSE_PACKAGE_MANANGER_LOGGING_KEY = "gpm_verbose"; - /// - /// The show install assets key. - /// - public const string SHOW_INSTALL_ASSETS_KEY = "gpm_showInstallAssets"; - /// - /// The string key binder used in key string concatenation - /// - public const string STRING_KEY_BINDER = ":"; - /// - /// The gpm label key used in labeling assets - /// - public const string GPM_LABEL_KEY = "key"; - /// - /// The gpm label client used in labeling assets - /// - public const string GPM_LABEL_CLIENT = "client"; - /// - /// The fetch timout threshold - no fetch for external data should take longer - /// - public const double FETCH_TIMOUT_THRESHOLD = 10.0d; - /// - /// The gpm deps xml postfix used to check for package deps. - /// - public const string GPM_DEPS_XML_POSTFIX = "gpm.dep.xml"; - /// - /// The android sdk root Unity editor preference key. - /// - public const string ANDROID_SDK_ROOT_PREF_KEY = "AndroidSdkRoot"; - /// - /// The project settings key. - /// - public const string PROJECT_SETTINGS_KEY = "ProjectSettings"; - /// - /// The project record filename stored above Assets - /// - public const string PROJECT_RECORD_FILENAME = "project.gpm.xml"; - /// - /// The version unknown marker. - /// - public const string VERSION_UNKNOWN = "-"; - } -} diff --git a/source/PackageManager/src/Controllers.cs b/source/PackageManager/src/Controllers.cs deleted file mode 100644 index 8a14b4bd..00000000 --- a/source/PackageManager/src/Controllers.cs +++ /dev/null @@ -1,1777 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - using System; - using System.Collections.Generic; - using System.IO; - using System.Xml.Serialization; - using JarResolver; - using UnityEditor; - using UnityEngine; - - /// - /// Logging controller used for logging messages. All member classes of PackageManager that - /// need to log strings should use this controller as it is environment aware and can be used - /// during testcase execution. - /// - public static class LoggingController { - public static bool testing = false; - /// - /// Log the specified msg to the context appropriate console. - /// - /// Message to write to console. - public static void Log(string msg) { - if (!testing) { - if (SettingsController.VerboseLogging) { - Debug.Log(msg); - } - } else { - Console.WriteLine(msg); - } - } - /// - /// Logs the warning to the context appropriate console. - /// - /// Message to write as a warning to console. - public static void LogWarning(string msg) { - if (!testing) { - if (SettingsController.VerboseLogging) { - Debug.LogWarning(msg); - } - } else { - Console.WriteLine(msg); - } - } - /// - /// Logs the error to the context appropriate console - /// - /// Message to write as an error to the console. - public static void LogError(string msg) { - if (!testing) { - Debug.LogError(msg); - } else { - Console.WriteLine(msg); - } - } - } - - /// - /// Unity editor prefs abstraction. Mirrors partial API of Unity EditorPrefs. - /// - public interface IEditorPrefs { - void DeleteAll(); - void DeleteKey(string key); - bool GetBool(string key, bool defaultValue = false); - float GetFloat(string key, float defaultValue = 0.0F); - int GetInt(string key, int defaultValue = 0); - string GetString(string key, string defaultValue = ""); - bool HasKey(string key); - void SetBool(string key, bool value); - void SetFloat(string key, float value); - void SetInt(string key, int value); - void SetString(string key, string value); - } - - /// - /// Unity environment data abstraction interface to allow decoupling and - /// module isolation. - /// - public interface IUnityEnvironmentData { - string GetApplicationDataPath(); - } - - /// - /// Unity environment data implementation used as default implementation. - /// - public class UnityEnvironmentData : IUnityEnvironmentData { - public string GetApplicationDataPath() { - return Application.dataPath; - } - } - - /// - /// Unity editor prefs implementation. The reason this exists is to allow for - /// the separation of UnityEditor calls from the controllers. Used to support - /// testing and enforce cleaner separations. Mirrors partial API of Unity - /// EditorPrefs class. - /// - public class UnityEditorPrefs : IEditorPrefs { - public void DeleteAll() { - EditorPrefs.DeleteAll(); - } - - public void DeleteKey(string key) { - EditorPrefs.DeleteKey(key); - } - - public bool GetBool(string key, bool defaultValue = false) { - return EditorPrefs.GetBool(key, defaultValue); - } - - public float GetFloat(string key, float defaultValue = 0) { - return EditorPrefs.GetFloat(key, defaultValue); - } - - public int GetInt(string key, int defaultValue = 0) { - return EditorPrefs.GetInt(key, defaultValue); - } - - public string GetString(string key, string defaultValue = "") { - return EditorPrefs.GetString(key, defaultValue); - } - - public bool HasKey(string key) { - return EditorPrefs.HasKey(key); - } - - public void SetBool(string key, bool value) { - EditorPrefs.SetBool(key, value); - } - - public void SetFloat(string key, float value) { - EditorPrefs.SetFloat(key, value); - } - - public void SetInt(string key, int value) { - EditorPrefs.SetInt(key, value); - } - - public void SetString(string key, string value) { - EditorPrefs.SetString(key, value); - } - } - - /// - /// UnityController acts as a wrapper around Unity APIs that other controllers use. - /// This is useful during testing as it allows separation of concerns. - /// - public static class UnityController { - public static IEditorPrefs EditorPrefs { get; private set; } - public static IUnityEnvironmentData EnvironmentData { get; private set; } - static UnityController() { - EditorPrefs = new UnityEditorPrefs(); - EnvironmentData = new UnityEnvironmentData(); - } - - /// - /// Swaps the environment data. - /// - /// New env data. - public static void SwapEnvironmentData(IUnityEnvironmentData newEnvData) { - EnvironmentData = newEnvData; - } - - /// - /// Swaps the editor prefs. Exposed for testing. - /// - /// New editor prefs. - public static void SwapEditorPrefs(IEditorPrefs newEditorPrefs) { - EditorPrefs = newEditorPrefs; - } - } - - /// - /// Helper class for interfacing with package manager specific settings. - /// - public static class SettingsController { - /// - /// Location on filesystem where downloaded packages are stored. - /// - public static string DownloadCachePath { - get { - return UnityController.EditorPrefs.GetString(Constants.KEY_DOWNLOAD_CACHE, - GetDefaultDownloadPath()); - } - set { - if (Directory.Exists(value)) { - UnityController.EditorPrefs.SetString(Constants.KEY_DOWNLOAD_CACHE, value); - } else { - throw new Exception("Download Cache location does not exist: " + - value); - } - } - } - /// - /// Verbose logging property flag. - /// - public static bool VerboseLogging { - get { - return UnityController.EditorPrefs.GetBool( - Constants.VERBOSE_PACKAGE_MANANGER_LOGGING_KEY, true); - } - set { - UnityController.EditorPrefs.SetBool( - Constants.VERBOSE_PACKAGE_MANANGER_LOGGING_KEY, value); - } - } - /// - /// Determines if the user should be able to see the plugin package files - /// before installing a plugin. - /// - public static bool ShowInstallFiles { - get { - return UnityController.EditorPrefs.GetBool(Constants.SHOW_INSTALL_ASSETS_KEY, - true); - } - set { - UnityController.EditorPrefs.SetBool(Constants.SHOW_INSTALL_ASSETS_KEY, value); - } - } - - /// - /// Gets the default download cache path. This is where plugin packages - /// are downloaded before installation. - /// - /// The default download path. - static string GetDefaultDownloadPath() { - return Path.Combine(Environment.GetFolderPath( - Environment.SpecialFolder.LocalApplicationData), - Constants.GPM_CACHE_NAME); - } - } - - /// - /// Response codes used by controllers in PackageManager. - /// - public enum ResponseCode { - /// - /// Registry already in memory - /// - REGISTRY_ALREADY_PRESENT, - /// - /// Registry was added to recorded preferences - /// - REGISTRY_ADDED, - /// - /// Registry was removed from recorded preferences - /// - REGISTRY_REMOVED, - /// - /// Registry that was requested could not be found - /// - REGISTRY_NOT_FOUND, - /// - /// Requested plugin install halted because it is already installed - /// - PLUGIN_ALREADY_INSTALLED, - /// - /// Could not process the plugin binary package - /// - PLUGIN_BINARY_ERROR, - /// - /// Plugin was successfully installed - /// - PLUGIN_INSTALLED, - /// - /// Plugin resolution (getting its data from source) succeeded - /// - PLUGIN_RESOLVED, - /// - /// Plugin was successfully removed - /// - PLUGIN_REMOVED, - /// - /// Plugin metadata was not processed due to an error - /// - PLUGIN_METADATA_FAILURE, - /// - /// Requested plugin was not found based on information provided - /// - PLUGIN_NOT_FOUND, - /// - /// Plugin is not installed in the project - /// - PLUGIN_NOT_INSTALLED, - /// - /// Plugin removal halted because of an error - /// - PLUGIN_NOT_REMOVED, - /// - /// Fetch of data resulted in an error - /// - FETCH_ERROR, - /// - /// Fetch of data took too long and exceeded timout period - /// - FETCH_TIMEOUT, - /// - /// Fetch of data completed successfully - /// - FETCH_COMPLETE, - /// - /// The XML data caused an exception - /// - XML_INVALID, - /// - /// The URI value does not point to a reachable location or is not well formed - /// - URI_INVALID, - } - - /// - /// URI data fetcher interface. - /// - public interface IUriDataFetcher { - /// - /// Thread blocking fetch for a string result. - /// - /// The fetch as string. - /// URI. - /// Result string. - ResponseCode BlockingFetchAsString(Uri uri, out string result); - /// - /// Thread blocking fetch for a byte[] result. - /// - /// The fetch as bytes. - /// URI. - /// Result bytes. - ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result); - } - - /// - /// URI fetcher. b/34930031 investigate using external fetch through compiled python lib - /// - public class UriFetcher : IUriDataFetcher { - byte[] bytesResult; - string textResult; - bool isForBytes = false; - - private ResponseCode DoBlockingFetch(Uri uri) { - var www = new WWW(uri.AbsoluteUri); - double startTime = EditorApplication.timeSinceStartup; - while (www.error == null && !www.isDone) { - var elapsed = EditorApplication.timeSinceStartup - startTime; - if (elapsed > Constants.FETCH_TIMOUT_THRESHOLD) { - LoggingController.Log("Fetch threshold exceeded."); - return ResponseCode.FETCH_TIMEOUT; - } - } - if (www.error != null) { - LoggingController.Log(www.error); - return ResponseCode.FETCH_ERROR; - } - if (isForBytes) { - bytesResult = www.bytes; - } else { - textResult = www.text; - } - return ResponseCode.FETCH_COMPLETE; - } - - /// - /// Blocking fetch of URI where the expected result is returned as - /// byte information. - /// - /// A byte array. - /// URI location to fetch from. - /// Result is the container that holds the result - /// byte data. - public ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result) { - isForBytes = true; - ResponseCode rc = DoBlockingFetch(uri); - result = bytesResult; - return rc; - } - - /// - /// Blocking fetch of URI where the expected result is returned as a - /// string. - /// - /// A string. - /// URI location to fetch from. - /// Result is the container that holds the result - /// string data. - public ResponseCode BlockingFetchAsString(Uri uri, out string result) { - ResponseCode rc = DoBlockingFetch(uri); - result = textResult; - return rc; - } - } - - /// - /// Intermediator URI data fetch controller. - /// - public static class UriDataFetchController { - public static IUriDataFetcher uriFetcher = new UriFetcher(); - /// - /// Swaps the URI data fetcher with another instance. Used in testcases - /// to setup deterministic execution. - /// - /// New fetcher. - public static void SwapUriDataFetcher(IUriDataFetcher newFetcher) { - uriFetcher = newFetcher; - } - } - - /// - /// Some constants are inconvenient for testing purposes. This class wraps the constants that - /// may be changed during test execution. - /// - public static class TestableConstants { - public static bool testcase = false; - static string debugDefaultRegistryLocation = Constants.DEFAULT_REGISTRY_LOCATION; - - /// - /// Gets or sets the default registry location. Is environment context aware. - /// - /// The default registry location. - public static string DefaultRegistryLocation { - get { - return debugDefaultRegistryLocation; - } - /// - /// Sets the default registry location only if testing enabled. - /// - /// Value. - set { - if (testcase) { - debugDefaultRegistryLocation = value; - } else { - LoggingController.LogError( - "Attempted to set DefaultRegistryLocation outside of testcase."); - } - } - } - } - - /// - /// Registry wrapper pairs Uri of registry with Registry object. This allows - /// for a cleaner separation of model and logic. This makes it easier to key - /// a specific Registry model object on its registered Uri. - /// - public class RegistryWrapper { - /// - /// Gets or sets the location of the Registry held in Model - /// - /// The location. - public Uri Location { get; set; } - /// - /// Gets or sets the model which is the actual registry. - /// - /// The model. - public Registry Model { get; set; } - } - - /// - /// Registry manager controller responsible for adding/removing known registry locations. The - /// set of known registries is available across all Unity projects. - /// - public static class RegistryManagerController { - /// - /// Registry database used to hold known registries, serializeable to xml. - /// - [XmlRoot("regdb")] - public class RegistryDatabase : PackageManagerModel { - [XmlArray("registries")] - [XmlArrayItem("reg-uri-string")] - public HashSet registryLocation = new HashSet(); - [XmlElement("lastUpdate")] - public string lastUpdate; - [XmlIgnore] - public Dictionary wrapperCache = - new Dictionary(); - } - - /// - /// The registry database of all recorded registry locations. Serialzed/Deserialized to - /// editor prefs. - /// - static RegistryDatabase regDb; - - /// - /// Initializes the class. - /// - static RegistryManagerController() { - LoadRegistryDatabase(); - } - - /// - /// Saves the registry database to editor prefs. - /// - static void SaveRegistryDatabase() { - if (regDb == null) { - return; - } - regDb.lastUpdate = DateTime.UtcNow.ToString("o"); - var xml = regDb.SerializeToXMLString(); - UnityController.EditorPrefs.SetString(Constants.KEY_REGISTRIES, xml); - } - - /// - /// Inflates the registry database from Unity editor preferences key. - /// Force reloading causes a new object instance to be created. - /// - /// If set to true force reload. - public static void LoadRegistryDatabase(bool forceReload = false) { - var existingRegDB = regDb; - var regDbXml = UnityController.EditorPrefs.GetString(Constants.KEY_REGISTRIES, null); - if ((regDbXml == null || regDbXml.Length == 0) && existingRegDB == null) { - CreateRegistryDatabase(); - } else { - var readRegDb = RegistryDatabase.LoadFromString(regDbXml); - - if (existingRegDB != null) { - // compare time stamps - var existingComparedToOther = Convert.ToDateTime(existingRegDB.lastUpdate) - .CompareTo(Convert.ToDateTime(readRegDb.lastUpdate)); - if (existingComparedToOther > 0 || forceReload) { - // The existing db is newer than the loaded one or we are forcing a refresh - // The source of truth is always what has been recorded to the editor pref. - regDb = readRegDb; - } - } else { - regDb = readRegDb; - } - } - if (regDb.registryLocation.Count == 0) { - // really there is no good reason to have an empty registry database - AddRegistry(new Uri(TestableConstants.DefaultRegistryLocation)); - } - RefreshRegistryCache(); - } - - /// - /// Creates a new registry database instance with a default registry. - /// Will indirectly destroy existing registry database held in memory. - /// - static void CreateRegistryDatabase() { - regDb = new RegistryDatabase(); - regDb.registryLocation.Add(TestableConstants.DefaultRegistryLocation); - regDb.lastUpdate = DateTime.UtcNow.ToString("o"); - } - - /// - /// Refreshes the registry cache for the provided RegistryWrapper. - /// - /// Wrapper. - public static void RefreshRegistryCache(RegistryWrapper wrapper = null) { - var regLocs = new List(regDb.registryLocation); - if (wrapper != null) { - regLocs.Clear(); - regLocs.Add(wrapper.Location.AbsoluteUri); - } - - foreach (var regUri in regLocs) { - var uri = new Uri(regUri); - string xmlData; - ResponseCode rc = UriDataFetchController - .uriFetcher.BlockingFetchAsString(uri, out xmlData); - if (rc != ResponseCode.FETCH_COMPLETE) { - LoggingController.LogError( - string.Format("Failed attempt to fetch {0} got response code {1}", regUri, rc)); - continue; - } - try { - regDb.wrapperCache[uri] = new RegistryWrapper { - Location = uri, - Model = Registry.LoadFromString(xmlData) - }; - } catch (Exception e) { - LoggingController.LogError( - string.Format("EXCEPTION: {0} inflating Registry {1} using returned xml." + - "\n\n{2}\n\n", e, regUri, xmlData)); - continue; - } - } - } - - /// - /// Attempts to add a registry to the known set of registries using the - /// provided Uri. If the Uri turns out to be a registry location that is - /// not already part of the known set of registries then it is added to - /// the set of known registries. Once a valid registry Uri has been provided - /// it will persist across Unity projects. - /// - /// A - /// URI. - public static ResponseCode AddRegistry(Uri uri) { - if (regDb != null && regDb.registryLocation.Contains(uri.AbsoluteUri)) { - return ResponseCode.REGISTRY_ALREADY_PRESENT; - } - - string xmlData; - ResponseCode rc = UriDataFetchController - .uriFetcher.BlockingFetchAsString(uri, out xmlData); - if (rc != ResponseCode.FETCH_COMPLETE) { - LoggingController.LogError( - string.Format("Attempted fetch of {0} got response code {1}", - uri.AbsoluteUri, rc)); - return rc; - } - - try { - var reg = Registry.LoadFromString(xmlData); - regDb.registryLocation.Add(uri.AbsoluteUri); - SaveRegistryDatabase(); - regDb.wrapperCache[uri] = new RegistryWrapper { Location = uri, Model = reg }; - var xml = regDb.SerializeToXMLString(); - UnityController.EditorPrefs.SetString(Constants.KEY_REGISTRIES, xml); - } catch (Exception e) { - LoggingController.LogError( - string.Format("EXCEPTION Adding Registry {0}: \n\n{1}", uri.AbsoluteUri, e)); - return ResponseCode.XML_INVALID; - } - - return ResponseCode.REGISTRY_ADDED; - } - - /// - /// Attempts to remove the registry identified by the uri. - /// - /// A ResponseCode - /// URI. - public static ResponseCode RemoveRegistry(Uri uri) { - if (uri == null) { - LoggingController.LogWarning("Attempted to remove a registry with null uri."); - return ResponseCode.URI_INVALID; - } - if (regDb == null || regDb.wrapperCache == null) { - LoadRegistryDatabase(); - } - - if (!regDb.wrapperCache.ContainsKey(uri)) { - LoggingController.LogWarning( - string.Format("No registry to remove at {0}. Not found in cache.", - uri.AbsoluteUri)); - return ResponseCode.REGISTRY_NOT_FOUND; - } - regDb.wrapperCache.Remove(uri); - regDb.registryLocation.Remove(uri.AbsoluteUri); - SaveRegistryDatabase(); - var xml = regDb.SerializeToXMLString(); - UnityController.EditorPrefs.SetString(Constants.KEY_REGISTRIES, xml); - return ResponseCode.REGISTRY_REMOVED; - } - - /// - /// Gets all wrapped registries. - /// - /// All wrapped registries. - public static List AllWrappedRegistries { - get { - var result = new List(); - result.AddRange(regDb.wrapperCache.Values); - return result; - } - } - - /// - /// Gets the URI for registry. - /// - /// The URI for registry or null if not found. - /// Registry object - public static Uri GetUriForRegistry(Registry reg) { - if (reg == null) { - return null; - } - Uri result = null; - foreach (var wrapper in regDb.wrapperCache.Values) { - if (wrapper.Model.GenerateUniqueKey().Equals(reg.GenerateUniqueKey())) { - result = wrapper.Location; - break; - } - } - return result; - } - } - - /// - /// Packaged plugin wrapper class that binds multiple models togeather. This - /// makes it easier to pass around a bundled representation of a packaged - /// plugin. - /// - public class PackagedPlugin { - /// - /// Gets or sets the parent registry which is the owner of this plugin. - /// - /// The parent registry. - public Registry ParentRegistry { get; set; } - /// - /// Gets or sets the meta data which holds the details about the plugin. - /// - /// The meta data. - public PluginMetaData MetaData { get; set; } - /// - /// Gets or sets the description for the release version of the plugin. - /// - /// The description. - public PluginDescription Description { get; set; } - /// - /// Gets or sets the location of where the MetaData came from. - /// - /// The location. - public Uri Location { get; set; } - } - - /// - /// Plugin manager controller. - /// - public static class PluginManagerController { - /// - /// The plugin cache associates RegistryWrapper keys to lists of PackagedPlugins that are - /// part of the set of plugins defined by the Registry. - /// - static Dictionary> pluginCache = - new Dictionary>(); - /// - /// The plugin map is used to quickly lookup a specific plugin using its unique key. - /// - static Dictionary pluginMap = - new Dictionary(); - /// - /// Versionless plugin map is used to quickly lookup a specific plugin using its group and - /// artifact. - /// - static Dictionary versionlessPluginMap = - new Dictionary(); - - /// - /// Creates the versionless key. - /// - /// The versionless key. - /// A PackagedPlugin - public static string CreateVersionlessKey(PackagedPlugin plugin) { - return string.Join(Constants.STRING_KEY_BINDER, new string[] { - plugin.MetaData.groupId, - plugin.MetaData.artifactId - }); - } - - /// - /// Changes a versioned plugin unique key into a versionless one. - /// - /// Versionless plugin key. - /// Versioned plugin key. - public static string VersionedPluginKeyToVersionless(string versionedPluginKey) { - return versionedPluginKey - .Substring(0, versionedPluginKey.LastIndexOf(Constants.STRING_KEY_BINDER)); - } - - /// - /// Uses the versionless key to lookup and return a packaged plugin. If - /// no matching packaged plugin exists then returns null. The format of - /// the versionless key is "plugin-groupId:plugin-artifactId". - /// - /// The plugin for versionless key. - /// Versionless plugin key. - public static PackagedPlugin GetPluginForVersionlessKey(string versionlessKey) { - PackagedPlugin plugin = null; - versionlessPluginMap.TryGetValue(versionlessKey, out plugin); - return plugin; - } - - /// - /// Gets the list of all plugins across all registered registries. If - /// refresh is true then the plugin data returned is guaranteed to be - /// up to date since each plugin source data is fetched prior to this - /// method returning. - /// - /// The list of all plugins. - /// If set to true refresh. - public static List GetListOfAllPlugins(bool refresh = false) { - var result = new List(); - foreach (var wr in RegistryManagerController.AllWrappedRegistries) { - result.AddRange(GetPluginsForRegistry(wr, refresh)); - } - return result; - } - - /// - /// Gets the plugins for provided registry. May try to resolve the data for the registry - /// plugin modules if registry has not been seen before or if the cache was flushed. - /// - /// The plugins for registry or null if there was a failure. - /// RegistryWrapper - public static List GetPluginsForRegistry(RegistryWrapper regWrapper, - bool refresh = false) { - var pluginList = new List(); - if (regWrapper == null) { - return pluginList; - } - if (!refresh) { // Just return what ever is known currently. - if (pluginCache.TryGetValue(regWrapper, out pluginList)) { - // data available - return pluginList; - } - // did not find anything for the registry so we need to return an empty list - return new List(); - } - - PurgeFromCache(regWrapper); - - pluginList = new List(); - // now there is no trace of plugins from the registry - time to rebuild data from source - // the module locations are known once a registry is resolved - foreach (var module in regWrapper.Model.modules.module) { - // Is the module a remote or local? - Uri pluginModuleUri = ResolvePluginModuleUri(regWrapper, module); - PackagedPlugin plugin; - ResponseCode rc = - ResolvePluginDetails(pluginModuleUri, regWrapper.Model, out plugin); - switch (rc) { - case ResponseCode.PLUGIN_RESOLVED: - // resolved so we add it - pluginList.Add(plugin); - AddOrUpdatePluginMap(plugin); - AddOrUpdateVersionlessPluginMap(plugin); - break; - default: - LoggingController.LogWarning( - string.Format("Plugin not resolved: {0} for uri {1}", rc, pluginModuleUri)); - break; - } - } - - pluginCache[regWrapper] = pluginList; - - foreach (var plugin in pluginList) { - var versionLessKey = CreateVersionlessKey(plugin); - pluginMap[versionLessKey] = plugin; - } - return pluginList; - } - - /// - /// Purges all plugins belonging to the provided registry wrapper from controller cache. - /// - /// Registry wrapper that is the parent of all the plugins to - /// purge from the controller cache. - static void PurgeFromCache(RegistryWrapper regWrapper) { - List pluginList; - // a refresh has been requested - fetch is implied - // this is a refresh so we remove the stale data first - pluginCache[regWrapper] = null; - RegistryManagerController.RefreshRegistryCache(regWrapper); - // purge the correct plugins from the cache - if (pluginCache.TryGetValue(regWrapper, out pluginList)) { - if (pluginList == null) { - pluginCache[regWrapper] = new List(); - } else { - foreach (var plugin in pluginList) { - pluginMap[CreateVersionlessKey(plugin)] = null; - } - } - } - } - - /// - /// Adds the plugin to or updates the versionless plugin map plugin reference. - /// - /// Plugin. - static void AddOrUpdateVersionlessPluginMap(PackagedPlugin plugin) { - versionlessPluginMap[CreateVersionlessKey(plugin)] = plugin; - } - - /// - /// Adds the plugin to or updates the plugin map plugin reference. - /// - /// Plugin. - static void AddOrUpdatePluginMap(PackagedPlugin plugin) { - pluginMap[plugin.MetaData.UniqueKey] = plugin; - } - - /// - /// Resolves the plugin module URI since it could be local relative to the registry or a - /// fully qualified Uri. - /// - /// The plugin module URI. - /// Reg wrapper. - /// Module location. - static Uri ResolvePluginModuleUri(RegistryWrapper regWrapper, string moduleLoc) { - Uri pluginModuleUri; - try { - pluginModuleUri = new Uri(moduleLoc); - } catch { - // if local then rewrite as remote relative - pluginModuleUri = ChangeRegistryUriIntoModuleUri(regWrapper.Location, moduleLoc); - } - - return pluginModuleUri; - } - - /// - /// Resolves the plugin details. Is public in so that individual plugins can be re-resolved - /// which is needed in some situations. - /// - /// The plugin details. - /// URI. - /// Parent. - /// Plugin. - public static ResponseCode ResolvePluginDetails(Uri uri, Registry parent, - out PackagedPlugin plugin) { - string xmlData; - ResponseCode rc = UriDataFetchController - .uriFetcher.BlockingFetchAsString(uri, out xmlData); - if (rc != ResponseCode.FETCH_COMPLETE) { - plugin = null; - return rc; - } - PluginMetaData metaData; - PluginDescription description; - try { - metaData = PluginMetaData.LoadFromString(xmlData); - Uri descUri = GenerateDescriptionUri(uri, metaData); - rc = UriDataFetchController.uriFetcher.BlockingFetchAsString(descUri, out xmlData); - if (rc != ResponseCode.FETCH_COMPLETE) { - plugin = null; - return rc; - } - description = PluginDescription.LoadFromString(xmlData); - } catch (Exception e) { - Console.WriteLine(e); - plugin = null; - return ResponseCode.PLUGIN_METADATA_FAILURE; - } - plugin = new PackagedPlugin { - Description = description, - Location = uri, - MetaData = metaData, - ParentRegistry = parent - }; - return ResponseCode.PLUGIN_RESOLVED; - } - - /// - /// Generates the description URI. - /// Public for testing. - /// - /// The description URI. - /// Plugin URI. - /// Meta data. - public static Uri GenerateDescriptionUri(Uri pluginUri, PluginMetaData metaData) { - var descUri = new Uri(Utility.GetURLMinusSegment(pluginUri.AbsoluteUri)); - descUri = new Uri(descUri, metaData.artifactId + "/"); - descUri = new Uri(descUri, metaData.versioning.release + "/"); - descUri = new Uri(descUri, Constants.DESCRIPTION_FILE_NAME); - return descUri; - } - - /// - /// Generates the binary URI location based on the plugin meta data and source of the - /// metadata. - /// Public for testing. - /// - /// The binary package URI. - /// Plugin metadata URI. - /// Meta data object for plugin. - public static Uri GenerateBinaryUri(Uri pluginUri, PluginMetaData metaData) { - var descUri = new Uri(Utility.GetURLMinusSegment(pluginUri.AbsoluteUri)); - descUri = new Uri(descUri, metaData.artifactId + "/"); - descUri = new Uri(descUri, metaData.versioning.release + "/"); - descUri = new Uri(descUri, GenerateBinaryFilename(metaData)); - return descUri; - } - - /// - /// Changes the registry URI into module URI. - /// Public for testing. - /// - /// A registry Uri related module URI. - /// Registry URI. - /// Local module name. - public static Uri ChangeRegistryUriIntoModuleUri(Uri registryUri, string localModuleName) { - var pluginUri = new Uri(Utility.GetURLMinusSegment(registryUri.AbsoluteUri)); - pluginUri = new Uri(pluginUri, localModuleName + "/"); - pluginUri = new Uri(pluginUri, Constants.MANIFEST_FILE_NAME); - return pluginUri; - } - - /// - /// Refresh the specified registry plugin data. - /// - /// RegistryWrapper with a valid registry. - public static void Refresh(RegistryWrapper regWrapper) { - GetPluginsForRegistry(regWrapper, true); - } - - /// - /// Generates a package binary filename for the plugin. - /// - /// The binary filename. - /// Plugin. - public static string GenerateBinaryFilename(PluginMetaData plugin) { - return string.Format("{0}.{1}", plugin.artifactId, plugin.packaging); - } - } - - /// - /// Unity asset database proxy interface. - /// - public interface IUnityAssetDatabaseProxy { - string[] GetAllAssetPaths(); - string[] GetLabelsForAssetAtPath(string path); - string[] FindAssets(string filter); - string GUIDToAssetPath(string guid); - void SetLabels(string path, string[] labels); - void ImportPackage(string packagePath, bool interactive); - bool DeleteAsset(string path); - string[] FindAssets(string filter, string[] searchInFolders); - void Refresh(ImportAssetOptions options = ImportAssetOptions.Default); - } - - /// - /// Unity engine asset database proxy to intermediate Unity AssertDatabase. - /// - public class UnityEngineAssetDatabaseProxy : IUnityAssetDatabaseProxy { - public bool DeleteAsset(string path) { - return AssetDatabase.DeleteAsset(path); - } - - public string[] FindAssets(string filter) { - return AssetDatabase.FindAssets(filter); - } - - public string[] FindAssets(string filter, string[] searchInFolders) { - return AssetDatabase.FindAssets(filter, searchInFolders); - } - - public string[] GetAllAssetPaths() { - return AssetDatabase.GetAllAssetPaths(); - } - - public string[] GetLabelsForAssetAtPath(string path) { - return AssetDatabase.GetLabels(AssetDatabase.LoadMainAssetAtPath(path)); - } - - public string GUIDToAssetPath(string guid) { - return AssetDatabase.GUIDToAssetPath(guid); - } - - public void ImportPackage(string packagePath, bool interactive) { - AssetDatabase.ImportPackage(packagePath, interactive); - } - - public void SetLabels(string path, string[] labels) { - AssetDatabase.SetLabels(AssetDatabase.LoadMainAssetAtPath(path), labels); - } - - public void Refresh(ImportAssetOptions options = ImportAssetOptions.Default) { - AssetDatabase.Refresh(); - } - } - - /// - /// Asset database controller that acts as an intermediary. Test cases can swap out the backing - /// databaseProxy instance. - /// - public static class AssetDatabaseController { - public static bool ImportInitiatedFromController { get; private set; } - static IUnityAssetDatabaseProxy databaseProxy = new UnityEngineAssetDatabaseProxy(); - public static void SwapDatabaseProxy(IUnityAssetDatabaseProxy newProxy) { - databaseProxy = newProxy; - } - public static string[] GetAllAssetPaths() { - return databaseProxy.GetAllAssetPaths(); - } - public static string[] GetLabelsForAssetAtPath(string path) { - return databaseProxy.GetLabelsForAssetAtPath(path); - } - public static string[] FindAssets(string filter) { - return databaseProxy.FindAssets(filter); - } - public static string GUIDToAssetPath(string guid) { - return databaseProxy.GUIDToAssetPath(guid); - } - public static void SetLabels(string path, string[] labels) { - databaseProxy.SetLabels(path, labels); - } - public static void ImportPackage(string packagePath, bool interactive) { - ImportInitiatedFromController = true; - databaseProxy.ImportPackage(packagePath, interactive); - } - public static bool DeleteAsset(string path) { - return databaseProxy.DeleteAsset(path); - } - public static string[] FindAssets(string filter, string[] searchInFolders) { - return databaseProxy.FindAssets(filter, searchInFolders); - } - public static void Refresh() { - databaseProxy.Refresh(); - } - public static void ClearImportFlag() { - ImportInitiatedFromController = false; - } - } - - /// - /// Project manager controller handles actions related to a project like installing, removing - /// plugins. - /// - [InitializeOnLoad] - public static class ProjectManagerController { - static readonly HashSet allProjectAssetLabels = new HashSet(); - static ProjectPackages gpmPackagesInProject; - /// - /// The project dirty flag - set if new plugin package installed or target platform changed - /// - static bool projectDirty = false; - static BuildTarget currentBuildTarget; - - static ProjectManagerController() { - gpmPackagesInProject = InflateProjectRecord(GetProjectRecordPath()); - EditorApplication.update += Update; - currentBuildTarget = EditorUserBuildSettings.activeBuildTarget; - EnsurePluginsDirectory(); - } - - /// - /// Unity editor will call this method during the Unity editor update loop. - /// - static void Update() { - if (EditorUserBuildSettings.activeBuildTarget != currentBuildTarget) { - currentBuildTarget = EditorUserBuildSettings.activeBuildTarget; - EnsurePluginsDirectory(); - projectDirty = true; - } - - if (projectDirty) { - // check project for re-resolve on target if dirty - RefreshProject(); - } - } - - /// - /// Ensures the plugins directory exists, creates if missing. - /// - static void EnsurePluginsDirectory() { - if (!AssetDatabase.IsValidFolder("Assets/Plugins")) { - AssetDatabase.CreateFolder("Assets", "Plugins"); - } - if (!AssetDatabase.IsValidFolder("Assets/Plugins/Android")) { - AssetDatabase.CreateFolder("Assets/Plugins", "Android"); - } - if (!AssetDatabase.IsValidFolder("Assets/Plugins/IOS")) { - AssetDatabase.CreateFolder("Assets/Plugins", "IOS"); - } - - AssetDatabase.Refresh(); - } - - /// - /// Removes the client from the project - /// - /// Client name. - static void RemoveClient(string clientName) { - LoggingController.Log( - string.Format("Removing Client for Key {0}", clientName)); - List clients = GetAllClients(); - if (clients == null) { - LoggingController.LogError( - string.Format("No Client for Key {0}", clientName)); - return; - } - ProjectClient removeMe = null; - foreach (var client in clients) { - if (client.GenerateUniqueKey().StartsWith(clientName)) { - removeMe = client; - LoggingController.Log( - string.Format("Found client object to remove {0}", removeMe)); - } - } - if (removeMe != null) { - gpmPackagesInProject.clients.Remove(removeMe); - WriteProjectPackages(); - LoggingController.Log( - string.Format("Removed client object for {0}", clientName)); - } - } - - /// - /// Refreshes the client, resolves it's dependencies if needed. - /// - /// Client. - static void RefreshClient(ProjectClient client) { - switch (currentBuildTarget) { - case BuildTarget.Android: - if (!client.resolvedForAndroid) { // Android deps have not been resolved - EnsureAllAssetsLabeled(client.Name); - PlayServicesSupport support = null; - if (!PlayServicesSupport.instances.TryGetValue(client.Name, out support)) { - support = PlayServicesSupport.CreateInstance(client.Name, - UnityController.EditorPrefs.GetString( - Constants.ANDROID_SDK_ROOT_PREF_KEY), - Constants.PROJECT_SETTINGS_KEY); - } - foreach (var packageDep in client.clientDependencies.androidDependencies) { - string[] packageIdsArray = null; - if (packageDep.args != null && packageDep.args.packageIds != null) { - packageIdsArray = new string[packageDep.args.packageIds.Count]; - packageDep.args.packageIds.CopyTo(packageIdsArray); - } - string[] repositories = null; - if (packageDep.args != null && packageDep.args.repositories != null) { - repositories = new string[packageDep.args.repositories.Count]; - packageDep.args.repositories.CopyTo(repositories); - } - support.DependOn(packageDep.group, - packageDep.artifact, - packageDep.version, - packageIdsArray, - repositories); - } - - WriteProjectPackages(); - EnsurePluginsDirectory(); - - try { - LoggingController.Log( - string.Format("About to resolve for client: {0}", client.Name)); - GooglePlayServices.PlayServicesResolver.Resolve( - resolutionComplete: () => { - AssetDatabase.Refresh(); - LoggingController.Log( - string.Format("Android resolution complete for client: {0}", - client.Name)); - client.resolvedForAndroid = true; - WriteProjectPackages(); - // tag/label all dependency files additive tags if needed - EnsureLabeledDependencies(client.Name); - }); - } catch (Exception e) { - LoggingController.LogError( - string.Format("EXCEPTION during Android resolve dependencies: {0}\n{1}", - e, e.StackTrace)); - } - } - break; - case BuildTarget.iOS: - // TODO: b/34936552 implement for iOS POD deps - break; - default: - break; - } - } - - /// - /// Ensures all assets belonging to a packaged plugin are labeled. - /// - /// Client name. - static void EnsureAllAssetsLabeled(string clientName) { - var client = GetClientForKey(clientName); - if (client == null) { - return; - } - foreach (var assetPath in client.assets) { - var asset = AssetDatabase.LoadMainAssetAtPath(assetPath); - if (asset != null) { - var existingLabels = AssetDatabase.GetLabels(asset); - var labelSet = new HashSet(); - labelSet.Add(Constants.GPM_LABEL_MARKER); - labelSet.Add(string.Join(Constants.STRING_KEY_BINDER, new string[] { - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_CLIENT, - clientName - })); - labelSet.Add(string.Join(Constants.STRING_KEY_BINDER, new string[] { - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_KEY, - clientName, - client.version - })); - labelSet.UnionWith(existingLabels); - var labels = new string[labelSet.Count]; - labelSet.CopyTo(labels); - AssetDatabase.SetLabels( - asset, - labels); - } - } - } - - /// - /// Ensures the dependency assets are labeled for the client. - /// - /// Client name. - static void EnsureLabeledDependencies(string clientName) { - var client = GetClientForKey(clientName); - if (client == null) { - return; - } - foreach (var depName in client.depNames) { - switch (currentBuildTarget) { - case BuildTarget.Android: - var name = depName.Substring(depName.IndexOf(':') + 1); - var assets = AssetDatabase.FindAssets(name, new string[] { - "Assets/Plugins/Android"}); - if (assets.Length > 0) { - var asset = AssetDatabase.LoadMainAssetAtPath( - AssetDatabase.GUIDToAssetPath(assets[0])); - if (asset != null) { - var existingLabels = AssetDatabase.GetLabels(asset); - var labelSet = new HashSet(); - labelSet.Add(Constants.GPM_LABEL_MARKER); - labelSet.Add(string.Join(Constants.STRING_KEY_BINDER, new string[] { - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_CLIENT, - clientName - })); - labelSet.UnionWith(existingLabels); - var labels = new string[labelSet.Count]; - labelSet.CopyTo(labels); - AssetDatabase.SetLabels( - asset, - labels); - } - } - break; - case BuildTarget.iOS: - LoggingController.LogError("IOS EnsureLabeledDependencies Not Implemented."); - break; - } - } - } - - /// - /// Refreshes the project by reading project file, checking each client package to see if - /// the current target platform has been resolved and if not then it resolves and updates - /// the list of assets associated with the client. - /// - static void RefreshProject() { - var clients = GetAllClients(); - foreach (var client in clients) { - RefreshClient(client); - } - } - - /// - /// Reloads the project packages from the xml file living above the Assets directory. - /// - static void ReloadProjectPackages() { - gpmPackagesInProject = InflateProjectRecord(GetProjectRecordPath()); - } - - /// - /// Writes the project packages to an xml file living above the Assets directory. - /// - static void WriteProjectPackages() { - if (gpmPackagesInProject != null) { - try { - File.WriteAllText(GetProjectRecordPath(), - gpmPackagesInProject.SerializeToXMLString()); - } catch (Exception e) { - LoggingController.LogError( - string.Format("Could not write project file due to exception - {0}", e)); - } - } - } - - /// - /// Gets the client for client name or creates new one. - /// - /// The client for client name. - /// Client name (versionless plugin key). - public static ProjectClient GetClientForKey(string clientName) { - LoggingController.Log( - string.Format("Getting Client for Key {0}", clientName)); - List clients = GetAllClients(); - if (clients == null) { - return null; - } - ProjectClient projectClient = null; - foreach (var client in clients) { - try { - var v = client.GenerateUniqueKey(); - if (PluginManagerController - .VersionedPluginKeyToVersionless(v).Equals(clientName)) { - // client exists in project - LoggingController.Log( - string.Format("Discovered client {0} in project record.", clientName)); - projectClient = client; - } - } catch { - // an un-initialized client in set - skip - continue; - } - } - if (projectClient == null) { - LoggingController.Log( - string.Format("Initialized new client {0}. Call SaveClient to persist.", - clientName)); - projectClient = new ProjectClient(); - string[] keyComponents = clientName.Split(Constants.STRING_KEY_BINDER[0]); - projectClient.groupId = keyComponents[0]; - projectClient.artifactId = keyComponents[1]; - projectClient.version = Constants.VERSION_UNKNOWN; - gpmPackagesInProject.clients.Add(projectClient); - } - return projectClient; - } - - /// - /// Gets all clients listed in the project.xml that lives above the Assets directory. - /// - /// The all clients. - public static List GetAllClients() { - if (gpmPackagesInProject == null) { - ReloadProjectPackages(); - if (gpmPackagesInProject == null) { - // no project to load - return null; - } - } - return gpmPackagesInProject.clients; - } - - /// - /// Removes the client record for key from the project.xml that lives above the Assets - /// directory as well as the in memory model object. - /// - /// Client name. - public static void RemoveClientForKey(string clientName) { - var client = GetClientForKey(clientName); - if (client != null) { - gpmPackagesInProject.clients.Remove(client); - WriteProjectPackages(); - } - } - - /// - /// Saves the client the project.xml that lives above the Assets directory as well as the - /// in memory model object. - /// - /// Client name. - public static void SaveClient(string clientName) { - var client = GetClientForKey(clientName); - if (client != null) { - WriteProjectPackages(); - } - } - - /// - /// Inflates the project record from an xml file at the path provided. - /// - /// The project record. - /// Project file. - static ProjectPackages InflateProjectRecord(string projectFile) { - ProjectPackages result = null; - if (File.Exists(projectFile)) { - try { - result = ProjectPackages.LoadFromFile(projectFile); - } catch (Exception e) { - LoggingController.LogError(string.Format("Exception loading project meta: {0}", - e)); - } - } - if (result == null) { - result = new ProjectPackages(); - LoggingController.Log( - string.Format("Project GPM data does not exist. Creating new object.")); - } - return result; - } - - /// - /// Gets the project record path that lives above the Assets directory. - /// - /// The project record path. - static string GetProjectRecordPath() { - return Path.Combine(Path.Combine( - UnityController.EnvironmentData.GetApplicationDataPath(), ".."), - Constants.PROJECT_RECORD_FILENAME); - } - - /// - /// Refreshes the list of asset labels that are present in the current project. - /// This method does NOT modify the asset labels on assets, it just reads them all. - /// - public static void RefreshListOfAssetLabels() { - allProjectAssetLabels.Clear(); - var paths = AssetDatabaseController.GetAllAssetPaths(); - foreach (var path in paths) { - var labels = AssetDatabaseController.GetLabelsForAssetAtPath(path); - if (labels.Length > 0) { - foreach (var label in labels) { - allProjectAssetLabels.Add(label); - } - } - } - } - - /// - /// Checks if the plugin identified by pluginKey is installed in project. - /// - /// true, if plugin installed in project was ised, false - /// otherwise. - /// Plugin key. - public static bool IsPluginInstalledInProject(string pluginKey) { - var versionlessKey = PluginManagerController.VersionedPluginKeyToVersionless(pluginKey); - var listOfClients = GetAllClients(); - if (listOfClients == null) { - return false; - } - foreach (var client in listOfClients) { - // Versionless comparison here - if (PluginManagerController - .VersionedPluginKeyToVersionless(client.GenerateUniqueKey()) - .Equals(versionlessKey)) { - return true; - } - } - return false; - } - - /// - /// Checks if the specific plugin version is installed. - /// - /// true, if plugin version in project, false otherwise. - /// Plugin key (includes version). - public static bool IsPluginVersionInProject(string pluginKey) { - var listOfClients = GetAllClients(); - if (listOfClients == null) { - return false; - } - foreach (var client in listOfClients) { - if (client.GenerateUniqueKey().Equals(pluginKey)) { - return true; - } - } - return false; - } - - /// - /// Installs the plugin into the current project. Will download the plugin package if needed - /// but will check for local copy first in the download cache location. Calls the resolve - /// dependencies regardless of if auto-resolution is enabled. - /// - /// A response code - /// Plugin key. - public static ResponseCode InstallPlugin(string pluginKey) { - LoggingController.Log( - string.Format("Attempt install of plugin with key {0}.", pluginKey)); - // Is the plugin already installed and is it available to install. - if (IsPluginInstalledInProject(pluginKey)) { - LoggingController.Log("Plugin already installed!"); - return ResponseCode.PLUGIN_ALREADY_INSTALLED; - } - - // non-version check - 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) { - - var depsModels = new List(); - // look for *gpm.dep.xml in importedAssets - // TODO: b/34936751 handle case of multiple gpm.dep.xml files found - foreach (string str in importedAssets) { - if (str.EndsWith(Constants.GPM_DEPS_XML_POSTFIX)) { - // this is the deps file - resolve fully - var res = File.ReadAllText(Path.GetFullPath(str)); - try { - depsModels.Add(PackageDependencies.LoadFromString(res)); - } catch (Exception e) { - LoggingController.Log(string.Format("{0}: \n{1}", e, res)); - } - LoggingController.Log( - string.Format("OnPostprocessAllAssets: Dependencies xml: {0}", res)); - } - } - foreach (var depsModel in depsModels) { - ProcessDepModel(depsModel, importedAssets); - } - - foreach (var deleted in deletedAssets) { - LoggingController.Log(string.Format("Observed deletion of: {0}", deleted)); - } - } - } - - /// - /// Processes the dependency model with the assets being imported. - /// - /// Deps model. - /// Imported assets. - static void ProcessDepModel(PackageDependencies depsModel, string[] importedAssets) { - string clientName = string.Format("{0}:{1}", depsModel.groupId, depsModel.artifactId); - LoggingController.Log("Dependencies Client: " + clientName); - var projectClient = GetClientForKey(clientName); - if (projectClient.version.Equals(Constants.VERSION_UNKNOWN)) { - // new packaged plugin install - projectClient.version = depsModel.version; - } else { - // new version being installed over an older version? - // TODO: b/34936656 version compare - make sure new version is newer or same - // - if newer then need to remove all old assets - } - - projectClient.assets.AddRange(importedAssets); - projectClient.clientDependencies = depsModel; - SaveClient(clientName); - // mark project dirty - projectDirty = true; - } - } -} diff --git a/source/PackageManager/src/Models.cs b/source/PackageManager/src/Models.cs deleted file mode 100644 index cfc6b60a..00000000 --- a/source/PackageManager/src/Models.cs +++ /dev/null @@ -1,427 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml.Serialization; - - /// - /// Abstract base class for XML serializable model classes. Derived model - /// classes get implementations of common methods for load and save of model - /// data. - /// - public abstract class PackageManagerModel { - /// - /// The xml model version. Used to detect and handle future model - /// changes. - /// - [XmlElement("xmlModelVersion")] - public string xmlModelVersion; - - // TODO: b/34936401 add xmlModelVersion validation. - - /// - /// Deserializes a model from a provided stream containing XML data for the model. - /// - /// The from stream. - /// Reader. - public static T LoadFromStream(StreamReader reader) { - return (T)((new XmlSerializer(typeof(T)).Deserialize(reader))); - } - - /// - /// Deserializes a model from a specified XML model file. - /// - /// The inflated model object. Will throw an exception if the - /// file was not found. - /// The XML file path to read from. - public static T LoadFromFile(string file) { - return LoadFromStream(new StreamReader(file, Encoding.UTF8, true)); - } - - /// - /// Builds model tree from string containing valid XML. - /// - /// The model built from the provided utf-8 string. - /// Xml data encoded in utf-8. - public static T LoadFromString(string xmlData) { - return LoadFromStream( - new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(xmlData)))); - } - - /// - /// Serializes the model to an XML string. - /// - /// An XML formatted string representing the model state. - /// - public string SerializeToXMLString() { - var serializer = new XmlSerializer(typeof(T)); - var memoryStream = new MemoryStream(); - var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8); - serializer.Serialize(streamWriter, this); - byte[] utf8EncodedXml = memoryStream.ToArray(); - return Encoding.UTF8.GetString(utf8EncodedXml, 0, utf8EncodedXml.Length); - } - } - - /// - /// Abstract base class for XML serializable model classes. Derived model - /// classes get implementations of common methods for asset labels and keys. - /// - public abstract class PackageManagerLabeledModel : PackageManagerModel { - /// - /// The group identifier based on Maven POM model. - /// https://maven.apache.org/guides/mini/guide-naming-conventions.html - /// - [XmlElement("groupId")] - public string groupId; - - /// - /// The artifact identifier based on Maven POM model. - /// https://maven.apache.org/guides/mini/guide-naming-conventions.html - /// - [XmlElement("artifactId")] - public string artifactId; - - /// - /// The version based on Maven POM model. - /// https://maven.apache.org/guides/mini/guide-naming-conventions.html - /// - [XmlElement("version")] - public string version; - - /// - /// Meta information for the plugin encoded as a string. - /// If groupId, artifactId or version are not set then this value will be null. - /// - /// The asset label. - public string GenerateAssetLabel() { - if (groupId == null || artifactId == null || version == null) { - return null; - } - return string.Join( - Constants.STRING_KEY_BINDER, - new string[]{ - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_KEY, - GenerateUniqueKey() - }); - } - - /// - /// Generates the unique key based on the current groupId, artifactId and version. - /// - /// The unique key. - public string GenerateUniqueKey() { - if (groupId == null || artifactId == null || version == null) { - throw new Exception(string.Format("Attempted to generate unique " + - "key on object {0} without setting " + - "object state. [groupId = {1}, artifactId = " + - "{2}, version = {3}]", this, ""+groupId, - ""+artifactId, ""+version)); - } - return string.Join(Constants.STRING_KEY_BINDER, - new string[] { groupId, artifactId, version }); - } - } - - /// - /// Registry model class contains details about registered plugins which are - /// part of a specific registry instance. - /// - /// A registry is a URI addressable XML model that references a set of - /// PackageManager compatable packaged plugins. A registry encapsulates the - /// concept of a lookup group. - /// - [XmlRoot("registry")] - public class Registry : PackageManagerLabeledModel { - /// - /// When was this registry last updated in epoch time. - /// - [XmlElement("lastUpdated")] - public long lastUpdated; - /// - /// The modules (packaged plugin references) contained within this registry. - /// - [XmlElement("modules")] - public Modules modules = new Modules(); - } - - /// - /// Modules is a container that holds the list of registered plugins. It is - /// used to keep the XML model clean and readable. - /// - public class Modules : PackageManagerModel { - /// - /// A module as represented in this model can be one of the following: - /// 1) A groupId of a packaged plugin, if that packaged plugin is relative to the registry. - /// (meaning that it has a path that is a decendent of the registry Uri path) - /// 2) An absolute Uri to a location of a packaged plugin's package-manifest.xml - /// - /// Note: It is permitted to mix representations in a registry model. Meaning you can have - /// a mix of groupId representations and absolute Uri representations in the same registry. - /// - [XmlElement("module")] - public List module = new List(); - } - - /// - /// Plugin meta data associated with a plugin for use in package management. - /// This model contains the bulk of the packaged plugin details used for - /// managing plugin installation and deletion. - /// - [XmlRoot("metadata")] - public class PluginMetaData : PackageManagerLabeledModel { - /// - /// Format eg. 1.2.3 - /// - [XmlElement("modelVersion")] - public string modelVersion; - - /// - /// What format the binary package is in. Will usually be "unitypackage". - /// - [XmlElement("packaging")] - public string packaging; - - /// - /// A container element that holds a list of all versions the plugin has. - /// - [XmlElement("versioning")] - public Versioning versioning = new Versioning(); - - /// - /// The ISO 8601 date this plugin was last updated. - /// - [XmlElement("lastUpdated")] - public long lastUpdated; - - /// - /// UniqueKey format: groupId:artifactId:version - /// - /// The unique key. - [XmlIgnore] - public string UniqueKey { - get { - return GenerateUniqueKey(); - } - } - } - - /// - /// Versioning is a container class for release information and other available - /// versions of the plugin. - /// - public class Versioning : PackageManagerModel { - /// - /// The currently released version of the plugin package. This value is - /// used to help select the best version to install in a project. - /// - [XmlElement("release")] - public string release; - - /// - /// The available and published versions of the packaged plugin. - /// - [XmlArray("versions")] - [XmlArrayItem("version")] - public HashSet versions = new HashSet(); - } - - /// - /// A description of the packaged plugin in terms of a specific language code. - /// - /// TODO(krispy): add support for additional languages - /// - [XmlRoot("language")] - public class Language : PackageManagerModel { - /// - /// The lang code is used to indicate the language used in the description. - /// Currently only a value of 'en' is supported by the UI. - /// - [XmlAttribute("type")] - public string langCode; - - /// - /// The name of the plugin in a human readable form. - /// - [XmlElement("name")] - public string name; - - /// - /// A short description of the plugin. Usually a single marketing sentence. - /// - [XmlElement("short")] - public string shortDesc; - - /// - /// A long form description of the packaged plugin. Used to describe features - /// and bug fixes. - /// - [XmlElement("full")] - public string fullDesc; - } - - /// - /// Plugin description containing the text details about a plugin as will be - /// presented to the user in the Package Manager UI. - /// - [XmlRoot("description")] - public class PluginDescription : PackageManagerModel { - /// - /// The set of descriptions for this packaged plugin. - /// - [XmlArray("languages")] - [XmlArrayItem("language")] - public List languages = new List(); - } - - /// - /// Package dependencies model representing the JarResolver dependencies a package plugin has. - /// - [XmlRoot("package-dependencies")] - public class PackageDependencies : PackageManagerLabeledModel { - /// - /// The root dependencies that the Unity plugin package has. - /// - [XmlArray("android-dependencies")] - [XmlArrayItem("android-dependency")] - public List androidDependencies = - new List(); - /// - /// The iOS Pod dependencies. - /// - [XmlArray("ios-pod-dependencies")] - [XmlArrayItem("ios-pod-dependency")] - public List iOSDependencies = new List(); - } - - /// - /// IOS Package dependency model representing a specific iOS dependency. - /// - public class IOSPodDependency : PackageManagerModel { - /// - /// The name of the POD. - /// - [XmlElement("name")] - public string name; - /// - /// The version of the POD. - /// - [XmlElement("version")] - public string version; - } - - /// - /// Package dependency model representing a specific android dependency. - /// - public class AndroidPackageDependency : PackageManagerModel { - /// - /// The group identifier for the Android dependency. eg "com.google.android.gms" - /// - [XmlElement("group")] - public string group; - /// - /// The artifact identifier for the Android dependency. eg. "play-services-ads" - /// - [XmlElement("artifact")] - public string artifact; - /// - /// The flexible version identifier for the Android dependency. eg. "LATEST", "23.1+" etc. - /// - [XmlElement("version")] - public string version; - /// - /// The arguments to support where to find details about the dependency. - /// - [XmlElement("args")] - public DependencyArgument args = new DependencyArgument(); - } - - /// - /// Dependency argument set containing sets of android packages and repositories - /// - public class DependencyArgument : PackageManagerModel { - /// - /// Maps to the concept of PlayServicesSupport/Dependency packageIds. - /// Eg. {"extra-google-m2repository","extra-android-m2repository"} - /// - [XmlArray("android-packages")] - [XmlArrayItem("android-package")] - public List packageIds = new List(); - /// - /// Maps to the concept of PlayServicesSupport/Dependency repositories - /// - [XmlArray("repositories")] - [XmlArrayItem("repository")] - public List repositories = new List(); - } - - /// - /// Project packages model used to represent a manifest of what packaged plugins have been added - /// to the project. This model supports the ability to cleanly remove packaged plugins without - /// relying on the JarResolver. - /// - [XmlRoot("gpm-project")] - public class ProjectPackages : PackageManagerModel { - [XmlArray("clients")] - [XmlArrayItem("client")] - public List clients = new List(); - } - - /// - /// Project client represents an installed packaged plugin with all known - /// data associated with its installation into a project. - /// - public class ProjectClient : PackageManagerLabeledModel { - /// - /// The client dependencies declared in the gpm.dep.xml for the package - /// - [XmlElement("package-dependencies")] - public PackageDependencies clientDependencies; - /// - /// The assets - all known that belong to this package - /// - [XmlArray("assets")] - [XmlArrayItem("asset")] - public List assets = new List(); - /// - /// List of versionless asset names from resolution - /// - [XmlArray("resolved-dep-names")] - [XmlArrayItem("dep-name")] - public List depNames = new List(); - /// - /// Has this package resolved its android deps? - /// - [XmlElement("android-resolved")] - public bool resolvedForAndroid = false; - /// - /// Has this package resolved its ios deps? - /// - [XmlElement("ios-resolved")] - public bool resolvedForIOS = false; - - [XmlIgnore] - public string Name { - get { - return string.Format("{0}{1}{2}",groupId, Constants.STRING_KEY_BINDER, artifactId); - } - } - } -} \ No newline at end of file diff --git a/source/PackageManager/src/Utilities.cs b/source/PackageManager/src/Utilities.cs deleted file mode 100644 index 1085d7dc..00000000 --- a/source/PackageManager/src/Utilities.cs +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - - /// - /// A collection of useful utility methods. - /// - public static class Utility { - public static void EnsureDirectory(string directoryPath) { - (new DirectoryInfo(directoryPath)).Create(); - } - - /// - /// Checks that the URI is valid in terms of what Unity supports. - /// - /// true, if URI string is valid, false - /// otherwise. - /// URI. - public static bool IsValidUriString(string uri) { - Uri uriResult; - return Uri.TryCreate(uri,UriKind.Absolute, out uriResult); - } - - /// - /// Returns an absolute Uri string with one segment removed from the end. - /// - /// For example: - /// http://domain.com/segment1/segment2/segment3 - /// - /// would be returned as - /// - /// http://domain.com/segment1/segment2/ - /// - /// Also: - /// - /// http://domain.com/segment1/segment2/segment3 - /// and - /// http://domain.com/segment1/segment2/segment3/ - /// - /// would be treated the same regardless of the trailing slash. - /// - /// The absolute Uri minus the last segment. - /// URI to remove segment from - public static string GetURLMinusSegment(string uri) { - Uri outUri; - Uri.TryCreate(uri,UriKind.Absolute, out outUri); - return outUri.AbsoluteUri.Remove(outUri.AbsoluteUri.Length - - outUri.Segments.Last().Length); - } - } -} \ No newline at end of file diff --git a/source/PackageManager/src/Views.cs b/source/PackageManager/src/Views.cs deleted file mode 100644 index 8a4652dc..00000000 --- a/source/PackageManager/src/Views.cs +++ /dev/null @@ -1,427 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - using System; - using System.Collections.Generic; - using UnityEditor; - using UnityEngine; - - /// - /// Registry manager view provides the UI within Unity's editor allowing the user - /// to manage the set of registered registries. - /// - public class RegistryManagerView : EditorWindow { - Vector2 scrollPos; - string newRegUri; - bool registryDataDirty = true; - Stack installRegistry = new Stack(); - Stack uninstallRegistry = new Stack(); - - void OnInspectorGUI() { - EditorUtility.SetDirty(this); - } - - /// - /// Update override method called by Unity editor. The update cycle looks - /// for any action events that were generated in the OnGUI method by the - /// user and takes action on those events. - /// - void Update() { - if (registryDataDirty) { - LoggingController.Log("plugin data marked dirty - refreshing..."); - registryDataDirty = false; - RegistryManagerController.RefreshRegistryCache(); - EditorUtility.SetDirty(this); - } - - while (installRegistry.Count > 0) { - var regUriStr = installRegistry.Pop(); - try { - ResponseCode rc = RegistryManagerController.AddRegistry(new Uri(regUriStr)); - if (ResponseCode.REGISTRY_ADDED == rc) { - registryDataDirty = true; - } else if (ResponseCode.REGISTRY_ALREADY_PRESENT == rc) { - EditorUtility.DisplayDialog("Registry Already Present", - "The registry was NOT added since it" + - "is already known.", - "Ok"); - } else { - EditorUtility.DisplayDialog("Registry Location Not Valid", - string.Format( - "The registry cannot be added. An " + - "error has occurred using the provided " + - "location.\n\n{0}", rc), - "Ok"); - } - } catch (Exception e) { - // failure - bad data - EditorUtility.DisplayDialog("Registry Location Processing Error", - string.Format("A processing exception was " + - "generated while trying to add {0}." + - "\n\n{1}", regUriStr, e), - "Ok"); - } - } - - while (uninstallRegistry.Count > 0) { - var regUriStr = uninstallRegistry.Pop(); - if (EditorUtility.DisplayDialog("Confirm Delete Registry", - "Are you sure you want to delete the registry?", - "Yes Delete It!", - "Cancel")) { - ResponseCode rc = RegistryManagerController - .RemoveRegistry(new Uri(regUriStr)); - registryDataDirty = true; - if (ResponseCode.REGISTRY_NOT_FOUND == rc) { - EditorUtility.DisplayDialog("Registry Not Found!", - "There was a problem while trying to " + - "remove the registry. It was not " + - "found when we tried to remove it." - , "Ok"); - } - } - } - } - - void OnGUI() { - using (var h = new EditorGUILayout.HorizontalScope()) { - using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPos)) { - scrollPos = scrollView.scrollPosition; - RenderAddRegistryForm(); - RenderListOfRegistries(); - } - } - } - - /// - /// Renders the list of known registries and interactive UI component to remove a - /// registry entry. - /// - void RenderListOfRegistries() { - try { - foreach (var wrappedReg in RegistryManagerController.AllWrappedRegistries) { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField(wrappedReg.Model.GenerateUniqueKey()); - GUI.backgroundColor = Color.red; - if (GUILayout.Button("Remove Registry")) { - uninstallRegistry.Push(wrappedReg.Location.AbsoluteUri); - } - EditorGUILayout.EndHorizontal(); - EditorGUILayout.LabelField(wrappedReg.Location.AbsoluteUri); - } - } catch (Exception e) { - Debug.LogError(e); - } - } - - /// - /// Renders UI with interactive UI components allowing the user to add a - /// registry location Uri. - /// - void RenderAddRegistryForm() { - EditorGUILayout.BeginHorizontal(); - newRegUri = EditorGUILayout.TextField("New Registry Uri:", newRegUri); - GUI.backgroundColor = Color.green; - if (GUILayout.Button("Add Registry")) { - if (newRegUri != null && newRegUri.Length > 12) { // min chars 12 - installRegistry.Push(newRegUri); - newRegUri = ""; - } - } - EditorGUILayout.EndHorizontal(); - } - - /// - /// Adds the Registries menu item in Unity Editor. - /// - [MenuItem("Assets/Play Services Resolver/Package Manager/Registries")] - public static void ShowRegistriesManagerWindow() { - var window = (RegistryManagerView)EditorWindow.GetWindow( - typeof(RegistryManagerView), true, "Package Manager Registries"); - window.Show(); - } - } - - /// - /// Shows the available plugins and allows user to install/remove plugins - /// from project using UI. - /// - public class PluginManagerView : EditorWindow { - Vector2 scrollPos; - List plugins; - bool pluginDataDirty = true; - Stack installPlugins = new Stack(); - HashSet installingPlugins = new HashSet(); - Stack uninstallPlugins = new Stack(); - HashSet uninstallingPlugins = new HashSet(); - Stack moreInfoPlugins = new Stack(); - - /// - /// Called by Unity editor when the Window is created and becomes active. - /// - void OnEnable() { - plugins = PluginManagerController.GetListOfAllPlugins(true); - } - - void OnInspectorGUI() { - EditorUtility.SetDirty(this); - } - - /// - /// Ensures that the plugin details are up to date for rendering UI elements. - /// - void RefreshPluginDataForWindow() { - plugins = PluginManagerController.GetListOfAllPlugins(true); - } - - /// - /// Update override method called by Unity editor. The update cycle looks - /// for any action events that were generated in the OnGUI method by the - /// user and takes action on those events. - /// - void Update() { - if (pluginDataDirty) { - pluginDataDirty = false; - RefreshPluginDataForWindow(); - } - - while (installPlugins.Count > 0) { - var pluginKey = installPlugins.Pop(); - ResponseCode rc = ProjectManagerController.InstallPlugin(pluginKey); - if (ResponseCode.PLUGIN_NOT_INSTALLED == rc) { - EditorUtility.DisplayDialog("Plugin Install Error", - "There was a problem installing the selected plugin.", - "Ok"); - LoggingController.LogError( - string.Format("Could not install plugin with key {0}." + - "Got {1} response code.", pluginKey, rc)); - } else { - pluginDataDirty = true; - } - installingPlugins.Remove(pluginKey); - } - - while (moreInfoPlugins.Count > 0) { - var pluginKey = moreInfoPlugins.Pop(); - var plugin = PluginManagerController.GetPluginForVersionlessKey( - PluginManagerController.VersionedPluginKeyToVersionless(pluginKey)); - // popup with full description - EditorUtility.DisplayDialog( - string.Format("{0}", plugin.MetaData.artifactId), - plugin.Description.languages[0].fullDesc, - "Ok"); - } - - while (uninstallPlugins.Count > 0) { - var pluginKey = uninstallPlugins.Pop(); - ResponseCode rc = ProjectManagerController.UninstallPlugin(pluginKey); - if (ResponseCode.PLUGIN_NOT_REMOVED == rc) { - EditorUtility.DisplayDialog("Plugin Uninstall Error", - "There was a problem removing the selected plugin.", - "Ok"); - LoggingController.LogError( - string.Format("Could not uninstall plugin with key {0}." + - "Got {1} response code.", pluginKey, rc)); - } else { - pluginDataDirty = true; - } - uninstallingPlugins.Remove(pluginKey); - } - } - - void OnGUI() { - using (var h = new EditorGUILayout.HorizontalScope()) { - using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPos)) { - scrollPos = scrollView.scrollPosition; - foreach (var plugin in plugins) { - RenderPluginDetails(plugin); - } - if (GUILayout.Button("Force Refresh")) { - pluginDataDirty = true; - } - } - } - } - - void RenderPluginDetails(PackagedPlugin plugin) { - GUILayout.Space(5); - GUI.backgroundColor = Color.white; - EditorGUILayout.Separator(); - EditorGUILayout.LabelField("Registry: " + plugin.ParentRegistry.GenerateUniqueKey()); - EditorGUILayout.Separator(); - // Name - Version - Short Desc - var v = string.Format("{0} - version: {1} '{2}'", - plugin.MetaData.artifactId, - plugin.MetaData.version, - plugin.Description - .languages[0].shortDesc); - var pluginKey = string.Format("{0}", plugin.MetaData.UniqueKey); - - EditorGUILayout.LabelField(v); - // [More Info] - [Install|Remove|Update] - EditorGUILayout.BeginHorizontal(); - - GUI.backgroundColor = Color.cyan; - if (GUILayout.Button("More Info")) { - moreInfoPlugins.Push(pluginKey); - } - - if (ProjectManagerController.IsPluginInstalledInProject(plugin.MetaData.UniqueKey)) { - // delete or update - GUI.backgroundColor = Color.red; - if (GUILayout.Button("Uninstall")) { - uninstallPlugins.Push(pluginKey); - uninstallingPlugins.Add(pluginKey); - } - } else if (installingPlugins.Contains(pluginKey)) { - GUI.backgroundColor = Color.gray; - if (GUILayout.Button("Installing...")) { - } - } else if (uninstallingPlugins.Contains(pluginKey)) { - GUI.backgroundColor = Color.blue; - if (GUILayout.Button("Un-Installing...")) { - } - } else { - GUI.backgroundColor = Color.green; - if (GUILayout.Button("Install")) { - installPlugins.Push(pluginKey); - installingPlugins.Add(pluginKey); - } - } - - EditorGUILayout.EndHorizontal(); - GUILayout.Space(5); - } - - /// - /// Adds the Plugins menu item in Unity Editor. - /// - [MenuItem("Assets/Play Services Resolver/Package Manager/Plugins")] - public static void ShowPluginManagerWindow() { - var window = (PluginManagerView)GetWindow( - typeof(PluginManagerView), true, "Package Manager Plugins"); - window.Show(); - } - } - - /// - /// Allows the user to view and make changes to the settings - /// associated with the Package Manager module. - /// - public class SettingsManagerView : EditorWindow { - void OnInspectorGUI() { - EditorUtility.SetDirty(this); - } - - void OnGUI() { - EditorGUILayout.LabelField("Package Manager Settings"); - /// - /// Download Cache Path where the downloaded binary data will be stored. - /// - SettingsController.DownloadCachePath = - EditorGUILayout.TextField("Download Cache Path:", - SettingsController.DownloadCachePath); - /// - /// Display the package contents before installing a plugin. - /// - SettingsController.ShowInstallFiles = - EditorGUILayout.ToggleLeft( - "Show plugin package contents before install?", - SettingsController.ShowInstallFiles); - - /// - /// Toggle Verbose Logging. - /// - SettingsController.VerboseLogging = EditorGUILayout.ToggleLeft( - "Enable Verbose Logging", - SettingsController.VerboseLogging); - - } - - /// - /// Actual menu item for showing Settings. - /// - [MenuItem("Assets/Play Services Resolver/Package Manager/Settings")] - public static void ShowSettingsWindow() { - var window = (SettingsManagerView)EditorWindow.GetWindow( - typeof(SettingsManagerView), true, "Package Manager Settings"); - window.Show(); - } - } - - /// - /// A view that implements logic allowing for context menu - /// option to remove a plugin based on a selected asset. - /// - public static class PluginRemovalContextView { - static readonly List selectionLabels = new List(); - - /// - /// Validates the menu context for RemovePlugin based on selected asset. - /// - /// true, if selected asset validated, false otherwise. - [MenuItem("Assets/Remove Associated Plugin", true)] - static bool ValidateRemovePlugin() { - selectionLabels.Clear(); - // is the current selected asset a GPM labeled asset? - bool gpmAssetLabelFound = false; - var assetGuids = Selection.assetGUIDs; - foreach (var aGuid in assetGuids) { - var asset = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(aGuid)); - var labels = AssetDatabase.GetLabels(asset); - foreach (var label in labels) { - if (label.StartsWith(Constants.GPM_LABEL_MARKER)) { - gpmAssetLabelFound = true; - selectionLabels.Add(label); - } - } - } - return gpmAssetLabelFound; - } - - /// - /// Actual menu item that allows user to remove a plugin based on a selected asset. - /// - [MenuItem("Assets/Remove Associated Plugin")] - public static void RemovePlugin() { - var candidateRemovals = new List(selectionLabels); - var window = (PluginCandidateRemovalWindow)EditorWindow.GetWindow( - typeof(PluginCandidateRemovalWindow), true, "Google Package Manager"); - window.candidateRemovals = candidateRemovals; - window.Show(); - } - } - - /// - /// Plugin candidate removal window. Displays the information required for the - /// user to select what plugins they would like to remove after selecting an asset - /// and choosing to remove the associated plugin. - /// - public class PluginCandidateRemovalWindow : EditorWindow { - void OnInspectorGUI() { - EditorUtility.SetDirty(this); - } - - public List candidateRemovals = new List(); - - void OnGUI() { - // TODO: b/34930539 - foreach (var cr in candidateRemovals) { - EditorGUILayout.ToggleLeft(cr, true); - } - } - } -} \ No newline at end of file diff --git a/source/PackageManagerResolver/PackageManagerResolver.csproj b/source/PackageManagerResolver/PackageManagerResolver.csproj new file mode 100644 index 00000000..dc3963f5 --- /dev/null +++ b/source/PackageManagerResolver/PackageManagerResolver.csproj @@ -0,0 +1,82 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {77EBE819-CBE6-4CA8-A791-ED747EA29D30} + Library + Google + Google.PackageManagerResolver + 1.2 + v3.5 + + + True + full + False + bin\Debug + DEBUG;UNITY_EDITOR + prompt + 4 + False + + + True + full + True + bin\Release + DEBUG;UNITY_EDITOR + prompt + 4 + False + + + ..\..\unity_dlls + + + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + + + ..\VersionHandler\bin\Release\Google.VersionHandler.dll + + + ..\VersionHandler\bin\Release\Google.VersionHandlerImpl.dll + + + + + + + + + + + + + + + + + + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + diff --git a/source/PackageManagerResolver/Properties/AssemblyInfo.cs b/source/PackageManagerResolver/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..39f74645 --- /dev/null +++ b/source/PackageManagerResolver/Properties/AssemblyInfo.cs @@ -0,0 +1,58 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. +[assembly: AssemblyTitle("Google.PackageManagerResolver")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Google LLC")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Copyright 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. +[assembly: AssemblyVersion("1.2.0.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +// [assembly: AssemblyDelaySign(false)] +// // +// Copyright (C) 2016 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// [assembly: AssemblyKeyFile("")] + +[assembly: InternalsVisibleTo("Google.PackageMigratorIntegrationTests")] +[assembly: InternalsVisibleTo("Google.PackageManagerResolverTests")] +[assembly: InternalsVisibleTo("Google.PackageManagerClientIntegrationTests")] diff --git a/source/PackageManagerResolver/src/PackageManagerClient.cs b/source/PackageManagerResolver/src/PackageManagerClient.cs new file mode 100644 index 00000000..873288ef --- /dev/null +++ b/source/PackageManagerResolver/src/PackageManagerClient.cs @@ -0,0 +1,1372 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using UnityEditor; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Google { + +/// +/// Provides a safe, simple interface that can be used across any Unity version for interaction +/// with the Unity Package Manager. +/// +internal static class PackageManagerClient { + + /// + /// Wrapper object. + /// + public class Wrapper { + + /// + /// Wrapped instance. + /// + protected object instance; + + /// + /// Construct an null wrapper. + /// + public Wrapper() {} + + /// + /// Construct a wrapper around an object. + /// + /// Object to wrap. + public Wrapper(object instance) { + this.instance = instance; + } + + + /// + /// Convert a collection of objects to a field separated string. + /// + /// Collection of objects to convert to a list of strings. + /// The string to use as a separator. + /// A string that consists of members of delimited + /// by string.. + protected static string ObjectCollectionToString(IEnumerable objects, + string separator) { + var components = new List(); + foreach (var obj in objects) components.Add(obj.ToString()); + return String.Join(separator, components.ToArray()); + } + } + + /// + /// Wraps an enumerable list of objects in a collection of wrappers. + /// + private class CollectionWrapper where T : Wrapper, new() { + + /// + /// Objects wrapped in instances of type T. + /// + private ICollection wrapped = new List(); + + /// + /// Wrap an enumerable set of objects by the wrapper class. + /// + public CollectionWrapper(IEnumerable instances) { + if (instances != null) { + foreach (var instance in instances) { + wrapped.Add(Activator.CreateInstance(typeof(T), + new object[] { instance }) as T); + } + } + } + + /// + /// Get the collection. + /// + public ICollection Collection { get { return wrapped; } } + } + + /// + /// Wrapper for PackageManager.Error. + /// + public class Error : Wrapper { + + /// + /// PackageManager.Error type. + /// + private static Type type; + + /// + /// PackageManager.Error.errorCode property. + /// + private static PropertyInfo errorCodeProperty; + + /// + /// PackageManager.Error.message property. + /// + private static PropertyInfo messageProperty; + + /// + /// Message to use if "instance" is null. + /// + private string fallbackMessage; + + /// + /// Wrap the supplied error object. + /// + /// Error instance to wrap. + public Error(object error) : base(error) { + type = type ?? + VersionHandler.FindClass("UnityEditor", "UnityEditor.PackageManager.Error"); + if (type != null) { + errorCodeProperty = errorCodeProperty ?? type.GetProperty("errorCode"); + messageProperty = messageProperty ?? type.GetProperty("message"); + } + instance = error; + fallbackMessage = instance != null ? "Package Manager Not Supported" : ""; + } + + /// + /// Get the error code as a string. + /// + public string ErrorCodeString { + get { + return instance != null ? + errorCodeProperty.GetValue(instance, null).ToString() : ""; + } + } + + /// + /// Get the error message. + /// + public string Message { + get { + return instance != null ? + messageProperty.GetValue(instance, null) as string : fallbackMessage; + } + } + + /// + /// Convert to a string. + /// + /// String representation. + public override string ToString() { + var components = new List(); + var message = Message; + if (!String.IsNullOrEmpty(message)) components.Add(message); + var errorCodeString = ErrorCodeString; + if (!String.IsNullOrEmpty(errorCodeString)) { + components.Add(String.Format("({0})", errorCodeString)); + } + return String.Join(" ", components.ToArray()); + } + } + + /// + /// Wrapper for PackageManager.AuthorInfo. + /// + public class AuthorInfo : Wrapper { + + /// + /// PackageManager.AuthorInfo class. + /// + private static Type type; + + /// + /// PackageManager.AuthorInfo.email property. + /// + private static PropertyInfo emailProperty; + + /// + /// PackageManager.AuthorInfo.name property. + /// + private static PropertyInfo nameProperty; + + /// + /// PackageManager.AuthorInfo.url property. + /// + private static PropertyInfo urlProperty; + + /// + /// Wrap an AuthorInfo instance. + /// + /// Instance to wrap. + public AuthorInfo(object authorInfo) : base(authorInfo) { + type = type ?? + VersionHandler.FindClass("UnityEditor", "UnityEditor.PackageManager.AuthorInfo"); + if (type != null) { + emailProperty = emailProperty ?? type.GetProperty("email"); + nameProperty = nameProperty ?? type.GetProperty("name"); + urlProperty = urlProperty ?? type.GetProperty("url"); + } + instance = authorInfo; + } + + /// + /// email of a package author. + /// + public string Email { get { return emailProperty.GetValue(instance, null) as string; } } + + /// + /// Name of a package author. + /// + public string Name { get { return nameProperty.GetValue(instance, null) as string; } } + + /// + /// URL of a package author. + /// + public string Url { get { return urlProperty.GetValue(instance, null) as string; } } + + /// + /// Convert to a string. + /// + /// String representation. + public override string ToString() { + var components = new string[] {Name, Url, Email}; + var toStringComponents = new List(); + foreach (var component in components) { + if (!String.IsNullOrEmpty(component)) toStringComponents.Add(component); + } + return String.Join(", ", toStringComponents.ToArray()); + } + } + + /// + /// Wrapper for PackageManager.DependencyInfo. + /// + public class DependencyInfo : Wrapper { + + /// + /// PackageManager.DependencyInfo class. + /// + private static Type type; + + /// + /// PackageManager.DependencyInfo.name property. + /// + private static PropertyInfo nameProperty; + + /// + /// PackageManager.DependencyInfo.version property. + /// + private static PropertyInfo versionProperty; + + /// + /// Empty constructor for use in generics. + /// + public DependencyInfo() {} + + /// + /// Wrap a DependencyInfo instance. + /// + /// Instance to wrap. + public DependencyInfo(object dependencyInfo) : base(dependencyInfo) { + type = type ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.DependencyInfo"); + if (type != null) { + nameProperty = nameProperty ?? type.GetProperty("name"); + versionProperty = versionProperty ?? type.GetProperty("version"); + } + instance = dependencyInfo; + } + + /// + /// Get the name of the dependency. + /// + public string Name { get { return nameProperty.GetValue(instance, null) as string; } } + + /// + /// Get the version of the dependency. + /// + public string Version { get { return versionProperty.GetValue(instance, null) as string; } } + + /// + /// Convert to a string. + /// + /// String representation. + public override string ToString() { + return String.Format("{0}@{1}", Name, Version); + } + } + + /// + /// Wrapper for PackageManager.VersionsInfo. + /// + public class VersionsInfo : Wrapper { + + /// + /// PackageManager.VersionsInfo type. + /// + private static Type type; + + /// + /// PackageManager.VersionsInfo.all property. + /// + private static PropertyInfo allProperty; + + /// + /// PackageManager.VersionsInfo.compatible property. + /// + private static PropertyInfo compatibleProperty; + + /// + /// PackageManager.VersionsInfo.latest property. + /// + private static PropertyInfo latestProperty; + + /// + /// PackageManager.VersionsInfo.latestCompatible property. + /// + private static PropertyInfo latestCompatibleProperty; + + /// + /// PackageManager.VersionsInfo.recommended property. + /// + private static PropertyInfo recommendedProperty; + + /// + /// Empty constructor for use in generics. + /// + public VersionsInfo() {} + + /// + /// Wrap a VersionsInfo instance. + /// + /// Instance to wrap. + public VersionsInfo(object versionsInfo) : base(versionsInfo) { + type = type ?? + VersionHandler.FindClass("UnityEditor", "UnityEditor.PackageManager.VersionsInfo"); + if (type != null) { + allProperty = allProperty ?? type.GetProperty("all"); + compatibleProperty = compatibleProperty ?? type.GetProperty("compatible"); + latestProperty = latestProperty ?? type.GetProperty("latest"); + latestCompatibleProperty = latestCompatibleProperty ?? + type.GetProperty("latestCompatible"); + recommendedProperty = recommendedProperty ?? type.GetProperty("recommended"); + } + instance = versionsInfo; + } + + /// + /// All versions. + /// + public string[] All { get { return allProperty.GetValue(instance, null) as string[]; } } + + /// + /// Compatible versions for the current version of Unity. + /// + public string[] Compatible { + get { + return compatibleProperty.GetValue(instance, null) as string[]; + } + } + + /// + /// Latest version of the package. + /// + public string Latest { get { return latestProperty.GetValue(instance, null) as string; } } + + /// + /// Latest version compatible with the current version of Unity. + /// + public string LatestCompatible { + get { + return latestCompatibleProperty.GetValue(instance, null) as string; + } + } + + /// + /// Recommended version of the package. + /// + public string Recommended { + get { + return recommendedProperty.GetValue(instance, null) as string; + } + } + } + + /// + /// Wrapper for PackageManager.PackageInfo. + /// + public class PackageInfo : Wrapper { + + /// + /// PackageManager.PackageInfo class. + /// + private static Type typeStore; + + /// + /// Get the PackageInfo class. + /// + public static Type Type { + get { + typeStore = typeStore ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.PackageInfo"); + return typeStore; + } + } + + /// + /// PackageManager.PackageInfo.author property. + /// + private static PropertyInfo authorProperty; + + /// + /// PackageManager.PackageInfo.category property. + /// + private static PropertyInfo categoryProperty; + + /// + /// PackageManager.PackageInfo.dependencies property. + /// + private static PropertyInfo dependenciesProperty; + + /// + /// PackageManager.PackageInfo.description property. + /// + private static PropertyInfo descriptionProperty; + + /// + /// PackageManager.PackageInfo.displayName property. + /// + private static PropertyInfo displayNameProperty; + + /// + /// PackageManager.PackageInfo.keywords property. + /// + private static PropertyInfo keywordsProperty; + + /// + /// PackageManager.PackageInfo.name property. + /// + private static PropertyInfo nameProperty; + + /// + /// PackageManager.PackageInfo.packageId property. + /// + private static PropertyInfo packageIdProperty; + + /// + /// PackageManager.PackageInfo.resolvedDependencies property. + /// + private static PropertyInfo resolvedDependenciesProperty; + + /// + /// PackageManager.PackageInfo.version property. + /// + private static PropertyInfo versionProperty; + + /// + /// PackageManager.PackageInfo.versions property. + /// + private static PropertyInfo versionsProperty; + + /// + /// Empty constructor for use in generics. + /// + public PackageInfo() {} + + /// + /// Wrap a packageInfo object. + /// + /// PackageInfo to wrap. + public PackageInfo(object packageInfo) : base(packageInfo) { + var type = Type; + if (type != null) { + authorProperty = authorProperty ?? type.GetProperty("author"); + categoryProperty = categoryProperty ?? type.GetProperty("category"); + dependenciesProperty = dependenciesProperty ?? type.GetProperty("dependencies"); + descriptionProperty = descriptionProperty ?? type.GetProperty("description"); + displayNameProperty = displayNameProperty ?? type.GetProperty("displayName"); + keywordsProperty = keywordsProperty ?? type.GetProperty("keywords"); + nameProperty = nameProperty ?? type.GetProperty("name"); + packageIdProperty = packageIdProperty ?? type.GetProperty("packageId"); + resolvedDependenciesProperty = resolvedDependenciesProperty ?? + type.GetProperty("resolvedDependencies"); + versionProperty = versionProperty ?? type.GetProperty("version"); + versionsProperty = versionsProperty ?? type.GetProperty("versions"); + } + } + + /// + /// Get the package author. + /// + public AuthorInfo Author { + get { + // Author isn't exposed until Unity 2018.3. + var author = authorProperty != null ? authorProperty.GetValue(instance, null) : + null; + return author != null ? new AuthorInfo(author) : null; + } + } + + /// + /// Get the package category. + /// + public string Category { + get { return categoryProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the package dependencies. + /// + public ICollection Dependencies { + get { + // Not available until Unity 2018.3. + return dependenciesProperty != null ? + (new CollectionWrapper( + dependenciesProperty.GetValue(instance, null) as IEnumerable)).Collection : + new DependencyInfo[] {}; + } + } + + /// + /// Get the package description. + /// + public string Description { + get { return descriptionProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the package display name. + /// + public string DisplayName { + get { return displayNameProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the package keywords. + /// + public ICollection Keywords { + get { + // Not available until Unity 2018.3. + var value = keywordsProperty != null ? + keywordsProperty.GetValue(instance, null) as string[] : null; + return value ?? new string[] {}; + } + } + + /// + /// Get the package's unique name. + /// + public string Name { + get { return nameProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the package ID. + /// + public string PackageId { + get { return packageIdProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the resolved direct and indirect dependencies of this package. + /// + public ICollection ResolvedDependencies { + get { + return resolvedDependenciesProperty != null ? + (new CollectionWrapper( + resolvedDependenciesProperty.GetValue(instance, null) + as IEnumerable)).Collection : new DependencyInfo[] {}; + } + } + + /// + /// Get the package version. + /// + public string Version { + get { return versionProperty.GetValue(instance, null) as string; } + } + + /// + /// Get available versions of this package. + /// + public ICollection Versions { + get { + // Not available until Unity 2018. + return versionsProperty != null ? (new CollectionWrapper( + versionsProperty.GetValue(instance, null) as IEnumerable)).Collection : + new VersionsInfo[] {}; + } + } + + + /// + /// Convert to a string. + /// + public override string ToString() { + var components = new List(); + components.Add(String.Format("displayName: '{0}'", DisplayName)); + components.Add(String.Format("name: {0}", Name)); + components.Add(String.Format("packageId: {0}", PackageId)); + components.Add(String.Format("author: '{0}'", Author)); + components.Add(String.Format("version: {0}", Version)); + components.Add(String.Format("availableVersions: [{0}]", + ObjectCollectionToString(Versions, ", "))); + components.Add(String.Format("dependencies: [{0}]", + ObjectCollectionToString(Dependencies, ", "))); + components.Add(String.Format("resolvedDependencies: [{0}]", + ObjectCollectionToString(ResolvedDependencies, ", "))); + components.Add(String.Format("category: '{0}'", Category)); + components.Add(String.Format("keywords: [{0}]", + ObjectCollectionToString(Keywords, ", "))); + return String.Join(", ", components.ToArray()); + } + } + + /// + /// Wrapper for PackageManager.Requests.Request. + /// + private class Request : Wrapper { + + /// + /// PackageManager.Request type. + /// + private static Type type; + + /// + /// PackageManager.Request.Error property. + /// + private static PropertyInfo errorProperty; + + /// + /// PackageManager.Request.IsCompleted property. + /// + private static PropertyInfo isCompletedProperty; + + /// + /// Initialize the wrapper. + /// + /// Request instance to wrap. + public Request(object request) : base(request) { + type = type ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.Requests.Request"); + if (type != null) { + errorProperty = errorProperty ?? type.GetProperty("Error"); + isCompletedProperty = isCompletedProperty ?? type.GetProperty("IsCompleted"); + } + } + + /// + /// Whether the request is complete. + /// + public bool IsComplete { + get { return (bool)isCompletedProperty.GetValue(instance, null); } + } + + /// + /// Error associated with the request (valid when IsComplete is true). + /// + public Error Error { + get { + var error = errorProperty.GetValue(instance, null); + return error != null ? new PackageManagerClient.Error(error) : null; + } + } + } + + /// + /// Request that wraps a class of type UnityEditor.PackageManager.Requests.typeName which + /// returns a collection of objects of type T. + /// + private class CollectionRequest : Request where T : Wrapper, new() { + + /// + /// UnityEditor.PackageManager.Requests.typeName class. + /// + private Type collectionRequestType; + + /// + /// PackageManager.Requests.typeName property. + /// + private PropertyInfo resultProperty; + + /// + /// Create a wrapper around UnityEditor.PackageManager.Requests.typeName. + /// + /// Object to wrap. + /// Name of the type under + /// UnityEditor.PackageManager.Requests to wrap. + public CollectionRequest(object request, string typeName) : base(request) { + collectionRequestType = + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.Requests." + typeName); + if (collectionRequestType != null) { + resultProperty = collectionRequestType.GetProperty("Result"); + } + } + + /// + /// Get the set of packages returned by the request. + /// + public ICollection Result { + get { + return (new CollectionWrapper(resultProperty.GetValue(instance, null) as + IEnumerable).Collection); + } + } + } + + /// + /// Wrapper for UnityEditor.PackageManager.Requests.AddRequest + /// + private class AddRequest : Request { + + /// + /// UnityEditor.PackageManager.Requests.AddRequest class. + /// + private static Type addRequestType; + + /// + /// PackageManager.Requests.AddRequest.Result property. + /// + private static PropertyInfo resultProperty; + + /// + /// Create a wrapper around AddRequest. + /// + /// Object to wrap. + public AddRequest(object request) : base(request) { + addRequestType = addRequestType ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.Requests.AddRequest"); + if (addRequestType != null) { + resultProperty = resultProperty ?? addRequestType.GetProperty("Result"); + } + } + + /// + /// Get the installed package if successful, null otherwise. + /// + public PackageInfo Result { + get { + var result = resultProperty.GetValue(instance, null); + return result != null ? new PackageInfo(result) : null; + } + } + } + + /// + /// Wrapper for UnityEditor.PackageManager.Requests.RemoveRequest + /// + private class RemoveRequest : Request { + + /// + /// UnityEditor.PackageManager.Requests.RemoveRequest class. + /// + private static Type removeRequestType; + + /// + /// PackageManager.Requests.RemoveRequest.Result property. + /// + private static PropertyInfo resultProperty; + + /// + /// Create a wrapper around RemoveRequest. + /// + /// Object to wrap. + public RemoveRequest(object request) : base(request) { + removeRequestType = removeRequestType ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.Requests.RemoveRequest"); + if (removeRequestType != null) { + resultProperty = resultProperty ?? removeRequestType.GetProperty("PackageIdOrName"); + } + } + + /// + /// Get the removed package if successful, null otherwise. + /// + public string Result { + get { + var result = resultProperty.GetValue(instance, null); + return result != null ? result as string : null; + } + } + } + + /// + /// Wrapper for UnityEditor.PackageManager.Requests.ListRequest + /// + private class ListRequest : CollectionRequest { + + /// + /// Create a wrapper around ListRequest. + /// + /// Object to wrap. + public ListRequest(object request) : base(request, "ListRequest") {} + } + + /// + /// Wrapper for UnityEditor.PackageManager.Requests.SearchRequest + /// + private class SearchRequest : CollectionRequest { + + /// + /// Create a wrapper around SearchRequest. + /// + /// Object to wrap. + public SearchRequest(object request) : base(request, "SearchRequest") {} + } + + /// + /// Wrapper for PackageManager.Client. + /// + private static class Client { + /// + /// PackageManager.Client static class. + /// + private static Type type; + + /// + /// Method to add a package. + /// + private static MethodInfo addMethod; + + /// + /// Method to remove a package. + /// + private static MethodInfo removeMethod; + + /// + /// Method to list packages. + /// + private static MethodInfo listMethod; + + /// + /// Method to list packages with an optional offline mode parameter. + /// + private static MethodInfo listMethodOfflineMode; + + /// + /// Method to search for a package. + /// + private static MethodInfo searchMethod; + + /// + /// Method to search for all available packages. + /// + private static MethodInfo searchAllMethod; + + /// + /// Cache the PackageManager.Client class and methods. + /// + static Client() { + type = type ?? + VersionHandler.FindClass("UnityEditor", "UnityEditor.PackageManager.Client"); + if (type != null) { + addMethod = addMethod ?? type.GetMethod("Add", new [] { typeof(String) }); + removeMethod = removeMethod ?? type.GetMethod("Remove", new [] { typeof(String) }); + listMethod = listMethod ?? type.GetMethod("List", Type.EmptyTypes); + listMethodOfflineMode = + listMethodOfflineMode ?? type.GetMethod("List", new [] { typeof(bool) }); + searchMethod = searchMethod ?? type.GetMethod("Search", new [] { typeof(String) }); + searchAllMethod = searchAllMethod ?? type.GetMethod("SearchAll", Type.EmptyTypes); + } + } + + /// + /// Determine Whether the package manager is available. + /// + public static bool Available { + get { + // The current set of methods are checked for as this provides a baseline set + // of functionality for listing, searching and adding / removing packages across + // all versions of Unity since the package manager was introduced. + // listMethodOfflineMode is available in Unity 2018 and above, + // listMethod is available in Unity 2017.x so always utilize the behavior from + // 2017 (i.e no offline queries). + return type != null && addMethod != null && removeMethod != null && + (listMethod != null || listMethodOfflineMode != null) && searchMethod != null; + } + } + + /// + /// Add a package. + /// + /// Name of the package to add. + /// Request object to monitor progress. + public static AddRequest Add(string packageIdOrName) { + return new AddRequest(addMethod.Invoke(null, new object[] { packageIdOrName })); + } + + /// + /// Remove a package. + /// + /// Name of the package to add. + /// Request object to monitor progress. + public static RemoveRequest Remove(string packageIdOrName) { + return new RemoveRequest(removeMethod.Invoke(null, new object[] { packageIdOrName })); + } + + /// + /// List packages available to install. + /// + public static ListRequest List() { + object request = listMethodOfflineMode != null ? + listMethodOfflineMode.Invoke(null, new object[] { false }) : + listMethod.Invoke(null, null); + return new ListRequest(request); + } + + /// + /// Search for an available package. + /// + /// Name of the package to add. + /// Request object to monitor progress. + public static SearchRequest Search(string packageIdOrName) { + return new SearchRequest(searchMethod.Invoke(null, new object[] { packageIdOrName })); + } + + /// + /// Search for all available packages. + /// + /// Request object to monitor progress or null if SearchAll() isn't + /// available. + public static SearchRequest SearchAll() { + return searchAllMethod != null ? + new SearchRequest(searchAllMethod.Invoke(null, new object[] {})) : null; + } + } + + /// + /// Enumerates through a set of items, optionally reporting progress. + /// + private class EnumeratorProgressReporter { + /// + /// Enumerator of packages to search for. + /// + private IEnumerator enumerator; + + /// + /// Called as the search progresses. + /// + private Action progress = null; + + /// + /// Number of items to search. + /// + private int itemCount; + + /// + /// Number of items searched. + /// + private int itemIndex = 0; + + /// + /// Construct the instance. + /// + /// Items to iterate through. + /// Reports progress as iteration proceeds. + public EnumeratorProgressReporter(ICollection items, + Action progressReporter) { + itemCount = items.Count; + enumerator = items.GetEnumerator(); + progress = progressReporter; + } + + /// + /// Report progress. + /// + /// Progress through the operation. + /// Item being worked on. + protected void ReportProgress(float itemProgress, string item) { + if (progress != null) { + try { + progress(itemProgress, item); + } catch (Exception e) { + PackageManagerClient.Logger.Log( + String.Format("Progress reporter raised exception {0}", e), + level: LogLevel.Error); + } + } + } + + /// + /// Get the next item. + /// + protected string NextItem() { + if (!enumerator.MoveNext()) { + ReportProgress(1.0f, ""); + return null; + } + var item = enumerator.Current; + ReportProgress((float)itemIndex / (float)itemCount, item); + itemIndex++; + return item; + } + } + + /// + /// Result of a package remove operation. + /// + public class RemoveResult { + + /// + /// Package ID involved in the remove operation. + /// + public string PackageId { get; set; } + + /// + /// Error. + /// + public Error Error { get; set; } + + /// + /// Construct an empty result. + /// + public RemoveResult() { + this.PackageId = ""; + this.Error = new Error(null); + } + } + + /// + /// Result of a package add / install operation. + /// + public class InstallResult { + + /// + /// Information about the installed package, null if no package is installed. + /// + public PackageInfo Package { get; set; } + + /// + /// Error. + /// + public Error Error { get; set; } + + /// + /// Construct an empty result. + /// + public InstallResult() { + this.Package = null; + this.Error = new Error(null); + } + } + + /// + /// Adds a set of packages to the project. + /// + private class PackageInstaller : EnumeratorProgressReporter { + + /// + /// Pending add request. + /// + private AddRequest request = null; + + /// + /// Result of package installation. + /// + private Dictionary installed = + new Dictionary(); + + /// + /// Called when the operation is complete. + /// + Action> complete; + + /// + /// Install a set of packages using the package manager. + /// + /// Package IDs or names to search for. + /// Called when the operation is complete. + /// Reports progress through the search. + public PackageInstaller(ICollection packageIdsOrNames, + Action> complete, + Action progress = null) : + base(packageIdsOrNames, progress) { + this.complete = complete; + InstallNext(); + } + + /// + /// Install the next package. + /// + private void InstallNext() { + var packageIdOrName = NextItem(); + if (String.IsNullOrEmpty(packageIdOrName)) { + var completion = complete; + complete = null; + completion(installed); + return; + } + request = Client.Add(packageIdOrName); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!request.IsComplete) return false; + if (complete == null) return true; + installed[packageIdOrName] = new InstallResult() { + Package = request.Result, + Error = request.Error ?? new Error(null) + }; + RunOnMainThread.Run(() => { InstallNext(); }); + return true; + }); + } + } + + /// + /// Result of a search operation. + /// + public class SearchResult { + + /// + /// Packages found. + /// + public ICollection Packages { get; set; } + + /// + /// Error. + /// + public Error Error { get; set; } + + /// + /// Construct an empty result. + /// + public SearchResult() { + Packages = new List(); + Error = new Error(null); + } + } + + /// + /// Searches for packages by name. + /// + private class PackageSearcher : EnumeratorProgressReporter { + + /// + /// Pending search request. + /// + private SearchRequest request = null; + + /// + /// Packages found by the search. + /// + private Dictionary found = new Dictionary(); + + /// + /// Called when the operation is complete. + /// + private Action> complete; + + /// + /// Search for a set of packages in the package manager. + /// + /// Package IDs or names to search for. + /// Called when the operation is complete. + /// Reports progress through the search. + public PackageSearcher(ICollection packageIdsOrNames, + Action> complete, + Action progress = null) : + base(packageIdsOrNames, progress){ + this.complete = complete; + SearchNext(); + } + + /// + /// Perform the next search operation. + /// + private void SearchNext() { + var packageIdOrName = NextItem(); + if (String.IsNullOrEmpty(packageIdOrName)) { + var completion = complete; + complete = null; + completion(found); + return; + } + + request = Client.Search(packageIdOrName); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!request.IsComplete) return false; + if (complete == null) return true; + var packages = new List(); + var result = request.Result; + if (request.Result != null) packages.AddRange(result); + found[packageIdOrName] = new SearchResult() { + Packages = packages, + Error = request.Error ?? new Error(null) + }; + RunOnMainThread.Run(() => { SearchNext(); }); + return true; + }); + } + } + + /// + /// Logger for this class. + /// + public static Logger Logger = PackageManagerResolver.logger; + + /// + /// Job queue for package managers jobs. + /// + /// + /// PackageManager.Client operations are not thread-safe, each operation needs to be + /// executed sequentially so this class simplifies the process of scheduling operations on + /// the main thread. + /// + private static RunOnMainThread.JobQueue jobQueue = new RunOnMainThread.JobQueue(); + + /// + /// Determine Whether the package manager is available. + /// + public static bool Available { get { return Client.Available; } } + + /// + /// Add a package to the project. + /// + /// ID or name of the package to add. + /// Called when the operation is complete. + public static void AddPackage(string packageIdOrName, Action complete) { + if (!Available) { + complete(new InstallResult()); + return; + } + jobQueue.Schedule(() => { + new PackageInstaller(new [] { packageIdOrName }, + (result) => { + jobQueue.Complete(); + complete(result[packageIdOrName]); + }, + progress: null); + }); + } + + /// + /// Add packages to the project. + /// + /// IDs or names of the packages to add. + /// Called when the operation is complete. + /// Reports progress through the installation. + public static void AddPackages(ICollection packageIdsOrNames, + Action> complete, + Action progress = null) { + if (!Client.Available) { + if (progress != null) progress(1.0f, ""); + complete(new Dictionary()); + return; + } + jobQueue.Schedule(() => { + new PackageInstaller(packageIdsOrNames, + (result) => { + jobQueue.Complete(); + complete(result); + }, + progress: progress); + }); + } + + /// + /// Remove a package from the project. + /// + /// ID or name of the package to add. + /// Called when the operation is complete. + public static void RemovePackage(string packageIdOrName, Action complete) { + if (!Available) { + complete(new RemoveResult()); + return; + } + jobQueue.Schedule(() => { + var result = Client.Remove(packageIdOrName); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!result.IsComplete) return false; + jobQueue.Complete(); + complete(new RemoveResult() { + PackageId = result.Result ?? packageIdOrName, + Error = result.Error ?? new Error(null) + }); + return true; + }); + }); + } + + /// + /// List all packages that the project current depends upon. + /// + /// Action that is called with the list of packages in the + /// project. + public static void ListInstalledPackages(Action complete) { + if (!Available) { + complete(new SearchResult()); + return; + } + jobQueue.Schedule(() => { + var request = Client.List(); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!request.IsComplete) return false; + jobQueue.Complete(); + complete(new SearchResult() { + Packages = request.Result, + Error = request.Error ?? new Error(null) + }); + return true; + }); + }); + } + + /// + /// Search for all packages available for installation in the package manager. + /// + /// Action that is called with the list of packages available for + /// installation. + public static void SearchAvailablePackages(Action complete) { + jobQueue.Schedule(() => { + var request = Client.SearchAll(); + if (request == null) { + jobQueue.Complete(); + complete(new SearchResult()); + return; + } + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!request.IsComplete) return false; + jobQueue.Complete(); + complete(new SearchResult() { + Packages = request.Result, + Error = request.Error ?? new Error(null) + }); + return true; + }); + }); + } + + /// + /// Search for a set of packages in the package manager. + /// + /// Packages to search for. + /// Action that is called with a collection of available packages that + /// is a result of each search string. + /// Reports progress through the search. + public static void SearchAvailablePackages( + ICollection packageIdsOrNames, + Action> complete, + Action progress = null) { + if (!Available) { + if (progress != null) progress(1.0f, ""); + complete(new Dictionary()); + return; + } + jobQueue.Schedule(() => { + new PackageSearcher(packageIdsOrNames, + (result) => { + jobQueue.Complete(); + complete(result); + }, + progress: progress); + }); + } +} + +} diff --git a/source/PackageManagerResolver/src/PackageManagerRegistry.cs b/source/PackageManagerResolver/src/PackageManagerRegistry.cs new file mode 100644 index 00000000..d62db6e0 --- /dev/null +++ b/source/PackageManagerResolver/src/PackageManagerRegistry.cs @@ -0,0 +1,152 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google { + using System; + using System.Collections.Generic; + + /// + /// UPM/NPM package registry. + /// + internal class PackageManagerRegistry { + + /// + /// Construct an empty registry. + /// + public PackageManagerRegistry() { + Scopes = new List(); + } + + /// + /// Registry name. + /// + public string Name; + + /// + /// Registry URL. + /// + public string Url; + + /// + /// Scopes for this registry. + /// See https://docs.unity3d.com/Manual/upm-scoped.html and + /// https://docs.npmjs.com/using-npm/scope.html + /// + public List Scopes; + + /// + /// Terms of service for the registry. + /// + public string TermsOfService; + + /// + /// Privacy policy for the registry. + /// + public string PrivacyPolicy; + + /// + /// Tag that indicates where this was created. + /// + /// + /// This can be used to display which file this was read from or where in code this + /// registry was created. + /// + public string CreatedBy = System.Environment.StackTrace; + + /// + /// Arbitrary custom runtime data. + /// + /// + /// For example, this can be used associate this instance with the Dictionary representation + /// parsed from a JSON file. Storing the parsed JSON Dictionary it's possible to easily + /// remove object from the JSON document without requiring a separate data structure + /// (e.g a map) to associate the two data representations. + /// + public object CustomData; + + /// + /// Convert to a human readable string excluding the TermsOfService and CreatedBy fields. + /// + /// String representation of this instance. + public override string ToString() { + return String.Format("name: {0}, url: {1}, scopes: {2}", + Name, Url, + Scopes != null ? + String.Format("[{0}]", String.Join(", ", Scopes.ToArray())) : + "[]"); + } + + /// + /// Compare with this object. + /// + /// Object to compare with. + /// true if both objects have the same contents excluding CreatedBy, + /// false otherwise. + public override bool Equals(System.Object obj) { + var other = obj as PackageManagerRegistry; + return other != null && + Name == other.Name && + Url == other.Url && + TermsOfService == other.TermsOfService && + PrivacyPolicy == other.PrivacyPolicy && + Scopes != null && other.Scopes != null && + (new HashSet(Scopes)).SetEquals(other.Scopes) && + CustomData == other.CustomData; + } + + /// + /// Generate a hash of this object excluding CreatedBy. + /// + /// Hash of this object. + public override int GetHashCode() { + int hash = 0; + if (!String.IsNullOrEmpty(Name)) hash ^= Name.GetHashCode(); + if (!String.IsNullOrEmpty(Url)) hash ^= Url.GetHashCode(); + if (!String.IsNullOrEmpty(TermsOfService)) hash ^= TermsOfService.GetHashCode(); + if (!String.IsNullOrEmpty(PrivacyPolicy)) hash ^= PrivacyPolicy.GetHashCode(); + if (Scopes != null) { + foreach (var scope in Scopes) { + hash ^= scope.GetHashCode(); + } + } + if (CustomData != null) hash ^= CustomData.GetHashCode(); + return hash; + } + + /// + /// Convert a list of PackageManagerRegistry instances to a list of strings. + /// + /// List of registries to convert to strings. + /// List of strings. + public static List ToStringList( + IEnumerable registries) { + var registryStrings = new List(); + foreach (var registry in registries) { + registryStrings.Add(registry.ToString()); + } + return registryStrings; + } + + /// + /// Convert a list of PackageManagerRegistry instance to a newline separated string. + /// + /// List of registries to convert to strings. + /// String representation of the list. + public static string ToString(IEnumerable registries) { + return String.Join("\n", ToStringList(registries).ToArray()); + } + } +} diff --git a/source/PackageManagerResolver/src/PackageManagerResolver.cs b/source/PackageManagerResolver/src/PackageManagerResolver.cs new file mode 100644 index 00000000..d041cc26 --- /dev/null +++ b/source/PackageManagerResolver/src/PackageManagerResolver.cs @@ -0,0 +1,665 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using UnityEngine; +using UnityEditor; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Google { + +[InitializeOnLoad] +public class PackageManagerResolver : AssetPostprocessor { + /// + /// A unique class to create the multi-select window to add registries. + /// + class PackageManagerResolverWindow : MultiSelectWindow {} + + /// + /// Name of the plugin. + /// + internal const string PLUGIN_NAME = "Package Manager Resolver"; + + /// + /// The operation to perform when modifying the manifest. + /// + internal enum ManifestModificationMode { + /// + /// Add package registries that are not in the manifest. + /// + Add, + + /// + /// Remove package registries from the manifest that are in the loaded config. + /// + Remove, + + /// + /// Display and add / remove all package registries from the manifest. + /// + Modify, + } + + private const string ADD_REGISTRIES_QUESTION = + "Add the selected Package Manager registries to your project?"; + private const string REMOVE_REGISTRIES_QUESTION = + "Remove the selected Package Manager registries from your project?"; + private const string ADD_REGISTRIES_DESCRIPTION = + "Adding a registry will allow you to install, upgrade and remove packages from the " + + "registry's server in the Package Manager. By adding the selected registries, you " + + "agree that your use of these registries are subject to their terms of service and you " + + "acknowledge that data will be collected in accordance with each registry's privacy " + + "policy."; + private const string REMOVE_REGISTRIES_DESCRIPTION = + "Removing a registry will prevent you from installing and upgrading packages from the " + + "registry's server in the Package Manager. It will not remove packages from the " + + "registry's server that are already installed"; + private const string ADD_OR_REMOVE_REGISTRIES_QUESTION = + "Add the selected Package Manager registries to and remove the " + + "unselected registries from your project?"; + private const string MODIFY_MENU_ITEM_DESCRIPTION = + "You can always add or remove registries at a later time using menu item:\n" + + "'Assets > External Dependency Manager > Package Manager Resolver > " + + "Modify Registries'."; + + /// + /// Scroll location of the manifest view on the left in the registry selection window. + /// + private static Vector2 scrollManifestViewLeft; + + /// + /// Scroll location of the manifest view on the right in the registry selection window. + /// + private static Vector2 scrollManifestViewRight; + + /// + /// Enables / disables external package registries for Package Manager. + /// + static PackageManagerResolver() { + logger.Log("Loaded PackageManagerResolver", level: LogLevel.Verbose); + + RunOnMainThread.Run(() => { + // Load log preferences. + UpdateLoggerLevel(VerboseLoggingEnabled); + }, runNow: false); + } + + /// + /// Display documentation. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Documentation")] + public static void ShowDocumentation() { + analytics.OpenUrl(VersionHandlerImpl.DocumentationUrl( + "#package-manager-resolver-usage"), "Usage"); + } + + /// + /// Add the settings dialog for this module to the menu and show the + /// window when the menu item is selected. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Settings")] + public static void ShowSettings() { + PackageManagerResolverSettingsDialog window = + (PackageManagerResolverSettingsDialog)EditorWindow.GetWindow( + typeof(PackageManagerResolverSettingsDialog), true, PLUGIN_NAME + " Settings"); + window.Initialize(); + window.Show(); + } + + /// + /// Check registry status based on current settings. + /// + internal static void CheckRegistries() { + if (Enable) { + UpdateManifest(ManifestModificationMode.Add, + promptBeforeAction: PromptToAddRegistries, + showDisableButton: true); + } + } + + + /// + /// Called by Unity when all assets have been updated and checks to see whether any registries + /// have changed. + /// + /// Imported assets. + /// Deleted assets. + /// Moved assets. + /// Moved from asset paths. (unused) + private static void OnPostprocessAllAssets(string[] importedAssets, + string[] deletedAssets, + string[] movedAssets, + string[] movedFromAssetPaths) { + if (!Enable) return; + bool registriesChanged = false; + var checkAssets = new List(importedAssets); + checkAssets.AddRange(movedAssets); + foreach (var asset in checkAssets) { + if (XmlPackageManagerRegistries.IsRegistriesFile(asset)) { + registriesChanged = true; + break; + } + AssetImporter importer = AssetImporter.GetAtPath(asset); + if (importer != null) { + foreach (var assetLabel in AssetDatabase.GetLabels(importer)) { + if (assetLabel == XmlPackageManagerRegistries.REGISTRIES_LABEL) { + registriesChanged = true; + break; + } + } + } + } + if (registriesChanged) CheckRegistries(); + } + + /// + /// Add registries in the XML configuration to the project manifest. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Add Registries")] + public static void AddRegistries() { + UpdateManifest(ManifestModificationMode.Add, promptBeforeAction: true, + showDisableButton: false); + } + + /// + /// Remove registries in the XML configuration from the project manifest. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Remove Registries")] + public static void RemoveRegistries() { + UpdateManifest(ManifestModificationMode.Remove, promptBeforeAction: true, + showDisableButton: false); + } + + /// + /// Add or remove registries in the project manifest based upon the set available in the XML + /// configuration. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Modify Registries")] + public static void ModifyRegistries() { + UpdateManifest(ManifestModificationMode.Modify, promptBeforeAction: true, + showDisableButton: false); + } + + /// + /// Read registries from XML configuration files. + /// + /// Dictionary of registries indexed by URL. + private static Dictionary ReadRegistriesFromXml() { + // Read registries from XML files. + var xmlReader = new XmlPackageManagerRegistries(); + xmlReader.ReadAll(logger); + return xmlReader.Registries; + } + + /// + /// Apply registry changes to the modifier. + /// + /// Object that modifies the project's manifest. + /// Registries added to the modifier. + /// Registries removed from the modifier. + /// Registries that are available in the + /// configuration. + /// Registries that are present in the manifest. + /// URLs of selected registries, these should be items in + /// availableRegistries. + /// Whether to add selected registries to the manifest. + /// Whether to remove unselected registries from the + /// manifest. + /// If false, adds the selected registries and removes the + /// unselected registries. If true, removes the selected registries and adds the unselected + /// registries. + /// true if the manifest is modified. + private static bool SyncRegistriesToModifier( + PackageManifestModifier manifestModifier, + out List registriesToAdd, + out List registriesToRemove, + Dictionary availableRegistries, + Dictionary> manifestRegistries, + HashSet selectedRegistryUrls, + bool addRegistries = true, + bool removeRegistries = true, + bool invertSelection = false) { + // Build a list of registries to add to and remove from the modifier. + registriesToAdd = new List(); + registriesToRemove = new List(); + + foreach (var availableRegistry in availableRegistries.Values) { + var url = availableRegistry.Url; + bool isSelected = selectedRegistryUrls.Contains(url); + if (invertSelection) isSelected = !isSelected; + + bool currentlyInManifest = manifestRegistries.ContainsKey(url); + + if (isSelected) { + if (addRegistries && !currentlyInManifest) { + registriesToAdd.Add(availableRegistry); + } + } else { + if (removeRegistries && currentlyInManifest) { + registriesToRemove.Add(availableRegistry); + } + } + } + + bool manifestModified = false; + if (registriesToAdd.Count > 0) { + manifestModifier.AddRegistries(registriesToAdd); + manifestModified = true; + } + if (registriesToRemove.Count > 0) { + manifestModifier.RemoveRegistries(registriesToRemove); + manifestModified = true; + } + return manifestModified; + } + + /// + /// Apply registry changes to the projects manifest. + /// + /// Object that modifies the project's manifest. + /// Registries that are available in the + /// configuration. + /// Registries that are present in the manifest. + /// URLs of selected registries, these should be items in + /// availableRegistries. + /// Whether to add selected registries to the manifest. + /// Whether to remove unselected registries from the + /// manifest. + /// If false, adds the selected registries and removes the + /// unselected registries. If true, removes the selected registries and adds the unselected + /// registries. + /// If specified, is extended with the list of registries added + /// to the manifest. + /// true if successful, false otherwise. + private static bool SyncRegistriesToManifest( + PackageManifestModifier manifestModifier, + Dictionary availableRegistries, + Dictionary> manifestRegistries, + HashSet selectedRegistryUrls, + bool addRegistries = true, + bool removeRegistries = true, + bool invertSelection = false, + List addedRegistries = null) { + List registriesToAdd; + List registriesToRemove; + bool manifestModified = SyncRegistriesToModifier( + manifestModifier, out registriesToAdd, out registriesToRemove, + availableRegistries, manifestRegistries, selectedRegistryUrls, + addRegistries, removeRegistries, invertSelection); + + bool successful = true; + if (manifestModified) { + successful = manifestModifier.WriteManifest(); + if (successful) { + if (registriesToAdd.Count > 0) { + logger.Log(String.Format( + "Added registries to {0}:\n{1}", + PackageManifestModifier.MANIFEST_FILE_PATH, + PackageManagerRegistry.ToString(registriesToAdd))); + if (addedRegistries != null) addedRegistries.AddRange(registriesToAdd); + } + if (registriesToRemove.Count > 0) { + logger.Log(String.Format( + "Removed registries from {0}:\n{1}", + PackageManifestModifier.MANIFEST_FILE_PATH, + PackageManagerRegistry.ToString(registriesToRemove))); + } + analytics.Report( + "registry_manifest/write/success", + new KeyValuePair[] { + new KeyValuePair("added", registriesToAdd.Count.ToString()), + new KeyValuePair("removed", + registriesToRemove.Count.ToString()) + }, + "Project Manifest Modified"); + } else { + analytics.Report("registry_manifest/write/failed", "Project Manifest Write Failed"); + } + } + return successful; + } + + /// + /// Update manifest file based on the settings. + /// + /// Manifest modification mode being applied. + /// Whether to display a window that prompts the user for + /// confirmation before applying changes. + /// Whether to show a button to disable auto-registry + /// addition. + /// List of scope prefixes used to filter the set of registries + /// being operated on. + internal static void UpdateManifest(ManifestModificationMode mode, + bool promptBeforeAction = true, + bool showDisableButton = false, + IEnumerable scopePrefixFilter = null) { + if (!ScopedRegistriesSupported) { + logger.Log(String.Format("Scoped registries not supported in this version of Unity."), + level: LogLevel.Verbose); + return; + } + + PackageManifestModifier modifier = new PackageManifestModifier() { Logger = logger }; + Dictionary> manifestRegistries = + modifier.ReadManifest() ? modifier.PackageManagerRegistries : null; + if (manifestRegistries == null) { + PackageManagerResolver.analytics.Report( + "registry_manifest/read/failed", + "Update Manifest failed: Read/Parse manifest failed"); + return; + } + + var xmlRegistries = ReadRegistriesFromXml(); + + if (xmlRegistries.Count == 0) { + logger.Log("No registry found from any Registries.xml files", level: LogLevel.Warning); + } + + // Filter registries using the scope prefixes. + if (scopePrefixFilter != null) { + foreach (var registry in new List(xmlRegistries.Values)) { + bool removeRegistry = true; + foreach (var scope in registry.Scopes) { + foreach (var scopePrefix in scopePrefixFilter) { + if (scope.StartsWith(scopePrefix)) { + removeRegistry = false; + } + } + } + if (removeRegistry) xmlRegistries.Remove(registry.Url); + } + } + + // Filter the set of considered registries based upon the modification mode. + HashSet selectedRegistryUrls = null; + switch (mode) { + case ManifestModificationMode.Add: + // Remove all items from the XML loaded registries that are present in the manifest. + foreach (var url in manifestRegistries.Keys) xmlRegistries.Remove(url); + selectedRegistryUrls = new HashSet(xmlRegistries.Keys); + break; + case ManifestModificationMode.Remove: + // Remove all items from the XML loaded registries that are not present in the + // manifest. + foreach (var url in new List(xmlRegistries.Keys)) { + if (!manifestRegistries.ContainsKey(url)) { + xmlRegistries.Remove(url); + } + } + selectedRegistryUrls = new HashSet(xmlRegistries.Keys); + break; + case ManifestModificationMode.Modify: + selectedRegistryUrls = new HashSet(); + // Keep all XML loaded registries and select the items in the manifest. + foreach (var url in xmlRegistries.Keys) { + if (manifestRegistries.ContainsKey(url)) { + selectedRegistryUrls.Add(url); + } + } + break; + } + + // Applies the manifest modification based upon the modification mode. + Action> syncRegistriesToManifest = (urlSelectionToApply) => { + var addedRegistries = new List(); + SyncRegistriesToManifest(modifier, xmlRegistries, manifestRegistries, + urlSelectionToApply, + addRegistries: (mode == ManifestModificationMode.Add || + mode == ManifestModificationMode.Modify), + removeRegistries: (mode == ManifestModificationMode.Remove || + mode == ManifestModificationMode.Modify), + invertSelection: mode == ManifestModificationMode.Remove, + addedRegistries: addedRegistries); + // If any registries were added try migration if enabled. + if (addedRegistries.Count > 0 && PromptToMigratePackages) { + PackageMigrator.MigratePackages(); + } + }; + + // Get the manifest json string based on the current selection and mode. + Func, string> getManifestJsonAfterChange = (urlSelectionToApply) => { + PackageManifestModifier clonedModifier = new PackageManifestModifier(modifier); + List toAdd; + List toRemove; + SyncRegistriesToModifier(clonedModifier, out toAdd, out toRemove, + xmlRegistries, manifestRegistries, + urlSelectionToApply, + addRegistries: (mode == ManifestModificationMode.Add || + mode == ManifestModificationMode.Modify), + removeRegistries: (mode == ManifestModificationMode.Remove || + mode == ManifestModificationMode.Modify), + invertSelection: mode == ManifestModificationMode.Remove); + return clonedModifier.GetManifestJson(); + }; + + if (xmlRegistries.Count > 0) { + if (promptBeforeAction) { + // Build a list of items to display. + var registryItems = new List>(); + foreach (var kv in xmlRegistries) { + registryItems.Add(new KeyValuePair(kv.Key, kv.Value.Name)); + } + + // Optional when prompting is enabled or forced. + var window = + MultiSelectWindow.CreateMultiSelectWindow( + PLUGIN_NAME); + window.minSize = new Vector2(1024, 500); + window.AvailableItems = registryItems; + window.Sort(1); + window.SelectedItems = selectedRegistryUrls; + switch (mode) { + case ManifestModificationMode.Add: + window.Caption = String.Format("{0}\n\n{1}\n\n{2}", + ADD_REGISTRIES_QUESTION, + ADD_REGISTRIES_DESCRIPTION, + MODIFY_MENU_ITEM_DESCRIPTION); + window.ApplyLabel = "Add Selected Registries"; + break; + case ManifestModificationMode.Remove: + window.Caption = String.Format("{0}\n\n{1}{2}", + REMOVE_REGISTRIES_QUESTION, + REMOVE_REGISTRIES_DESCRIPTION, + MODIFY_MENU_ITEM_DESCRIPTION); + window.ApplyLabel = "Remove Selected Registries"; + break; + case ManifestModificationMode.Modify: + window.Caption = String.Format("{0}\n\n{1} {2}", + ADD_OR_REMOVE_REGISTRIES_QUESTION, + ADD_REGISTRIES_DESCRIPTION, + REMOVE_REGISTRIES_DESCRIPTION); + window.ApplyLabel = "Modify Registries"; + break; + } + window.RenderItem = (item) => { + var registry = xmlRegistries[item.Key]; + var termsOfService = registry.TermsOfService; + if (!String.IsNullOrEmpty(termsOfService)) { + if (GUILayout.Button("View Terms of Service")) { + Application.OpenURL(termsOfService); + } + } + var privacyPolicy = registry.PrivacyPolicy; + if (!String.IsNullOrEmpty(privacyPolicy)) { + if (GUILayout.Button("View Privacy Policy")) { + Application.OpenURL(privacyPolicy); + } + } + }; + // Set the scroll position to the bottom since "scopedRegistry" section is most + // likely at the bottom of the file. + scrollManifestViewLeft = new Vector2(0.0f, float.PositiveInfinity); + scrollManifestViewRight = new Vector2(0.0f, float.PositiveInfinity); + + // Render the change in manifest.json dynamically. + window.RenderAfterItems = () => { + GUILayout.Label("Changes to Packages/manifest.json"); + EditorGUILayout.Space(); + + EditorGUILayout.BeginHorizontal(); + + EditorGUILayout.BeginVertical(); + GUILayout.Label("Before", EditorStyles.boldLabel); + EditorGUILayout.Space(); + scrollManifestViewLeft = + EditorGUILayout.BeginScrollView(scrollManifestViewLeft, + GUILayout.MaxWidth(window.position.width / 2.0f)); + EditorGUILayout.TextArea(modifier.GetManifestJson()); + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(); + + EditorGUILayout.BeginVertical(); + GUILayout.Label("After", EditorStyles.boldLabel); + EditorGUILayout.Space(); + scrollManifestViewRight = + EditorGUILayout.BeginScrollView(scrollManifestViewRight, + GUILayout.MaxWidth(window.position.width / 2.0f)); + EditorGUILayout.TextArea(getManifestJsonAfterChange(window.SelectedItems)); + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.EndHorizontal(); + }; + if (showDisableButton) { + window.RenderBeforeCancelApply = () => { + if (GUILayout.Button("Disable Registry Addition")) { + Enable = false; + window.Close(); + } + }; + } + window.OnApply = () => { syncRegistriesToManifest(window.SelectedItems); }; + window.Show(); + } else { + syncRegistriesToManifest(selectedRegistryUrls); + } + } + } + + /// + /// Reset settings of this plugin to default values. + /// + internal static void RestoreDefaultSettings() { + settings.DeleteKeys(PreferenceKeys); + analytics.RestoreDefaultSettings(); + } + + // Keys in the editor preferences which control the behavior of this + // module. + private const string PreferenceEnable = + "Google.PackageManagerResolver.Enable"; + private const string PreferencePromptToAddRegistries = + "Google.PackageManagerResolver.PromptToAddRegistries"; + private const string PreferencePromptToMigratePackages = + "Google.PackageManagerResolver.PromptToMigratePackages"; + private const string PreferenceVerboseLoggingEnabled = + "Google.PackageManagerResolver.VerboseLoggingEnabled"; + // List of preference keys, used to restore default settings. + private static string[] PreferenceKeys = new[] { + PreferenceEnable, + PreferencePromptToAddRegistries, + PreferencePromptToMigratePackages, + PreferenceVerboseLoggingEnabled + }; + + // Unity started supporting scoped registries from 2018.4. + internal const float MinimumUnityVersionFloat = 2018.4f; + internal const string MinimumUnityVersionString = "2018.4"; + + // Settings used by this module. + internal static ProjectSettings settings = + new ProjectSettings("Google.PackageManagerResolver."); + + /// + /// Logger for this module. + /// + internal static Logger logger = new Logger(); + + // Analytics reporter. + internal static EditorMeasurement analytics = + new EditorMeasurement(settings, logger, VersionHandlerImpl.GA_TRACKING_ID, + VersionHandlerImpl.MEASUREMENT_ID, + VersionHandlerImpl.PLUGIN_SUITE_NAME, "", + VersionHandlerImpl.PRIVACY_POLICY) { + BasePath = "/upmresolver/", + BaseQuery = + String.Format("version={0}", PackageManagerResolverVersionNumber.Value.ToString()), + BaseReportName = "Package Manager Resolver: ", + InstallSourceFilename = Assembly.GetAssembly(typeof(PackageManagerResolver)).Location, + DataUsageUrl = VersionHandlerImpl.DATA_USAGE_URL + }; + + /// + /// Whether to use project level settings. + /// + public static bool UseProjectSettings { + get { return settings.UseProjectSettings; } + set { settings.UseProjectSettings = value; } + } + + /// + /// Enable / disable management of external registries. + /// + public static bool Enable { + get { return settings.GetBool(PreferenceEnable, defaultValue: true); } + set { settings.SetBool(PreferenceEnable, value); } + } + + /// + /// Enable / disable prompting the user to add registries. + /// + public static bool PromptToAddRegistries { + get { return settings.GetBool(PreferencePromptToAddRegistries, defaultValue: true); } + set { settings.SetBool(PreferencePromptToAddRegistries, value); } + } + + /// + /// Enable / disable prompting the user to migrate Version Handler to UPM packages after a + /// registry has been added. + /// + public static bool PromptToMigratePackages { + get { return settings.GetBool(PreferencePromptToMigratePackages, defaultValue: true); } + set { settings.SetBool(PreferencePromptToMigratePackages, value); } + } + + /// + /// Enable / disable verbose logging. + /// + public static bool VerboseLoggingEnabled { + get { return settings.GetBool(PreferenceVerboseLoggingEnabled, defaultValue: false); } + set { + settings.SetBool(PreferenceVerboseLoggingEnabled, value); + UpdateLoggerLevel(value); + } + } + + private static void UpdateLoggerLevel(bool verboseLoggingEnabled) { + logger.Level = verboseLoggingEnabled ? LogLevel.Verbose : LogLevel.Info; + } + + /// + /// Whether scoped registries are supported in current Unity editor. + /// + public static bool ScopedRegistriesSupported { + get { + return VersionHandler.GetUnityVersionMajorMinor() >= MinimumUnityVersionFloat; + } + } +} +} // namespace Google diff --git a/source/PackageManagerResolver/src/PackageManifestModifier.cs b/source/PackageManagerResolver/src/PackageManifestModifier.cs new file mode 100644 index 00000000..ac91099f --- /dev/null +++ b/source/PackageManagerResolver/src/PackageManifestModifier.cs @@ -0,0 +1,331 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using EDMInternal.MiniJSON; +using System; +using System.IO; +using System.Collections.Generic; + +namespace Google { + +internal class PackageManifestModifier { + + /// + /// Thrown if an error occurs while parsing the manifest. + /// + internal class ParseException : Exception { + + /// + /// Construct an exception with a message. + /// + public ParseException(string message) : base(message) {} + } + + /// + /// Relative location of the manifest file from project root. + /// + internal const string MANIFEST_FILE_PATH = "Packages/manifest.json"; + + /// + /// JSON keys to be used in manifest.json + /// manifest.json expects scoped registries to be specified in the following format: + /// { + /// "scopedRegistries" : [ + /// { + /// "name": "Registry Name", + /// "url": "https://path/to/registry", + /// "scopes": [ "com.company.scope" ] + /// } + /// ] + /// } + /// + private const string MANIFEST_SCOPED_REGISTRIES_KEY = "scopedRegistries"; + private const string MANIFEST_REGISTRY_NAME_KEY = "name"; + private const string MANIFEST_REGISTRY_URL_KEY = "url"; + private const string MANIFEST_REGISTRY_SCOPES_KEY = "scopes"; + + /// + /// Logger for this object. + /// + public Google.Logger Logger; + + /// + /// Parsed manifest data from the manifest JSON file. + /// + internal Dictionary manifestDict = null; + + /// + /// Construct an object to modify manifest file. + /// + public PackageManifestModifier() { Logger = new Google.Logger(); } + + /// + /// Construct an object based on another modifier. + /// + public PackageManifestModifier(PackageManifestModifier other) { + Logger = other.Logger; + try { + manifestDict = Json.Deserialize(other.GetManifestJson()) as Dictionary; + } catch (Exception e) { + Logger.Log(String.Format("Failed to clone PackageManifestModifier. \nException:{1}", + MANIFEST_FILE_PATH, e.ToString()), LogLevel.Error); + } + } + + /// + /// Read manifest from the file and parse JSON string into a dictionary. + /// + /// True if read and parsed successfully. + internal bool ReadManifest() { + string manifestText; + try { + manifestText = File.ReadAllText(MANIFEST_FILE_PATH); + } catch (Exception e) { + Logger.Log(String.Format("Failed to read {0}. \nException:{1}", + MANIFEST_FILE_PATH, e.ToString()), LogLevel.Error); + return false; + } + try { + manifestDict = Json.Deserialize(manifestText) as Dictionary; + } catch (Exception e) { + Logger.Log(String.Format("Failed to parse {0}. \nException:{1}", + MANIFEST_FILE_PATH, e.ToString()), LogLevel.Error); + return false; + } + if (manifestDict == null) { + Logger.Log(String.Format("Failed to read the {0} because it is empty or malformed.", + MANIFEST_FILE_PATH), LogLevel.Error); + return false; + } + return true; + } + + /// + /// Try to get the scopedRegistries entry from the manifest. + /// + /// List of scoped registries if found, null otherwise. + /// Throws ParseException if the manifest isn't loaded or there is an error parsing + /// the manifest. + private List ScopedRegistries { + get { + object scopedRegistriesObj = null; + if (manifestDict == null) { + throw new ParseException(String.Format("'{0}' not loaded.", MANIFEST_FILE_PATH)); + } else if (manifestDict.TryGetValue(MANIFEST_SCOPED_REGISTRIES_KEY, + out scopedRegistriesObj)) { + var scopedRegistries = scopedRegistriesObj as List; + if (scopedRegistries == null) { + throw new ParseException( + String.Format("Found malformed '{0}' section in '{1}'.", + MANIFEST_SCOPED_REGISTRIES_KEY, MANIFEST_FILE_PATH)); + } + return scopedRegistries; + } + return null; + } + } + + + /// + /// Extract scoped registries from the parsed manifest. + /// + internal Dictionary> PackageManagerRegistries { + get { + var upmRegistries = new Dictionary>(); + List scopedRegistries = null; + try { + scopedRegistries = ScopedRegistries; + } catch (ParseException exception) { + Logger.Log(exception.ToString(), level: LogLevel.Warning); + } + if (scopedRegistries == null) { + Logger.Log(String.Format("No registries found in '{0}'", MANIFEST_FILE_PATH), + level: LogLevel.Verbose); + return upmRegistries; + } + Logger.Log(String.Format("Reading '{0}' from '{1}'", + MANIFEST_SCOPED_REGISTRIES_KEY, MANIFEST_FILE_PATH), + level: LogLevel.Verbose); + foreach (var obj in scopedRegistries) { + var registry = obj as Dictionary; + if (registry == null) continue; + string name = null; + string url = null; + var scopes = new List(); + object nameObj = null; + object urlObj = null; + object scopesObj; + if (registry.TryGetValue(MANIFEST_REGISTRY_NAME_KEY, out nameObj)) { + name = nameObj as string; + } + if (registry.TryGetValue(MANIFEST_REGISTRY_URL_KEY, out urlObj)) { + url = urlObj as string; + } + if (registry.TryGetValue(MANIFEST_REGISTRY_SCOPES_KEY, + out scopesObj)) { + List scopesObjList = scopesObj as List; + if (scopesObjList != null && scopesObjList.Count > 0) { + foreach (var scopeObj in scopesObjList) { + string scope = scopeObj as string; + if (!String.IsNullOrEmpty(scope)) scopes.Add(scope); + } + } + } + var upmRegistry = new PackageManagerRegistry() { + Name = name, + Url = url, + Scopes = scopes, + CustomData = registry, + CreatedBy = MANIFEST_FILE_PATH + }; + if (!String.IsNullOrEmpty(name) && + !String.IsNullOrEmpty(url) && + scopes.Count > 0) { + List upmRegistryList; + if (!upmRegistries.TryGetValue(url, out upmRegistryList)) { + upmRegistryList = new List(); + upmRegistries[url] = upmRegistryList; + } + upmRegistryList.Add(upmRegistry); + Logger.Log(String.Format("Read '{0}' from '{1}'", + upmRegistry.ToString(), MANIFEST_FILE_PATH), + level: LogLevel.Verbose); + } else { + Logger.Log( + String.Format("Ignoring malformed registry {0} in {1}", + upmRegistry.ToString(), MANIFEST_FILE_PATH), + level: LogLevel.Warning); + } + + } + return upmRegistries; + } + } + + /// + /// Add a scoped registries. + /// + /// Registries to add to the manifest. + /// true if the registries are added to the manifest, false otherwise. + internal bool AddRegistries(IEnumerable registries) { + List scopedRegistries; + try { + scopedRegistries = ScopedRegistries; + } catch (ParseException exception) { + Logger.Log(String.Format("{0} Unable to add registries:\n", + exception.ToString(), + PackageManagerRegistry.ToString(registries)), + level: LogLevel.Error); + return false; + } + if (scopedRegistries == null) { + scopedRegistries = new List(); + manifestDict[MANIFEST_SCOPED_REGISTRIES_KEY] = scopedRegistries; + } + RemoveRegistries(registries, displayWarning: false); + foreach (var registry in registries) { + scopedRegistries.Add(new Dictionary() { + { MANIFEST_REGISTRY_NAME_KEY, registry.Name }, + { MANIFEST_REGISTRY_URL_KEY, registry.Url }, + { MANIFEST_REGISTRY_SCOPES_KEY, registry.Scopes } + }); + } + return true; + } + + /// + /// Remove all scoped registries in the given list. + /// + /// A list of scoped registry to be removed + /// Whether to display a warning if specified registries were not + /// found. + /// true if the registries could be removed, false otherwise. + internal bool RemoveRegistries(IEnumerable registries, + bool displayWarning = true) { + List scopedRegistries = null; + try { + scopedRegistries = ScopedRegistries; + } catch (ParseException exception) { + Logger.Log(String.Format("{0} Unable to remove registries:\n", exception.ToString(), + PackageManagerRegistry.ToString(registries)), + level: LogLevel.Error); + return false; + } + int removed = 0; + int numberOfRegistries = 0; + var scopedRegistriesByUrl = PackageManagerRegistries; + foreach (var registry in registries) { + numberOfRegistries ++; + List existingRegistries; + if (scopedRegistriesByUrl.TryGetValue(registry.Url, out existingRegistries)) { + int remaining = existingRegistries.Count; + foreach (var existingRegistry in existingRegistries) { + if (scopedRegistries.Remove(existingRegistry.CustomData)) { + remaining --; + } else { + Logger.Log(String.Format("Failed to remove registry '{0}' from '{1}'", + existingRegistry, MANIFEST_FILE_PATH), + level: LogLevel.Error); + } + } + if (remaining == 0) removed ++; + } + } + if (displayWarning) { + Logger.Log(String.Format("Removed {0}/{1} registries from '{2}'", + removed, numberOfRegistries, MANIFEST_FILE_PATH), + level: removed == numberOfRegistries ? LogLevel.Verbose : LogLevel.Warning); + } + return removed == numberOfRegistries; + } + + /// + /// Write the dictionary to manifest file. + /// + /// True if serialized and wrote successfully. + internal bool WriteManifest() { + if (manifestDict == null) { + Logger.Log(String.Format("No manifest to write to '{0}'", MANIFEST_FILE_PATH), + level: LogLevel.Error); + return false; + } + try { + string manifestText = GetManifestJson(); + + if (!String.IsNullOrEmpty(manifestText)) { + File.WriteAllText(MANIFEST_FILE_PATH, manifestText); + } + } catch (Exception e) { + Logger.Log( + String.Format("Failed to write to {0}. \nException:{1}", + MANIFEST_FILE_PATH, e.ToString()), + level: LogLevel.Error); + return false; + } + return true; + } + + /// + /// Get the manifest json string. + /// + /// Manifest json string. + internal string GetManifestJson() { + return manifestDict != null ? + Json.Serialize(manifestDict, humanReadable: true, indentSpaces: 2) : + ""; + } +} +} // namespace Google diff --git a/source/PackageManagerResolver/src/PackageMigrator.cs b/source/PackageManagerResolver/src/PackageMigrator.cs new file mode 100644 index 00000000..96f8ce6f --- /dev/null +++ b/source/PackageManagerResolver/src/PackageMigrator.cs @@ -0,0 +1,1068 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using UnityEditor; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using System.Xml; + +namespace Google { + +/// +/// Searches for packages installed in the project that are available in UPM and prompts the +/// developer to migrate them to UPM. +/// +[InitializeOnLoad] +internal class PackageMigrator { + /// + /// A unique class to create the multi-select window to migrate packages. + /// + private class PackageMigratorWindow : MultiSelectWindow {} + + /// + /// Map of Version Handler determined package to Package Manager Package ID and version. + /// + internal class PackageMap { + + /// + /// Available Package Manager package information indexed by package ID. + /// This should be set via CacheAvailablePackageInfo() before creating new (i.e + /// not read from a file) PackageMap instances. + private static readonly Dictionary + availablePackageInfoCache = + new Dictionary(); + + /// + /// Installed Package Manager package information indexed by package ID. + /// This should be set via CacheInstalledPackageInfo() before creating PackageMap + /// instances. + private static readonly Dictionary + installedPackageInfoCache = + new Dictionary(); + + /// + /// Version Handler package name. + /// + public string VersionHandlerPackageName { get; set; } + + /// + /// Version Handler package name and version string. + /// + public string VersionHandlerPackageId { + get { + return String.Format("'{0}' v{1}", VersionHandlerPackageName, + VersionHandlerPackageVersion); + } + } + + /// + /// Get the files associated with the version handler package. + /// + public VersionHandlerImpl.ManifestReferences VersionHandlerManifest { + get { + if (!String.IsNullOrEmpty(VersionHandlerPackageName)) { + var manifestsByPackageName = + VersionHandlerImpl.ManifestReferences. + FindAndReadManifestsInAssetsFolderByPackageName(); + VersionHandlerImpl.ManifestReferences manifestReferences; + if (manifestsByPackageName.TryGetValue(VersionHandlerPackageName, + out manifestReferences)) { + return manifestReferences; + } + } + return null; + } + } + + /// + /// Get the version string from the Version Handler package. + /// + public string VersionHandlerPackageVersion { + get { + var manifest = VersionHandlerManifest; + return manifest != null ? manifest.currentMetadata.versionString : null; + } + } + + /// + /// Get the numeric version from the Version Handler package. + /// + public long VersionHandlerPackageCalculatedVersion { + get { + var manifest = VersionHandlerManifest; + return manifest != null ? manifest.currentMetadata.CalculateVersion() : 0; + } + } + + /// + /// Get the Package Manager Package ID in the form "name@version". + /// + public string PackageManagerPackageId { get; set; } + + /// + /// Get the Package Manager package info associated with the specific package ID. + /// + public PackageManagerClient.PackageInfo AvailablePackageManagerPackageInfo { + get { + return FindPackageInfoById(availablePackageInfoCache, PackageManagerPackageId); + } + } + + /// + /// Get the Package Manager package info associated with the specific package ID. + /// + public PackageManagerClient.PackageInfo InstalledPackageManagerPackageInfo { + get { + return FindPackageInfoById(installedPackageInfoCache, PackageManagerPackageId); + } + } + + /// + /// Determine whether a package has been migrated. + /// + public bool Migrated { + get { + return InstalledPackageManagerPackageInfo != null && + VersionHandlerManifest == null; + } + } + + /// + /// Construct an empty package map. + /// + public PackageMap() { + VersionHandlerPackageName = ""; + PackageManagerPackageId = ""; + } + + /// + /// Compare with this object. + /// + /// Object to compare with. + /// true if both objects have the same contents excluding CreatedBy, + /// false otherwise. + public override bool Equals(System.Object obj) { + var other = obj as PackageMap; + return other != null && + VersionHandlerPackageName == other.VersionHandlerPackageName && + PackageManagerPackageId == other.PackageManagerPackageId; + } + + /// + /// Geneerate a hash of this object. + /// + /// Hash of this object. + public override int GetHashCode() { + return VersionHandlerPackageName.GetHashCode() ^ + PackageManagerPackageId.GetHashCode(); + } + + /// + /// Convert to a human readable string. + /// + public override string ToString() { + return String.Format("Migrated: {0}, {1} -> '{2}'", Migrated, VersionHandlerPackageId, + PackageManagerPackageId); + } + + /// + /// Stores a set of PackageMap instances. + /// + internal static readonly string PackageMapFile = + Path.Combine("Temp", "PackageManagerResolverPackageMap.xml"); + + /// + /// Read a set of PackageMap instances from a file. + /// + /// List of package maps. + /// Thrown if an error occurs while reading the + /// file. + public static ICollection ReadFromFile() { + var packageMaps = new List(); + if (!File.Exists(PackageMapFile)) return packageMaps; + + PackageMap currentPackageMap = null; + if (!XmlUtilities.ParseXmlTextFileElements( + PackageMapFile, PackageMigrator.Logger, + (reader, elementName, isStart, parentElementName, elementNameStack) => { + if (elementName == "packageMaps" && parentElementName == "") { + if (isStart) { + currentPackageMap = new PackageMap(); + } else { + if (currentPackageMap != null && + !String.IsNullOrEmpty( + currentPackageMap.VersionHandlerPackageName) && + !String.IsNullOrEmpty( + currentPackageMap.PackageManagerPackageId)) { + packageMaps.Add(currentPackageMap); + } + } + return true; + } else if (elementName == "packageMap" && parentElementName == "packageMaps") { + return true; + } else if (elementName == "versionHandlerPackageName" && + parentElementName == "packageMap") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentPackageMap.VersionHandlerPackageName = + reader.ReadContentAsString(); + } + return true; + } else if (elementName == "unityPackageManagerPackageId" && + parentElementName == "packageMap") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentPackageMap.PackageManagerPackageId = + reader.ReadContentAsString(); + } + return true; + } + PackageMigrator.Logger.Log( + String.Format("{0}:{1}: Unknown XML element '{2}', parent '{3}'", + PackageMapFile, reader.LineNumber, elementName, + parentElementName, reader.LineNumber), + level: LogLevel.Error); + return false; + })) { + throw new IOException(String.Format("Failed to read package map file {0}. " + + "Package migration will likely fail.", + PackageMapFile)); + } + return packageMaps; + } + + /// + /// Generate a collection sorted by Version Handler package name. + /// + /// Sorted list of package maps. + private static ICollection SortedPackageMap( + IEnumerable packages) { + var sorted = new SortedDictionary(); + foreach (var pkg in packages) { + if (!String.IsNullOrEmpty(pkg.VersionHandlerPackageName)) { + sorted[pkg.VersionHandlerPackageName] = pkg; + } + } + return sorted.Values; + } + + /// + /// Write a set of PackageMap instances to a file. + /// + /// Package maps to write to a file. + /// Thrown if an error occurs while writing the + /// file. + public static void WriteToFile(ICollection packageMaps) { + try { + if (packageMaps.Count == 0) { + var failed = FileUtils.DeleteExistingFileOrDirectory(PackageMapFile); + if (failed.Count == 0) { + return; + } + throw new IOException(String.Format( + "Failed to delete {0}, package migration may attempt to resume from the " + + "cached state.", PackageMapFile)); + } + Directory.CreateDirectory(Path.GetDirectoryName(PackageMapFile)); + using (var writer = new XmlTextWriter(new StreamWriter(PackageMapFile)) { + Formatting = Formatting.Indented, + }) { + writer.WriteStartElement("packageMaps"); + foreach (var pkg in SortedPackageMap(packageMaps)) { + writer.WriteStartElement("packageMap"); + writer.WriteStartElement("versionHandlerPackageName"); + writer.WriteValue(pkg.VersionHandlerPackageName); + writer.WriteEndElement(); + writer.WriteStartElement("unityPackageManagerPackageId"); + writer.WriteValue(pkg.PackageManagerPackageId); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + } catch (Exception e) { + throw new IOException( + String.Format("Failed to write package map file {0} ({1})\n" + + "Package migration will likely fail.", PackageMapFile, e), e); + } + } + + /// + /// Lookup a package in the info cache. + /// + /// Cache to search. + /// Package ID to search with. + /// PackageInfo if found, null otherwise. + private static PackageManagerClient.PackageInfo FindPackageInfoById( + Dictionary cache, string packageId) { + PackageManagerClient.PackageInfo packageInfo; + if (cache.TryGetValue(packageId, out packageInfo)) { + return packageInfo; + } + return null; + } + + /// + /// Delegate used to list / search for Package Manager packages. + /// + /// Called with the result of the list / search operation. + private delegate void SearchPackageManagerDelegate( + Action result); + + /// + /// Reports progress of the package search process. + /// + /// Progress (0..1). + /// Description of the operation being performed. + public delegate void FindPackagesProgressDelegate(float progress, string description); + + /// + /// Cache Package Manager packages. + /// + /// Whether to refresh the cache or data in memory. + /// Search operation that returns a result to cache. + /// Type of search operation being performed for error + /// reporting. + /// Cache of PackageInfo objects to update. + /// Called when packages have been cached by this class. + /// Called as the operation progresses. + /// Length of time to use to simulate + /// progress of the search operation. The search will be reported as complete in this + /// length of time. + private static void CachePackageInfo( + bool refresh, SearchPackageManagerDelegate search, string searchDescription, + Dictionary cache, + Action complete, + FindPackagesProgressDelegate progressDelegate, + float simulateProgressTimeInMilliseconds) { + if (!refresh && cache.Count > 0) { + progressDelegate(1.0f, searchDescription); + complete(new PackageManagerClient.Error(null)); + return; + } + progressDelegate(0.0f, searchDescription); + + // Start a job to report progress during the search operation. + const float ProgressReportIntervalInMilliseconds = 1000.0f; + var progressPerUpdate = ProgressReportIntervalInMilliseconds / + simulateProgressTimeInMilliseconds; + object progressState = 0.0f; + var progressJob = new RunOnMainThread.PeriodicJob(() => { + float progress = (float)progressState; + progressDelegate(progress, searchDescription); + progressState = Math.Min(progress + progressPerUpdate, 1.0f); + return false; + }) { + IntervalInMilliseconds = ProgressReportIntervalInMilliseconds + }; + progressJob.Execute(); + + search((result) => { + var finishedDescription = searchDescription; + if (!String.IsNullOrEmpty(result.Error.Message)) { + finishedDescription = String.Format("{0} failed ({1})", searchDescription, + result.Error); + Logger.Log(finishedDescription, level: LogLevel.Error); + } else { + cache.Clear(); + foreach (var pkg in result.Packages) cache[pkg.PackageId] = pkg; + } + progressJob.Stop(); + progressDelegate(1.0f, finishedDescription); + complete(result.Error); + }); + } + + /// + /// Cache installed Package Manager packages. + /// + /// Whether to refresh the cache or data in memory. + /// Called when packages have been cached by this class. + /// Called as the operation progresses. + public static void CacheInstalledPackageInfo( + bool refresh, Action complete, + FindPackagesProgressDelegate progressDelegate) { + CachePackageInfo(refresh, PackageManagerClient.ListInstalledPackages, + "Listing installed UPM packages", + installedPackageInfoCache, complete, progressDelegate, + 2000.0f /* 2 seconds */); + } + + /// + /// Cache available Package Manager packages. + /// + /// Whether to refresh the cache or data in memory. + /// Called when packages have been cached by this class. + /// Called as the operation progresses. + public static void CacheAvailablePackageInfo( + bool refresh, Action complete, + FindPackagesProgressDelegate progressDelegate) { + CachePackageInfo(refresh, PackageManagerClient.SearchAvailablePackages, + "Searching for available UPM packages", + availablePackageInfoCache, complete, progressDelegate, + 20000.0f /* 20 seconds */); + } + + /// + /// Cache available and installed Package Manager packages. + /// + /// Whether to refresh the cache or data in memory. + /// Called when packages have been cached by this class. + /// Called as the operation progresses. + public static void CachePackageInfo(bool refresh, + Action complete, + FindPackagesProgressDelegate progressDelegate) { + // Fraction of caching progress for each of the following operations. + const float CacheInstallPackageProgress = 0.1f; + const float CacheAvailablePackageProgress = 0.9f; + + CacheInstalledPackageInfo(refresh, (installedError) => { + if (!String.IsNullOrEmpty(installedError.Message)) { + complete(installedError); + return; + } + CacheAvailablePackageInfo(refresh, (availableError) => { + complete(availableError); + }, + (progress, description) => { + progressDelegate(CacheInstallPackageProgress + + (progress * CacheAvailablePackageProgress), + description); + }); + }, + (progress, description) => { + progressDelegate(progress * CacheInstallPackageProgress, + description); + }); + } + + /// + /// Find packages in the project managed by the Version Handler that can be migrated to + /// the Package Manager. + /// + /// Called with an error string (empty if no error occured) and the + /// list of packages that should be migrated. + /// Called as the operation progresses. + /// Include UPM packages that are older than the + /// packages installed in the project. + public static void FindPackagesToMigrate(Action> complete, + FindPackagesProgressDelegate progressDelegate, + bool includeOutOfDatePackages = false) { + if (!PackageMigrator.Available) { + complete(PackageMigrator.NotAvailableMessage, new PackageMap[] {}); + return; + } + var packageMaps = new HashSet(); + + progressDelegate(0.0f, "Searching for managed Version Handler packages"); + var installedManifestsByPackageName = VersionHandlerImpl.ManifestReferences. + FindAndReadManifestsInAssetsFolderByPackageName(); + var installedManifestsByNamesAndAliases = + new Dictionary( + installedManifestsByPackageName); + foreach (var manifest in installedManifestsByPackageName.Values) { + foreach (var manifestAlias in manifest.Aliases) { + installedManifestsByNamesAndAliases[manifestAlias] = manifest; + } + } + var installedManifestPackageNames = + new HashSet(installedManifestsByNamesAndAliases.Keys); + + // If no Version Handler packages are installed, return an empty set. + if (installedManifestsByNamesAndAliases.Count == 0) { + progressDelegate(1.0f, "No Version Handler packages found"); + complete("", packageMaps); + return; + } + + Logger.Log(String.Format( + "Detected version handler packages:\n{0}", + String.Join("\n", (new List( + installedManifestsByPackageName.Keys)).ToArray())), + level: LogLevel.Verbose); + + CachePackageInfo(true, (error) => { + if (!String.IsNullOrEmpty(error.Message)) { + progressDelegate(1.0f, String.Format("Failed: {0}", error.Message)); + complete(error.Message, packageMaps); + return; + } + var packageInfos = new List( + installedPackageInfoCache.Values); + packageInfos.AddRange(availablePackageInfoCache.Values); + + foreach (var pkg in packageInfos) { + var foundVersionHandlerPackageNames = pkg.GetVersionHandlerPackageNames(); + var versionHandlerPackageNames = + new HashSet(foundVersionHandlerPackageNames); + versionHandlerPackageNames.IntersectWith(installedManifestPackageNames); + + if (versionHandlerPackageNames.Count == 0) { + Logger.Log(String.Format( + "{0} does not map to an installed Version Handler package [{1}]", + pkg.PackageId, + String.Join(", ", (new List( + foundVersionHandlerPackageNames)).ToArray())), + level: LogLevel.Debug); + continue; + } + + // Map all aliases to the canonical names of version handler packages to + // migrate. + var canonicalVersionHandlerPackageNames = new HashSet(); + var appliedMappings = new List(); + foreach (var versionHandlerPackageName in versionHandlerPackageNames) { + var canonicalName = installedManifestsByNamesAndAliases[ + versionHandlerPackageName].filenameCanonical; + canonicalVersionHandlerPackageNames.Add(canonicalName); + if (canonicalName == versionHandlerPackageName || + versionHandlerPackageNames.Contains(canonicalName)) { + continue; + } + appliedMappings.Add(String.Format( + "'{0}' UPM package references VH package '{1}' with canonical " + + "name '{2}'", pkg.PackageId, versionHandlerPackageName, + canonicalName)); + } + if (appliedMappings.Count > 0) { + Logger.Log(String.Format("Mapped VH package aliases:\n{0}", + String.Join("\n", appliedMappings.ToArray())), + level: LogLevel.Verbose); + } + + foreach (var versionHandlerPackageName in + canonicalVersionHandlerPackageNames) { + var packageMap = new PackageMap() { + VersionHandlerPackageName = versionHandlerPackageName, + PackageManagerPackageId = pkg.PackageId + }; + Logger.Log( + String.Format("Found Version Handler package to migrate to UPM " + + "package {0} --> '{1}'.", + packageMap.VersionHandlerPackageId, + packageMap.PackageManagerPackageId), + level: LogLevel.Verbose); + if (!includeOutOfDatePackages && + packageMap.AvailablePackageManagerPackageInfo. + CalculateVersion() < + packageMap.VersionHandlerPackageCalculatedVersion) { + Logger.Log( + String.Format( + "Ignoring UPM package '{0}' as it's older than " + + "installed package '{1}' at version {2}.", + packageMap.PackageManagerPackageId, + packageMap.VersionHandlerPackageName, + packageMap.VersionHandlerPackageVersion), + level: LogLevel.Verbose); + continue; + } + packageMaps.Add(packageMap); + } + } + + complete("", packageMaps); + }, progressDelegate); + } + + /// + /// Calculate migration progress through the specified list. + /// + /// List of packages to migrated. + /// A value between 0 (no progress) and 1 (complete). + public static float CalculateProgress(ICollection packages) { + int total = packages.Count; + int migrated = 0; + foreach (var pkg in packages) { + if (pkg.Migrated) migrated ++; + } + return total > 0 ? (float)migrated / (float)total : 1.0f; + } + } + + /// + /// Logger for this module. + /// + public static Logger Logger = PackageManagerResolver.logger; + + /// + /// Job queue to execute package migration. + /// + private static RunOnMainThread.JobQueue migrationJobQueue = new RunOnMainThread.JobQueue(); + + /// + /// Packages being migrated. + /// + private static ICollection inProgressPackageMaps = null; + + /// + /// Enumerates through set of packages being migrated. + /// + private static IEnumerator inProgressPackageMapsEnumerator = null; + + /// + /// Reports progress of the package migration process. + /// + /// Progress (0..1). + /// Package being migrated or null if complete. + private delegate void ProgressDelegate(float progress, PackageMap packageMap); + + /// + /// Determine whether the package manager with scoped registries is available. + /// + public static bool Available { + get { + return PackageManagerClient.Available && + PackageManagerResolver.ScopedRegistriesSupported; + } + } + + /// + /// Message displayed if package migration doesn't work. + /// + private const string NotAvailableMessage = + "Unity Package Manager with support for scoped registries is not " + + "available in this version of Unity."; + + /// + /// Read the package migration state. + /// + /// true if the migration state is already in memory or read successfully and contains + /// a non-zero number of PackageMap instances, false otherwise. + /// Throws IOException if the read fails. + private static bool ReadMigrationState() { + if (inProgressPackageMaps == null || inProgressPackageMapsEnumerator == null) { + // Read in-progress migrations including progress through the queue. + try { + inProgressPackageMaps = PackageMap.ReadFromFile(); + inProgressPackageMapsEnumerator = inProgressPackageMaps.GetEnumerator(); + } catch (IOException error) { + ClearMigrationState(); + throw error; + } + } else { + inProgressPackageMapsEnumerator.Reset(); + } + return inProgressPackageMapsEnumerator.MoveNext(); + } + + + /// + /// Clear the package migration state. + /// + private static void ClearMigrationState() { + inProgressPackageMaps = null; + inProgressPackageMapsEnumerator = null; + PackageMap.WriteToFile(new PackageMap[] {}); + } + + /// + /// Migrate the next package in the queue. + /// + /// + /// This method assumes it's being executed as a job on migrationJobQueue so + /// migrationJobQueue.Complete() must be called before the complete() method in all code paths. + /// + /// When complete, called with a null string if successful, + /// called with an error message otherwise. + /// Called to report progress. + private static void MigrateNext(Action complete, ProgressDelegate progress) { + do { + var packageMap = inProgressPackageMapsEnumerator.Current; + Logger.Log(String.Format("Examining package to migrate: {0}", packageMap.ToString()), + level: LogLevel.Verbose); + if (!packageMap.Migrated) { + progress(PackageMap.CalculateProgress(inProgressPackageMaps), packageMap); + + Logger.Log(String.Format("Removing {0} to replace with {1}", + packageMap.VersionHandlerPackageName, + packageMap.PackageManagerPackageId), + level: LogLevel.Verbose); + // Uninstall the .unitypackage. + var deleteResult = VersionHandlerImpl.ManifestReferences.DeletePackages( + new HashSet() { packageMap.VersionHandlerPackageName }, + force: true); + if (deleteResult.Count > 0) { + var error = String.Format("Uninstallation of .unitypackage '{0}' failed, " + + "halted package migration to '{1}'. You will need " + + "to reinstall '{0}' to restore your project", + packageMap.VersionHandlerPackageName, + packageMap.PackageManagerPackageId); + migrationJobQueue.Complete(); + complete(error); + return; + } + + Logger.Log(String.Format("Installing {0} to replace {1}", + packageMap.PackageManagerPackageId, + packageMap.VersionHandlerPackageName), + level: LogLevel.Verbose); + PackageManagerClient.AddPackage( + packageMap.PackageManagerPackageId, + (result) => { + if (!String.IsNullOrEmpty(result.Error.Message)) { + var error = String.Format("Installation of package '{0}' failed, " + + "halted package migration ({1}). You will " + + "need to reinstall {2} to restore your " + + "project.", + packageMap.PackageManagerPackageId, + result.Error.Message, + packageMap.VersionHandlerPackageName); + migrationJobQueue.Complete(); + complete(error); + } else { + MigrateNext(complete, progress); + } + }); + return; + } + } while (inProgressPackageMapsEnumerator.MoveNext()); + + Logger.Log("Package migration complete", level: LogLevel.Verbose); + progress(1.0f, null); + ClearMigrationState(); + migrationJobQueue.Complete(); + complete(null); + } + + /// + /// Start / resume package migration. + /// + /// When complete, called with a null string if successful, + /// called with an error message otherwise. + /// Called to report progress of package + /// migration. + /// Called to report progress of package + /// migration initialization. + private static void StartOrResumeMigration( + Action complete, ProgressDelegate migratePackagesProgressDelegate, + PackageMap.FindPackagesProgressDelegate findPackagesProgressDelegate) { + migrationJobQueue.Schedule(() => { + // Read in-progress migrations including progress through the queue. + try { + if (!ReadMigrationState()) { + migrationJobQueue.Complete(); + complete(null); + return; + } + } catch (IOException ioError) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/read_snapshot", + "Migrate Packages: Read Snapshot Failed"); + migrationJobQueue.Complete(); + complete(ioError.Message); + return; + } + // Fetch the list of installed packages before starting migration. + PackageMap.CacheInstalledPackageInfo( + false, (error) => { + if (!String.IsNullOrEmpty(error.Message)) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/find_packages", + "Migrate Packages: Find Packages Failed"); + migrationJobQueue.Complete(); + complete(error.Message); + return; + } + // Migrate packages. + MigrateNext(complete, migratePackagesProgressDelegate); + }, findPackagesProgressDelegate); + }); + } + + /// + /// Called when the assembly is initialized by Unity. + /// + static PackageMigrator() { + ResumeMigration(); + } + + /// + /// Report that package migration failed. + /// + private static void ReportPackageMigrationFailed() { + int numberOfSelectedPackages = -1; + int numberOfMigratedPackages = -1; + try { + ReadMigrationState(); + numberOfSelectedPackages = inProgressPackageMaps.Count; + numberOfMigratedPackages = 0; + foreach (var packageMap in inProgressPackageMaps) { + if (packageMap.Migrated) numberOfMigratedPackages ++; + } + } catch (IOException) { + // Ignore the exception. + } + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed", + new KeyValuePair[] { + new KeyValuePair("selected", numberOfSelectedPackages.ToString()), + new KeyValuePair("migrated", numberOfMigratedPackages.ToString()), + }, + "Migrate Packages: Failed"); + } + + + /// + /// Resume migration after an app domain reload. + /// + public static void ResumeMigration() { + RunOnMainThread.Run(() => { + if (!Available) return; + try { + if (ReadMigrationState()) { + Logger.Log(String.Format("Resuming migration from {0} of {1} packages", + PackageMap.PackageMapFile, + inProgressPackageMaps.Count), + level: LogLevel.Verbose); + TryMigration((error) => { + if (!String.IsNullOrEmpty(error)) { + Logger.Log(String.Format("Migration failed: {0}", error), + level: LogLevel.Error); + } + }); + } + } catch (IOException ioError) { + Logger.Log(String.Format("Failed to resume package migration: {0}", ioError), + level: LogLevel.Error); + ReportPackageMigrationFailed(); + } + }, runNow: false); + } + + /// + /// Displayed when searching for / migrating packages. + /// + const string WindowTitle = "Migrating Packages"; + + /// + /// Displays progress of a find packages operation. + /// + /// Progress (0..1). + /// Description of the operation being performed. + private static void UpdateFindProgressBar(float progress, string description) { + var message = String.Format("Finding package(s) to migrate {0}%: {1}", + (int)(progress * 100.0f), description); + Logger.Log(message, level: LogLevel.Verbose); + EditorUtility.DisplayProgressBar(WindowTitle, description, progress); + } + + /// + /// Display progress of a package migration operation. + /// + /// Progress (0..1). + /// Package being migrated. + private static void UpdatePackageMigrationProgressBar(float progress, PackageMap packageMap) { + var description = packageMap != null ? + String.Format("{0} --> {1}", + packageMap.VersionHandlerPackageId, + packageMap.PackageManagerPackageId) : "(none)"; + var message = String.Format("Migrating package(s) {0}%: {1}", + (int)(progress * 100.0f), description); + Logger.Log(message, level: LogLevel.Verbose); + EditorUtility.DisplayProgressBar(WindowTitle, description, progress); + } + + /// + /// Run through the whole process. + /// + /// Called when migration is complete with an error message if + /// it fails. + public static void TryMigration(Action complete) { + if (!Available) { + complete(NotAvailableMessage); + return; + } + + Action clearProgressAndComplete = (error) => { + EditorUtility.ClearProgressBar(); + if (String.IsNullOrEmpty(error)) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/success", + new KeyValuePair[] { + new KeyValuePair( + "migrated", inProgressPackageMaps.Count.ToString()), + }, + "Migrate Packages: Success"); + } + ClearMigrationState(); + complete(error); + }; + + StartOrResumeMigration((migrationError) => { + if (!String.IsNullOrEmpty(migrationError)) { + clearProgressAndComplete(migrationError); + return; + } + + PackageMap.FindPackagesToMigrate((error, packageMaps) => { + if (!String.IsNullOrEmpty(error)) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/find_packages", + "Migrate Packages: Find Packages Failed"); + clearProgressAndComplete(error); + return; + } + try { + PackageMap.WriteToFile(packageMaps); + } catch (IOException e) { + EditorUtility.ClearProgressBar(); + clearProgressAndComplete(e.Message); + return; + } + StartOrResumeMigration(clearProgressAndComplete, + UpdatePackageMigrationProgressBar, + UpdateFindProgressBar); + }, UpdateFindProgressBar); + }, UpdatePackageMigrationProgressBar, UpdateFindProgressBar); + } + + /// + /// Display a window to select which packages to install. + /// + /// Set of package maps available for selection. + /// Called with a set of items selected from the specified + /// list. + public static void DisplaySelectionWindow(ICollection packageMaps, + Action> selectedPackageMaps) { + var packageMapsByPackageId = new Dictionary(); + var items = new List>(); + foreach (var pkg in packageMaps) { + packageMapsByPackageId[pkg.PackageManagerPackageId] = pkg; + items.Add(new KeyValuePair( + pkg.PackageManagerPackageId, + String.Format("{0} --> {1}", pkg.VersionHandlerPackageId, + pkg.PackageManagerPackageId))); + } + + var window = MultiSelectWindow.CreateMultiSelectWindow(WindowTitle); + window.minSize = new UnityEngine.Vector2(800, 400); + window.SelectedItems = new HashSet(packageMapsByPackageId.Keys); + window.AvailableItems = items; + window.Sort(1); + window.Caption = + "Select the set of packages to migrate from being installed under " + + "the Assets folder to being installed using the Unity Package Manager\n\n" + + "As each package is migrated, it will be removed from the Assets folder and " + + "installed via the Unity Package Manager. If this package (EDM4U) is being " + + "migrated the progress bar will disappear as it is migrated and migration will " + + "resume when this package is reloaded after being installed by the Unity " + + "Package Manager"; + window.OnApply = () => { + var selected = new List(); + foreach (var item in window.SelectedItems) { + selected.Add(packageMapsByPackageId[item]); + } + selectedPackageMaps(selected); + }; + window.OnCancel = () => { + selectedPackageMaps(new PackageMap[] {}); + }; + } + + /// + /// Display an error in a dialog. + /// + /// Error message to display. + private static void DisplayError(string error) { + Logger.Log(error, level: LogLevel.Error); + DialogWindow.Display(WindowTitle, error, DialogWindow.Option.Selected0, "OK"); + } + + /// + /// Find packages to migrate, display a window to provide the user a way to select the packages + /// to migrate and start migration if they're selected. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Migrate Packages")] + public static void MigratePackages() { + PackageMap.FindPackagesToMigrate((findError, availablePackageMaps) => { + EditorUtility.ClearProgressBar(); + + // If an error occurs, display a dialog. + if (!String.IsNullOrEmpty(findError)) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/find_packages", + "Migrate Packages: Find Packages Failed"); + DisplayError(findError); + return; + } + + // Show a package selection window and start migration if the user selects apply. + DisplaySelectionWindow(availablePackageMaps, (selectedPackageMaps) => { + if (selectedPackageMaps.Count == 0) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/canceled", + "Migrate Packages: Canceled"); + ClearMigrationState(); + return; + } + try { + ClearMigrationState(); + PackageMap.WriteToFile(selectedPackageMaps); + } catch (IOException e) { + DisplayError(String.Format("Migration failed ({0})", e.Message)); + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/write_snapshot", + "Migrate Packages: Write Snapshot Failed"); + return; + } + + TryMigration((migrationError) => { + if (!String.IsNullOrEmpty(migrationError)) { + DisplayError(migrationError); + } + }); + }); + }, UpdateFindProgressBar); + } +} + +/// +/// Extension class to retrieve Version Handler package information from PackageInfo. +/// +internal static class PackageInfoVersionHandlerExtensions { + + /// + /// Regular expression that extracts the Version Handler package name from a label. + /// + private static Regex KEYWORD_VERSION_HANDLER_NAME_REGEX = new Regex("^vh[-_]name:(.*)"); + + /// + /// Get Version Handler package names associated with this Package Manager package. + /// + /// Set of package names associataed with this package. + public static HashSet GetVersionHandlerPackageNames( + this PackageManagerClient.PackageInfo packageInfo) { + var versionHandlerPackageNames = new HashSet(); + foreach (var keyword in packageInfo.Keywords) { + var match = KEYWORD_VERSION_HANDLER_NAME_REGEX.Match(keyword); + if (match.Success) versionHandlerPackageNames.Add(match.Groups[1].Value); + } + return versionHandlerPackageNames; + } + + /// + /// Get a numeric version of the Package Manager package. + /// + /// Numeric version of the package that can be compared with Version Handler package + /// versions. + public static long CalculateVersion(this PackageManagerClient.PackageInfo packageInfo) { + var version = packageInfo.Version; + return version != null ? + VersionHandlerImpl.FileMetadata.CalculateVersion(version) : 0; + } +} + +} diff --git a/source/PackageManagerResolver/src/SettingsDialog.cs b/source/PackageManagerResolver/src/SettingsDialog.cs new file mode 100644 index 00000000..f323edc6 --- /dev/null +++ b/source/PackageManagerResolver/src/SettingsDialog.cs @@ -0,0 +1,234 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google { + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +/// +/// Settings dialog for PackageManagerResolver. +/// +public class PackageManagerResolverSettingsDialog : EditorWindow +{ + /// + /// Loads / saves settings for this dialog. + /// + private class Settings { + /// + /// Whether enable external registries to be added to this Unity project + /// + internal bool enable; + + /// + /// Whether to prompt the user before to adding external registries. + /// + internal bool promptToAddRegistries; + + + /// + /// Whether to prompt the user to migrate Version Handler to UPM packages after a + /// registry has been added. + /// + internal bool promptToMigratePackages; + + /// + /// Whether to enable / disable verbose logging. + /// + internal bool verboseLoggingEnabled; + + /// + /// Whether settings are project specific. + /// + internal bool useProjectSettings; + + /// + /// Analytics settings. + /// + internal EditorMeasurement.Settings analyticsSettings; + + /// + /// Load settings into the dialog. + /// + internal Settings() { + enable = PackageManagerResolver.Enable; + promptToAddRegistries = PackageManagerResolver.PromptToAddRegistries; + promptToMigratePackages = PackageManagerResolver.PromptToMigratePackages; + verboseLoggingEnabled = PackageManagerResolver.VerboseLoggingEnabled; + useProjectSettings = PackageManagerResolver.UseProjectSettings; + analyticsSettings = + new EditorMeasurement.Settings(PackageManagerResolver.analytics); + } + + /// + /// Save dialog settings to preferences. + /// + internal void Save() { + PackageManagerResolver.Enable = enable; + PackageManagerResolver.PromptToAddRegistries = promptToAddRegistries; + PackageManagerResolver.PromptToMigratePackages = promptToMigratePackages; + PackageManagerResolver.VerboseLoggingEnabled = verboseLoggingEnabled; + PackageManagerResolver.UseProjectSettings = useProjectSettings; + analyticsSettings.Save(); + } + } + + private Settings settings; + + private Vector2 scrollPosition = new Vector2(0, 0); + + /// + /// Load settings for this dialog. + /// + private void LoadSettings() { + settings = new Settings(); + } + + /// + /// Setup the window's initial position and size. + /// + public void Initialize() { + minSize = new Vector2(350, 430); + position = new Rect(UnityEngine.Screen.width / 3, + UnityEngine.Screen.height / 3, + minSize.x, minSize.y); + PackageManagerResolver.analytics.Report("settings/show", "Settings"); + } + + /// + /// Called when the window is loaded. + /// + public void OnEnable() { + LoadSettings(); + } + + /// + /// Called when the GUI should be rendered. + /// + public void OnGUI() { + GUI.skin.label.wordWrap = true; + + GUILayout.BeginVertical(); + + GUILayout.Label(String.Format("{0} (version {1}.{2}.{3})", + PackageManagerResolver.PLUGIN_NAME, + PackageManagerResolverVersionNumber.Value.Major, + PackageManagerResolverVersionNumber.Value.Minor, + PackageManagerResolverVersionNumber.Value.Build)); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + if (!PackageManagerResolver.ScopedRegistriesSupported) { + GUILayout.Label( + String.Format("Only supported from Unity {0} and above.", + PackageManagerResolver.MinimumUnityVersionString)); + } + + // Disable all GUI if scoped registries are not supported. + GUI.enabled = PackageManagerResolver.ScopedRegistriesSupported; + + GUILayout.BeginHorizontal(); + GUILayout.Label("Add package registries", EditorStyles.boldLabel); + settings.enable = EditorGUILayout.Toggle(settings.enable); + GUILayout.EndHorizontal(); + GUILayout.Label("When this option is enabled, Unity Package Manager registries " + + "discovered by this plugin will be added to the project's manifest. " + + "This allows Unity packages from additional sources to be " + + "discovered and managed by the Unity Package Manager."); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Prompt to add package registries", EditorStyles.boldLabel); + settings.promptToAddRegistries = EditorGUILayout.Toggle(settings.promptToAddRegistries); + GUILayout.EndHorizontal(); + GUILayout.Label("When this option is enabled, this plugin will prompt for confirmation " + + "before adding Unity Package Manager registries to the project's " + + "manifest."); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Prompt to migrate packages", EditorStyles.boldLabel); + settings.promptToMigratePackages = EditorGUILayout.Toggle(settings.promptToMigratePackages); + GUILayout.EndHorizontal(); + GUILayout.Label("When this option is enabled, this plugin will search the Unity Package " + + "Manager (UPM) for available packages that are currently installed in " + + "the project in the `Assets` directory that have equivalent or newer " + + "versions available on UPM and prompt to migrate these packages."); + + settings.analyticsSettings.RenderGui(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Verbose logging", EditorStyles.boldLabel); + settings.verboseLoggingEnabled = EditorGUILayout.Toggle(settings.verboseLoggingEnabled); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Use project settings", EditorStyles.boldLabel); + settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); + GUILayout.EndHorizontal(); + + GUILayout.Space(10); + + if (GUILayout.Button("Reset to Defaults")) { + // Load default settings into the dialog but preserve the state in the user's + // saved preferences. + var backupSettings = new Settings(); + PackageManagerResolver.RestoreDefaultSettings(); + PackageManagerResolver.analytics.Report("settings/reset", "Settings Reset"); + LoadSettings(); + backupSettings.Save(); + } + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Cancel")) { + PackageManagerResolver.analytics.Report("settings/cancel", "Settings Cancel"); + Close(); + } + if (GUILayout.Button("OK")) { + PackageManagerResolver.analytics.Report( + "settings/save", + new KeyValuePair[] { + new KeyValuePair( + "enabled", + PackageManagerResolver.Enable.ToString()), + new KeyValuePair( + "promptToAddRegistries", + PackageManagerResolver.PromptToAddRegistries.ToString()), + new KeyValuePair( + "promptToMigratePackages", + PackageManagerResolver.PromptToMigratePackages.ToString()), + new KeyValuePair( + "verboseLoggingEnabled", + PackageManagerResolver.VerboseLoggingEnabled.ToString()), + }, + "Settings Save"); + settings.Save(); + Close(); + + PackageManagerResolver.CheckRegistries(); + } + GUILayout.EndHorizontal(); + + EditorGUILayout.EndScrollView(); + GUILayout.EndVertical(); + + // Re-enable GUI + GUI.enabled = true; + } +} + +} // namespace Google + diff --git a/source/PackageManagerResolver/src/VersionNumber.cs b/source/PackageManagerResolver/src/VersionNumber.cs new file mode 100644 index 00000000..d112d2be --- /dev/null +++ b/source/PackageManagerResolver/src/VersionNumber.cs @@ -0,0 +1,42 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google { + using System; + + using UnityEditor; + + /// + /// Get the version number of this plugin. + /// + public class PackageManagerResolverVersionNumber { + + /// + /// Version number, patched by the build process. + /// + private const string VERSION_STRING = "1.2.186"; + + /// + /// Cached version structure. + /// + private static Version value = new Version(VERSION_STRING); + + /// + /// Get the version number. + /// + public static Version Value { get { return value; } } + } +} diff --git a/source/PackageManagerResolver/src/XmlPackageManagerRegistries.cs b/source/PackageManagerResolver/src/XmlPackageManagerRegistries.cs new file mode 100644 index 00000000..9dd3909c --- /dev/null +++ b/source/PackageManagerResolver/src/XmlPackageManagerRegistries.cs @@ -0,0 +1,202 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +namespace Google { + using System; + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + using Google; + using UnityEditor; + + /// + /// Parses XML declared Unity Package Manager (UPM) registries required by a Unity plugin into + /// the XmlRegistries.Registry class. + /// + internal class XmlPackageManagerRegistries { + + /// + /// Set of regular expressions that match files which contain dependency + /// specifications. + /// + internal static readonly HashSet fileRegularExpressions = new HashSet { + new Regex(@".*[/\\]Editor[/\\].*Registries\.xml$") + }; + + /// + /// Paths to search for files. + /// + internal static readonly string[] fileSearchPaths = new string[] { "Assets", "Packages"}; + + /// + /// Label applied to registries. + /// + internal static readonly string REGISTRIES_LABEL = "gumpr_registries"; + + /// + /// Asset managed by this module. + /// + private static readonly string UPM_REGISTRIES = "Package Manager Registries"; + + /// + /// Registries read from files indexed by URL. + /// + internal Dictionary Registries; + + /// + /// Construct an empty XML UPM registries reader. + /// + public XmlPackageManagerRegistries() { Clear(); } + + /// + /// Clear the cached registries. + /// + internal void Clear() { + Registries = new Dictionary(); + } + + /// + /// Determines whether a filename matches an XML registries file. + /// + /// Filename to check. + /// true if it is a match, false otherwise. + internal static bool IsRegistriesFile(string filename) { + foreach (var regex in fileRegularExpressions) { + if (regex.Match(filename).Success) { + return true; + } + } + return false; + } + + /// + /// Find all XML declared registries files. + /// + /// Set of XML registries filenames in the project. + private IEnumerable FindFiles() { + var foundFiles = new HashSet( + VersionHandlerImpl.SearchAssetDatabase( + assetsFilter: "Registries t:TextAsset", + filter: IsRegistriesFile, + directories: fileSearchPaths)); + foundFiles.UnionWith(VersionHandlerImpl.SearchAssetDatabase( + assetsFilter: "l:" + REGISTRIES_LABEL, + directories: fileSearchPaths)); + return foundFiles; + } + + /// + /// Read XML declared registries. + /// + /// File to read. + /// Logger class. + /// true if the file was read successfully, false otherwise. + internal bool Read(string filename, Logger logger) { + PackageManagerRegistry upmRegistry = null; + logger.Log(String.Format("Reading {0} XML file {1}", UPM_REGISTRIES, filename), + level: LogLevel.Verbose); + if (!XmlUtilities.ParseXmlTextFileElements( + filename, logger, + (reader, elementName, isStart, parentElementName, elementNameStack) => { + if (elementName == "registries" && parentElementName == "") { + return true; + } else if (elementName == "registry" && + parentElementName == "registries" && + isStart) { + upmRegistry = new PackageManagerRegistry() { + Name = reader.GetAttribute("name") ?? "", + Url = reader.GetAttribute("url") ?? "", + TermsOfService = reader.GetAttribute("termsOfService") ?? "", + PrivacyPolicy = reader.GetAttribute("privacyPolicy") ?? "", + CreatedBy = String.Format("{0}:{1}", filename, + reader.LineNumber) + }; + return true; + } else if (elementName == "scopes" && + parentElementName == "registry") { + if (isStart) upmRegistry.Scopes = new List(); + return true; + } else if (elementName == "scope" && + parentElementName == "scopes" && + !(String.IsNullOrEmpty(upmRegistry.Name) || + String.IsNullOrEmpty(upmRegistry.Url))) { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + upmRegistry.Scopes.Add(reader.ReadContentAsString()); + } + return true; + } else if (elementName == "registry" && + parentElementName == "registries" && + !isStart) { + if (!(String.IsNullOrEmpty(upmRegistry.Name) || + String.IsNullOrEmpty(upmRegistry.Url) || + upmRegistry.Scopes.Count == 0)) { + PackageManagerRegistry existingRegistry; + if (!Registries.TryGetValue(upmRegistry.Url, out existingRegistry)) { + Registries[upmRegistry.Url] = upmRegistry; + } else if (!existingRegistry.Equals(upmRegistry)) { + logger.Log( + String.Format( + "{0} for URL '{1}' called '{2}' was already read " + + "from '{3}'.\n" + + "{0} from '{4}' will be ignored.", + UPM_REGISTRIES, upmRegistry.Url, upmRegistry.Name, + existingRegistry.CreatedBy, filename), + level: LogLevel.Warning); + } + } else { + logger.Log( + String.Format( + "Malformed {0} for registry {1} " + + "found in {2}.\n" + + "All {0} will be ignored in {2}.", + UPM_REGISTRIES, upmRegistry.ToString(), filename), + level: LogLevel.Error); + return false; + } + return true; + } + return false; + })) { + return false; + } + return true; + } + + /// + /// Find and read all XML declared registries. + /// + /// Logger class. + /// true if all files were read successfully, false otherwise. + public bool ReadAll(Logger logger) { + bool success = true; + Clear(); + foreach (var filename in FindFiles()) { + if (!Read(filename, logger)) { + logger.Log(String.Format("Unable to read {0} from {1}.\n" + + "{0} in this file will be ignored.", + UPM_REGISTRIES, filename), + level: LogLevel.Error); + success = false; + } + } + return success; + } + } +} + + diff --git a/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests.csproj b/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests.csproj new file mode 100644 index 00000000..c3e407bb --- /dev/null +++ b/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests.csproj @@ -0,0 +1,65 @@ + + + + Debug + AnyCPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01} + Library + Google.PackageManagerClientIntegrationTests + Google.PackageManagerClientIntegrationTests + v2.0 + 1.2 + 12.0.0 + 2.0 + + + True + full + False + bin\Debug + DEBUG; + prompt + 4 + False + + + none + True + bin\Release + prompt + 4 + False + + + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + + + + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040} + IntegrationTester + + + {77EBE819-CBE6-4CA8-A791-ED747EA29D30} + PackageManagerResolver + + + diff --git a/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests/PackageManagerClientIntegrationTests.cs b/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests/PackageManagerClientIntegrationTests.cs new file mode 100644 index 00000000..b4b81f05 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests/PackageManagerClientIntegrationTests.cs @@ -0,0 +1,353 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Collections; +using System.Reflection; +using System.IO; + +using Google; + +namespace Google.PackageManagerClientIntegrationTests { + +/// +/// Integration tests for PackageManagerClient. +/// +public static class PackageManagerClientTests { + + /// + /// Whether the Unity Package Manager is available in the current version of Unity. + /// + private static bool UpmAvailable { + get { return ExecutionEnvironment.VersionMajorMinor >= 2017.3; } + } + + /// + /// Whether the SearchAll method is available in the Unity Package Manager. + /// + private static bool UpmSearchAllAvailable { + get { return ExecutionEnvironment.VersionMajorMinor >= 2018.0; } + } + + /// + /// Determine whether the available method is returning the package manager in the current + /// version of Unity. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestAvailable(IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + if (UpmAvailable != PackageManagerClient.Available) { + testCaseResult.ErrorMessages.Add(String.Format("PackageManagerClient.Available " + + "returned {0}, expected {1}", + PackageManagerClient.Available, + UpmAvailable)); + } + testCaseComplete(testCaseResult); + } + + /// + /// Convert a list of packages to a list of strings. + /// + /// List of PackageInfo instances to convert to strings. + /// List of string representation of the specified packages. + private static List PackageInfoListToStringList( + IEnumerable packageInfos) { + var packageInfosAsStrings = new List(); + if (packageInfos != null) { + foreach (var pkg in packageInfos) packageInfosAsStrings.Add(pkg.ToString()); + } + return packageInfosAsStrings; + } + + /// + /// Convert a list of packages to a list of string package names. + /// + /// List of PackageInfo instances to convert to strings. + /// List of package names of the specified packages. + private static List PackageInfoListToNameList( + IEnumerable packageInfos) { + var packageInfosAsStrings = new List(); + if (packageInfos != null) { + foreach (var pkg in packageInfos) packageInfosAsStrings.Add(pkg.Name); + } + return packageInfosAsStrings; + } + + /// + /// Make sure a set of package names are present in the specified list of PackageInfo objects. + /// + /// List of package names expected in the list. + /// List of PackageInfo instances to search. + /// TestCaseResult to add an error message to if the expected + /// packages aren't found in the packageInfos list. + /// String to add to the start of the error message if + /// expectedPackageNames are not in packageInfos. + private static void CheckPackageNamesInPackageInfos( + List expectedPackageNames, + IEnumerable packageInfos, + IntegrationTester.TestCaseResult testCaseResult, + string errorMessagePrefix) { + var packageNames = PackageInfoListToNameList(packageInfos); + if (!(new HashSet(packageNames)).IsSupersetOf(expectedPackageNames)) { + testCaseResult.ErrorMessages.Add(String.Format( + "{0}, package names [{1}] not found in:\n{2}\n", + errorMessagePrefix, String.Join(", ", expectedPackageNames.ToArray()), + String.Join("\n", packageNames.ToArray()))); + } + } + + /// + /// List packages installed in the project. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestListInstalledPackages( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + PackageManagerClient.ListInstalledPackages((result) => { + // Unity 2017.x doesn't install any default packages. + if (ExecutionEnvironment.VersionMajorMinor >= 2018.0) { + // Make sure a subset of the default packages installed in all a newly created + // Unity project are present in the returned list. + CheckPackageNamesInPackageInfos( + new List() { + "com.unity.modules.audio", + "com.unity.modules.physics" + }, + result.Packages, testCaseResult, "Found an unexpected set of packages"); + } + var message = String.Format( + "Error: '{0}', PackageInfos:\n{1}\n", + result.Error, + String.Join("\n", PackageInfoListToStringList(result.Packages).ToArray())); + if (!String.IsNullOrEmpty(result.Error.ToString())) { + testCaseResult.ErrorMessages.Add(message); + } else { + UnityEngine.Debug.Log(message); + } + testCaseComplete(testCaseResult); + }); + } + + /// + /// Search for all available packages. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestSearchAvailablePackagesAll( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + PackageManagerClient.SearchAvailablePackages( + (result) => { + // Make sure common optional Unity packages are returned in the search result. + if (UpmSearchAllAvailable) { + CheckPackageNamesInPackageInfos( + new List() { + "com.unity.2d.animation", + "com.unity.test-framework" + }, + result.Packages, testCaseResult, + "SearchAvailablePackages returned an unexpected set of packages"); + } + + var message = String.Format( + "Error: '{0}', PackageInfos:\n{1}\n", result.Error, + String.Join("\n", PackageInfoListToStringList(result.Packages).ToArray())); + if (!String.IsNullOrEmpty(result.Error.ToString())) { + testCaseResult.ErrorMessages.Add(message); + } else { + UnityEngine.Debug.Log(message); + } + testCaseComplete(testCaseResult); + }); + } + + /// + /// Search for a set of available packages. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestSearchAvailablePackages( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + var progressLines = new List(); + PackageManagerClient.SearchAvailablePackages( + new [] { + "com.unity.ads", + "com.unity.analytics@2.0.13" + }, + (result) => { + var expectedPackageNameByQuery = UpmAvailable ? + new Dictionary() { + {"com.unity.ads", "com.unity.ads"}, + {"com.unity.analytics@2.0.13", "com.unity.analytics"}, + } : new Dictionary(); + + // Make sure all expected queries were performed. + var queriesPerformed = new HashSet(result.Keys); + if (!queriesPerformed.SetEquals(expectedPackageNameByQuery.Keys)) { + testCaseResult.ErrorMessages.Add( + String.Format( + "Search returned a subset of queries [{0}] vs. expected [{1}]", + String.Join(", ", (new List(queriesPerformed)).ToArray()), + String.Join(", ", (new List( + expectedPackageNameByQuery.Keys)).ToArray()))); + } + + var packageResults = new List(); + foreach (var kv in result) { + var searchQuery = kv.Key; + var searchResult = kv.Value; + packageResults.Add(String.Format( + "{0}, Error: '{1}':\n{2}\n", searchQuery, searchResult.Error, + String.Join("\n", + PackageInfoListToStringList(searchResult.Packages).ToArray()))); + + if (!String.IsNullOrEmpty(searchResult.Error.ToString())) { + testCaseResult.ErrorMessages.Add( + String.Format("Failed when searching for '{0}', Error '{1}'", + searchQuery, searchResult.Error.ToString())); + } + + // Make sure returned packages match the search pattern. + string expectedPackageName; + if (expectedPackageNameByQuery.TryGetValue(searchQuery, + out expectedPackageName)) { + CheckPackageNamesInPackageInfos( + new List() { expectedPackageName }, searchResult.Packages, + testCaseResult, + String.Format("Returned an unexpected list of for search query '{0}'", + searchQuery)); + } else { + testCaseResult.ErrorMessages.Add( + String.Format("Unexpected search result returned '{0}'", searchQuery)); + } + } + + // Make sure progress was reported. + if (progressLines.Count == 0) { + testCaseResult.ErrorMessages.Add("No progress reported"); + } + + var message = String.Format(String.Join("\n", packageResults.ToArray())); + if (testCaseResult.ErrorMessages.Count == 0) { + UnityEngine.Debug.Log(message); + } else { + testCaseResult.ErrorMessages.Add(message); + } + testCaseComplete(testCaseResult); + }, + progress: (value, item) => { + progressLines.Add(String.Format("Progress: {0}: {1}", value, item)); + }); + } + + /// + /// Check a package manager change result for errors. + /// + /// Error to check. + /// Name of the changed package. + /// Expected installed / removed package name. + /// TestCaseResult to add an error message to if the result + /// indicates a failure or doesn't match the expected package name. + /// String description of the result. + private static string CheckChangeResult( + PackageManagerClient.Error error, string packageName, + string expectedPackageName, IntegrationTester.TestCaseResult testCaseResult) { + var message = String.Format("Error '{0}', Package Installed '{1}'", error, packageName); + if (!String.IsNullOrEmpty(error.ToString())) { + testCaseResult.ErrorMessages.Add(message); + } + + if (packageName != expectedPackageName) { + testCaseResult.ErrorMessages.Add(String.Format( + "Unexpected package installed '{0}' vs. '{1}', Error '{2}'", + packageName, expectedPackageName, error)); + } + return message; + } + + /// + /// Add a package. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestAddPackage( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + const string installPackage = "com.unity.analytics"; + PackageManagerClient.AddPackage( + installPackage, + (result) => { + var message = UpmAvailable ? CheckChangeResult( + result.Error, result.Package != null ? result.Package.Name : null, + installPackage, testCaseResult) : ""; + if (testCaseResult.ErrorMessages.Count == 0) { + UnityEngine.Debug.Log(message); + } + testCaseComplete(testCaseResult); + }); + } + + /// + /// Add and remove a package. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestAddAndRemovePackage( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + const string packageToModify = "com.unity.ads"; + PackageManagerClient.AddPackage( + packageToModify, + (result) => { + if (UpmAvailable) { + CheckChangeResult(result.Error, + result.Package != null ? result.Package.Name : null, + packageToModify, testCaseResult); + } + }); + + PackageManagerClient.RemovePackage( + packageToModify, + (result) => { + var message = UpmAvailable ? + CheckChangeResult(result.Error, result.PackageId, packageToModify, + testCaseResult) : ""; + if (testCaseResult.ErrorMessages.Count == 0) { + UnityEngine.Debug.Log(message); + } + testCaseComplete(testCaseResult); + }); + } +} + + +} diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTests.csproj b/source/PackageManagerResolver/test/PackageMigratorIntegrationTests.csproj new file mode 100644 index 00000000..8b99f876 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTests.csproj @@ -0,0 +1,65 @@ + + + + Debug + AnyCPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03} + Library + Google.PackageMigratorIntegrationTests + Google.PackageMigratorIntegrationTests + v2.0 + 1.2 + 12.0.0 + 2.0 + + + True + full + False + bin\Debug + DEBUG; + prompt + 4 + False + + + none + True + bin\Release + prompt + 4 + False + + + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + + + + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040} + IntegrationTester + + + {77EBE819-CBE6-4CA8-A791-ED747EA29D30} + PackageManagerResolver + + + diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTests/PackageMigratorIntegrationTests.cs b/source/PackageManagerResolver/test/PackageMigratorIntegrationTests/PackageMigratorIntegrationTests.cs new file mode 100644 index 00000000..41c7a283 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTests/PackageMigratorIntegrationTests.cs @@ -0,0 +1,212 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Remove this define when EDM with package migration is available on the Unity Package Manager. +#define EDM_WITH_MIGRATION_NOT_AVAILABLE_ON_UPM + +using System; +using System.Collections.Generic; + +using Google; + +namespace Google.PackageMigratorIntegrationTests { + +/// +/// Integration tests for PackageMigrator. +/// +public static class PackageManagerTests { + + /// + /// Initialize logging and expose GPR to UPM. + /// + [IntegrationTester.Initializer] + public static void Initialize() { + // Enable verbose logging. + PackageManagerResolver.logger.Level = LogLevel.Verbose; + + // Ensure the game package registry is added for the test. + PackageManagerResolver.UpdateManifest( + PackageManagerResolver.ManifestModificationMode.Add, + promptBeforeAction: false, + showDisableButton: false); + } + + /// + /// If UPM scoped registries aren't available, expect a task failure or report an error and + /// complete the specified test. + /// + /// Error string returned by a completed task. + /// Test case result to update. + /// Called when the test case is complete. + /// true if the task is complete, false otherwise. + private static bool CompleteIfNotAvailable( + string completionError, + IntegrationTester.TestCaseResult testCaseResult, + Action testCaseComplete) { + // If scoped registries support isn't available, expect this to fail. + if (!(PackageManagerClient.Available && + PackageManagerResolver.ScopedRegistriesSupported)) { + if (String.IsNullOrEmpty(completionError)) { + testCaseResult.ErrorMessages.Add("Expected failure but returned no error"); + } + testCaseComplete(testCaseResult); + return true; + } + return false; + } + + /// + /// Test searching for an EDM package to migrate that is the same version or newer than the + /// installed package. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestFindPackagesToMigrate( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + + // Collect progress reports. + var progressLog = new List>(); + PackageMigrator.PackageMap.FindPackagesProgressDelegate reportFindProgress = + (progressValue, description) => { + UnityEngine.Debug.Log(String.Format("Find progress {0}, {1}", progressValue, + description)); + progressLog.Add(new KeyValuePair(progressValue, description)); + }; + + PackageMigrator.PackageMap.FindPackagesToMigrate((error, packageMaps) => { + if (CompleteIfNotAvailable(error, testCaseResult, testCaseComplete)) return; + + if (!String.IsNullOrEmpty(error)) { + testCaseResult.ErrorMessages.Add(String.Format("Failed with error {0}", error)); + } + + var packageMapStrings = new List(); + var packageMapsByUpmName = new Dictionary(); + foreach (var packageMap in packageMaps) { + packageMapsByUpmName[packageMap.AvailablePackageManagerPackageInfo.Name] = + packageMap; + packageMapStrings.Add(packageMap.ToString()); + } + + // Version-less mapping of UPM package name to Version Handler package names. + var expectedVhNameAndUpmNames = new KeyValuePair[] { + new KeyValuePair("External Dependency Manager", + "com.google.external-dependency-manager"), + new KeyValuePair("Firebase Authentication", + "com.google.firebase.auth") + }; + foreach (var vhNameAndUpmName in expectedVhNameAndUpmNames) { + string expectedVhName = vhNameAndUpmName.Key; + string expectedUpmName = vhNameAndUpmName.Value; + PackageMigrator.PackageMap packageMap; + if (packageMapsByUpmName.TryGetValue(expectedUpmName, out packageMap)) { + if (packageMap.VersionHandlerPackageName != expectedVhName) { + testCaseResult.ErrorMessages.Add(String.Format( + "Unexpected Version Handler package name '{0}' vs. '{1}' for '{2}'", + packageMap.VersionHandlerPackageName, expectedVhName, + expectedUpmName)); + } + if (packageMap.AvailablePackageManagerPackageInfo.CalculateVersion() < + packageMap.VersionHandlerPackageCalculatedVersion) { + testCaseResult.ErrorMessages.Add(String.Format( + "Returned older UPM package {0} than VH package {1} for " + + "{2} --> {3}", + packageMap.AvailablePackageManagerPackageInfo.Version, + packageMap.VersionHandlerPackageVersion, expectedVhName, + expectedUpmName)); + } + } else { + testCaseResult.ErrorMessages.Add(String.Format( + "Package map {0} --> {1} not found.", expectedVhName, expectedUpmName)); + } + } + + if (packageMaps.Count != expectedVhNameAndUpmNames.Length) { + testCaseResult.ErrorMessages.Add( + String.Format("Migrator returned unexpected package maps:\n{0}", + String.Join("\n", packageMapStrings.ToArray()))); + } + + if (progressLog.Count == 0) { + testCaseResult.ErrorMessages.Add("No progress updates"); + } + testCaseComplete(testCaseResult); + }, reportFindProgress); + } + + /// + /// Test migration. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestMigration( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { +#if EDM_WITH_MIGRATION_NOT_AVAILABLE_ON_UPM + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { Skipped = true }); + return; +#endif + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + PackageMigrator.TryMigration((error) => { + if (CompleteIfNotAvailable(error, testCaseResult, testCaseComplete)) return; + if (!String.IsNullOrEmpty(error)) { + testCaseResult.ErrorMessages.Add(String.Format( + "Migration failed with error {0}", error)); + } + + // Make sure only expected version handler packages are still installed. + var expectedVersionHandlerPackages = new HashSet() { + "Firebase Realtime Database" + }; + var manifestsByPackageName = new HashSet( + VersionHandlerImpl.ManifestReferences. + FindAndReadManifestsInAssetsFolderByPackageName().Keys); + manifestsByPackageName.ExceptWith(expectedVersionHandlerPackages); + if (manifestsByPackageName.Count > 0) { + testCaseResult.ErrorMessages.Add(String.Format( + "Unexpected version handler packages found in the project:\n{0}", + (new List(manifestsByPackageName)).ToArray())); + } + + // Make sure the expected UPM packages are installed. + PackageManagerClient.ListInstalledPackages((listResult) => { + var installedPackageNames = new HashSet(); + foreach (var pkg in listResult.Packages) { + installedPackageNames.Add(pkg.Name); + } + + // Make sure expected UPM packages are installed. + var expectedPackageNames = new List() { + "com.google.external-dependency-manager", + "com.google.firebase.auth" + }; + if (installedPackageNames.IsSupersetOf(expectedPackageNames)) { + testCaseResult.ErrorMessages.Add(String.Format( + "Expected packages [{0}] not installed", + String.Join(", ", expectedPackageNames.ToArray()))); + } + + testCaseComplete(testCaseResult); + }); + }); + } +} + +} diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt new file mode 100644 index 00000000..e69de29b diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt.meta b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt.meta new file mode 100644 index 00000000..a0ee6360 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 582ec7fc4a96a07e46c1bd207510d1b8 +labels: +- gvh_version-6.13.0 +- gvh +- gvh_manifest +- gvhp_manifestname-0Firebase Authentication +- gvhp_manifestname-FirebaseAuth +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt @@ -0,0 +1 @@ + diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt.meta b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt.meta new file mode 100644 index 00000000..29a5a071 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: b99ef6d392c27dc76df26bcc8e887ba7 +labels: +- gvh_version-999.999.999 +- gvh +- gvh_manifest +- gvhp_manifestname-0Firebase Realtime Database +- gvhp_manifestname-FirebaseDatabase +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerRegistryTest.cs b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerRegistryTest.cs new file mode 100644 index 00000000..b89ea56a --- /dev/null +++ b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerRegistryTest.cs @@ -0,0 +1,183 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google.PackageManagerResolver.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + + using Google; + + /// + /// Tests the PackageManagerRegistry class. + /// + [TestFixture] + public class PackageManagerRegistryTest { + + /// + /// Construct a PackageManagerRegistry and use all accessors. + /// + [Test] + public void TestConstruct() { + var registry = new PackageManagerRegistry() { + Name = "Reg", + Url = "http://unity.reg.org/", + Scopes = new List { "org.foo.bar" }, + TermsOfService = "http://unity.reg.org/terms", + PrivacyPolicy = "http://unity.reg.org/privacy", + CreatedBy = "foo.xml:123", + CustomData = "hello world" + }; + Assert.That(registry.Name, Is.EqualTo("Reg")); + Assert.That(registry.Url, Is.EqualTo("http://unity.reg.org/")); + CollectionAssert.AreEquivalent(registry.Scopes, new List { "org.foo.bar" }); + Assert.That(registry.TermsOfService, Is.EqualTo("http://unity.reg.org/terms")); + Assert.That(registry.PrivacyPolicy, Is.EqualTo("http://unity.reg.org/privacy")); + Assert.That(registry.CreatedBy, Is.EqualTo("foo.xml:123")); + Assert.That(registry.CustomData, Is.EqualTo("hello world")); + } + + /// + /// Test object comparison and hash code. + /// + [Test] + public void TestCompareAndGetHashCode() { + var reg = new PackageManagerRegistry(); + Assert.That(reg.GetHashCode(), Is.EqualTo(0)); + + var reg1 = new PackageManagerRegistry() { + Name = "Reg", + Url = "http://reg1.org/", + Scopes = new List { "org.foo.bar" }, + TermsOfService = "http://reg1.org/terms", + PrivacyPolicy = "http://reg1.org/privacy", + CreatedBy = "foo.xml:123", + CustomData = "hello world" + }; + var reg2 = new PackageManagerRegistry() { + Name = "Reg", + Url = "http://reg1.org/", + Scopes = new List { "org.foo.bar" }, + TermsOfService = "http://reg1.org/terms", + PrivacyPolicy = "http://reg1.org/privacy", + CreatedBy = "foo.xml:123", + CustomData = "hello world" + }; + Assert.That(reg1.Equals(reg2), Is.EqualTo(true)); + Assert.That(reg1.GetHashCode(), Is.EqualTo(reg2.GetHashCode())); + + reg2.CreatedBy = "foo2.xml:111"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(true)); + Assert.That(reg1.GetHashCode(), Is.EqualTo(reg2.GetHashCode())); + + reg2.Name = "reg2"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.Name = reg1.Name; + reg2.Url = "http://reg2.org/"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.Url = reg1.Url; + reg2.TermsOfService = "http://reg2.org/terms"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.TermsOfService = reg1.TermsOfService; + reg2.PrivacyPolicy = "http://reg2.org/privacy"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.PrivacyPolicy = reg1.PrivacyPolicy; + reg2.Scopes = null; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.Scopes = new List { "org.reg2" }; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.Scopes = reg1.Scopes; + reg2.CustomData = "hello from reg2"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + } + + /// + /// Convert a PackageManagerRegistry to a string representation. + /// + [Test] + public void TestToString() { + var registry = new PackageManagerRegistry() { + Name = "Reg", + Url = "http://unity.reg.org/", + Scopes = new List { "org.foo.bar", "org.foo.baz"}, + TermsOfService = "http://unity.reg.org/terms", + CreatedBy = "foo.xml:123", + CustomData = "hello world" + }; + Assert.That(registry.ToString(), + Is.EqualTo("name: Reg, url: http://unity.reg.org, " + + "scopes: [org.foo.bar, org.foo.baz]")); + } + + /// + /// Convert a list of PackageManagerRegistry instances to a list of strings. + /// + [Test] + public void TestToStringList() { + var registries = new PackageManagerRegistry[] { + new PackageManagerRegistry() { + Name = "foo", + Url = "http://foo.com/", + Scopes = new List() { "foo.bar" }, + }, + new PackageManagerRegistry() { + Name = "bar", + Url = "http://bar.com/" + } + }; + Assert.That(PackageManagerRegistry.ToStringList(registries), + Is.EqualTo(new List() { + "name: foo, url: http://foo.com, scopes: [foo.bar]", + "name: bar, url: http://bar.com, scopes: []" + })); + } + + /// + /// Convert a list of PackageManagerRegistry instances to a string. + /// + [Test] + public void TestListToString() { + var registries = new PackageManagerRegistry[] { + new PackageManagerRegistry() { + Name = "foo", + Url = "http://foo.com/", + Scopes = new List() { "foo.bar" }, + }, + new PackageManagerRegistry() { + Name = "bar", + Url = "http://bar.com/" + } + }; + Assert.That(PackageManagerRegistry.ToString(registries), + Is.EqualTo("name: foo, url: http://foo.com, scopes: [foo.bar]\n" + + "name: bar, url: http://bar.com, scopes: []")); + } + } +} diff --git a/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerResolverTests.asmdef b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerResolverTests.asmdef new file mode 100644 index 00000000..1826febe --- /dev/null +++ b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerResolverTests.asmdef @@ -0,0 +1,25 @@ +{ + "name": "Google.PackageManagerResolverTests", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Google.VersionHandler.dll", + "Google.VersionHandlerImpl.dll", + "Google.PackageManagerResolver.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManifestModifierTest.cs b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManifestModifierTest.cs new file mode 100644 index 00000000..2a937608 --- /dev/null +++ b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManifestModifierTest.cs @@ -0,0 +1,348 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google.PackageManagerResolver.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + + using Google; + + /// + /// Tests the PackageManifestModifier class. + /// + [TestFixture] + public class PackageManifestModifierTest { + + /// + /// Object under test. + /// + PackageManifestModifier modifier; + + /// + /// Setup for the test + /// + [SetUp] + public void Setup() { + // Delete the temporary manifest if it exists. + if (File.Exists(PackageManifestModifier.MANIFEST_FILE_PATH)) { + File.Delete(PackageManifestModifier.MANIFEST_FILE_PATH); + } + + // Create a modifier that uses a logs to the system console. + modifier = new PackageManifestModifier(); + modifier.Logger.Target = LogTarget.Console; + modifier.Logger.Level = LogLevel.Debug; + } + + /// + /// Read a project manifest. + /// + private string ReadManifest() { + return File.ReadAllText(PackageManifestModifier.MANIFEST_FILE_PATH); + } + + /// + /// Write a project manifest. + /// + /// JSON string to write to the manifest file.> + private void WriteManifest(string manifest) { + var manifestDirectory = Path.GetDirectoryName( + PackageManifestModifier.MANIFEST_FILE_PATH); + if (!Directory.Exists(manifestDirectory)) Directory.CreateDirectory(manifestDirectory); + File.WriteAllText(PackageManifestModifier.MANIFEST_FILE_PATH, manifest); + } + + const string MANIFEST_SIMPLE = + "{\n" + + " \"dependencies\": {\n" + + " \"com.bar.foo\": \"1.2.3\"\n" + + " },\n" + + " \"scopedRegistries\": [\n" + + " {\n" + + " \"name\": \"A UPM Registry\",\n" + + " \"url\": \"https://unity.foobar.com/",\n" + + " \"scopes\": [\n" + + " \"foobar.unity.voxels\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + + /// + /// Test manifest with a few different registries. + /// + const string MANIFEST_MULTI_REGISTRIES = + "{\n" + + " \"scopedRegistries\": [\n" + + " {\n" + + " \"name\": \"Reg1\",\n" + + " \"url\": \"https://reg1.com/",\n" + + " \"scopes\": [\n" + + " \"com.reg1.foo\",\n" + + " \"com.reg1.bar\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"Reg1 Ext\",\n" + + " \"url\": \"https://reg1.com/",\n" + + " \"scopes\": [\n" + + " \"com.reg1.ext\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"Reg2\",\n" + + " \"url\": \"https://unity.reg2.com/",\n" + + " \"scopes\": [\n" + + " \"com.reg2.bish\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + + /// + /// Read a valid manifest. + /// + [Test] + public void TestReadManifestValid() { + WriteManifest(MANIFEST_SIMPLE); + Assert.That(modifier.GetManifestJson(), Is.Empty); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That(modifier.GetManifestJson(), Is.EqualTo(MANIFEST_SIMPLE)); + + var expected = new Dictionary() { + { + "dependencies", + new Dictionary() { + {"com.bar.foo", "1.2.3"} + } + }, + { + "scopedRegistries", + new List>() { + new Dictionary() { + { "name", "A UPM Registry" }, + { "url", "https://unity.foobar.com/" }, + { + "scopes", + new List() { "foobar.unity.voxels" } + } + } + } + } + }; + CollectionAssert.AreEquivalent(modifier.manifestDict, expected); + } + + /// + /// Copy constructor + /// + [Test] + public void TestCopyConstructor() { + WriteManifest(MANIFEST_SIMPLE); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + + PackageManifestModifier copy = new PackageManifestModifier(modifier); + + CollectionAssert.AreEquivalent(copy.manifestDict, modifier.manifestDict); + Assert.That(copy.GetManifestJson(), Is.EqualTo(MANIFEST_SIMPLE)); + Assert.That(copy.GetManifestJson(), Is.EqualTo(modifier.GetManifestJson())); + } + + /// + /// Try reading a manifest that doesn't exist. + /// + [Test] + public void TestReadManifestMissing() { + Assert.That(modifier.ReadManifest(), Is.EqualTo(false)); + } + + /// + /// Try reading a manifest with invalid JSON. + /// + [Test] + public void TestReadManifestInvalid() { + WriteManifest("This is not valid JSON"); + Assert.That(modifier.ReadManifest(), Is.EqualTo(false)); + } + + /// + /// Try to retrieve registries from a modifier that hasn't read a manifest. + /// + [Test] + public void TestPackageManagerRegistriesWithNoManifestLoaded() { + Assert.That(modifier.PackageManagerRegistries.Count, Is.EqualTo(0)); + } + + /// + /// Parse registries from a manifest. + /// + [Test] + public void TestPackageManagerRegistries() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + + var registries = modifier.PackageManagerRegistries; + + Assert.That(registries.ContainsKey("https://reg1.com/"), Is.EqualTo(true)); + Assert.That(registries.ContainsKey("https://unity.reg2.com/"), Is.EqualTo(true)); + + var reg1 = registries["https://reg1.com/"]; + Assert.That(reg1.Count, Is.EqualTo(2)); + Assert.That(reg1[0].Name, Is.EqualTo("Reg1")); + Assert.That(reg1[0].Url, Is.EqualTo("https://reg1.com/")); + CollectionAssert.AreEquivalent(reg1[0].Scopes, + new List() { "com.reg1.foo", "com.reg1.bar" }); + Assert.That(reg1[1].Name, Is.EqualTo("Reg1 Ext")); + Assert.That(reg1[1].Url, Is.EqualTo("https://reg1.com/")); + CollectionAssert.AreEquivalent( + reg1[1].Scopes, new List() { "com.reg1.ext" }); + + var reg2 = registries["https://unity.reg2.com/"]; + Assert.That(reg2.Count, Is.EqualTo(1)); + Assert.That(reg2[0].Name, Is.EqualTo("Reg2")); + Assert.That(reg2[0].Url, Is.EqualTo("https://unity.reg2.com/")); + CollectionAssert.AreEquivalent(reg2[0].Scopes, new List() { "com.reg2.bish" }); + } + + /// + /// Try adding registries when a manifest isn't loaded. + /// + [Test] + public void TestAddRegistriesWithNoManifestLoaded() { + Assert.That(modifier.AddRegistries(new List()), + Is.EqualTo(false)); + } + + /// + /// Add no registries to a manifest and write out the results. + /// + [Test] + public void TestAddRegistriesEmptyAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That(modifier.AddRegistries(new List()), + Is.EqualTo(true)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That(ReadManifest(), Is.EqualTo(MANIFEST_MULTI_REGISTRIES)); + } + + /// + /// Add some registries to the manifest and write out the results. + /// + [Test] + public void TestAddRegistriesAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That( + modifier.AddRegistries(new PackageManagerRegistry[] { + new PackageManagerRegistry() { + Name = "Reg1", + Url = "https://reg1.com/", + Scopes = new List() { "com.reg1.foo", "com.reg1.bar" } + }, + new PackageManagerRegistry() { + Name = "Reg1 Ext", + Url = "https://reg1.com/", + Scopes = new List() { "com.reg1.ext" } + }, + new PackageManagerRegistry() { + Name = "Reg2", + Url = "https://unity.reg2.com/", + Scopes = new List() { "com.reg2.bish" } + } + }), + Is.EqualTo(true)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That(ReadManifest(), Is.EqualTo(MANIFEST_MULTI_REGISTRIES)); + } + + /// + /// Try removing registries when the manifest isn't loaded. + /// + [Test] + public void TestRemoveRegistriesWithNoManifestLoader() { + Assert.That(modifier.RemoveRegistries(new List()), + Is.EqualTo(false)); + } + + /// + /// Remove no registries from a manifest and write out the results. + /// + [Test] + public void TestRemoveRegistriesEmptyAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That(modifier.RemoveRegistries(new List()), + Is.EqualTo(true)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That(ReadManifest(), Is.EqualTo(MANIFEST_MULTI_REGISTRIES)); + } + + /// + /// Remove non-existent registries from a manifest and write out the results. + /// + [Test] + public void TestRemoveRegistriesNonExistentAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That( + modifier.RemoveRegistries(new PackageManagerRegistry[] { + new PackageManagerRegistry { + Name = "Texture It", + Url = "http://unity.cooltextures.org/", + Scopes = new List() { "org.cooltextures.textureit" } + } + }), + Is.EqualTo(false)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That(ReadManifest(), Is.EqualTo(MANIFEST_MULTI_REGISTRIES)); + } + + /// + /// Remove registries from a manifest and write out the results. + /// + [Test] + public void TestRemoveRegistriesAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That( + modifier.RemoveRegistries(new PackageManagerRegistry[] { + new PackageManagerRegistry() { + Name = "Reg1 Ext", + Url = "https://reg1.com/", + Scopes = new List() { "com.reg1.ext" } + }, + }), + Is.EqualTo(true)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That( + ReadManifest(), + Is.EqualTo("{\n" + + " \"scopedRegistries\": [\n" + + " {\n" + + " \"name\": \"Reg2\",\n" + + " \"url\": \"https://unity.reg2.com/",\n" + + " \"scopes\": [\n" + + " \"com.reg2.bish\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}")); + } + } +} diff --git a/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/XmlPackageManagerRegistriesTest.cs b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/XmlPackageManagerRegistriesTest.cs new file mode 100644 index 00000000..3530b0d1 --- /dev/null +++ b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/XmlPackageManagerRegistriesTest.cs @@ -0,0 +1,231 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google.PackageManagerResolver.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + + using Google; + + /// + /// Tests the PackageManagerRegistry class. + /// + [TestFixture] + public class XmlPackageManagerRegistriesTest { + + /// + /// Name of the test configuration file. + /// + const string TEST_CONFIGURATION_FILENAME = "TestRegistries.xml"; + + /// + /// Object under test. + /// + private XmlPackageManagerRegistries registries; + + /// + /// Logger for this test. + /// + private Logger logger = new Logger() { + Target = LogTarget.Console, + Level = LogLevel.Debug + }; + + /// + /// Write to the test registries file. + /// + /// String to write to the file. + private void WriteRegistries(string configuration) { + if (File.Exists(TEST_CONFIGURATION_FILENAME)) File.Delete(TEST_CONFIGURATION_FILENAME); + File.WriteAllText(TEST_CONFIGURATION_FILENAME, configuration); + } + + /// + /// Setup for the test + /// + [SetUp] + public void Setup() { + registries = new XmlPackageManagerRegistries(); + } + + /// + /// Make sure that a constructed object is empty. + /// + [Test] + public void TestRegistriesEmpty() { + Assert.That(registries.Registries.Count, Is.EqualTo(0)); + } + + /// + /// Add some items to the registries, clear and validate it's empty. + /// + [Test] + public void TestClear() { + registries.Registries["http://foo.bar.com/"] = new PackageManagerRegistry() { + Name = "foobar", + Url = "http://foo.bar.com/", + Scopes = new List() { "com.bar" } + }; + Assert.That(registries.Registries.Count, Is.EqualTo(1)); + registries.Clear(); + Assert.That(registries.Registries.Count, Is.EqualTo(0)); + } + + /// + /// Determine whether a filename is a container of UPM registries. + /// + [Test] + public void TestIsRegistriesFile() { + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/SomeRegistries.xml"), + Is.EqualTo(false)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/MyPlugin/SomeRegistries.xml"), + Is.EqualTo(false)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/Editor/SomeRegistries.txt"), + Is.EqualTo(false)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/Editor/SomeRegistries.xml"), + Is.EqualTo(true)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets\\Editor\\SomeRegistries.xml"), + Is.EqualTo(true)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/MyPlugin/Editor/SomeRegistries.xml"), + Is.EqualTo(true)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets\\MyPlugin\\Editor\\SomeRegistries.xml"), + Is.EqualTo(true)); + } + + /// + /// Test Read() with a valid XML file. + /// + [Test] + public void TestRead() { + WriteRegistries("\n" + + " \n" + + " \n" + + " com.reg1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " com.reg2.foo\n" + + " com.reg2.bar\n" + + " \n" + + " \n" + + "\n"); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(true)); + Assert.That(registries.Registries.Count, Is.EqualTo(2)); + CollectionAssert.AreEquivalent(registries.Registries.Keys, + new [] { "https://reg1.com/", "https://reg2.com/"}); + var reg1 = registries.Registries["https://reg1.com/"]; + Assert.That(reg1.Name, Is.EqualTo("Reg1")); + Assert.That(reg1.Url, Is.EqualTo("https://reg1.com/")); + Assert.That(reg1.TermsOfService, Is.EqualTo("https://reg1.com/terms")); + Assert.That(reg1.PrivacyPolicy, Is.EqualTo("https://reg1.com/privacy")); + CollectionAssert.AreEquivalent(reg1.Scopes, new [] { "com.reg1" } ); + var reg2 = registries.Registries["https://reg2.com/"]; + Assert.That(reg2.Name, Is.EqualTo("Reg2")); + Assert.That(reg2.Url, Is.EqualTo("https://reg2.com/")); + Assert.That(reg2.TermsOfService, Is.EqualTo("")); + CollectionAssert.AreEquivalent(reg2.Scopes, new [] { "com.reg2.foo", "com.reg2.bar" } ); + } + + /// + /// Test Read() with a configuration that uses the same registry URL multiple times. + /// + [Test] + public void TestReadDuplicateUrl() { + WriteRegistries("\n" + + " \n" + + " \n" + + " com.reg1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " com.reg1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " com.reg1.foobar\n" + + " \n" + + " \n" + + "\n"); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(true)); + Assert.That(registries.Registries.Count, Is.EqualTo(1)); + CollectionAssert.AreEquivalent(registries.Registries.Keys, + new [] { "https://reg1.com/" }); + var reg1 = registries.Registries["https://reg1.com/"]; + Assert.That(reg1.Name, Is.EqualTo("Reg1")); + Assert.That(reg1.Url, Is.EqualTo("https://reg1.com/")); + Assert.That(reg1.TermsOfService, Is.EqualTo("https://reg1.com/terms")); + CollectionAssert.AreEquivalent(reg1.Scopes, new [] { "com.reg1" } ); + } + + /// + /// Try reading a malformed configuration files. + /// + [Test] + public void TestReadBrokenConfigs() { + // Not XML. + WriteRegistries("this is not xml"); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + + // Invalid tag. + WriteRegistries(""); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + + // Missing url attribute. + WriteRegistries(""); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + + // Missing scopes block and scope entries. + WriteRegistries("\n" + + " \n" + + " \n" + + ""); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + + // Missing scope entries. + WriteRegistries("\n" + + " \n" + + " \n" + + " \n" + + ""); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + } + } +} diff --git a/source/PackageManagerTests/PackageManagerTests.csproj b/source/PackageManagerTests/PackageManagerTests.csproj deleted file mode 100644 index ee9608a7..00000000 --- a/source/PackageManagerTests/PackageManagerTests.csproj +++ /dev/null @@ -1,70 +0,0 @@ - - - - Debug - AnyCPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2} - Library - PackageManagerTests - PackageManagerTests - v2.0 - 1.2 - 12.0.0 - 2.0 - - - True - full - False - bin\Debug - DEBUG; - prompt - 4 - False - - - none - True - bin\Release - prompt - 4 - False - - - ..\packages\NUnit.2.6.3\lib\ - - - - - - $(NUnityHintPath)/nunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - - {8B0A2564-01ED-426B-AF33-33EED4A81828} - PackageManager - - - - - - diff --git a/source/PackageManagerTests/packages.config b/source/PackageManagerTests/packages.config deleted file mode 100644 index d4e241a2..00000000 --- a/source/PackageManagerTests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/source/PackageManagerTests/src/Google.PackageManager.Tests/ControllerTests.cs b/source/PackageManagerTests/src/Google.PackageManager.Tests/ControllerTests.cs deleted file mode 100644 index 1bec5249..00000000 --- a/source/PackageManagerTests/src/Google.PackageManager.Tests/ControllerTests.cs +++ /dev/null @@ -1,339 +0,0 @@ -// -// Copyright (C) 2014 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager.Tests { - using System.IO; - using PackageManager; - using NUnit.Framework; - using System.Collections.Generic; - using System; - - internal static class TestData { - public class MockEditorPrefs : IEditorPrefs { - public Dictionary data; - - public MockEditorPrefs() { - data = new Dictionary(); - } - - public void DeleteAll() { - data.Clear(); - } - - public void DeleteKey(string key) { - data.Remove(key); - } - - string GetValue(string key, object defaultValue) { - string tmp; - if (data.TryGetValue(key, out tmp)) { - return tmp; - } - return string.Format("{0}", defaultValue); - } - - public bool GetBool(string key, bool defaultValue = false) { - return bool.Parse(GetValue(key, defaultValue)); - } - - public float GetFloat(string key, float defaultValue = 0) { - return float.Parse(GetValue(key, defaultValue)); - } - - public int GetInt(string key, int defaultValue = 0) { - return int.Parse(GetValue(key, defaultValue)); - } - - public string GetString(string key, string defaultValue = "") { - return GetValue(key, defaultValue); - } - - public bool HasKey(string key) { - return data.ContainsKey(key); - } - - void SetValue(string key, object value) { - if (data.ContainsKey(key)) { - data[key] = string.Format("{0}", value); - } else { - data.Add(key, string.Format("{0}", value)); - } - } - - public void SetBool(string key, bool value) { - SetValue(key, value); - } - - public void SetFloat(string key, float value) { - SetValue(key, value); - } - - public void SetInt(string key, int value) { - SetValue(key, value); - } - - public void SetString(string key, string value) { - SetValue(key, value); - } - } - - public static IEditorPrefs editorPrefs = new MockEditorPrefs(); - - // Path to test data, contains a mock data. - public static string PATH { - get { - var dir = Environment.GetEnvironmentVariable("TEST_DATA_DIR"); - return dir != null ? dir : "../../testData"; - } - } - - public class MockFetcher : IUriDataFetcher { - string textResult; - ResponseCode rc; - public MockFetcher(string giveText, ResponseCode giveResponse) { - textResult = giveText; - rc = giveResponse; - } - - public ResponseCode BlockingFetchAsString(Uri uri, out string result) { - result = textResult; - return rc; - } - - public ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result) { - throw new NotImplementedException(); - } - } - - public class MockMultiFetcher : IUriDataFetcher { - List textResults = new List(); - List responses = new List(); - public int currentIndex = 0; - - public void ResetIndex() { - currentIndex = 0; - } - - public void AddResponse(string giveText, ResponseCode giveResponse) { - textResults.Add(giveText); - responses.Add(giveResponse); - } - - public ResponseCode BlockingFetchAsString(Uri uri, out string result) { - result = textResults[currentIndex]; - var responseCode = responses[currentIndex]; - ++currentIndex; - if (currentIndex >= textResults.Count) { - throw new Exception("TEST CASE EXCEPTION - Multi Fetch exceeded index"); - } - return responseCode; - } - - public ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result) { - throw new NotImplementedException(); - } - } - - public class MockDeterministicFetcher : IUriDataFetcher { - Dictionary urlToXml = new Dictionary(); - Dictionary uriToResponse = new Dictionary(); - public void AddResponse(string uri, string giveText, ResponseCode giveResponse) { - if (!urlToXml.ContainsKey(uri)) { - urlToXml.Add(uri, giveText); - } else { - urlToXml[uri] = giveText; - } - if (!uriToResponse.ContainsKey(uri)) { - uriToResponse.Add(uri, giveResponse); - } else { - uriToResponse[uri] = giveResponse; - } - } - public ResponseCode BlockingFetchAsString(Uri uri, out string result) { - if (urlToXml.TryGetValue(uri.AbsoluteUri, out result)) { - ResponseCode r; - uriToResponse.TryGetValue(uri.AbsoluteUri, out r); - Console.WriteLine(string.Format("MockDeterministicFetcher:\nASK:{0}" + - "\nRESP:{1}\nDATA:{2}", uri, r, result)); - return r; - } - result = null; - return ResponseCode.FETCH_ERROR; - } - - public ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result) { - throw new NotImplementedException(); - } - } - } - - /// - /// Test case set that exercises the PackageManagerController. - /// - [TestFixture] - public class ControllerTests { - TestData.MockDeterministicFetcher mockFetcher = new TestData.MockDeterministicFetcher(); - - [SetUp] - public void Setup() { - UnityController.SwapEditorPrefs(new TestData.MockEditorPrefs()); - LoggingController.testing = true; - TestableConstants.testcase = true; - TestableConstants.DefaultRegistryLocation = - new Uri(Path.GetFullPath( - Path.Combine(TestData.PATH, "registry/registry.xml"))) - .AbsoluteUri; - - string testRegXmlPath = TestableConstants.DefaultRegistryLocation; - mockFetcher.AddResponse((new Uri(testRegXmlPath)).AbsoluteUri, - File.ReadAllText((new Uri(testRegXmlPath)).AbsolutePath), - ResponseCode.FETCH_COMPLETE); - var rb = new RegistryManagerController.RegistryDatabase(); - rb.registryLocation.Add(TestableConstants.DefaultRegistryLocation); - UnityController.EditorPrefs.SetString(Constants.KEY_REGISTRIES, - rb.SerializeToXMLString()); - - var u = new Uri(Path.GetFullPath(Path.Combine(TestData.PATH,"registry2/registry.xml"))); - mockFetcher.AddResponse(u.AbsoluteUri, - File.ReadAllText(u.AbsolutePath), - ResponseCode.FETCH_COMPLETE); - - UriDataFetchController.SwapUriDataFetcher(mockFetcher); - UnityController.SwapEditorPrefs(TestData.editorPrefs); - - var rdb = new RegistryManagerController.RegistryDatabase(); - rdb.registryLocation.Add(TestableConstants.DefaultRegistryLocation); - - /// ISO 8601 format: yyyy-MM-ddTHH:mm:ssZ - rdb.lastUpdate = DateTime.UtcNow.ToString("o"); // "o" = ISO 8601 formatting - Console.Write(rdb.SerializeToXMLString()); - TestData.editorPrefs.SetString(Constants.KEY_REGISTRIES, rdb.SerializeToXMLString()); - - } - - /// - /// Tests the settings controller by toggleing the boolean settings and - /// ensuring the DownloadCache location is set by default. - /// - [Test] - public void TestSettingsController() { - // DowloadCachePath should start with a value (system dependent). - Assert.NotNull(SettingsController.DownloadCachePath, - "Why is download cache path null?"); - - // Set and Get test. - SettingsController.DownloadCachePath = Path.GetFullPath(TestData.PATH); - Assert.NotNull(SettingsController.DownloadCachePath, - "Why is download cache path null?"); - Assert.AreEqual(Path.GetFullPath(TestData.PATH), SettingsController.DownloadCachePath, - "Why is download cache path different?"); - - // Verbose logging Get/Set. - Assert.IsTrue(SettingsController.VerboseLogging, "VerboseLogging to start true."); - SettingsController.VerboseLogging = false; - Assert.IsFalse(SettingsController.VerboseLogging); - - // Show install files Get/Set. - Assert.IsTrue(SettingsController.ShowInstallFiles, "ShowInstallFiles to start true"); - SettingsController.ShowInstallFiles = false; - Assert.IsFalse(SettingsController.ShowInstallFiles); - } - - [Test] - public void TestRegistryManagerController() { - Assert.AreEqual(1, RegistryManagerController.AllWrappedRegistries.Count); - - var u = new Uri(Path.GetFullPath(Path.Combine(TestData.PATH, - "registry2/registry.xml"))); - Assert.AreEqual(ResponseCode.REGISTRY_ADDED, - RegistryManagerController.AddRegistry(u)); - Assert.AreEqual(2, RegistryManagerController.AllWrappedRegistries.Count); - - // Cannot add same uri. - Assert.AreEqual(ResponseCode.REGISTRY_ALREADY_PRESENT, - RegistryManagerController.AddRegistry(u)); - - // loading the database again should still be in same state - RegistryManagerController.LoadRegistryDatabase(); - Assert.AreEqual(2,RegistryManagerController.AllWrappedRegistries.Count); - - Assert.AreEqual(ResponseCode.REGISTRY_REMOVED, - RegistryManagerController.RemoveRegistry(u)); - - // Can't remove it a second time it won't be there. - Assert.AreEqual(ResponseCode.REGISTRY_NOT_FOUND, - RegistryManagerController.RemoveRegistry(u)); - } - - [Test] - public void TestPluginManagerController() { - RegistryManagerController.LoadRegistryDatabase(); - Assert.AreEqual(1,RegistryManagerController.AllWrappedRegistries.Count); - RegistryWrapper r = RegistryManagerController.AllWrappedRegistries[0]; - - var u = new Uri(Path.GetFullPath(Path.Combine( - TestData.PATH, "registry/com.google.unity.example/package-manifest.xml"))); - mockFetcher.AddResponse(u.AbsoluteUri, - File.ReadAllText(u.AbsolutePath), - ResponseCode.FETCH_COMPLETE); - - u = new Uri(Path.GetFullPath(Path.Combine( - TestData.PATH, - "registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml"))); - mockFetcher.AddResponse(u.AbsoluteUri, - File.ReadAllText(u.AbsolutePath), - ResponseCode.FETCH_COMPLETE); - - // Test ChangeRegistryUriIntoModuleUri. - var regU = new Uri(TestableConstants.DefaultRegistryLocation); - var modName = "apples-oranges"; - var metaLoc = PluginManagerController.ChangeRegistryUriIntoModuleUri(regU, modName); - Assert.IsTrue(metaLoc.AbsoluteUri.Contains(modName)); - Assert.IsTrue(metaLoc.AbsoluteUri.Contains(Constants.MANIFEST_FILE_NAME)); - - // Test GetPluginForRegistry. - var plugins = PluginManagerController.GetPluginsForRegistry(r, true); - Assert.AreEqual(1, plugins.Count); - var packagedPlugin = plugins[0]; - Assert.NotNull(packagedPlugin); - Assert.AreEqual(r.Model, packagedPlugin.ParentRegistry); - - // Test GenerateDescriptionUri. - var d = PluginManagerController.GenerateDescriptionUri(metaLoc, - packagedPlugin.MetaData); - Assert.IsTrue(d.AbsoluteUri.Contains(packagedPlugin.MetaData.artifactId)); - Assert.IsTrue(d.AbsoluteUri.Contains(packagedPlugin.MetaData.versioning.release)); - Assert.IsTrue(d.AbsoluteUri.Contains(Constants.DESCRIPTION_FILE_NAME)); - - plugins = PluginManagerController.GetPluginsForRegistry(null); - Assert.AreEqual(0, plugins.Count); - - // Test Refresh. - PluginManagerController.Refresh(r); - plugins = PluginManagerController.GetPluginsForRegistry(r); - Assert.AreEqual(1, plugins.Count); - packagedPlugin = plugins[0]; - Assert.NotNull(packagedPlugin); - Assert.AreEqual(r.Model, packagedPlugin.ParentRegistry); - } - - [Test] - public void TestProjectManagerController() { - // TODO - test refresh list of assets - // TODO - test IsPluginInstalledInProject - - } - } -} diff --git a/source/PackageManagerTests/src/Google.PackageManager.Tests/ModelTests.cs b/source/PackageManagerTests/src/Google.PackageManager.Tests/ModelTests.cs deleted file mode 100644 index dd81f9df..00000000 --- a/source/PackageManagerTests/src/Google.PackageManager.Tests/ModelTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (C) 2014 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager.Tests { - using System.IO; - using PackageManager; - using NUnit.Framework; - using System; - - [TestFixture] - public class PackageManagerModelTests { - /// - /// Tests root models are able to load from file. - /// Root models include: - /// - Repository - /// - Description - /// - PluginMetaData - /// - PackageExportSettings - /// - [Test] - public void TestLoadFromFile() { - string registryPath = Path.Combine(TestData.PATH,"registry/registry.xml"); - Registry registry = Registry.LoadFromFile(registryPath); - Assert.AreEqual("registry.google.unity",registry.groupId); - Assert.AreEqual("jarresolver-google-registry",registry.artifactId); - Assert.AreEqual("0.0.1.1",registry.version); - Assert.AreEqual(1482171836,registry.lastUpdated); - Assert.NotNull(registry.modules); - Assert.AreEqual(1,registry.modules.module.Count); - Assert.AreEqual("com.google.unity.example",registry.modules.module[0]); - - string barPluginPath = Path.Combine(TestData.PATH, - "registry/com.google.unity.example/package-manifest.xml"); - PluginMetaData pluginMetaData = PluginMetaData.LoadFromFile(barPluginPath); - Assert.AreEqual("com.google.unity.example",pluginMetaData.groupId); - Assert.AreEqual("gpm-example-plugin",pluginMetaData.artifactId); - Assert.AreEqual("unitypackage",pluginMetaData.packaging); - Assert.NotNull(pluginMetaData.versioning); - Assert.AreEqual("1.0.0.0",pluginMetaData.versioning.release); - Assert.NotNull(pluginMetaData.versioning.versions); - Assert.AreEqual(1,pluginMetaData.versioning.versions.Count); - Assert.AreEqual(0,pluginMetaData.lastUpdated); - - string barDescriptionPath = Path.Combine(TestData.PATH, - "registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml"); - PluginDescription description = PluginDescription.LoadFromFile(barDescriptionPath); - Assert.NotNull(description.languages); - Assert.AreEqual(1,description.languages.Count); - } - - [Test] - public void TestPackageDependencies() { - var pd = new PackageDependencies(); - pd.groupId = "my.group.id"; - pd.artifactId = "my-artifact-id"; - - var dep = new AndroidPackageDependency(); - dep.group = "com.google.android.gms"; - dep.artifact = "play-services-ads"; - dep.version = "LATEST"; - - var arg = new DependencyArgument(); - arg.packageIds.Add("extra-google-m2repository"); - arg.packageIds.Add("extra-google-m2repository"); - arg.repositories.Add("some-repository"); - - dep.args = arg; - pd.androidDependencies.Add(dep); - - var xml = pd.SerializeToXMLString(); - // these can come back out of order on inflate - so we remove them - xml = xml.Replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance/"",""); - xml = xml.Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema/"",""); - Console.WriteLine("Actual: "+xml+"\n\n"); - xml = xml.Substring(1); // strip BOM for test - var expectedXml = File.ReadAllText( - Path.Combine( - Path.Combine( - Path.GetFullPath(TestData.PATH), - "flatdeps"), - "group.id.example-artifact.gpm.deps.xml")); - // these can come back out of order on inflate - so we remove them - expectedXml = expectedXml.Replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance/"",""); - expectedXml = expectedXml.Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema/"",""); - Console.WriteLine("Expected: " + xml + "\n\n"); - Assert.AreEqual(expectedXml,xml); - } - } -} diff --git a/source/PackageManagerTests/testData/editor/PackageManagerPackager.cfg.xml b/source/PackageManagerTests/testData/editor/PackageManagerPackager.cfg.xml deleted file mode 100644 index 3442c869..00000000 --- a/source/PackageManagerTests/testData/editor/PackageManagerPackager.cfg.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - Krispy Bar Plugin - A bar as a plugin - Woot some description goes here - - - - Bar Plugin - krispy.co.bar - bar-plugin - 1.0.0.0 - GPM:krispy.co.bar:bar-plugin:1.0.0.0 - - - Assets/BarPlugin - Assets/BarPlugin/Editor - Assets/BarPlugin/Editor/BarGoogleDeps.cs - Assets/BarPlugin/Editor/SomeScript.cs - - - - - - - - Google Unity Package Manager - A package manager for plugins that make use of the JarResolver - A complete package management solution for developers who make use of the - JarResolver. Provides discoverability, install & clean removal - of plugins. - - - - Package Manager Plugin - com.google.unity - package-manager - 1.0.0.0 - GPM:com.google.unity:package-manager:1.0.0.0 - - - Assets/Editor - Assets/Editor/PackageManager.cs - Assets/Editor/PackageManagerModels.cs - Assets/Editor/PackageManagerPackager.cs - Assets/PlayServicesResolver - Assets/PlayServicesResolver/Editor - Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll - Assets/PlayServicesResolver/Editor/Google.JarResolver.dll - Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll - Assets/PlayServicesResolver/Editor/play-services-resolver.txt - - - - - diff --git a/source/PackageManagerTests/testData/flatdeps/group.id.example-artifact.gpm.deps.xml b/source/PackageManagerTests/testData/flatdeps/group.id.example-artifact.gpm.deps.xml deleted file mode 100644 index 06ce2d1f..00000000 --- a/source/PackageManagerTests/testData/flatdeps/group.id.example-artifact.gpm.deps.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - my.group.id - my-artifact-id - - - com.google.android.gms - play-services-ads - LATEST - - - extra-google-m2repository - extra-google-m2repository - - - some-repository - - - - - - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml b/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml deleted file mode 100755 index f620ecb1..00000000 --- a/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - GPM Example Plugin - A demonstration of a Google Package Manager compatable plugin. - This plugin demonstrates how a Unity plugin can be created with -the Google Package Manager Packager and be available from a Google -Package Manager registry. - - - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/gpm-example-plugin.unitypackage b/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/gpm-example-plugin.unitypackage deleted file mode 100755 index b89606e1..00000000 Binary files a/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/gpm-example-plugin.unitypackage and /dev/null differ diff --git a/source/PackageManagerTests/testData/registry/com.google.unity.example/package-manifest.xml b/source/PackageManagerTests/testData/registry/com.google.unity.example/package-manifest.xml deleted file mode 100755 index a69e93e2..00000000 --- a/source/PackageManagerTests/testData/registry/com.google.unity.example/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.example - gpm-example-plugin - 1.0.0.0 - unitypackage - - 1.0.0.0 - - 1.0.0.0 - - - 0 - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry/registry.xml b/source/PackageManagerTests/testData/registry/registry.xml deleted file mode 100755 index f82f2ee5..00000000 --- a/source/PackageManagerTests/testData/registry/registry.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - registry.google.unity - jarresolver-google-registry - 0.0.1.1 - 1482171836 - - com.google.unity.example - - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/description.xml b/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/description.xml deleted file mode 100755 index 9bfd8717..00000000 --- a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/description.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Example2 Plugin - A demonstration of a Package Manager compatable plugin. - This plugin demonstrates how a Unity plugin can be created with -the Package Manager Packager and be available from a Google -Package Manager registry. - - - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/gpm-example2-plugin.unitypackage b/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/gpm-example2-plugin.unitypackage deleted file mode 100755 index b89606e1..00000000 Binary files a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/gpm-example2-plugin.unitypackage and /dev/null differ diff --git a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/package-manifest.xml b/source/PackageManagerTests/testData/registry2/com.google.unity.example2/package-manifest.xml deleted file mode 100755 index 3f5ccec1..00000000 --- a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.example2 - gpm-example2-plugin - 4.3.2.1 - unitypackage - - 4.3.2.1 - - 4.3.2.1 - - - 0 - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry2/registry.xml b/source/PackageManagerTests/testData/registry2/registry.xml deleted file mode 100755 index 5704d477..00000000 --- a/source/PackageManagerTests/testData/registry2/registry.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - registry2.google.unity - test-sample-registry - 2.0.1.1 - 1482883127 - - com.google.unity.example2 - - \ No newline at end of file diff --git a/source/PlayServicesResolver/src/AlertModal.cs b/source/PlayServicesResolver/src/AlertModal.cs deleted file mode 100644 index b9bde4a3..00000000 --- a/source/PlayServicesResolver/src/AlertModal.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Runtime.Remoting.Messaging; -using UnityEngine; - -namespace GooglePlayServices { - using System; - using UnityEditor; - - /// - /// A fluid wrapper around the EditorUtility.DisplayDialogue - /// interface. - /// - public class AlertModal { - private const string DEFAULT_EMPTY = ""; - private const string DEFAULT_OK = "Yes"; - private const string DEFAULT_CANCEL = "No"; - private static Action DefaultEmptyAction = () => { }; - - public class LabeledAction { - public string Label { get; set; } - public Action DelegateAction { get; set; } - } - - /// - /// Add a title to your Dialog box - /// - public string Title { get; set; } - - /// - /// Add a message to your Dialog box - /// - public string Message { get; set; } - - /// - /// The text and action to associate with the "ok" button. - /// - public LabeledAction Ok { get; set; } - - /// - /// The text and action to associate with the "cancel" button. - /// - public LabeledAction Cancel { get; set; } - - /// - /// The text and action to associate with the "alt" button. - /// If this property is not specified, a two button display - /// will be used. - /// - public LabeledAction Alt { get; set; } - - /// - /// Constructor for the DialogBuilder sets defaults - /// for required fields. - /// - public AlertModal() { - Title = DEFAULT_EMPTY; - Message = DEFAULT_EMPTY; - Ok = new LabeledAction { - Label = DEFAULT_OK, - DelegateAction = DefaultEmptyAction - }; - Cancel = new LabeledAction { - Label = DEFAULT_CANCEL, - DelegateAction = DefaultEmptyAction - }; - } - - /// - /// Display the window for the user's input. If no "alt" button is - /// specified, display a normal DisplayDialog, otherwise use a - /// DisplayDialogComplex - /// - public void Display() { - if (Alt == null) { - DisplaySimple(); - } - else { - DisplayComplex(); - } - } - - /// - /// Display a ComplexDialog with title, message, - /// and 3 buttons - ok, cancel, and alt. - /// - private void DisplayComplex() { - int option = EditorUtility.DisplayDialogComplex(Title, Message, Ok.Label, - Cancel.Label, Alt.Label); - - switch (option) { - // Ok option (perform action in the affirmative) - case 0: - Ok.DelegateAction(); - break; - // Cancel option (whatever the negative is) - case 1: - Cancel.DelegateAction(); - break; - // Alt option (whatever the third option you intended is) - case 2: - Alt.DelegateAction(); - break; - } - } - - /// - /// Display a simple Dialog with a title, message, and - /// two buttons - ok and cancel. - /// - private void DisplaySimple() { - bool option = EditorUtility.DisplayDialog(Title, Message, Ok.Label, Cancel.Label); - - if (option) { - Ok.DelegateAction(); - } - else { - Cancel.DelegateAction(); - } - } - } -} \ No newline at end of file diff --git a/source/PlayServicesResolver/src/GradleTemplateResolver.cs b/source/PlayServicesResolver/src/GradleTemplateResolver.cs deleted file mode 100644 index 4b3f554b..00000000 --- a/source/PlayServicesResolver/src/GradleTemplateResolver.cs +++ /dev/null @@ -1,350 +0,0 @@ -// -// Copyright (C) 2019 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace GooglePlayServices { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text.RegularExpressions; - - using Google; - using Google.JarResolver; - - using UnityEditor; - - /// - /// Resolver which simply injects dependencies into a gradle template file. - /// - internal class GradleTemplateResolver { - - /// - /// Path of the Gradle template file. - /// - public static string GradleTemplatePath = - Path.Combine(SettingsDialog.AndroidPluginsDir, "mainTemplate.gradle"); - - /// - /// Line that indicates the start of the injected repos block in the template. - /// - private const string ReposStartLine = "// Android Resolver Repos Start"; - - /// - /// Line that indicates the end of the injected repos block in the template. - /// - private const string ReposEndLine = "// Android Resolver Repos End"; - - /// - /// Line that indicates where to initially inject repos in the default template. - /// - private const string ReposInjectionLine = - @".*apply plugin: 'com\.android\.(application|library)'.*"; - - /// - /// 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 +{"; - - /// - /// Copy srcaar files to aar files that are excluded from Unity's build process. - /// - /// Dependencies to inject. - /// true if successful, false otherwise. - private static bool CopySrcAars(ICollection dependencies) { - bool succeeded = true; - var aarFiles = new List(); - // Copy each .srcaar file to .aar while configuring the plugin importer to ignore the - // file. - foreach (var aar in LocalMavenRepository.FindAarsInLocalRepos(dependencies)) { - var dir = Path.GetDirectoryName(aar); - var filename = Path.GetFileNameWithoutExtension(aar); - var targetFilename = Path.Combine(dir, filename + ".aar"); - bool configuredAar = File.Exists(targetFilename); - if (!configuredAar) { - bool copiedAndLabeledAar = AssetDatabase.CopyAsset(aar, targetFilename); - if (copiedAndLabeledAar) { - var unlabeledAssets = new HashSet(); - PlayServicesResolver.LabelAssets( - new [] { targetFilename }, - complete: (unlabeled) => { unlabeledAssets.UnionWith(unlabeled); }); - copiedAndLabeledAar = unlabeledAssets.Count == 0; - } - if (copiedAndLabeledAar) { - try { - PluginImporter importer = (PluginImporter)AssetImporter.GetAtPath( - targetFilename); - importer.SetCompatibleWithAnyPlatform(false); - importer.SetCompatibleWithPlatform(BuildTarget.Android, false); - configuredAar = true; - } catch (Exception ex) { - PlayServicesResolver.Log(String.Format( - "Failed to disable {0} from being included by Unity's " + - "internal build. {0} has been deleted and will not be " + - "included in Gradle builds. ({1})", aar, ex), - level: LogLevel.Error); - } - } else { - PlayServicesResolver.Log(String.Format( - "Unable to copy {0} to {1}. {1} will not be included in Gradle " + - "builds.", aar, targetFilename), level: LogLevel.Error); - } - if (configuredAar) { - aarFiles.Add(targetFilename); - // 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 aar in aarFiles) { - if (!LocalMavenRepository.PatchPomFile(aar)) succeeded = false; - } - return succeeded; - } - - /// - /// Finds an area in a set of lines to inject a block of text. - /// - private class TextFileLineInjector { - // Token which, if found within a line, indicates where to inject the block of text if - // the start / end block isn't found - private 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; - } - } - - /// - /// Inject / update dependencies in the gradle template file. - /// - /// Dependencies to inject. - /// true if successful, false otherwise. - public static bool InjectDependencies(ICollection dependencies) { - var fileDescription = String.Format("gradle template {0}", GradleTemplatePath); - PlayServicesResolver.Log(String.Format("Reading {0}", fileDescription), - level: LogLevel.Verbose); - IEnumerable lines; - try { - lines = File.ReadAllLines(GradleTemplatePath); - } catch (Exception ex) { - PlayServicesResolver.Log( - String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), - level: LogLevel.Error); - return false; - } - - PlayServicesResolver.Log(String.Format("Searching for {0} in {1}", DependenciesToken, - fileDescription), - level: LogLevel.Verbose); - // Determine whether dependencies should be injected. - 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.Log( - String.Format("No {0} token found in {1}, Android Resolver libraries will " + - "not be added to the file.", DependenciesToken, fileDescription), - level: LogLevel.Warning); - return true; - } - - // Copy all srcaar files in the project to aar filenames so that they'll be included in - // the Gradle build. - if (!CopySrcAars(dependencies)) return false; - - var repoLines = new List(); - // Optionally enable the jetifier. - if (SettingsDialog.UseJetifier && dependencies.Count > 0) { - repoLines.AddRange(new [] { - "([rootProject] + (rootProject.subprojects as List)).each {", - " ext {", - " it.setProperty(\"android.useAndroidX\", true)", - " it.setProperty(\"android.enableJetifier\", true)", - " }", - "}" - }); - } - 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), - }; - // 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); - } - if (!FileUtils.CheckoutFile(GradleTemplatePath, PlayServicesResolver.logger)) { - PlayServicesResolver.Log( - String.Format("Failed to checkout '{0}', unable to patch the file.", - GradleTemplatePath), level: LogLevel.Error); - return false; - } - PlayServicesResolver.Log( - String.Format("Writing updated {0}", fileDescription), - level: LogLevel.Verbose); - try { - File.WriteAllText(GradleTemplatePath, - String.Join("\n", outputLines.ToArray()) + "\n"); - } catch (Exception ex) { - PlayServicesResolver.Log( - String.Format("Unable to patch {0} ({1})", fileDescription, - ex.ToString()), level: LogLevel.Error); - return false; - } - return true; - } - } -} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar deleted file mode 100644 index 19dba6da..00000000 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar and /dev/null differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar deleted file mode 100644 index dde15322..00000000 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar and /dev/null differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-basement-15.0.1.aar b/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-basement-15.0.1.aar deleted file mode 100644 index 318e7fcf..00000000 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-basement-15.0.1.aar and /dev/null differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar deleted file mode 100644 index 82803e2f..00000000 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar and /dev/null differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-app-unity-5.1.1.aar deleted file mode 100644 index b0fedd63..00000000 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-app-unity-5.1.1.aar and /dev/null differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar b/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar deleted file mode 100644 index 4d82c9a6..00000000 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar and /dev/null differ diff --git a/source/UnityAssetUploader/README.md b/source/UnityAssetUploader/README.md index eeed8518..0c14cff9 100644 --- a/source/UnityAssetUploader/README.md +++ b/source/UnityAssetUploader/README.md @@ -10,7 +10,7 @@ packages. * 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. @@ -39,3 +39,4 @@ global arguments: 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/VersionHandler/src/VersionHandler.cs b/source/VersionHandler/src/VersionHandler.cs index 87f80608..0b0f719e 100644 --- a/source/VersionHandler/src/VersionHandler.cs +++ b/source/VersionHandler/src/VersionHandler.cs @@ -112,12 +112,18 @@ private static bool BootStrapping { /// static VersionHandler() { // Schedule the process if the version handler isn't disabled on the command line. - if (System.Environment.CommandLine.Contains("-gvh_disable")) { + if (System.Environment.CommandLine.ToLower().Contains("-gvh_disable")) { UnityEngine.Debug.Log(String.Format("{0} bootstrap disabled", VERSION_HANDLER_ASSEMBLY_NAME)); } else { EditorApplication.update -= BootStrap; EditorApplication.update += BootStrap; + // A workaround to make sure bootstrap continues if Unity reloads assemblies + // during bootstrapping. The issue only observed in Unity 2019 and 2020 + float unityVersion = GetUnityVersionMajorMinor(); + if (unityVersion < 2021.0f && unityVersion >= 2019.0f) { + var type = BootStrappedImpl; + } } } @@ -147,7 +153,7 @@ private static void BootStrap() { if (implAvailable) return; var assemblies = new List(); - foreach (string assetGuid in AssetDatabase.FindAssets("l:gvh")) { + foreach (var assetGuid in AssetDatabase.FindAssets("l:gvh")) { string filename = AssetDatabase.GUIDToAssetPath(assetGuid); var match = VERSION_HANDLER_FILENAME_RE.Match(filename); if (match.Success) assemblies.Add(match); @@ -381,9 +387,7 @@ public static string[] FindAllAssets() { /// public static void UpdateVersionedAssets(bool forceUpdate = false) { InvokeImplMethod("UpdateVersionedAssets", - namedArgs: new Dictionary { - { "forceUpdate", forceUpdate } - }, + args: new object[] { forceUpdate }, schedule: true); } @@ -444,6 +448,7 @@ public static Type FindClass(string assemblyName, string className) { foreach (var currentType in assembly.GetTypes()) { if (currentType.FullName == className) { type = currentType; + break; } } if (type != null) break; @@ -460,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( @@ -476,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( @@ -492,7 +497,7 @@ public static object InvokeStaticMethod( /// Class to call the method on. /// Object to call a method on. /// Name of the method to call. - /// Positional arguments of the method. + /// Positional arguments of the method. /// Named arguments of the method. /// object returned by the method. public static object InvokeMethod( @@ -555,6 +560,87 @@ public static object InvokeMethod( } return foundMethod.Invoke(objectInstance, parameterValues); } + + /// + /// Call a method on a static event. + /// + /// Class to call the method on. + /// The name of the event. + /// Action to add/remove from the event. + /// The func to get the method on the event. + /// True if the method is called successfully. + private static bool InvokeStaticEventMethod(Type type, string eventName, Action action, + Func getMethod) { + EventInfo eventInfo = type.GetEvent(eventName); + if (eventInfo != null) { + MethodInfo method = getMethod(eventInfo); + Delegate d = Delegate.CreateDelegate( + eventInfo.EventHandlerType, action.Target, action.Method); + System.Object[] args = { d }; + if (method.IsStatic) { + method.Invoke(null, args); + return true; + } + } + return false; + } + + /// + /// Call adder method on a static event. + /// + /// Class to call the method on. + /// The name of the event. + /// Action to add from the event. + /// True if the action is added successfully. + public static bool InvokeStaticEventAddMethod(Type type, string eventName, Action action) { + return InvokeStaticEventMethod(type, eventName, action, + eventInfo => eventInfo.GetAddMethod()); + } + + /// + /// Call remover method on a static event. + /// + /// Class to call the method on. + /// The name of the event. + /// Action to remove from the event. + /// True if the action is removed successfully. + public static bool InvokeStaticEventRemoveMethod(Type type, string eventName, Action action) { + return InvokeStaticEventMethod(type, eventName, action, + eventInfo => eventInfo.GetRemoveMethod()); + } + + // Name of UnityEditor.AssemblyReloadEvents.beforeAssemblyReload event. + private const string BeforeAssemblyReloadEventName = "beforeAssemblyReload"; + + /// + /// Register for beforeAssemblyReload event. + /// Note that AssemblyReloadEvents is only availabe from Unity 2017. + /// + /// Action to register for. + /// True if the action is registered successfully. + public static bool RegisterBeforeAssemblyReloadEvent(Action action) { + Type eventType = VersionHandler.FindClass("UnityEditor", + "UnityEditor.AssemblyReloadEvents"); + if (eventType != null) { + return InvokeStaticEventAddMethod(eventType, BeforeAssemblyReloadEventName, action); + } + return false; + } + + /// + /// Unregister for beforeAssemblyReload event. + /// Note that AssemblyReloadEvents is only availabe from Unity 2017. + /// + /// Action to unregister for. + /// True if the action is unregistered successfully. + public static bool UnregisterBeforeAssemblyReloadEvent(Action action) { + Type eventType = VersionHandler.FindClass("UnityEditor", + "UnityEditor.AssemblyReloadEvents"); + if (eventType != null) { + return InvokeStaticEventRemoveMethod(eventType, BeforeAssemblyReloadEventName, action); + } + return false; + } } } // namespace Google diff --git a/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs b/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs index 5da7dcce..abaffa31 100644 --- a/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs +++ b/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs @@ -25,6 +25,10 @@ public class Greeter { private string name; + public event Action instanceEvent; + + static public event Action staticEvent; + public Greeter(string name) { this.name = name; } @@ -77,6 +81,18 @@ public string HelloWithCustomerNameAndPronoun(string customerName, return Greeter.GenericHelloWithCustomerNameAndPronoun(customerName, pronoun: pronoun) + MyNameIs(); } + + public static void InvokeStaticEvent() { + if (staticEvent != null) { + staticEvent.Invoke(); + } + } + + public void InvokeInstanceEvent() { + if (instanceEvent != null) { + instanceEvent.Invoke(); + } + } } [UnityEditor.InitializeOnLoad] @@ -105,6 +121,7 @@ static TestReflection() { TestInvokeInstanceMethodWithNoArgs, TestInvokeInstanceMethodWithNamedArgDefault, TestInvokeInstanceMethodWithNamedArg, + TestInvokeStaticEventMethod, }) { var testName = test.Method.Name; Exception exception = null; @@ -257,4 +274,61 @@ static bool TestInvokeInstanceMethodWithNamedArg() { new object[] { "Smith" }, new Dictionary { { "pronoun", "Mrs" } })); } + + // Check if the delegate is properly added/removed from the given event using the function to + // test. + static bool CheckEvent(Func funcToTest, Action invokeEvent, + bool expectSuccess, bool expectInvoked) { + + bool invoked = false; + + Action actionToInvoke = () => { + invoked = true; + }; + + bool result = funcToTest(actionToInvoke); + if (result != expectSuccess){ + throw new Exception( + String.Format("Expect funcToTest returns '{0}', but actually returned '{1}'", + expectSuccess, result)); + } + invokeEvent(); + if ( invoked != expectInvoked) { + throw new Exception(String.Format( + "Expect event invoked: {0}, but actually event invoked: {1}", + expectInvoked, invoked)); + } + + return true; + } + + // Test adding/removing delegate to a static event. + static bool TestInvokeStaticEventMethod() { + CheckEvent( funcToTest: (action) => + VersionHandler.InvokeStaticEventAddMethod(typeof(Greeter), "staticEvent", action), + invokeEvent: Greeter.InvokeStaticEvent, + expectSuccess: true, + expectInvoked: true); + + CheckEvent( funcToTest: (action) => + VersionHandler.InvokeStaticEventRemoveMethod(typeof(Greeter), "staticEvent", + action), + invokeEvent: Greeter.InvokeStaticEvent, + expectSuccess: true, + expectInvoked: false); + + CheckEvent( funcToTest: (action) => + VersionHandler.InvokeStaticEventAddMethod(typeof(Greeter), "foo", action), + invokeEvent: Greeter.InvokeStaticEvent, + expectSuccess: false, + expectInvoked: false); + + CheckEvent( funcToTest: (action) => + VersionHandler.InvokeStaticEventRemoveMethod(typeof(Greeter), "foo", action), + invokeEvent: Greeter.InvokeStaticEvent, + expectSuccess: false, + expectInvoked: false); + + return true; + } } diff --git a/source/VersionHandlerImpl/Properties/AssemblyInfo.cs b/source/VersionHandlerImpl/Properties/AssemblyInfo.cs index 0e474061..ce756cd2 100644 --- a/source/VersionHandlerImpl/Properties/AssemblyInfo.cs +++ b/source/VersionHandlerImpl/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// +// // Copyright (C) 2014 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +37,7 @@ // if desired. See the Mono documentation for more information about signing. // [assembly: AssemblyDelaySign(false)] -// // +// // // Copyright (C) 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,3 +56,7 @@ // Uses VersionHandlerImpl.RestoreDefaultSettings(preferenceKeys) [assembly: InternalsVisibleTo("Google.IOSResolver")] [assembly: InternalsVisibleTo("Google.JarResolver")] +[assembly: InternalsVisibleTo("Google.PackageManagerResolver")] +[assembly: InternalsVisibleTo("Google.VersionHandlerImplTests")] +[assembly: InternalsVisibleTo("Google.IntegrationTester")] +[assembly: InternalsVisibleTo("Google.PackageManagerClientIntegrationTests")] diff --git a/source/VersionHandlerImpl/VersionHandlerImpl.csproj b/source/VersionHandlerImpl/VersionHandlerImpl.csproj index 6c062c02..d93cb5a4 100644 --- a/source/VersionHandlerImpl/VersionHandlerImpl.csproj +++ b/source/VersionHandlerImpl/VersionHandlerImpl.csproj @@ -44,6 +44,7 @@ + @@ -52,16 +53,22 @@ + + + + + + diff --git a/source/VersionHandlerImpl/src/DialogWindow.cs b/source/VersionHandlerImpl/src/DialogWindow.cs new file mode 100644 index 00000000..7d7d37d4 --- /dev/null +++ b/source/VersionHandlerImpl/src/DialogWindow.cs @@ -0,0 +1,431 @@ +// +// Copyright (C) 2020 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Google { + +/// +/// Non-blocking version of dialog implemeted from EditorWindows. +/// The dialog will not block the editor. When multiple dialogs are triggered, they will be queued +/// and only be shown one at a time based on the triggering order. +/// +public class DialogWindow : EditorWindow { + /// + /// Option selected by the dialog. + /// + public enum Option { + + /// + /// Value that can be used as a default to provide different behavior in a non-interactive + /// mode of operation. + /// + SelectedNone = -1, + + /// + /// Option0 was selected by the user. + /// + Selected0 = 0, + + /// + /// Option1 was selected by the user. + /// + Selected1 = 1, + + /// + /// Option2 was selected by the user. + /// + Selected2 = 2, + }; + + // Default width of the dialog. + private const float DEFAULT_WINDOWS_WIDTH = 400.0f; + + /// + /// All the data to render the content of the dialog and react to the user interaction. + /// All the context should be serialable/deserialable so that all the context can be reloaded + /// after Unity hot reloads. + /// + private class DialogContext { + // Title of the dialog. + internal string Title; + + // Message to display in the dialog. + internal string Message; + + // Option selected if interactivity is disabled. + internal Option DefaultOption = Option.SelectedNone; + + // Text for the first option. + internal string Option0String; + + // Text for the second option or null to disable. + internal string Option1String; + + // Text for the third option or null to disable. + internal string Option2String; + + // Width of the dialog window. + internal float WindowWidth = DEFAULT_WINDOWS_WIDTH; + + // Option selected if the dialog is closed. + internal Option WindowCloseOption = Option.SelectedNone; + + // Callback to trigger once a selection is made. + internal Action +using System; + namespace Google { /// /// Log severity. /// - internal enum LogLevel { + public enum LogLevel { Debug, Verbose, Info, @@ -27,21 +29,45 @@ internal enum LogLevel { Error, }; + /// + /// Where to log. + /// + [Flags] + public enum LogTarget { + Console = 1, + Unity = 2, + File = 4, + }; + /// /// Writes filtered logs to the Unity log. /// - internal class Logger { + public class Logger { + + /// + /// Whether all log messages should be display. + /// + internal static bool DebugLoggingEnabled { + get { + return Environment.CommandLine.ToLower().Contains("-gvh_log_debug"); + } + } /// /// Filter the log level. /// - internal LogLevel Level { get; set; } + public LogLevel Level { get; set; } + + /// + /// Filter the log targets. + /// + public LogTarget Target { get; set; } /// /// Enable / disable verbose logging. /// This toggles between Info vs. Verbose levels. /// - internal bool Verbose { + public bool Verbose { set { Level = value ? LogLevel.Verbose : LogLevel.Info; } get { return Level <= LogLevel.Verbose; } } @@ -49,24 +75,27 @@ internal bool Verbose { /// /// Name of the file to log to, if this is null this will not log to a file. /// - internal string LogFilename { get; set; } + public string LogFilename { get; set; } /// /// Delegate function used to log messages. /// /// Message to log. /// Log level of the message. - internal delegate void LogMessageDelegate(string message, LogLevel level); + public delegate void LogMessageDelegate(string message, LogLevel level); /// /// Event that is called for each logged message. /// - internal event LogMessageDelegate LogMessage; + public event LogMessageDelegate LogMessage; /// /// Construct a logger. /// - internal Logger() {} + public Logger() { + Level = LogLevel.Info; + Target = LogTarget.Unity | LogTarget.File; + } /// /// Write a message to the log file. @@ -86,22 +115,29 @@ private void LogToFile(string message) { /// 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 virtual void Log(string message, LogLevel level = LogLevel.Info) { - if (level >= Level || ExecutionEnvironment.InBatchMode) { + public virtual void Log(string message, LogLevel level = LogLevel.Info) { + if (level >= Level || DebugLoggingEnabled) { switch (level) { case LogLevel.Debug: case LogLevel.Verbose: case LogLevel.Info: - UnityEngine.Debug.Log(message); - LogToFile(message); + if ((Target & LogTarget.Unity) != 0) UnityEngine.Debug.Log(message); + if ((Target & LogTarget.File) != 0) LogToFile(message); + if ((Target & LogTarget.Console) != 0) System.Console.WriteLine(message); break; case LogLevel.Warning: - UnityEngine.Debug.LogWarning(message); - LogToFile("WARNING: " + message); + if ((Target & LogTarget.Unity) != 0) UnityEngine.Debug.LogWarning(message); + if ((Target & LogTarget.File) != 0) LogToFile("WARNING: " + message); + if ((Target & LogTarget.Console) != 0) { + System.Console.WriteLine("WARNING: " + message); + } break; case LogLevel.Error: - UnityEngine.Debug.LogError(message); - LogToFile("ERROR: " + message); + if ((Target & LogTarget.Unity) != 0) UnityEngine.Debug.LogError(message); + if ((Target & LogTarget.File) != 0) LogToFile("ERROR: " + message); + if ((Target & LogTarget.Console) != 0) { + System.Console.WriteLine("ERROR: " + message); + } break; } } diff --git a/source/VersionHandlerImpl/src/MiniJSON.cs b/source/VersionHandlerImpl/src/MiniJSON.cs new file mode 100644 index 00000000..43ac72f7 --- /dev/null +++ b/source/VersionHandlerImpl/src/MiniJSON.cs @@ -0,0 +1,585 @@ +/* + * 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. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +// Forked from https://github.com/Jackyjjc/MiniJSON.cs +// version: 6de00beb134bbab9d873033a48b32e4067ed0c25 + +namespace EDMInternal { +namespace MiniJSON { + // Example usage: + // + // using UnityEngine; + // using System.Collections; + // using System.Collections.Generic; + // using MiniJSON; + // + // public class MiniJSONTest : MonoBehaviour { + // void Start () { + // var jsonString = "{ \"array\": [1.44,2,3], " + + // "\"object\": {\"key1\":\"value1\", \"key2\":256}, " + + // "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " + + // "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " + + // "\"int\": 65536, " + + // "\"float\": 3.1415926, " + + // "\"bool\": true, " + + // "\"null\": null }"; + // + // var dict = Json.Deserialize(jsonString) as Dictionary; + // + // Debug.Log("deserialized: " + dict.GetType()); + // Debug.Log("dict['array'][0]: " + ((List) dict["array"])[0]); + // Debug.Log("dict['string']: " + (string) dict["string"]); + // Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles + // Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs + // Debug.Log("dict['unicode']: " + (string) dict["unicode"]); + // + // var str = Json.Serialize(dict); + // + // Debug.Log("serialized: " + str); + // } + // } + + /// + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary. + /// All numbers are parsed to doubles. + /// + internal static class Json { + /// + /// Parses the string json into a value + /// + /// A JSON string. + /// An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false + public static object Deserialize(string json) { + // save the string for debug information + if (json == null) { + return null; + } + + return Parser.Parse(json); + } + + sealed class Parser : IDisposable { + const string WORD_BREAK = "{}[],:\""; + + public static bool IsWordBreak(char c) { + return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1; + } + + enum TOKEN { + NONE, + CURLY_OPEN, + CURLY_CLOSE, + SQUARED_OPEN, + SQUARED_CLOSE, + COLON, + COMMA, + STRING, + NUMBER, + TRUE, + FALSE, + NULL + }; + + StringReader json; + + Parser(string jsonString) { + json = new StringReader(jsonString); + } + + public static object Parse(string jsonString) { + using (var instance = new Parser(jsonString)) { + return instance.ParseValue(); + } + } + + public void Dispose() { + json.Dispose(); + json = null; + } + + Dictionary ParseObject() { + Dictionary table = new Dictionary(); + + // ditch opening brace + json.Read(); + + // { + while (true) { + switch (NextToken) { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.CURLY_CLOSE: + return table; + default: + // name + string name = ParseString(); + if (name == null) { + return null; + } + + // : + if (NextToken != TOKEN.COLON) { + return null; + } + // ditch the colon + json.Read(); + + // value + table[name] = ParseValue(); + break; + } + } + } + + List ParseArray() { + List array = new List(); + + // ditch opening bracket + json.Read(); + + // [ + var parsing = true; + while (parsing) { + TOKEN nextToken = NextToken; + + switch (nextToken) { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.SQUARED_CLOSE: + parsing = false; + break; + default: + object value = ParseByToken(nextToken); + + array.Add(value); + break; + } + } + + return array; + } + + object ParseValue() { + TOKEN nextToken = NextToken; + return ParseByToken(nextToken); + } + + object ParseByToken(TOKEN token) { + switch (token) { + case TOKEN.STRING: + return ParseString(); + case TOKEN.NUMBER: + return ParseNumber(); + case TOKEN.CURLY_OPEN: + return ParseObject(); + case TOKEN.SQUARED_OPEN: + return ParseArray(); + case TOKEN.TRUE: + return true; + case TOKEN.FALSE: + return false; + case TOKEN.NULL: + return null; + default: + return null; + } + } + + string ParseString() { + StringBuilder s = new StringBuilder(); + char c; + + // ditch opening quote + json.Read(); + + bool parsing = true; + while (parsing) { + + if (json.Peek() == -1) { + parsing = false; + break; + } + + c = NextChar; + switch (c) { + case '"': + parsing = false; + break; + case '\\': + if (json.Peek() == -1) { + parsing = false; + break; + } + + c = NextChar; + switch (c) { + case '"': + case '\\': + case '/': + s.Append(c); + break; + case 'b': + s.Append('\b'); + break; + case 'f': + s.Append('\f'); + break; + case 'n': + s.Append('\n'); + break; + case 'r': + s.Append('\r'); + break; + case 't': + s.Append('\t'); + break; + case 'u': + var hex = new char[4]; + + for (int i=0; i< 4; i++) { + hex[i] = NextChar; + } + + s.Append((char) Convert.ToInt32(new string(hex), 16)); + break; + } + break; + default: + s.Append(c); + break; + } + } + + return s.ToString(); + } + + object ParseNumber() { + string number = NextWord; + + if (number.IndexOf('.') == -1 && number.IndexOf('E') == -1 && number.IndexOf('e') == -1) { + long parsedInt; + Int64.TryParse(number, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out parsedInt); + return parsedInt; + } + + double parsedDouble; + Double.TryParse(number, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out parsedDouble); + return parsedDouble; + } + + void EatWhitespace() { + while (Char.IsWhiteSpace(PeekChar)) { + json.Read(); + + if (json.Peek() == -1) { + break; + } + } + } + + char PeekChar { + get { + return Convert.ToChar(json.Peek()); + } + } + + char NextChar { + get { + return Convert.ToChar(json.Read()); + } + } + + string NextWord { + get { + StringBuilder word = new StringBuilder(); + + while (!IsWordBreak(PeekChar)) { + word.Append(NextChar); + + if (json.Peek() == -1) { + break; + } + } + + return word.ToString(); + } + } + + TOKEN NextToken { + get { + EatWhitespace(); + + if (json.Peek() == -1) { + return TOKEN.NONE; + } + + switch (PeekChar) { + case '{': + return TOKEN.CURLY_OPEN; + case '}': + json.Read(); + return TOKEN.CURLY_CLOSE; + case '[': + return TOKEN.SQUARED_OPEN; + case ']': + json.Read(); + return TOKEN.SQUARED_CLOSE; + case ',': + json.Read(); + return TOKEN.COMMA; + case '"': + return TOKEN.STRING; + case ':': + return TOKEN.COLON; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN.NUMBER; + } + + switch (NextWord) { + case "false": + return TOKEN.FALSE; + case "true": + return TOKEN.TRUE; + case "null": + return TOKEN.NULL; + } + + return TOKEN.NONE; + } + } + } + + /// + /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string + /// + /// A Dictionary<string, object> / List<object> + /// Whether output as human readable format with spaces and + /// indentations. + /// Number of spaces for each level of indentation. + /// A JSON encoded string, or null if object 'json' is not serializable + public static string Serialize(object obj, + bool humanReadable = false, + int indentSpaces = 2) { + return Serializer.Serialize(obj, humanReadable, indentSpaces); + } + + sealed class Serializer { + StringBuilder builder; + bool humanReadable; + int indentSpaces; + int indentLevel; + + Serializer(bool humanReadable, int indentSpaces) { + builder = new StringBuilder(); + this.humanReadable = humanReadable; + this.indentSpaces = indentSpaces; + indentLevel = 0; + } + + public static string Serialize(object obj, bool humanReadable, int indentSpaces) { + var instance = new Serializer(humanReadable, indentSpaces); + + instance.SerializeValue(obj); + + return instance.builder.ToString(); + } + + void SerializeValue(object value) { + IList asList; + IDictionary asDict; + string asStr; + + if (value == null) { + builder.Append("null"); + } else if ((asStr = value as string) != null) { + SerializeString(asStr); + } else if (value is bool) { + builder.Append((bool) value ? "true" : "false"); + } else if ((asList = value as IList) != null) { + SerializeArray(asList); + } else if ((asDict = value as IDictionary) != null) { + SerializeObject(asDict); + } else if (value is char) { + SerializeString(new string((char) value, 1)); + } else { + SerializeOther(value); + } + } + + void AppendNewLineFunc() { + builder.AppendLine(); + builder.Append(' ', indentSpaces * indentLevel); + } + + void SerializeObject(IDictionary obj) { + bool first = true; + + builder.Append('{'); + ++indentLevel; + + foreach (object e in obj.Keys) { + if (first) { + if (humanReadable) AppendNewLineFunc(); + } else { + builder.Append(','); + if (humanReadable) AppendNewLineFunc(); + } + + SerializeString(e.ToString()); + builder.Append(':'); + if (humanReadable) builder.Append(' '); + + SerializeValue(obj[e]); + + first = false; + } + + --indentLevel; + if (humanReadable && obj.Count > 0) AppendNewLineFunc(); + + builder.Append('}'); + } + + void SerializeArray(IList anArray) { + builder.Append('['); + ++indentLevel; + + bool first = true; + + for (int i=0; i 0) AppendNewLineFunc(); + + builder.Append(']'); + } + + void SerializeString(string str) { + builder.Append('\"'); + + char[] charArray = str.ToCharArray(); + for (int i=0; i= 32) && (codepoint <= 126)) { + builder.Append(c); + } else { + builder.Append("\\u"); + builder.Append(codepoint.ToString("x4")); + } + break; + } + } + + builder.Append('\"'); + } + + void SerializeOther(object value) { + // NOTE: decimals lose precision during serialization. + // They always have, I'm just letting you know. + // Previously floats and doubles lost precision too. + if (value is float) { + builder.Append(((float) value).ToString("R", System.Globalization.CultureInfo.InvariantCulture)); + } else if (value is int + || value is uint + || value is long + || value is sbyte + || value is byte + || value is short + || value is ushort + || value is ulong) { + builder.Append(value); + } else if (value is double + || value is decimal) { + builder.Append(Convert.ToDouble(value).ToString("R", System.Globalization.CultureInfo.InvariantCulture)); + } else { + SerializeString(value.ToString()); + } + } + } + } +} +} diff --git a/source/VersionHandlerImpl/src/MultiSelectWindow.cs b/source/VersionHandlerImpl/src/MultiSelectWindow.cs index e845a2f2..c5558df9 100644 --- a/source/VersionHandlerImpl/src/MultiSelectWindow.cs +++ b/source/VersionHandlerImpl/src/MultiSelectWindow.cs @@ -72,6 +72,28 @@ public class MultiSelectWindow : EditorWindow { /// public Action OnCancel; + /// + /// Delegate that allows the rendering of each item to be customized. + /// + /// Item being rendered. + public delegate void RenderItemDelegate(KeyValuePair item); + + /// + /// Delegate which can be used to customize item rendering. + /// + public RenderItemDelegate RenderItem; + + /// + /// Action to render additional contents after the listed items. + /// + public Action RenderAfterItems; + + /// + /// Action to render additional items in the button area of the window before the + /// cancel and apply buttons. + /// + public Action RenderBeforeCancelApply; + /// /// Styles for unselected items in the list. /// @@ -82,6 +104,11 @@ public class MultiSelectWindow : EditorWindow { /// private GUIStyle[] selectedItemStyles; + /// + /// Style for wrapped labels. + /// + private GUIStyle wrappedLabel; + /// /// Initialize the window. /// @@ -102,6 +129,7 @@ public virtual void Initialize() { minSize = new Vector2(300, 200); unselectedItemStyles = null; selectedItemStyles = null; + wrappedLabel = null; } /// @@ -172,6 +200,10 @@ protected virtual void InitializeStyles() { selectedItemStyles[i] = style; } } + if (wrappedLabel == null) { + wrappedLabel = new GUIStyle(EditorStyles.label); + wrappedLabel.wordWrap = true; + } if (AvailableItems != null && sortedItems.Count != AvailableItems.Count) { InitializeSortedItems(); } @@ -184,9 +216,11 @@ protected virtual void OnGUI() { InitializeStyles(); if (!String.IsNullOrEmpty(Caption)) { EditorGUILayout.BeginHorizontal(); - EditorGUILayout.SelectableLabel(Caption); + GUILayout.Label(Caption, wrappedLabel); EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginVertical(EditorStyles.textField); EditorGUILayout.Space(); + EditorGUILayout.EndVertical(); } EditorGUILayout.BeginVertical(); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); @@ -206,16 +240,30 @@ protected virtual void OnGUI() { SelectedItems.Remove(item); } } + if (RenderItem != null) RenderItem(indexAndItem.Value); EditorGUILayout.EndHorizontal(); displayIndex++; } EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); + + if (RenderAfterItems != null) { + EditorGUILayout.BeginVertical(EditorStyles.textField); + EditorGUILayout.Space(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.BeginVertical(); + RenderAfterItems(); + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + } + EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("All")) SelectAll(); if (GUILayout.Button("None")) SelectNone(); EditorGUILayout.Space(); EditorGUILayout.Space(); + if (RenderBeforeCancelApply != null) RenderBeforeCancelApply(); bool cancel = GUILayout.Button(CancelLabel); bool apply = GUILayout.Button(ApplyLabel); EditorGUILayout.EndHorizontal(); @@ -232,11 +280,26 @@ protected virtual void OnGUI() { /// /// Title to display on the window. /// Reference to this class + [Obsolete("This method deprecated. Please use CreateMultiSelectWindow() instead.")] public static MultiSelectWindow CreateMultiSelectWindow(string title) { MultiSelectWindow window = (MultiSelectWindow)EditorWindow.GetWindow( typeof(MultiSelectWindow), true, title, true); window.Initialize(); return window; } + + /// + /// Get the existing multi-select window or create a new one. + /// To create an unique MultiSelectWindow, pass a type derived from MultiSelectWindow + /// as the type parameter. + /// + /// A type that inherits from the MultiSelectWindow. + /// Title to display on the window. + /// Reference to this class + public static T CreateMultiSelectWindow(string title) where T : MultiSelectWindow { + T window = (T)EditorWindow.GetWindow(typeof(T), true, title, true); + window.Initialize(); + return window; + } } } diff --git a/source/VersionHandlerImpl/src/PackageUninstallWindow.cs b/source/VersionHandlerImpl/src/PackageUninstallWindow.cs new file mode 100644 index 00000000..b2389229 --- /dev/null +++ b/source/VersionHandlerImpl/src/PackageUninstallWindow.cs @@ -0,0 +1,95 @@ +// +// 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 System.IO; +using UnityEditor; + +namespace Google { + +/// +/// A unique class to create the multi-select window to uninstall packages managed by +/// VersionHandler. +/// +[InitializeOnLoad] +public class PackageUninstallWindow : MultiSelectWindow { + // Hardcoded text for the window. + private static string windowTitle = "Uninstall Managed Packages"; + private static string caption = + "Select packages to uninstall.\n\n" + + "NOTE: If the files in the package have been moved, VersionHandler cannot properly "+ + "remove moved files."; + private static string applylable = "Uninstall Selected Packages"; + + /// + /// Show the window for the user to uninstall packages managed by Version Handler. + /// + [MenuItem("Assets/External Dependency Manager/Version Handler/Uninstall Managed Packages")] + public static void UninstallPackage() { + // Display MultiSelectWindow + var window =MultiSelectWindow.CreateMultiSelectWindow(windowTitle); + window.AvailableItems = GetSelectionList(); + window.Sort(1); + window.Caption = caption; + window.ApplyLabel = applylable; + window.OnApply = () => { + if (window.SelectedItems.Count > 0) { + VersionHandlerImpl.ManifestReferences.DeletePackages(window.SelectedItems); + if (window.SelectedItems.Count == window.AvailableItems.Count) { + VersionHandlerImpl.analytics.Report("uninstallpackagewindow/confirm/all", + "Confirm to Uninstall All Packages"); + } else { + VersionHandlerImpl.analytics.Report("uninstallpackagewindow/confirm/subset", + "Confirm to Uninstall a Subset of Packages"); + } + } + }; + window.OnCancel = () => { + VersionHandlerImpl.analytics.Report("uninstallpackagewindow/cancel", + "Cancel to Uninstall Packages"); + }; + window.Show(); + VersionHandlerImpl.analytics.Report("uninstallpackagewindow/show", + "Show Uninstall Package Window"); + } + + /// + /// Get a List of packages for seleciton. + /// + /// A List of key-value pairs of canonical name to display name + private static List> GetSelectionList() { + var manifests = VersionHandlerImpl.ManifestReferences.FindAndReadManifestsInAssetsFolder(); + List> selections = + new List>(); + foreach (var pkg in manifests) { + if (!String.IsNullOrEmpty(pkg.filenameCanonical) && pkg.metadataByVersion != null) { + string filename = Path.GetFileNameWithoutExtension(pkg.filenameCanonical); + var versions = new List(); + foreach (var fileMetadata in pkg.metadataByVersion.Values) { + versions.Add(fileMetadata.versionString); + } + string displayName = + String.Format("{0} version: [{1}]", filename, + String.Join(", ", versions.ToArray())); + selections.Add( + new KeyValuePair(pkg.filenameCanonical, displayName)); + } + } + return selections; + } +} +} // namespace Google diff --git a/source/VersionHandlerImpl/src/PortableWebRequest.cs b/source/VersionHandlerImpl/src/PortableWebRequest.cs new file mode 100644 index 00000000..9747a2e8 --- /dev/null +++ b/source/VersionHandlerImpl/src/PortableWebRequest.cs @@ -0,0 +1,578 @@ +// +// 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.Reflection; +using System.Net; +using System.Text; +using System.Web; + +using UnityEngine; + +/// +/// Minimal interface to retrieve the status and result of a web request. +/// +public interface IPortableWebRequestStatus { + /// + /// Determine whether the request is complete + /// + bool Complete { get; } + + /// + /// Get the response / payload of the request. + /// + byte[] Result { get; } + + /// + /// Get the response headers. + /// + IDictionary Headers { get; } + + /// + /// Get the status code from the response headers. + /// + HttpStatusCode Status { get; } +} + +/// +/// Interface for an object that starts a web request. +/// +public interface IPortableWebRequest { + /// + /// Post to a URL. + /// + /// URL to send data to. + /// Headers to use when performing the request. + /// Form fields to URL encode and send. + /// Web request if successfully started, null otherwise. + IPortableWebRequestStatus Post(string url, IDictionary headers, + IEnumerable> formFields); + + /// + /// Post to a URL. + /// + /// URL path to send data to. + /// Query parameters to be appended to the URL. + /// Headers to use when performing the request. + /// Form fields to URL encode and send. + /// Web request if successfully started, null otherwise. + IPortableWebRequestStatus Post(string path, + IEnumerable> queryParams, + IDictionary headers, + IEnumerable> formFields); + + /// + /// Get the contents of a URL. + /// + /// URL to retrieve data from. + /// Headers to use when performing the request. + IPortableWebRequestStatus Get(string url, + IDictionary headers); +} + +/// +/// Extension methods for the IPortableWebRequestStatus interface. +/// +internal static class IPortableWebRequestStatusExtension { + + /// + /// Get the status code from the response headers. + /// + public static HttpStatusCode GetStatus(this IPortableWebRequestStatus requestStatus) { + string headerValue = null; + string code = null; + if (requestStatus.Headers.TryGetValue("Status-Code", out headerValue)) { + code = headerValue; + } else if (requestStatus.Headers.TryGetValue("STATUS", out headerValue)) { + // Unity puts the status-line (see RFC7230) into the STATUS header field. + // Parse the string "http-version status-code reason-phrase" + var tokens = headerValue.Split(' '); + if (tokens.Length >= 3) code = tokens[1]; + } else { + UnityEngine.Debug.Log("Status code not found"); + foreach (var kv in requestStatus.Headers) { + UnityEngine.Debug.Log(String.Format("{0}={1}", kv.Key, kv.Value)); + } + } + if (!String.IsNullOrEmpty(code)) { + try { + return (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), code, false); + } catch (ArgumentException) { + // Ignored + } + } + return (HttpStatusCode)0; + } +} + +/// +/// *Very* basic web request that works across Unity 4.x and newer. +/// +public class PortableWebRequest : IPortableWebRequest { + + /// + /// Minimal interface to retrieve the status and result of a web request. + /// + protected class RequestStatus : IPortableWebRequestStatus { + + /// + /// Backing store for the Request property. + /// + private object request = null; + + /// + /// Whether an invalid request was assigned to this object. + /// + private bool requestError = false; + + /// + /// Lock for the request property. + /// + private object requestLock = new object(); + + /// + /// Unity web request object. + /// + public object Request { + set { + lock (requestLock) { + request = value; + requestError = request == null; + } + } + + get { + lock (requestLock) { + return request; + } + } + } + + /// + /// Determine whether the request is complete + /// + public bool Complete { + get { + if (requestError || PortableWebRequest.isDoneProperty == null) return true; + var requestObj = Request; + if (requestObj == null || + !(bool)PortableWebRequest.isDoneProperty.GetValue(requestObj, null)) { + return false; + } + bool isWww = requestObj.GetType().Name == "WWW"; + // Populate the response. + if (Result == null) { + if (isWww) { + Result = (byte[])PortableWebRequest.bytesProperty.GetValue(requestObj, + null); + } else { + var handler = + PortableWebRequest.downloadHandlerProperty.GetValue(requestObj, null); + Result = handler != null ? + (byte[])PortableWebRequest.downloadHandlerDataProperty.GetValue( + handler, null) : null; + } + } + // Populate the response headers. + if (Headers.Count == 0) { + if (isWww) { + Headers = (IDictionary) + PortableWebRequest.responseHeadersProperty.GetValue(requestObj, null); + } else { + var headers = (IDictionary) + PortableWebRequest.getResponseHeadersMethod.Invoke( + requestObj, null); + if (headers != null) { + var headersWithStatus = new Dictionary(headers); + headersWithStatus["Status-Code"] = + responseCodeProperty.GetValue(requestObj, null).ToString(); + headers = headersWithStatus; + } + Headers = headers; + } + } + return true; + } + } + + /// + /// Get the response / payload of the request. + /// + public byte[] Result { private set; get; } + + /// + /// Get the response headers. + /// + public IDictionary Headers { private set; get; } + + /// + /// Get the status code from the response headers. + /// + public HttpStatusCode Status { get { return this.GetStatus(); } } + + /// + /// Construct an empty request status object. + /// + public RequestStatus() { + Headers = new Dictionary(); + } + } + + /// + /// Logger for this object. + /// Exposed for testing purposes. + /// + internal static Logger logger = new Logger(); + + /// + /// Singleton instance of this class. + /// This can only be set by tests. + /// + public static IPortableWebRequest DefaultInstance { get; internal set; } + + /// + /// Whether an attempt has already been made to cache the web request type. + /// + private static bool attempedToCacheRequestType = false; + + /// + /// Type of "request". + /// + private static Type requestType; + + /// + /// Method used to construct a POST UnityWebRequest. + /// + private static MethodInfo postMethod; + + /// + /// Method used to send a UnityWebRequest after it has been initialized. + /// + private static MethodInfo sendMethod; + + /// + /// Method used to construct a GET UnityWebRequest. + /// + private static MethodInfo getMethod; + + /// + /// Instance method used to set headers on a UnityWebRequest. + /// + private static MethodInfo setRequestHeaderMethod; + + /// + /// Instance method used to get all headers from a UnityWebRequest. + /// + private static MethodInfo getResponseHeadersMethod; + + /// + /// Instance method used to get the status / response code from a UnityWebRequest. + /// + private static PropertyInfo responseCodeProperty; + + /// + /// WWW property that contains the bytes returned by the server. + /// + private static PropertyInfo bytesProperty; + + /// + /// WWW property that contains the headers returned by the server. + /// + private static PropertyInfo responseHeadersProperty; + + /// + /// UnityWebRequest property that contains the download handler. + /// By default this is DownloadHandlerBuffer. + /// + private static PropertyInfo downloadHandlerProperty; + + /// + /// Field of DownloadHandler that contains the downloaded data. + /// + private static PropertyInfo downloadHandlerDataProperty; + + /// + /// Property that indicates whether a request is complete. + /// + private static PropertyInfo isDoneProperty; + + /// + /// Initialize the singleton. + /// + static PortableWebRequest() { + DefaultInstance = new PortableWebRequest(); + } + + /// + /// Find and cache the WWW type and it's properties. + /// + /// true if successful, false otherwise. + private static bool FindAndCacheRequestTypeWww() { + requestType = Type.GetType("UnityEngine.WWW, UnityEngine"); + if (requestType != null) { + bytesProperty = requestType.GetProperty("bytes"); + responseHeadersProperty = requestType.GetProperty("responseHeaders"); + if (bytesProperty == null || responseHeadersProperty == null) { + logger.Log(String.Format("Unable to get bytes='{0}' or responseHeaders='{1}' " + + "property from '{2}'", + bytesProperty, responseHeadersProperty, requestType), + level: LogLevel.Warning); + requestType = null; + } + } + return requestType != null; + } + + /// + /// Try to find a type in a list of assemblies. + /// + /// Fully qualified type name. + /// Names of assemblies to search. + /// Type if successful, null otherwise. + private static Type FindTypeInAssemblies(string typeName, IEnumerable assemblyNames) { + Type type = null; + foreach (var assemblyName in assemblyNames) { + type = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName)); + if (type != null) break; + } + return type; + } + + /// + /// Find and cache the UnityWebRequest type and associated classes. + /// + /// true if successful, false otherwise. + private static bool FindAndCacheRequestTypeUnityWebRequest() { + // These types moved between assemblies between Unity 5.x and Unity 2017.x + var networkingAssemblyNames = new [] { "UnityEngine.UnityWebRequestModule", "UnityEngine" }; + requestType = FindTypeInAssemblies("UnityEngine.Networking.UnityWebRequest", + networkingAssemblyNames); + if (requestType != null) { + postMethod = requestType.GetMethod("Post", + new [] { typeof(String), typeof(WWWForm) }); + getMethod = requestType.GetMethod("Get", new [] { typeof(String) }); + downloadHandlerProperty = requestType.GetProperty("downloadHandler"); + sendMethod = requestType.GetMethod("SendWebRequest"); + sendMethod = sendMethod == null ? requestType.GetMethod("Send") : sendMethod; + setRequestHeaderMethod = requestType.GetMethod("SetRequestHeader"); + getResponseHeadersMethod = requestType.GetMethod("GetResponseHeaders"); + responseCodeProperty = requestType.GetProperty("responseCode"); + if (postMethod == null || getMethod == null || downloadHandlerProperty == null || + sendMethod == null || setRequestHeaderMethod == null || + getResponseHeadersMethod == null || responseCodeProperty == null) { + logger.Log( + String.Format( + "Failed to get postMethod='{0}', getMethod='{1}', " + + "sendMethod='{2}', downloadHandlerProperty='{3}', " + + "setRequestHeaderMethod='{4}', getResponseHeadersMethod='{5}' " + + "responseCodeProperty='{6}' from '{7}'", + postMethod, getMethod, sendMethod, downloadHandlerProperty, + setRequestHeaderMethod, getResponseHeadersMethod, responseCodeProperty, + requestType), + level: LogLevel.Warning); + requestType = null; + } + } + + var downloadHandlerType = FindTypeInAssemblies("UnityEngine.Networking.DownloadHandler", + networkingAssemblyNames); + if (downloadHandlerType != null) { + downloadHandlerDataProperty = downloadHandlerType.GetProperty("data"); + if (downloadHandlerDataProperty == null) { + logger.Log(String.Format("Failed to get data property for {0}.", + downloadHandlerType), level: LogLevel.Warning); + requestType = null; + } + } else { + logger.Log("DownloadHandler type not found.", level: LogLevel.Warning); + requestType = null; + } + return requestType != null; + } + + /// + /// Find the request type supported by the current version of Unity. + /// + /// true if a request type is found, false otherwise. + private static bool FindAndCacheRequestType() { + lock (logger) { + if (attempedToCacheRequestType) return requestType != null; + attempedToCacheRequestType = true; + + logger.Log("Find web request type", level: LogLevel.Debug); + bool cachedRequestType = FindAndCacheRequestTypeUnityWebRequest() || + FindAndCacheRequestTypeWww(); + logger.Log(cachedRequestType ? + String.Format("PortableWebRequest using '{0}'", + requestType.AssemblyQualifiedName) : + "PortableWebRequest unable to find a supported web request type.", + level: LogLevel.Debug); + if (cachedRequestType) { + isDoneProperty = requestType.GetProperty("isDone"); + if (isDoneProperty == null) { + logger.Log(String.Format("Failed to get isDone field / property from {0}", + requestType), level: LogLevel.Warning); + requestType = null; + } + } + return requestType != null; + } + } + + /// + /// HTTP method to use in the request. + /// + private enum HttpMethod { + Get, + Post, + }; + + /// + /// Start a web request on the main thread. + /// + /// Method to use. + /// Target URL. + /// Headers to use when performing the request. + /// Payload to send if this is a Post request, ignored otherwise. + /// PortableWebRequest instance that provides the status of the request. + private static IPortableWebRequestStatus StartRequestOnMainThread( + HttpMethod method, string url, IDictionary headers, WWWForm form) { + var requestStatus = new RequestStatus(); + RunOnMainThread.Run(() => { + requestStatus.Request = StartRequest(method, url, headers, form); + }); + return requestStatus; + } + + /// + /// Start a web request. + /// + /// Method to use. + /// Target URL. + /// Headers to use when performing the request. + /// Payload to send if this is a Post request, ignored otherwise. + /// Request object that provides the status of the request. + private static object StartRequest(HttpMethod method, string url, + IDictionary headers, + WWWForm form) { + object unityRequest = null; + if (FindAndCacheRequestType()) { + if (requestType.Name == "WWW") { + var uploadHeaders = new Dictionary(); + if (headers != null) { + foreach (var kv in headers) { + uploadHeaders[kv.Key] = kv.Value; + } + } + // Need to manually add the Content-Type header when sending a form as the + // constructor that takes a WWWForm that doesn't allow the specification of + // headers. + if (method == HttpMethod.Post) { + uploadHeaders["Content-Type"] = "application/x-www-form-urlencoded"; + } + object[] args = method == HttpMethod.Get ? + new object[] { url, null, uploadHeaders } : + new object[] { url, form.data, uploadHeaders }; + unityRequest = Activator.CreateInstance(requestType, args); + } else if (requestType.Name == "UnityWebRequest") { + switch (method) { + case HttpMethod.Post: + unityRequest = postMethod.Invoke(null, new object[] { url, form }); + break; + case HttpMethod.Get: + unityRequest = getMethod.Invoke(null, new object[] { url }); + break; + } + if (headers != null) { + foreach (var kv in headers) { + setRequestHeaderMethod.Invoke(unityRequest, + new object[] { kv.Key, kv.Value }); + } + } + if (unityRequest != null) sendMethod.Invoke(unityRequest, null); + } + } + return unityRequest; + + } + + /// + /// Post to a URL. + /// + /// URL to send data to. + /// Headers to use when performing the request. + /// Form fields to URL encode and send. + /// Web request if successfully started, null otherwise. + public IPortableWebRequestStatus Post( + string url, IDictionary headers, + IEnumerable> formFields) { + var form = new WWWForm(); + if (formFields != null) { + foreach (var formField in formFields) form.AddField(formField.Key, formField.Value); + } + + try { + return StartRequestOnMainThread(HttpMethod.Post, url, headers, form); + } catch (Exception ex) { + logger.Log(String.Format("Failed to send post request {0}", ex), + level: LogLevel.Verbose); + return null; + } + } + + /// + /// Post to a URL. + /// + /// URL path to send data to. + /// Query parameters to be appended to the URL. + /// Headers to use when performing the request. + /// Form fields to URL encode and send. + /// Web request if successfully started, null otherwise. + public IPortableWebRequestStatus Post(string path, + IEnumerable> queryParams, + IDictionary headers, + IEnumerable> formFields) { + var url = new StringBuilder(256); + foreach (var param in queryParams) { + url.AppendFormat("{0}{1}={2}", + url.Length == 0 ? "?" : "&", + Uri.EscapeDataString(param.Key).Trim(), + Uri.EscapeDataString(param.Value).Trim()); + } + url.Insert(0, path); + return Post(url.ToString(), headers, formFields); + } + + /// + /// Get the contents of a URL. + /// + /// URL to retrieve data from. + /// Headers to use when performing the request. + public IPortableWebRequestStatus Get(string url, IDictionary headers) { + try { + return StartRequestOnMainThread(HttpMethod.Get, url, headers, null); + } catch (Exception ex) { + logger.Log(String.Format("Failed to send get request {0}", ex), + level: LogLevel.Verbose); + return null; + } + } +} + +} diff --git a/source/VersionHandlerImpl/src/ProjectSettings.cs b/source/VersionHandlerImpl/src/ProjectSettings.cs index cd07cfce..a2c0001a 100644 --- a/source/VersionHandlerImpl/src/ProjectSettings.cs +++ b/source/VersionHandlerImpl/src/ProjectSettings.cs @@ -17,36 +17,348 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text; using System.Xml; using UnityEditor; namespace Google { + /// + /// Enum to determine where a setting should be loaded from or saved to. + /// + [Flags] + public enum SettingsLocation { + + /// + /// Load from / save to the project settings. + /// + Project = (1 << 0), + + /// + /// Load from / save to system wide settings. + /// + System = (1 << 1), + + /// + /// Load from project settings if available and fallback to system wide settings if it + /// isn't present. Save to both project settings and system-wide settings. + /// + All = (1 << 0) | (1 << 1), + } + + + /// + /// Interface for ProjectSettings and EditorPrefs used for testing. + /// This is a UnityEditor.EditorPrefs compatible interface. + /// + public interface ISettings { + /// + /// Set an int property. + /// + /// Name of the value. + /// Value to set. + void SetInt(string name, int value); + + /// + /// Set a bool property. + /// + /// Name of the value. + /// Value to set. + void SetBool(string name, bool value); + + /// + /// Set a float property. + /// + /// Name of the value. + /// Value to set. + void SetFloat(string name, float value); + + /// + /// Set a string property. + /// + /// Name of the value. + /// Value to set. + void SetString(string name, string value); + + /// + /// Get an int property. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + int GetInt(string name, int defaultValue = 0); + + /// + /// Get a bool property. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + bool GetBool(string name, bool defaultValue = false); + + /// + /// Get a float property. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + float GetFloat(string name, float defaultValue = 0.0f); + + /// + /// Get a string property. + /// This falls back to application-wide settings to allow for users to transition + /// from application to project level settings. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + string GetString(string name, string defaultValue = ""); + + /// + /// Determine whether a setting is set. + /// + /// Name of the value to query. + bool HasKey(string name); + + /// + /// Remove a setting. + /// + /// Name of the value to delete. + void DeleteKey(string name); + + /// + /// Get all setting keys. + /// + IEnumerable Keys { get; } + } + + /// + /// Default implementation of system wide settings. + /// + internal class EditorSettings : ISettings { + /// + /// Set a int property. + /// + /// Name of the value. + /// Value to set. + public void SetInt(string name, int value) { EditorPrefs.SetInt(name, value); } + + /// + /// Set a bool property. + /// + /// Name of the value. + /// Value to set. + public void SetBool(string name, bool value) { EditorPrefs.SetBool(name, value); } + + /// + /// Set a float property. + /// + /// Name of the value. + /// Value to set. + public void SetFloat(string name, float value) { EditorPrefs.SetFloat(name, value); } + + /// + /// Set a string property. + /// + /// Name of the value. + /// Value to set. + public void SetString(string name, string value) { EditorPrefs.SetString(name, value); } + + /// + /// Get an int property. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + public int GetInt(string name, int defaultValue = 0) { + return EditorPrefs.GetInt(name, defaultValue); + } + + /// + /// Get a bool property. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + public bool GetBool(string name, bool defaultValue = false) { + return EditorPrefs.GetBool(name, defaultValue); + } + + /// + /// Get a float property. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + public float GetFloat(string name, float defaultValue = 0.0f) { + return EditorPrefs.GetFloat(name, defaultValue); + } + + /// + /// Get a string property. + /// This falls back to application-wide settings to allow for users to transition + /// from application to project level settings. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + public string GetString(string name, string defaultValue = "") { + return EditorPrefs.GetString(name, defaultValue); + } + + /// + /// Determine whether a setting is set. + /// + /// Name of the value to query. + public bool HasKey(string name) { return EditorPrefs.HasKey(name); } + + /// + /// Remove a setting. + /// + /// Name of the value to delete. + public void DeleteKey(string name) { EditorPrefs.DeleteKey(name); } + + /// + /// Get all setting keys. + /// + public IEnumerable Keys { + get { + throw new NotImplementedException("It is not advised to get all system-wide " + + "settings."); + } + } + } + + /// + /// In-memory settings storage. + /// + internal class InMemorySettings : ISettings { + /// + /// In-memory storage for settings. + /// + private SortedDictionary settings = new SortedDictionary(); + + /// + /// Set a project level setting. + /// + /// Name of the value. + /// Value to set. + private void Set(string name, T value) { + settings[name] = value.ToString(); + } + + /// + /// Set a int property. + /// + /// Name of the value. + /// Value to set. + public void SetInt(string name, int value) { Set(name, value); } + + /// + /// Set a bool property. + /// + /// Name of the value. + /// Value to set. + public void SetBool(string name, bool value) { Set(name, value); } + + /// + /// Set a float property. + /// + /// Name of the value. + /// Value to set. + public void SetFloat(string name, float value) { Set(name, value); } + + /// + /// Set a string property. + /// + /// Name of the value. + /// Value to set. + public void SetString(string name, string value) { Set(name, value); } + + /// + /// Get a project level setting. + /// + /// Name of the value. + /// Default value of the setting if it's not set. + public int GetInt(string name, int defaultValue = 0) { + int value; + if (Int32.TryParse(GetString(name, defaultValue.ToString()), out value)) { + return value; + } + return defaultValue; + } + + /// + /// Get a project level setting. + /// + /// Name of the value. + /// Default value of the setting if it's not set. + public bool GetBool(string name, bool defaultValue = false) { + bool value; + if (Boolean.TryParse(GetString(name, defaultValue.ToString()), out value)) { + return value; + } + return defaultValue; + } + + /// + /// Get a project level setting. + /// + /// Name of the value. + /// Default value of the setting if it's not set. + public float GetFloat(string name, float defaultValue = 0.0f) { + float value; + if (Single.TryParse(GetString(name, defaultValue.ToString()), out value)) { + return value; + } + return defaultValue; + } + + /// + /// Get a project level setting. + /// + /// Name of the value. + /// Default value of the setting if it's not set. + public string GetString(string name, string defaultValue = "") { + string stringValue; + if (settings.TryGetValue(name, out stringValue)) { + return stringValue; + } + return defaultValue; + } + + /// + /// Determine whether a setting is set. + /// + /// Name of the value to query. + public bool HasKey(string name) { + string ignoredValue; + return settings.TryGetValue(name, out ignoredValue); + } + + /// + /// Remove a setting. + /// + /// Name of the value to delete. + public void DeleteKey(string name) { + settings.Remove(name); + } + + /// + /// Get all setting keys. + /// + public IEnumerable Keys { get { return settings.Keys; } } + } + /// /// Provides storage of project or global settings. /// This class is compatible with UnityEditor.EditorPrefs allowing a user to read from /// either application or project level settings based upon the UseProjectSettings flag. /// - internal class ProjectSettings { + public class ProjectSettings : ISettings { /// - /// Enum to determine if setting should be saved to system-level EditorPrefs + /// Whether to load settings from and save settings to disk. + /// Exposed for testing. /// - public enum SettingsSave { - ProjectOnly, - EditorPrefs, - BothProjectAndEditorPrefs - } + internal static bool persistenceEnabled = true; /// /// File to store project level settings. /// - private static readonly string PROJECT_SETTINGS_FILE = Path.Combine( + internal static readonly string PROJECT_SETTINGS_FILE = Path.Combine( "ProjectSettings", "GvhProjectSettings.xml"); - /// - /// Backing store for Settings property. - /// - private static SortedDictionary settings; - /// /// Used to lock the static class; /// @@ -54,14 +366,50 @@ public enum SettingsSave { /// /// Logger used to log messages when loading / saving settings. + /// Exposed for testing. + /// + internal static readonly Logger logger = new Logger(); + + /// + /// Delegate that checks out a file. + /// + internal delegate bool CheckoutFileDelegate(string filename, Logger logger); + + /// + /// Function that checks out a file. + /// Exposed for testing. + /// + internal static CheckoutFileDelegate checkoutFile = (filename, logger) => { + return FileUtils.CheckoutFile(filename, logger); + }; + + /// + /// Access system wide settings. + /// Exposed for testing. + /// + internal static ISettings systemSettings = new EditorSettings(); + + /// + /// Access project settings + /// Exposed for testing. /// - private static readonly Logger logger = new Logger(); + internal static ISettings projectSettings = new InMemorySettings(); + + /// + /// Project settings that have been loaded. + /// + private static ISettings loadedSettings = null; /// /// Name of the module used to control whether project or global settings are used. /// private readonly string moduleName; + /// + /// Get the module name prefix. + /// + public string ModuleName { get { return moduleName; } } + /// /// Create an instance of the settings class. /// @@ -73,20 +421,11 @@ public ProjectSettings(string moduleName) { this.moduleName = moduleName; } - /// - /// In-memory cache of project specific settings. - /// - private static SortedDictionary Settings { - get { - LoadIfEmpty(); - return settings; - } - } - /// /// Name of the setting that controls whether project settings are being used. + /// Exposed for testing only. /// - private string UseProjectSettingsName { + internal string UseProjectSettingsName { get { return moduleName + "UseProjectSettings"; } } @@ -95,46 +434,41 @@ private string UseProjectSettingsName { /// the application using UnityEditor.EditorPrefs. /// public bool UseProjectSettings { - get { return EditorPrefs.GetBool(UseProjectSettingsName, true); } - set { EditorPrefs.SetBool(UseProjectSettingsName, value); } + get { return systemSettings.GetBool(UseProjectSettingsName, true); } + set { systemSettings.SetBool(UseProjectSettingsName, value); } } /// - /// Load settings if they're not loaded. + /// Get the location to fetch settings from. /// - private static void LoadIfEmpty() { - lock (classLock) { - if (settings == null) Load(); - } + private SettingsLocation GetLocation { + get { return UseProjectSettings ? SettingsLocation.Project : SettingsLocation.System; } } /// - /// Set a project level setting. + /// Set an int property. /// /// Name of the value. /// Value to set. - private static void Set(string name, T value) { - lock (classLock) { - Settings[name] = value.ToString(); - Save(); + /// Where to save the setting. + public void SetInt(string name, int value, SettingsLocation location) { + if ((location & SettingsLocation.Project) != 0) { + lock (classLock) { + LoadIfEmpty(); + projectSettings.SetInt(name, value); + Save(); + } } + if ((location & SettingsLocation.System) != 0) systemSettings.SetInt(name, value); } - private void SavePreferences(SettingsSave saveLevel, Action saveToProject, Action - saveToEditor) { - switch (saveLevel) { - case SettingsSave.ProjectOnly: - saveToProject(); - break; - case SettingsSave.EditorPrefs: - saveToEditor(); - break; - case SettingsSave.BothProjectAndEditorPrefs: - default: - saveToEditor(); - saveToProject(); - break; - } + /// + /// Set an int property. + /// + /// Name of the value. + /// Value to set. + public void SetInt(string name, int value) { + SetInt(name, value, SettingsLocation.All); } /// @@ -142,17 +476,25 @@ private void SavePreferences(SettingsSave saveLevel, Action saveToProject, Actio /// /// Name of the value. /// Value to set. - /// Determine how setting should save - public void SetBool(string name, bool value, SettingsSave saveLevel) { - SavePreferences(saveLevel, - () => { Set(name, value); }, - () => { EditorPrefs.SetBool(name, value); }); + /// Where to save the setting. + public void SetBool(string name, bool value, SettingsLocation location) { + if ((location & SettingsLocation.Project) != 0) { + lock (classLock) { + LoadIfEmpty(); + projectSettings.SetBool(name, value); + Save(); + } + } + if ((location & SettingsLocation.System) != 0) systemSettings.SetBool(name, value); } + /// + /// Set a bool property. + /// + /// Name of the value. + /// Value to set. public void SetBool(string name, bool value) { - SavePreferences(SettingsSave.BothProjectAndEditorPrefs, - () => { Set(name, value); }, - () => { EditorPrefs.SetBool(name, value); }); + SetBool(name, value, SettingsLocation.All); } /// @@ -160,123 +502,141 @@ public void SetBool(string name, bool value) { /// /// Name of the value. /// Value to set. - public void SetFloat(string name, float value, SettingsSave saveLevel) { - SavePreferences(saveLevel, () => { Set(name, value); }, - () => { EditorPrefs.SetFloat(name, value); }); + /// Where to save the setting. + public void SetFloat(string name, float value, SettingsLocation location) { + if ((location & SettingsLocation.Project) != 0) { + lock (classLock) { + LoadIfEmpty(); + projectSettings.SetFloat(name, value); + Save(); + } + } + if ((location & SettingsLocation.System) != 0) systemSettings.SetFloat(name, value); } + /// + /// Set a float property. + /// + /// Name of the value. + /// Value to set. public void SetFloat(string name, float value) { - SavePreferences(SettingsSave.BothProjectAndEditorPrefs, - () => { Set(name, value); }, - () => { EditorPrefs.SetFloat(name, value); }); + SetFloat(name, value, SettingsLocation.All); } /// - /// Set a int property. + /// Set a string property. /// /// Name of the value. /// Value to set. - public void SetInt(string name, int value, SettingsSave saveLevel) { - SavePreferences(saveLevel, () => { Set(name, value); }, - () => { EditorPrefs.SetInt(name, value); }); - } - - public void SetInt(string name, int value) { - SavePreferences(SettingsSave.BothProjectAndEditorPrefs, - () => { Set(name, value); }, - () => { EditorPrefs.SetInt(name, value); }); + /// Where to save the setting. + public void SetString(string name, string value, SettingsLocation location) { + if ((location & SettingsLocation.Project) != 0) { + lock (classLock) { + LoadIfEmpty(); + projectSettings.SetString(name, value); + Save(); + } + } + if ((location & SettingsLocation.System) != 0) systemSettings.SetString(name, value); } - /// /// Set a string property. /// /// Name of the value. /// Value to set. - public void SetString(string name, string value, SettingsSave saveLevel) { - SavePreferences(saveLevel, () => { Set(name, value); }, - () => { EditorPrefs.SetString(name, value); }); - } - public void SetString(string name, string value) { - SavePreferences(SettingsSave.BothProjectAndEditorPrefs, () => { Set(name, value); }, - () => { EditorPrefs.SetString(name, value); }); + SetString(name, value, SettingsLocation.All); } /// - /// Get a project level setting. + /// Load project settings if they're not loaded or have been changed. /// - /// Name of the value. - /// Default value of the setting if it's not set. - private static string Get(string name, string defaultValue) { + private static void LoadIfEmpty() { lock (classLock) { - string stringValue; - if (Settings.TryGetValue(name, out stringValue)) { - return stringValue; + if (loadedSettings != projectSettings) { + Load(); + loadedSettings = projectSettings; } } - return defaultValue; } /// - /// Get a project level setting. + /// Get an int property. /// /// Name of the value. - /// Default value of the setting if it's not set. - private static bool Get(string name, bool defaultValue) { - bool value; - if (Boolean.TryParse(Get(name, defaultValue.ToString()), out value)) { - return value; + /// Default value of the property if it isn't set. + /// Where to read the setting from. + public int GetInt(string name, int defaultValue, SettingsLocation location) { + int value = defaultValue; + if ((location & SettingsLocation.System) != 0) { + value = systemSettings.GetInt(name, value); } - return defaultValue; + if ((location & SettingsLocation.Project) != 0) { + lock (classLock) { + LoadIfEmpty(); + value = projectSettings.GetInt(name, value); + } + } + return value; } /// - /// Get a project level setting. + /// Get an int property. /// /// Name of the value. - /// Default value of the setting if it's not set. - private static float Get(string name, float defaultValue) { - float value; - if (Single.TryParse(Get(name, defaultValue.ToString()), out value)) { - return value; - } - return defaultValue; + /// Default value of the property if it isn't set. + public int GetInt(string name, int defaultValue = 0) { + return GetInt(name, defaultValue, GetLocation); } /// - /// Get a project level setting. + /// Get a bool property. /// /// Name of the value. - /// Default value of the setting if it's not set. - private static int Get(string name, int defaultValue) { - int value; - if (Int32.TryParse(Get(name, defaultValue.ToString()), out value)) { - return value; + /// Default value of the property if it isn't set. + /// Where to read the setting from. + public bool GetBool(string name, bool defaultValue, SettingsLocation location) { + bool value = defaultValue; + if ((location & SettingsLocation.System) != 0) { + value = systemSettings.GetBool(name, value); } - return defaultValue; + if ((location & SettingsLocation.Project) != 0) { + lock (classLock) { + LoadIfEmpty(); + value = projectSettings.GetBool(name, value); + } + } + return value; } /// - /// Get a string property. - /// This falls back to application-wide settings to allow for users to transition - /// from application to project level settings. + /// Get a bool property. /// /// Name of the value. /// Default value of the property if it isn't set. - public string GetString(string name, string defaultValue = "") { - var systemValue = EditorPrefs.GetString(name, defaultValue: defaultValue); - return UseProjectSettings ? Get(name, systemValue) : systemValue; + public bool GetBool(string name, bool defaultValue = false) { + return GetBool(name, defaultValue, GetLocation); } /// - /// Get a bool property. + /// Get a float property. /// /// Name of the value. /// Default value of the property if it isn't set. - public bool GetBool(string name, bool defaultValue = false) { - var systemValue = EditorPrefs.GetBool(name, defaultValue: defaultValue); - return UseProjectSettings ? Get(name, systemValue) : systemValue; + /// Where to read the setting from. + public float GetFloat(string name, float defaultValue, SettingsLocation location) { + float value = defaultValue; + if ((location & SettingsLocation.System) != 0) { + value = systemSettings.GetFloat(name, value); + } + if ((location & SettingsLocation.Project) != 0) { + lock (classLock) { + LoadIfEmpty(); + value = projectSettings.GetFloat(name, value); + } + } + return value; } /// @@ -285,52 +645,77 @@ public bool GetBool(string name, bool defaultValue = false) { /// Name of the value. /// Default value of the property if it isn't set. public float GetFloat(string name, float defaultValue = 0.0f) { - var systemValue = EditorPrefs.GetFloat(name, defaultValue: defaultValue); - return UseProjectSettings ? Get(name, systemValue) : systemValue; + return GetFloat(name, defaultValue, GetLocation); } /// - /// Get an int property. + /// Get a string property. /// /// Name of the value. /// Default value of the property if it isn't set. - public int GetInt(string name, int defaultValue = 0) { - var systemValue = EditorPrefs.GetInt(name, defaultValue: defaultValue); - return UseProjectSettings ? Get(name, systemValue) : systemValue; + /// Where to read the setting from. + public string GetString(string name, string defaultValue, SettingsLocation location) { + string value = defaultValue; + if ((location & SettingsLocation.System) != 0) { + value = systemSettings.GetString(name, value); + } + if ((location & SettingsLocation.Project) != 0) { + lock (classLock) { + LoadIfEmpty(); + value = projectSettings.GetString(name, value); + } + } + return value; + } + + /// + /// Get a string property. + /// This falls back to application-wide settings to allow for users to transition + /// from application to project level settings. + /// + /// Name of the value. + /// Default value of the property if it isn't set. + public string GetString(string name, string defaultValue = "") { + return GetString(name, defaultValue, GetLocation); } /// /// Determine whether a setting is set. /// /// Name of the value to query. - public bool HasKey(string name) { - if (UseProjectSettings) { - string ignoredValue; + /// Where to search for the setting. + public bool HasKey(string name, SettingsLocation location) { + bool hasKey = false; + if ((location & SettingsLocation.Project) != 0) { lock (classLock) { - return Settings.TryGetValue(name, out ignoredValue); + LoadIfEmpty(); + hasKey |= projectSettings.HasKey(name); } - } else { - return EditorPrefs.HasKey(name); } + if (!hasKey && (location & SettingsLocation.System) != 0) { + hasKey |= systemSettings.HasKey(name); + } + return hasKey; } /// - /// Remove all settings. + /// Determine whether a setting is set. /// - public void DeleteAll() { - EditorPrefs.DeleteAll(); - Clear(); - Save(); + /// Name of the value to query. + public bool HasKey(string name) { + return HasKey(name, GetLocation); } + /// /// Remove a setting. /// /// Name of the value to delete. public void DeleteKey(string name) { - EditorPrefs.DeleteKey(name); + systemSettings.DeleteKey(name); lock (classLock) { - Settings.Remove(name); + LoadIfEmpty(); + projectSettings.DeleteKey(name); Save(); } } @@ -339,18 +724,38 @@ public void DeleteKey(string name) { /// Delete the specified set of keys (this will revert to default settings). /// /// Names of the values to delete. - internal void DeleteKeys(IEnumerable names) { - foreach (var name in names) { - if (HasKey(name)) DeleteKey(name); + public void DeleteKeys(IEnumerable names) { + lock (classLock) { + LoadIfEmpty(); + foreach (var name in names) { + systemSettings.DeleteKey(name); + projectSettings.DeleteKey(name); + } + Save(); } } /// - /// Clear in-memory settings. + /// Delete all project level settings. + /// Exposed for testing. /// - private static void Clear() { + /// Whether to save the settings. + internal static void DeleteAllProjectKeys(bool save = true) { lock (classLock) { - settings = new SortedDictionary(); + if (save) LoadIfEmpty(); + foreach (var key in new List(projectSettings.Keys)) { + projectSettings.DeleteKey(key); + } + if (save) Save(); + } + } + + /// + /// Get all setting keys. + /// + public IEnumerable Keys { + get { + throw new NotImplementedException("It is not possible to get all system keys."); } } @@ -368,8 +773,8 @@ private static void Clear() { /// true if settings are successfully loaded, false otherwise. private static bool Load() { lock (classLock) { - Clear(); - if (!XmlUtilities.ParseXmlTextFileElements( + DeleteAllProjectKeys(false); + if (!persistenceEnabled || !XmlUtilities.ParseXmlTextFileElements( PROJECT_SETTINGS_FILE, logger, (reader, elementName, isStart, parentElementName, elementNameStack) => { if (elementName == "projectSettings" && parentElementName == "") { @@ -377,13 +782,13 @@ private static bool Load() { } else if (elementName == "projectSetting" && parentElementName == "projectSettings") { if (isStart) { - var name = reader.GetAttribute("name"); + var key = reader.GetAttribute("name"); var value = reader.GetAttribute("value"); - if (!String.IsNullOrEmpty(name)) { + if (!String.IsNullOrEmpty(key)) { if (String.IsNullOrEmpty(value)) { - settings.Remove(name); + projectSettings.DeleteKey(key); } else { - settings[name] = value; + projectSettings.SetString(key, value); } } } @@ -402,29 +807,46 @@ private static bool Load() { /// private static void Save() { lock (classLock) { - if (settings == null) { + if (projectSettings == null || !persistenceEnabled) { return; } Directory.CreateDirectory(Path.GetDirectoryName(PROJECT_SETTINGS_FILE)); - if (!FileUtils.CheckoutFile(PROJECT_SETTINGS_FILE, logger)) { + if (!checkoutFile(PROJECT_SETTINGS_FILE, logger)) { logger.Log( String.Format("Unable to checkout '{0}'. Project settings were not saved!", PROJECT_SETTINGS_FILE), LogLevel.Error); return; } - using (var writer = new XmlTextWriter(new StreamWriter(PROJECT_SETTINGS_FILE)) { - Formatting = Formatting.Indented, - }) { - writer.WriteStartElement("projectSettings"); - foreach (var kv in settings) { - writer.WriteStartElement("projectSetting"); - if (!String.IsNullOrEmpty(kv.Key) && !String.IsNullOrEmpty(kv.Value)) { - writer.WriteAttributeString("name", kv.Key); - writer.WriteAttributeString("value", kv.Value); + try { + using (var writer = + XmlWriter.Create(PROJECT_SETTINGS_FILE, + new XmlWriterSettings { + Encoding = new UTF8Encoding(false), + Indent = true, + IndentChars = " ", + NewLineChars = "\n", + NewLineHandling = NewLineHandling.Replace + })) { + writer.WriteStartElement("projectSettings"); + foreach (var key in projectSettings.Keys) { + var value = projectSettings.GetString(key); + writer.WriteStartElement("projectSetting"); + if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value)) { + writer.WriteAttributeString("name", key); + writer.WriteAttributeString("value", value); + } + writer.WriteEndElement(); } writer.WriteEndElement(); } - writer.WriteEndElement(); + } catch (Exception exception) { + if (exception is IOException || exception is UnauthorizedAccessException) { + logger.Log(String.Format("Unable to write to '{0}' ({1}, " + + "Project settings were not saved!", + PROJECT_SETTINGS_FILE, exception), LogLevel.Error); + return; + } + throw exception; } } } diff --git a/source/VersionHandlerImpl/src/RunOnMainThread.cs b/source/VersionHandlerImpl/src/RunOnMainThread.cs index 156b02d6..0c3a11dd 100644 --- a/source/VersionHandlerImpl/src/RunOnMainThread.cs +++ b/source/VersionHandlerImpl/src/RunOnMainThread.cs @@ -78,7 +78,7 @@ public static int Schedule(Action job, double delayInMilliseconds) { scheduledJob = new ScheduledJob { Job = job, JobId = nextJobId, - DelayInMilliseconds = ExecutionEnvironment.InBatchMode ? 0.0 : + DelayInMilliseconds = ExecutionEnvironment.ExecuteMethodEnabled ? 0.0 : delayInMilliseconds }; scheduledJobs[nextJobId++] = scheduledJob; @@ -130,6 +130,107 @@ public bool PollUntilExecutionTime() { } } + /// + /// Enqueues jobs on the main thread to that need to be executed in serial order. + /// + public class JobQueue { + + /// + /// Queue of jobs to execute. + /// + private Queue jobs = new Queue(); + + /// + /// Execute the next job. + /// + private void ExecuteNext() { + var job = jobs.Peek(); + try { + job(); + } catch (Exception e) { + UnityEngine.Debug.LogError( + String.Format("Serial job {0} failed due to exception: {1}", + job, e.ToString())); + Complete(); + } + } + + /// + /// Schedule the execution of a job. + /// + /// Action that will be called in future. This job should call + /// Complete() to signal the end of the operation. + public void Schedule(Action job) { + RunOnMainThread.Run(() => { + jobs.Enqueue(job); + if (jobs.Count == 1) ExecuteNext(); + }, runNow: false); + } + + /// + /// Signal the end of job execution. + /// + public void Complete() { + RunOnMainThread.Run(() => { + var remaining = jobs.Count; + if (remaining > 0) jobs.Dequeue(); + if (remaining > 1) ExecuteNext(); + }, runNow: false); + } + } + + /// + /// Job which calls a function periodically over an interval. + /// + public class PeriodicJob { + + /// + /// ID of the next update. + /// + private int jobId; + + /// + /// Closure to execute on each update. + /// + private Func condition; + + /// + /// Interval to wait between each execution of the job. + /// + public double IntervalInMilliseconds; + + /// + /// Construct a periodic job. + /// + /// Method that returns true when the operation is complete, false + /// otherwise. + public PeriodicJob(Func condition) { + this.condition = condition; + } + + /// + /// Execute the condition and if it isn't complete, schedule the next execution. + /// + public void Execute() { + if (condition != null) { + if (!condition()) { + jobId = RunOnMainThread.Schedule(() => { Execute(); }, IntervalInMilliseconds); + } else { + Stop(); + } + } + } + + /// + /// Stop periodic execution of the job. + /// + public void Stop() { + RunOnMainThread.Cancel(jobId); + jobId = 0; + condition = null; + } + } + /// /// ID of the main thread. /// @@ -158,6 +259,11 @@ private static bool OnMainThread { get { return mainThreadId == System.Threading.Thread.CurrentThread.ManagedThreadId; } } + /// + /// Number of times ExecuteAll() has been called on the current thread. + /// + private static int runningExecuteAllCount = 0; + /// /// Flag which indicates whether any jobs are running on the main thread. /// This is set and cleared by RunAction(). @@ -169,7 +275,10 @@ private static bool OnMainThread { /// This property is reset to its' default value after each set of jobs is dispatched. /// public static bool ExecuteNow { - get { return ExecutionEnvironment.InBatchMode && !runningJobs; } + get { + return ExecutionEnvironment.ExecuteMethodEnabled && !runningJobs && + runningExecuteAllCount == 0; + } } /// @@ -180,7 +289,7 @@ static RunOnMainThread() { mainThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId; // NOTE: This hooks ExecuteAll on the main thread here and never unregisters as we can't // register event handlers on any thread except for the main thread. - if (!ExecutionEnvironment.InBatchMode) OnUpdate += ExecuteAll; + OnUpdate += ExecuteAll; } /// @@ -207,9 +316,9 @@ private static void AddOnUpdateCallback(EditorApplication.CallbackFunction callb Run(() => { EditorApplication.update -= callback; EditorApplication.update += callback; - // If we're in batch mode, execute the callback now as EditorApplication.update - // will not be signaled if Unity was launched to execute a single method. - if (ExecutionEnvironment.InBatchMode) callback(); + // If we're in running a single method, execute the callback now as + // EditorApplication.update will not be signaled. + if (ExecutionEnvironment.ExecuteMethodEnabled) callback(); }); } @@ -253,7 +362,7 @@ public static void PollOnUpdateUntilComplete(Func condition, bool synchron if (ExecuteNow && OnMainThread) { RunAction(() => { while (true) { - ExecuteAll(); + ExecuteAllUnnested(true); lock (pollingJobs) { if (pollingJobs.Count == 0) break; } @@ -267,7 +376,7 @@ public static void PollOnUpdateUntilComplete(Func condition, bool synchron if (!pollingJobs.Contains(condition)) break; } if (OnMainThread) { - ExecuteAll(); + ExecuteAllUnnested(true); } else { // Wait 100ms. Thread.Sleep(100); @@ -280,7 +389,7 @@ public static void PollOnUpdateUntilComplete(Func condition, bool synchron /// Execute polling jobs, removing completed jobs from the list. /// This method must be called from the main thread. /// - /// Number of jobs remaining in the polling job list. + /// Number of jobs remaining in the polling job list. private static int ExecutePollingJobs() { int numberOfPollingJobs; bool completedJobs = false; @@ -359,13 +468,13 @@ public static void Run(Action job, bool runNow = true) { // as part of the process until the queue is empty. // If we're not executing the job right now, the job queue is pumped from the UnityEditor // update event. - if ((firstJob || ExecuteNow) && runNow && OnMainThread) { - ExecuteAll(); + if (firstJob && (runNow || ExecuteNow) && OnMainThread) { + ExecuteAllUnnested(false); } } /// - /// Execute the next resolve job on the queue. + /// Execute the next job on the queue. /// private static bool ExecuteNext() { Action nextJob = null; @@ -388,7 +497,7 @@ private static bool ExecuteNext() { /// true if the caller is on the main thread, false otherwise. public static bool TryExecuteAll() { if (OnMainThread) { - ExecuteAll(); + ExecuteAllUnnested(true); return true; } return false; @@ -397,22 +506,46 @@ public static bool TryExecuteAll() { /// /// Execute all scheduled jobs and remove from the update loop if no jobs are remaining. /// + /// Force execution when a re-entrant call of this method is detected. + /// This is useful when an application is forcing execution to block the main thread. private static void ExecuteAll() { + ExecuteAllUnnested(false); + } + + /// + /// Execute all scheduled jobs and remove from the update loop if no jobs are remaining. + /// + /// Force execution when a re-entrant call of this method is detected. + /// This is useful when an application is forcing execution to block the main thread. + private static void ExecuteAllUnnested(bool allowNested) { if (!OnMainThread) { UnityEngine.Debug.LogError("ExecuteAll must be executed from the main thread."); return; } + // Don't nest job execution on the main thread, return to the last stack frame + // running ExecuteAll(). + if (runningExecuteAllCount > 0 && !allowNested) return; + RunAction(() => { - // Execute jobs. - while (ExecuteNext()) { - } + runningExecuteAllCount ++; + bool jobsRemaining = true; + while (jobsRemaining) { + jobsRemaining = false; + // Execute jobs. + while (ExecuteNext()) { + jobsRemaining = true; + } - // Execute polling jobs. - int remainingJobs; - do { - remainingJobs = ExecutePollingJobs(); - } while (remainingJobs > 0 && ExecutionEnvironment.InBatchMode); + // Execute polling jobs. + int remainingJobs = ExecutePollingJobs(); + // If we're in running a single method, keep on executing until no polling jobs + // remain. + if (ExecutionEnvironment.ExecuteMethodEnabled && remainingJobs > 0) { + jobsRemaining = true; + } + } + runningExecuteAllCount --; }); } } diff --git a/source/VersionHandlerImpl/src/SettingsDialog.cs b/source/VersionHandlerImpl/src/SettingsDialog.cs index ea75ae48..30815c22 100644 --- a/source/VersionHandlerImpl/src/SettingsDialog.cs +++ b/source/VersionHandlerImpl/src/SettingsDialog.cs @@ -17,6 +17,7 @@ namespace Google { using System; +using System.Collections.Generic; using UnityEditor; using UnityEngine; @@ -59,6 +60,11 @@ private class Settings { /// internal bool renameToDisableFilesEnabled; + /// + /// Analytics settings. + /// + internal EditorMeasurement.Settings analyticsSettings; + /// /// Load settings into the dialog. /// @@ -69,6 +75,7 @@ internal Settings() { verboseLoggingEnabled = VersionHandlerImpl.VerboseLoggingEnabled; useProjectSettings = VersionHandlerImpl.UseProjectSettings; renameToDisableFilesEnabled = VersionHandlerImpl.RenameToDisableFilesEnabled; + analyticsSettings = new EditorMeasurement.Settings(VersionHandlerImpl.analytics); } /// @@ -81,7 +88,7 @@ internal void Save() { VersionHandlerImpl.VerboseLoggingEnabled = verboseLoggingEnabled; VersionHandlerImpl.UseProjectSettings = useProjectSettings; VersionHandlerImpl.RenameToDisableFilesEnabled = renameToDisableFilesEnabled; - + analyticsSettings.Save(); VersionHandlerImpl.BuildTargetChecker.HandleSettingsChanged(); } } @@ -99,10 +106,11 @@ private void LoadSettings() { /// Setup the window's initial position and size. /// public void Initialize() { - minSize = new Vector2(300, 265); + minSize = new Vector2(300, 288); position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, minSize.x, minSize.y); + VersionHandlerImpl.analytics.Report("settings/show", "Settings"); } /// @@ -150,6 +158,8 @@ public void OnGUI() { EditorGUILayout.Toggle(settings.renameToDisableFilesEnabled); GUILayout.EndHorizontal(); + settings.analyticsSettings.RenderGui(); + GUILayout.BeginHorizontal(); GUILayout.Label("Verbose logging", EditorStyles.boldLabel); settings.verboseLoggingEnabled = EditorGUILayout.Toggle(settings.verboseLoggingEnabled); @@ -160,7 +170,6 @@ public void OnGUI() { settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); GUILayout.EndHorizontal(); - GUILayout.Space(10); if (GUILayout.Button("Reset to Defaults")) { @@ -168,15 +177,37 @@ public void OnGUI() { // saved preferences. var backupSettings = new Settings(); VersionHandlerImpl.RestoreDefaultSettings(); + VersionHandlerImpl.analytics.Report("settings/reset", "Settings Reset"); LoadSettings(); backupSettings.Save(); } GUILayout.BeginHorizontal(); if (GUILayout.Button("Cancel")) { + VersionHandlerImpl.analytics.Report("settings/cancel", "Settings Cancel"); Close(); } if (GUILayout.Button("OK")) { + VersionHandlerImpl.analytics.Report( + "settings/save", + new KeyValuePair[] { + new KeyValuePair( + "enabled", + VersionHandlerImpl.Enabled.ToString()), + new KeyValuePair( + "cleanUpPromptEnabled", + VersionHandlerImpl.CleanUpPromptEnabled.ToString()), + new KeyValuePair( + "renameToCanonicalFilenames", + VersionHandlerImpl.RenameToCanonicalFilenames.ToString()), + new KeyValuePair( + "verboseLoggingEnabled", + VersionHandlerImpl.VerboseLoggingEnabled.ToString()), + new KeyValuePair( + "renameToDisableFilesEnabled", + VersionHandlerImpl.RenameToDisableFilesEnabled.ToString()), + }, + "Settings Save"); settings.Save(); Close(); // If the handler has been enabled, refresh the asset database diff --git a/source/VersionHandlerImpl/src/VersionHandlerImpl.cs b/source/VersionHandlerImpl/src/VersionHandlerImpl.cs index 900b9ae7..f0022556 100644 --- a/source/VersionHandlerImpl/src/VersionHandlerImpl.cs +++ b/source/VersionHandlerImpl/src/VersionHandlerImpl.cs @@ -27,6 +27,11 @@ namespace Google { [InitializeOnLoad] public class VersionHandlerImpl : AssetPostprocessor { + /// + /// A unique class to create the multi-select window to obsolete files. + /// + private class ObsoleteFilesWindow : MultiSelectWindow {} + /// /// Derives metadata from an asset filename. /// @@ -34,6 +39,9 @@ public class FileMetadata { // Splits a filename into components. private class FilenameComponents { + // Known multi-component extensions to strip. + private static readonly string[] MULTI_COMPONENT_EXTENSIONS = new [] { ".dll.mdb" }; + // Name of the file. public string filename; // Directory component. @@ -50,13 +58,17 @@ public FilenameComponents(string filename) { this.filename = filename; directory = Path.GetDirectoryName(filename); basename = Path.GetFileName(filename); - extension = Path.GetExtension(basename); + // Strip known multi-component extensions from the filename. + extension = null; + foreach (var knownExtension in MULTI_COMPONENT_EXTENSIONS) { + if (basename.ToLower().EndsWith(knownExtension)) extension = knownExtension; + } + if (String.IsNullOrEmpty(extension)) 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 @@ -65,17 +77,29 @@ public FilenameComponents(string filename) { // Prefix which identifies the targets metadata in the filename or // asset label. - private static string[] TOKEN_TARGETS = new [] { "targets-", "t" }; + public static string[] TOKEN_TARGETS = new [] { "targets-", "t" }; // Prefix which identifies the version metadata in the filename or // asset label. - private static string[] TOKEN_VERSION = new [] { "version-", "v" }; + public static string[] TOKEN_VERSION = new [] { "version-", "v" }; // Prefix which identifies the .NET version metadata in the filename // or asset label. - private static string[] TOKEN_DOTNET_TARGETS = new [] { "dotnet-" }; + public static string[] TOKEN_DOTNET_TARGETS = new [] { "dotnet-" }; // Prefix which indicates this file is a package manifest. - private static string[] TOKEN_MANIFEST = new [] { "manifest" }; + public static string[] TOKEN_MANIFEST = new [] { "manifest" }; + // Prefix which allows a manifest to specify a human readable package name. + // If the name begins with a digit [0-9] the numeric letters at the start of the string + // are used to order the priority of the package alias. + // For example, given the names: + // - "0current name" + // - "1old name" + // - "another alias" + // will create the list of names ["current name", "old name", "another alias"] where + // "current name" is the current display name of the package. + public static string[] TOKEN_MANIFEST_NAME = new[] { "manifestname-"}; // Prefix which identifies the canonical name of this Linux library. - private static string[] TOKEN_LINUX_LIBRARY_BASENAME = new [] { "linuxlibname-" }; + public static string[] TOKEN_LINUX_LIBRARY_BASENAME = new [] { "linuxlibname-" }; + // Prefix which identifies the original path of a file when the package was exported. + public static string[] TOKEN_EXPORT_PATH = new [] { "exportpath-" }; // Delimiter for version numbers. private static char[] VERSION_DELIMITER = new char[] { '.' }; @@ -86,6 +110,9 @@ public FilenameComponents(string filename) { private static long VERSION_COMPONENT_MULTIPLIER = 1000; // Prefix for labels which encode metadata of an asset. private static string LABEL_PREFIX = "gvh_"; + // Prefix for labels which encode metadata of the asset for 1.2.138 and above. + // These labels are never removed by the version handler. + private static string LABEL_PREFIX_PRESERVE = "gvhp_"; // Initialized depending on the version of unity we are running against private static HashSet targetBlackList = null; // Initialized by parsing BuildTarget enumeration values from @@ -159,6 +186,8 @@ static public Dictionary new Regex("^(editor|" + String.Join( "|", (new List(BUILD_TARGET_NAME_TO_ENUM_NAME.Keys)).ToArray()) + ")$", RegexOptions.IgnoreCase); + // Regular expression which matches an index in a manifest name field. + private static Regex MANIFEST_NAME_REGEX = new Regex("^([0-9]+)(.*)"); /// /// Get a set of build target names mapped to supported BuildTarget @@ -207,8 +236,10 @@ static HashSet GetBlackList() { if (targetBlackList == null) { targetBlackList = new HashSet(); if (VersionHandlerImpl.GetUnityVersionMajorMinor() >= 5.5) { +#pragma warning disable 618 targetBlackList.Add(BuildTarget.PS3); targetBlackList.Add(BuildTarget.XBOX360); +#pragma warning restore 618 } } return targetBlackList; @@ -273,6 +304,18 @@ public static string ScriptingRuntimeDotNetVersion { /// public bool isManifest = false; + /// + /// Offset subtracted from entries inserted in customManifestNames. + /// + private const int CUSTOM_MANIFEST_NAMES_FIRST_INDEX_OFFSET = Int32.MaxValue / 2; + + /// + /// Backing store for aliases of the manifest name, the current package name is always + /// first in the set. This contains only values parsed from manifestname labels, + /// the ManifestName property is used to retrieve the preferred manifest name. + /// + public SortedList customManifestNames = new SortedList(); + /// /// Set if this references an asset which is handled by PluginManager. /// @@ -288,15 +331,92 @@ public static string ScriptingRuntimeDotNetVersion { /// public string linuxLibraryBasename = null; + /// + /// Path of the file when it was originally exported as a package. + /// + public string exportPath = ""; + + /// + /// Path of the file when it was originally exported as a package in the project. + /// + public string ExportPathInProject { + get { + var exportPathInProject = exportPath; + if (!String.IsNullOrEmpty(exportPathInProject)) { + // Remove the assets folder, if specified. + if (FileUtils.IsUnderDirectory(exportPathInProject, FileUtils.ASSETS_FOLDER)) { + exportPathInProject = + exportPath.Substring(FileUtils.ASSETS_FOLDER.Length + 1); + } + // Determine whether this package is installed as a package or asset to + // determine the root directory. + var packageDirectory = FileUtils.GetPackageDirectory(filename); + var installRoot = !String.IsNullOrEmpty(packageDirectory) ? + packageDirectory : FileUtils.ASSETS_FOLDER; + // Translate exportPath into a package relative path. + exportPathInProject = FileUtils.PosixPathSeparators( + Path.Combine(installRoot, exportPathInProject)); + } + return exportPathInProject; + } + } + + /// + /// If this is a manifest, get the display name. + /// + /// If this file is a manifest, returns the display name of the manifest, + /// null otherwise. + public string ManifestName { + get { + string name = null; + bool hasManifestNames = customManifestNames != null && + customManifestNames.Count > 0; + if (isManifest || hasManifestNames) { + if (hasManifestNames) { + name = customManifestNames.Values[0]; + } else { + name = (new FilenameComponents(filenameCanonical)).basenameNoExtension; + } + } + return name; + } + } + + /// + /// Whether it's possible to change the asset metadata. + /// + /// true if the asset metadata for this file is read-only, + /// false otherwise. + public bool IsReadOnly { + get { + return FileUtils.IsUnderPackageDirectory(filename); + } + } + /// /// Parse metadata from filename and store in this class. /// /// Name of the file to parse. public FileMetadata(string filename) { this.filename = FileUtils.NormalizePathSeparators(filename); - filenameCanonical = this.filename; + filenameCanonical = ParseMetadataFromFilename(this.filename); + ParseMetadataFromAssetLabels(); + + // If the export path was specified, override the canonical filename. + var exportPathInProject = ExportPathInProject; + if (!String.IsNullOrEmpty(exportPathInProject)) { + filenameCanonical = ParseMetadataFromFilename(exportPathInProject); + } + UpdateAssetLabels(); + } - var filenameComponents = new FilenameComponents(filename); + /// + /// Parse metadata from the specified filename and store in this class. + /// + /// Parse metadata from the specified filename. + /// Filename with metadata removed. + private string ParseMetadataFromFilename(string filenameToParse) { + var filenameComponents = new FilenameComponents(filenameToParse); // Parse metadata from the filename. string[] tokens = filenameComponents.basenameNoExtension.Split( @@ -310,28 +430,31 @@ public FileMetadata(string filename) { } filenameComponents.basenameNoExtension = basenameNoExtension; } - // 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)) { - ParseLabel(label); - } - - isHandledByPluginImporter = typeof(PluginImporter).IsInstanceOfType(importer); - } - // 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 = FileUtils.NormalizePathSeparators(Path.Combine( + return FileUtils.NormalizePathSeparators(Path.Combine( filenameComponents.directory, filenameComponents.basenameNoExtension + filenameComponents.extension)); - UpdateAssetLabels(); + } + + /// + /// Parse metadata from asset labels. + /// + public void ParseMetadataFromAssetLabels() { + // 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)) { + ParseLabel(label); + } + isHandledByPluginImporter = typeof(PluginImporter).IsInstanceOfType(importer); + } } /// @@ -377,7 +500,24 @@ private bool StringListMatchesRegex(IEnumerable items, Regex regEx) { /// true if the token is parsed, false otherwise. private bool ParseToken(string token, string prefix = null) { prefix = prefix ?? ""; - var values = MatchPrefixesGetValues(token, TOKEN_MANIFEST, prefix); + var values = MatchPrefixesGetValues(token, TOKEN_MANIFEST_NAME, prefix); + if (values != null) { + var name = String.Join(FIELD_SEPARATOR[0].ToString(), values); + var nameMatch = MANIFEST_NAME_REGEX.Match(name); + int order = CUSTOM_MANIFEST_NAMES_FIRST_INDEX_OFFSET - customManifestNames.Count; + if (nameMatch.Success) { + int parsedOrder; + if (Int32.TryParse(nameMatch.Groups[1].Value, out parsedOrder)) { + order = parsedOrder - CUSTOM_MANIFEST_NAMES_FIRST_INDEX_OFFSET; + } + name = nameMatch.Groups[2].Value; + } + customManifestNames.Remove(order); + customManifestNames.Add(order, name); + isManifest = true; + return true; + } + values = MatchPrefixesGetValues(token, TOKEN_MANIFEST, prefix); if (values != null) { isManifest = true; return true; @@ -403,7 +543,7 @@ private bool ParseToken(string token, string prefix = null) { } values = MatchPrefixesGetValues(token, TOKEN_VERSION, prefix); if (values != null && StringListMatchesRegex(values, VERSION_REGEX)) { - if (String.IsNullOrEmpty(versionString) && values.Length > 0) { + if (values.Length > 0) { versionString = values[0]; return true; } @@ -413,6 +553,12 @@ private bool ParseToken(string token, string prefix = null) { linuxLibraryBasename = String.Join(FIELD_SEPARATOR[0].ToString(), values); return true; } + values = MatchPrefixesGetValues(token, TOKEN_EXPORT_PATH, prefix); + if (values != null) { + exportPath = FileUtils.PosixPathSeparators( + String.Join(FIELD_SEPARATOR[0].ToString(), values)); + return true; + } return false; } @@ -422,7 +568,8 @@ private bool ParseToken(string token, string prefix = null) { /// Asset label to parse. /// true if the token is parsed, false otherwise. private bool ParseLabel(string label) { - return ParseToken(label, prefix: LABEL_PREFIX); + return ParseToken(label, prefix: LABEL_PREFIX_PRESERVE) || + ParseToken(label, prefix: LABEL_PREFIX); } /// @@ -443,11 +590,14 @@ private string CreateToken(string[] fieldPrefixes, string[] values) { /// The first item of this list is used as the prefix. /// /// Set of values to store with the field. - private string[] CreateLabels(string[] fieldPrefixes, IEnumerable values) { + /// Labels to search for the suitable prefix. + /// Always preserve these labels. + private static string[] CreateLabels(string[] fieldPrefixes, IEnumerable values, + HashSet currentLabels, bool preserve = false) { string prefix = fieldPrefixes[0]; List labels = new List(); foreach (var value in values) { - labels.Add(CreateLabel(prefix, value)); + labels.Add(CreateLabel(prefix, value, currentLabels, preserve: preserve)); } return labels.ToArray(); @@ -459,8 +609,26 @@ private string[] CreateLabels(string[] fieldPrefixes, IEnumerable values /// The field prefix to be applied to the label. /// /// The value to store in the field - private string CreateLabel(string prefix, string value) { - return LABEL_PREFIX + prefix + value; + /// Whether the label should be preserved. + public static string CreateLabel(string prefix, string value, bool preserve = false) { + return (preserve ? LABEL_PREFIX_PRESERVE : LABEL_PREFIX) + prefix + value; + } + + /// + /// Create an asset label keeping the preservation in the supplied set if it already exists. + /// + /// The field prefix to be applied to the label. + /// + /// The value to store in the field + /// Labels to search for the suitable prefix. + /// Whether the label should be preserved. + public static string CreateLabel(string prefix, string value, HashSet currentLabels, + bool preserve = false) { + var legacyLabel = CreateLabel(prefix, value, false); + var preservedLabel = CreateLabel(prefix, value, true); + if (currentLabels.Contains(legacyLabel)) return legacyLabel; + if (currentLabels.Contains(preservedLabel)) return preservedLabel; + return preserve ? preservedLabel : legacyLabel; } /// @@ -510,6 +678,18 @@ public HashSet GetBuildTargets() { return buildTargetSet; } + /// + /// Get the set of build targets as strings. + /// + /// Set of build targets as strings. + public HashSet GetBuildTargetStrings() { + var buildTargetStringsSet = new HashSet(); + foreach (var buildTarget in GetBuildTargets()) { + buildTargetStringsSet.Add(buildTarget.ToString()); + } + return buildTargetStringsSet; + } + /// /// Get the list of .NET versions this file is compatible with. /// @@ -526,10 +706,12 @@ public HashSet GetDotNetTargets() { /// Save metadata from this class into the asset's labels. /// public void UpdateAssetLabels() { - if (String.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(filename))) return; + if (String.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(filename)) || IsReadOnly) { + return; + } AssetImporter importer = AssetImporter.GetAtPath(filename); - var labels = new List(); - var currentLabels = new List(); + var labels = new HashSet(); + var currentLabels = new HashSet(); // Strip labels we're currently managing. foreach (string label in AssetDatabase.GetLabels(importer)) { currentLabels.Add(label); @@ -543,34 +725,57 @@ public void UpdateAssetLabels() { labels.Add(ASSET_LABEL); // Add labels for the metadata in this class. if (!String.IsNullOrEmpty(versionString)) { - labels.Add(CreateLabel(TOKEN_VERSION[0], versionString)); + labels.Add(CreateLabel(TOKEN_VERSION[0], versionString, currentLabels)); } if (targets != null && targets.Count > 0) { - labels.AddRange(CreateLabels(TOKEN_TARGETS, targets)); + labels.UnionWith(CreateLabels(TOKEN_TARGETS, targets, currentLabels)); if (!isHandledByPluginImporter) { labels.Add(ASSET_LABEL_RENAME_TO_DISABLE); } } if (dotNetTargets != null && dotNetTargets.Count > 0) { - labels.AddRange(CreateLabels(TOKEN_DOTNET_TARGETS, dotNetTargets)); + labels.UnionWith(CreateLabels(TOKEN_DOTNET_TARGETS, dotNetTargets, currentLabels)); } if (!String.IsNullOrEmpty(linuxLibraryBasename)) { - labels.Add(CreateLabel(TOKEN_LINUX_LIBRARY_BASENAME[0], linuxLibraryBasename)); + labels.Add(CreateLabel(TOKEN_LINUX_LIBRARY_BASENAME[0], linuxLibraryBasename, + currentLabels)); + } + if (!String.IsNullOrEmpty(exportPath)) { + labels.Add(CreateLabel(TOKEN_EXPORT_PATH[0], exportPath, currentLabels, + preserve: true)); } if (isManifest) { - labels.Add(CreateLabel(TOKEN_MANIFEST[0], null)); + labels.Add(CreateLabel(TOKEN_MANIFEST[0], null, currentLabels)); } - if (!(new HashSet(labels)).SetEquals(new HashSet(currentLabels))) { + if (customManifestNames != null && customManifestNames.Count > 0) { + foreach (var indexAndName in customManifestNames) { + int order = indexAndName.Key + CUSTOM_MANIFEST_NAMES_FIRST_INDEX_OFFSET; + var name = indexAndName.Value; + if (order < CUSTOM_MANIFEST_NAMES_FIRST_INDEX_OFFSET) { + labels.Add(CreateLabel(TOKEN_MANIFEST_NAME[0], order.ToString() + name, + currentLabels, preserve: true)); + } else { + labels.Add(CreateLabel(TOKEN_MANIFEST_NAME[0], name, currentLabels, + preserve: true)); + } + } + } + if (!labels.SetEquals(currentLabels)) { + var sortedLabels = new List(labels); + var sortedCurrentLabels = new List(currentLabels); + sortedCurrentLabels.Sort(); + sortedLabels.Sort(); + var labelsArray = sortedLabels.ToArray(); Log(String.Format("Changing labels of {0}\n" + "from: {1}\n" + - "to: {2}\n", + "to: {2}\n", filename, - String.Join(", ", currentLabels.ToArray()), - String.Join(", ", labels.ToArray())), + String.Join(", ", sortedCurrentLabels.ToArray()), + String.Join(", ", labelsArray)), verbose: true); + AssetDatabase.SetLabels(importer, labelsArray); } - AssetDatabase.SetLabels(importer, labels.ToArray()); } /// @@ -606,15 +811,24 @@ public bool RenameAsset(string newFilename) { } } try { - // This is *really* slow. - string error = AssetDatabase.RenameAsset( - filename, filenameComponents.basenameNoExtension); - if (!String.IsNullOrEmpty(error)) { - Log("Failed to rename asset " + filename + " to " + - newFilename + " (" + error + ")", - level: LogLevel.Error); - return false; - } + var targetDir = Path.GetDirectoryName(filename); + if (!String.IsNullOrEmpty(targetDir)) { + Directory.CreateDirectory(targetDir); + } + // This is *really* slow. + string error = AssetDatabase.MoveAsset(filename, newFilename); + if (!String.IsNullOrEmpty(error)) { + string renameError = AssetDatabase.RenameAsset( + filename, filenameComponents.basenameNoExtension); + error = String.IsNullOrEmpty(renameError) ? + renameError : String.Format("{0}, {1}", error, renameError); + } + if (!String.IsNullOrEmpty(error)) { + Log("Failed to rename asset " + filename + " to " + + newFilename + " (" + error + ")", + level: LogLevel.Error); + return false; + } } catch (Exception) { // Unity 5.3 and below can end up throw all sorts of // exceptions here when attempting to reload renamed @@ -649,7 +863,7 @@ public long CalculateVersion() { /// If the version string contains more than MAX_VERSION_COMPONENTS the /// remaining components are ignored. /// - /// Version string to parse. + /// Version string to parse. /// 64-bit version number. public static long CalculateVersion(string versionString) { long versionNumber = 0; @@ -672,7 +886,7 @@ public static long CalculateVersion(string versionString) { /// /// Convert a numeric version back to a version string. /// - /// Numeric version number. + /// Numeric version number. /// Version string. public static string VersionNumberToString(long versionNumber) { List components = new List(); @@ -741,6 +955,16 @@ public FileMetadata MostRecentVersion { } } + /// + /// Backing store for EnabledEditorDlls. + /// + private HashSet enabledEditorDlls = new HashSet(); + + /// + /// Full path of DLLs that should be loaded into the editor application domain. + /// + public ICollection EnabledEditorDlls { get { return enabledEditorDlls; } } + /// /// Determine whether the PluginImporter class is available in /// UnityEditor. Unity 4 does not have the PluginImporter class so @@ -767,7 +991,7 @@ public FileMetadataByVersion(string filenameCanonical) { /// Add metadata to the set. /// public void Add(FileMetadata metadata) { - System.Diagnostics.Debug.Assert( + Debug.Assert( filenameCanonical == null || metadata.filenameCanonical.Equals(filenameCanonical)); metadataByVersion[metadata.CalculateVersion()] = metadata; @@ -778,13 +1002,25 @@ public void Add(FileMetadata metadata) { /// recent versions. /// /// true if any plugin metadata was modified and requires an - /// AssetDatabase.Refresh(), false otherwise. + /// AssetDatabase.Refresh(), false otherwise. public bool EnableMostRecentPlugins() { + return EnableMostRecentPlugins(new HashSet()); + } + + /// + /// If this instance references a set of plugins, enable the most + /// recent versions. + /// + /// Set of files in the project that should be disabled. + /// true if any plugin metadata was modified and requires an + /// AssetDatabase.Refresh(), false otherwise. + public bool EnableMostRecentPlugins(HashSet disableFiles) { bool modified = false; int versionIndex = 0; int numberOfVersions = metadataByVersion.Count; var disabledVersions = new List(); string enabledVersion = null; + enabledEditorDlls = new HashSet(); // If the canonical file is out of date, update it. if (numberOfVersions > 0) { @@ -823,6 +1059,9 @@ public bool EnableMostRecentPlugins() { } catch (InvalidCastException) { continue; } + if (pluginImporter == null) { + continue; + } bool editorEnabled = metadata.GetEditorEnabled(); var selectedTargets = metadata.GetBuildTargets(); var hasBuildTargets = metadata.GetBuildTargetsSpecified(); @@ -831,13 +1070,16 @@ public bool EnableMostRecentPlugins() { bool modifiedThisVersion = false; // Only enable the most recent plugin - SortedDictionary // orders keys in ascending order. - bool obsoleteVersion = (numberOfVersions > 1 && - versionIndex < numberOfVersions); + bool obsoleteVersion = + (numberOfVersions > 1 && versionIndex < numberOfVersions) || + disableFiles.Contains(metadata.filename); // If this is an obsolete version. if (obsoleteVersion) { // Disable for all platforms and the editor. editorEnabled = false; selectedTargets = new HashSet(); + Log(String.Format("{0} is obsolete and will be disabled.", metadata.filename), + verbose: true); } else { if (hasDotNetTargets) { // Determine whether this is supported by the selected .NET version. @@ -875,6 +1117,9 @@ public bool EnableMostRecentPlugins() { dotNetVersionMessage), verbose: true); pluginImporter.SetCompatibleWithEditor(editorEnabled); + if (metadata.filename.ToLower().EndsWith(".dll")) { + enabledEditorDlls.Add(Path.GetFullPath(metadata.filename)); + } modifiedThisVersion = true; } bool compatibleWithAnyPlatform = pluginImporter.GetCompatibleWithAnyPlatform(); @@ -916,6 +1161,8 @@ public bool EnableMostRecentPlugins() { // Therefore, force a reimport of each file touched by the // plugin importer. if (modifiedThisVersion) { + Log(String.Format("Metadata changed: force import of {0}", metadata.filename), + verbose: true); AssetDatabase.ImportAsset(metadata.filename, ImportAssetOptions.ForceUpdate); } @@ -967,13 +1214,26 @@ public HashSet FindObsoleteVersions() { /// public class FileMetadataSet { /// - /// Dictionary of FileMetadataVersions indexed by filename with + /// Dictionary of FileMetadataByVersion indexed by filename with /// metadata stripped. /// + /// + /// This shouldn't not be modified directly, use Add() and Clear() instead. + /// private Dictionary metadataByCanonicalFilename = new Dictionary(); + /// + /// Dictionary of FileMetadata indexed by filename in the project and the export path if + /// it exists. This is the inverse mapping of metadataByCanonicalFilename. + /// + /// + /// This shouldn't not be modified directly, use Add() and Clear() instead. + /// + private Dictionary metadataByFilename = + new Dictionary(); + /// /// Get the FileMetadataByVersion for each filename bucket in this set. /// @@ -982,24 +1242,285 @@ public Dictionary.ValueCollection get { return metadataByCanonicalFilename.Values; } } + /// + /// Retrieves a map of in-project filename to file metadata. + /// + public Dictionary MetadataByFilename { + get { return metadataByFilename; } + } + + /// + /// Backing store for EnabledEditorDlls. + /// + private HashSet enabledEditorDlls = new HashSet(); + + /// + /// Full path of DLLs that should be loaded into the editor application domain. + /// + public ICollection EnabledEditorDlls { get { return enabledEditorDlls; } } + /// /// Construct an instance. /// public FileMetadataSet() { } + /// + /// Filter all files that can't be modified. + /// + /// Metadata set to filter. + /// New FileMetadata with files that can't be modified removed. + public static FileMetadataSet FilterOutReadOnlyFiles(FileMetadataSet metadataSet) { + var filteredMetadata = new FileMetadataSet(); + foreach (var metadataByVersion in metadataSet.Values) { + foreach (var metadata in metadataByVersion.Values) { + if (!metadata.IsReadOnly) filteredMetadata.Add(metadata); + } + } + return filteredMetadata; + } + + /// + /// Empty the set. + /// + public void Clear() { + metadataByCanonicalFilename = new Dictionary(); + metadataByFilename = new Dictionary(); + enabledEditorDlls = new HashSet(); + } + /// /// Add file metadata to the set. /// + /// File metadata to add to the set. public void Add(FileMetadata metadata) { + Add(metadata.filenameCanonical, metadata); + } + + /// + /// Add file metadata to the set. + /// + /// File to associate the metadata with. + /// File metadata to add to the set. + public void Add(string filenameCanonical, FileMetadata metadata) { + FindMetadataByVersion(filenameCanonical, true).Add(metadata); + UpdateMetadataByFilename(metadata); + } + + /// + /// Add a set of files to the specified set. + /// + /// File to associate the metadata with. + /// Set of files to add to the set. + public void Add(string filenameCanonical, FileMetadataByVersion metadataByVersion) { + var existingMetadataByVersion = FindMetadataByVersion(filenameCanonical, true); + foreach (var metadata in metadataByVersion.Values) { + existingMetadataByVersion.Add(metadata); + UpdateMetadataByFilename(metadata); + } + } + + /// + /// Update the mapping of filename to metadata. + /// + /// File metadata to add to the set. + private void UpdateMetadataByFilename(FileMetadata metadata) { + metadataByFilename[metadata.filename] = metadata; + if (!String.IsNullOrEmpty(metadata.ExportPathInProject)) { + metadataByFilename[metadata.ExportPathInProject] = metadata; + } + } + + /// + /// 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 or 0 to find the + /// most recent file. + /// Reference to the metadata if successful, null otherwise. + public FileMetadata FindMetadata(string filenameCanonical, + long version) { + FileMetadata metadata; + if (metadataByFilename.TryGetValue(filenameCanonical, out metadata)) { + return metadata; + } + var metadataByVersion = FindMetadataByVersion(filenameCanonical, false); + if (metadataByVersion != null) { + metadata = version > 0 ? metadataByVersion[version] : + metadataByVersion.MostRecentVersion; + if (metadata != null) return metadata; + } + return null; + } + + /// + /// Get metadata by version for a given canonical filename. + /// + /// Name of the file set to search for. + /// Whether to add an entry if the metadata isn't found. + /// Reference to the metadata by version if successful or addEntry is true, + /// null otherwise. + public FileMetadataByVersion FindMetadataByVersion(string filenameCanonical, + bool addEntry) { FileMetadataByVersion metadataByVersion; - string filenameCanonical = metadata.filenameCanonical; if (!metadataByCanonicalFilename.TryGetValue( filenameCanonical, out metadataByVersion)) { - metadataByVersion = - new FileMetadataByVersion(filenameCanonical); + if (!addEntry) return null; + metadataByVersion = new FileMetadataByVersion(filenameCanonical); + metadataByCanonicalFilename[filenameCanonical] = metadataByVersion; + } + return metadataByVersion; + } + + /// + /// Find the highest priority name of a manifest. + /// + /// Name to lookup from the graph. + /// Adjacency list (graph) of aliases to search. + /// Maximum depth to traverse the graph. + /// Graph traversal depth. + /// Name and depth in the graph or the supplied name and a depth of -1 + /// if not found. + private static KeyValuePair FindHighestPriorityManifestName( + string name, Dictionary> aliasesByName, int maxDepth, + int depth = 0) { + if (depth > maxDepth) { + Log(String.Format( + "Detected manifest name alias loop for name {0}, to fix this change the " + + "list (see below) to not contain a loop:\n{1}", + name, String.Join("\n", (new List(aliasesByName.Keys)).ToArray())), + level: LogLevel.Warning); + return new KeyValuePair(name, -1); + } + KeyValuePair deepestNameAndDepth = + new KeyValuePair(name, depth); + HashSet aliases; + if (!aliasesByName.TryGetValue(name, out aliases)) { + return deepestNameAndDepth; + } + if (aliases.Count == 1 && (new List(aliases))[0] == name) { + return deepestNameAndDepth; + } + foreach (var alias in aliases) { + var nameAndDepth = FindHighestPriorityManifestName( + alias, aliasesByName, maxDepth, depth: depth + 1); + if (nameAndDepth.Value > deepestNameAndDepth.Value) { + deepestNameAndDepth = nameAndDepth; + } } - metadataByVersion.Add(metadata); - metadataByCanonicalFilename[filenameCanonical] = metadataByVersion; + return deepestNameAndDepth; + } + + /// + /// Create an adjacency list of manifest names. + /// + /// Adjacency list of manifest names. For example, given the set of manifest + /// names for a file "foo.txt" [manifestName-0A, manifestName-1B, manifestName-1C] + /// this returns the set of aliaes for the name of manifest "A", i.e {"A": ["B, "C"]}. + /// + private Dictionary> GetManifestAliasesByName() { + // Create an adjacency list of manifest alias to name which can be used to search the + // highest priority name of a manifest "foo", which is the entry {"foo": ["foo"]}. + var aliasesByName = new Dictionary>(); + foreach (var metadataByVersion in Values) { + var metadata = metadataByVersion.MostRecentVersion; + if (metadata.isManifest) { + foreach (var name in metadata.customManifestNames.Values) { + HashSet aliases = null; + if (!aliasesByName.TryGetValue(name, out aliases)) { + aliases = new HashSet(); + aliasesByName[name] = aliases; + } + aliases.Add(metadata.customManifestNames.Values[0]); + } + } + } + // If manifest isn't an alias and doesn't have any aliases store an empty set. + foreach (var metadataByVersion in Values) { + var metadata = metadataByVersion.MostRecentVersion; + if (metadata.isManifest) { + var manifestName = metadata.ManifestName; + bool found = false; + foreach (var kv in aliasesByName) { + if (kv.Key == manifestName || kv.Value.Contains(manifestName)) { + found = true; + break; + } + } + // If there are no aliases, store an empty set. + if (!found) { + aliasesByName[metadata.ManifestName] = new HashSet(); + } + } + } + + // Display adjacency list for debugging. + var logLines = new List(); + foreach (var nameAndAliases in aliasesByName) { + logLines.Add(String.Format( + "name: {0} --> aliases: [{1}]", nameAndAliases.Key, + String.Join(", ", (new List(nameAndAliases.Value)).ToArray()))); + } + if (logLines.Count > 0) { + Log(String.Format("Manifest aliases:\n{0}", + String.Join("\n", logLines.ToArray())), verbose: true); + } + return aliasesByName; + } + + /// + /// Use manifest aliases to consolidate manifest metadata. + /// + /// Flattened map of highest priority manifest name by each alias of the manifest + /// name. + public Dictionary ConsolidateManifests() { + var aliasesByName = GetManifestAliasesByName(); + // Flatten graph of manifest aliases so that each entry maps to the highest priority + // name. + var manifestAliases = new Dictionary(); + int numberOfAliases = aliasesByName.Count; + + var logLines = new List(); + foreach (var name in aliasesByName.Keys) { + var foundName = FindHighestPriorityManifestName(name, aliasesByName, + numberOfAliases).Key; + manifestAliases[name] = foundName; + logLines.Add(String.Format("name: {0} --> alias: {1}", name, foundName)); + } + if (logLines.Count > 0) { + Log(String.Format("Flattened manifest aliases:\n{0}", + String.Join("\n", logLines.ToArray())), verbose: true); + } + + // Create a new metadata map consolidating manifests by their highest priority name. + var oldMetadataByCanonicalFilename = metadataByCanonicalFilename; + Clear(); + foreach (var canonicalFilenameAndMetadataByVersion in oldMetadataByCanonicalFilename) { + var metadata = canonicalFilenameAndMetadataByVersion.Value.MostRecentVersion; + if (metadata.isManifest) { + FileMetadataByVersion manifests = canonicalFilenameAndMetadataByVersion.Value; + // Merge multiple versions of the manifest. + string manifestName; + if (!manifestAliases.TryGetValue(metadata.ManifestName, out manifestName)) { + manifestName = metadata.ManifestName; + } + Add(manifestName, manifests); + + logLines = new List(); + foreach (var manifest in manifests.Values) { + logLines.Add(String.Format("file: {0}, version: {1}", + manifest.filename, manifest.versionString)); + } + Log(String.Format("Add manifests to package '{0}':\n{1}", + manifestName, String.Join("\n", logLines.ToArray())), + verbose: true); + } else { + Add(canonicalFilenameAndMetadataByVersion.Key, + canonicalFilenameAndMetadataByVersion.Value); + } + } + return manifestAliases; } /// @@ -1010,8 +1531,23 @@ public void Add(FileMetadata metadata) { /// Whether the update was forced by the /// user. /// true if any plugin metadata was modified and requires an - /// AssetDatabase.Refresh(), false otherwise. + /// AssetDatabase.Refresh(), false otherwise. public bool EnableMostRecentPlugins(bool forceUpdate) { + return EnableMostRecentPlugins(forceUpdate, new HashSet()); + } + + /// + /// For each plugin (DLL) referenced by this set, disable targeting + /// for all versions and re-enable platform targeting for the most + /// recent version. + /// + /// Whether the update was forced by the + /// user. + /// Set of files that should be disabled. + /// true if any plugin metadata was modified and requires an + /// AssetDatabase.Refresh(), false otherwise. + public bool EnableMostRecentPlugins(bool forceUpdate, + HashSet disableFiles) { bool modified = false; // If PluginImporter isn't available it's not possible @@ -1045,8 +1581,7 @@ public bool EnableMostRecentPlugins(bool forceUpdate) { " - In Unity 4.x:\n" + " - Select 'Assets > Reimport' from the menu.\n"; var warningLines = new List(); - foreach (var metadataByVersion in - metadataByCanonicalFilename.Values) { + foreach (var metadataByVersion in Values) { bool hasRelevantVersions = false; var fileInfoLines = new List(); fileInfoLines.Add(String.Format("Target Filename: {0}", @@ -1072,7 +1607,7 @@ public bool EnableMostRecentPlugins(bool forceUpdate) { metadata.versionString, currentFilename, metadata.targets != null ? - String.Join(", ", new List(metadata.targets).ToArray()) + String.Join(", ", new List(metadata.targets).ToArray()) : "")); } fileInfoLines.Add(""); @@ -1090,9 +1625,9 @@ public bool EnableMostRecentPlugins(bool forceUpdate) { return false; } - foreach (var metadataByVersion in - metadataByCanonicalFilename.Values) { - modified |= metadataByVersion.EnableMostRecentPlugins(); + foreach (var metadataByVersion in Values) { + modified |= metadataByVersion.EnableMostRecentPlugins(disableFiles); + enabledEditorDlls.UnionWith(metadataByVersion.EnabledEditorDlls); } return modified; } @@ -1100,7 +1635,7 @@ public bool EnableMostRecentPlugins(bool forceUpdate) { /// /// Parse metadata from a set of filenames. /// - /// Filenames to parse. + /// Filenames to parse. /// FileMetadataSet referencing metadata parsed from filenames /// ordered by version and bucketed by canonical filename. /// @@ -1118,7 +1653,7 @@ public static FileMetadataSet ParseFromFilenames(string[] filenames) { /// with metadata that selects the set of target platforms. /// /// Set to filter. - /// Filtered MetadataSet. + /// Filtered MetadataSet. public static FileMetadataSet FindWithPendingUpdates( FileMetadataSet metadataSet) { FileMetadataSet outMetadataSet = new FileMetadataSet(); @@ -1135,33 +1670,11 @@ public static FileMetadataSet FindWithPendingUpdates( } } if (needsUpdate) { - Log(filenameAndMetadata.Key + " metadata will be checked", - verbose: true); - outMetadataSet.metadataByCanonicalFilename[ - filenameAndMetadata.Key] = filenameAndMetadata.Value; + outMetadataSet.Add(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]; - } } /// @@ -1188,16 +1701,111 @@ public class ManifestReferences { /// public HashSet currentFiles = new HashSet(); + /// + /// Metadata of files in the package indexed by current filename. + /// + public Dictionary metadataByFilename = + new Dictionary(); + /// /// Set of obsolete files in this package. /// public HashSet obsoleteFiles = new HashSet(); + /// + /// Backing store of Aliases. + /// + private HashSet aliases = new HashSet(); + + /// + /// Alias names of this manifest. + /// + public ICollection Aliases { get { return aliases; } } + + /// + /// Cache of all manifest references indexed by package name. + /// + private static Dictionary cacheAll = + new Dictionary(); + + /// + /// Cache of manifest references installed under Assets (i.e not in the Unity Package + /// Manager) indexed by package name. + /// + private static Dictionary cacheInAssets = + new Dictionary(); + + /// + /// Get all files managed by this package, including manifest and obsolete files. + /// + public IEnumerable All { + get { + List files = new List(); + if (currentMetadata != null) { + files.Add(currentMetadata.filename); + } + if (metadataByVersion != null) { + foreach (var fileMetadata in metadataByVersion.Values) { + files.Add(fileMetadata.filename); + } + } + if (currentFiles != null) { + foreach (var file in currentFiles) { + files.Add(file); + } + } + if (obsoleteFiles != null) { + foreach (var file in obsoleteFiles) { + files.Add(file); + } + } + return files; + } + } + /// /// Create an instance. /// public ManifestReferences() { } + /// + /// Parse a legacy manfiest file. + /// + /// Package manifest to parse. + /// 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. + /// Metadata of files in the package indexed by current filename. + /// The FileMetadataByVersion will be null for files that aren't present in the + /// asset database. + public Dictionary> + ParseLegacyManifest(FileMetadata metadata, FileMetadataSet metadataSet) { + var filesInManifest = + new Dictionary>(); + StreamReader manifestFile = + new StreamReader(metadata.filename); + string line; + while ((line = manifestFile.ReadLine()) != null) { + var strippedLine = line.Trim(); + if (String.IsNullOrEmpty(strippedLine)) continue; + var manifestFileMetadata = new FileMetadata(strippedLine); + // Check for a renamed file. + long version = manifestFileMetadata.CalculateVersion(); + var existingFileMetadata = + metadataSet.FindMetadata(manifestFileMetadata.filenameCanonical, version) ?? + metadataSet.FindMetadata(manifestFileMetadata.filename, version); + if (existingFileMetadata != null) manifestFileMetadata = existingFileMetadata; + filesInManifest[manifestFileMetadata.filename] = + new KeyValuePair( + manifestFileMetadata, + metadataSet.FindMetadataByVersion(manifestFileMetadata.filenameCanonical, + false)); + } + manifestFile.Close(); + return filesInManifest; + } + /// /// Parse current and obsolete file references from a package's /// manifest files. @@ -1221,52 +1829,60 @@ public bool ParseManifests(FileMetadataByVersion metadataByVersion, foreach (FileMetadata metadata in metadataByVersion.Values) { versionIndex++; if (!metadata.isManifest) return false; + Log(String.Format("Parsing manifest '{0}' of package '{1}'", metadata.filename, + metadataByVersion.filenameCanonical), verbose: true); this.metadataByVersion = metadataByVersion; - 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(); - + var filesInManifest = ParseLegacyManifest(metadata, metadataSet); // 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); + var filenames = new HashSet(); + // Add references to the most recent file metadata for each referenced file. + metadataByFilename = new Dictionary(); + foreach (var kv in filesInManifest.Values) { + var fileMetadataByVersion = kv.Value; + var mostRecentMetadata = + fileMetadataByVersion != null ? + fileMetadataByVersion.MostRecentVersion : kv.Key; + metadataByFilename[mostRecentMetadata.filename] = mostRecentMetadata; + filenames.Add(mostRecentMetadata.filename); + if (fileMetadataByVersion != null) { + var versions = fileMetadataByVersion.Values; + int numberOfFileVersions = versions.Count; + int fileVersionIndex = 0; + foreach (var version in versions) { + fileVersionIndex ++; + if (fileVersionIndex == numberOfFileVersions) break; + obsoleteFiles.Add(version.filename); + } + } } - } - // 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(); + currentFiles.UnionWith(filenames); + obsoleteFiles.ExceptWith(filenames); + currentMetadata = metadata; + } else { + var filenames = new HashSet(filesInManifest.Keys); + obsoleteFiles.UnionWith(filenames); } } - this.filenameCanonical = metadataByVersion.filenameCanonical; + filenameCanonical = metadataByVersion.filenameCanonical; + var currentFilesSorted = new List(currentFiles); + var obsoleteFilesSorted = new List(obsoleteFiles); + currentFilesSorted.Sort(); + obsoleteFilesSorted.Sort(); + var components = new List(); + if (currentFilesSorted.Count > 0) { + components.Add(String.Format("Current files:\n{0}", + String.Join("\n", currentFilesSorted.ToArray()))); + } + if (obsoleteFilesSorted.Count > 0) { + components.Add(String.Format("Obsolete files:\n{0}", + String.Join("\n", obsoleteFilesSorted.ToArray()))); + } + Log(String.Format("'{0}' Manifest:\n{1}", + filenameCanonical, String.Join("\n", components.ToArray())), + verbose: true); return true; } @@ -1276,18 +1892,153 @@ public bool ParseManifests(FileMetadataByVersion metadataByVersion, /// 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(); + public static List FindAndReadManifests(FileMetadataSet metadataSet) { + return new List( + FindAndReadManifestsByPackageName(metadataSet).Values); + } + + /// + /// Find and read all package manifests and bucket by package name. + /// + /// Dictionary of ManifestReferences indexed by package name. + public static Dictionary FindAndReadManifestsByPackageName( + FileMetadataSet metadataSet) { + var manifestAliases = metadataSet.ConsolidateManifests(); + // Invert the map of manifest aliases to create a dictionary that maps canonical name + // to a set of aliases. + var aliasesByName = new Dictionary>(); + foreach (var aliasAndName in manifestAliases) { + var alias = aliasAndName.Key; + var name = aliasAndName.Value; + HashSet aliases; + if (!aliasesByName.TryGetValue(name, out aliases)) { + aliases = new HashSet(); + aliasesByName[name] = aliases; + } + aliases.Add(alias); + } + + var allObsoleteFiles = new HashSet(); + var manifestReferencesByPackageName = new Dictionary(); foreach (var metadataByVersion in metadataSet.Values) { - ManifestReferences manifestReferences = - new ManifestReferences(); + ManifestReferences manifestReferences = new ManifestReferences(); if (manifestReferences.ParseManifests(metadataByVersion, metadataSet)) { - manifestReferencesList.Add(manifestReferences); + manifestReferences.aliases = + aliasesByName[manifestReferences.filenameCanonical]; + manifestReferencesByPackageName[manifestReferences.filenameCanonical] = + manifestReferences; + allObsoleteFiles.UnionWith(manifestReferences.obsoleteFiles); } } - return manifestReferencesList; + + // Move globally obsolete files to the obsolete files set across all manifests. + foreach (var manifestReferences in manifestReferencesByPackageName.Values) { + var newlyObsoleteFiles = new HashSet(manifestReferences.currentFiles); + newlyObsoleteFiles.IntersectWith(allObsoleteFiles); + manifestReferences.currentFiles.ExceptWith(newlyObsoleteFiles); + manifestReferences.obsoleteFiles.UnionWith(newlyObsoleteFiles); + } + + return manifestReferencesByPackageName; + } + + /// + /// Find and read all package manifests. + /// + /// List of ManifestReferences which contain current and + /// obsolete files referenced in each manifest file. + public static List FindAndReadManifests() { + if (cacheAll.Count == 0) { + cacheAll = FindAndReadManifestsByPackageName( + FileMetadataSet.ParseFromFilenames(FindAllAssets())); + } + return new List(cacheAll.Values); + } + + /// + /// Find and read all package manifests from the Assets folder and index by name. + /// + /// Dictionary of ManifestReferences indexed by package name. + public static Dictionary + FindAndReadManifestsInAssetsFolderByPackageName() { + if (cacheInAssets.Count == 0) { + cacheInAssets = FindAndReadManifestsByPackageName( + FileMetadataSet.FilterOutReadOnlyFiles( + FileMetadataSet.ParseFromFilenames(FindAllAssets()))); + + } + return cacheInAssets; + } + + /// + /// Find and read all package manifests from Assets folder. + /// + /// List of ManifestReferences from Assets folder + public static List FindAndReadManifestsInAssetsFolder() { + return new List( + FindAndReadManifestsInAssetsFolderByPackageName().Values); + } + + /// + /// Delete a subset of packages managed by Version Handler. + /// + /// A HashSet of canonical name of the packages to be + /// uninstalled + /// Force to remove all file even it is referenced by other + /// packages. + /// If successful returns an empty collection, a collection of files that could + /// not be removed otherwise. + public static ICollection DeletePackages(HashSet packages, + bool force = false) { + // Create a map from canonical name to ManifestReferences. + var manifestMap = new Dictionary( + FindAndReadManifestsInAssetsFolderByPackageName()); + + HashSet filesToRemove = new HashSet(); + foreach (var pkgName in packages) { + ManifestReferences pkg = null; + if (manifestMap.TryGetValue(pkgName, out pkg)) { + filesToRemove.UnionWith(pkg.All); + } + manifestMap.Remove(pkgName); + } + + HashSet filesToExclude = new HashSet(); + if (!force) { + // Keep files which are referenced by other packages. + foreach (var manifestEntry in manifestMap) { + filesToExclude.UnionWith(manifestEntry.Value.All); + } + } + + HashSet filesToKeep = new HashSet(filesToRemove); + filesToKeep.IntersectWith(filesToExclude); + filesToRemove.ExceptWith(filesToExclude); + + VersionHandlerImpl.Log(String.Format("Uninstalling the following packages:\n{0}\n{1}", + String.Join("\n", (new List(packages)).ToArray()), + filesToKeep.Count == 0 ? "" : String.Format( + "Ignoring the following files referenced by other packages:\n{0}\n", + String.Join("\n", (new List(filesToKeep)).ToArray())))); + + var result = FileUtils.RemoveAssets(filesToRemove, VersionHandlerImpl.Logger); + if (result.Success) { + VersionHandlerImpl.analytics.Report("uninstallpackage/delete/success", + "Successfully Deleted All Files in Packages"); + } else { + VersionHandlerImpl.analytics.Report("uninstallpackage/delete/fail", + "Fail to Delete Some Files in Packages"); + } + return result.RemoveFailed; + } + + /// + /// Flush the caches. + /// + public static void FlushCaches() { + cacheAll = new Dictionary(); + cacheInAssets = new Dictionary(); } } @@ -1310,12 +2061,24 @@ public class ObsoleteFiles { /// Obsolete files that are referenced by manifests. Each item in /// the dictionary contains a list of manifests referencing the file. /// - public Dictionary> referenced; + public Dictionary> referenced; /// /// Same as the "referenced" member excluding manifest files. /// - public Dictionary> referencedExcludingManifests; + public Dictionary> referencedExcludingManifests; + + /// + /// Get all referenced and unreferenced obsolete files. + /// + public HashSet All { + get { + var all = new HashSet(); + all.UnionWith(unreferenced); + all.UnionWith(referenced.Keys); + return all; + } + } /// /// Build an ObsoleteFiles instance searching a set of @@ -1327,9 +2090,7 @@ public class ObsoleteFiles { /// List of manifests to query /// for obsolete files. /// Set of metadata to query for obsolete - /// files. - /// ObsoleteFiles instance which references the discovered - /// obsolete files. + /// files. public ObsoleteFiles( List manifestReferencesList, FileMetadataSet metadataSet) { @@ -1358,17 +2119,16 @@ public ObsoleteFiles( // which contains a list of manifest filenames which reference // each file. var referencedObsoleteFiles = - new Dictionary>(); - var referencedObsoleteFilesExcludingManifests = new Dictionary>(); + new Dictionary>(); + var referencedObsoleteFilesExcludingManifests = + new Dictionary>(); var obsoleteFilesToDelete = new HashSet(); var obsoleteFilesToDeleteExcludingManifests = new HashSet(); foreach (var obsoleteFile in obsoleteFiles) { - var manifestsReferencingFile = new List(); + var manifestsReferencingFile = new List(); foreach (var manifestReferences in manifestReferencesList) { - if (manifestReferences.currentFiles.Contains( - obsoleteFile)) { - manifestsReferencingFile.Add( - manifestReferences.currentMetadata.filename); + if (manifestReferences.currentFiles.Contains(obsoleteFile)) { + manifestsReferencingFile.Add(manifestReferences); } } // If the referenced file doesn't exist, ignore it. @@ -1485,6 +2245,9 @@ private string LibraryPrefix { // Path of the file that indicates whether the asset database is currently being refreshed // due to this module. private const string REFRESH_PATH = "Temp/VersionHandlerImplRefresh"; + // Path of the file that indicates whether files need to be cleaned up after an asset database + // refresh. + private const string CLEANUP_FILES_PENDING_PATH = "Temp/VersionHandlerImplCleanupFilesPending"; // Whether compilation is currently occuring. private static bool compiling = false; @@ -1492,6 +2255,44 @@ private string LibraryPrefix { // Settings used by this module. internal static ProjectSettings settings = new ProjectSettings("Google.VersionHandler."); + /// + /// Logger for this module. + /// + private static Logger logger = new Logger(); + + public static Logger Logger { + get { return logger; } + } + + // Google Analytics tracking ID. + internal const string GA_TRACKING_ID = "UA-54627617-3"; + internal const string MEASUREMENT_ID = "com.google.external-dependency-manager"; + // Name of the plugin suite. + internal const string PLUGIN_SUITE_NAME = "External Dependency Manager"; + // Privacy policy for analytics data usage. + internal const string PRIVACY_POLICY = "https://policies.google.com/privacy"; + // Product Url + internal const string DATA_USAGE_URL = + "https://github.com/googlesamples/unity-jar-resolver#analytics"; + + // Analytics reporter. + internal static EditorMeasurement analytics = new EditorMeasurement( + settings, logger, GA_TRACKING_ID, MEASUREMENT_ID, PLUGIN_SUITE_NAME, "", + PRIVACY_POLICY) { + BasePath = "/versionhandler/", + BaseQuery = String.Format("version={0}", VersionHandlerVersionNumber.Value.ToString()), + BaseReportName = "Version Handler: ", + InstallSourceFilename = Assembly.GetAssembly(typeof(VersionHandlerImpl)).Location, + DataUsageUrl = DATA_USAGE_URL + }; + + /// + /// Load log preferences. + /// + private static void LoadLogPreferences() { + UpdateLoggerLevel(VerboseLoggingEnabled); + } + /// /// Enables / disables assets imported at multiple revisions / versions. /// In addition, this module will read text files matching _manifest_ @@ -1499,10 +2300,18 @@ private string LibraryPrefix { /// static VersionHandlerImpl() { Log("Loaded VersionHandlerImpl", verbose: true); - RunOnMainThread.Run(() => { UpdateVersionedAssetsOnUpdate(); }, runNow: false); + RunOnMainThread.Run(() => { + LoadLogPreferences(); + UpdateVersionedAssetsOnUpdate(); + }, runNow: false); } static void UpdateVersionedAssetsOnUpdate() { + // Skips update if this module is disabled. + if (!Enabled) { + return; + } + UpdateVersionedAssets(); NotifyWhenCompliationComplete(false); UpdateAssetsWithBuildTargets(EditorUserBuildSettings.activeBuildTarget); @@ -1528,6 +2337,50 @@ private static bool Refreshing { } } + /// + /// Whether files need to be cleaned up after an asset database refresh. + /// + private static bool CleanupFilesPending { + get { + return File.Exists(CLEANUP_FILES_PENDING_PATH); + } + + set { + bool pending = CleanupFilesPending; + if (pending != value) { + if (value) { + File.WriteAllText(CLEANUP_FILES_PENDING_PATH, "Cleanup files after refresh"); + } else { + File.Delete(CLEANUP_FILES_PENDING_PATH); + } + } + } + } + + /// + /// Whether all editor DLLs have been loaded into the app domain. + /// + private static bool EnabledEditorDllsLoaded { + get { + var loadedAssemblyPaths = new HashSet(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + string location; + try { + location = assembly.Location; + } catch (NotSupportedException) { + // Dynamic assemblies do not have a file location so ignore. + continue; + } + if (String.IsNullOrEmpty(location)) continue; + var path = Path.GetFullPath(location); + if (enabledEditorDlls.Contains(path)) { + loadedAssemblyPaths.Add(path); + } + } + return loadedAssemblyPaths.IsSupersetOf(enabledEditorDlls); + } + } + /// /// Polls on main thread until compilation is finished prior to calling /// NotifyUpdateCompleteMethods() if an asset database refresh was in progress or @@ -1549,15 +2402,15 @@ private static bool Refreshing { /// refreshing. private static void NotifyWhenCompliationComplete(bool forceNotification) { RunOnMainThread.PollOnUpdateUntilComplete(() => { - if (EditorApplication.isCompiling) { + if (EditorApplication.isCompiling || !EnabledEditorDllsLoaded) { if (!compiling) { Log("Compiling...", verbose: true); } compiling = true; - // In batch mode this can get stuck forever as PollOnUpdateUntilComplete() - // will block the main thread which prevents the EditorApplication.isCompiling - // flag from being updated by the editor. - return ExecutionEnvironment.InBatchMode; + // When running a single method this can get stuck forever as + // PollOnUpdateUntilComplete() will block the main thread which prevents the + // EditorApplication.isCompiling flag from being updated by the editor. + return ExecutionEnvironment.ExecuteMethodEnabled; } if (compiling) { Log("Compilation complete.", verbose: true); @@ -1566,7 +2419,18 @@ private static void NotifyWhenCompliationComplete(bool forceNotification) { // If a refresh was initiated by this module, clear the refresh flag. var wasRefreshing = Refreshing; Refreshing = false; - if (wasRefreshing || forceNotification) NotifyUpdateCompleteMethods(); + if (wasRefreshing || forceNotification) { + if (CleanupFilesPending) { + CleanupFilesPending = false; + RunOnMainThread.Run(() => { + UpdateVersionedAssetsOnMainThread(false, () => {}, + setCleanupFilesPending: false); + }, runNow: false); + } + + analytics.Report("enablemostrecentplugins", "Enable Most Recent Plugins"); + NotifyUpdateCompleteMethods(); + } return true; }); } @@ -1600,6 +2464,7 @@ private static void NotifyUpdateCompleteMethods() { /// internal static void RestoreDefaultSettings() { settings.DeleteKeys(PREFERENCE_KEYS); + analytics.RestoreDefaultSettings(); } /// @@ -1615,7 +2480,7 @@ public static bool UseProjectSettings { /// public static bool Enabled { get { - return !System.Environment.CommandLine.Contains("-gvh_disable") && + return !System.Environment.CommandLine.ToLower().Contains("-gvh_disable") && settings.GetBool(PREFERENCE_ENABLED, defaultValue: true); } set { settings.SetBool(PREFERENCE_ENABLED, value); } @@ -1643,10 +2508,16 @@ public static bool RenameToCanonicalFilenames { /// Enable / disable verbose logging. /// public static bool VerboseLoggingEnabled { - get { return System.Environment.CommandLine.Contains("-batchmode") || - settings.GetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, - defaultValue: false); } - set { settings.SetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, value); } + get { return settings.GetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, + defaultValue: false); } + set { + settings.SetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, value); + UpdateLoggerLevel(value); + } + } + + private static void UpdateLoggerLevel(bool verboseLoggingEnabled) { + logger.Level = verboseLoggingEnabled ? LogLevel.Verbose : LogLevel.Info; } /// @@ -1689,11 +2560,6 @@ public static IEnumerable UpdateCompleteMethods { } } - /// - /// Logger for this module. - /// - private static Logger logger = new Logger(); - /// /// Whether to also log to a file in the project. /// @@ -1711,7 +2577,6 @@ internal static bool LogToFile { /// Severity of the message. internal static void Log(string message, bool verbose = false, LogLevel level = LogLevel.Info) { - logger.Level = VerboseLoggingEnabled ? LogLevel.Verbose : LogLevel.Info; logger.Log(message, level: verbose ? LogLevel.Verbose : level); } @@ -1728,24 +2593,24 @@ internal static string DocumentationUrl(string subsection) { /// /// Link to the project documentation. /// - [MenuItem("Assets/Play Services Resolver/Documentation")] + [MenuItem("Assets/External Dependency Manager/Documentation")] public static void OpenProjectDocumentation() { - Application.OpenURL(VersionHandlerImpl.DocumentationUrl("#overview")); + analytics.OpenUrl(VersionHandlerImpl.DocumentationUrl("#overview"), "Overview"); } /// /// Link to the documentation. /// - [MenuItem("Assets/Play Services Resolver/Version Handler/Documentation")] + [MenuItem("Assets/External Dependency Manager/Version Handler/Documentation")] public static void OpenDocumentation() { - Application.OpenURL(VersionHandlerImpl.DocumentationUrl("#version-handler-usage")); + analytics.OpenUrl(VersionHandlerImpl.DocumentationUrl("#version-handler-usage"), "Usage"); } /// /// Add the settings dialog for this module to the menu and show the /// window when the menu item is selected. /// - [MenuItem("Assets/Play Services Resolver/Version Handler/Settings")] + [MenuItem("Assets/External Dependency Manager/Version Handler/Settings")] public static void ShowSettings() { SettingsDialog window = (SettingsDialog)EditorWindow.GetWindow( typeof(SettingsDialog), true, PLUGIN_NAME + " Settings"); @@ -1756,15 +2621,77 @@ public static void ShowSettings() { /// /// Menu item which forces version handler execution. /// - [MenuItem("Assets/Play Services Resolver/Version Handler/Update")] + [MenuItem("Assets/External Dependency Manager/Version Handler/Update")] public static void UpdateNow() { + LoadLogPreferences(); UpdateVersionedAssets(true, () => { - if (!ExecutionEnvironment.InBatchMode) { - EditorUtility.DisplayDialog(PLUGIN_NAME, "Update complete.", "OK"); - } + DialogWindow.Display(PLUGIN_NAME, "Update complete.", + DialogWindow.Option.Selected0, "OK"); }); } + /// + /// Menu item which logs the set of installed packages and their contents to the console. + /// + [MenuItem("Assets/External Dependency Manager/Version Handler/Display Managed Packages")] + public static void DisplayInstalledPackages() { + var manifests = ManifestReferences.FindAndReadManifestsInAssetsFolder(); + foreach (var pkg in manifests) { + if (!String.IsNullOrEmpty(pkg.filenameCanonical) && pkg.metadataByVersion != null) { + var versions = new List(); + foreach (var fileMetadata in pkg.metadataByVersion.Values) { + versions.Add(fileMetadata.versionString); + } + var lines = new List(); + lines.Add(String.Format("{0}: [{1}]", pkg.filenameCanonical, + String.Join(", ", versions.ToArray()))); + + var packageFiles = new List(pkg.currentFiles); + packageFiles.Sort(); + if (packageFiles.Count > 0) { + lines.Add(String.Format("Up-to-date files:\n{0}\n\n", + String.Join("\n", packageFiles.ToArray()))); + } + var obsoleteFiles = new List(pkg.obsoleteFiles); + obsoleteFiles.Sort(); + if (obsoleteFiles.Count > 0) { + lines.Add(String.Format("Obsolete files:\n{0}\n\n", + String.Join("\n", obsoleteFiles.ToArray()))); + } + Log(String.Join("\n", lines.ToArray())); + } + } + } + + /// + /// Menu item which moves all managed files to their initial install locations. + /// + [MenuItem("Assets/External Dependency Manager/Version Handler/Move Files To Install Locations")] + public static void MoveFilesToInstallLocations() { + var manifests = ManifestReferences.FindAndReadManifestsInAssetsFolder(); + foreach (var pkg in manifests) { + if (!String.IsNullOrEmpty(pkg.filenameCanonical) && pkg.metadataByVersion != null) { + var logLines = new List(); + foreach (var metadata in pkg.metadataByFilename.Values) { + var exportPathInProject = metadata.ExportPathInProject; + if (!String.IsNullOrEmpty(exportPathInProject)) { + var originalFilename = metadata.filename; + if (originalFilename != exportPathInProject && + metadata.RenameAsset(exportPathInProject)) { + logLines.Add(String.Format("{0} --> {1}", originalFilename, + exportPathInProject)); + } + } + } + if (logLines.Count > 0) { + Log(String.Format("'{0}' files moved to their install locations:\n{1}", + pkg.filenameCanonical, + String.Join("\n", logLines.ToArray()))); + } + } + } + } + /// /// Delegate used to filter a file and directory names. /// @@ -1799,7 +2726,7 @@ public static string[] SearchAssetDatabase(string assetsFilter = null, } var assetGuids = searchDirectories == null ? AssetDatabase.FindAssets(assetsFilter) : AssetDatabase.FindAssets(assetsFilter, searchDirectories); - foreach (string assetGuid in assetGuids) { + foreach (var assetGuid in assetGuids) { string filename = AssetDatabase.GUIDToAssetPath(assetGuid); // Ignore non-existent files as it's possible for the asset database to reference // missing files if it hasn't been refreshed or completed a refresh. @@ -1854,6 +2781,10 @@ public static void UpdateVersionedAssets(bool forceUpdate) { UpdateVersionedAssets(forceUpdate, () => {}); } + /// + /// All editor DLLs enabled by the last pass of UpdateVersionedAssets(). + /// + private static HashSet enabledEditorDlls = new HashSet(); /// /// Find all files in the asset database with multiple version numbers @@ -1864,6 +2795,25 @@ public static void UpdateVersionedAssets(bool forceUpdate) { /// Whether to force an update. /// Called when this is method is complete. public static void UpdateVersionedAssets(bool forceUpdate, Action complete) { + CancelUpdateVersionedAssets(); + RunOnMainThread.Run(() => { + UpdateVersionedAssetsOnMainThread(forceUpdate, complete); }, + runNow: false); + } + + /// + /// Find all files in the asset database with multiple version numbers + /// encoded in their filename, select the most recent revisions and + /// delete obsolete versions and files referenced by old manifests that + /// are not present in the most recent manifests. + /// + /// Whether to force an update. + /// Called when this is method is complete. + /// Whether to set the CleanupFilesPending flag to run + /// this method again after an asset database refresh is complete. + private static void UpdateVersionedAssetsOnMainThread(bool forceUpdate, + Action complete, + bool setCleanupFilesPending = true) { // If this module is disabled do nothing. if (!forceUpdate && !Enabled) { complete(); @@ -1872,22 +2822,23 @@ public static void UpdateVersionedAssets(bool forceUpdate, Action complete) { UpdateAssetsWithBuildTargets(EditorUserBuildSettings.activeBuildTarget); - var metadataSet = FileMetadataSet.ParseFromFilenames(FindAllAssets()); + var allMetadataSet = FileMetadataSet.FilterOutReadOnlyFiles( + FileMetadataSet.ParseFromFilenames(FindAllAssets())); // Rename linux libraries, if any are being tracked. - var linuxLibraries = new LinuxLibraryRenamer(metadataSet); + var linuxLibraries = new LinuxLibraryRenamer(allMetadataSet); linuxLibraries.RenameLibraries(); - if (!forceUpdate) { - metadataSet = FileMetadataSet.FindWithPendingUpdates(metadataSet); - } - if (metadataSet.EnableMostRecentPlugins(forceUpdate)) { + var metadataSet = allMetadataSet; + if (!forceUpdate) metadataSet = FileMetadataSet.FindWithPendingUpdates(allMetadataSet); + + var obsoleteFiles = new ObsoleteFiles( + ManifestReferences.FindAndReadManifests(allMetadataSet), allMetadataSet); + if (metadataSet.EnableMostRecentPlugins(forceUpdate, obsoleteFiles.All)) { + enabledEditorDlls.UnionWith(metadataSet.EnabledEditorDlls); AssetDatabase.Refresh(); Refreshing = true; } - var obsoleteFiles = new ObsoleteFiles( - ManifestReferences.FindAndReadManifests(metadataSet), metadataSet); - // Obsolete files that are no longer referenced can be safely deleted, prompt the user for // confirmation if they have the option enabled. var cleanupFiles = new List>(); @@ -1898,7 +2849,7 @@ public static void UpdateVersionedAssets(bool forceUpdate, Action complete) { cleanupFiles.Add(new KeyValuePair(filename, filename)); } } else { - foreach (var filename in obsoleteFiles.unreferenced) MoveAssetToTrash(filename); + FileUtils.RemoveAssets(obsoleteFiles.unreferenced, VersionHandlerImpl.Logger); } } @@ -1908,7 +2859,11 @@ public static void UpdateVersionedAssets(bool forceUpdate, Action complete) { if (!ExecutionEnvironment.InBatchMode) { foreach (var item in obsoleteFiles.referenced) { var filename = item.Key; - var references = item.Value; + var manifestReferencesList = item.Value; + var references = new List(); + foreach (var manifestReferences in manifestReferencesList) { + references.Add(manifestReferences.filenameCanonical); + } cleanupFiles.Add( new KeyValuePair( filename, @@ -1916,28 +2871,40 @@ public static void UpdateVersionedAssets(bool forceUpdate, Action complete) { String.Join(", ", references.ToArray())))); } } else { - foreach (var item in obsoleteFiles.referenced) MoveAssetToTrash(item.Key); + FileUtils.RemoveAssets(obsoleteFiles.referenced.Keys, VersionHandlerImpl.Logger); } } - if (cleanupFiles.Count > 0) { - var window = MultiSelectWindow.CreateMultiSelectWindow(PLUGIN_NAME); + bool cleanupFilesPending = cleanupFiles.Count > 0; + if (cleanupFilesPending && !Refreshing) { + var window = MultiSelectWindow.CreateMultiSelectWindow(PLUGIN_NAME); Action logObsoleteFile = (filename) => { Log("Leaving obsolete file: " + filename, verbose: true); }; Action deleteFiles = () => { - foreach (var filename in window.SelectedItems) MoveAssetToTrash(filename); + bool deletedAll = true; + FileUtils.RemoveAssets(window.SelectedItems, VersionHandlerImpl.Logger); foreach (var filenameAndDisplay in window.AvailableItems) { if (!window.SelectedItems.Contains(filenameAndDisplay.Key)) { + deletedAll = false; logObsoleteFile(filenameAndDisplay.Value); } } + if (deletedAll) { + analytics.Report("deleteobsoletefiles/confirm/all", + "Delete All Obsolete Files"); + } else { + analytics.Report("deleteobsoletefiles/confirm/subset", + "Delete Subset of Obsolete Files"); + } complete(); }; Action leaveFiles = () => { foreach (var filenameAndDisplay in window.AvailableItems) { logObsoleteFile(filenameAndDisplay.Value); } + analytics.Report("deleteobsoletefiles/abort", + "Leave Obsolete Files"); complete(); }; window.AvailableItems = new List>(cleanupFiles); @@ -1949,6 +2916,7 @@ public static void UpdateVersionedAssets(bool forceUpdate, Action complete) { window.OnCancel = leaveFiles; window.Show(); } else { + if (cleanupFilesPending && setCleanupFilesPending) CleanupFilesPending = true; complete(); } @@ -1979,7 +2947,15 @@ public static void UpdateAssetsWithBuildTargets(BuildTarget currentBuildTarget) foreach (var versionData in versionPair.Values) { bool enabled = false; if (versionData == versionPair.MostRecentVersion) { - if (versionData.GetBuildTargetsSpecified()) { + Log(String.Format( + "{0}: editor enabled {1}, build targets [{2}] (current target {3})", + versionData.filename, versionData.GetEditorEnabled(), + String.Join( + ", ", + (new List(versionData.GetBuildTargetStrings())).ToArray()), + currentBuildTarget), + level: LogLevel.Verbose); + if (versionData.GetBuildTargetsSpecified() && !versionData.GetEditorEnabled()) { var buildTargets = versionData.GetBuildTargets(); enabled = buildTargets.Contains(currentBuildTarget); } else { @@ -2038,13 +3014,35 @@ public static float GetUnityVersionMajorMinor() { return ExecutionEnvironment.VersionMajorMinor; } + // ID of the scheduled job which performs an update. + private static int updateVersionedAssetsJob = 0; + + /// + /// Cancel the update versioned assets job. + /// + private static void CancelUpdateVersionedAssets() { + if (updateVersionedAssetsJob > 0) { + RunOnMainThread.Cancel(updateVersionedAssetsJob); + updateVersionedAssetsJob = 0; + } + } + /// /// Scanned for versioned assets and apply modifications if required. /// - private static void OnPostProcessAllAssets ( + private static void OnPostprocessAllAssets( string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromPath) { - UpdateVersionedAssets(); + ManifestReferences.FlushCaches(); + if (Enabled) { + const double UpdateDelayInMiliseconds = 2000; + CancelUpdateVersionedAssets(); + updateVersionedAssetsJob = + RunOnMainThread.Schedule(() => { + UpdateVersionedAssets(); + }, + UpdateDelayInMiliseconds); + } } /// @@ -2075,7 +3073,7 @@ internal static string[] OnWillSaveAssets(string[] paths) { bool logToFilePrevious = LogToFile; // Enable logging to a file as all logs on shutdown do not end up in Unity's log // file. - LogToFile = VerboseLoggingEnabled; + LogToFile = logger.Level == LogLevel.Verbose; Log(String.Format(".NET framework version changed from {0} to {1}\n", currentDotNetVersion, newDotNetVersion)); currentDotNetVersion = FileMetadata.ScriptingRuntimeDotNetVersion; @@ -2107,7 +3105,16 @@ private static void CheckBuildTarget() { lastKnownBuildTarget = newBuildTarget; HandleBuildTargetChanged(newBuildTarget); } - if (Enabled && RenameToDisableFilesEnabled && !ExecutionEnvironment.InBatchMode) { + + // Disable callback queue in non-interactive (batch) mode and when + // -executeMethod is specified on the command line. + // In batch mode, everything is executed in a single thread. So there + // is no way for VersionHandler to gain control after its initially called. + // -executeMethod doesn't trigger a reliable EditorApplication.update + // event which can cause the queue to grow unbounded, possibly freezing Unity. + if (Enabled && RenameToDisableFilesEnabled && + !ExecutionEnvironment.InBatchMode && + !ExecutionEnvironment.ExecuteMethodEnabled) { RunOnMainThread.Schedule(CheckBuildTarget, POLL_INTERVAL_MILLISECONDS); } } diff --git a/source/VersionHandlerImpl/src/VersionNumber.cs b/source/VersionHandlerImpl/src/VersionNumber.cs index fc364601..58c6f697 100644 --- a/source/VersionHandlerImpl/src/VersionNumber.cs +++ b/source/VersionHandlerImpl/src/VersionNumber.cs @@ -27,7 +27,7 @@ public class VersionHandlerVersionNumber { /// /// Version number, patched by the build process. /// - private const string VERSION_STRING = "1.2.128.0"; + private const string VERSION_STRING = "1.2.186"; /// /// Cached version structure. diff --git a/source/VersionHandlerImpl/src/XmlUtilities.cs b/source/VersionHandlerImpl/src/XmlUtilities.cs index 73342d91..a3d08278 100644 --- a/source/VersionHandlerImpl/src/XmlUtilities.cs +++ b/source/VersionHandlerImpl/src/XmlUtilities.cs @@ -99,6 +99,7 @@ public bool Read() { internal static bool ParseXmlTextFileElements( string filename, Logger logger, ParseElement parseElement) { if (!File.Exists(filename)) return false; + bool successful = true; try { using (var xmlReader = new XmlTextReader(new StreamReader(filename))) { var elementNameStack = new List(); @@ -109,9 +110,12 @@ internal static bool ParseXmlTextFileElements( var elementName = xmlReader.Name; var parentElementName = getParentElement(); if (xmlReader.NodeType == XmlNodeType.Element) { - if (parseElement(xmlReader, elementName, true, - parentElementName, elementNameStack)) { + bool parsedElement = parseElement(xmlReader, elementName, true, + parentElementName, elementNameStack); + if (parsedElement) { elementNameStack.Insert(0, elementName); + } else { + successful = false; } // If the parse delegate read data, move to the next XML node. @@ -133,8 +137,10 @@ internal static bool ParseXmlTextFileElements( // so clear the stack. elementNameStack.Clear(); } - parseElement(xmlReader, elementName, false, - getParentElement(), elementNameStack); + if (!parseElement(xmlReader, elementName, false, + getParentElement(), elementNameStack)) { + successful = false; + } } reader.Read(); } @@ -144,7 +150,7 @@ internal static bool ParseXmlTextFileElements( filename, exception.ToString()), level: LogLevel.Error); return false; } - return true; + return successful; } } } diff --git a/source/VersionHandlerImpl/test/activation/Assets/PlayServicesResolver/Editor/TestEnabledCallback.cs b/source/VersionHandlerImpl/test/activation/Assets/PlayServicesResolver/Editor/TestEnabledCallback.cs index 36560baf..bf4b3ece 100644 --- a/source/VersionHandlerImpl/test/activation/Assets/PlayServicesResolver/Editor/TestEnabledCallback.cs +++ b/source/VersionHandlerImpl/test/activation/Assets/PlayServicesResolver/Editor/TestEnabledCallback.cs @@ -42,7 +42,11 @@ private static Dictionary EntryPoints { { "IOS Resolver", Google.VersionHandler.FindClass("Google.IOSResolver", "Google.IOSResolver") - } + }, + { + "Package Manager Resolver", + Google.VersionHandler.FindClass("Google.PackageManagerResolver", "Google.PackageManagerResolver") + } }; } } diff --git a/source/VersionHandlerImpl/test/webrequest/Assets/PlayServicesResolver/Editor/TestPortableWebRequest.cs_DISABLED b/source/VersionHandlerImpl/test/webrequest/Assets/PlayServicesResolver/Editor/TestPortableWebRequest.cs_DISABLED new file mode 100644 index 00000000..9f5d216c --- /dev/null +++ b/source/VersionHandlerImpl/test/webrequest/Assets/PlayServicesResolver/Editor/TestPortableWebRequest.cs_DISABLED @@ -0,0 +1,274 @@ +// +// 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.Net; +using Google; + +[UnityEditor.InitializeOnLoad] +public class TestPortableWebRequest { + + // Whether the get operation succeeded. + private static bool getSucceeded = false; + // Whether the get operation with no query succeeded. + private static bool getNoQuerySucceeded = false; + // Whether the get operation with no headers succeeded. + private static bool getNoHeadersSucceeded = false; + // Whether the post operation succeeded. + private static bool postSucceeded = false; + // Whether the post operation with no headers succeeded. + private static bool postNoHeadersSucceeded = false; + // Whether the post operation with no form fields succeeded. + private static bool postNoFormFieldsSucceeded = false; + + /// + /// Register a method to call when the Version Handler has enabled all plugins in the project. + /// + static TestPortableWebRequest() { + // Disable stack traces for more condensed logs. + UnityEngine.Application.stackTraceLogType = UnityEngine.StackTraceLogType.None; + RunOnMainThread.Run(StartTests, runNow: false); + } + + /// + /// Convert a dictionary of string key value pairs to a sorted newline separated string + /// + /// If specified, only keys will this prefix are included. + /// Convert all keys to lowercase, this is required as WWW class + /// converts all headers to uppercase. + /// Items to convert to a newline separated string. + /// Sorted newline separated string. + private static string DictionaryToString(string prefix, bool lowercaseKeys, + IDictionary items) { + var stringList = new List(); + foreach (var kv in items) { + var key = lowercaseKeys ? kv.Key.ToLower() : kv.Key; + if (String.IsNullOrEmpty(prefix) || key.StartsWith(prefix)) { + stringList.Add(String.Format("{0}={1}", key, kv.Value)); + } + } + stringList.Sort(); + return String.Join("\n", stringList.ToArray()); + } + + /// + /// Determine whether two values are equal. + /// + /// Object to compare. + /// Object to compare. + /// true if they're both equal, false and prints each object as a string to the console + /// if they differ. + private static bool CheckEqual(object lhs, object rhs) { + if (!lhs.Equals(rhs)) { + UnityEngine.Debug.Log(String.Format("Check failed:\n '{0}' !=\n '{1}'", lhs, rhs)); + return false; + } + return true; + } + + /// + /// Execute tests. + /// + public static void StartTests() { + var webRequest = PortableWebRequest.DefaultInstance; + UnityEngine.Debug.Log("Running get test..."); + var getStatus = webRequest.Get("http://localhost:8000/get?foo1=bar1&foo2=bar2", + new Dictionary { + { "Echo-Foo", "Bar" }, + { "Echo-Bish", "Bosh" } + }); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + var complete = getStatus.Complete; + if (complete) { + getSucceeded = CheckEqual(getStatus.Status, HttpStatusCode.OK); + getSucceeded &= CheckEqual(DictionaryToString("echo-", true, getStatus.Headers), + "echo-bish=Bosh\n" + + "echo-foo=Bar"); + var result = System.Text.Encoding.Default.GetString(getStatus.Result); + var expected = + "{\"data\": \"Hello from a test server\", " + + "\"headers\": {\"Echo-Bish\": \"Bosh\", \"Echo-Foo\": \"Bar\"}, " + + "\"path\": \"/get?foo1=bar1&foo2=bar2\", " + + "\"query\": {\"foo1\": [\"bar1\"], \"foo2\": [\"bar2\"]}}"; + getSucceeded &= CheckEqual(result, expected); + UnityEngine.Debug.Log(String.Format("Get complete succeeded={0}\n{1}", + getSucceeded, result)); + } + return complete; + }, synchronous: true); + + UnityEngine.Debug.Log("Running get with no query test..."); + var getStatusNoQuery = webRequest.Get("http://localhost:8000/get_with_no_query", + new Dictionary { + { "Echo-Foo", "Bar" }, + { "Echo-Bish", "Bosh" } + }); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + var complete = getStatusNoQuery.Complete; + if (complete) { + getNoQuerySucceeded = CheckEqual(getStatusNoQuery.Status, HttpStatusCode.OK); + getNoQuerySucceeded &= CheckEqual( + DictionaryToString("echo-", true, getStatusNoQuery.Headers), + "echo-bish=Bosh\n" + + "echo-foo=Bar"); + var result = System.Text.Encoding.Default.GetString(getStatusNoQuery.Result); + var expected = + "{\"data\": \"Hello from a test server\", " + + "\"headers\": {\"Echo-Bish\": \"Bosh\", \"Echo-Foo\": \"Bar\"}, " + + "\"path\": \"/get_with_no_query\", " + + "\"query\": {}}"; + getNoQuerySucceeded &= CheckEqual(result, expected); + UnityEngine.Debug.Log(String.Format("Get with no query succeeded={0}\n{1}", + getNoQuerySucceeded, result)); + } + return complete; + }, synchronous: true); + + UnityEngine.Debug.Log("Running get with no headers test..."); + var getStatusNoHeaders = webRequest.Get("http://localhost:8000/get?foo1=bar1&foo2=bar2", + null); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + var complete = getStatusNoHeaders.Complete; + if (complete) { + getNoHeadersSucceeded = CheckEqual(getStatusNoHeaders.Status, + HttpStatusCode.OK); + getNoHeadersSucceeded &= CheckEqual(DictionaryToString("echo-", true, + getStatusNoHeaders.Headers), ""); + var result = System.Text.Encoding.Default.GetString(getStatusNoHeaders.Result); + var expected = + "{\"data\": \"Hello from a test server\", " + + "\"headers\": {}, " + + "\"path\": \"/get?foo1=bar1&foo2=bar2\", " + + "\"query\": {\"foo1\": [\"bar1\"], \"foo2\": [\"bar2\"]}}"; + getNoHeadersSucceeded &= CheckEqual(result, expected); + UnityEngine.Debug.Log(String.Format("Get with no headers succeeded={0}\n{1}", + getNoHeadersSucceeded, result)); + } + return complete; + }, synchronous: true); + + UnityEngine.Debug.Log("Running post test..."); + var postStatus = webRequest.Post( + "http://localhost:8000/post?queryfoo1=querybar1&queryfoo2=querybar2", + headers: new Dictionary { + { "Echo-Foo", "Bar" }, + { "Echo-Bish", "Bosh" } + }, + formFields: new[] { + new KeyValuePair("foo1", "bar1"), + new KeyValuePair("foo2", "bar2") + }); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + var complete = postStatus.Complete; + if (complete) { + postSucceeded = CheckEqual(postStatus.Status, HttpStatusCode.OK); + postSucceeded &= CheckEqual( + DictionaryToString("echo-", true, postStatus.Headers), + "echo-bish=Bosh\n" + + "echo-foo=Bar"); + var result = System.Text.Encoding.Default.GetString(postStatus.Result); + var expected = + "{\"data\": \"Hello from a test server\", " + + "\"form\": {\"foo1\": [\"bar1\"], \"foo2\": [\"bar2\"]}, " + + "\"headers\": {\"Echo-Bish\": \"Bosh\", \"Echo-Foo\": \"Bar\"}, " + + "\"path\": \"/post?queryfoo1=querybar1&queryfoo2=querybar2\", " + + "\"query\": {\"queryfoo1\": [\"querybar1\"], " + + "\"queryfoo2\": [\"querybar2\"]}}"; + postSucceeded &= CheckEqual(result, expected); + UnityEngine.Debug.Log(String.Format("Post complete succeeded={0}\n{1}", + postSucceeded, result)); + } + return complete; + }, synchronous: true); + + UnityEngine.Debug.Log("Running post test with no form fields..."); + var postNoFormFieldsStatus = webRequest.Post( + "http://localhost:8000/post", + queryParams: new[] { + new KeyValuePair("foo1", "bar1"), + new KeyValuePair("foo2", "bar2"), + new KeyValuePair("foo with/special+char", + "bar with/special+char") + }, + headers: new Dictionary { + { "Echo-Foo", "Bar" }, + { "Echo-Bish", "Bosh" } + }, + formFields: null); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + var complete = postNoFormFieldsStatus.Complete; + if (complete) { + postNoFormFieldsSucceeded = CheckEqual(postNoFormFieldsStatus.Status, + HttpStatusCode.OK); + postNoFormFieldsSucceeded &= CheckEqual( + DictionaryToString("echo-", true, postNoFormFieldsStatus.Headers), + "echo-bish=Bosh\n" + + "echo-foo=Bar"); + var result = System.Text.Encoding.Default.GetString( + postNoFormFieldsStatus.Result); + var expected = + "{\"data\": \"Hello from a test server\", " + + "\"headers\": {\"Echo-Bish\": \"Bosh\", \"Echo-Foo\": \"Bar\"}, " + + "\"path\": \"/post?foo1=bar1&foo2=bar2&foo%20with%2Fspecial%2Bchar=bar%20with%2Fspecial%2Bchar\", " + + "\"query\": {\"foo with/special+char\": [\"bar with/special+char\"], " + + "\"foo1\": [\"bar1\"], \"foo2\": [\"bar2\"]}}"; + postNoFormFieldsSucceeded &= CheckEqual(result, expected); + UnityEngine.Debug.Log(String.Format( + "Post with no firm fields succeeded={0}\n{1}", + postNoFormFieldsSucceeded, result)); + } + return complete; + }, synchronous: true); + + UnityEngine.Debug.Log("Running post test with no headers..."); + var postNoHeadersStatus = webRequest.Post( + "http://localhost:8000/post/with/no/headers", + headers: null, + formFields: new[] { + new KeyValuePair("foo1", "bar1"), + new KeyValuePair("foo2", "bar2") + }); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + var complete = postNoHeadersStatus.Complete; + if (complete) { + postNoHeadersSucceeded = CheckEqual(postNoHeadersStatus.Status, + HttpStatusCode.OK); + postNoHeadersSucceeded &= CheckEqual(DictionaryToString( + "echo-", true, postNoHeadersStatus.Headers), ""); + var result = System.Text.Encoding.Default.GetString(postNoHeadersStatus.Result); + var expected = + "{\"data\": \"Hello from a test server\", " + + "\"form\": {\"foo1\": [\"bar1\"], \"foo2\": [\"bar2\"]}, " + + "\"headers\": {}, " + + "\"path\": \"/post/with/no/headers\", \"query\": {}}"; + postNoHeadersSucceeded &= CheckEqual(result, expected); + UnityEngine.Debug.Log(String.Format("Post with no headers succeeded={0}\n{1}", + postNoHeadersSucceeded, result)); + } + return complete; + }, synchronous: true); + + // Exit when the tests are complete. + if (!(getSucceeded && getNoQuerySucceeded && getNoHeadersSucceeded && postSucceeded + && postNoHeadersSucceeded)) { + UnityEngine.Debug.Log("Test failed"); + UnityEditor.EditorApplication.Exit(1); + } + UnityEngine.Debug.Log("Test passed"); + UnityEditor.EditorApplication.Exit(0); + } +} diff --git a/source/VersionHandlerImpl/test/webrequest/Assets/PlayServicesResolver/Editor/TestPortableWebRequest.cs_DISABLED.meta b/source/VersionHandlerImpl/test/webrequest/Assets/PlayServicesResolver/Editor/TestPortableWebRequest.cs_DISABLED.meta new file mode 100644 index 00000000..7f6da84e --- /dev/null +++ b/source/VersionHandlerImpl/test/webrequest/Assets/PlayServicesResolver/Editor/TestPortableWebRequest.cs_DISABLED.meta @@ -0,0 +1,16 @@ +fileFormatVersion: 2 +guid: a0e7a1be153b040ff916da8b65c4f433 +timeCreated: 1570835312 +licenseType: Pro +labels: +- gvh +- gvh_rename_to_disable +- gvh_targets-editor +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/VersionHandlerImpl/test/webrequest_launcher.py b/source/VersionHandlerImpl/test/webrequest_launcher.py new file mode 100755 index 00000000..9adf9d3d --- /dev/null +++ b/source/VersionHandlerImpl/test/webrequest_launcher.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +# 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. + +import http +import http.server +import json +import socketserver +import subprocess +import sys +import threading +import urllib.parse + + +class TestServer(socketserver.TCPServer): + """TCPServer that starts an integration test .""" + + # If the process is restarted, this will reuse the OS socket used by this + # server. + allow_reuse_address = True + + def __init__(self, server_address, RequestHandlerClass, + bind_and_activate=True): + """Initialize with no callable for the service_actions() event. + + Args: + server_address: Address of the server. + RequestHandlerClass: Class that handles requests. + bind_and_activate: The constructor automatically attempts to invoke + server_bind() and server_activate(). + """ + super().__init__(server_address, RequestHandlerClass, bind_and_activate) + self.__service_actions_callable = None + + def set_service_actions_callable(self, callback): + """Stores a method that is called on the next poll of service_actions. + + Args: + callback: Callable to call on the next poll of service_actions. + """ + self.__service_actions_callable = callback + + def service_actions(self): + """Calls the callable registered with set_service_actions_callable().""" + if self.__service_actions_callable: + self.__service_actions_callable() + self.__service_actions_callable = None + + +class TestHandler(http.server.BaseHTTPRequestHandler): + """Echos GET and POST requests.""" + + def build_response(self): + """Build a common response for GET and POST requests. + + Writes a successful response with the JSON payload containing: + * requested path + * parsed query as a dictionary + * parsed form data as a dictionary + * constant data in the "data" field. + """ + url = urllib.parse.urlparse(self.path) + query = urllib.parse.parse_qs(url.query) if url.query else {} + + # If the request has a payload, parse it. + form_data = {} + content_length = int(self.headers.get("Content-Length", "0")) + if (content_length and + self.headers.get("Content-Type", "") == + "application/x-www-form-urlencoded"): + form_data = urllib.parse.parse_qs( + self.rfile.read(content_length).decode()) + + # Echo headers that start with "Echo". + echo_headers = {} + for header_key, header_value in self.headers.items(): + if header_key.startswith("Echo"): + echo_headers[header_key] = header_value + + response = { + "data": "Hello from a test server", + "headers": echo_headers, + "path": self.path, + "query": query} + if form_data: + response["form"] = form_data + + self.send_response(http.HTTPStatus.OK) + for header_key, header_value in echo_headers.items(): + self.send_header(header_key, header_value) + self.end_headers() + self.wfile.write(json.dumps(response, sort_keys=True).encode()) + + def do_GET(self): + """Handle a GET request. + + Writes a success header with the requested path and query and some constant + data in the response. + """ + self.build_response() + + def do_POST(self): + """Handle a POST request. + + Writes a success header with the requested path and query, posted form data + and some constant data in the response. + """ + self.build_response() + + +class TestRunner(threading.Thread): + """Runs a test process, stopping the TCP server when the test is complete.""" + + def __init__(self, tcp_server, test_args, **kwargs): + """Initialize the test runner. + + Args: + test_args: Subprocess arguments to start the test. + tcp_server: Server to stop when the test is complete. + **kwargs: Arguments to pass to the Thread constructor. + """ + self.tcp_server = tcp_server + self.test_args = test_args + self.process_result = -1 + super().__init__(**kwargs) + + def __call__(self): + """Starts the thread.""" + self.start() + + def run(self): + """Start the test subprocess and stop the server when it's complete.""" + with subprocess.Popen(self.test_args) as proc: + proc.wait() + self.process_result = proc.returncode + self.tcp_server.shutdown() + +def main(): + """Run a test web server and start a test by forking a process. + + The test web server is started by this method which runs a subprocess using + the arguments of this script. When the subprocess is complete the server is + shut down. + """ + subprocess_arguments = sys.argv[1:] + runner = None + with TestServer(("localhost", 8000), TestHandler) as httpd: + runner = TestRunner(httpd, subprocess_arguments) + httpd.set_service_actions_callable(runner) + httpd.serve_forever() + return runner.process_result if runner else -1 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/EditorMeasurementTest.cs b/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/EditorMeasurementTest.cs new file mode 100644 index 00000000..a41593b3 --- /dev/null +++ b/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/EditorMeasurementTest.cs @@ -0,0 +1,561 @@ +// +// 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.VersionHandlerImpl.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.Net; + using System.IO; + using System.Text; + using System.Web; + + using Google; + + /// + /// Tests the EditorMeasurement class. + /// + [TestFixture] + public class EditorMeasurementTest { + + /// + /// Fake implementation of IPortableWebRequest that aggregates posted data. + /// + class FakePortableWebRequest : IPortableWebRequest { + + /// + /// Fake implementation of IPortableWebRequestStatus that returns a completed request. + /// + private class RequestStatus : IPortableWebRequestStatus { + + /// + /// Always returns true. + /// + public bool Complete { get { return true; } } + + /// + /// Returns an empty result. + /// + public byte[] Result { get { return new byte[0]; } } + + /// + /// Get the response headers. + /// + public IDictionary Headers { + get { + return new Dictionary(); + } + } + + /// + /// Get the status code from the response headers. + /// + public HttpStatusCode Status { get { return HttpStatusCode.OK; } } + } + + /// + /// List of posted data. + /// + public List> PostedUrlAndForms { get; private set; } + + /// + /// Initialize the list of posted data. + /// + public FakePortableWebRequest() { + PostedUrlAndForms = new List>(); + } + + /// + /// Start a post request that is immediately completed. + /// + public IPortableWebRequestStatus Post( + string url, IDictionary headers, + IEnumerable> formFields) { + var formLines = new List(); + if (formFields != null) { + foreach (var kv in formFields) { + var value = kv.Value; + // Change the random cache buster to 0. + if (kv.Key == "z") value = "0"; + formLines.Add(String.Format("{0}={1}", kv.Key, value)); + } + } + + PostedUrlAndForms.Add( + new KeyValuePair(url, String.Join("\n", formLines.ToArray()))); + return new RequestStatus(); + } + + /// + /// Start a post request that is immediately completed. + /// + public IPortableWebRequestStatus Post( + string path, IEnumerable> queryParams, + IDictionary headers, + IEnumerable> formFields) + { + var url = new StringBuilder(256); + foreach (var param in queryParams) { + var value = param.Value; + // Change the random cache buster to 0. + if (param.Key == "z") value = "0"; + + url.AppendFormat("{0}{1}={2}", + url.Length == 0 ? "?" : "&", + Uri.EscapeDataString(param.Key).Trim(), + Uri.EscapeDataString(value).Trim()); + } + url.Insert(0, path); + return Post(url.ToString(), headers, formFields); + } + + public IPortableWebRequestStatus Get(string url, IDictionary headers) { + throw new NotImplementedException("PortableWebRequest.Get() should not be called"); + } + } + + private const string GA_TRACKING_ID = "a-test-id"; + private const string PLUGIN_NAME = "my plugin"; + private const string SETTINGS_NAMESPACE = "com.foo.myplugin"; + private const string DATA_COLLECTION_DESCRIPTION = "to improve my plugin"; + private const string PRIVACY_POLICY = "http://a.link.to/a/privacy/policy"; + + private ProjectSettings settings = new ProjectSettings("EditorMeasurementTest"); + private List openedUrls = new List(); + private FakePortableWebRequest webRequest; + + + /// + /// Isolate ProjectSettings from Unity APIs and global state. + /// + [SetUp] + public void Setup() { + ProjectSettings.persistenceEnabled = false; + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + ProjectSettings.logger.Target = LogTarget.Console; + ProjectSettings.checkoutFile = (filename, logger) => { return true; }; + EditorMeasurement.GloballyEnabled = true; + EditorMeasurement.unityVersion = "5.6.1f1"; + EditorMeasurement.unityRuntimePlatform = "WindowsEditor"; + webRequest = new FakePortableWebRequest(); + PortableWebRequest.DefaultInstance = webRequest; + openedUrls.Clear(); + } + + /// + /// Create an EditorMeasurement instance. + /// + private EditorMeasurement CreateEditorMeasurement() { + var analytics = new EditorMeasurement(settings, ProjectSettings.logger, GA_TRACKING_ID, + SETTINGS_NAMESPACE, PLUGIN_NAME, + DATA_COLLECTION_DESCRIPTION, PRIVACY_POLICY); + analytics.displayDialog = (title, message, defaultOption, option0, option1, Nullable, + windowWidth, windowCloseOption, + complete, renderContent, renderButtons, init) => { + throw new Exception("Unexpected dialog displayed"); + }; + analytics.openUrl = (url) => { + openedUrls.Add(url); + }; + analytics.ReportUnityVersion = false; + analytics.ReportUnityPlatform = false; + return analytics; + } + + /// + /// Concatenate query strings. + /// + [Test] + public void ConcatenateQueryStrings() { + Assert.That(EditorMeasurement.ConcatenateQueryStrings(null, null), Is.EqualTo(null)); + Assert.That(EditorMeasurement.ConcatenateQueryStrings("foo", null), Is.EqualTo("foo")); + Assert.That(EditorMeasurement.ConcatenateQueryStrings(null, "foo"), Is.EqualTo("foo")); + Assert.That(EditorMeasurement.ConcatenateQueryStrings(null, "foo"), Is.EqualTo("foo")); + Assert.That(EditorMeasurement.ConcatenateQueryStrings("foo=bar", "bish=bosh"), + Is.EqualTo("foo=bar&bish=bosh")); + Assert.That(EditorMeasurement.ConcatenateQueryStrings("?foo=bar&", "bish=bosh"), + Is.EqualTo("foo=bar&bish=bosh")); + } + + /// + /// Test construction of an EditorMeasurement instance. + /// + [Test] + public void Construct() { + var analytics = CreateEditorMeasurement(); + Assert.That(analytics.PluginName, Is.EqualTo(PLUGIN_NAME)); + Assert.That(analytics.DataCollectionDescription, + Is.EqualTo(DATA_COLLECTION_DESCRIPTION)); + Assert.That(analytics.Enabled, Is.EqualTo(true)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(false)); + Assert.That(analytics.Cookie, Is.EqualTo("")); + Assert.That(analytics.SystemCookie, Is.EqualTo("")); + Assert.That(openedUrls, Is.EqualTo(new List())); + } + + /// + /// Create a display dialog delegate. + /// + /// 0..2 + /// Display dialog delegate. + private DialogWindow.DisplayDelegate CreateDisplayDialogDelegate( + List selectedOptions) { + return (string title, string message, DialogWindow.Option defaultOption, + string option0, string option1, string option2, + float windowWidth, DialogWindow.Option windowCloseOption, + Action complete, + Action renderContent, + Action renderButtons, + Action init) => { + Assert.That(title, Is.Not.Empty); + Assert.That(message, Is.Not.Empty); + Assert.That(defaultOption, Is.EqualTo(DialogWindow.Option.Selected1)); + Assert.That(option0, Is.Not.Empty); + Assert.That(option1, Is.Not.Empty); + Assert.That(option2, Is.Null); + Assert.That(windowCloseOption, Is.EqualTo(DialogWindow.Option.SelectedNone)); + Assert.That(complete, Is.Not.Null); + Assert.That(renderContent, Is.Not.Null); + Assert.That(renderButtons, Is.Not.Null); + var selectedOption = selectedOptions[0]; + selectedOptions.RemoveAt(0); + complete(selectedOption); + }; + } + + /// + /// Request for consent to enable analytics reporting, which is approved by the user. + /// + [Test] + public void PromptToEnableYes() { + var analytics = CreateEditorMeasurement(); + analytics.displayDialog = CreateDisplayDialogDelegate( + new List { + DialogWindow.Option.Selected0 /* yes */ + }); + Assert.That(analytics.Enabled, Is.EqualTo(true)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(false)); + analytics.PromptToEnable(() => {}); + Assert.That(analytics.Enabled, Is.EqualTo(true)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(true)); + Assert.That(openedUrls, Is.EqualTo(new List())); + } + + /// + /// Request for consent to enable analytics reporting, which is denied by the user. + /// + [Test] + public void PromptToEnableNo() { + var analytics = CreateEditorMeasurement(); + analytics.displayDialog = CreateDisplayDialogDelegate( + new List { + DialogWindow.Option.Selected1 /* no */ + }); + Assert.That(analytics.Enabled, Is.EqualTo(true)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(false)); + analytics.PromptToEnable(() => {}); + Assert.That(analytics.Enabled, Is.EqualTo(false)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(true)); + Assert.That(openedUrls, Is.EqualTo(new List())); + } + + /// + /// Request for consent to enable analytics reporting then restore default settings which + /// should revoke consent. + /// + [Test] + public void RestoreDefaultSettings() { + var analytics = CreateEditorMeasurement(); + analytics.displayDialog = CreateDisplayDialogDelegate( + new List { + DialogWindow.Option.Selected1 /* no */ + }); + Assert.That(analytics.Enabled, Is.EqualTo(true)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(false)); + analytics.PromptToEnable(() => {}); + Assert.That(analytics.Enabled, Is.EqualTo(false)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(true)); + Assert.That(openedUrls, Is.EqualTo(new List())); + + analytics.RestoreDefaultSettings(); + Assert.That(analytics.Enabled, Is.EqualTo(true)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(false)); + } + + /// + /// Verify cookies are generated after analytics is enabled. + /// + [Test] + public void GenerateCookies() { + var analytics = CreateEditorMeasurement(); + analytics.displayDialog = CreateDisplayDialogDelegate( + new List { + DialogWindow.Option.Selected0 /* yes */ + }); + Assert.That(analytics.Cookie, Is.EqualTo("")); + Assert.That(analytics.SystemCookie, Is.EqualTo("")); + analytics.PromptToEnable(() => {}); + Assert.That(analytics.Cookie, Is.Not.EqualTo("")); + Assert.That(analytics.SystemCookie, Is.Not.EqualTo("")); + Assert.That(openedUrls, Is.EqualTo(new List())); + } + + /// + /// Verify consent is requested when trying reporting an event without consent. + /// + [Test] + public void ReportWithoutConsent() { + var analytics = CreateEditorMeasurement(); + var selectedOptions = new List { + DialogWindow.Option.Selected1 /* no */ }; + analytics.displayDialog = CreateDisplayDialogDelegate(selectedOptions); + analytics.Report("/a/new/event", "something interesting"); + analytics.Report("/a/new/event", "something else"); + analytics.Report("/a/new/event", + new KeyValuePair[] { + new KeyValuePair("foo", "bar"), + new KeyValuePair("bish", "bosh") + }, "something with parameters"); + Assert.That(selectedOptions, Is.EqualTo(new List())); + Assert.That(analytics.Enabled, Is.EqualTo(false)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(true)); + Assert.That(webRequest.PostedUrlAndForms, + Is.EqualTo(new List>())); + } + + /// + /// Verify nothing is reported if the class is globally disabled. + /// + [Test] + public void ReportWhenDisabled() { + EditorMeasurement.GloballyEnabled = false; + var analytics = CreateEditorMeasurement(); + analytics.Report("/a/new/event", "something interesting"); + analytics.Report("/a/new/event", "something else"); + analytics.Report("/a/new/event", + new KeyValuePair[] { + new KeyValuePair("foo", "bar"), + new KeyValuePair("bish", "bosh") + }, "something with parameters"); + Assert.That(analytics.Enabled, Is.EqualTo(true)); + Assert.That(analytics.ConsentRequested, Is.EqualTo(false)); + Assert.That(webRequest.PostedUrlAndForms, + Is.EqualTo(new List>())); + } + + /// + /// Create an expected URL and form string for the Google Analytics v1 measurement protocol. + /// + /// URL being reported. + /// Name / title of the URL. + /// Cookie used for the report. + private KeyValuePair CreateMeasurementEvent( + string reportUrl, string reportName, string cookie) { + return new KeyValuePair( + String.Format("https://www.google-analytics.com/collect" + + "?v=1" + + "&tid={0}" + + "&cid={1}" + + "&t=pageview" + + "&dl={2}" + + "&dt={3}" + + "&z=0", + Uri.EscapeDataString(GA_TRACKING_ID).Trim(), + Uri.EscapeDataString(cookie).Trim(), + Uri.EscapeDataString(reportUrl).Trim(), + Uri.EscapeDataString(reportName).Trim()), + "" /* empty form fields */); + } + + /// + /// Create a list of expected URL and form strings for the Google Analytics v1 measurement + /// protocol using the system and project cookies. + /// + /// Object to get cookies from. + /// URL being reported. + /// Name / title of the URL. + private KeyValuePair[] CreateMeasurementEvents( + EditorMeasurement analytics, string reportPath, string reportQuery, + string reportAnchor, string reportName) { + Func createUrl = (string scope) => { + return reportPath + reportQuery + (String.IsNullOrEmpty(reportQuery) ? "?" : "&") + + "scope=" + scope + reportAnchor; + }; + return new KeyValuePair[] { + CreateMeasurementEvent(createUrl("project"), reportName, analytics.Cookie), + CreateMeasurementEvent(createUrl("system"), reportName, analytics.SystemCookie), + }; + } + + /// + /// Report an event after obtaining consent. + /// + [Test] + public void ReportWithConsent() { + var analytics = CreateEditorMeasurement(); + var selectedOptions = new List { + DialogWindow.Option.Selected0 /* yes */ }; + analytics.displayDialog = CreateDisplayDialogDelegate(selectedOptions); + analytics.Report("/a/new/event", "something interesting"); + analytics.Report("/a/new/event#neat", "something else"); + analytics.Report("/a/new/event?setting=foo", "something interesting"); + analytics.Report("/a/new/event?setting=bar#neat", "something else"); + analytics.Report("/a/new/event", + new KeyValuePair[] { + new KeyValuePair("foo", "bar"), + new KeyValuePair("bish", "bosh") + }, "something with parameters"); + Assert.That(selectedOptions, Is.EqualTo(new List())); + var expectedEvents = new List>(); + expectedEvents.AddRange(CreateMeasurementEvents(analytics, "/a/new/event", "", "", + "something interesting")); + expectedEvents.AddRange(CreateMeasurementEvents(analytics, "/a/new/event", "", "#neat", + "something else")); + expectedEvents.AddRange(CreateMeasurementEvents(analytics, "/a/new/event", + "?setting=foo", "", + "something interesting")); + expectedEvents.AddRange(CreateMeasurementEvents(analytics, "/a/new/event", + "?setting=bar", "#neat", + "something else")); + expectedEvents.AddRange(CreateMeasurementEvents(analytics, + "/a/new/event", + "?foo=bar&bish=bosh", "", + "something with parameters")); + Assert.That(webRequest.PostedUrlAndForms, Is.EqualTo(expectedEvents)); + } + + /// + /// Report an event after obtaining consent adding a base path, query and report name. + /// + [Test] + public void ReportWithConsentWithBasePathQueryAndReportName() { + var analytics = CreateEditorMeasurement(); + analytics.BasePath = "/myplugin"; + analytics.BaseQuery = "version=1.2.3"; + analytics.BaseReportName = "My Plugin: "; + var selectedOptions = new List { + DialogWindow.Option.Selected0 /* yes */ }; + analytics.displayDialog = CreateDisplayDialogDelegate(selectedOptions); + analytics.Report("/a/new/event", "something interesting"); + Assert.That(webRequest.PostedUrlAndForms, + Is.EqualTo(CreateMeasurementEvents(analytics, + "/myplugin/a/new/event", + "?version=1.2.3", "", + "My Plugin: something interesting"))); + } + + /// + /// Report an event after obtaining consent adding a base path, query, common query + /// parameters and report name. + /// + [Test] + public void ReportWithConsentWithBasePathQueryCommonParamsAndReportName() { + var analytics = CreateEditorMeasurement(); + analytics.ReportUnityVersion = true; + analytics.ReportUnityPlatform = true; + analytics.InstallSource = "unitypackage"; + analytics.BasePath = "/myplugin"; + analytics.BaseQuery = "version=1.2.3"; + analytics.BaseReportName = "My Plugin: "; + var selectedOptions = new List { + DialogWindow.Option.Selected0 /* yes */ }; + analytics.displayDialog = CreateDisplayDialogDelegate(selectedOptions); + analytics.Report("/a/new/event", "something interesting"); + Assert.That(webRequest.PostedUrlAndForms, + Is.EqualTo(CreateMeasurementEvents( + analytics, + "/myplugin/a/new/event", + "?unityVersion=5.6.1f1&unityPlatform=WindowsEditor&" + + "installSource=unitypackage&version=1.2.3", "", + "My Plugin: something interesting"))); + } + + /// + /// Test reporting the install source based upon the specified filename. + /// + /// Path of the installed file. + /// Install source that should be reported. + public void TestInstallSourceFilename(string filename, string expectedInstallSource) { + var analytics = CreateEditorMeasurement(); + analytics.ReportUnityVersion = true; + analytics.ReportUnityPlatform = true; + analytics.InstallSource = null; + analytics.InstallSourceFilename = filename; + var selectedOptions = new List { + DialogWindow.Option.Selected0 /* yes */ }; + analytics.displayDialog = CreateDisplayDialogDelegate(selectedOptions); + analytics.Report("/a/new/event", "something interesting"); + Assert.That(webRequest.PostedUrlAndForms, + Is.EqualTo(CreateMeasurementEvents( + analytics, + "/a/new/event", + "?unityVersion=5.6.1f1&unityPlatform=WindowsEditor&" + + "installSource=" + expectedInstallSource, "", + "something interesting"))); + } + + /// + /// Report an event after obtaining consent using an install filename in the Assets folder. + /// + [Test] + public void ReportWithConsentWithInstallSourceFilenameAssets() { + TestInstallSourceFilename( + Path.Combine(Path.Combine(Directory.GetCurrentDirectory(), + Path.Combine("Assets", "MyPlugin")), + "MyComponent.dll"), + "unitypackage"); + } + + /// + /// Report an event after obtaining consent using an install filename in the Library + /// folder. + /// + [Test] + public void ReportWithConsentWithInstallSourceFilenameLibrary() { + TestInstallSourceFilename( + Path.Combine(Path.Combine(Directory.GetCurrentDirectory(), + Path.Combine("Library", "MyPlugin")), + "MyComponent.dll"), "upm"); + } + + /// + /// Report an event when opening a URL. + /// + [Test] + public void OpenUrl() { + var analytics = CreateEditorMeasurement(); + var selectedOptions = new List { + DialogWindow.Option.Selected0 /* yes */ }; + analytics.displayDialog = CreateDisplayDialogDelegate(selectedOptions); + analytics.OpenUrl("https://github.com/googlesamples/unity-jar-resolver?do=something" + + "#version-handler-usage", "Version Handler Usage"); + Assert.That(selectedOptions, Is.EqualTo(new List())); + Assert.That(openedUrls, + Is.EqualTo(new List() { + "https://github.com/googlesamples/unity-jar-resolver?do=something" + + "#version-handler-usage" + })); + Assert.That(webRequest.PostedUrlAndForms, + Is.EqualTo(CreateMeasurementEvents(analytics, + "/github.com/googlesamples/" + + "unity-jar-resolver", + "?do=something", + "#version-handler-usage", + "Version Handler Usage"))); + } + } +} diff --git a/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/FileUtilsTest.cs b/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/FileUtilsTest.cs new file mode 100644 index 00000000..3aaa62f6 --- /dev/null +++ b/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/FileUtilsTest.cs @@ -0,0 +1,440 @@ +// +// 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, +// WITHBar 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.VersionHandlerImpl.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + + using Google; + + /// + /// Test the ProjectSettings class. + /// + [TestFixture] + public class FileUtilsTest { + + /// + /// Isolate ProjectSettings from Unity APIs and global state. + /// + [SetUp] + public void Setup() { + } + + /// + /// Test FileUtils.IsUnderDirectory() + /// + [Test] + public void IsUnderDirectory() { + Assert.That(FileUtils.IsUnderDirectory("Foo", "Foo"), Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("Foo", "Foo/"), Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("Foo/", "Foo"), Is.EqualTo(true)); + Assert.That(FileUtils.IsUnderDirectory("Foo/", "Foo/"), Is.EqualTo(true)); + Assert.That(FileUtils.IsUnderDirectory("Foo/Bar", "Foo"), + Is.EqualTo(true)); + Assert.That(FileUtils.IsUnderDirectory("Foo/Bar", "Foo/"), + Is.EqualTo(true)); + Assert.That(FileUtils.IsUnderDirectory("Foo/Bar", "Foo/Bar"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("Foo/Bar", "Foo/Bar/"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("Foo/Bar/", "Foo/Bar"), + Is.EqualTo(true)); + Assert.That(FileUtils.IsUnderDirectory("Foo/Bar/", "Foo/Bar/"), + Is.EqualTo(true)); + + Assert.That(FileUtils.IsUnderDirectory("", ""), Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("Foo", ""), Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("", "Foo"), Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("Foo", "/"), Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("Foo", "Some"), Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderDirectory("Foo/Bar", "Bar"), + Is.EqualTo(false)); + + } + + /// + /// Test FileUtils.GetPackageDirectoryType() + /// + [Test] + public void GetPackageDirectoryType() { + Assert.That(FileUtils.GetPackageDirectoryType(""), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType("Foo/Bar"), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType("Packages"), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType("Packages/"), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType("Packages/com.company.pkg"), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType("Packages/com.company.pkg/"), + Is.EqualTo(FileUtils.PackageDirectoryType.AssetDatabasePath)); + Assert.That(FileUtils.GetPackageDirectoryType("Packages/com.company.pkg/Foo"), + Is.EqualTo(FileUtils.PackageDirectoryType.AssetDatabasePath)); + Assert.That(FileUtils.GetPackageDirectoryType("Packages/com.company.pkg/Foo/Bar"), + Is.EqualTo(FileUtils.PackageDirectoryType.AssetDatabasePath)); + Assert.That(FileUtils.GetPackageDirectoryType("Library"), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType("Library/PackageCache"), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType("Library/PackageCache/"), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType( + "Library/PackageCache/com.company.pkg@1.2.3"), + Is.EqualTo(FileUtils.PackageDirectoryType.None)); + Assert.That(FileUtils.GetPackageDirectoryType( + "Library/PackageCache/com.company.pkg@1.2.3/Foo"), + Is.EqualTo(FileUtils.PackageDirectoryType.PhysicalPath)); + Assert.That(FileUtils.GetPackageDirectoryType( + "Library/PackageCache/com.company.pkg@1.2.3/Foo/Bar"), + Is.EqualTo(FileUtils.PackageDirectoryType.PhysicalPath)); + } + + /// + /// Test FileUtils.IsUnderPackageDirectory() + /// + [Test] + public void IsUnderPackageDirectory() { + Assert.That(FileUtils.IsUnderPackageDirectory(""), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory("Foo/Bar"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory("Packages"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory("Packages/"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory("Packages/com.company.pkg"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory("Packages/com.company.pkg/Foo"), + Is.EqualTo(true)); + Assert.That(FileUtils.IsUnderPackageDirectory("Packages/com.company.pkg/Foo/Bar"), + Is.EqualTo(true)); + Assert.That(FileUtils.IsUnderPackageDirectory("Library"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory("Library/PackageCache"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory("Library/PackageCache/"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3"), + Is.EqualTo(false)); + Assert.That(FileUtils.IsUnderPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/Foo"), + Is.EqualTo(true)); + Assert.That(FileUtils.IsUnderPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/Foo/Bar"), + Is.EqualTo(true)); + } + + /// + /// Test FileUtils.GetPackageDirectory() + /// + [Test] + public void GetPackageDirectory() { + const string expectedAssetDBPath = "Packages/com.company.pkg"; + const string expectedActualPath = "Library/PackageCache/com.company.pkg@1.2.3"; + + Assert.That(FileUtils.GetPackageDirectory(""), Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory("Foo/Bar"), Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory("Packages"), Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory("Packages/"), Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg"), + Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg/"), + Is.EqualTo(expectedAssetDBPath)); + Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg/Foo"), + Is.EqualTo(expectedAssetDBPath)); + Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg/Foo/Bar"), + Is.EqualTo(expectedAssetDBPath)); + Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg", + FileUtils.PackageDirectoryType.AssetDatabasePath), + Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg/", + FileUtils.PackageDirectoryType.AssetDatabasePath), + Is.EqualTo(expectedAssetDBPath)); + Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg/Foo", + FileUtils.PackageDirectoryType.AssetDatabasePath), + Is.EqualTo(expectedAssetDBPath)); + Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg/Foo/Bar", + FileUtils.PackageDirectoryType.AssetDatabasePath), + Is.EqualTo(expectedAssetDBPath)); + // The following test does not work since it requires UnityEngine namespace + // TODO: Switch to IntegrationTest framework + // Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg", + // FileUtils.PackageDirectoryType.PhysicalPath), + // Is.EqualTo(expectedActualPath)); + // Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg/Foo", + // FileUtils.PackageDirectoryType.PhysicalPath), + // Is.EqualTo(expectedActualPath)); + // Assert.That(FileUtils.GetPackageDirectory("Packages/com.company.pkg/Foo/Bar", + // FileUtils.PackageDirectoryType.PhysicalPath), + // Is.EqualTo(expectedActualPath)); + Assert.That(FileUtils.GetPackageDirectory("Library"), + Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory("Library/PackageCache"), + Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory("Library/PackageCache/"), + Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3"), + Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/"), + Is.EqualTo(expectedActualPath)); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/Foo"), + Is.EqualTo(expectedActualPath)); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/Foo/Bar"), + Is.EqualTo(expectedActualPath)); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3", + FileUtils.PackageDirectoryType.AssetDatabasePath), + Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/", + FileUtils.PackageDirectoryType.AssetDatabasePath), + Is.EqualTo(expectedAssetDBPath)); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/Foo", + FileUtils.PackageDirectoryType.AssetDatabasePath), + Is.EqualTo(expectedAssetDBPath)); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/Foo/Bar", + FileUtils.PackageDirectoryType.AssetDatabasePath), + Is.EqualTo(expectedAssetDBPath)); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3", + FileUtils.PackageDirectoryType.PhysicalPath), + Is.EqualTo("")); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/", + FileUtils.PackageDirectoryType.PhysicalPath), + Is.EqualTo(expectedActualPath)); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/Foo", + FileUtils.PackageDirectoryType.PhysicalPath), + Is.EqualTo(expectedActualPath)); + Assert.That(FileUtils.GetPackageDirectory( + "Library/PackageCache/com.company.pkg@1.2.3/Foo/Bar", + FileUtils.PackageDirectoryType.PhysicalPath), + Is.EqualTo(expectedActualPath)); + + } + + /// + /// Test FileUtils.GetRelativePathFromAssetsOrPackagesFolder() + /// + [Test] + public void GetRelativePathFromAssetsOrPackagesFolder() { + string basePath; + string relativePath; + bool result; + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Assets", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(false)); + Assert.That(basePath, Is.EqualTo("")); + Assert.That(relativePath, Is.EqualTo("Assets")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Assets/", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Assets")); + Assert.That(relativePath, Is.EqualTo("")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Assets/Foo", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Assets")); + Assert.That(relativePath, Is.EqualTo("Foo")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Assets/Foo/", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Assets")); + Assert.That(relativePath, Is.EqualTo("Foo/")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Assets/Foo/Bar", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Assets")); + Assert.That(relativePath, Is.EqualTo("Foo/Bar")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Packages/com.company.pkg", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(false)); + Assert.That(basePath, Is.EqualTo("")); + Assert.That(relativePath, Is.EqualTo("Packages/com.company.pkg")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Packages/com.company.pkg/", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Packages/com.company.pkg")); + Assert.That(relativePath, Is.EqualTo("")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Packages/com.company.pkg/Foo", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Packages/com.company.pkg")); + Assert.That(relativePath, Is.EqualTo("Foo")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Packages/com.company.pkg/Foo/", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Packages/com.company.pkg")); + Assert.That(relativePath, Is.EqualTo("Foo/")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Packages/com.company.pkg/Foo/Bar", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Packages/com.company.pkg")); + Assert.That(relativePath, Is.EqualTo("Foo/Bar")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Library/PackageCache/com.company.pkg@1.2.3", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(false)); + Assert.That(basePath, Is.EqualTo("")); + Assert.That(relativePath, Is.EqualTo("Library/PackageCache/com.company.pkg@1.2.3")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Library/PackageCache/com.company.pkg@1.2.3/", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Library/PackageCache/com.company.pkg@1.2.3")); + Assert.That(relativePath, Is.EqualTo("")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Library/PackageCache/com.company.pkg@1.2.3/Foo", + out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Library/PackageCache/com.company.pkg@1.2.3")); + Assert.That(relativePath, Is.EqualTo("Foo")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Library/PackageCache/com.company.pkg@1.2.3/Foo/", + out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Library/PackageCache/com.company.pkg@1.2.3")); + Assert.That(relativePath, Is.EqualTo("Foo/")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Library/PackageCache/com.company.pkg@1.2.3/Foo/Bar", + out basePath, out relativePath); + Assert.That(result, Is.EqualTo(true)); + Assert.That(basePath, Is.EqualTo("Library/PackageCache/com.company.pkg@1.2.3")); + Assert.That(relativePath, Is.EqualTo("Foo/Bar")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "/Foo", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(false)); + Assert.That(basePath, Is.EqualTo("")); + Assert.That(relativePath, Is.EqualTo("/Foo")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Foo", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(false)); + Assert.That(basePath, Is.EqualTo("")); + Assert.That(relativePath, Is.EqualTo("Foo")); + + result = FileUtils.GetRelativePathFromAssetsOrPackagesFolder( + "Foo/Bar", out basePath, out relativePath); + Assert.That(result, Is.EqualTo(false)); + Assert.That(basePath, Is.EqualTo("")); + Assert.That(relativePath, Is.EqualTo("Foo/Bar")); + } + + /// + /// Test FileUtils.ReplaceBaseAssetsOrPackagesFolder() + /// + [Test] + public void ReplaceBaseAssetsOrPackagesFolder() { + Assert.That( + FileUtils.ReplaceBaseAssetsOrPackagesFolder("Assets/Bar", "Foo"), + Is.EqualTo("Foo/Bar")); + Assert.That( + FileUtils.ReplaceBaseAssetsOrPackagesFolder("Assets/Bar", "Assets/Foo"), + Is.EqualTo("Assets/Foo/Bar")); + Assert.That( + FileUtils.ReplaceBaseAssetsOrPackagesFolder( + "Packages/com.company.pkg/Bar", "Foo"), + Is.EqualTo("Foo/Bar")); + Assert.That( + FileUtils.ReplaceBaseAssetsOrPackagesFolder( + "Packages/com.company.pkg/Bar", "Assets/Foo"), + Is.EqualTo("Assets/Foo/Bar")); + Assert.That( + FileUtils.ReplaceBaseAssetsOrPackagesFolder( + "Library/PackageCache/com.company.pkg@1.2.3/Bar", "Foo"), + Is.EqualTo("Foo/Bar")); + Assert.That( + FileUtils.ReplaceBaseAssetsOrPackagesFolder( + "Library/PackageCache/com.company.pkg@1.2.3/Bar", "Assets/Foo"), + Is.EqualTo("Assets/Foo/Bar")); + + Assert.That( + FileUtils.ReplaceBaseAssetsOrPackagesFolder( + "Foo/Bar", "Assets"), + Is.EqualTo("Foo/Bar")); + } + + /// + /// Test FileUtils.IsValidGuid() when it returns true + /// + [Test] + public void IsValidGuid_TrueCases() { + Assert.That( + FileUtils.IsValidGuid("4b7c4a82-79ca-4eb5-a154-5d78a3b3d3d7"), + Is.EqualTo(true)); + + Assert.That( + FileUtils.IsValidGuid("017885d9f22374a53844077ede0ccda6"), + Is.EqualTo(true)); + } + + /// + /// Test FileUtils.IsValidGuid() when it returns false + /// + [Test] + public void IsValidGuid_FalseCases() { + Assert.That( + FileUtils.IsValidGuid(""), + Is.EqualTo(false)); + Assert.That( + FileUtils.IsValidGuid(null), + Is.EqualTo(false)); + Assert.That( + FileUtils.IsValidGuid("00000000-0000-0000-0000-000000000000"), + Is.EqualTo(false)); + Assert.That( + FileUtils.IsValidGuid("00000000000000000000000000000000"), + Is.EqualTo(false)); + Assert.That( + FileUtils.IsValidGuid("g000000000000000000000000000000"), + Is.EqualTo(false)); + Assert.That( + FileUtils.IsValidGuid(" "), + Is.EqualTo(false)); + Assert.That( + FileUtils.IsValidGuid("12300000 0000 0000 0000 000000000000"), + Is.EqualTo(false)); + Assert.That( + FileUtils.IsValidGuid("12300000\n0000\n0000\n0000\n000000000000"), + Is.EqualTo(false)); + } + } +} diff --git a/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/ProjectSettingsTest.cs b/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/ProjectSettingsTest.cs new file mode 100644 index 00000000..0e394380 --- /dev/null +++ b/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/ProjectSettingsTest.cs @@ -0,0 +1,442 @@ +// +// 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.VersionHandlerImpl.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + + using Google; + + /// + /// Test the InMemorySettings class. + /// + [TestFixture] + public class InMemorySettingsTest { + + /// + /// Construct an empty instance. + /// + [Test] + public void Construct() { + ISettings settings = new InMemorySettings(); + Assert.That(settings.Keys, Is.EqualTo(new List())); + } + + /// + /// Test Get*() methods with and without default values. + /// + [Test] + public void Get() { + ISettings settings = new InMemorySettings(); + Assert.That(settings.HasKey("int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("int"), Is.EqualTo(0)); + Assert.That(settings.GetInt("int", 42), Is.EqualTo(42)); + Assert.That(settings.GetBool("bool"), Is.EqualTo(false)); + Assert.That(settings.GetBool("bool", true), Is.EqualTo(true)); + Assert.That(settings.GetFloat("float"), Is.EqualTo(0.0f)); + Assert.That(settings.GetFloat("float", 3.14f), Is.EqualTo(3.14f)); + Assert.That(settings.GetString("string"), Is.EqualTo("")); + Assert.That(settings.GetString("string", "nada"), Is.EqualTo("nada")); + Assert.That(settings.Keys, Is.EqualTo(new List())); + } + + /// + /// Test Set*() methods by fetching stored results. + /// + public void Set() { + ISettings settings = new InMemorySettings(); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(0)); + settings.SetInt("an_int", 42); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + Assert.That(settings.GetInt("an_int", 21), Is.EqualTo(42)); + + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(false)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(false)); + settings.SetBool("a_bool", true); + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(true)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(true)); + Assert.That(settings.GetBool("a_bool", false), Is.EqualTo(true)); + + Assert.That(settings.HasKey("a_float"), Is.EqualTo(false)); + Assert.That(settings.GetFloat("a_float"), Is.EqualTo(0.0f)); + settings.SetFloat("a_float", 3.14f); + Assert.That(settings.HasKey("a_float"), Is.EqualTo(true)); + Assert.That(settings.GetFloat("a_float"), Is.EqualTo(3.14f)); + Assert.That(settings.GetFloat("a_float", 0.707f), Is.EqualTo(3.14f)); + + Assert.That(settings.HasKey("a_string"), Is.EqualTo(false)); + Assert.That(settings.GetString("a_string"), Is.EqualTo("")); + settings.SetString("a_string", "nada"); + Assert.That(settings.HasKey("a_string"), Is.EqualTo(true)); + Assert.That(settings.GetString("a_string"), Is.EqualTo("nada")); + Assert.That(settings.GetString("a_string", "casa"), Is.EqualTo("nada")); + + Assert.That(new HashSet(settings.Keys), + Is.EqualTo(new HashSet { + "an_int", "a_bool", "a_float", "a_string" + })); + } + } + + /// + /// Test the ProjectSettings class. + /// + [TestFixture] + public class ProjectSettingsTest { + + /// + /// Isolate ProjectSettings from Unity APIs and global state. + /// + [SetUp] + public void Setup() { + ProjectSettings.persistenceEnabled = true; + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + ProjectSettings.logger.Target = LogTarget.Console; + ProjectSettings.checkoutFile = (filename, logger) => { return true; }; + // Delete any persisted settings. + if (File.Exists(ProjectSettings.PROJECT_SETTINGS_FILE)) { + (new System.IO.FileInfo(ProjectSettings.PROJECT_SETTINGS_FILE)).IsReadOnly = false; + File.Delete(ProjectSettings.PROJECT_SETTINGS_FILE); + } + + } + + /// + /// Construct an empty settings object. + /// + [Test] + public void Construct() { + var settings = new ProjectSettings("myplugin"); + Assert.That(settings.ModuleName, Is.EqualTo("myplugin")); + Assert.That(settings.UseProjectSettings, Is.EqualTo(true)); + Assert.Throws(() => { + #pragma warning disable 0168 + var keys = settings.Keys; + #pragma warning restore 0168 + }); + } + + /// + /// Enable / disable project settings. + /// + [Test] + public void UseProjectSettings() { + var settings = new ProjectSettings("myplugin"); + Assert.That(settings.UseProjectSettings, Is.EqualTo(true)); + // The project preference hasn't been stored in system settings. + Assert.That(ProjectSettings.systemSettings.HasKey(settings.UseProjectSettingsName), + Is.EqualTo(false)); + + // Disable project settings and verify that it's stored in system settings. + settings.UseProjectSettings = false; + Assert.That(settings.UseProjectSettings, Is.EqualTo(false)); + Assert.That(ProjectSettings.systemSettings.GetBool(settings.UseProjectSettingsName), + Is.EqualTo(false)); + + // Enable project settings and verify that it's stored in system settings. + settings.UseProjectSettings = true; + Assert.That(settings.UseProjectSettings, Is.EqualTo(true)); + Assert.That(ProjectSettings.systemSettings.GetBool(settings.UseProjectSettingsName), + Is.EqualTo(true)); + } + + /// + /// Test persistence of project-level settings. + /// + [Test] + public void PersistSettings() { + var settings = new ProjectSettings("myplugin"); + + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(0)); + Assert.That(settings.GetInt("an_int", 10), Is.EqualTo(10)); + settings.SetInt("an_int", 42); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + Assert.That(settings.GetInt("an_int", 21), Is.EqualTo(42)); + + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(false)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(false)); + Assert.That(settings.GetBool("a_bool", true), Is.EqualTo(true)); + settings.SetBool("a_bool", true); + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(true)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(true)); + Assert.That(settings.GetBool("a_bool", false), Is.EqualTo(true)); + + Assert.That(settings.HasKey("a_float"), Is.EqualTo(false)); + Assert.That(settings.GetFloat("a_float"), Is.EqualTo(0.0f)); + Assert.That(settings.GetFloat("a_float", 2.72f), Is.EqualTo(2.72f)); + settings.SetFloat("a_float", 3.14f); + Assert.That(settings.HasKey("a_float"), Is.EqualTo(true)); + Assert.That(settings.GetFloat("a_float"), Is.EqualTo(3.14f)); + Assert.That(settings.GetFloat("a_float", 0.707f), Is.EqualTo(3.14f)); + + Assert.That(settings.HasKey("a_string"), Is.EqualTo(false)); + Assert.That(settings.GetString("a_string"), Is.EqualTo("")); + Assert.That(settings.GetString("a_string", "cansada"), Is.EqualTo("cansada")); + settings.SetString("a_string", "nada"); + Assert.That(settings.HasKey("a_string"), Is.EqualTo(true)); + Assert.That(settings.GetString("a_string"), Is.EqualTo("nada")); + Assert.That(settings.GetString("a_string", "casa"), Is.EqualTo("nada")); + + // Replace the backing stores for system and project settings. + // This should force the project settings to be loaded from disk. + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(true)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(true)); + Assert.That(settings.HasKey("a_float"), Is.EqualTo(true)); + Assert.That(settings.GetFloat("a_float"), Is.EqualTo(3.14f)); + Assert.That(settings.HasKey("a_string"), Is.EqualTo(true)); + Assert.That(settings.GetString("a_string"), Is.EqualTo("nada")); + + // Force reload of settings. + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + // Make sure that setting a value also loads other settings into the cache. + settings.SetFloat("a_float", 0.707f); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(true)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(true)); + Assert.That(settings.HasKey("a_float"), Is.EqualTo(true)); + Assert.That(settings.GetFloat("a_float"), Is.EqualTo(0.707f)); + Assert.That(settings.HasKey("a_string"), Is.EqualTo(true)); + Assert.That(settings.GetString("a_string"), Is.EqualTo("nada")); + } + + /// + /// Test attempting to store persisted settings to a read-only file. + /// + [Test] + public void SaveToReadonlySettings() { + // Create a readonly settings file. + File.WriteAllText(ProjectSettings.PROJECT_SETTINGS_FILE, + "\n" + + " \n" + + "\n"); + (new System.IO.FileInfo(ProjectSettings.PROJECT_SETTINGS_FILE)).IsReadOnly = true; + var settings = new ProjectSettings("myplugin"); + Assert.That(settings.HasKey("myplugin.foo", SettingsLocation.Project), + Is.EqualTo(false)); + Assert.That(settings.HasKey("myplugin.bar", SettingsLocation.Project), + Is.EqualTo(true)); + Assert.That(settings.GetBool("myplugin.bar", false, SettingsLocation.Project), + Is.EqualTo(true)); + settings.SetBool("myplugin.foo", true, SettingsLocation.Project); + Assert.That(settings.HasKey("myplugin.foo", SettingsLocation.Project), + Is.EqualTo(true)); + Assert.That(settings.GetBool("myplugin.foo", false, SettingsLocation.Project), + Is.EqualTo(true)); + } + + /// + /// Delete a persisted project-level setting. + /// + [Test] + public void DeleteOneSetting() { + var settings = new ProjectSettings("myplugin"); + + settings.SetInt("an_int", 42); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + + // Replace the backing stores for system and project settings. + // This should force the project settings to be loaded from disk. + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + + settings.DeleteKey("an_int"); + settings.DeleteKey("non_existent"); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(0)); + + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(0)); + } + + /// + /// Delete multiple settings. + /// + [Test] + public void DeleteMultipleSettings() { + var settings = new ProjectSettings("myplugin"); + + settings.SetInt("an_int", 42); + settings.SetInt("another_int", 21); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + Assert.That(settings.HasKey("another_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("another_int"), Is.EqualTo(21)); + + // Replace the backing stores for system and project settings. + // This should force the project settings to be loaded from disk. + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + Assert.That(settings.HasKey("another_int"), Is.EqualTo(true)); + Assert.That(settings.GetInt("another_int"), Is.EqualTo(21)); + + settings.DeleteKeys(new [] { "an_int", "another_int", "non_existent" }); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(0)); + Assert.That(settings.HasKey("another_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("another_int"), Is.EqualTo(0)); + + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(0)); + Assert.That(settings.HasKey("another_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("another_int"), Is.EqualTo(0)); + } + + /// + /// Ensure settings are not persisted when persistence is disabled. + /// + [Test] + public void PersistenceDisabled() { + ProjectSettings.persistenceEnabled = false; + var settings = new ProjectSettings("myplugin"); + + settings.SetInt("an_int", 42); + settings.SetBool("a_bool", true); + settings.SetFloat("a_float", 3.14f); + settings.SetString("a_string", "nada"); + + // Replace the backing store for the project settings, this should not load settings + // from disk. + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(0)); + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(false)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(false)); + Assert.That(settings.HasKey("a_float"), Is.EqualTo(false)); + Assert.That(settings.GetFloat("a_float"), Is.EqualTo(0.0f)); + Assert.That(settings.HasKey("a_string"), Is.EqualTo(false)); + Assert.That(settings.GetString("a_string"), Is.EqualTo("")); + } + + /// + /// Test storing system settings, ensuring they're not persisted as they're only in memory + /// in this test environment. + /// + [Test] + public void AccessSystemSettings() { + var settings = new ProjectSettings("myplugin"); + + Assert.That(settings.GetInt("an_int", 10, SettingsLocation.System), Is.EqualTo(10)); + settings.SetInt("an_int", 42, SettingsLocation.System); + Assert.That(settings.GetInt("an_int", 21, SettingsLocation.System), Is.EqualTo(42)); + Assert.That(settings.HasKey("an_int", SettingsLocation.System), Is.EqualTo(true)); + Assert.That(settings.HasKey("an_int", SettingsLocation.Project), Is.EqualTo(false)); + // By default this is configured to read from the project settings so this should + // be empty. + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + + Assert.That(settings.GetBool("a_bool", true, SettingsLocation.System), + Is.EqualTo(true)); + settings.SetBool("a_bool", true, SettingsLocation.System); + Assert.That(settings.GetBool("a_bool", false, SettingsLocation.System), + Is.EqualTo(true)); + Assert.That(settings.HasKey("a_bool", SettingsLocation.System), Is.EqualTo(true)); + Assert.That(settings.HasKey("a_bool", SettingsLocation.Project), Is.EqualTo(false)); + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(false)); + + Assert.That(settings.GetFloat("a_float", 2.72f, SettingsLocation.System), + Is.EqualTo(2.72f)); + settings.SetFloat("a_float", 3.14f, SettingsLocation.System); + Assert.That(settings.GetFloat("a_float", 0.707f, SettingsLocation.System), + Is.EqualTo(3.14f)); + Assert.That(settings.HasKey("a_float", SettingsLocation.System), Is.EqualTo(true)); + Assert.That(settings.HasKey("a_float", SettingsLocation.Project), Is.EqualTo(false)); + Assert.That(settings.HasKey("a_float"), Is.EqualTo(false)); + + Assert.That(settings.GetString("a_string", "cansada", SettingsLocation.System), + Is.EqualTo("cansada")); + settings.SetString("a_string", "nada", SettingsLocation.System); + Assert.That(settings.GetString("a_string", "casa", SettingsLocation.System), + Is.EqualTo("nada")); + Assert.That(settings.HasKey("a_string"), Is.EqualTo(false)); + Assert.That(settings.HasKey("a_string", SettingsLocation.System), Is.EqualTo(true)); + Assert.That(settings.HasKey("a_string", SettingsLocation.Project), Is.EqualTo(false)); + Assert.That(settings.HasKey("a_string"), Is.EqualTo(false)); + + // Replace the backing stores for system and project settings. + ProjectSettings.projectSettings = new InMemorySettings(); + ProjectSettings.systemSettings = new InMemorySettings(); + + Assert.That(settings.HasKey("an_int"), Is.EqualTo(false)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(0)); + Assert.That(settings.HasKey("a_bool"), Is.EqualTo(false)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(false)); + Assert.That(settings.HasKey("a_float"), Is.EqualTo(false)); + Assert.That(settings.GetFloat("a_float"), Is.EqualTo(0.0f)); + Assert.That(settings.HasKey("a_string"), Is.EqualTo(false)); + Assert.That(settings.GetString("a_string"), Is.EqualTo("")); + } + + /// + /// Ensure project settings override system settings when project settings are enabled. + /// + [Test] + public void OverrideSystemSettings() { + var settings = new ProjectSettings("myplugin"); + + settings.SetInt("an_int", 21, SettingsLocation.System); + settings.SetInt("an_int", 42, SettingsLocation.Project); + Assert.That(settings.GetInt("an_int", 0, SettingsLocation.System), Is.EqualTo(21)); + Assert.That(settings.GetInt("an_int", 0, SettingsLocation.Project), Is.EqualTo(42)); + Assert.That(settings.GetInt("an_int"), Is.EqualTo(42)); + + settings.SetBool("a_bool", true, SettingsLocation.System); + settings.SetBool("a_bool", false, SettingsLocation.Project); + Assert.That(settings.GetBool("a_bool", false, SettingsLocation.System), + Is.EqualTo(true)); + Assert.That(settings.GetBool("a_bool", true, SettingsLocation.Project), + Is.EqualTo(false)); + Assert.That(settings.GetBool("a_bool"), Is.EqualTo(false)); + + settings.SetFloat("a_float", 3.14f, SettingsLocation.System); + settings.SetFloat("a_float", 0.707f, SettingsLocation.Project); + Assert.That(settings.GetFloat("a_float", 2.72f, SettingsLocation.System), + Is.EqualTo(3.14f)); + Assert.That(settings.GetFloat("a_float", 2.72f, SettingsLocation.Project), + Is.EqualTo(0.707f)); + + settings.SetString("a_string", "foo", SettingsLocation.System); + settings.SetString("a_string", "bar", SettingsLocation.Project); + Assert.That(settings.GetString("a_string", "bish", SettingsLocation.System), + Is.EqualTo("foo")); + Assert.That(settings.GetString("a_string", "bosh", SettingsLocation.Project), + Is.EqualTo("bar")); + Assert.That(settings.GetString("a_string"), Is.EqualTo("bar")); + } + } +} diff --git a/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/VersionHandlerImplTests.asmdef b/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/VersionHandlerImplTests.asmdef new file mode 100644 index 00000000..7061f59d --- /dev/null +++ b/source/VersionHandlerImpl/unit_tests/Assets/VersionHandlerImplTests/VersionHandlerImplTests.asmdef @@ -0,0 +1,24 @@ +{ + "name": "Google.VersionHandlerImplTests", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Google.VersionHandler.dll", + "Google.VersionHandlerImpl.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/test_resources/nunit_upm/manifest.json b/test_resources/nunit_upm/manifest.json new file mode 100644 index 00000000..807a360d --- /dev/null +++ b/test_resources/nunit_upm/manifest.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "com.unity.test-framework": "1.1.33" + } +} diff --git a/test_resources/version_handler_update/VersionHandlerUpdater.cs b/test_resources/version_handler_update/VersionHandlerUpdater.cs new file mode 100644 index 00000000..b80b1884 --- /dev/null +++ b/test_resources/version_handler_update/VersionHandlerUpdater.cs @@ -0,0 +1,48 @@ +// +// Copyright (C) 2023 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; + +/// +/// Run Version Handler to ensure all the libraries are properly enabled. +/// +[UnityEditor.InitializeOnLoad] +public class VersionHandlerUpdater { + /// + /// Register a method to call when the Version Handler has enabled all plugins in the project. + /// + static VersionHandlerUpdater() { + // Disable stack traces for more condensed logs. + UnityEngine.Application.stackTraceLogType = UnityEngine.StackTraceLogType.None; + UnityEngine.Debug.Log("Set up callback on Version Handler completion."); + Google.VersionHandler.UpdateCompleteMethods = new [] { + ":VersionHandlerUpdater:VersionHandlerReady" + }; + UnityEngine.Debug.Log("Enable plugin using the Version Handler."); + Google.VersionHandler.UpdateNow(); + } + + /// + /// Called when the Version Handler has enabled all managed plugins in a project. + /// + public static void VersionHandlerReady() { + UnityEngine.Debug.Log("The plugin should now be enabled by the Version Handler."); + UnityEditor.EditorApplication.Exit(0); + } +} diff --git a/troubleshooting-faq.md b/troubleshooting-faq.md new file mode 100644 index 00000000..e542b810 --- /dev/null +++ b/troubleshooting-faq.md @@ -0,0 +1,143 @@ + +# EDM4U Usage Troubleshooting Guide + +--- +## Table of contents +1. [Resolver and Target Platform Build Debug Process](#introduction) +2. [General Tips](#general_tips) + 1. [Enable Verbose Logging](#verbose_loggin) + 2. [Turn off auto-resolution on Android](#android_auto_resolution) +3. [Android](#android) + 1. [Fixing "Resolution Failed" errors](#resolution_failed) + 2. [Use Force Resolve if having issues with resolution](#force_resolve) + 3. [JDK, SDK, NDK, Gradle Issues (including locating)](#jdk_sdk_ndk) + 4. [Enable Custom Main Gradle Template, a.k.a. mainTemplate.gradle](#custom_main_gradle_template) + 5. [Enable Jetifier if you can](#jetifier) +4. [iOS](#ios) + 1. [Investigate Cocoapods if iOS resolution fails](#cocoapods) + 2. [Prefer opening Xcode Workspace files in Xcode to opening Xcode Project Files](#xcode_workspace_files) + 3. [Win32 Errors when building Xcode Workspace/Project on Mac](#win32_errors) + 4. [Runtime Swift Issues](#swift) +--- + +# Resolver and Target Platform Build Debug Process + + +The following is a roughly chronological process for debugging and exploring the use of EDM4U when building your game/app for a particular platform. The first section ("General Tips'') applies regardless of your target, while the remaining two have to do with which target you are building for (Android or or iOS). + +Consider each step within a section in order: If you do not have an issue with the step, move on to the next; If you do have an issue, attempt the listed steps and proceed once the issue is cleared. + +Throughout the process, additionally address and explore both warnings and error messages displayed in the Unity Editor console and/or device logs. Oftentimes, seemingly unrelated errors can cause issues and, even when they don't, they can hide bigger issues. + +
+ +

Note: This guide assumes you have already tested and verified the expected functionality of your Unity game/app in the Editor when compiling for the target platform.

+

If you have not done so yet, perform your tests and resolve any issues you find before returning to this process.

+
+
+

+ + +# **General Tips** + +If at any point you want or need more resolver or build information, consider enabling verbose logging and reading the log after trying to build again + +### **Enable Verbose Logging** + +#### Android + +Enable **Verbose Logging** in **Assets > External Dependency Manager > Android Resolver > Settings** + +#### iOS + +Enable **Verbose Logging** in **Assets > External Dependency Manager > iOS Resolver > Settings** + +### **Turn off auto-resolution on Android** + +When the auto-resolution feature is enabled, the Android resolver can trigger when assets are changed or when AppDomain is reloaded, which happens every time you press the Play button in the editor. Dependency resolution can be slow when the Unity project is big or when the resolver needs to download and patch many Android libraries. You can improve iteration time by disabling most of the auto-resolution features and manually triggering resolution instead. + +* Manual resolution is available through **Assets > External Dependency Manager > Android Resolver > Resolve** and **Assets > External Dependency Manager > Android Resolver > Force Resolve** menu items. +* Turn off "Enable Auto-Resolution" in the settings menu to prevent resolution triggered by assets changes and AppDomain reloads. +* Turn off "Enable Resolution on Build" in the settings menu to speed up build time. + +# **Android** + +### **Fixing "Resolution Failed" errors** + +If EDM4U fails resolution, try the following sequentially, making sure to check whether resolution succeeded between each. If a section heading includes "if you can", perform the step unless you *know* you cannot or that there are issues with doing so in your project. + +### **Use Force Resolve if having issues with resolution** + +* When trying to build, if you receive errors, try to resolve dependencies by clicking **Assets > External Dependency Manager > Android Resolver > Resolve** +* If this fails, try **Assets > External Dependency Manager > Android Resolver > Force Resolve.** While this is slower, it is more dependendable as it clears old intermediate data. + +### **JDK, SDK, NDK, Gradle Issues (including locating)** + +* Reasons to do this: + * If **Force Resolve** is failing and you have not done this yet, try this. + * If you receive error logs about Unity being unable to locate the `JDK`, `Android SDK Tools` or `NDK` + * This issue is mostly observed in Unity 2019 and 2020. +* What it does: + * Toggling the external tool settings forces Unity to acknowledge them as it may not have loaded them properly. +* What to do: + * Enter **Unity > Preferences> External Tools** + * Toggle the `JDK`, `Android SDK`, `Android NDK` and `Gradle` **checkboxes** such that they have the opposite value of what they started with + * Toggle them back to their original values + * Try **Force Resolve** and/or building again + +### **Enable Custom Main Gradle Template, a.k.a. mainTemplate.gradle** + +* By default, EDM4U used [a custom Gradle script](https://github.com/googlesamples/unity-jar-resolver/blob/master/source/AndroidResolver/scripts/download_artifacts.gradle) to download Android libraries to the "Assets/Plugins/Android/" folder. This can be problematic in several cases: + * When Unity adds some common Android libraries with specific versions to the Gradle by default, play-core or game-activity. This would very likely cause duplicate class errors during build time. + * When multiple Unity plugins depend on the same Android libraries with a very different range of versions. The Gradle project generated by Unity can handle resolution far better than the custom script in EDM4U. + * Downloading large amounts of Android libraries can be slow. +* If you do this and are on Unity 2019.3+ you *must* enable [**Custom Gradle Properties Template**](https://docs.unity3d.com/Manual/class-PlayerSettingsAndroid.html#Publishing) to enable AndroidX and Jetifier, which are described in the next step. + +### **Enable Jetifier if you can** + +* Android 9 introduced a new set of support libraries (AndroidX) which use the same class name but under a different package name. If your project has dependencies (including transitive dependencies) on both AndroidX and the older Android Support Libraries, duplicated class errors in `com.google.android.support.*` and `com.google.androidx.*` will occur during build time. [Jetifier](https://developer.android.com/tools/jetifier) is a tool to resolve such cases. In general, you should *enable Jetifier if your target Android version is 9+, or API level 28+*. +* Android Resolver can configure Unity projects to enable Jetifier. This feature can be enabled by the **Use Jetifier** option in Android Resolver settings. When enabled, + * Android Resolver uses Jetifier to patch every Android library it downloaded to the "Assets/Plugins/Android" folder. + * When **Custom Main Gradle Template** is enabled, it injects scripts to enable AndroidX and Jetifier to this template, prior to Unity 2019.3. After Unity 2019.3, AndroidX and Jetifier can only be enabled in **Custom Gradle Properties Template**. +* When using Unity 2019.3 or above, it is recommended to enable **Custom Gradle Properties Template**, regardless if you are using **Custom Main Gradle Template** or not. + +# **iOS** + +If EDM4U fails resolution, try the following sequentially, making sure to check whether resolution succeeded between each. + +### **Investigate Cocoapods if iOS resolution fails** + +* First of all make sure it's [properly installed](https://guides.cocoapods.org/using/getting-started.html) + * Verify that [`pod install` and `pod update` run without errors](https://guides.cocoapods.org/using/pod-install-vs-update.html) in the folder where the Podfile is (usually the root folder of the Xcode project). +* Cocoapods text encoding issues when building from Mac + * Do this if you are building on a Mac and see the following in the cocoapods log + `WARNING: CocoaPods requires your terminal to be using UTF-8 encoding.` + * When building for iOS, Cocoapod installation may fail with an error about the language locale, or UTF-8 encoding. There are currently several different ways to work around the issue. + * From the terminal, run `pod install` directly, and open the resulting `xcworkspace` file. + * Downgrade the version of Cocoapods to 1.10.2. The issue exists only in version 1.11 and newer. + * In your `~/.bash_profile` or equivalent, add `export LANG=en_US.UTF-8` + +### **Prefer opening Xcode Workspace files in Xcode to opening Xcode Project Files** + +Try to [build iOS builds from Xcode Workspaces](https://developer.apple.com/library/archive/featuredarticles/XcodeConcepts/Concept-Workspace.html) generated by Cocoapods rather than Xcode projects: + +* Rationale: + * Unity by default only generates `.xcodeproject` files. If EDM4U is in the project, it first generates Podfiles from all iOS dependencies specified in files named "Dependencies.xml" with a prefix (ex. "AppDependencies.xml") , then runs Cocoapods, which generates an `.xcworkspace` file + * In this case, it is recommended to open the generated project by double-clicking on `.xcworkspace` instead of `.xcodeproject` since the former contains references to pods. +* If you are building in an environment you cannot open Xcode workspaces from (such as unity cloud build) then go into the **iOS resolver settings**, enter the dropdown **Cocoapods Integration** and select **Xcode project** + +### **Win32 Errors when building Xcode Workspace/Project on Mac** + +If the Unity Editor's console displays [build output that mentions win32 errors](https://issuetracker.unity3d.com/issues/webgl-builderror-constant-il2cpp-build-error-after-osx-12-dot-3-upgrade), upgrade to a more recent LTS version of Unity after 2020.3.40f1. + +* While workarounds exist, upgrading is the fastest, most convenient and most reliable way to handle it. + +### **Runtime Swift Issues** + +If you run into an issue when trying to run the game with error logs that mention Swift, try the following: + +* Turn on `Enable Swift Framework Support Workaround` in **Assets > External Dependency Manager > iOS Resolver > Settings**. + * Read the description in the settings menu. + * Make sure those changes are made to the generated Xcode project. + +If you are still experiencing issues at this point, investigate whether troubleshooting the product that utilizes EDM4U works differently or better now. Consider filing an issue here or with the product you are employing that utilizes EDM4U. \ No newline at end of file diff --git a/upm/CHANGELOG.md b/upm/CHANGELOG.md new file mode 100755 index 00000000..e1294a3a --- /dev/null +++ b/upm/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/upm/CHANGELOG.md.meta b/upm/CHANGELOG.md.meta new file mode 100644 index 00000000..88b8b785 --- /dev/null +++ b/upm/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: dd6a29a412594aadb37d9698db325eca +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-CHANGELOG.md +timeCreated: 0 diff --git a/upm/Documentation~/index.md b/upm/Documentation~/index.md new file mode 100755 index 00000000..a9aafe9f --- /dev/null +++ b/upm/Documentation~/index.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/upm/ExternalDependencyManager.meta b/upm/ExternalDependencyManager.meta new file mode 100644 index 00000000..113fad84 --- /dev/null +++ b/upm/ExternalDependencyManager.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 46f5870ddbde4a6091f50656dcd5573e +timeCreated: 1480838400 +folderAsset: true +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/upm/ExternalDependencyManager/Editor.meta b/upm/ExternalDependencyManager/Editor.meta new file mode 100644 index 00000000..7f569fa6 --- /dev/null +++ b/upm/ExternalDependencyManager/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1f23cd25474841f6ad7555705b4807e9 +timeCreated: 1480838400 +folderAsset: true +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/upm/ExternalDependencyManager/Editor/1.2.186.meta b/upm/ExternalDependencyManager/Editor/1.2.186.meta new file mode 100644 index 00000000..c9cc4f8b --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8a6a9bd2649d4370b43be75e1689748c +timeCreated: 1480838400 +folderAsset: true +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll b/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll new file mode 100755 index 00000000..30030559 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll differ diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta b/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta new file mode 100644 index 00000000..fe84d30d --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta @@ -0,0 +1,37 @@ +fileFormatVersion: 2 +guid: e2d7ea0845de4cf984265d2a444b7aa4 +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll +- gvhp_targets-editor +timeCreated: 1480838400 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + validateReferences: 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/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb b/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb new file mode 100755 index 00000000..493d3b28 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb differ diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta b/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta new file mode 100644 index 00000000..ab8ef649 --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta @@ -0,0 +1,19 @@ +fileFormatVersion: 2 +guid: baf24db2bf904e729e7796721c09e8ad +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb +- gvhp_targets-editor +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: +PluginImporter: + platformData: + - first: + Editor: Editor + second: + enabled: 1 diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll b/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll new file mode 100755 index 00000000..645d5cd3 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll differ diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta b/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta new file mode 100644 index 00000000..b3831d2a --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: fa49a85d4ba140a0ae21528ed12d174c +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll +- gvhp_targets-editor +timeCreated: 1480838400 +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/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb b/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb new file mode 100755 index 00000000..c6eaef49 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb differ diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta b/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta new file mode 100644 index 00000000..d8895653 --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta @@ -0,0 +1,19 @@ +fileFormatVersion: 2 +guid: d13c8602d5e14e43b0e92459754c4315 +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb +- gvhp_targets-editor +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: +PluginImporter: + platformData: + - first: + Editor: Editor + second: + enabled: 1 diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll b/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll new file mode 100755 index 00000000..a4a6590c Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll differ diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta b/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta new file mode 100644 index 00000000..260efc5e --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: d8bb10c56a0147bc855a6296778e025e +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll +- gvhp_targets-editor +timeCreated: 1480838400 +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/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb b/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb new file mode 100755 index 00000000..884ce212 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb differ diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta b/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta new file mode 100644 index 00000000..e08c8e68 --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta @@ -0,0 +1,19 @@ +fileFormatVersion: 2 +guid: a695eb9f64fe49569a2db0c4246c877d +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb +- gvhp_targets-editor +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: +PluginImporter: + platformData: + - first: + Editor: Editor + second: + enabled: 1 diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll b/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll new file mode 100755 index 00000000..8562ef33 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll differ diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta b/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta new file mode 100644 index 00000000..a3e0b420 --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: 5980a684c61d42fbb6b74e2eb3477016 +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll +- gvhp_targets-editor +timeCreated: 1480838400 +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/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb b/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb new file mode 100755 index 00000000..ed8cc976 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb differ diff --git a/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta b/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta new file mode 100644 index 00000000..cb5d8ef5 --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta @@ -0,0 +1,19 @@ +fileFormatVersion: 2 +guid: 9f56badf3ca84753b00163c3b632d4e5 +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb +- gvhp_targets-editor +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: +PluginImporter: + platformData: + - first: + Editor: Editor + second: + enabled: 1 diff --git a/upm/ExternalDependencyManager/Editor/Google.VersionHandler.dll b/upm/ExternalDependencyManager/Editor/Google.VersionHandler.dll new file mode 100755 index 00000000..12c150e2 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/Google.VersionHandler.dll differ diff --git a/upm/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta b/upm/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta new file mode 100644 index 00000000..b43088d1 --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: f7632a50b10045458c53a5ddf7b6d238 +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.dll +- gvhp_targets-editor +timeCreated: 1480838400 +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/upm/ExternalDependencyManager/Editor/Google.VersionHandler.pdb b/upm/ExternalDependencyManager/Editor/Google.VersionHandler.pdb new file mode 100755 index 00000000..7dd02af5 Binary files /dev/null and b/upm/ExternalDependencyManager/Editor/Google.VersionHandler.pdb differ diff --git a/upm/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta b/upm/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta new file mode 100644 index 00000000..d4a34e05 --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 57f5a82a79ab4b098f09326c8f3c73a6 +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.pdb +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/upm/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt b/upm/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt new file mode 100755 index 00000000..81c97ed6 --- /dev/null +++ b/upm/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/upm/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta b/upm/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta new file mode 100644 index 00000000..dca3f95f --- /dev/null +++ b/upm/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: c9a3138961c74d99b7046b783112fceb +labels: +- gvh +- gvh_manifest +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt +- gvhp_manifestname-0External Dependency Manager +- gvhp_manifestname-play-services-resolver +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/upm/LICENSE.md b/upm/LICENSE.md new file mode 100755 index 00000000..6258cc47 --- /dev/null +++ b/upm/LICENSE.md @@ -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/upm/LICENSE.md.meta b/upm/LICENSE.md.meta new file mode 100644 index 00000000..e3344950 --- /dev/null +++ b/upm/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f61a1c8e753b496bb696e77d7eedfb95 +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-LICENSE.md +timeCreated: 0 diff --git a/upm/README.md b/upm/README.md new file mode 100755 index 00000000..a9aafe9f --- /dev/null +++ b/upm/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/upm/README.md.meta b/upm/README.md.meta new file mode 100644 index 00000000..a768cca5 --- /dev/null +++ b/upm/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cbbebcaa6ecb4b9582dce440a386de75 +labels: +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-README.md +timeCreated: 0 diff --git a/upm/package.json b/upm/package.json new file mode 100755 index 00000000..6d0b27ea --- /dev/null +++ b/upm/package.json @@ -0,0 +1,25 @@ +{ + "name": "com.google.external-dependency-manager", + "version": "1.2.186", + "displayName": "External Dependency Manager for Unity", + "keywords": [ + "Google", + "Android", + "Gradle", + "Cocoapods", + "Dependency", + "Unity Package Manager", + "Unity", + "vh-name:play-services-resolver", + "vh-name:unity-jar-resolver", + "vh-name:external-dependency-manager", + "vh-name:External Dependency Manager for Unity" + ], + "author": { + "name": "Google LLC", + "url": "https://github.com/googlesamples/unity-jar-resolver" + }, + "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.", + "unity": "2019.1", + "dependencies": {} +} \ No newline at end of file diff --git a/upm/package.json.meta b/upm/package.json.meta new file mode 100644 index 00000000..f5581030 --- /dev/null +++ b/upm/package.json.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9bed450d5c03481d87e61b61431cf00a +labels: +- gupmr_manifest +- gvh +- gvh_version-1.2.186 +- gvhp_exportpath-package.json +timeCreated: 0