Roslyn Analyzer for Unity package

Im currently working on a package that provides a framework, and want to write some custom DiagnosticAnalyzers for the correct usage of it.
Ive written a few for non-Unity projects before, so my question is not about the analyzers themselves, but rather about how to best integrate them within a Unity project.

As far as official documentation goes, this page is all I could find.
On there, they suggest you build the analyzer .dll outside of Unity, and then paste it into your project.
That does work, but since im building an analyzer for a framework ive made I’d ideally want to be able to directly reference the frameworks classes instead of just relying on strings, to make it more resilient to namespace changes etc.

As for directly writing the analyzer in the unity project, from what I could find Unity does not allow for building .dlls.

This is the ideal project structure Im trying to achieve (but Ill take anything that allows my analyzer to directly reference the framework classes):

Assets/
|- MyFramework/
| |- MyFramework
| | |- MyFramework.asmdef
| | |- Framework classes…
| |- MyFrameworkAnalyzer/
| | |- MyFrameworkAnalyzer.asmdef
| | |- MyAnalyzer.cs
| | |- MyFrameworkAnalyzer.dll
|- Scripts
| |- SomeOtherAssembly.asmdef
| |- SomeClass.cs

where MyFramewrokAnalyzer.asmdef of course references MyFramework.asmdef.
SomeOtherAssembly would then reference both of the framework assemblies, and SomeClass could use the framework classes with the custom diagnostics.

Did you find a solution? I am facing the same issue currently.

Currently I am experimenting with a setup that looks something like this:

Assets/
Packages/
  Foo // a local package
    Scripts
      Foo.asmdef
      ...
    Tests
      Foo.Tests.asmdef
      ...
    Analyzers
      Foo.Analyzers.asmdef
      ...

This would result in the analyzer DLL being compiled by Unity’s compilation pipeline to Libraries/ScriptAssemblies/ Foo.Analyzers.dll.

I was planning to then use some callback and timestamp comparison to automatically move the DLL back into the Packages/Foo/Scripts folder, because according to the documentation, this would result in the analyzer being applied to users of the package:

If an analyzer is in a folder that contains an assembly definition, or a subfolder of such a folder, the analyzer only applies to the assembly generated from that assembly definition, and to any other assembly that references it.

I think this more or less would work.

However, currently I am getting an error that I could not yet solve:

Foo.Analyzers references netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51. A roslyn analyzer should reference netstandard version 2.0

Might be worth exploring a manual compilation of the analyzer using UnityEditor.Compilation.AssemblyBuilder.

You’re not really meant to compile analyzers from within Unity. Also, AssemblyBuilder is obsolete in Unity 6. Instead, you should create an appropriate .NET Standard 2.0 project and compile it with a recent .NET SDK, and move it into place as desired. Here is an example of such a project:

As for referencing existing code in an analyzer, that code would itself need to be shipped as a compiled assembly in order for the built analyzer to be able to reference it.

I understand that this is what Unity is recommending currently, but this is just a hacky workaround at best.

How do you solve a setup where you have multiple projects and packages in a mono repo and want to include an analyzer in one of the packages, so that users of the package are alerted to incorrect use of that package? Manually building the analyzer and embedding the DLL into the project is ugly for several reasons.

You generally don’t want to commit binaries that are derived from source within the repo. And editing the analyzer code would require a separate workflow from editing package or project code.

In an ideal solution, the analyzer would be committed as code, the package (and its analyzer) would be referenced in a project, the analyzer would be automatically built whenever its source changes and then updated in the project to be used for analysis.

You’re describing exactly the pipeline I would like as well.

Have you had any luck on this front?

Maybe i dont understand this correctly, as i dont know much about broad .NET dev. But what you pretend seems not possible.
An Analyzer works by being embedded into the compilation pipeline. You cannot compile the analyzer ALONG the compilation of the rest of assemblies. Even though it could sound ideal, the Analyzer should be pre-compiled in all cases. If you want to reference your original framework assembly from within the Analyzer, then that should also be pre-compiled on its own, and then referenced from the Analyzer csproj.

I believe you have no workaround for including the Analyzer into a Unity Project as source-code. It should always be externally compiled.

However some things could be done for it to be less cumberstone. If you check the folder structure of the example provided by @Spy-Master, you see that the Source for the Analyzer is within a folder that is named “Source~”. The final “~” signals Unity that it should ignore that folder, so you can effectively have your Analyzer .NET project inside your package folder, without Unity generating .meta files or touching it in any way. Then, on the Analyzer .csproj, you can setup post-build callbacks that will directly copy the “.dll” in you desired folder each time you compile it through your IDE (and now that you project lives within the package, you could just use relative paths like “../../YourAnalyzer.dll”)

<!-- This is my normal setup for copying the Dll, but im seeing now that
     in the example provided by @Spy-Master they do this more elegantly.
     I still leave it for reference
-->
<Project Sdk="Microsoft.NET.Sdk">
...
    <PropertyGroup>
        <RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
         ...
    </PropertyGroup>
    <Target Name="PostBuild" AfterTargets="PostBuildEvent">
        <Exec Command="start XCOPY /Y /R &quot;$(TargetPath)&quot; &quot;$(ProjectDir)..\..\..\$(TargetFileName)&quot;"/>
    </Target>
</Project>

This way, whenever you want to edit your Analyzer, you can just compile from IDE and Unity will auto-pick it up without you having to move around the .dll. Also your source-code will be within the package itself (alas in another project, but at least you have them together for dev purposes).

As for referencing your framework within the Analyzer. I understand that this also can be helpfull, but maybe you should avoid it, and just deal with strings. If you still want to, my recommendation would be that you extract the bare-minimun core Types into an independent .dll, and reference that. This way you can still develop your package source-code in Unity. but referencing a “Core features” .dll that will be externally compiled. The Analyzer would also reference that “Core features” (whatever its name) .dll.

This would work only if those “Core” Types you are extracting are not likely to change often, if not it would be just a waste of time, but i myself think you don’t have many more options, except for it being a fully externally compiled .dll (i could perfectly be completely wrong)

PD: i just realized this was necroed soz

Thank you for this description - I will read it over in some more detail.

Is it bad to reply to an older post for some reason?