Skip to content

Copy PackageReferences to generated csproj #2347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 3, 2023
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
9 changes: 6 additions & 3 deletions src/BenchmarkDotNet/Templates/CsProj.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@
<Deterministic>true</Deterministic>
<!-- needed for custom build configurations (only "Release" builds are optimized by default) -->
<Optimize Condition=" '$(Configuration)' != 'Debug' ">true</Optimize>
<!-- Begin copied settings from benchmarks project -->
$COPIEDSETTINGS$
<!-- End copied settings -->
<!-- we set LangVersion after any copied settings which might contain LangVersion copied from the benchmarks project -->
<LangVersion Condition="'$(LangVersion)' == '' Or ($([System.Char]::IsDigit('$(LangVersion)', 0)) And '$(LangVersion)' &lt; '7.3')">latest</LangVersion>
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
<!-- fix for NETSDK1150: https://docs.microsoft.com/en-us/dotnet/core/compatibility/sdk/5.0/referencing-executable-generates-error -->
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
<StartupObject>BenchmarkDotNet.Autogenerated.UniqueProgramName</StartupObject>
</PropertyGroup>

<ItemGroup>
Expand All @@ -38,6 +36,11 @@
<ItemGroup>
<ProjectReference Include="$CSPROJPATH$" />
</ItemGroup>

<!-- Begin copied settings from benchmarks project -->
$COPIEDSETTINGS$
<!-- End copied settings -->

$RUNTIMESETTINGS$

</Project>
5 changes: 5 additions & 0 deletions src/BenchmarkDotNet/Templates/MonoAOTLLVMCsProj.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<AssemblyName>$PROGRAMNAME$</AssemblyName>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<StartupObject>BenchmarkDotNet.Autogenerated.UniqueProgramName</StartupObject>
</PropertyGroup>

<ItemGroup>
Expand All @@ -24,6 +25,10 @@
<ProjectReference Include="$CSPROJPATH$" />
</ItemGroup>

<!-- Begin copied settings from benchmarks project -->
$COPIEDSETTINGS$
<!-- End copied settings -->

<!-- Redirect 'dotnet publish' to in-tree runtime pack -->
<Target Name="TrickRuntimePackLocation" AfterTargets="ProcessFrameworkReferences">
<ItemGroup>
Expand Down
6 changes: 5 additions & 1 deletion src/BenchmarkDotNet/Templates/WasmCsProj.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<WasmGenerateRunV8Script>true</WasmGenerateRunV8Script>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
<EnableDefaultWasmAssembliesToBundle>false</EnableDefaultWasmAssembliesToBundle>
$COPIEDSETTINGS$
<StartupObject>BenchmarkDotNet.Autogenerated.UniqueProgramName</StartupObject>
</PropertyGroup>

<ItemGroup>
Expand All @@ -38,6 +38,10 @@
<ProjectReference Include="$(OriginalCSProjPath)" />
</ItemGroup>

<!-- Begin copied settings from benchmarks project -->
$COPIEDSETTINGS$
<!-- End copied settings -->

<PropertyGroup>
<WasmBuildAppAfterThisTarget>PrepareForWasmBuild</WasmBuildAppAfterThisTarget>
</PropertyGroup>
Expand Down
195 changes: 148 additions & 47 deletions src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
Expand All @@ -22,8 +23,20 @@ public class CsProjGenerator : DotNetCliGenerator, IEquatable<CsProjGenerator>
{
private const string DefaultSdkName = "Microsoft.NET.Sdk";

private static readonly ImmutableArray<string> SettingsWeWantToCopy =
new[] { "NetCoreAppImplicitPackageVersion", "RuntimeFrameworkVersion", "PackageTargetFallback", "LangVersion", "UseWpf", "UseWindowsForms", "CopyLocalLockFileAssemblies", "PreserveCompilationContext", "UserSecretsId", "EnablePreviewFeatures" }.ToImmutableArray();
private static readonly ImmutableArray<string> SettingsWeWantToCopy = new[]
{
"NetCoreAppImplicitPackageVersion",
"RuntimeFrameworkVersion",
"PackageTargetFallback",
"LangVersion",
"UseWpf",
"UseWindowsForms",
"CopyLocalLockFileAssemblies",
"PreserveCompilationContext",
"UserSecretsId",
"EnablePreviewFeatures",
"PackageReference"
}.ToImmutableArray();

public string RuntimeFrameworkVersion { get; }

Expand Down Expand Up @@ -57,24 +70,23 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
var benchmark = buildPartition.RepresentativeBenchmarkCase;
var projectFile = GetProjectFilePath(benchmark.Descriptor.Type, logger);

using (var file = new StreamReader(File.OpenRead(projectFile.FullName)))
{
var (customProperties, sdkName) = GetSettingsThatNeedsToBeCopied(file, projectFile);

var content = new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
.Replace("$CSPROJPATH$", projectFile.FullName)
.Replace("$TFM$", TargetFrameworkMoniker)
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName)
.Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(benchmark.Job.Environment.Gc, buildPartition.Resolver))
.Replace("$COPIEDSETTINGS$", customProperties)
.Replace("$CONFIGURATIONNAME$", buildPartition.BuildConfiguration)
.Replace("$SDKNAME$", sdkName)
.ToString();

File.WriteAllText(artifactsPaths.ProjectFilePath, content);
}
var xmlDoc = new XmlDocument();
xmlDoc.Load(projectFile.FullName);
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);

var content = new StringBuilder(ResourceHelper.LoadTemplate("CsProj.txt"))
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
.Replace("$CSPROJPATH$", projectFile.FullName)
.Replace("$TFM$", TargetFrameworkMoniker)
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName)
.Replace("$RUNTIMESETTINGS$", GetRuntimeSettings(benchmark.Job.Environment.Gc, buildPartition.Resolver))
.Replace("$COPIEDSETTINGS$", customProperties)
.Replace("$CONFIGURATIONNAME$", buildPartition.BuildConfiguration)
.Replace("$SDKNAME$", sdkName)
.ToString();

File.WriteAllText(artifactsPaths.ProjectFilePath, content);
}

/// <summary>
Expand All @@ -97,45 +109,134 @@ protected virtual string GetRuntimeSettings(GcMode gcMode, IResolver resolver)
// the host project or one of the .props file that it imports might contain some custom settings that needs to be copied, sth like
// <NetCoreAppImplicitPackageVersion>2.0.0-beta-001607-00</NetCoreAppImplicitPackageVersion>
// <RuntimeFrameworkVersion>2.0.0-beta-001607-00</RuntimeFrameworkVersion>
internal (string customProperties, string sdkName) GetSettingsThatNeedsToBeCopied(TextReader streamReader, FileInfo projectFile)
internal (string customProperties, string sdkName) GetSettingsThatNeedToBeCopied(XmlDocument xmlDoc, FileInfo projectFile)
{
if (!string.IsNullOrEmpty(RuntimeFrameworkVersion)) // some power users knows what to configure, just do it and copy nothing more
return ($"<RuntimeFrameworkVersion>{RuntimeFrameworkVersion}</RuntimeFrameworkVersion>", DefaultSdkName);
{
return (@$"<PropertyGroup>
<RuntimeFrameworkVersion>{RuntimeFrameworkVersion}</RuntimeFrameworkVersion>
</PropertyGroup>", DefaultSdkName);
}

XmlElement projectElement = xmlDoc.DocumentElement;
// custom SDKs are not added for non-netcoreapp apps (like net471), so when the TFM != netcoreapp we dont parse "<Import Sdk="
// we don't allow for that mostly to prevent from edge cases like the following
// <Import Sdk="Microsoft.NET.Sdk.WindowsDesktop" Project="Sdk.props" Condition="'$(TargetFramework)'=='netcoreapp3.0'"/>
string sdkName = null;
if (TargetFrameworkMoniker.StartsWith("netcoreapp", StringComparison.InvariantCultureIgnoreCase))
{
foreach (XmlElement importElement in projectElement.GetElementsByTagName("Import"))
{
sdkName = importElement.GetAttribute("Sdk");
if (!string.IsNullOrEmpty(sdkName))
{
break;
}
}
}
if (string.IsNullOrEmpty(sdkName))
{
sdkName = projectElement.GetAttribute("Sdk");
}
// If Sdk isn't an attribute on the Project element, it could be a child element.
if (string.IsNullOrEmpty(sdkName))
{
foreach (XmlElement sdkElement in projectElement.GetElementsByTagName("Sdk"))
{
sdkName = sdkElement.GetAttribute("Name");
if (string.IsNullOrEmpty(sdkName))
{
continue;
}
string version = sdkElement.GetAttribute("Version");
// Version is optional
if (!string.IsNullOrEmpty(version))
{
sdkName += $"/{version}";
}
break;
}
}
if (string.IsNullOrEmpty(sdkName))
{
sdkName = DefaultSdkName;
}

XmlDocument itemGroupsettings = null;
XmlDocument propertyGroupSettings = null;

var customProperties = new StringBuilder();
var sdkName = DefaultSdkName;
GetSettingsThatNeedToBeCopied(projectElement, ref itemGroupsettings, ref propertyGroupSettings, projectFile);

string line;
while ((line = streamReader.ReadLine()) != null)
List<string> customSettings = new List<string>(2);
if (itemGroupsettings != null)
{
var trimmedLine = line.Trim();
customSettings.Add(GetIndentedXmlString(itemGroupsettings));
}
if (propertyGroupSettings != null)
{
customSettings.Add(GetIndentedXmlString(propertyGroupSettings));
}

foreach (string setting in SettingsWeWantToCopy)
if (trimmedLine.Contains(setting))
customProperties.AppendLine(trimmedLine);
return (string.Join(Environment.NewLine + Environment.NewLine, customSettings), sdkName);
}

if (trimmedLine.StartsWith("<Import Project"))
private static void GetSettingsThatNeedToBeCopied(XmlElement projectElement, ref XmlDocument itemGroupsettings, ref XmlDocument propertyGroupSettings, FileInfo projectFile)
{
CopyProperties(projectElement, ref itemGroupsettings, "ItemGroup");
CopyProperties(projectElement, ref propertyGroupSettings, "PropertyGroup");

foreach (XmlElement importElement in projectElement.GetElementsByTagName("Import"))
{
string propsFilePath = importElement.GetAttribute("Project");
var directoryName = projectFile.DirectoryName ?? throw new DirectoryNotFoundException(projectFile.DirectoryName);
string absolutePath = File.Exists(propsFilePath)
? propsFilePath // absolute path or relative to current dir
: Path.Combine(directoryName, propsFilePath); // relative to csproj
if (File.Exists(absolutePath))
{
string propsFilePath = trimmedLine.Split('"')[1]; // its sth like <Import Project="..\..\build\common.props" />
var directoryName = projectFile.DirectoryName ?? throw new DirectoryNotFoundException(projectFile.DirectoryName);
string absolutePath = File.Exists(propsFilePath)
? propsFilePath // absolute path or relative to current dir
: Path.Combine(directoryName, propsFilePath); // relative to csproj

if (File.Exists(absolutePath))
using (var importedFile = new StreamReader(File.OpenRead(absolutePath)))
customProperties.Append(GetSettingsThatNeedsToBeCopied(importedFile, new FileInfo(absolutePath)).customProperties);
var importXmlDoc = new XmlDocument();
importXmlDoc.Load(absolutePath);
GetSettingsThatNeedToBeCopied(importXmlDoc.DocumentElement, ref itemGroupsettings, ref propertyGroupSettings, projectFile);
}
}
}

// custom SDKs are not added for non-netcoreapp apps (like net471), so when the TFM != netcoreapp we dont parse "<Import Sdk="
// we don't allow for that mostly to prevent from edge cases like the following
// <Import Sdk="Microsoft.NET.Sdk.WindowsDesktop" Project="Sdk.props" Condition="'$(TargetFramework)'=='netcoreapp3.0'"/>
if (trimmedLine.StartsWith("<Project Sdk=\"")
|| (TargetFrameworkMoniker.StartsWith("netcoreapp", StringComparison.InvariantCultureIgnoreCase) && trimmedLine.StartsWith("<Import Sdk=\"")))
sdkName = trimmedLine.Split('"')[1]; // its sth like Sdk="name"
private static void CopyProperties(XmlElement projectElement, ref XmlDocument copyToDocument, string groupName)
{
XmlElement itemGroupElement = copyToDocument?.DocumentElement;
foreach (XmlElement groupElement in projectElement.GetElementsByTagName(groupName))
{
foreach (var node in groupElement.ChildNodes)
{
if (node is XmlElement setting && SettingsWeWantToCopy.Contains(setting.Name))
{
if (copyToDocument is null)
{
copyToDocument = new XmlDocument();
itemGroupElement = copyToDocument.CreateElement(groupName);
copyToDocument.AppendChild(itemGroupElement);
}
XmlNode copiedNode = copyToDocument.ImportNode(setting, true);
itemGroupElement.AppendChild(copiedNode);
}
}
}
}

return (customProperties.ToString(), sdkName);
private static string GetIndentedXmlString(XmlDocument doc)
{
StringBuilder sb = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true,
IndentChars = " "
};
using (XmlWriter writer = XmlWriter.Create(sb, settings))
{
doc.Save(writer);
}
return sb.ToString();
}

/// <summary>
Expand Down
38 changes: 19 additions & 19 deletions src/BenchmarkDotNet/Toolchains/MonoAotLLVM/MonoAotLLVMGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.IO;
using System.Text;
using System.Xml;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Loggers;
Expand Down Expand Up @@ -30,27 +31,26 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts

string useLLVM = AotCompilerMode == MonoAotCompilerMode.llvm ? "true" : "false";

using (var file = new StreamReader(File.OpenRead(projectFile.FullName)))
{
var (customProperties, sdkName) = GetSettingsThatNeedsToBeCopied(file, projectFile);
var xmlDoc = new XmlDocument();
xmlDoc.Load(projectFile.FullName);
var (customProperties, sdkName) = GetSettingsThatNeedToBeCopied(xmlDoc, projectFile);

string content = new StringBuilder(ResourceHelper.LoadTemplate("MonoAOTLLVMCsProj.txt"))
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
.Replace("$CSPROJPATH$", projectFile.FullName)
.Replace("$TFM$", TargetFrameworkMoniker)
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName)
.Replace("$COPIEDSETTINGS$", customProperties)
.Replace("$CONFIGURATIONNAME$", buildPartition.BuildConfiguration)
.Replace("$SDKNAME$", sdkName)
.Replace("$RUNTIMEPACK$", CustomRuntimePack ?? "")
.Replace("$COMPILERBINARYPATH$", AotCompilerPath)
.Replace("$RUNTIMEIDENTIFIER$", CustomDotNetCliToolchainBuilder.GetPortableRuntimeIdentifier())
.Replace("$USELLVM$", useLLVM)
.ToString();
string content = new StringBuilder(ResourceHelper.LoadTemplate("MonoAOTLLVMCsProj.txt"))
.Replace("$PLATFORM$", buildPartition.Platform.ToConfig())
.Replace("$CODEFILENAME$", Path.GetFileName(artifactsPaths.ProgramCodePath))
.Replace("$CSPROJPATH$", projectFile.FullName)
.Replace("$TFM$", TargetFrameworkMoniker)
.Replace("$PROGRAMNAME$", artifactsPaths.ProgramName)
.Replace("$COPIEDSETTINGS$", customProperties)
.Replace("$CONFIGURATIONNAME$", buildPartition.BuildConfiguration)
.Replace("$SDKNAME$", sdkName)
.Replace("$RUNTIMEPACK$", CustomRuntimePack ?? "")
.Replace("$COMPILERBINARYPATH$", AotCompilerPath)
.Replace("$RUNTIMEIDENTIFIER$", CustomDotNetCliToolchainBuilder.GetPortableRuntimeIdentifier())
.Replace("$USELLVM$", useLLVM)
.ToString();

File.WriteAllText(artifactsPaths.ProjectFilePath, content);
}
File.WriteAllText(artifactsPaths.ProjectFilePath, content);
}

protected override string GetExecutablePath(string binariesDirectoryPath, string programName)
Expand Down
Loading