|
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.UnitTests.TestFiles;
namespace Microsoft.CodeAnalysis.MSBuild.UnitTests
{
/// <summary>
/// Flexible and extensible API to generate MSBuild projects and solutions without external files or resources.
/// </summary>
public static class SolutionGeneration
{
public const string NS = "http://schemas.microsoft.com/developer/msbuild/2003";
private const string CSharpProjectTemplate =
@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project ToolsVersion=""12.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<Import Project=""$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"" Condition=""Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"" />
<PropertyGroup>
<Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>
<Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">
</PropertyGroup>
<Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />
</Project>";
private const string SolutionTemplate =
@"
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.30110.0
MinimumVisualStudioVersion = 10.0.40219.1
{0}Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
";
public const string PublicKey = "00240000048000009400000006020000002400005253413100040000010001003bb5de1b79bee9bf5ba44bdb42974c6f40fdc4b329c8e1b833fa798cf0859529485b2bfc359a08e16f025fe57efd293c4dc3541cb2e0929b1c4a92db87eed7a9454dbd08beb7c7308941384b3bfb088de781b51caef23677f8f6defb671e97e1fc5e0979858e52828c86aca1d4ea1797f1f1254bf64073a28e5be520d5397fb0";
public const string PublicKeyToken = "39d7e8ec38707fde";
public static readonly byte[] KeySnk = Resources.Key_snk;
public static IEnumerable<(string fileName, object fileContent)> GetSolutionFiles(params IBuilder[] inputs)
{
var list = new List<(string, object)>();
var projectBuilders = inputs.OfType<ProjectBuilder>();
var files = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var fileIndex = 1;
var projectIndex = 1;
// first make sure all projects have names, as a separate loop
foreach (var project in projectBuilders)
{
if (project.Name == null)
{
project.Name = "Project" + projectIndex;
projectIndex++;
}
}
foreach (var project in projectBuilders)
{
foreach (var document in project.Documents)
{
if (document.FilePath == null)
{
document.FilePath = "Document" + fileIndex + (project.Language == LanguageNames.VisualBasic ? ".vb" : ".cs");
fileIndex++;
}
}
foreach (var projectReference in project.ProjectReferences)
{
if (projectReference.Guid == Guid.Empty)
{
var referencedProject = projectBuilders.First(p => p.Name == projectReference.ProjectName);
projectReference.Guid = referencedProject.Guid;
projectReference.ProjectFileName = referencedProject.Name + referencedProject.Extension;
}
}
foreach (var (fileName, fileContent) in project.Files)
{
if (files.Add(fileName + fileContent))
{
list.Add((fileName, fileContent));
}
}
}
list.Add(("Solution.sln", GetSolutionContent(projectBuilders)));
return list;
}
public static IBuilder Project(params IBuilder[] inputs)
{
var projectReferences = inputs.OfType<ProjectReferenceBuilder>();
var documents = inputs.OfType<DocumentBuilder>().ToList();
var projectName = inputs.OfType<ProjectNameBuilder>().FirstOrDefault();
var properties = inputs.OfType<PropertyBuilder>().ToList();
var sign = inputs.OfType<SignBuilder>();
if (sign != null)
{
properties.Add((PropertyBuilder)Property("SignAssembly", "true"));
properties.Add((PropertyBuilder)Property("AssemblyOriginatorKeyFile", "key.snk"));
documents.Add((DocumentBuilder)Document(KeySnk, "key.snk", "None"));
}
return new ProjectBuilder
{
Name = projectName?.Name,
Documents = documents,
ProjectReferences = projectReferences,
Properties = properties
};
}
public static IBuilder ProjectReference(string projectName)
{
return new ProjectReferenceBuilder
{
ProjectName = projectName
};
}
public static IBuilder ProjectName(string projectName)
{
return new ProjectNameBuilder
{
Name = projectName
};
}
public static IBuilder Property(string propertyName, string propertyValue)
{
return new PropertyBuilder
{
Name = propertyName,
Value = propertyValue
};
}
public static IBuilder Sign
{
get
{
return new SignBuilder();
}
}
public static IBuilder Document(
object content = null,
string filePath = null,
string itemType = "Compile")
{
return new DocumentBuilder
{
FilePath = filePath,
Content = content,
ItemType = itemType
};
}
private static string GetSolutionContent(IEnumerable<ProjectBuilder> projects)
{
var sb = new StringBuilder();
foreach (var project in projects)
{
var fileName = project.Name + project.Extension;
var languageGuid = project.Language == LanguageNames.VisualBasic
? "{F184B08F-C81C-45F6-A57F-5ABD9991F28F}"
: "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";
sb.AppendLine(
string.Format(
@"Project(""{0}"") = ""{1}"", ""{2}"", ""{3}""",
languageGuid,
project.Name,
fileName,
project.Guid.ToString("B")));
sb.AppendLine("EndProject");
}
return string.Format(SolutionTemplate, sb.ToString());
}
public interface IBuilder
{
}
private class ProjectBuilder : IBuilder
{
public string Name { get; set; }
public string Language { get; set; }
public Guid Guid { get; set; }
public string OutputType { get; set; }
public string OutputPath { get; set; }
public IEnumerable<DocumentBuilder> Documents { get; set; }
public IEnumerable<ProjectReferenceBuilder> ProjectReferences { get; set; }
public IEnumerable<PropertyBuilder> Properties { get; set; }
public string Extension
{
get
{
return Language == LanguageNames.VisualBasic ? ".vbproj" : ".csproj";
}
}
public IEnumerable<(string fileName, object fileContent)> Files
{
get
{
foreach (var document in Documents)
{
yield return (document.FilePath, document.Content);
}
yield return (Name + Extension, GetProjectContent());
}
}
private string GetProjectContent()
{
if (Language == LanguageNames.VisualBasic)
{
throw new NotImplementedException("Need VB support");
}
if (Guid == Guid.Empty)
{
Guid = Guid.NewGuid();
}
if (string.IsNullOrEmpty(OutputType))
{
OutputType = "Library";
}
if (string.IsNullOrEmpty(OutputPath))
{
OutputPath = ".";
}
var document = XDocument.Parse(CSharpProjectTemplate);
var propertyGroup = document.Root.Descendants(XName.Get("PropertyGroup", NS)).First();
AddXElement(propertyGroup, "ProjectGuid", Guid.ToString("B"));
AddXElement(propertyGroup, "OutputType", OutputType);
AddXElement(propertyGroup, "OutputPath", OutputPath);
AddXElement(propertyGroup, "AssemblyName", Name);
if (Properties != null)
{
foreach (var property in Properties)
{
AddXElement(propertyGroup, property.Name, property.Value);
}
}
var importTargets = document.Root.Elements().Last();
if (ProjectReferences != null && ProjectReferences.Any())
{
AddItemGroup(
importTargets,
_ => "ProjectReference",
ProjectReferences,
i => i.ProjectFileName,
(projectReference, xmlElement) =>
{
if (projectReference.Guid != Guid.Empty)
{
AddXElement(xmlElement, "Project", projectReference.Guid.ToString("B"));
}
AddXElement(xmlElement, "Name", Path.GetFileNameWithoutExtension(projectReference.ProjectName));
});
}
if (Documents != null)
{
AddItemGroup(
importTargets,
i => i.ItemType,
Documents,
i => i.FilePath);
}
return document.ToString();
}
private static void AddItemGroup<T>(
XElement addBefore,
Func<T, string> itemTypeSelector,
IEnumerable<T> items,
Func<T, string> attributeValueGetter,
Action<T, XElement> elementModifier = null)
{
var itemGroup = CreateXElement("ItemGroup");
addBefore.AddBeforeSelf(itemGroup);
foreach (var item in items)
{
var itemElement = CreateXElement(itemTypeSelector(item));
itemElement.SetAttributeValue("Include", attributeValueGetter(item));
elementModifier?.Invoke(item, itemElement);
itemGroup.Add(itemElement);
}
}
private static XElement CreateXElement(string name)
{
return new XElement(XName.Get(name, NS));
}
private static void AddXElement(XElement element, string elementName, string elementValue)
{
element.Add(new XElement(XName.Get(elementName, NS), elementValue));
}
}
private class ProjectReferenceBuilder : IBuilder
{
public string ProjectName { get; set; }
public Guid Guid { get; set; }
public string ProjectFileName { get; set; }
}
private class ProjectNameBuilder : IBuilder
{
public string Name { get; set; }
}
private class PropertyBuilder : IBuilder
{
public string Name { get; set; }
public string Value { get; set; }
}
private class SignBuilder : IBuilder { }
private class DocumentBuilder : IBuilder
{
public string FilePath { get; set; }
public object Content { get; set; }
public string ItemType { get; set; }
}
}
}
|