Skip to content
This repository was archived by the owner on Dec 12, 2020. It is now read-only.

Commit f36a658

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/master'
2 parents f6c98a0 + 50458b5 commit f36a658

22 files changed

+1290
-100
lines changed

README.md

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,30 @@ This includes design-time support, such that code generation can respond to
88
changes made in hand-authored code files by generating new code that shows
99
up to Intellisense as soon as the file is saved to disk.
1010

11+
## Table of Contents
12+
13+
* [How to write your own code generator][]
14+
* [Apply code generation][]
15+
* [Developing your code generator][]
16+
* [Packaging up your code generator for others' use][]
17+
1118
## How to write your own code generator
19+
[How to write your own code generator]: #how-to-write-your-own-code-generator
1220

1321
In this walkthrough, we will define a code generator that replicates any class
1422
your code generation attribute is applied to, but with a suffix appended to its name.
1523

1624
### Define code generator
25+
[Define code generator]: #define-code-generator
1726

18-
This must be done in a library that targets netstandard1.5 or net462 (or later).
27+
This must be done in a library that targets netstandard1.6 or net461 (or later;
28+
net461 is supported if you have .NET Core SDK v2.0+ installed, [see docs for details][netstandard-table]).
1929
Your generator cannot be defined in the same project that will have code generated
2030
for it because code generation runs *before* the receiving project is itself compiled.
2131

2232
Install the [CodeGeneration.Roslyn][NuPkg] NuGet Package.
2333

24-
Define the generator class:
34+
Define the generator class (*note: constructor accepting `AttributeData` parameter is required*):
2535

2636
```csharp
2737
using CodeGeneration.Roslyn;
@@ -67,7 +77,7 @@ public class DuplicateWithSuffixGenerator : ICodeGenerator
6777
To activate your code generator, you need to define an attribute that can be
6878
applied to the class to be copied. This attribute may be defined in the same
6979
assembly as defines your code generator, but since your code generator must
70-
be defined in a netstandard1.3 or net46 library, this may limit which projects
80+
be defined in a netstandard1.6+ or net461+ library, this may limit which projects
7181
can apply your attribute. So define your attribute in another assembly if
7282
it must be applied to projects that target older platforms.
7383

@@ -76,7 +86,7 @@ If your attributes are in their own project, you must install the
7686

7787
Define your attribute class.
7888
For this walkthrough, we will assume that the attributes are defined in the same
79-
netstandard1.3 project that defines the generator which allows us to use the more
89+
netstandard1.6 project that defines the generator which allows us to use the more
8090
convenient `typeof` syntax when declaring the code generator type.
8191
If the attributes and code generator classes were in separate assemblies, you must
8292
specify the assembly-qualified name of the generator type as a string instead.
@@ -109,10 +119,13 @@ instead as just a compile-time hint to code generation, and allowing you to not
109119
with a dependency on your code generation assembly.
110120

111121
## Apply code generation
122+
[Apply code generation]: #apply-code-generation
112123

113124
The attribute may not be applied in the same assembly that defines the generator.
114125
This is because the code generator must be compiled in order to execute before compiling
115-
the project that applies the attribute.
126+
the project that applies the attribute. Also, the consuming project (where the code
127+
will be generated) must use SDK-style csproj, which implies using VS2017+ or `dotnet`
128+
CLI tooling (VS Code with omnisharp, for example).
116129

117130
Applying code generation is incredibly simple. Just add the attribute on any type
118131
or member supported by the attribute and generator you wrote. Note you will need to
@@ -127,7 +140,14 @@ public class Foo
127140

128141
Install the [CodeGeneration.Roslyn.BuildTime][BuildTimeNuPkg] package into the
129142
project that uses your attribute. You may set `PrivateAssets="all"` on this reference
130-
because this is a build-time only package.
143+
because this is a build-time only package. You must also add this item to an `<ItemGroup>` in the project
144+
that will execute the code generator as part of your build:
145+
146+
```xml
147+
<DotNetCliToolReference Include="dotnet-codegen" Version="0.4.12" />
148+
```
149+
150+
You should adjust the version in the above xml to match the version of this tool you are using.
131151

132152
You can then consume the generated code at design-time:
133153

@@ -145,43 +165,23 @@ If you execute Go To Definition on it, Visual Studio will open the generated cod
145165
that actually defines `FooA`, and you'll notice it's exactly like `Foo`, just renamed
146166
as our code generator defined it to be.
147167

148-
### Using `dotnet build`
149-
150-
If you build with `dotnet build`, you need to take a couple extra steps. First, define a `nuget.config` file with an extra package source:
151-
152-
```xml
153-
<?xml version="1.0" encoding="utf-8"?>
154-
<configuration>
155-
<packageSources>
156-
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
157-
<add key="corefxlab" value="https://dotnet.myget.org/F/dotnet-corefxlab/api/v3/index.json" />
158-
</packageSources>
159-
</configuration>
160-
```
161-
162-
Second, add this item to an `<ItemGroup>` in the project that will execute the code generator as part of your build:
163-
164-
```xml
165-
<DotNetCliToolReference Include="dotnet-codegen" Version="0.4.12" />
166-
```
167-
168-
You should adjust the version in the above xml to match the version of this tool you are using.
169-
170168
### Shared Projects
171169

172170
When using shared projects and partial classes across the definitions of your class in shared and platform projects:
173171

174172
* The code generation attributes should be applied only to the files in the shared project
175173
(or in other words, the attribute should only be applied once per type to avoid multiple generator invocations).
176-
* The MSBuild:GenerateCodeFromAttributes custom tool must be applied to every file we want to auto generate code from.
174+
* The `MSBuild:GenerateCodeFromAttributes` custom tool must be applied to every file we want to auto generate code from.
177175

178176
## Developing your code generator
177+
[Developing your code generator]: #developing-your-code-generator
179178

180179
Your code generator can be defined in a project in the same solution as the solution with
181180
the project that consumes it. You can edit your code generator and build the solution
182181
to immediately see the effects of your changes on the generated code.
183182

184183
## Packaging up your code generator for others' use
184+
[Packaging up your code generator for others' use]: #packaging-up-your-code-generator-for-others-use
185185

186186
You can also package up your code generator as a NuGet package for others to install
187187
and use. Your NuGet package should include a dependency on the `CodeGeneration.Roslyn.BuildTime`
@@ -209,7 +209,7 @@ For example your package should have a `build\MyPackage.targets` file with this
209209
Then your package should also have a `tools` folder that contains your code generator and any of the runtime
210210
dependencies it needs *besides those delivered by the `CodeGeneration.Roslyn.BuildTime` package*.
211211

212-
Your attributes assembly should be placed under your package's `lib` folder` so consuming projects
212+
Your attributes assembly should be placed under your package's `lib` folder so consuming projects
213213
can apply those attributes.
214214

215215
Your consumers should depend on your package, and the required dotnet CLI tool,
@@ -222,21 +222,10 @@ so that the MSBuild Task can invoke the `dotnet codegen` command line tool:
222222
</ItemGroup>
223223
```
224224

225-
Also, any consumer must have a nuget.config file with at least this content:
226-
227-
```xml
228-
<?xml version="1.0" encoding="utf-8"?>
229-
<configuration>
230-
<packageSources>
231-
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
232-
<add key="corefxlab" value="https://dotnet.myget.org/F/dotnet-corefxlab/api/v3/index.json" />
233-
</packageSources>
234-
</configuration>
235-
```
236-
237225
Make sure that the DotNetCliToolReference version matches the version of the
238226
`CodeGeneration.Roslyn` package your package depends on.
239227

240228
[NuPkg]: https://nuget.org/packages/CodeGeneration.Roslyn
241229
[BuildTimeNuPkg]: https://nuget.org/packages/CodeGeneration.Roslyn.BuildTime
242230
[AttrNuPkg]: https://nuget.org/packages/CodeGeneration.Roslyn.Attributes
231+
[netstandard-table]: https://docs.microsoft.com/dotnet/standard/net-standard#net-implementation-support

src/CodeGeneration.Roslyn.Tasks/GenerateCodeFromAttributes.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,51 @@ protected override string GenerateResponseFileCommands()
9191
argBuilder.AppendLine(this.generatedCompileItemsFilePath);
9292

9393
argBuilder.AppendLine("--");
94-
foreach(var item in this.Compile)
94+
foreach (var item in this.Compile)
9595
{
9696
argBuilder.AppendLine(item.ItemSpec);
9797
}
9898

9999
return argBuilder.ToString();
100100
}
101+
102+
protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)
103+
{
104+
MessageImportance newImportance;
105+
if (DidExtractPrefix("High"))
106+
{
107+
newImportance = MessageImportance.High;
108+
}
109+
else if (DidExtractPrefix("Normal"))
110+
{
111+
newImportance = MessageImportance.Normal;
112+
}
113+
else if (DidExtractPrefix("Low"))
114+
{
115+
newImportance = MessageImportance.Low;
116+
}
117+
else
118+
{
119+
newImportance = messageImportance;
120+
}
121+
122+
if (newImportance < messageImportance)
123+
{
124+
messageImportance = newImportance; // Lower value => higher importance
125+
}
126+
127+
base.LogEventsFromTextOutput(singleLine, messageImportance);
128+
129+
bool DidExtractPrefix(string importanceString)
130+
{
131+
var prefix = $"::{importanceString}::";
132+
if (singleLine.StartsWith(prefix))
133+
{
134+
singleLine = singleLine.Substring(prefix.Length);
135+
return true;
136+
}
137+
return false;
138+
}
139+
}
101140
}
102141
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Andrew Arnott. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
3+
4+
namespace CodeGeneration.Roslyn.Tests.Generators
5+
{
6+
7+
using System;
8+
using System.Diagnostics;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
12+
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
13+
[CodeGenerationAttribute(typeof(AddGeneratedAttributeGenerator))]
14+
[Conditional("CodeGeneration")]
15+
public class AddGeneratedAttributeAttribute : Attribute
16+
{
17+
public AddGeneratedAttributeAttribute(string attribute)
18+
{
19+
Attribute = attribute;
20+
}
21+
22+
public string Attribute { get; }
23+
}
24+
25+
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
26+
public class GeneratedAttribute : Attribute
27+
{ }
28+
29+
public class AddGeneratedAttributeGenerator : RichBaseGenerator
30+
{
31+
32+
public AddGeneratedAttributeGenerator(AttributeData attributeData) : base(attributeData)
33+
{
34+
Attribute = (string)AttributeData.ConstructorArguments[0].Value;
35+
}
36+
37+
public string Attribute { get; }
38+
39+
protected override void Generate(RichGenerationContext context)
40+
{
41+
var attribute = SyntaxFactory.Attribute(SyntaxFactory.ParseName(Attribute));
42+
context.AddAttribute(attribute);
43+
}
44+
}
45+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Andrew Arnott. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
3+
4+
namespace CodeGeneration.Roslyn.Tests.Generators
5+
{
6+
7+
using System;
8+
using System.Diagnostics;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
12+
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
13+
[CodeGenerationAttribute(typeof(AddGeneratedExternGenerator))]
14+
[Conditional("CodeGeneration")]
15+
public class AddGeneratedExternAttribute : Attribute
16+
{
17+
public AddGeneratedExternAttribute(string @extern)
18+
{
19+
Extern = @extern;
20+
}
21+
22+
public string Extern { get; }
23+
}
24+
25+
public class AddGeneratedExternGenerator : RichBaseGenerator
26+
{
27+
28+
public AddGeneratedExternGenerator(AttributeData attributeData) : base(attributeData)
29+
{
30+
Extern = (string)AttributeData.ConstructorArguments[0].Value;
31+
}
32+
33+
public string Extern { get; }
34+
35+
protected override void Generate(RichGenerationContext context)
36+
{
37+
var externAlias = SyntaxFactory.ExternAliasDirective(Extern);
38+
context.AddExtern(externAlias);
39+
}
40+
}
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Andrew Arnott. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
3+
4+
using System.Diagnostics;
5+
6+
namespace CodeGeneration.Roslyn.Tests.Generators
7+
{
8+
using System;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
12+
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
13+
[CodeGenerationAttribute(typeof(AddGeneratedUsingGenerator))]
14+
[Conditional("CodeGeneration")]
15+
public class AddGeneratedUsingAttribute : Attribute
16+
{
17+
public AddGeneratedUsingAttribute(string @using)
18+
{
19+
Using = @using;
20+
}
21+
22+
public string Using { get; }
23+
}
24+
25+
public class AddGeneratedUsingGenerator : RichBaseGenerator
26+
{
27+
28+
public AddGeneratedUsingGenerator(AttributeData attributeData) : base(attributeData)
29+
{
30+
Using = (string)AttributeData.ConstructorArguments[0].Value;
31+
}
32+
33+
public string Using { get; }
34+
35+
protected override void Generate(RichGenerationContext context)
36+
{
37+
var usingSyntax = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(Using));
38+
context.AddUsing(usingSyntax);
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)