Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/content/getting-started/MSBuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,41 @@ We often add a dummy target to a project to print out some values:

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

## Analyze FSharp Projects After Build

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:

```xml
<PropertyGroup>
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<FSharpAnalyzersOtherFlags>similar to previous section</FSharpAnalyzersOtherFlags>
</PropertyGroup>
```

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:

- `RunAnalyzersDuringBuild` : Controls whether analyzers run at build time.
- `RunAnalyzers` : It takes precedence over `RunAnalyzersDuringBuild` and is used to control whether analyzers run at build time or not.

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).

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`:

```xml
<PropertyGroup>
<FSharpAnalyzersAlwaysRunAfterBuild>true</FSharpAnalyzersAlwaysRunAfterBuild>
</PropertyGroup>
```

### Treating Warnings as Errors

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:

```xml
<PropertyGroup>
<WarningsAsErrors>OV001</WarningsAsErrors>
</PropertyGroup>
```


[Next]({{fsdocs-next-page-link}})
31 changes: 31 additions & 0 deletions samples/MsBuildExample/MsBuildExample.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../src/FSharp.Analyzers.Build/buildMultitargeting/FSharp.Analyzers.Build.targets" />

<PropertyGroup>
<!-- Set this to true if you'd like the analyzer to run during a build -->
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<!-- Additional flags to pass to the analyzer CLI -->
<FSharpAnalyzersOtherFlags>--analyzers-path "../../artifacts/bin/OptionAnalyzer/debug" -v d</FSharpAnalyzersOtherFlags>
<!-- Point to the dotnet tool or the FSharp.Analyzers.Cli.dll for debugging. -->
<FSharpAnalyzersExe>../../artifacts/bin/FSharp.Analyzers.Cli/debug/FSharp.Analyzers.Cli.dll</FSharpAnalyzersExe>
<!-- Uncomment to always run the analyzer after a build, even if CoreCompile did not run -->
<!-- <FSharpAnalyzersAlwaysRunAfterBuild>true</FSharpAnalyzersAlwaysRunAfterBuild> -->
<!-- Uncomment to Treat OV001 (OptionAnalyzer) warnings as errors -->
<!-- <WarningsAsErrors>OV001</WarningsAsErrors> -->
</PropertyGroup>

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../OptionAnalyzer/OptionAnalyzer.fsproj" ReferenceOutputAssembly="false" />
<ProjectReference Include="../../src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
5 changes: 5 additions & 0 deletions samples/MsBuildExample/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// For more information see https://aka.ms/fsharp-console-apps

let value = Some 42

printfn "The value is: %d" value.Value // This will cause a warning from the OptionAnalyzer
66 changes: 64 additions & 2 deletions src/FSharp.Analyzers.Build/build/FSharp.Analyzers.Build.targets
Original file line number Diff line number Diff line change
@@ -1,11 +1,73 @@
<Project>

<PropertyGroup>
<!-- Used for dotnet host discovery -->
<FSharpAnalyzersExeHost Condition="'$(FSharpAnalyzersExeHost)' == ''">dotnet</FSharpAnalyzersExeHost>
<!-- Points to the dotnet tool or the FSharp.Analyzers.Cli.dll for debugging -->
<FSharpAnalyzersExe Condition="'$(FSharpAnalyzersExe)' == ''">fsharp-analyzers</FSharpAnalyzersExe>
<!-- The build will continue even if the analyzer fails -->
<FSharpAnalyzersContinueOnError Condition="'$(FSharpAnalyzersContinueOnError)' == ''">true</FSharpAnalyzersContinueOnError>
<_FSharpAnalyzersProjectOptions>--project &quot;$(MSBuildProjectFile)&quot;</_FSharpAnalyzersProjectOptions>
</PropertyGroup>

<Target Name="_AnalyzeFSharpProject">
<Error Condition="$(FSharpAnalyzersOtherFlags) == ''" Text="A property FSharpAnalyzersOtherFlags should exists with all the analyzer cli arguments!" />
<Exec
ContinueOnError="true"
ContinueOnError="$(FSharpAnalyzersContinueOnError)"
IgnoreExitCode="true"
Command="dotnet fsharp-analyzers --project &quot;$(MSBuildProjectFile)&quot; $(FSharpAnalyzersOtherFlags)" />
Command="$(FSharpAnalyzersExeHost) $(FSharpAnalyzersExe) $(_FSharpAnalyzersProjectOptions) $(FSharpAnalyzersOtherFlags)" />
</Target>

<Target Name="AnalyzeFSharpProject" DependsOnTargets="_AnalyzeFSharpProject" />

<Target Name="_FSharpAnalyzersDetermineRunAnalyzersValues" BeforeTargets="CoreCompile">
<!--
Roslyn uses multiple values for determining whether to run analyzers
see: https://learn.microsoft.com/en-us/visualstudio/code-quality/disable-code-analysis?view=vs-2019#net-framework-projects-1

RunAnalyzersDuringBuild : Controls whether analyzers run at build time.
RunAnalyzers: It takes precedence over RunAnalyzersDuringBuild
-->
<PropertyGroup>
<_FSharpAnalyzersRunDuringBuild Condition="'$(RunAnalyzersDuringBuild)' != ''">$(RunAnalyzersDuringBuild)</_FSharpAnalyzersRunDuringBuild>
<_FSharpAnalyzersRunDuringBuild Condition="'$(RunAnalyzers)' != ''">$(RunAnalyzers)</_FSharpAnalyzersRunDuringBuild>
</PropertyGroup>
</Target>

<Target
Name="_SetupFSharpAnalyzerProjectOptions"
DependsOnTargets="_FSharpAnalyzersDetermineRunAnalyzersValues"
BeforeTargets="CoreCompile"
Condition="'$(_FSharpAnalyzersRunDuringBuild)' == 'true'">
<PropertyGroup>
<!--
Required for F# Targets CoreCompile to output command line arguments
https://github.com/dotnet/fsharp/blob/53929f2e01281a614a15033dfaae6fb6d00bb543/src/FSharp.Build/Fsc.fs#L721-L725
https://github.com/dotnet/fsharp/blob/53929f2e01281a614a15033dfaae6fb6d00bb543/src/FSharp.Build/Microsoft.FSharp.Targets#L418C19-L418C32
-->
<ProvideCommandLineArgs>true</ProvideCommandLineArgs>
</PropertyGroup>
</Target>

<Target
Name="_FSharpAnalyzerAfterBuild"
DependsOnTargets="_SetupFSharpAnalyzerProjectOptions;CoreCompile"
AfterTargets="AfterBuild"
Condition="'$(_FSharpAnalyzersRunDuringBuild)' == 'true'">
<Error Condition="$(FSharpAnalyzersOtherFlags) == ''" Text="A property FSharpAnalyzersOtherFlags should exists with all the analyzer cli arguments!" />
<PropertyGroup>
<!--
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
-->
<FSharpAnalyzersAlwaysRunAfterBuild Condition="'$(FSharpAnalyzersAlwaysRunAfterBuild)' == '' and '@(FscCommandLineArgs->Count())' != '0'">true</FSharpAnalyzersAlwaysRunAfterBuild>
<!-- We'll try to use FscCommandLineArgs- to speed up analyzer execution since this skips requiring a design-time build -->
<_FSharpAnalyzersProjectOptions Condition="'@(FscCommandLineArgs->Count())' != '0'">--fsc-args &quot;@(FscCommandLineArgs)&quot;</_FSharpAnalyzersProjectOptions>
</PropertyGroup>
<Exec
ContinueOnError="$(FSharpAnalyzersContinueOnError)"
Condition="'$(FSharpAnalyzersAlwaysRunAfterBuild)' == 'true'"
Command="$(FSharpAnalyzersExeHost) $(FSharpAnalyzersExe) $(_FSharpAnalyzersProjectOptions) $(FSharpAnalyzersOtherFlags)" />
</Target>

<Target Name="FSharpAnalyzerAfterBuild" DependsOnTargets="_FSharpAnalyzerAfterBuild"/>
</Project>