Skip to content

Commit 302ef5a

Browse files
authored
Merge pull request #241 from ionide/run-analyzers-using-msbuild-fsc-args
Utilize Fsc args in msbuild analyze if available
2 parents da8ad10 + a4389a1 commit 302ef5a

File tree

4 files changed

+137
-2
lines changed

4 files changed

+137
-2
lines changed

docs/content/getting-started/MSBuild.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,41 @@ We often add a dummy target to a project to print out some values:
195195

196196
Run `dotnet msbuild YourProject.fsproj /t:Dump` and verify that `CodeRoot` has a value or not.
197197

198+
## Analyze FSharp Projects After Build
199+
200+
If you'd like the analyzer to be run after a `dotnet build`, you can set `RunAnalyzersDuringBuild` or `RunAnalyzers` to `true` in your project file:
201+
202+
```xml
203+
<PropertyGroup>
204+
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
205+
<FSharpAnalyzersOtherFlags>similar to previous section</FSharpAnalyzersOtherFlags>
206+
</PropertyGroup>
207+
```
208+
209+
This is reusing the [Roslyn Analyzers variables](https://learn.microsoft.com/en-us/visualstudio/code-quality/disable-code-analysis?view=vs-2022#net-framework-projects-1). For brevity, here are the relevant variables:
210+
211+
- `RunAnalyzersDuringBuild` : Controls whether analyzers run at build time.
212+
- `RunAnalyzers` : It takes precedence over `RunAnalyzersDuringBuild` and is used to control whether analyzers run at build time or not.
213+
214+
This will run after the `CoreCompile` [target](https://github.com/dotnet/fsharp/blob/dd929579fc275ab99fd496da34bbe6bdade73c86/src/FSharp.Build/Microsoft.FSharp.Targets#L279-L280), which is the default target for building F# projects. The benefit of running after the `CoreCompile` target is this will speed up the analyzers execution as it will attempt to re-use the F# Compiler command line args `FscCommandLineArgs` property to run the analyzers without requiring a [design-time build](https://github.com/dotnet/project-system/blob/main/docs/design-time-builds.md).
215+
216+
However, the `FSharpAnalyzerAfterBuild` target might be skipped if the `CoreCompile` target is not run due to [incremental builds](https://learn.microsoft.com/en-us/visualstudio/msbuild/incremental-builds?view=vs-2022). If you want to run the analyzers after every build, you can set `FSharpAnalyzersAlwaysRunAfterBuild` to `true`:
217+
218+
```xml
219+
<PropertyGroup>
220+
<FSharpAnalyzersAlwaysRunAfterBuild>true</FSharpAnalyzersAlwaysRunAfterBuild>
221+
</PropertyGroup>
222+
```
223+
224+
### Treating Warnings as Errors
225+
226+
You can use the standard [WarningsAsErrors](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/errors-warnings#warningsaserrors-and-warningsnotaserrors) MSBuild property to treat specific warnings as errors. For example, to treat all warnings from the `OptionAnalyzer` (OV001) as errors, you can add the following to your project file:
227+
228+
```xml
229+
<PropertyGroup>
230+
<WarningsAsErrors>OV001</WarningsAsErrors>
231+
</PropertyGroup>
232+
```
233+
234+
198235
[Next]({{fsdocs-next-page-link}})
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<Import Project="../../src/FSharp.Analyzers.Build/buildMultitargeting/FSharp.Analyzers.Build.targets" />
3+
4+
<PropertyGroup>
5+
<!-- Set this to true if you'd like the analyzer to run during a build -->
6+
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
7+
<!-- Additional flags to pass to the analyzer CLI -->
8+
<FSharpAnalyzersOtherFlags>--analyzers-path "../../artifacts/bin/OptionAnalyzer/debug" -v d</FSharpAnalyzersOtherFlags>
9+
<!-- Point to the dotnet tool or the FSharp.Analyzers.Cli.dll for debugging. -->
10+
<FSharpAnalyzersExe>../../artifacts/bin/FSharp.Analyzers.Cli/debug/FSharp.Analyzers.Cli.dll</FSharpAnalyzersExe>
11+
<!-- Uncomment to always run the analyzer after a build, even if CoreCompile did not run -->
12+
<!-- <FSharpAnalyzersAlwaysRunAfterBuild>true</FSharpAnalyzersAlwaysRunAfterBuild> -->
13+
<!-- Uncomment to Treat OV001 (OptionAnalyzer) warnings as errors -->
14+
<!-- <WarningsAsErrors>OV001</WarningsAsErrors> -->
15+
</PropertyGroup>
16+
17+
<PropertyGroup>
18+
<OutputType>Exe</OutputType>
19+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
20+
</PropertyGroup>
21+
22+
<ItemGroup>
23+
<Compile Include="Program.fs" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<ProjectReference Include="../OptionAnalyzer/OptionAnalyzer.fsproj" ReferenceOutputAssembly="false" />
28+
<ProjectReference Include="../../src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj" ReferenceOutputAssembly="false" />
29+
</ItemGroup>
30+
31+
</Project>

samples/MsBuildExample/Program.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// For more information see https://aka.ms/fsharp-console-apps
2+
3+
let value = Some 42
4+
5+
printfn "The value is: %d" value.Value // This will cause a warning from the OptionAnalyzer
Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,73 @@
11
<Project>
2+
3+
<PropertyGroup>
4+
<!-- Used for dotnet host discovery -->
5+
<FSharpAnalyzersExeHost Condition="'$(FSharpAnalyzersExeHost)' == ''">dotnet</FSharpAnalyzersExeHost>
6+
<!-- Points to the dotnet tool or the FSharp.Analyzers.Cli.dll for debugging -->
7+
<FSharpAnalyzersExe Condition="'$(FSharpAnalyzersExe)' == ''">fsharp-analyzers</FSharpAnalyzersExe>
8+
<!-- The build will continue even if the analyzer fails -->
9+
<FSharpAnalyzersContinueOnError Condition="'$(FSharpAnalyzersContinueOnError)' == ''">true</FSharpAnalyzersContinueOnError>
10+
<_FSharpAnalyzersProjectOptions>--project &quot;$(MSBuildProjectFile)&quot;</_FSharpAnalyzersProjectOptions>
11+
</PropertyGroup>
12+
213
<Target Name="_AnalyzeFSharpProject">
314
<Error Condition="$(FSharpAnalyzersOtherFlags) == ''" Text="A property FSharpAnalyzersOtherFlags should exists with all the analyzer cli arguments!" />
415
<Exec
5-
ContinueOnError="true"
16+
ContinueOnError="$(FSharpAnalyzersContinueOnError)"
617
IgnoreExitCode="true"
7-
Command="dotnet fsharp-analyzers --project &quot;$(MSBuildProjectFile)&quot; $(FSharpAnalyzersOtherFlags)" />
18+
Command="$(FSharpAnalyzersExeHost) $(FSharpAnalyzersExe) $(_FSharpAnalyzersProjectOptions) $(FSharpAnalyzersOtherFlags)" />
819
</Target>
920

1021
<Target Name="AnalyzeFSharpProject" DependsOnTargets="_AnalyzeFSharpProject" />
22+
23+
<Target Name="_FSharpAnalyzersDetermineRunAnalyzersValues" BeforeTargets="CoreCompile">
24+
<!--
25+
Roslyn uses multiple values for determining whether to run analyzers
26+
see: https://learn.microsoft.com/en-us/visualstudio/code-quality/disable-code-analysis?view=vs-2019#net-framework-projects-1
27+
28+
RunAnalyzersDuringBuild : Controls whether analyzers run at build time.
29+
RunAnalyzers: It takes precedence over RunAnalyzersDuringBuild
30+
-->
31+
<PropertyGroup>
32+
<_FSharpAnalyzersRunDuringBuild Condition="'$(RunAnalyzersDuringBuild)' != ''">$(RunAnalyzersDuringBuild)</_FSharpAnalyzersRunDuringBuild>
33+
<_FSharpAnalyzersRunDuringBuild Condition="'$(RunAnalyzers)' != ''">$(RunAnalyzers)</_FSharpAnalyzersRunDuringBuild>
34+
</PropertyGroup>
35+
</Target>
36+
37+
<Target
38+
Name="_SetupFSharpAnalyzerProjectOptions"
39+
DependsOnTargets="_FSharpAnalyzersDetermineRunAnalyzersValues"
40+
BeforeTargets="CoreCompile"
41+
Condition="'$(_FSharpAnalyzersRunDuringBuild)' == 'true'">
42+
<PropertyGroup>
43+
<!--
44+
Required for F# Targets CoreCompile to output command line arguments
45+
https://github.com/dotnet/fsharp/blob/53929f2e01281a614a15033dfaae6fb6d00bb543/src/FSharp.Build/Fsc.fs#L721-L725
46+
https://github.com/dotnet/fsharp/blob/53929f2e01281a614a15033dfaae6fb6d00bb543/src/FSharp.Build/Microsoft.FSharp.Targets#L418C19-L418C32
47+
-->
48+
<ProvideCommandLineArgs>true</ProvideCommandLineArgs>
49+
</PropertyGroup>
50+
</Target>
51+
52+
<Target
53+
Name="_FSharpAnalyzerAfterBuild"
54+
DependsOnTargets="_SetupFSharpAnalyzerProjectOptions;CoreCompile"
55+
AfterTargets="AfterBuild"
56+
Condition="'$(_FSharpAnalyzersRunDuringBuild)' == 'true'">
57+
<Error Condition="$(FSharpAnalyzersOtherFlags) == ''" Text="A property FSharpAnalyzersOtherFlags should exists with all the analyzer cli arguments!" />
58+
<PropertyGroup>
59+
<!--
60+
If FscCommandLineArgs is empty, then CoreCompile did not run, we'll try to skip running the analyzer if this wasn't explicitly set to true
61+
-->
62+
<FSharpAnalyzersAlwaysRunAfterBuild Condition="'$(FSharpAnalyzersAlwaysRunAfterBuild)' == '' and '@(FscCommandLineArgs->Count())' != '0'">true</FSharpAnalyzersAlwaysRunAfterBuild>
63+
<!-- We'll try to use FscCommandLineArgs- to speed up analyzer execution since this skips requiring a design-time build -->
64+
<_FSharpAnalyzersProjectOptions Condition="'@(FscCommandLineArgs->Count())' != '0'">--fsc-args &quot;@(FscCommandLineArgs)&quot;</_FSharpAnalyzersProjectOptions>
65+
</PropertyGroup>
66+
<Exec
67+
ContinueOnError="$(FSharpAnalyzersContinueOnError)"
68+
Condition="'$(FSharpAnalyzersAlwaysRunAfterBuild)' == 'true'"
69+
Command="$(FSharpAnalyzersExeHost) $(FSharpAnalyzersExe) $(_FSharpAnalyzersProjectOptions) $(FSharpAnalyzersOtherFlags)" />
70+
</Target>
71+
72+
<Target Name="FSharpAnalyzerAfterBuild" DependsOnTargets="_FSharpAnalyzerAfterBuild"/>
1173
</Project>

0 commit comments

Comments
 (0)