Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
26 changes: 26 additions & 0 deletions docs/content/getting-started/MSBuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,30 @@ 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 ran after a 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, this target might be skipped if the project is not built again 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 `FSharpAnalyzers_AlwaysRunAfterBuild` to `true`:

```xml
<PropertyGroup>
<FSharpAnalyzers_AlwaysRunAfterBuild>true</FSharpAnalyzers_AlwaysRunAfterBuild>
</PropertyGroup>
```

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

<Import Project="../../src/FSharp.Analyzers.Build/buildMultitargeting/FSharp.Analyzers.Build.targets" />


<PropertyGroup>
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<FSharpAnalyzersOtherFlags>--analyzers-path "../../artifacts/bin/OptionAnalyzer/debug" -v d</FSharpAnalyzersOtherFlags>
<FSharpAnalyzers_Exe>../../artifacts/bin/FSharp.Analyzers.Cli/debug/FSharp.Analyzers.Cli.dll</FSharpAnalyzers_Exe>
<!-- <FSharpAnalyzers_AlwaysRunAfterBuild>true</FSharpAnalyzers_AlwaysRunAfterBuild> -->
</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 -->
<FSharpAnalyzers_ExeHost Condition="'$(FSharpAnalyzers_ExeHost)' == ''">dotnet</FSharpAnalyzers_ExeHost>
<!-- Points to the dotnet tool or the FSharp.Analyzers.Cli.dll for debugging -->
<FSharpAnalyzers_Exe Condition="'$(FSharpAnalyzers_Exe)' == ''">fsharp-analyzers</FSharpAnalyzers_Exe>
<!-- The build will continue even if the analyzer fails -->
<FSharpAnalyzers_ContinueOnError Condition="'$(FSharpAnalyzers_ContinueOnError)' == ''">true</FSharpAnalyzers_ContinueOnError>
<_FSharpAnalyzers_ProjectOptions>--project &quot;$(MSBuildProjectFile)&quot;</_FSharpAnalyzers_ProjectOptions>
</PropertyGroup>

<Target Name="_AnalyzeFSharpProject">
<Error Condition="$(FSharpAnalyzersOtherFlags) == ''" Text="A property FSharpAnalyzersOtherFlags should exists with all the analyzer cli arguments!" />
<Exec
ContinueOnError="true"
ContinueOnError="$(FSharpAnalyzers_ContinueOnError)"
IgnoreExitCode="true"
Command="dotnet fsharp-analyzers --project &quot;$(MSBuildProjectFile)&quot; $(FSharpAnalyzersOtherFlags)" />
Command="$(FSharpAnalyzers_ExeHost) $(FSharpAnalyzers_Exe) $(_FSharpAnalyzers_ProjectOptions) $(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>
<_FSharpAnalyzers_RunDuringBuild Condition="'$(RunAnalyzersDuringBuild)' != ''">$(RunAnalyzersDuringBuild)</_FSharpAnalyzers_RunDuringBuild>
<_FSharpAnalyzers_RunDuringBuild Condition="'$(RunAnalyzers)' != ''">$(RunAnalyzers)</_FSharpAnalyzers_RunDuringBuild>
</PropertyGroup>
</Target>

<Target
Name="_SetupFSharpAnalyzerProjectOptions"
DependsOnTargets="_FSharpAnalyzersDetermineRunAnalyzersValues"
BeforeTargets="CoreCompile"
Condition="'$(_FSharpAnalyzers_RunDuringBuild)' == '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="'$(_FSharpAnalyzers_RunDuringBuild)' == '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
-->
<FSharpAnalyzers_AlwaysRunAfterBuild Condition="'$(FSharpAnalyzers_AlwaysRunAfterBuild)' == '' and '@(FscCommandLineArgs->Count())' != '0'">true</FSharpAnalyzers_AlwaysRunAfterBuild>
<!-- We'll try to use FscCommandLineArgs- to speed up analyzer execution since this skips requiring a design-time build -->
<_FSharpAnalyzers_ProjectOptions Condition="'@(FscCommandLineArgs->Count())' != '0'">--fsc-args &quot;@(FscCommandLineArgs)&quot;</_FSharpAnalyzers_ProjectOptions>
</PropertyGroup>
<Exec
ContinueOnError="$(FSharpAnalyzers_ContinueOnError)"
Condition="'$(FSharpAnalyzers_AlwaysRunAfterBuild)' == 'true'"
Command="$(FSharpAnalyzers_ExeHost) $(FSharpAnalyzers_Exe) $(_FSharpAnalyzers_ProjectOptions) $(FSharpAnalyzersOtherFlags)" />
</Target>

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