Skip to content

Commit 42d5a77

Browse files
committed
Recommend single Solution target
1 parent 4e004b4 commit 42d5a77

File tree

1 file changed

+35
-173
lines changed

1 file changed

+35
-173
lines changed

docs/content/getting-started/MSBuild.md

Lines changed: 35 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -6,193 +6,55 @@ index: 4
66

77
# MSBuild
88

9-
## Using Analyzer Build Target in a Project
9+
## Analyzer paths
1010

11-
The path to the analyzer DLL files could be tricky to get right across a wide range of setups. Luckily, we can use a MSBuild custom target to take care of the path construction. Add [FSharp.Analyzers.Build](https://www.nuget.org/packages/FSharp.Analyzers.Build) to your project. This imports a new target to your project file (`AnalyzeFSharpProject`) and will allow us to easily run the analyzers for our project.
11+
Finding the analyzer DLLs can be tricky across different environments. Luckily, MSBuild computes these paths after restore.
12+
We can add a solution-level target that invokes the analyzer CLI with the correct paths.
1213

13-
### Installing Target via Nuget
14+
## Solution targets
1415

15-
If you are using Nuget, add it to your `.fsproj` file:
16+
MSBuild targets defined in `Directory.Solution.targets` can be invoked with `dotnet msbuild /t:YourTargetName`. We can create a target that reads the generated NuGet `*.nuget.g.props` file to discover analyzer package paths and collect all the projects we wish to analyze:
1617

17-
```xml
18-
<PackageReference Include="FSharp.Analyzers.Build" Version="0.2.0">
19-
<PrivateAssets>all</PrivateAssets>
20-
<IncludeAssets>build</IncludeAssets>
21-
</PackageReference>
22-
```
23-
24-
### Installing Target via Paket
25-
26-
If you are using Paket, add it to your `paket.dependencies`
27-
28-
```paket
29-
group analyzers
30-
source https://api.nuget.org/v3/index.json
31-
32-
nuget FSharp.Analyzers.Build
33-
```
34-
35-
as well as the `paket.references` of your project:
36-
37-
```paket
38-
group analyzers
39-
FSharp.Analyzers.Build
40-
```
41-
42-
### Configuring the Build Target
43-
44-
Before we can run `dotnet msbuild /t:AnalyzeFSharpProject`, we need to specify our settings in a property called `FSharpAnalyzersOtherFlags`:
45-
46-
```xml
47-
<PropertyGroup>
48-
<FSharpAnalyzersOtherFlags>--analyzers-path &quot;$(PkgG-Research_FSharp_Analyzers)/analyzers/dotnet/fs&quot; --report &quot;$(MSBuildProjectName)-$(TargetFramework).sarif&quot; --treat-as-warning IONIDE-004 --verbosity d</FSharpAnalyzersOtherFlags>
49-
</PropertyGroup>
50-
```
51-
52-
To locate the analyzer DLLs in the filesystem, we use the variable `$(PkgG-Research_FSharp_Analyzers)`. It's produced by NuGet and normalized to be usable by [MSBuild](https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#generatepathproperty).
53-
In general, a `Pkg` prefix is added and dots in the package ID are replaced by underscores. But make sure to look at the [nuget.g.props](https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#restore-outputs) file in the `obj` folder for the exact string.
54-
55-
The `/analyzers/dotnet/fs` subpath is a convention analyzer authors should follow when creating their packages.
56-
57-
### Running the Build Target
58-
59-
At last, you can run the analyzer from the project folder:
60-
61-
```shell
62-
dotnet msbuild /t:AnalyzeFSharpProject
63-
```
64-
65-
📓 Note: If your project has multiple `TargetFrameworks` the tool will be invoked for each target framework.
66-
67-
## Using Analyzer Build Target in a Solution
68-
69-
Adding the custom target from above to all `.fsproj` files of a solution doesn't scale very well. We can use the MSBuild infrastructure to add the needed package reference and the MSBuild target to all projects in one go.
70-
71-
### Setting up Directory.Build.props
72-
73-
We start with adding the `PackageReference` pointing to your favorite analyzers to the [Directory.Build.props](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022) file.
74-
This adds the package reference to all `.fsproj` files that are in a subfolder of the file location of `Directory.Build.props`:
75-
76-
```xml
77-
<ItemGroup>
78-
<PackageReference Include="FSharp.Analyzers.Build" Version="0.2.0">
79-
<PrivateAssets>all</PrivateAssets>
80-
<IncludeAssets>build</IncludeAssets>
81-
</PackageReference>
82-
<PackageReference Include="G-Research.FSharp.Analyzers" Version="0.1.6">
83-
<PrivateAssets>all</PrivateAssets>
84-
<IncludeAssets>analyzers</IncludeAssets>
85-
</PackageReference>
86-
</ItemGroup>
87-
```
88-
89-
### Setting up Directory.Build.targets
90-
91-
Likewise we add the `FSharpAnalyzersOtherFlags` property to the [Directory.Build.targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022) file. For first time MSBuild users, this is effectively the same as adding a property to each `*proj` file which exists in a subfolder.
18+
_Directory.Solution.targets_
9219

9320
```xml
9421
<Project>
95-
<PropertyGroup>
96-
<SarifOutput Condition="$(SarifOutput) == ''">./</SarifOutput>
97-
<CodeRoot Condition="$(CodeRoot) == ''">.</CodeRoot>
98-
<FSharpAnalyzersOtherFlags>--analyzers-path &quot;$(PkgG-Research_FSharp_Analyzers)/analyzers/dotnet/fs&quot; --report &quot;$(SarifOutput)$(MSBuildProjectName)-$(TargetFramework).sarif&quot; --code-root $(CodeRoot) --treat-as-warning IONIDE-004 --verbosity d</FSharpAnalyzersOtherFlags>
99-
</PropertyGroup>
22+
<!-- Import the NuGet props file to get access to Pkg* variables -->
23+
<Import Project="artifacts/obj/Telplin/Telplin.fsproj.nuget.g.props" Condition="Exists('artifacts/obj/Telplin/Telplin.fsproj.nuget.g.props')"/>
24+
<ItemGroup>
25+
<ProjectsToAnalyze Include="src/**/*.fsproj"/>
26+
</ItemGroup>
27+
<Target Name="AnalyzeSolution" Condition="Exists('artifacts/obj/Telplin/Telplin.fsproj.nuget.g.props')">
28+
<PropertyGroup>
29+
<CodeRoot>$(SolutionDir)</CodeRoot>
30+
</PropertyGroup>
31+
<PropertyGroup>
32+
<FSharpAnalyzersOtherFlags>--analyzers-path &quot;$(PkgG-Research_FSharp_Analyzers)/analyzers/dotnet/fs&quot;</FSharpAnalyzersOtherFlags>
33+
<FSharpAnalyzersOtherFlags>$(FSharpAnalyzersOtherFlags) --analyzers-path &quot;$(PkgIonide_Analyzers)/analyzers/dotnet/fs&quot;</FSharpAnalyzersOtherFlags>
34+
<FSharpAnalyzersOtherFlags>$(FSharpAnalyzersOtherFlags) --configuration $(Configuration)</FSharpAnalyzersOtherFlags>
35+
<FSharpAnalyzersOtherFlags>$(FSharpAnalyzersOtherFlags) --verbosity d</FSharpAnalyzersOtherFlags>
36+
<FSharpAnalyzersOtherFlags>$(FSharpAnalyzersOtherFlags) --code-root $(CodeRoot)</FSharpAnalyzersOtherFlags>
37+
<FSharpAnalyzersOtherFlags>$(FSharpAnalyzersOtherFlags) --report &quot;$(CodeRoot)/analysis.sarif&quot;</FSharpAnalyzersOtherFlags>
38+
</PropertyGroup>
39+
<Delete Files="$(SolutionDir)/analysis.sarif" Condition="Exists('$(SolutionDir)/analysis.sarif')"/>
40+
<!-- Execute fsharp-analyzers with all projects in a single process -->
41+
<Exec Command="dotnet fsharp-analyzers $(FSharpAnalyzersOtherFlags) @(ProjectsToAnalyze->'--project &quot;%(FullPath)&quot;', ' ')"
42+
ContinueOnError="true"/>
43+
</Target>
10044
</Project>
10145
```
10246

103-
⚠️ We are adding the `FSharpAnalyzersOtherFlags` property to our **Directory.Build.targets** and **not to** any **Directory.Build.props** file! MSBuild will first evaluate `Directory.Build.props` which has no access to the generated nuget.g.props. `$(PkgG-Research_FSharp_Analyzers)` won't be known at this point. `Directory.Build.targets` is evaluated after the project file and has access to `Pkg` generated properties.
104-
105-
### Run Target for All Projects in the Solution
106-
107-
We can run the `AnalyzeFSharpProject` target against all projects in a solution
108-
109-
```shell
110-
dotnet msbuild YourSolution.sln /t:AnalyzeFSharpProject
111-
```
112-
113-
### Configuring Specific Projects to Run
114-
115-
As we may not always want to target every project in a solution, we can create a second custom MSBuild target that calls the project-specific target for all relevant projects.
116-
Add the following custom target to the [Directory.Solution.targets](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-solution-build?view=vs-2022) file to be able to invoke analysis from all selected projects in one simple command:
117-
118-
```xml
119-
<Project>
120-
<ItemGroup>
121-
<ProjectsToAnalyze Include="src/**/*.fsproj" />
122-
</ItemGroup>
123-
124-
<Target Name="AnalyzeSolution">
125-
<MSBuild Projects="@(ProjectsToAnalyze)" Targets="AnalyzeFSharpProject" />
126-
</Target>
127-
</Project>
128-
```
129-
130-
You can also exclude certain projects from the analysis if they fall within the same pattern
131-
132-
```xml
133-
<Project>
134-
<ItemGroup>
135-
<ProjectsToAnalyze Include="src/**/*.fsproj" Exclude="src/**/Special.fsproj" />
136-
</ItemGroup>
137-
138-
<Target Name="AnalyzeSolution">
139-
<MSBuild Projects="@(ProjectsToAnalyze)" Targets="AnalyzeFSharpProject" />
140-
</Target>
141-
</Project>
142-
```
143-
144-
### Running the Solution Target
145-
146-
At last, you can run the analyzer from the solution folder:
47+
Run it with
14748

14849
```shell
14950
dotnet msbuild /t:AnalyzeSolution
15051
```
15152

152-
Note: we passed the `--code-root` flag so that the `*.sarif` report files will report file paths relative to this root. This can be imported for certain editors to function properly.
153-
154-
## MSBuild Tips and Tricks
155-
156-
MSBuild can be overwhelming for the uninitiated. Here are some tricks we've seen in the wild:
157-
158-
### Use Well-Known Properties
159-
160-
Checkout the [MSBuild reserved and well-known properties](https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-reserved-and-well-known-properties?view=vs-2022) to use existing variables like `$(MSBuildProjectFile)`.
161-
162-
### Wrap Path Arguments in Quotes
163-
164-
As MSBuild is all XML, you can use `&quot;` to wrap evaluated values in quotes:
165-
166-
```xml
167-
<PropertyGroup>
168-
<WithQuotes>&quot;$(SolutionDir)&quot;</WithQuotes>
169-
</PropertyGroup>
170-
```
171-
172-
### Extend `<FSharpAnalyzersOtherFlags>` in Multiple Lines
173-
174-
You can extend the value of `$(FSharpAnalyzersOtherFlags)` by setting it again in multiple lines:
175-
176-
```xml
177-
<PropertyGroup>
178-
<FSharpAnalyzersOtherFlags>--analyzers-path &quot;$(PkgG-Research_FSharp_Analyzers)/analyzers/dotnet/fs&quot;</FSharpAnalyzersOtherFlags>
179-
<FSharpAnalyzersOtherFlags>$(FSharpAnalyzersOtherFlags) --analyzers-path &quot;$(PkgIonide_Analyzers)/analyzers/dotnet/fs&quot;</FSharpAnalyzersOtherFlags>
180-
<FSharpAnalyzersOtherFlags>$(FSharpAnalyzersOtherFlags) --configuration $(Configuration)</FSharpAnalyzersOtherFlags>
181-
<FSharpAnalyzersOtherFlags>$(FSharpAnalyzersOtherFlags) --exclude-analyzers PartialAppAnalyzer</FSharpAnalyzersOtherFlags>
182-
</PropertyGroup>
183-
```
184-
185-
### Verify Parameters are Present
186-
187-
It can be a bit confusing to find out if a variable contains the value you think it does.
188-
We often add a dummy target to a project to print out some values:
189-
190-
```xml
191-
<Target Name="Dump">
192-
<Message Importance="high" Text="$(CodeRoot)" />
193-
</Target>
194-
```
53+
Notes:
19554

196-
Run `dotnet msbuild YourProject.fsproj /t:Dump` and verify that `CodeRoot` has a value or not.
55+
- `artifacts/obj/Telplin/Telplin.fsproj.nuget.g.props` is generated after `dotnet restore` runs.
56+
The target depends on it, hence the `Condition="Exists('...nuget.g.props')"` check.
57+
- `--code-root` is important for certain tooling to have the correct links from the problems found to your source code.
58+
- `$(PkgG-Research_FSharp_Analyzers)` becomes available when your solution references the [G-Research.FSharp.Analyzers](https://www.nuget.org/packages/G-Research.FSharp.Analyzers) package. Expect a similar `$(Pkg...)` property for whichever analyzer package your project uses.
19759

198-
[Next]({{fsdocs-next-page-link}})
60+
[Next]({{fsdocs-next-page-link}})

0 commit comments

Comments
 (0)