<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!--
    This file contains targets to integrate SonarQube and MSBuild.

    The targets will produce an output folder structure containing information required by the sonar-scanner.

    A subdirectory will be created for each MSBuild project. Information about the project
    will be written to a file called "ProjectInfo.xml". Additional files may also be written
    containing information required by different analyzers. In particular, a file will be written
    containing the list of files in the project that should be analyzed by the sonar-scanner.

    The output will only be produced if the property $(SonarQubeTempPath) and is specified.
    $(SonarQubeTempPath) is the root folder under which per-analysis output should be written.

    Excluding projects and files from SonarQube analysis
    ****************************************************
    Individual projects can excluded from SonarQube analysis by setting the property $(SonarQubeExclude) e.g.

      <SonarQubeExclude>true</SonarQubeExclude>

    Individual files can be excluded from analysis by setting the <SonarQubeExclude> metadata item e.g.

      <Compile Include="Logger.cs">
        <SonarQubeExclude>true</SonarQubeExclude>
      </Compile>

    Project is also excluded if it's recognized as a Fakes or a Temp project. These projects are auto-generated.

    Test projects
    *************
    The analysis performed by SonarQube varies depending on whether a project is a test project or not.

    A project can be explicitly marked as being a test or product project by setting the property $(SonarQubeTestProject) e.g.

      <SonarQubeTestProject>true</SonarQubeTestProject>

    If $(SonarQubeTestProject) is not set explicitly then the project is categorized as a Test project if:
    * Is a Fakes or a Temp project explicitly marked as SonarQubeExclude=false
    * Has one of the expected test project GUIDs ("3AC096D0-A1C2-E12C-1390-A8335801FDAB" or legacy "82A7F48D-3B50-4B1E-B82E-3ADA8210C358").
    * Has a TestContainer project capability.
    * References a known unit test related assembly.
    * Deprecated: Project name matches "sonar.msbuild.testProjectPattern" parameter. Use SonarQubeTestProject=true instead.

    Otherwise it is a Product project.

    Supplying analysis setting in a project file
    ********************************************
    Project-level analysis settings can be specified in a project file using following
    syntax:

    <ItemGroup>
      <SonarQubeSetting Include="sonar.my.analysis.setting">
        <Value>42</Value>
      </SonarQubeSetting>
    </ItemGroup>

    Settings specified in the project file will override those specified on the SonarQube server.

    Specifying additional types of file to be analysed
    **************************************************

    The Scanner for MSBuild will automatically collect all of the common
    file types that can be analysed by the various Sonar language plugins
    (e.g. C# files, TypeScript, TSQL, C++ include and source files etc).
    The full list of item types that will be included for analysis is
    specified in $(SQAnalysisFileItemTypes) property below.

    If you have any additional item types that you want to have analyzed,
    you can specify them setting the property $(SQAdditionalAnalysisFileItemTypes)
    in the project being analysed e.g.

    <SQAdditionalAnalysisFileItemTypes>MyCustomFileType1;MyCustomFileType</SQAdditionalAnalysisFileItemTypes>

    Specific handling of Razor builds
    **************************************************
    During the compilation of an SDK-style MSBuild project with Razor, MSBuild executes 2 build targets:
    CoreCompile to compile the main code and RazorCoreCompile to compile ".cshtml" view files.
    Since it is not possible to execute one target more than once, there's a logic that prevents RazorCoreCompile
    from overriding output files from CoreCompile step:
      * CoreCompile produces output files in directory "X", where X represents project specific unique number.
      * Directory "X" with CoreCompile results is renamed to "X.tmp" before RazorCoreCompile and new empty directory "X" is prepared.
      * RazorCoreCompile produces its output to the current directory "X".
      * Directory "X" with Razor output is renamed to "X.Razor".
      * Directory "X.tmp" with original output is renamed back to "X".
-->

  <!-- **************************************************************************** -->
  <!-- SonarQube MSBuild Integration implementation logic -->
  <!-- **************************************************************************** -->
  <!-- Note: where possible, these targets avoid taking dependencies on specific
        managed or C++- targets e.g. they use
           "AfterTargets='Build'" which is general rather than
           "AfterTargets='CoreCompile'" which is specific to managed projects.
  -->

  <!-- Safeguard against importing this .targets file multiple times -->
  <PropertyGroup>
    <SonarQubeTargetsImported>true</SonarQubeTargetsImported>
  </PropertyGroup>

  <!-- Set defaults if explicit values have not been provided -->
  <PropertyGroup Condition=" $(SonarQubeTempPath) != '' ">
    <SonarQubeConfigPath Condition=" $(SonarQubeConfigPath) == '' ">$(SonarQubeTempPath)\conf</SonarQubeConfigPath>
    <SonarQubeOutputPath Condition=" $(SonarQubeOutputPath) == '' ">$(SonarQubeTempPath)\out</SonarQubeOutputPath>

    <!-- Specify the ItemGroups to be analyzed -->
    <SQAnalysisFileItemTypes Condition=" $(SQAnalysisFileItemTypes) == '' ">AndroidEnvironment;AndroidJavaSource;AndroidResource;ApplicationDefinition;Build;ClCompile;ClInclude;Compile;Content;DeploymentExtensionConfiguration;EmbeddedResource;EntityDeploy;None;Page;PostDeploy;PRIResource;PreDeploy;RefactorLog;Resource;Script;ScriptCode;TypeScriptCompile;$(SQAdditionalAnalysisFileItemTypes)</SQAnalysisFileItemTypes>
  </PropertyGroup>

  <!-- **************************************************************************** -->
  <!-- Using tasks -->
  <!-- **************************************************************************** -->
  <PropertyGroup Condition=" $(SonarQubeTempPath) != '' AND $(SonarQubeBuildTasksAssemblyFile) == '' ">
    <!-- Assume that the tasks assembly is in the same location as this targets file
         or in a parent directory unless another location has already been specified. -->
    <SonarQubeBuildTasksAssemblyFile>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), SonarScanner.MSBuild.Tasks.dll))\SonarScanner.MSBuild.Tasks.dll</SonarQubeBuildTasksAssemblyFile>
  </PropertyGroup>

  <!-- Unescape the paths to work around the issue "MSBuild 4.0 UsingTask cannot have a path with parentheses".
       See https://connect.microsoft.com/VisualStudio/feedback/details/713690/msbuild-4-0-usingtask-cannot-have-a-path-with-parentheses -->
  <UsingTask TaskName="WriteProjectConfigFile" AssemblyFile="$([MSBUILD]::Unescape($(SonarQubeBuildTasksAssemblyFile)))" />
  <UsingTask TaskName="WriteProjectInfoFile" AssemblyFile="$([MSBUILD]::Unescape($(SonarQubeBuildTasksAssemblyFile)))" />
  <UsingTask TaskName="IsTestFileByName" AssemblyFile="$([MSBUILD]::Unescape($(SonarQubeBuildTasksAssemblyFile)))" />
  <UsingTask TaskName="IsTestByReference" AssemblyFile="$([MSBUILD]::Unescape($(SonarQubeBuildTasksAssemblyFile)))" />
  <UsingTask TaskName="GetAnalyzerSettings" AssemblyFile="$([MSBUILD]::Unescape($(SonarQubeBuildTasksAssemblyFile)))" />
  <UsingTask TaskName="MakeUniqueDir" AssemblyFile="$([MSBUILD]::Unescape($(SonarQubeBuildTasksAssemblyFile)))" />
  <UsingTask TaskName="WriteZeroLengthFiles" AssemblyFile="$([MSBUILD]::Unescape($(SonarQubeBuildTasksAssemblyFile)))" />

  <!-- **************************************************************************** -->
  <!-- **************************************************************************** -->
  <!-- Targets -->
  <!-- **************************************************************************** -->
  <!-- **************************************************************************** -->

  <Target Name="SkippingSonarQubeAnalysis" BeforeTargets="Build"
          Condition=" $(SonarQubeTempPath) == '' ">
    <Message Importance="high" Text="Sonar: ($(MSBuildProjectFile)) Skipping dumping compile outputs because SonarQubeTempPath has not been specified" />
  </Target>

  <Target Name="FailIfLowerThanMSBuild14"
          Condition=" $(SonarQubeTempPath) != '' AND $(MSBuildToolsVersion) != '14.0' AND $(MSBuildToolsVersion) != '15.0' AND $(MSBuildToolsVersion) != 'Current'"
          BeforeTargets="Build">
    <Message Importance="high" Text="Sonar: ($(MSBuildProjectFile)) Current MSBuildToolsVersion: $(MSBuildToolsVersion)" />
    <Message Importance="high" Text="Sonar: ($(MSBuildProjectFile)) Current MSBuildAssemblyVersion: $(MSBuildAssemblyVersion)" />
    <Error Text="SonarQube analysis is only supported with MSBuild 14 or later." />
  </Target>

  <Target Name="SonarResolveReferencesMsBuild14"
          AfterTargets="ResolveAssemblyReferences"
          Condition=" $(SonarQubeTempPath) != '' AND $(MSBuildToolsVersion) == '14.0'">
    <ItemGroup>
      <SonarResolvedReferences Include="@(%(ReferencePath.FusionName))" />
    </ItemGroup>
  </Target>

  <Target Name="SonarResolveReferences"
          AfterTargets="FindReferenceAssembliesForReferences"
          Condition=" $(SonarQubeTempPath) != '' AND $(MSBuildToolsVersion) != '14.0'">
    <ItemGroup>
      <SonarResolvedReferences Include="@(%(ReferencePathWithRefAssemblies.FusionName))" />
    </ItemGroup>
  </Target>

  <!-- **************************************************************************** -->
  <!-- Categorize as a product or test project -->
  <!-- **************************************************************************** -->
  <!-- This target is a utility target: it won't be executed unless a target that depends on it is executed. -->
  <Target Name="SonarCategoriseProject"
          DependsOnTargets="SonarResolveReferencesMsBuild14;SonarResolveReferences"
          Condition=" $(SonarQubeTempPath) != '' ">

    <Message Text="Sonar: ($(MSBuildProjectFile)) Categorizing project as test or product code..." />
    <Message Condition="$(SonarQubeTestProject) != ''" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) SonarQubeTestProject has been set explicitly to $(SonarQubeTestProject)." />

    <!-- Fakes detection -->
    <PropertyGroup>
      <IsFakesProject Condition="$(AssemblyName.EndsWith('.fakes', System.StringComparison.OrdinalIgnoreCase))">true</IsFakesProject>
    </PropertyGroup>
    <!-- Before deciding to exclude or tag as test such a project, make sure it is not a legitimate project that happens to be called something.fakes
         In this case, we require from the user to explicitely state whether the project is to be analyzed/considered a test project -->
    <PropertyGroup Condition="$(IsFakesProject) == 'true' and $(SonarQubeExclude) == ''">
      <SonarQubeExclude>true</SonarQubeExclude>
    </PropertyGroup>
    <PropertyGroup Condition="$(IsFakesProject) == 'true' and $(SonarQubeTestProject) == ''">
      <SonarQubeTestProject>true</SonarQubeTestProject>
    </PropertyGroup>
    <Message Condition="$(IsFakesProject) == 'true' and $(SonarQubeExclude) == 'true'" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) project is a temporary project generated by Microsoft Fakes and will be ignored." />

    <!-- Temporary project detection -->
    <!-- Some kinds of project have logic to trigger additional special builds based on the same project file
         e.g. WPF projects build a temporary assembly before the main compilation takes place.
         We only want to analyze the "normal" build of the project, so we should turn off analysis for the known cases. -->
    <PropertyGroup>
      <!-- Special case 1: WPF projects call the task Windows.GenerateTemporaryTargetAssembly in PresentationBuildTasks. -->
      <IsTempProject Condition="$(MSBuildProjectFile.EndsWith('.tmp_proj', System.StringComparison.OrdinalIgnoreCase))">true</IsTempProject>
      <IsTempProject Condition="$(MSBuildProjectFile.EndsWith('_wpftmp.csproj', System.StringComparison.OrdinalIgnoreCase))">true</IsTempProject>
      <IsTempProject Condition="$(MSBuildProjectFile.EndsWith('_wpftmp.vbproj', System.StringComparison.OrdinalIgnoreCase))">true</IsTempProject>
    </PropertyGroup>

    <PropertyGroup Condition="$(IsTempProject) == 'true' AND $(SonarQubeExclude)==''" >
      <SonarQubeExclude>true</SonarQubeExclude>
    </PropertyGroup>
    <Message Condition="$(IsTempProject) == 'true' AND $(SonarQubeExclude)=='true'" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) project is a temporary project and will be excluded." />

    <PropertyGroup Condition=" $(SonarQubeTestProject) == '' ">
      <!-- The MS Test project type guid -->
      <SonarQubeMsTestProjectTypeGuid>3AC096D0-A1C2-E12C-1390-A8335801FDAB</SonarQubeMsTestProjectTypeGuid>
      <tmpSonarHasMSTestProjectTypeGuid Condition=" $(ProjectTypeGuids.ToUpperInvariant().Contains('$(SonarQubeMsTestProjectTypeGuid)')) ">true</tmpSonarHasMSTestProjectTypeGuid>
      <SonarQubeTestProject Condition=" $(tmpSonarHasMSTestProjectTypeGuid)=='true' ">true</SonarQubeTestProject>
    </PropertyGroup>
    <Message Condition="$(tmpSonarHasMSTestProjectTypeGuid)=='true'" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) project has the MSTest project type guid -> test project." />

    <PropertyGroup Condition=" $(SonarQubeTestProject) == '' ">
      <!-- The legacy Service tag added by the Test Explorer window -->
      <tmpSQServiceList>@(Service)</tmpSQServiceList>
      <tmpSonarHasServiceTag Condition="$(tmpSQServiceList.ToUpperInvariant().Contains('{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}')) ">true</tmpSonarHasServiceTag>
      <SonarQubeTestProject Condition="$(tmpSonarHasServiceTag)=='true'">true</SonarQubeTestProject>
    </PropertyGroup>
    <Message Condition="$(tmpSonarHasServiceTag)=='true'" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) project has the legacy Test Explorer Service tag {82A7F48D-3B50-4B1E-B82E-3ADA8210C358} -> test project." />

    <PropertyGroup Condition=" $(SonarQubeTestProject) == '' ">
      <!-- The ProjectCapability for test projects -->
      <tmpSQProjectCapabilities>@(ProjectCapability)</tmpSQProjectCapabilities>
      <tmpSonarHasTestContainerCapability Condition="$(tmpSQProjectCapabilities.ToUpperInvariant().Contains('TESTCONTAINER')) ">true</tmpSonarHasTestContainerCapability>
      <SonarQubeTestProject Condition="$(tmpSonarHasTestContainerCapability)=='true'">true</SonarQubeTestProject>
    </PropertyGroup>
    <Message Condition="$(tmpSonarHasTestContainerCapability)=='true'" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) project has the ProjectCapability 'TestContainer' -> test project." />

    <IsTestByReference Condition=" $(SonarQubeTestProject) == '' "
        References="@(SonarResolvedReferences)">
      <Output TaskParameter="TestReference" PropertyName="tmpSonarTestReferenceResult" />
    </IsTestByReference>
    <PropertyGroup Condition=" $(SonarQubeTestProject) == '' ">
      <SonarQubeTestProject Condition="$(tmpSonarTestReferenceResult)!=''">true</SonarQubeTestProject>
    </PropertyGroup>
    <Message Condition="$(tmpSonarTestReferenceResult)!=''" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) project is evaluated as a test project based on the '$(tmpSonarTestReferenceResult)' reference." />

    <!-- If we haven't already determined whether the project is a test project then check its path/name against the regular expression in the config file -->
    <IsTestFileByName Condition=" $(SonarQubeTestProject) == '' "
        AnalysisConfigDir="$(SonarQubeConfigPath)"
        FullFilePath="$(MSBuildProjectFullPath)">
      <Output TaskParameter="IsTest" PropertyName="tmpSonarIsTestFileByNameResult" />
    </IsTestFileByName>

    <PropertyGroup Condition=" $(SonarQubeTestProject) == '' ">
      <SonarQubeTestProject>$(tmpSonarIsTestFileByNameResult)</SonarQubeTestProject>
    </PropertyGroup>
    <Message Condition="$(tmpSonarIsTestFileByNameResult)=='true'" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) project is evaluated as a test project based on the project name." />

    <Message Importance="normal" Condition="$(SonarQubeTestProject)=='false'" Text="Sonar: ($(MSBuildProjectFile)) categorized as MAIN project (production code)." />
    <Message Importance="normal" Condition="$(SonarQubeTestProject)=='true'"  Text="Sonar: ($(MSBuildProjectFile)) categorized as TEST project (test code)." />
  </Target>

  <!-- **************************************************************************** -->
  <!-- Calculate the set of files to be analyzed -->
  <!-- **************************************************************************** -->
  <!-- This target is a utility target: it won't be executed unless a target that
       depends on it is executed.
  -->
  <Target Name="SonarWriteFilesToAnalyze"
        Condition=" $(SonarQubeTempPath) != '' ">

    <!-- Include all of contents of the specified item groups, but exclude
         any that have the metadata 'SonarQubeExclude' set, or that are auto-generated -->
    <ItemGroup Condition="('$(SQAnalysisFileItemTypes)' != '')">
      <SonarQubeAnalysisFileItems Include="$(SQAnalysisFileItemTypes)" />
      <SonarQubeAnalysisFiles Include="@(%(SonarQubeAnalysisFileItems.Identity))" />
      <SonarQubeAnalysisFiles Remove="@(SonarQubeAnalysisFiles)" Condition=" %(SonarQubeAnalysisFiles.SonarQubeExclude) == 'true' OR %(SonarQubeAnalysisFiles.AutoGen) == 'true' OR %(SonarQubeAnalysisFiles.FullPath) == '$(TargetFrameworkMonikerAssemblyAttributesPath)'" />
    </ItemGroup>

    <!-- Exclude items under the intermediate output folder e.g. XAML files that are auto-generated on build -->
    <PropertyGroup>
      <!-- Calculate the full path to the base intermediate folder -->
      <SQFullIntermediateOutputPath>$([System.IO.Path]::Combine($(MSBuildProjectDirectory),$(BaseIntermediateOutputPath)))</SQFullIntermediateOutputPath>
    </PropertyGroup>
    <ItemGroup>
      <!-- Remove based on absolute path -->
      <SQIntermediateFiles Include="$(SQFullIntermediateOutputPath)**\*.*" />
      <!-- Remove based on relative path -->
      <SQIntermediateFiles Include="$(BaseIntermediateOutputPath)**\*.*" />
      <SonarQubeAnalysisFiles Remove="@(SQIntermediateFiles)" />
    </ItemGroup>

    <PropertyGroup>
      <!-- Set a property indicating whether there are any files to analyze -->
      <AnalysisFilesExist Condition=" @(SonarQubeAnalysisFiles) != '' ">true</AnalysisFilesExist>
    </PropertyGroup>

    <!-- Write the list of files  -->
    <PropertyGroup>
      <!-- Set the file name for the list file -->
      <AnalysisFileList>$(ProjectSpecificConfDir)\FilesToAnalyze.txt</AnalysisFileList>
    </PropertyGroup>

    <Message Condition="$(AnalysisFilesExist)!='true'" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) No files were found to analyse" />
    <Message Condition="$(AnalysisFilesExist)=='true'" Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) Number of files to analyse: @(SonarQubeAnalysisFiles->Count()). The list of files to be analyzed is in $(AnalysisFileList)." />

    <!-- Write out a list of files to analyze that will be passed to the sonar-scanner -->
    <WriteLinesToFile Condition=" $(AnalysisFilesExist) == 'true' "
            File="$(AnalysisFileList)"
            Lines="%(SonarQubeAnalysisFiles.FullPath)"
            Overwrite="false"
            Encoding="utf-8" />
  </Target>

  <Target Name="SonarCreateProjectSpecificDirs" BeforeTargets="OverrideRoslynCodeAnalysisProperties;SonarWriteProjectData;SonarWriteFilesToAnalyze"  Condition=" $(SonarQubeTempPath) != '' ">
    <!-- **************************************************************************** -->
    <!-- Create the project-specific directories for conf and out -->

    <!-- Create folder with unique name in the .sonarqube/out/ folder -->
    <MakeUniqueDir Path="$(SonarQubeOutputPath)">
      <Output TaskParameter="UniquePath" PropertyName="ProjectSpecificOutDir" />
      <Output TaskParameter="UniqueName" PropertyName="UniqueName" />
    </MakeUniqueDir>
    <!-- Create .sonarqube/conf/ sub-folder with the same unique name -->
    <PropertyGroup>
      <ProjectSpecificConfDir>$(SonarQubeConfigPath)\$(UniqueName)</ProjectSpecificConfDir>
    </PropertyGroup>
    <MakeDir Directories="$(ProjectSpecificConfDir)" />
  </Target>

  <!-- SonarWriteProjectData has to be invoked after the Build target, so it can pick up the results for C++ projects.  -->
  <Target Name="InvokeSonarWriteProjectData_NonRazorProject"
          DependsOnTargets="SonarCategoriseProject;SonarWriteFilesToAnalyze;SonarCreateProjectSpecificDirs"
          AfterTargets="Build"
          Condition=" $(SonarQubeTempPath) != '' AND ('@(RazorCompile)'=='' OR ('@(RazorCompile)'!='' AND '$(UseRazorSourceGenerator)'=='true'))  ">
    <CallTarget Targets="SonarWriteProjectData" />
  </Target>

  <Target Name="InvokeSonarWriteProjectData_RazorProject"
          DependsOnTargets="SonarCategoriseProject;SonarWriteFilesToAnalyze;SonarCreateProjectSpecificDirs"
          BeforeTargets="SonarPrepareRazorProjectCodeAnalysis"
          AfterTargets="CoreCompile"
          Condition=" $(SonarQubeTempPath) != '' AND '@(RazorCompile)'!='' AND ('$(UseRazorSourceGenerator)'=='' OR '$(UseRazorSourceGenerator)'=='false') ">
    <CallTarget Targets="SonarWriteProjectData" />
  </Target>

  <Target Name="SonarWriteProjectData" >
    <PropertyGroup>
      <SonarErrorLogExists Condition=" $(SonarErrorLog) != '' AND  $([System.IO.File]::Exists($(SonarErrorLog))) == 'true' ">true</SonarErrorLogExists>
    </PropertyGroup>

    <ItemGroup>
      <SonarReportFilePath Condition= "$(SonarErrorLogExists) == 'true'" Include="$(SonarErrorLog)" />
    </ItemGroup>

    <ItemGroup>
      <SonarQubeSetting Include="sonar.$(SQLanguage).roslyn.reportFilePaths" Condition=" @(SonarReportFilePath) != '' ">
        <!-- Join the paths with |  -->
        <!-- This delimiter needs to be the same as PropertiesFileGenerator.RoslynReportPathsDelimiter -->
        <Value>@(SonarReportFilePath->'%(identity)','|')</Value>
      </SonarQubeSetting>
      <SonarQubeSetting Include="sonar.$(SQLanguage).analyzer.projectOutPaths" Condition= "$(SonarErrorLogExists) == 'true'">
        <Value>$(ProjectSpecificOutDir)</Value>
      </SonarQubeSetting>
    </ItemGroup>

    <!-- Record the list of files as an analysis result -->
    <ItemGroup Condition=" $(AnalysisFilesExist) == 'true' ">
      <AnalysisResults Include="$(AnalysisFileList)">
        <Id>FilesToAnalyze</Id>
      </AnalysisResults>
    </ItemGroup>

    <CallTarget Targets="FixUpTestProjectOutputs" />

    <WriteProjectInfoFile ProjectName="$(MSBuildProjectName)"
       FullProjectPath="$(MSBuildProjectFullPath)"
       ProjectLanguage="$(Language)"
       ProjectGuid="$(ProjectGuid)"
       SolutionConfigurationContents="$(CurrentSolutionConfigurationContents)"
       IsTest="$(SonarQubeTestProject)"
       IsExcluded="$(SonarQubeExclude)"
       AnalysisResults="@(AnalysisResults)"
       AnalysisSettings="@(SonarQubeSetting)"
       OutputFolder="$(ProjectSpecificOutDir)"
       CodePage="$(CodePage)"
       Configuration="$(Configuration)"
       Platform="$(Platform)"
       TargetFramework="$(TargetFramework)" />

    <Message Importance="high" Text="Sonar: ($(MSBuildProjectFile)) Project processed successfully" />
  </Target>

  <!--
    Set the Razor specific error log properties.
    Move the results of the core compilation to a temporary folder to prevent RazorCoreCompilation from overriding its content.
    The contents will be moved back in SonarFinishRazorProjectCodeAnalysis target.
  -->
  <Target Name="SonarPrepareRazorProjectCodeAnalysis" BeforeTargets="RazorCoreCompile" AfterTargets="CoreCompile" Condition="'@(RazorCompile)'!='' AND ('$(UseRazorSourceGenerator)'=='' OR '$(UseRazorSourceGenerator)'=='false')" >
    <PropertyGroup Condition=" $(SonarErrorLog) != '' ">
      <!-- https://github.com/SonarSource/sonar-scanner-msbuild/issues/1015 Respect user-provided value. When set, users should provide unique path for each build (project and target framework). -->
      <RazorSonarErrorLogName Condition=" $(RazorCompilationErrorLog) == '' ">Issues$(RazorTargetNameSuffix).json</RazorSonarErrorLogName>
      <RazorCompilationErrorLog Condition=" $(RazorSonarErrorLogName) != '' ">$(ProjectSpecificOutDir)\$(RazorSonarErrorLogName)</RazorCompilationErrorLog>
      <ErrorLog>$(RazorCompilationErrorLog)</ErrorLog>
      <!-- Keep the path so that we could set SonarQubeSetting with sonar.language.roslyn.reportFilePaths -->
      <RazorSonarErrorLog>$(RazorCompilationErrorLog)</RazorSonarErrorLog>
    </PropertyGroup>
    <PropertyGroup>
      <SonarTemporaryProjectSpecificOutDir>$(ProjectSpecificOutDir).tmp</SonarTemporaryProjectSpecificOutDir>
    </PropertyGroup>
    <ItemGroup>
      <CoreCompileOutFiles Include="$(ProjectSpecificOutDir)\**\*.*"/>
    </ItemGroup>
    <Move
      SourceFiles="@(CoreCompileOutFiles)"
      DestinationFolder="$(SonarTemporaryProjectSpecificOutDir)\%(RecursiveDir)" />
    <Message Importance="high" Text="Sonar: Preparing for Razor compilation, moved files (@(CoreCompileOutFiles)) to $(SonarTemporaryProjectSpecificOutDir)." />
  </Target>

  <!-- In case of Razor compilation the output files generated during the core compilation step are already moved into a temporary folder.
       This target renames output directories to their final state and creates the ProjectInfo.xml for them.
  -->
  <Target Name="SonarFinishRazorProjectCodeAnalysis" AfterTargets="RazorCoreCompile;InvokeSonarWriteProjectData_RazorProject" Condition=" $(SonarTemporaryProjectSpecificOutDir) != '' AND '@(RazorCompile)'!='' AND ('$(UseRazorSourceGenerator)'=='' OR '$(UseRazorSourceGenerator)'=='false') ">
    <PropertyGroup>
      <RazorSonarProjectSpecificOutDir>$(ProjectSpecificOutDir).Razor</RazorSonarProjectSpecificOutDir>
      <RazorSonarProjectInfo>$(RazorSonarProjectSpecificOutDir)\ProjectInfo.xml</RazorSonarProjectInfo>
    </PropertyGroup>
    <ItemGroup>
      <RazorCompilationOutFiles Include="$(ProjectSpecificOutDir)\**\*.*"/>
      <SonarTempFiles Include="$(SonarTemporaryProjectSpecificOutDir)\**\*.*"/>
    </ItemGroup>

    <!-- Move main and Razor results to their final location. -->
    <Move
      SourceFiles="@(RazorCompilationOutFiles)"
      DestinationFolder="$(RazorSonarProjectSpecificOutDir)\%(RecursiveDir)" />
    <Message Importance="high" Text="Sonar: After Razor compilation, moved files (@(RazorCompilationOutFiles)) to $(RazorSonarProjectSpecificOutDir)." />

    <Move
      SourceFiles="@(SonarTempFiles)"
      DestinationFolder="$(ProjectSpecificOutDir)\%(RecursiveDir)" />
    <Message Importance="high" Text="Sonar: After Razor compilation, moved files (@(SonarTempFiles)) to $(ProjectSpecificOutDir) and will remove the temporary folder." />
    <RemoveDir Directories="$(SonarTemporaryProjectSpecificOutDir)" />

    <PropertyGroup>
      <!-- This overwrites the previously set 'RazorSonarErrorLog' in 'SonarPrepareRazorProjectCodeAnalysis'
           if 'RazorSonarErrorLogName' is set, which means 'RazorCompilationErrorLog' was null.
           The condition is a bit difficult to follow, it would be easier to change it or also add "$(RazorSonarErrorLog) != ''" -->
      <RazorSonarErrorLog Condition=" $(RazorSonarErrorLogName) != '' ">$(RazorSonarProjectSpecificOutDir)\$(RazorSonarErrorLogName)</RazorSonarErrorLog>
      <RazorSonarErrorLogExists Condition=" $(RazorSonarErrorLog) != '' AND  $([System.IO.File]::Exists($(RazorSonarErrorLog))) == 'true' ">true</RazorSonarErrorLogExists>
    </PropertyGroup>

    <ItemGroup>
      <RazorSonarReportFilePath Condition= "$(RazorSonarErrorLogExists) == 'true'" Include="$(RazorSonarErrorLog)" />
      <RazorSonarQubeSetting Include="sonar.$(SQLanguage).roslyn.reportFilePaths" Condition=" @(RazorSonarReportFilePath) != '' ">
        <!-- Join the paths with |  -->
        <!-- This delimiter needs to be the same as PropertiesFileGenerator.RoslynReportPathsDelimiter -->
        <Value>@(RazorSonarReportFilePath->'%(identity)','|')</Value>
      </RazorSonarQubeSetting>
      <RazorSonarQubeSetting Include="sonar.$(SQLanguage).analyzer.projectOutPaths" Condition= "$(RazorSonarErrorLogExists) == 'true'">
        <Value>$(RazorSonarProjectSpecificOutDir)</Value>
      </RazorSonarQubeSetting>
    </ItemGroup>

    <!-- Create the ProjectInfo.xml for the Razor part. -->
    <WriteProjectInfoFile ProjectName="$(MSBuildProjectName)"
                          FullProjectPath="$(MSBuildProjectFullPath)"
                          ProjectLanguage="$(Language)"
                          ProjectGuid="$(ProjectGuid)"
                          SolutionConfigurationContents="$(CurrentSolutionConfigurationContents)"
                          IsTest="$(SonarQubeTestProject)"
                          IsExcluded="$(SonarQubeExclude)"
                          AnalysisResults="@(AnalysisResults)"
                          AnalysisSettings="@(RazorSonarQubeSetting)"
                          OutputFolder="$(RazorSonarProjectSpecificOutDir)"
                          CodePage="$(CodePage)"
                          Configuration="$(Configuration)"
                          Platform="$(Platform)"
                          TargetFramework="$(TargetFramework)" />
  </Target>

  <Target Name="FixUpTestProjectOutputs" Condition="$(SonarQubeTestProject)=='true'" >
    <Message Importance="low" Text="Sonar: ($(MSBuildProjectFile)) Fixing up test project outputs..." />

    <ItemGroup>
      <!-- Select specific metrics files that should not be uploaded for test files.
           Assumption: these files will be in a sub-folder under the project-specific output folder. -->
      <SQTestMetricFiles Include="$(ProjectSpecificOutDir)\*\metrics.pb" />
      <SQTestMetricFiles Include="$(ProjectSpecificOutDir)\*\token-cpd.pb" />
    </ItemGroup>

    <Message Importance="low" Text="Sonar: ($(MSBuildProjectFile)) Selected test metric file: @(SQTestMetricFiles)" />
    <WriteZeroLengthFiles FullFilePaths="@(SQTestMetricFiles)" />
  </Target>

  <!-- **************************************************************************** -->
  <!-- Code analysis -->
  <!-- **************************************************************************** -->
  <!--  Implementation notes:

        We want to override any code analysis-related properties that are set in the
        project file. To do this, we need to execute our targets at the correct
        point in the build.

        Note that both the FxCop and Roslyn targets use a property called "CodeAnalysisRuleSet".
        The FxCop targets use this value directly. However, the Roslyn targets use it
        as input to the "ResolveCodeAnalysisRuleSet" target, which sets the property
        "ResolvedCodeAnalysisRuleset".

        We override the property "ResolveCodeAnalysisRuleSet" with the path to our Roslyn ruleset.
  -->

  <PropertyGroup>
    <SQLanguage Condition="$(Language) == 'C#'">cs</SQLanguage>
    <SQLanguage Condition="$(Language) == 'VB'">vbnet</SQLanguage>
  </PropertyGroup>

  <!-- **************************************************************************** -->
  <!-- Roslyn analysis section -->
  <!-- **************************************************************************** -->
  <!-- Override VS2019 properties to make sure analysis runs in the scanner build.
       We need to run before BeforeCompile that executes _ComputeSkipAnalyzers target.
       We don't need to override this for excluded projects. -->
  <Target Name="SonarOverrideRunAnalyzers"
        Condition=" $(SonarQubeTempPath) != '' AND $(SonarQubeExclude) != 'true' "
        BeforeTargets="BeforeCompile">

    <PropertyGroup>
      <RunAnalyzers>true</RunAnalyzers>
      <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
    </PropertyGroup>
  </Target>

  <!-- Set the properties required to run Roslyn analysis as part of the build.
       Analysis is controlled by the following properties:
         ResolvedCodeAnalysisRuleSet - the full path to the ruleset to use
         ErrorLog and RazorCompilationErrorLog - the file path for the error reporting file

       We want to run after the core "ResolveCodeAnalysisRuleSet" target which assigns a value to "ResolvedCodeAnalysisRuleSet".

       Conditions:
       * project is excluded -> turn off Roslyn analysis
       * project is a test project and parameter sonar.dotnet.excludeTestProjects=true -> run only metrics rules
       * otherwise, use the analyzer settings loaded from the config file (removing any existing analyzers)
   -->
  <Target Name="OverrideRoslynCodeAnalysisProperties"
        Condition=" $(SonarQubeTempPath) != '' "
        DependsOnTargets="SonarCategoriseProject;SonarWriteFilesToAnalyze"
        AfterTargets="ResolveCodeAnalysisRuleSet"
        BeforeTargets="CoreCompile">

    <PropertyGroup>
      <!-- Make sure no warnings are treated as errors -->
      <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
      <WarningsAsErrors></WarningsAsErrors>

      <!-- The Roslyn analyzers output messages with different warning levels. This is tied to the diagnostic severity and currently
      is translated as follows - error: 0, warning: 1, info: 4. To avoid issues being missed override the warning level by setting it to max tolerance.-->
      <WarningLevel>4</WarningLevel>
    </PropertyGroup>

    <CallTarget Targets="SetRoslynCodeAnalysisProperties" Condition="$(SonarQubeExclude) != 'true'" />
  </Target>

  <Target Name="SetRoslynCodeAnalysisProperties">

    <!-- Fetch the relevant settings from the config file -->
    <GetAnalyzerSettings AnalysisConfigDir="$(SonarQubeConfigPath)"
                         Language="$(SQLanguage)"
                         CurrentProjectDirectoryPath="$(MSBuildProjectDirectory)"
                         OriginalAnalyzers="@(Analyzer)"
                         OriginalAdditionalFiles="@(AdditionalFiles)"
                         OriginalRulesetFilePath="$(ResolvedCodeAnalysisRuleset)"
                         ProjectSpecificConfigDirectory="$(ProjectSpecificConfDir)"
                         IsTestProject="$(SonarQubeTestProject)">
      <Output TaskParameter="RuleSetFilePath" PropertyName="SQRuleSetFilePath" />
      <Output TaskParameter="AnalyzerFilePaths" ItemName="SQAnalyzerFilePaths" />
      <Output TaskParameter="AdditionalFilePaths" ItemName="SQAdditionalFiles" />
    </GetAnalyzerSettings>

    <PropertyGroup>
      <!-- https://github.com/SonarSource/sonar-scanner-msbuild/issues/1015 Respect user-provided value. When set, users should provide unique path for each build (project and target framework). -->
      <!-- https://jira.sonarsource.com/browse/SONARMSBRU-211 - the full path should be unique per project and target framework. -->
      <ErrorLog Condition=" $(ErrorLog) == '' ">$(ProjectSpecificOutDir)\Issues.json</ErrorLog>
      <!-- Keep the path so that we could set SonarQubeSetting with sonar.language.roslyn.reportFilePaths -->
      <SonarErrorLog>$(ErrorLog)</SonarErrorLog>
    </PropertyGroup>

    <PropertyGroup>
      <ResolvedCodeAnalysisRuleSet>$(SQRuleSetFilePath)</ResolvedCodeAnalysisRuleSet>
      <SonarProjectOutFolderFilePath>$(ProjectSpecificConfDir)\ProjectOutFolderPath.txt</SonarProjectOutFolderFilePath>
    </PropertyGroup>

    <!--SonarProjectOutFolderFilePath is generated only for backward compatibility with CSharp and VBNet analyzers before 8.20 -->
    <WriteLinesToFile File="$(SonarProjectOutFolderFilePath)" Lines="$(ProjectSpecificOutDir)" Overwrite="true" />

    <WriteProjectConfigFile ConfigDir="$(ProjectSpecificConfDir)"
                            AnalysisConfigPath="$(SonarQubeConfigPath)\SonarQubeAnalysisConfig.xml"
                            ProjectPath="$(MSBuildProjectFullPath)"
                            FilesToAnalyzePath ="$(AnalysisFileList)"
                            OutPath="$(ProjectSpecificOutDir)"
                            IsTest="$(SonarQubeTestProject)"
                            TargetFramework="$(TargetFramework)">
      <Output TaskParameter="ProjectConfigFilePath" PropertyName="SonarProjectConfigFilePath" />
    </WriteProjectConfigFile>

    <ItemGroup>
      <!-- Clear the original set of analyzers and additional files -->
      <Analyzer Remove="@(Analyzer)" />
      <AdditionalFiles Remove="@(AdditionalFiles)" />

      <!-- Use the set of analyzers and additional files calculated by the GetAnalyzerSettings task -->
      <Analyzer Include="@(SQAnalyzerFilePaths)" />
      <AdditionalFiles Include="@(SQAdditionalFiles)" />
      <AdditionalFiles Include="$(SonarProjectConfigFilePath)" />
      <AdditionalFiles Include="$(SonarProjectOutFolderFilePath)" />
    </ItemGroup>

    <Message Importance="normal" Text="Sonar: ($(MSBuildProjectFile)) Analysis configured successfully with $(SonarProjectConfigFilePath)." />
  </Target>
  <!-- **************************************************************************** -->
  <!-- End of Roslyn analysis section-->
  <!-- **************************************************************************** -->

</Project>
