- Notifications
You must be signed in to change notification settings - Fork11
A Roslyn based C# source generation framework
License
unoplatform/Uno.SourceGeneration
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
The Uno source generator is an API compatible source generator inspiredbyRoslyn v2.0 source generation feature, and anmsbuild 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.
The Uno Source Generator also supports theRoslyn 3.8 (C# 9.0) Source Generator APIs, see below for more details. Note that as of Roslyn 3.8, generators do not support multi-pass generation where generators can depend on each others. In order to benefit from this feature, a generator must run using Uno.SourceGenerationTasks.
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.
TheUno.SourceGeneratorTasks
support updating generators on the fly, making iterative development easier as visual studio or MSBuild will not lock the generator's assemblies.
TheUno.SourceGeneratorTasks
support any target framework for code generation, though there are limitations whenusing a mixed targetframeworks graph, such as generating codein anet47
project that references anetstandard2.0
project. In such cases, prefer adding anet47
target instead of targetingnetstandard2.0
.
Visual Studio 2017 15.3+ for Windows, macOS and Linux builds are supported. Building for .NET Core requires .NET 3.0.100 or later.
Target | Branch | Status |
---|---|---|
development | master |
Package | nuget.org | usage |
---|---|---|
Uno.SourceGeneration | Use this package to create a generator | |
Uno.SourceGenerationTasks | Use this package to use a generator |
Experimental packages are available through this NuGet feed:https://pkgs.dev.azure.com/uno-platform/Uno%20Platform/_packaging/unoplatformdev/nuget/v3/index.json
In Visual Studio 2017, create a.NET Standard Class Library project named
MyGenerator
In the csproj file
- Change the TargetFramework to
net46
- Add a package reference to
Uno.SourceGeneration
(take the latest version)
<ItemGroup> <PackageReferenceInclude="Uno.SourceGeneration"Version="1.5.0" /></ItemGroup>
- Change the TargetFramework to
Add a new source file containing this code :
usingSystem;usingUno.SourceGeneration;namespaceMyGenerator{publicclassMyCustomSourceGenerator:SourceGenerator{publicoverridevoidExecute(SourceGeneratorContextcontext){varproject=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.
Create a file named
MyGenerator.props
(should be the name of your project +.props
) in a folder namedbuild
and set itsBuild Action toContent
. Put the following content:<Project> <ItemGroup> <SourceGeneratorInclude="$(MSBuildThisFileDirectory)..\bin\$(Configuration)\net46\MyGenerator.dll"Condition="Exists('$(MSBuildThisFileDirectory)..\bin')" /> <SourceGeneratorInclude="$(MSBuildThisFileDirectory)..\tools\MyGenerator.dll"Condition="Exists('$(MSBuildThisFileDirectory)..\tools')" /> </ItemGroup></Project>
- In Visual Studio 2017, create a.NET Standard Class Library project named
MyLibrary
.This is the project where your generator will do its generation. - In the
.csproj
file:- Change the TargetFramework to
net46
(.Net Framework v4.6) - Add a package reference to
Uno.SourceGenerationTasks
<ItemGroup> <PackageReferenceInclude="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.
- Import the source generator by placing the following line at the end :
<ImportProject="..\MyGenerator\build\MyGenerator.props" />
- Change the TargetFramework to
- Add some C# code that uses the
MyGeneratedCode.TestGeneration
class that the generator creates. - Compile... it should works.
- You can sneak at the generated code by clicking theShow All Files button in theSolution Explorer.The code will be in the folder
obj\<config>\<platform>\g\<generator name>\
.
Packaging the generator in nuget requires to :
In the
csproj
file containing your generator:- Add this group to your csproj:
<ItemGroup> <ContentInclude="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.
Update the package references as follows
<ItemGroup> <PackageReferenceInclude="Uno.SourceGeneration"Version="1.19.0-dev.316"PrivateAssets="All" /> <PackageReferenceInclude="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 yournew 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.
Add the following property:
<PropertyGroup> <IsTool>true</IsTool></PropertyGroup>
This will allow for the generator package to be installed on any target framework.
Based onC# 9.0 generators the bootstrapper defines a set of APIs that are compatible with Roslyn.
Here's a roslyn compatible generator:
[Generator]publicclassCustomGenerator:ISourceGenerator{publicvoidInitialize(GeneratorInitializationContextcontext){}publicvoidExecute(GeneratorExecutionContextcontext){context.AddSource("myGeneratedFile.cs",@"namespace GeneratedNamespace{ public class GeneratedClass { public static void GeneratedMethod() { // generated code } }}");}}
Uno also provides a set of methods giving accessto the MSBuild properties and items, compatible Uno's source generation tasks:
publicvoidExecute(GeneratorExecutionContextcontext){varmyProperty=context.GetMSBuildPropertyValue("MyTestProperty");varmyItems=context.GetMSBuildPropertyValue("GetMSBuildItems").Select(i=>i.Identity);}
Note that the a generator running under Uno.SourceGenerationTasks does not need to define in MSBuild which properties need to be used, whereas C# 9.0 requires it.
In your generator, add the following in theSourceGenerator.Execute
override :
Debugger.Launch();
This will open another visual studio instance, and allow for stepping through the generator's code.
Generators should have the least possible external dependencies.Generators are loaded in a separate
AppDomain
but multiple assemblies versions can betroublesome when loaded side by side.You can add a dependency on your generator by adding the
Uno.SourceGeneration.SourceGeneratorDependency
attribute on your class:[GenerateAfter("Uno.ImmutableGenerator")]// Generate ImmutableGenerator before EqualityGeneratorpublicclassEqualityGenerator:SourceGenerator
For instance here, it will ensure that the
ImmutableGenerator
is executed before yourEqualityGenerator
.If you don't declare those dependencies, when a project is loaded to be analyzed, all generated filesfrom a generator are excluded from the roslynCompilation
object of other generators, meaning that iftwo generators use the same conditions to generate the same code, there will be a compilationerror in the resulting code.You can also define a generator which must be executedafter yours. To do this, you need to declare adependent generator:
[GenerateBefore("Uno.EqualityGenerator")]// Generate ImmutableGenerator before EqualityGeneratorpublicclassImmutableGenerator:SourceGenerator
Sometimes you may need to kill all instances of MsBuild. On Windows, the fatest way to to thatis to open a shell in admin mode and type this line:
taskkill/fi"imagename eq msbuild.exe"/f/t
You can write to build output using the following code:
publicoverridevoidExecute(SourceGeneratorContextcontext){varlogger=context.GetLogger();// this is an extension method on SourceGeneratorContext// Log something to build output when the mode is "detailed".logger.Debug($"The current count is{count}");// Log something to build output when the mode is "normal".logger.Info($"A total of{filesCount} has been generated.");// Log something as warning in build output.logger.Warn($"No code generated because the mode is{currentMode}.");// Log something as error in build output.logger.Error($"Unable to open file{filename}. No code generated.");}
The source generation task provides set of properties that can alter its behavior based on your project.
TheUnoSourceGeneratorAdditionalProperty
item group provides the ability for a project to enable thepropagation of specific properties to the generators. This may be required if properties areinjected dynamically,or provided as global variables.
A good example of this is theJavaSdkDirectory
that is generally injected as a global parameter throughthe msbuild command line.
In such as case, add the following in your project file:
<ItemGroup> <UnoSourceGeneratorAdditionalPropertyInclude="JavaSdkDirectory" /></ItemGroup>
In this case, theJavaSdkDirectory
value will be captured in the original build environment, and providedto the generators' build environment.
This is issue is caused by aopen Roslyn issuefor which all projects of the solution must have all the possible "head" projects configuration.
For instance, if you are building a UWP application, all the projects in the solution musthave the10.0.xxx
target framework defined, even ifnetstandard2.0
would have been enough.
The source generator provides additional details when building, when running the_UnoSourceGenerator
msbuild target.
To view this information either place visual studio indetails
verbosity (Options,Projects and Solutions,Build and Run thenMSBuild project build output verbosity) or by using the excellentMSBuild Binary and Structured Log Viewer fromKirill Osenkov.
The source generation target can also optionally producesbinlog
file in the obj folder, used to troubleshoot issues with the msbuild instancecreated inside the application domain used to generate the code. The path for those files can be altered using theUnoSourceGeneratorBinLogOutputPath
msbuild property. In the context of a VSTS build, setting it to$(build.artifactstagingdirectory)
allows for an improved diagnostics experience. Set theUnoSourceGeneratorUnsecureBinLogEnabled
property to true to enabled binary logging.
Important: The binary logger may leak secret environment variables, it is a best practice to never enable this feature as part of normal build.
By default, in some cases, the source generation host will run into an internal error, and will exit without providing details about the generation error.
To enable the logging of these errors, add the following property to your project:
<UnoSourceGeneratorCaptureGenerationHostOutput>true</UnoSourceGeneratorCaptureGenerationHostOutput>
The errors will the be visible when the build logging output is set to detailed, or byusing the binary logger.
Make sure to visit ourStackOverflow,create an issue orvisit our gitter.
A breaking change has been introduced to support proper UWP head projects, and when upgrading to Uno.SourceGenerationTasks1.29
or lateryou will have to search for projects that contain theuap10.0
target framework and do the following:
- Update to the MSBuild.Sdk.Extras 1.6.65 or later
- Choose an UWP sdk version, and use the appropriate target framework (e.g.
uap10.0
touap10.0.16299
)
About
A Roslyn based C# source generation framework
Topics
Resources
License
Code of conduct
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors11
Uh oh!
There was an error while loading.Please reload this page.