googlesamples / unity-jar-resolver

Unity plugin which resolves Android & iOS dependencies and performs version management
Other
1.21k stars 336 forks source link
android-dependency android-resolver cocoapods ios-resolver jar-resolver unity unity-android unity-ios unity-package-manager unity-plugin

External Dependency Manager for Unity

openupm openupm

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:

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 in your package manifest (package.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

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 support was added to the Package Manager.

Getting Started

Check out troubleshooting if you need help.

Install via OpenUPM

EDM4U is available on OpenUPM:

openupm add com.google.external-dependency-manager

Install via Google APIs for Unity

EDM4U is available both in UPM and legacy .unitypackage formats on Google APIs for Unity.

You may install the UPM version (.tgz) as a local UPM package.

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) 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 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:

<dependencies>
  <androidPackages>
    <androidPackage spec="com.google.android.gms:play-services-games:9.8.0">
      <androidSdkPackageIds>
        <androidSdkPackageId>extra-google-m2repository</androidSdkPackageId>
      </androidSdkPackageIds>
    </androidPackage>
  </androidPackages>
</dependencies>

The version specification (last component) supports:

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:

<dependencies>
  <androidPackages>
    <androidPackage spec="com.google.api-client:google-api-client-android:1.22.0" />
  </androidPackages>
</dependencies>

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:

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:

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:

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. 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:

<dependencies>
  <iosPods>
    <iosPod name="Google-Mobile-Ads-SDK" version="~> 7.0" bitcodeEnabled="true"
            minTargetSdk="6.0" addToAllTargets="false" />
  </iosPods>
</dependencies>

Integration Strategies

The CocoaPods are either:

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:

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 (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:

<registries>
  <registry name="Cool Stuff"
            url="https://unityregistry.coolstuff.com"
            termsOfService="https://coolstuff.com/unityregistry/terms"
            privacyPolicy="https://coolstuff.com/unityregistry/privacy">
    <scopes>
      <scope>com.coolstuff</scope>
    </scopes>
  </registry>
</registries>

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.

Managing Registries

It's possible to add and remove registries that are specified via PMR XML configuration files via the following menu options:

Migration

PMR can migrate Version Handler packages installed in the Assets folder to PM packages. This requires the plugins to implement the following:

When using the Assets > External Dependency Manager > Package Manager Resolver > Migrate Packages menu option, PMR then will:

Configuration

PMR can be configured via the Assets > External Dependency Manager > Package Manager Resolver > Settings menu option:

Version Handler

The Version Handler component of this plugin manages:

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 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.

Redistributing a Managed Plugin

The Version Handler employs a couple of methods for managing version selection, upgrade and removal of plugins.

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.

  2. 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).

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. Redistribute EDM4U Unity plugin with your plugin. See the Plugin Redistribution section for details.

If you follow these steps:

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:

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:

iOS Dependency Management

The iOS Resolver component of this plugin integrates with CocoaPods 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 (PM) makes use of NPM 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 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:

The version of EDM4U included in the developer's project depends upon the order the developer imports SomePlugin or SomeOtherPlugin.

This results in:

The Version Handler solves the problem of managing transitive dependencies by:

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 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, ensuring that you add the -gvh_disable option.
  2. Export your plugin by running Unity from the command line, 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:

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):

./gradlew build

or Windows:

./gradlew.bat build

If Java 11 is not your default Java command, add -Dorg.gradle.java.home=<PATH_TO_JAVA_HOME> to the command above.

Testing

You can run the tests by running the following from your shell (Linux / OSX):

./gradlew test

or Windows:

./gradlew.bat test

The following properties can be set to narrow down the tests to run or change the test run behavior.

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.

./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: