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

[WIP] Refactor: Tool now self-contained, BuildTime merged into it #198

Merged
merged 23 commits into from Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0262b32
wip: pack dotnet-codegen as non-tool Package
amis92 Mar 25, 2020
0192020
fix: add deps.json to tool package
amis92 Mar 25, 2020
46ec4c7
docs: add comments on deps.json workaround
amis92 Mar 25, 2020
eadaa46
docs: Add nuget-packaged Generator sample
amis92 Mar 25, 2020
45830f8
docs: Add packaged sample with dependency
amis92 Mar 25, 2020
4dfe4c2
refactor: Package tool as CGR.Tool
amis92 Mar 25, 2020
6a39db8
refactor: Merge BuildTime into Tool package
amis92 Mar 25, 2020
c356f6a
build: Use nbgv as local tool, update to v3.1.71
amis92 Mar 25, 2020
f2b2e83
fix: Downgrade Nerdbank.GitVersioning to 3.0
amis92 Mar 25, 2020
df7555d
fix: Ignore cleanup errors in samples/build.ps1
amis92 Mar 25, 2020
f774997
ci: Fix sample build configuration mismatch
amis92 Mar 25, 2020
3dd31b7
ci: Simplify GH Actions CI workflow
amis92 Mar 26, 2020
595bc8b
test: Target netcoreapp3.1 with tests
amis92 Mar 26, 2020
834078a
feature: Update Sdk for new Plugin packaging
amis92 Mar 26, 2020
8d31f81
docs: Apply Sdk props/targets to samples
amis92 Mar 26, 2020
8dcac1b
refactor: Simplify Sdk usage in samples
amis92 Mar 26, 2020
1156feb
docs: Update changelog
amis92 Mar 26, 2020
080501f
refactor: Simplify Plugin.Sdk contents
amis92 Mar 27, 2020
3ce87a1
docs: Update Readme for new Tool/Plugin.Sdk
amis92 Mar 27, 2020
c8494e7
docs: Add a migration note to changelog
amis92 Mar 27, 2020
ec4e278
sample: Add MetapackageSample
amis92 Mar 27, 2020
69158f0
docs: Link to the Metapackage sample to README
amis92 Mar 27, 2020
2a9fbab
docs: Fix some Readme formatting
amis92 Mar 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions .config/dotnet-tools.json
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"nbgv": {
"version": "3.1.71",
"commands": [
"nbgv"
]
}
}
}
88 changes: 25 additions & 63 deletions .github/workflows/ci.yml
Expand Up @@ -12,87 +12,49 @@ on:

env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
build_configuration: Release
Configuration: Release

jobs:
build-windows:
runs-on: windows-latest
build:
strategy:
matrix:
os: [ windows-latest, ubuntu-latest, macos-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- name: Install .NET Core
shell: pwsh
run: |
$info = dotnet --info
$isRuntime = $info -contains 'Microsoft.NETCore.App 2.1'
Push-Location src; dotnet --version > $null; Pop-Location; $isSdk = $LASTEXITCODE -eq 0; $LASTEXITCODE = 0
if ($isSdk -and $isRuntime) { return }
[string]$dotnetroot = "~/.dotnet" | %{ if (-not (Test-Path $_)) { mkdir $_ > $null }; Resolve-Path $_ }
Invoke-WebRequest "https://dotnetwebsite.azurewebsites.net/download/dotnet-core/scripts/v1/dotnet-install.ps1" -OutFile ~/dotnet-install.ps1
~/dotnet-install.ps1 -JsonFile src/global.json -InstallDir $dotnetroot
~/dotnet-install.ps1 -Channel 2.1 -Runtime dotnet -InstallDir $dotnetroot
Write-Output "::add-path::$dotnetroot"
Write-Output "::set-env name=DOTNET_ROOT::$dotnetroot"

- name: Setup .NET Core
uses: actions/setup-dotnet@v1

- run: dotnet --info
- name: Install and run nbgv
run: |
dotnet tool install --tool-path . nbgv
./nbgv get-version -p src

- run: dotnet tool restore

- name: Run nbgv
run: dotnet nbgv get-version -p src

- name: Restore
run: dotnet restore src -v normal

- name: Build
run: dotnet build src -t:build,pack --no-restore -m -c ${{ env.build_configuration }} -bl:obj/logs/build-windows.binlog
run: dotnet build src -t:build,pack --no-restore -m -bl:obj/logs/build-${{ matrix.os }}.binlog

- name: Test
run: dotnet test src --no-build -c ${{ env.build_configuration }}
run: dotnet test src --no-build

- name: Upload nugets
if: github.event_name == 'push'
if: github.event_name == 'push' && matrix.os == 'windows-latest'
uses: actions/upload-artifact@v1
with:
name: nugets
path: bin/Packages/${{ env.build_configuration }}
- name: Upload logs
uses: actions/upload-artifact@v1
with:
name: logs-windows
path: obj/logs/
path: bin/Packages/${{ env.Configuration }}

build-other:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- name: Install .NET Core
shell: pwsh
run: |
$info = dotnet --info
$isRuntime = $info -contains 'Microsoft.NETCore.App 2.1'
Push-Location src; dotnet --version > $null; Pop-Location; $isSdk = $LASTEXITCODE -eq 0; $LASTEXITCODE = 0
if ($isSdk -and $isRuntime) { return }
[string]$dotnetroot = "~/.dotnet" | %{ if (-not (Test-Path $_)) { mkdir $_ > $null }; Resolve-Path $_ }
Invoke-WebRequest "https://dotnetwebsite.azurewebsites.net/download/dotnet-core/scripts/v1/dotnet-install.sh" -OutFile ~/dotnet-install.sh
chmod +x ~/dotnet-install.sh
~/dotnet-install.sh -JsonFile src/global.json -InstallDir $dotnetroot
~/dotnet-install.sh -Channel 2.1 -Runtime dotnet -InstallDir $dotnetroot
Write-Output "::add-path::$dotnetroot"
Write-Output "::set-env name=DOTNET_ROOT::$dotnetroot"
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
- run: dotnet --info
- name: Install and run nbgv
run: |
dotnet tool install --tool-path . nbgv --version 3.0.28
./nbgv get-version -p src
- name: Restore
run: dotnet restore src -v normal
- name: Build
run: dotnet build src --no-restore -m -c ${{ env.build_configuration }} -bl:obj/logs/build-${{ matrix.os }}.binlog
- name: Test
run: dotnet test src --no-build -c ${{ env.build_configuration }} -f netcoreapp2.1
- name: Upload logs
uses: actions/upload-artifact@v1
with:
name: logs-${{ matrix.os }}
path: obj/logs/

- name: Build samples
shell: pwsh
run: samples/build.ps1
17 changes: 14 additions & 3 deletions CHANGELOG.md
Expand Up @@ -7,24 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

> ⚠ There are major changes, please look at [v0.7 migration guide].

### Added
* GeneratorInCustomerSolution sample in `samples` folder
* Various samples in `samples` folder
* GitHub Actions CI
* Support for plugin dependencies! 🎉 ([#156]).
* Plugins (generators) are now easier to build using `CodeGeneration.Roslyn.Plugin.Sdk` MSBuildSdk package ([#113]).

### Changed
* .NET Core SDK version bumped to `3.1.100` ([#178]).
* `Attributes` package now targets `net20;net40` in addition to `netstandard1.0` ([#178]).
* `dotnet-codegen` now has `RollForward=Major` policy to allow it to run on newer runtimes than 2.x,
* Tool now has `RollForward=Major` policy to allow it to run on newer runtimes than 2.x,
e.g. .NET Core SDK v3.x *only* should suffice for most usage scenarios ([#178]).
* MSBuild ItemGroup used for registration of plugin paths changed to `CodeGenerationRoslynPlugin`
(was `GeneratorAssemblySearchPaths`). A warning for using old one is introduced (`CGR1002`). ([#156])
* ItemGroup now should contain full path to generator dll (previously it was a containing folder path)
* Old behavior has a compat-plug and the paths are searched for any dll, and those found are added to new ItemGroup.
* Old behavior has a compat-plug for now and the paths are searched for any dll, and those found are added to new ItemGroup.
* When using P2P generator (same solution), a consuming project needs to add an attribute `OutputItemType="CodeGenerationRoslynPlugin"` to the `ProjectReference` of the generator project. See [v0.7 migration guide].
* `dotnet-codegen` package is now `CodeGeneration.Roslyn.Tool` and is build very differently;
also it includes build assets from `BuildTime` package ([#198]).

### Removed
* `CodeGeneration.Roslyn.BuildTime` package is now merged into `CodeGeneration.Roslyn.Tool`
(which is now the only package required to be referenced by generator consumers, aside from generators themselves) ([#198]).

[#113]: https://github.com/AArnott/CodeGeneration.Roslyn/issues/113
[#156]: https://github.com/AArnott/CodeGeneration.Roslyn/pull/156
[#178]: https://github.com/AArnott/CodeGeneration.Roslyn/pull/178
[#198]: https://github.com/AArnott/CodeGeneration.Roslyn/pull/198
[v0.7 migration guide]: https://github.com/AArnott/CodeGeneration.Roslyn/wiki/Migrations#v07


Expand Down
100 changes: 52 additions & 48 deletions README.md
Expand Up @@ -52,6 +52,7 @@ for it. That's because code generation runs *before* the consuming project is it
Now we'll use an [MSBuild project SDK] `CodeGeneration.Roslyn.Plugin.Sdk` to speed up configuring our generator plugin. Edit your project file and add the `<Sdk>` element:

```xml
<!-- Duplicator/Duplicator.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<!-- Add the following element above any others: -->
<Sdk Name="CodeGeneration.Roslyn.Plugin.Sdk" Version="{replace with actual version used}"/>
Expand Down Expand Up @@ -141,6 +142,7 @@ with a dependency on your code generation assembly.

We'll consume our generator in a Reflector app:
> `dotnet new console -f netcoreapp2.1 -o Reflector`
>
> `dotnet add Reflector reference Duplicator`

Let's write a simple program that prints all types in its assembly:
Expand Down Expand Up @@ -183,14 +185,15 @@ namespace Reflector

Right now `dotnet run -p Reflector` outputs:
> `Reflector.Program`
>
> `Reflector.Test`

Now all that's left is to plumb the build pipeline with code generation tool. You'll need to add the following two references to your Reflector project file:
* [`CodeGeneration.Roslyn.BuildTime`][BuildTimeNuPkg]
* [`dotnet-codegen`][ToolNuPkg]
Now all that's left is to plumb the build pipeline with code generation tool.
You'll need to add a reference to [`CodeGeneration.Roslyn.Tool`][ToolNuPkg] package:
> `dotnet add Reflector package CodeGeneration.Roslyn.Tool`

Also, you need to add the following metadata to your generator project reference:
`OutputItemType="CodeGenerationRoslynPlugin"`. This will add the path to the `Duplicator.dll` to the list of plugins the tool uses.
`OutputItemType="CodeGenerationRoslynPlugin"`. This will add the path to the `Duplicator.dll` to the list of plugins the tool runs.

This is how your project file can look like:

Expand All @@ -200,31 +203,30 @@ This is how your project file can look like:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<CodeGenerationRoslynVersion>{replace with actual version used}</CodeGenerationRoslynVersion>
</PropertyGroup>
<ItemGroup>
<!-- This ProjectReference to the generator need to have the OutputItemType metadata -->
<ProjectReference Include="..\Duplicator\Duplicator.csproj"
OutputItemType="CodeGenerationRoslynPlugin" />
<!-- This reference imports targets that run the dotnet-codegen tool during build. It contains only MSBuild targets and so can be marked with PrivateAssets="all" -->
<PackageReference Include="CodeGeneration.Roslyn.BuildTime"
Version="$(CodeGenerationRoslynVersion)"
<!--
This contains the generation tool and MSBuild targets that invoke it,
and so can be marked with PrivateAssets="all"
-->
<PackageReference Include="CodeGeneration.Roslyn.Tool"
Version="{replace with actual version used}"
PrivateAssets="all" />
<!-- This allows the build to invoke dotnet-codegen console tool that actually
performs code generation: compiles you source files, runs plugins and saves
results adding them to the list of Compile sources for the CoreCompile step. -->
<DotNetCliToolReference Include="dotnet-codegen"
Version="$(CodeGenerationRoslynVersion)" />
</ItemGroup>
</Project>
```

And if all steps were done correctly, `dotnet run -p Reflector` should print:
> `Reflector.Program`
>
> `Reflector.Test`
>
> `Reflector.TestPassed`

> Notice that there is a `TestPassed` type in the assembly now.
> 💡 Notice that there is a `TestPassed` type in the assembly now.

What's even better is that you should see that new type in IntelliSense as well!
Try executing Go to Definition (<kbd>F12</kbd>) on it - your IDE (VS/VS Code) should open the generated file for you (it'll be located in `IntermediateOutputPath` - most commonly `obj/`).
Expand Down Expand Up @@ -264,18 +266,27 @@ So, the consuming project will have a simple ProjectReference to the
`Duplicator.Attributes` project, and the generator project will not have
any attribute defined. With that done, we can move to the next section.

> 📋 Side note: if you use generator only in a single TFM-incompatible project,
> 📋 Side note: if there's only one consumer project for your generator,
> you can define the triggering attribute in the consuming project as well.
> In our case, this would bean moving the `DuplicateWithSuffixAttribute.cs`
> from `Duplicator` to `Reflector`, and adding a reference to the
> [`CodeGeneration.Roslyn.Attributes`][AttrNuPkg] in Reflector:
> > `dotnet add Reflector package CodeGeneration.Roslyn.Attributes`
> >
> > `mv Duplicator/DuplicateWithSuffixAttribute.cs Reflector`
>
> For simplicity, we'll assume this is the case in the following sections.

### Customize generator reference

With the attribute available to consuming code, we don't need a reference to
the generator project, right? Well, not quite. The magic OutputItemType metadata
is important - it adds a path to the generator dll to the list of plugins known
to the `dotnet-codegen` tool. Additionally, we want to specify that there's a build
dependency of the consuming project on the generator. So we modify the reference:
to the `CodeGeneration.Roslyn.Tool` tool. Additionally, we want to specify that there's a build dependency of the consuming project on the generator. So we modify
the reference:

```xml
<!-- Reflector/Reflector.csproj -->
<ItemGroup>
<ProjectReference Include="..\Duplicator\Duplicator.csproj"
ReferenceOutputAssembly="false"
Expand All @@ -293,17 +304,28 @@ We add two new metadata attributes:

#### Multitargeting generator

The case gets more complicated when the generator project is multitargeting,
e.g. it has
It can happen that your generator project will become multi-targeting. You could
need to do that to use C#8's Nullable Reference Types feature in the Duplicator;
the generator has to target `netcoreapp2.1` as this is the framework it'll be run
in by the `CG.R.Tool` - on the other hand, NRT feature is only supported in newer
TFMs, starting with `netcoreapp3.1`. So you'll do:
```xml
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0</TargetFrameworks>
<!-- Duplicator/Duplicator.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
<!-- ... -->
</Project>
```

You could want to do that to use C#8's Nullable Reference Types feature.

This won't work now, because the generator's Build target output will contain two references. To fix that you can use `SetTargetFramework` metadata:
There'll be a build error, because the consumer (Reflector) doesn't know which
output to use (and assign to the CodeGenerationRoslynPlugin Item). To fix that
we have to use `SetTargetFramework` metadata. Setting it implies
`SkipGetTargetFrameworkProperties="true"` so we can replace it.

```xml
<!-- Reflector/Reflector.csproj -->
<ItemGroup>
<ProjectReference Include="..\Duplicator\Duplicator.csproj"
ReferenceOutputAssembly="false"
Expand All @@ -312,54 +334,36 @@ This won't work now, because the generator's Build target output will contain tw
</ItemGroup>
```

Non-empty `SetTargetFramework` automatically implies `SkipGetTargetFrameworkProperties="true"` so we can omit that.

We also need to add a condition on `TargetFrameworks` element so that we skip
setting it at all when a singular property is set.

```xml
<TargetFrameworks Condition="'$(TargetFramework)' == ''">
netcoreapp2.1;
netcoreapp3.0
</TargetFrameworks>
```

### Package your code generator

You can also package up your code generator as a NuGet package for others to install
and use. A project using `CodeGeneration.Roslyn.Plugin.Sdk` is automatically
configured to produce a correct Plugin nuget package.

Your consumers will have to depend on the following:
- [`dotnet-codegen`][ToolNuPkg] tool
- [`CodeGeneration.Roslyn.BuildTime`][BuildTimeNuPkg]
- [`CodeGeneration.Roslyn.Tool`][ToolNuPkg] tool
- `Duplicator.Attributes` (your attributes package)
- `Duplicator` (your generator/plugin package)

An example consuming project file should contain:
```xml
<!-- Reflector/Reflector.csproj -->
<ItemGroup>
<PackageReference Include="Duplicator" Version="1.0.0" PrivateAssets="all" />
<PackageReference Include="Duplicator.Attributes" Version="1.0.0" PrivateAssets="all" />
<PackageReference Include="CodeGeneration.Roslyn.BuildTime"
Version="$(CodeGenerationRoslynVersion)"
<PackageReference Include="CodeGeneration.Roslyn.Tool"
Version="{CodeGeneration.Roslyn.Tool version}"
PrivateAssets="all" />
<DotNetCliToolReference Include="dotnet-codegen"
Version="$(CodeGenerationRoslynVersion)" />
</ItemGroup>
```

where `CodeGenerationRoslynVersion` is a correctly defined Property.

> 📋 You can also attempt to craft a self-contained package that will
> flow all the needed dependencies and assets into the consuming project,
> but the `DotNetCliToolReference` has to be in the consumer's project file
> no matter what. Just a friendly reminder. Also, such a scenario is definitely
> outside this project's scope.
> flow all the needed dependencies and assets into the consuming project.
> For a sample implementation, see [MetapackageSample](samples/MetapackageSample/).

[NuPkg]: https://nuget.org/packages/CodeGeneration.Roslyn
[BuildTimeNuPkg]: https://nuget.org/packages/CodeGeneration.Roslyn.BuildTime
[AttrNuPkg]: https://nuget.org/packages/CodeGeneration.Roslyn.Attributes
[ToolNuPkg]: https://nuget.org/packages/dotnet-codegen
[ToolNuPkg]: https://nuget.org/packages/CodeGeneration.Roslyn.Tool
[netstandard-table]: https://docs.microsoft.com/dotnet/standard/net-standard#net-implementation-support
[MSBuild project SDK]: https://docs.microsoft.com/visualstudio/msbuild/how-to-use-project-sdk
4 changes: 2 additions & 2 deletions azure-pipeline.yml
Expand Up @@ -33,8 +33,8 @@ jobs:
workingDirectory: src

- script: |
dotnet tool install --tool-path . nbgv --version 3.0.28
.\nbgv cloud -p src
dotnet tool restore
dotnet nbgv cloud -p src
displayName: Set build number
condition: ne(variables['system.pullrequest.isfork'], true)

Expand Down
1 change: 1 addition & 0 deletions samples/.gitignore
@@ -0,0 +1 @@
.nuget