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

Commit 875f8b0

Browse files
authored
feat: Add templates package for dotnet new (#217)
* feat: Add Templates package * feat: script for testing template pack * ci: Build and test templates * docs: Add templates usage to readme
1 parent 22c9620 commit 875f8b0

File tree

32 files changed

+745
-52
lines changed

32 files changed

+745
-52
lines changed

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"isRoot": true,
44
"tools": {
55
"nbgv": {
6-
"version": "3.1.71",
6+
"version": "3.0.28",
77
"commands": [
88
"nbgv"
99
]

.github/workflows/ci.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ jobs:
2121
os: [ windows-latest, ubuntu-latest, macos-latest ]
2222
runs-on: ${{ matrix.os }}
2323
steps:
24-
- uses: actions/checkout@v1
24+
- uses: actions/checkout@v2
25+
with:
26+
fetch-depth: 0
2527

2628
- name: Setup .NET Core
2729
uses: actions/setup-dotnet@v1
@@ -58,3 +60,9 @@ jobs:
5860
- name: Build samples
5961
shell: pwsh
6062
run: samples/build.ps1
63+
64+
- name: Build and test templates
65+
shell: pwsh
66+
run: |
67+
templates/build.ps1
68+
templates/test-new.ps1

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,6 @@ FakesAssemblies/
209209
GeneratedArtifacts/
210210
_Pvt_Extensions/
211211
ModelManifest.xml
212+
213+
# MSBuild binlogs
214+
*.binlog

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
## Prerequisites
22

33
* .NET Core SDK - version as specified in [src/global.json](src/global.json).
4-
* netcoreapp2.1 runtime (Microsoft.NETCore.App 2.1.x)
4+
* PowerShell 7+ (`pwsh` in PATH)
55

66
## Building
77

README.md

Lines changed: 103 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ Instructions on development and using this project's source code are in [CONTRIB
1919
- [Table of Contents](#table-of-contents)
2020
- [How to write your own code generator](#how-to-write-your-own-code-generator)
2121
- [Prerequisites](#prerequisites)
22+
- [Use template pack](#use-template-pack)
2223
- [Define code generator](#define-code-generator)
23-
- [Create consuming console app](#create-consuming-console-app)
2424
- [Define attribute](#define-attribute)
25+
- [Create consuming console app](#create-consuming-console-app)
2526
- [Apply code generation](#apply-code-generation)
2627
- [Advanced scenarios](#advanced-scenarios)
2728
- [Customize generator reference](#customize-generator-reference)
@@ -44,6 +45,63 @@ In this walkthrough, we will define a code generator that replicates any class (
4445

4546
[dotnet-sdk-2.1]: https://dotnet.microsoft.com/download/dotnet-core/2.1
4647

48+
### Use template pack
49+
50+
To install the template pack, run:
51+
> `dotnet new -i CodeGeneration.Roslyn.Templates`
52+
53+
You'll then have our template pack installed and ready for use with `dotnet new`.
54+
For details see [templates Readme](./templates/README.md).
55+
56+
Prepare a directory where you want to create your Plugin projects, e.g. `mkdir DemoGeneration`.
57+
Then, in that directory, create the set of Plugin projects (we add the --sln to create solution as well):
58+
> `dotnet new cgrplugin -n Duplicator --sln`
59+
60+
This will create 3 ready-to-build projects. You can now skip through the next steps
61+
of creating and setting up projects, just apply the following changes to have the same content:
62+
- rename `Duplicator.Generators/Generator1.cs` to `DuplicateWithSuffixGenerator.cs`
63+
- also replace the `DuplicateWithSuffixGenerator` class with the following:
64+
```csharp
65+
public class DuplicateWithSuffixGenerator : ICodeGenerator
66+
{
67+
private readonly string suffix;
68+
69+
public DuplicateWithSuffixGenerator(AttributeData attributeData)
70+
{
71+
suffix = (string)attributeData.ConstructorArguments[0].Value;
72+
}
73+
74+
public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(TransformationContext context, IProgress<Diagnostic> progress, CancellationToken cancellationToken)
75+
{
76+
// Our generator is applied to any class that our attribute is applied to.
77+
var applyToClass = (ClassDeclarationSyntax)context.ProcessingNode;
78+
79+
// Apply a suffix to the name of a copy of the class.
80+
var copy = applyToClass.WithIdentifier(SyntaxFactory.Identifier(applyToClass.Identifier.ValueText + suffix));
81+
82+
// Return our modified copy. It will be added to the user's project for compilation.
83+
var results = SyntaxFactory.SingletonList<MemberDeclarationSyntax>(copy);
84+
return Task.FromResult(results);
85+
}
86+
}
87+
```
88+
- rename `Duplicator.Attributes/Generator1Attribute.cs` file to `DuplicateWithSuffixAttribute.cs`
89+
- also replace `DuplicateWithSuffixAttribute` class with the following:
90+
```csharp
91+
[AttributeUsage(AttributeTargets.Class)]
92+
[CodeGenerationAttribute("Duplicator.Generators.DuplicateWithSuffixGenerator, Duplicator.Generators")]
93+
[Conditional("CodeGeneration")]
94+
public class DuplicateWithSuffixAttribute : Attribute
95+
{
96+
public DuplicateWithSuffixAttribute(string suffix)
97+
{
98+
Suffix = suffix;
99+
}
100+
101+
public string Suffix { get; }
102+
}
103+
```
104+
47105
### Define code generator
48106

49107
Your generator cannot be defined in the same project that will have code generated
@@ -57,7 +115,7 @@ Now we'll use an [MSBuild project SDK] [`CodeGeneration.Roslyn.Plugin.Sdk`][Plug
57115
<!-- Duplicator.Generators/Duplicator.Generators.csproj -->
58116
<Project Sdk="Microsoft.NET.Sdk">
59117
<!-- Add the following element above any others: -->
60-
<Sdk Name="CodeGeneration.Roslyn.Plugin.Sdk" Version="{replace with actual version used}"/>
118+
<Sdk Name="CodeGeneration.Roslyn.Plugin.Sdk" Version="{replace with actual version used}" />
61119

62120
<PropertyGroup>
63121
<TargetFramework>netcoreapp2.1</TargetFramework>
@@ -107,56 +165,26 @@ namespace Duplicator.Generators
107165
}
108166
```
109167

110-
### Create consuming console app
111-
112-
We'll consume our generator in a Reflector app:
113-
> `dotnet new console -f netcoreapp2.1 -o Reflector`
114-
>
115-
> `dotnet add Reflector reference Duplicator.Generators`
116-
117-
Let's write a simple program that prints all types in its assembly:
118-
```csharp
119-
// Reflector/Program.cs
120-
using System;
121-
122-
namespace Reflector
123-
{
124-
class Program
125-
{
126-
static void Main(string[] args)
127-
{
128-
foreach (var type in typeof(Program).Assembly.GetTypes())
129-
Console.WriteLine(type.FullName);
130-
}
131-
}
132-
}
133-
```
134-
135-
Now, when we `dotnet run -p Reflector` we should get:
136-
> `Reflector.Program`
137-
138168
### Define attribute
139169

140170
To activate your code generator, you need to define an attribute with which
141-
we'll annotate the class to be copied. Install [Attributes package][AttrNuPkg]:
171+
we'll annotate the class to be copied. Let's do that in a new project:
172+
> `dotnet new classlib -f netstandard2.0 -o Duplicator.Attributes`
142173
143-
> `dotnet add Reflector package CodeGeneration.Roslyn.Attributes`
174+
Install [Attributes package][AttrNuPkg]:
175+
176+
> `dotnet add Duplicator.Attributes package CodeGeneration.Roslyn.Attributes`
144177
145178
Then, define your attribute class:
146179

147180
```csharp
148-
// Reflector/Program.cs
181+
// Duplicator.Attributes/DuplicateWithSuffixAttribute.cs
149182
using System;
150183
using System.Diagnostics;
151184
using CodeGeneration.Roslyn;
152185

153-
namespace Reflector
186+
namespace Duplicator
154187
{
155-
class Program
156-
{
157-
// ...
158-
}
159-
160188
[AttributeUsage(AttributeTargets.Class)]
161189
[CodeGenerationAttribute("Duplicator.Generators.DuplicateWithSuffixGenerator, Duplicator.Generators")]
162190
[Conditional("CodeGeneration")]
@@ -185,6 +213,36 @@ with a dependency on your code generation assembly.
185213
> ℹ Of course, the attribute will persist if you define compilation symbol
186214
> "CodeGeneration"; we assume it won't be defined.
187215
216+
### Create consuming console app
217+
218+
We'll consume our generator in a Reflector app:
219+
> `dotnet new console -o Reflector`
220+
>
221+
> `dotnet add Reflector reference Duplicator.Attributes`
222+
>
223+
> `dotnet add Reflector reference Duplicator.Generators`
224+
225+
Let's write a simple program that prints all types in its assembly:
226+
```csharp
227+
// Reflector/Program.cs
228+
using System;
229+
230+
namespace Reflector
231+
{
232+
class Program
233+
{
234+
static void Main(string[] args)
235+
{
236+
foreach (var type in typeof(Program).Assembly.GetTypes())
237+
Console.WriteLine(type.FullName);
238+
}
239+
}
240+
}
241+
```
242+
243+
Now, when we `dotnet run -p Reflector` we should get:
244+
> `Reflector.Program`
245+
188246
### Apply code generation
189247

190248
Applying code generation is incredibly simple. Just add the attribute on any type
@@ -195,6 +253,7 @@ or member supported by the attribute and generator you wrote. We'll test our Dup
195253
using System;
196254
using System.Diagnostics;
197255
using CodeGeneration.Roslyn;
256+
using Duplicator;
198257

199258
namespace Reflector
200259
{
@@ -205,19 +264,12 @@ namespace Reflector
205264
{
206265
// ...
207266
}
208-
209-
class DuplicateWithSuffixAttribute : Attribute
210-
{
211-
// ...
212-
}
213267
}
214268
```
215269

216270
Let's check our app again:
217271
> `> dotnet run -p Reflector`
218272
>
219-
> `Reflector.DuplicateWithSuffixAttribute`
220-
>
221273
> `Reflector.Program`
222274
>
223275
> `Reflector.Test`
@@ -261,8 +313,6 @@ This is how your project file can look like:
261313
And if all steps were done correctly:
262314
> `> dotnet run -p Reflector`
263315
>
264-
> `Reflector.DuplicateWithSuffixAttribute`
265-
>
266316
> `Reflector.Program`
267317
>
268318
> `Reflector.Test`
@@ -354,12 +404,16 @@ we have to use `SetTargetFramework` metadata. Setting it implies
354404

355405
### Package your code generator
356406

407+
> ℹ If you've used `cgrplugin` template, you've already got metapackage project ready.
408+
357409
You can also package up your code generator as a NuGet package for others to install
358410
and use. A project using `CodeGeneration.Roslyn.Plugin.Sdk` is automatically
359411
configured to produce a correct Plugin nuget package.
360412

361413
#### Separate out the attribute
362414

415+
> ⚠ This section is deprecated since it's now the default.
416+
363417
The triggering attribute has to be available in consuming code. Your consumers
364418
can write it themselves, but it's not a good idea to require them do so.
365419
So we'll separate the attribute into another project that has TFM allowing
@@ -398,7 +452,7 @@ An example consuming project file would contain:
398452
</ItemGroup>
399453
```
400454

401-
> There's a much better approach: **metapackage**.
455+
> There's a much better approach: **metapackage**.
402456
403457
For this, we'll use [`CodeGeneration.Roslyn.PluginMetapackage.Sdk`][PluginMetapkgSdkNuPkg] [MSBuild project SDK]
404458
in a new project called simply `Duplicator`, which will reference our attributes:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.Build.NoTargets">
2+
3+
<PropertyGroup>
4+
<PackageType>Template</PackageType>
5+
<Description>Templates to use when creating CodeGeneration.Roslyn Plugins (source code generators).</Description>
6+
<PackageTags>$(PackageTags);templates;cgrgen;cgratt;cgrplugin;cgrplugingens;cgrpluginatts</PackageTags>
7+
<TargetFramework>netstandard2.0</TargetFramework>
8+
<IncludeBuildOutput>false</IncludeBuildOutput>
9+
<IncludeContentInPack>true</IncludeContentInPack>
10+
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
11+
<IncludeSymbols>false</IncludeSymbols>
12+
<BeforePack>$(BeforePack);SetPackageVersionInTemplateConfigs</BeforePack>
13+
</PropertyGroup>
14+
15+
<ItemGroup>
16+
<Content Include="../../templates/content/**" Pack="true" PackagePath="content" />
17+
</ItemGroup>
18+
19+
<Target Name="SetPackageVersionInTemplateConfigs" DependsOnTargets="GetBuildVersion">
20+
<ItemGroup>
21+
<!-- Take template.json files, set patched Target paths and PackagePath -->
22+
<_TemplateJson Include="@(Content)"
23+
Condition=" '%(Filename)%(Extension)' == 'template.json' "
24+
TargetPath="$(IntermediateOutputPath)patched/%(RecursiveDir)%(Filename)%(Extension)"
25+
PackagePath="%(PackagePath)/%(RecursiveDir)"/>
26+
<Content Remove="@(_TemplateJson)" />
27+
<Content Include="@(_TemplateJson->'%(TargetPath)')" />
28+
</ItemGroup>
29+
<!-- Actually patch the files: pwsh -c "&{ ./patch.ps1 'src1','src2' 'trg1','trg2' 'version' }" -->
30+
<Exec Command="pwsh -c &quot; &amp;{ ./patch.ps1 @(_TemplateJson->'%27%(Identity)%27', ',') @(_TemplateJson->'%27%(TargetPath)%27', ',') '$(PackageVersion)' } &quot;" />
31+
</Target>
32+
33+
</Project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
param (
2+
[Parameter()]
3+
[string[]] $InputFile,
4+
[Parameter()]
5+
[string[]] $OutputFile,
6+
[Parameter()]
7+
[string] $Version
8+
)
9+
10+
while ($InputFile) {
11+
$input, $InputFile = $InputFile
12+
$output, $OutputFile = $OutputFile
13+
14+
$json = Get-Content $input | ConvertFrom-Json
15+
$versionSymbol = $json.symbols.'cgr-version'
16+
if ($versionSymbol) {
17+
$versionSymbol.defaultValue = $Version
18+
}
19+
New-Item $output -Value (ConvertTo-Json $json) -Force
20+
}

src/CodeGeneration.Roslyn.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeGeneration.Roslyn.Plugi
4242
EndProject
4343
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGeneration.Roslyn.PluginMetapackage.Sdk", "CodeGeneration.Roslyn.PluginMetapackage.Sdk\CodeGeneration.Roslyn.PluginMetapackage.Sdk.csproj", "{C1964B5A-1E9C-477B-B1A1-B464FFB8BCB7}"
4444
EndProject
45+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeGeneration.Roslyn.Templates", "CodeGeneration.Roslyn.Templates\CodeGeneration.Roslyn.Templates.csproj", "{ABEDDF07-58BA-47EB-BA0F-F615D1E6D397}"
46+
EndProject
4547
Global
4648
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4749
Debug|Any CPU = Debug|Any CPU
@@ -88,6 +90,10 @@ Global
8890
{C1964B5A-1E9C-477B-B1A1-B464FFB8BCB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
8991
{C1964B5A-1E9C-477B-B1A1-B464FFB8BCB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
9092
{C1964B5A-1E9C-477B-B1A1-B464FFB8BCB7}.Release|Any CPU.Build.0 = Release|Any CPU
93+
{ABEDDF07-58BA-47EB-BA0F-F615D1E6D397}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
94+
{ABEDDF07-58BA-47EB-BA0F-F615D1E6D397}.Debug|Any CPU.Build.0 = Debug|Any CPU
95+
{ABEDDF07-58BA-47EB-BA0F-F615D1E6D397}.Release|Any CPU.ActiveCfg = Release|Any CPU
96+
{ABEDDF07-58BA-47EB-BA0F-F615D1E6D397}.Release|Any CPU.Build.0 = Release|Any CPU
9197
EndGlobalSection
9298
GlobalSection(SolutionProperties) = preSolution
9399
HideSolutionNode = FALSE

src/Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<RepositoryUrl>https://github.com/aarnott/CodeGeneration.Roslyn</RepositoryUrl>
1010
<RepositoryType>git</RepositoryType>
1111
<PackageReleaseNotes>https://github.com/AArnott/CodeGeneration.Roslyn/blob/master/CHANGELOG.md</PackageReleaseNotes>
12+
<PackageTags>codegen;codegeneration;roslyn;sourcegen;sourcegeneration;source;generation</PackageTags>
1213

1314
<PublishRepositoryUrl>true</PublishRepositoryUrl>
1415
<EmbedUntrackedSources>true</EmbedUntrackedSources>

templates/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.nuget

0 commit comments

Comments
 (0)