File: Harness\ProjectGenerator.cs
Web Access
Project: src\test\Microsoft.ML.PerformanceTests\Microsoft.ML.PerformanceTests.csproj (Microsoft.ML.PerformanceTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains;
using BenchmarkDotNet.Toolchains.CsProj;
 
namespace Microsoft.ML.PerformanceTests.Harness
{
    /// <summary>
    /// to avoid side effects of benchmarks affect each other BenchmarkDotNet runs every benchmark in a standalone, dedicated process
    /// however to do that it needs to be able to create, build and run new executable
    /// 
    /// the problem with ML.NET is that it has native dependencies, which are NOT copied by MSBuild to the output folder
    /// in case where A has native dependency and B references A
    /// 
    /// this is why this class exists: 
    ///  1. to tell MSBuild to copy the native dependencies to folder with .exe (NativeAssemblyReference)
    ///  2. to generate a .csproj file that does not exclude Directory.Build.props (default BDN behavior) which contains custom NuGet feeds that are required for restore step
    /// </summary>
    public class ProjectGenerator : CsProjGenerator
    {
        private readonly string _runtimeIdentifier = string.Empty;
 
        public ProjectGenerator(string targetFrameworkMoniker) : base(targetFrameworkMoniker, null, null, null)
        {
#if NETFRAMEWORK
            _runtimeIdentifier = "win-x64";
#endif
        }
 
        protected override void GenerateProject(BuildPartition buildPartition, ArtifactsPaths artifactsPaths, ILogger logger)
            => File.WriteAllText(artifactsPaths.ProjectFilePath, $@"
<Project Sdk=""Microsoft.NET.Sdk"">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <OutputPath>bin\{buildPartition.BuildConfiguration}</OutputPath>
    <TargetFramework>{TargetFrameworkMoniker}</TargetFramework>
    <RuntimeIdentifier>{_runtimeIdentifier}</RuntimeIdentifier>
    <AssemblyName>{artifactsPaths.ProgramName}</AssemblyName>
    <AssemblyTitle>{artifactsPaths.ProgramName}</AssemblyTitle>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <TreatWarningsAsErrors>False</TreatWarningsAsErrors>
    <DebugType>pdbonly</DebugType>
    <DebugSymbols>true</DebugSymbols>
  </PropertyGroup>
  {GetRuntimeSettings(buildPartition.RepresentativeBenchmarkCase.Job.Environment.Gc, buildPartition.Resolver)}
  <ItemGroup>
    <Compile Include=""{Path.GetFileName(artifactsPaths.ProgramCodePath)}"" Exclude=""bin\**;obj\**;**\*.xproj;packages\**"" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include=""{GetProjectFilePath(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger).FullName}"" />
    {GenerateNativeReferences(buildPartition, logger)}
  </ItemGroup>
</Project>");
 
        // This overrides the .exe path to also involve the runtimeIdentifier for .NET Framework
        protected override string GetBinariesDirectoryPath(string buildArtifactsDirectoryPath, string configuration)
            => Path.Combine(buildArtifactsDirectoryPath, "bin", configuration, TargetFrameworkMoniker, _runtimeIdentifier);
 
        private string GenerateNativeReferences(BuildPartition buildPartition, ILogger logger)
        {
            var csproj = GetProjectFilePath(buildPartition.RepresentativeBenchmarkCase.Descriptor.Type, logger);
 
            return string.Join(Environment.NewLine, File.ReadAllLines(csproj.FullName).Where(line => line.Contains("<NativeAssemblyReference")));
        }
    }
}