A Roslyn based C# source generation framework
Перейти к файлу
Carl de Billy 1d7130afff
Merge pull request #5 from carldebilly/dev/cdb/generation-dependencies-doc
Improved doc (readme)
2018-01-25 16:15:14 -05:00
build Add appveyor configuration 2018-01-12 11:14:54 -05:00
doc Initial commit 2018-01-12 09:27:48 -05:00
samples/Basic Initial commit 2018-01-12 09:27:48 -05:00
src Added new SourceGeneratorDependencyAttribute 2018-01-25 14:46:26 -05:00
.appveyor.yml Add appveyor configuration 2018-01-12 11:14:54 -05:00
.gitattributes Initial commit 2018-01-12 09:27:48 -05:00
.gitignore Added new SourceGeneratorDependencyAttribute 2018-01-25 14:46:26 -05:00
AppVeyor.yml Publish nuget package, bump version to 1.20 2018-01-25 15:34:37 -05:00
License.md Initial commit 2018-01-12 09:27:48 -05:00
gitversion.yml Publish nuget package, bump version to 1.20 2018-01-25 15:34:37 -05:00
readme.md Improved doc (readme) 2018-01-25 16:06:37 -05:00

readme.md

Uno SourceGenerator

The Uno source generator is a API compatible source generator inspired by Roslyn v2.0 source generation feature, and an msbuild task which executes the SourceGenerators.

It provides a way to generate C# source code based on a project being built, using all of its syntactic and semantic model information.

Using this generator allows for a set of generators to share the same Roslyn compilation context, which is particularly expensive to create, and run all the generators in parallel.

The Uno.SourceGeneratorTasks support updating generators on the fly, making iterative development easier as visual studio or MSBuild will not lock the generator's assemblies.

The Uno.SourceGeneratorTasks support any target framework for code generation, though there are limitations when using a mixed targetframeworks graph, such as generating code in a net47 project that references a netstandard2.0 project. In such cases, prefer adding a net47 target instead of targeting netstandard2.0.

Creating a Source Generator

  1. In Visual Studio 2017, create a .NET Standard Class Library project named MyGenerator

  2. In the csproj file

    1. Change the TargetFramework to net46
    2. Add a package reference to Uno.SourceGeneration (take the latest version)
    <ItemGroup>
    	<PackageReference Include="Uno.SourceGeneration" Version="1.5.0" />
    </ItemGroup>
    
  3. Add a new source file containing this code :

    using System;
    using Uno.SourceGeneration;
    
    namespace MyGenerator
    {
    	public class MyCustomSourceGenerator : SourceGenerator
    	{
    		public override void Execute(SourceGeneratorContext context)
    		{
    			var project = context.GetProjectInstance();
    
    			context.AddCompilationUnit("Test", "namespace MyGeneratedCode { class TestGeneration { } }");
    		}
    	}
    }
    

    Note that the GetProjectInstance is a helper method that provides access to the msbuild project currently being built. It provides access to the msbuild properties and item groups of the project, allowing for fine configuration of the source generator.

  4. Create a file named MyGenerator.props (should be the name of your project + .props) in a folder named build and set its Build Action to Content. Put the following content:

    <Project>
    	<ItemGroup>
    		<SourceGenerator Include="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net46\MyGenerator.dll" 
    				 Condition="Exists('$(MSBuildThisFileDirectory)..\bin')" />
    		<SourceGenerator Include="$(MSBuildThisFileDirectory)..\tools\MyGenerator.dll" 
    				 Condition="Exists('$(MSBuildThisFileDirectory)..\tools')" />
    	</ItemGroup>
    </Project>
    

Using the generator inside the same solution (another project)

  1. In Visual Studio 2017, create a .NET Standard Class Library project named MyLibrary. This is the project where your generator will do its generation.
  2. In the .csproj file:
    1. Change the TargetFramework to net46 (.Net Framework v4.6)
    2. Add a package reference to Uno.SourceGenerationTasks
      <ItemGroup>
         <PackageReference Include="Uno.SourceGenerationTasks" Version="1.5.0" />
      </ItemGroup>
      

      *You can also use the Nuget Package Manager to add this package reference. The version can differ, please use the same than the generator project.

    3. Import the source generator by placing the following line at the end :
      <Import Project="..\MyGenerator\build\MyGenerator.props" />
      
  3. Add some C# code that uses the MyGeneratedCode.TestGeneration class that the generator creates.
  4. Compile... it should works.
  5. You can sneak at the generated code by clicking the Show All Files button in the Solution Explorer. The code will be in the folder obj\<config>\<platform>\g\<generator name>\.

Packaging the source generator in NuGet

Packaging the generator in nuget requires to :

  1. In the csproj file containing your generator:
    1. Add this group to your csproj:

      <ItemGroup>
        <Content Include="build/**/*.*">
          <Pack>true</Pack>
          <PackagePath>build</PackagePath>
        </Content>
      </ItemGroup>
      
      

      Note that the name of this file must match the package name to be taken into account by nuget.

    2. Update the package references as follows

      <ItemGroup>
        <PackageReference Include="Uno.SourceGeneration" Version="1.19.0-dev.316" PrivateAssets="All" />
        <PackageReference Include="Uno.SourceGenerationTasks" Version="1.19.0-dev.316" PrivateAssets="None" />
      </ItemGroup>
      

      This ensure that the source generator tasks will be included in any project referencing your new generator, and that the source generation interfaces are not included.

      *You can also use the Nuget Package Manager to add this package reference. The version can differ, please take the latest stable one.

    3. Add the following property:

      <PropertyGroup>
        <IsTool>true</IsTool>
      </PropertyGroup>
      

      This will allow for the generator package to be installed on any target framework.

Debugging a generator

In your generator, add the following in the SourceGenerator.Execute override :

Debugger.Launch();

This will open another visual studio instance, and allow for stepping through the generator's code.

General guidelines for creating generators

  • Generators should have the least possible external dependencies. Generators are loaded in a separate AppDomain but multiple assemblies versions can be troublesome when loaded side by side.
  • A generator currently cannot depend on another generator. When a project is loaded to be analyzed, all generated files are excluded from the roslyn Compilation, meaning that if two generators use the same conditions to generate the same code, there will be a compilation error in the resulting code.
  • If you need a generator to use the result of another one for its own compilation, you can use the [SourceGeneratorDependency] attribute. You simply need to specify the FullName (namespace + type name) of another generator. If this generator is found, it will ensure it is executed before and the result is added to the compilation before calling yours.
  • Sometimes you may need to kill all instances of MsBuild. On Windows, the fatest way to to that is to open a shell in admin mode and type this line:
    taskkill /fi "imagename eq msbuild.exe" /f /t
    

Troubleshooting

The source generator provides additional details when building, when running the _UnoSourceGenerator msbuild target.

To view this information either place visual studio in details verbosity (Options, Projects and Solutions, Build and Run then MSBuild project build output verbosity) or by using the excellent MSBuild Binary and Structured Log Viewer from Kirill Osenkov.