Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for PluginAPI #564 #597

Merged
merged 39 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
91dcd51
Added support for PluginAPI #564
igor84 Oct 18, 2023
ad1f820
Added igor84 as user allowed to edit github actions
igor84 Oct 24, 2023
1494039
Fixed condition
igor84 Oct 24, 2023
87b7ef8
Added username to the log of changed action files
igor84 Oct 24, 2023
fa97c2c
Added ability for plugins to register nuspec customizators and create…
igor84 Oct 25, 2023
e8f60fa
Fixed compile error in CLI version
igor84 Oct 25, 2023
b9cb680
move NuGetForUnity.PluginAPI to src/NuGetForUnity.PluginAPI + add out…
JoC0de Oct 28, 2023
2218188
format code + update autoformatter to include NugetForUnity.PluginAPI
JoC0de Oct 29, 2023
eb7c787
fix github action
JoC0de Oct 29, 2023
55af5db
fix chekout action
JoC0de Oct 29, 2023
d8ecdba
revert chekout chekout action
JoC0de Oct 29, 2023
4463723
fix casing in .cspron / .sln
JoC0de Oct 29, 2023
5bd6099
fix casing in .csproj
JoC0de Oct 29, 2023
990c766
fix DocumentationFile in NuGetForUnity.PluginAPI.csproj
JoC0de Oct 29, 2023
b9a1380
use GenerateDocumentationFile in NuGetForUnity.PluginAPI.csproj
JoC0de Oct 29, 2023
0ae9af7
fix action download artifacts names
JoC0de Oct 29, 2023
8aba919
fix directory seperator
JoC0de Oct 29, 2023
e4be862
always use 'Any CPU' for NuGetForUnity.PluginAPI + update unity docke…
JoC0de Oct 29, 2023
a47b715
try to fix test
JoC0de Oct 29, 2023
2a95125
make CurrentBuildTargetDotnetVersionCompatibilityLevel lazy to fix un…
JoC0de Oct 29, 2023
6d76bd0
fix test for unity 2018 + update action to check NuGetForUnity.Plugin…
JoC0de Oct 29, 2023
510e225
try fix action
JoC0de Oct 29, 2023
7666b30
try to produce deterministic .dll
JoC0de Oct 29, 2023
6f38c1b
try to produce deterministic .dll
JoC0de Oct 29, 2023
c39c760
debug action
JoC0de Oct 29, 2023
82a1b3b
change NuGetForUnity.PluginAPI update check
JoC0de Oct 29, 2023
3cc9928
fix Packager case-sensitive names
JoC0de Oct 29, 2023
06e4f77
try to fix action by revert .unitypackage build step version
JoC0de Oct 29, 2023
94ae731
fix nullability anotation in PluginAPI
JoC0de Oct 29, 2023
9ba1ccb
Changed defaultIcon for better viewability in the Editor Window (dark…
JoC0de Oct 29, 2023
6e29a34
revert Test on Linux game-ci version
JoC0de Oct 29, 2023
aaeb437
update game-ci to v2.2.0
JoC0de Oct 29, 2023
97393aa
try version 2.1.2
JoC0de Oct 29, 2023
4685368
fix GitHub action + fix some speelings in documentation
JoC0de Oct 29, 2023
9aeda69
fix *.asmdef
JoC0de Oct 29, 2023
16f6847
fix *.dll.meta
JoC0de Oct 29, 2023
5dce5a0
small changes + spelling changes
JoC0de Oct 29, 2023
646a752
update game-ci to v3.1.0
JoC0de Oct 29, 2023
5fc41a0
back to working version of game-ci + update to 4.0.2
JoC0de Oct 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/actions/checkout/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ runs:
if: |
github.event_name == 'pull_request_target' &&
steps.changedWorkflowFiles.outputs.any_changed == 'true' &&
github.event.pull_request.user.login != 'JoC0de'
github.event.pull_request.user.login != 'JoC0de' &&
github.event.pull_request.user.login != 'igor84'
shell: bash
run: |
echo "One or more files in the .github folder has changed."
echo "One or more files in the .github folder were changed by ${{ github.event.pull_request.user.login }}."
echo "List all the files that have changed: ${{ steps.changedWorkflowFiles.outputs.all_changed_files }}"
exit 1
2 changes: 1 addition & 1 deletion .github/actions/create-dll/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ runs:
source ./dotnet-install.sh &&
rm ./dotnet-install.sh &&
dotnet build ./src/NuGetForUnity.CreateDll/NuGetForUnity.CreateDll.csproj --nologo -p:AppxBundle=Always -p:Platform='Any CPU' --configuration Release
-p:ReferencePath=$UNITY_PATH/Editor/Data/Managed -p:Version=${{ inputs.version }}
-p:ReferencePath=$UNITY_PATH/Editor/Data/Managed -p:Version=${{ inputs.version }} -p:SolutionDir=./
7 changes: 5 additions & 2 deletions .github/workflows/main.yml
JoC0de marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,14 @@ jobs:
with:
version: ${{ needs.determineVersionNumber.outputs.version }}

- name: Upload NuGetForUnity.dll
- name: Upload NuGetForUnity dlls
uses: actions/upload-artifact@v3
with:
name: NuGetForUnity.dll
path: ./src/NuGetForUnity.CreateDll/bin/Release/NugetForUnity.dll
path: |
./src/NuGetForUnity.CreateDll/bin/Release/NugetForUnity.dll
./src/NuGetForUnity.CreateDll/bin/Release/NugetForUnity.PluginAPI.dll
./src/NuGetForUnity.CreateDll/bin/Release/NugetForUnity.PluginAPI.xml
if-no-files-found: error

packageOnLinux:
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ For more information see [.Net Tool Documentation](https://learn.microsoft.com/e

Restore nuget packages of a single Unity Project: `dotnet nugetforunity restore <PROJECT_PATH>`. If installed as a global tool it can be called without the `dotnet` prefix: `nugetforunity restore <PROJECT_PATH>`.

# Plugin support

NugetForUnity has plugin support. If you open the NugetForUnity section in Unity preferences it will list the plugins you have installed in your project and you can enable them from there.

Plugins are any dlls which contain NugetForUnityPlugin in their name and have a class inside them that implements `INugetPlugin` interface. They can be placed anywhere inside the project and even installed as a nuget package.

If you are interested in implementing a plugin read the [plugin development documentation](plugin-dev-readme.md).

# Common issues when installing NuGet packages

In the .Net ecosystem Unity is relatively special as it doesn't use the standard .Net runtime from Microsoft instead, it uses a fork of the [Mono](https://docs.unity3d.com/Manual/Mono.html) runtime. For some platforms Unity even uses [IL2CPP](https://docs.unity3d.com/Manual/IL2CPP.html) that compiles all C# code to C++ code. So Unity also uses a different build-system. This can lead to some issues with NuGet packages that heavily depend on the standard .Net build-system. NuGetForUnity tries to handle most of the changes needed to allow using NuGet packages inside Unity but it is not able to resolve all issues. This section contains some common issues and potential solutions.
Expand Down
97 changes: 97 additions & 0 deletions plugin-dev-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Plugin Development

## Introduction

In order to develop a NugetForUnity Plugin you need to start with these steps:

1. Decide on your plugin name. It could just be your company's name for example.
2. You plugin project name should be <PluginName>.NugetForUnityPlugin since the plugin loader will look for dlls which contain NugetForUnityPlugin in its name.
3. If you are targeting Unity older than 2021.3 create a .netstandard2.0 C# library project.
4. If you are targeting Unity 2021.3 or newer create a .netstandard2.1 C# library project.
5. NugetForUnity contains NugetForUnity.PluginAPI.dll that you need to add as a reference in your project. You can copy it to your project or reference using a relative path from some other place.
6. Depending on the needs of your plugin you might also need to add references to UnityEngine.dll and UnityEditor.dll from your Unity installation.
7. Write a class that implements `INugetPlugin` interface. In the `Register` method you will get a `INugetPluginRegistry` that has methods you can use to register your classes that implement custom handling of certain functionalities like installing and uninstalling the packages.

Note that `INugetPluginRegistry` provides you a few things you can use in your plugin:

- `IsRunningInUnity` property will be true if the plugin is being run from Unity and false if it is run from command line.
- `PluginService` property that you can pass to your custom handlers if they need to use any of these:
- `ProjectAssetsDir` property that gives you the absolute path to the project's Assets directory.
- `LogError`, `LogErrorFormat` and `LogVerbose` methods that you can use for logging. You should not use `UnityEngine.Debug.Log` methods since they will not work if plugin is used from command line.

## Extension points

NugetForUnity implements a certain extension points that your plugin can register to in order to provide custom processing. It can add new extension points in the future without breaking backward compatibility with existing ones.

### Custom action buttons in Nuget window

If you want to provide a custom action button next to some packages in NugetForUnity Manage Packages window you can write a class that implements `IPackageButtonsHandler` interface. It will give you a method bellow that you need to implement:

```cs
void DrawButtons(INugetPackage package, INugetPackage? installedPackage, bool existsInUnity);
```

Inside this method you will get info about an online `package` that is being rendered. The `installedPackage` is that same info about the version of that package that is installed if it is installed. It will be null if the package is not currently installed in the project. The third parameter tells you if this package is actually included in Unity itself which means installing it should be disabled.

Inside the method you can use `GUILayout.Button` Unity method to render additional buttons that will be rendered to the left of current Install/Uninstall/Update and similar buttons.

Since code here will use UnityEditor functionality which is not available when NugetForUnity is run from command line you should only register this class in plugin registry if it ruinning from Unity. You can do so in your `INugetPlugin.Register` implementation like this:

```cs
if (registry.IsRunningInUnity)
{
var myButtonHandler = new MyButtonHandler(registry.PluginService);
registry.RegisterPackageButtonDrawer(linkUnlinkSourceButton);
}
```

### Custom package installation

If you want to customize how packages are installed or just how certain files from packages are extracted and handled you can write a class that implements `IPackageInstallFileHandler` interface. It declares this method:

```cs
bool HandleFileExtraction(INugetPackage package, ZipArchiveEntry entry, string extractDirectory);`
```

When you implement that method you can choose if you want to handle each specific entry from nupkg file and how. If you handle the entry your self and you do not want the default installation of that file to occur you should return true from this method indicating that you have done all the processing you need for this entry. If you still want default installation logic to handle this entry just return false from this method.

### Custom handling of uninstallation

If you implement custom handling of installation you will often also need to implement custom handling of uninstallation. For that you need to write a class that implements `IPackageUninstallHandler` interface. It declares two methods:

```cs
void HandleUninstall(INugetPackage package, PackageUninstallReason uninstallReason);
void HandleUninstalledAll();
```

The first method is called for each package that is being uninstalled. The `uninstallReason` can be:

- `IndividualUninstall` when individual package uninstallation has be requested by the user.
- `UninstallAll` when user requested all packages from the project to be uninstalled.
- `IndividualUpdate` when user requested a package to be updated so we are uninstalling the current version.
- `UpdateAll` when user requested all packages to be updated so we are uninstalling old versions.

The second method, `HandleUninstalledAll()` will only be called if user requested all packages to be unininstalled after all the default uninstall processing has been done. If you don't need to do anything special in this case you can leave this method empty.

## New extension points

In case you have an idea for a plugin that requires some new extension points please open an issue requesting it with a description of how are you planing to use it. Pull requests implementing new extension points are also welcome as long as a clear description for their need is given.

# Plugin support implementation details

This section explains how plugin support is implemented in NugetForUnity which should also explain how new extension points can be added.

Under src/NugetForUnity.CreateDll there is a NuGetForUnity.CreateDll.sln solution. That solution contains two projects: NugetForUnity.CreateDll itself and NugetForUnity.PluginAPI project. PluginAPI project defines all the interfaces that should be visible to plugin implementations. NugetForUnity project references this one since it also implements and extends some of these interfaces.

PluginAPI project is setup so that it copies the built NugetForUnity.PluginAPI.dll to src/NuGetForUnity/Editor/ folder where the rest of actual source files of NugetForUnity reside. This is needed because src/NugetForUnity folder contains package.json file identifying that folder as a Unity package that can be locally referenced from the file system.

CreateDll project has two classes under PluginSupport folder:

- `NugetPluginSupport` which implements the `INugetPluginService`
- `PluginRegistry` that implements `INugetPluginRegistry` and also has `InitPlugins` method that is called after Nuget.config is loaded and a list of enabled plugins is read from it.

Note that `AssemblyLoader` class it uses to load the plugins has a different implementation in NugetForUnity.Cli project which is for running from command line. It also has a different implementation of `SessionStorage` class that will return "false" for `IsRunningInUnity` key.

`NugetPreferences` constructor has code that looks for all plugins installed in the project by checking all assemblies whose name contains "NugetForUnityPlugin" in its name. It will list these plugins in the preferences window so each can be enables or disabled.

In order to find where are extension points executed in the code you can just search for `PluginRegistry.Instance` through the entire solution. For example you will find that `PluginRegistry.Instance.HandleFileExtraction(...)` is called in `NugetPackageInstaller.Install()` method within the loop that handles the entries for nupgk file that is being installed.
25 changes: 25 additions & 0 deletions src/NuGetForUnity.Cli/Fakes/AssemblyLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#nullable enable
using System.Reflection;
using NuGetForUnity.Cli;
using NugetForUnity.Configuration;

namespace NugetForUnity.Helper
{
/// <summary>
/// Assembly load implementation for CLI version. There is another implementation for Unity Editor version.
/// </summary>
internal static class AssemblyLoader
{
/// <summary>
/// Loads the assembly for the given pluginId
/// </summary>
/// <param name="pluginId">Plugin Id to load.</param>
/// <returns>Assembly of the loaded plugin.</returns>
internal static Assembly Load(NugetForUnityPluginId pluginId)
{
var loadContext = new NugetAssemblyLoadContext(pluginId.Path);
var assembly = loadContext.LoadFromAssemblyPath(pluginId.Path);
return assembly;
}
}
}
2 changes: 1 addition & 1 deletion src/NuGetForUnity.Cli/Fakes/SessionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ internal static class SessionState
{
internal static string GetString(string key, string defaultValue)
{
return null;
return key == "IsRunningInUnity" ? "false" : null;
}

internal static void SetString(string key, object value)
Expand Down
6 changes: 6 additions & 0 deletions src/NuGetForUnity.Cli/NuGetForUnity.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<ItemGroup>
<Compile Remove="..\NuGetForUnity\Editor\Ui\*.cs" />
<Compile Remove="..\NuGetForUnity\Editor\NugetAssetPostprocessor.cs" />
<Compile Remove="..\NuGetForUnity\Editor\Helper\AssemblyLoader.cs"/>
<Compile Remove="..\NuGetForUnity\Editor\Helper\NugetPackageTextureHelper.cs" />
<Compile Remove="..\NuGetForUnity\Editor\OnLoadNugetPackageRestorer.cs" />
<Compile Remove="..\NuGetForUnity\Editor\UnityPreImportedLibraryResolver.cs" />
Expand All @@ -36,4 +37,9 @@
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="7.0.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="NugetForUnity.PluginAPI">
<HintPath>..\NuGetForUnity\Editor\NugetForUnity.PluginAPI.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
29 changes: 29 additions & 0 deletions src/NuGetForUnity.Cli/NugetAssemblyLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Reflection;
using System.Runtime.Loader;

namespace NuGetForUnity.Cli
{
/// <summary>
/// Class for loading an assembly.
/// </summary>
internal sealed class NugetAssemblyLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver resolver;

/// <summary>
/// Initializes a new instance of the <see cref="NugetAssemblyLoadContext"/> class.
/// </summary>
/// <param name="pluginPath">Path to the plugin that will be loaded.</param>
internal NugetAssemblyLoadContext(string pluginPath)
{
resolver = new AssemblyDependencyResolver(pluginPath);
}

/// <inheritdoc/>
protected override Assembly Load(AssemblyName assemblyName)
{
var assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
}
}
}
3 changes: 3 additions & 0 deletions src/NuGetForUnity.CreateDll/NuGetForUnity.CreateDll.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="NugetForUnity.PluginAPI\NugetForUnity.PluginAPI.csproj" />
</ItemGroup>
</Project>
6 changes: 6 additions & 0 deletions src/NuGetForUnity.CreateDll/NuGetForUnity.CreateDll.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGetForUnity.CreateDll", "NuGetForUnity.CreateDll.csproj", "{3FB0B522-A1E7-4FC6-8083-885621A5B4B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NugetForUnity.PluginAPI", "NugetForUnity.PluginAPI\NugetForUnity.PluginAPI.csproj", "{67591B40-D208-4BCF-9D82-7B6138F6355F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -15,6 +17,10 @@ Global
{3FB0B522-A1E7-4FC6-8083-885621A5B4B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FB0B522-A1E7-4FC6-8083-885621A5B4B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FB0B522-A1E7-4FC6-8083-885621A5B4B0}.Release|Any CPU.Build.0 = Release|Any CPU
{67591B40-D208-4BCF-9D82-7B6138F6355F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67591B40-D208-4BCF-9D82-7B6138F6355F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67591B40-D208-4BCF-9D82-7B6138F6355F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67591B40-D208-4BCF-9D82-7B6138F6355F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nupkg/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Binary file not shown.
Loading
Loading