Task for SDK-style .NET projects that post-processes installed dependencies so they work inside a Unity context.
This is a replacement and different approach to tools such as NuGetForUnity. Instead of partially reimplementing what dotnet restore and standard .NET tooling do, this approach builds upon them by post-processing installed NuGet packages to make them suitable to Unity.
Next to eliminating duplicate efforts, this also yields free support for private registries, lock files, and other advanced .NET CLI features that work out of the box for standard .NET projects as well.
Create NuGetDependencies/NuGetDependencies.csproj in your Unity project with the following contents:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<PostProcessDotNetPackagesForUnityAssemblyFile>$(PkgMrWatts_MSBuild_UnityPostProcessor)/lib/netstandard2.1/MSBuildUnityPostProcessor.dll</PostProcessDotNetPackagesForUnityAssemblyFile>
</PropertyGroup>
<ItemGroup>
<!-- Production dependencies. -->
<!-- <PackageReference Include="System.Text.Json" Version="7.0.2" /> -->
<!-- ... -->
<!-- Development dependencies. -->
<PackageReference Include="MrWatts.MSBuild.UnityPostProcessor" Version="x.y.z" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<!-- NOTE: Necessary because a `dotnet restore` will not prune packages you remove automatically. -->
<Target Name="CleanUpUnityPackageFolder" BeforeTargets="Restore">
<Message Text="Cleaning up installed packages to start from a clean slate..." Importance="high" />
<!-- We can't use NuGetPackageRoot here due to it not being available yet before restore, so read the value explicitly. -->
<XmlPeek XmlInputPath="nuget.config" Query="configuration/config/add[@key='globalPackagesFolder']/@value">
<Output TaskParameter="Result" ItemName="value" />
</XmlPeek>
<RemoveDir Directories="@(value)" />
</Target>
<UsingTask TaskName="UnityPostProcessor.PostProcessDotNetPackagesForUnity" AssemblyFile="$(PostProcessDotNetPackagesForUnityAssemblyFile)" />
<Target Name="PostProcessDotNetPackagesForUnity">
<Message Text="Running post-processing script for Unity..." Importance="high" />
<PostProcessDotNetPackagesForUnity ProjectRoot="$(ProjectDir)" PackageRoot="$(NuGetPackageRoot)" UnityInstallationBasePath="$(UNITY_INSTALLATION_BASE_PATH)" />
</Target>
</Project>Next, create NuGetDependencies/nuget.config with these contents:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="globalPackagesFolder" value="../Assets/Packages/NuGet" />
<add key="dependencyversion" value="Highest" />
</config>
</configuration>This will ensure that installed dependencies end up in your Unity project and that Unity can scan them to pick them up.
If you stumbled upon this public repository, know that that this package is currently only published to our private registry, so you will need a
nuget.configto tell .NET CLI to search there and get access from us, or build this yourself and updateUsingTaskto reference the built DLL.
After that, set UNITY_INSTALLATION_BASE_PATH as environment variable (e.g. the same way you set MRWATTS_PRIVATE_PACKAGE_REGISTRY_USERNAME for nuget.config), and run:
dotnet msbuild -target:PostProcessDotNetPackagesForUnity -restore NuGetDependenciesOn your project file, this task will be executed to post-process NuGet dependencies specifically for Unity. Output will be displayed as this happens.
After post-processing finishes, you can start or focus the Unity window of your project and let Unity import the dependencies.
Note that
AfterTargets="Restore"to automatically run after restore is not used because it will not work due to the way NuGet operates internally.
The task will mark Roslyn analyzers such as Roslynator and source generators with the RoslynAnalyzer tag for Unity automatically. Note that for at least Unity 2022 and up this means that these will automatically apply to every other assembly definition due to the way scoping in Unity works.
In older Unity versions you could turn off 'Enable Roslyn Analyzers' in the player settings but this option no longer exists since Unity 2022 and up, so if you don't want this, you can enable GenerateAssemblyDefinitionsPerPackageVersion:
<PostProcessDotNetPackagesForUnity ... GenerateAssemblyDefinitionsPerPackageVersion="true" />What this will do is generate a Unity assembly definition for each NuGet package (and for each version if multiple versions turn out to be installed) prefixed with nuget.analyzers. (e.g. nuget.analyzers.zeroql). You can then reference this assembly definition in your own to cherry-pick analyzers and source generators to apply to them as well. This allows you to apply e.g. the ZeroQL source generator to your Unity code (Scripts.asmdef or what you named it) without having other analyzers such as Roslynator also apply.
If you just want Roslyn analyzers in your IDE but not in the Unity Editor because your rules and analyzers block play mode, you can use the following trick (only tested with C# projects generated by com.unity.ide.visualstudio):
- Split off your Roslyn analyzer NuGet dependencies from
NuGetDependencies.csprojinto a.targetsfile.- Import that
.targetsfile back intoNuGetDependencies.csproj.
- Import that
- Create a
.csproj.usernext to the generated.csprojthat matches your custom asmdef. - Also import the
.targetsfile here.
This ensures only your Roslyn analyzer packages are pulled in by the IDE, which processes the .csproj files separately.
Inside the
.targetsfile you can also use e.g.<CodeAnalysisRuleSet>to reference a custom XML ruleset.
Ensure all variables in .env.dist are loaded in your environment. There are a couple of ways to do that:
- Export the variables in
.env.distmanually:- Using
$env:FOO = 'Bar'(PowerShell only) - Using
SET FOO=Bar(Windows Command Prompt only) - Using
export FOO=BAR(Bash and compatible shells only).- With Bash, you can also put these in your
.bashrcto not have to do this every time, if desired.
- With Bash, you can also put these in your
- Prepend the variables in
.env.distto the command usingFOO=BAR BAZ=CUX dotnet ...(Bash and compatible shells only).
- Using
- Copy
.env.distto.env, fill in the variables to your liking, and use something like direnv to automatically load them into your environment. (That way you can use the same configuration for native and container builds.)
After the variables are in your environment:
- Run
dotnet build.
To test inside a project, run e.g. dotnet build -c Release and replace the path of PostProcessDotNetPackagesForUnityAssemblyFile from Usage above with the full path to the DLL in bin/Release/netstandard2.1/MSBuildUnityPostProcessor.dll.