Browse Source

Implement project writer for SDK style projects

pull/2031/head
dymanoid 5 years ago
parent
commit
bfb57da93b
  1. 29
      ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs
  2. 13
      ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
  3. 212
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs
  4. 52
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs
  5. 2
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
  6. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

29
ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs

@ -88,5 +88,34 @@ namespace ICSharpCode.Decompiler.Tests @@ -88,5 +88,34 @@ namespace ICSharpCode.Decompiler.Tests
Assert.AreEqual(identifier, targetFramework.Identifier);
Assert.AreEqual(profile, targetFramework.Profile);
}
[TestCase(null, 350, "net35")]
[TestCase(".NETFramework", 350, "net35")]
[TestCase(".NETFramework", 400, "net40")]
[TestCase(".NETFramework", 451, "net451")]
[TestCase(".NETCoreApp", 200, "netcoreapp2.0")]
[TestCase(".NETCoreApp", 310, "netcoreapp3.1")]
[TestCase(".NETStandard", 130, "netstandard1.3")]
[TestCase(".NETStandard", 200, "netstandard2.0")]
[TestCase("Silverlight", 400, "sl4")]
[TestCase("Silverlight", 550, "sl5")]
[TestCase(".NETCore", 450, "netcore45")]
[TestCase(".NETCore", 451, "netcore451")]
[TestCase("WindowsPhone", 700, "wp7")]
[TestCase("WindowsPhone", 810, "wp81")]
[TestCase(".NETMicroFramework", 100, "netmf")]
[TestCase(".NETMicroFramework", 210, "netmf")]
[TestCase(".NETPortable", 100, null)]
[TestCase("Unsupported", 100, null)]
public void VerifyMoniker(string identifier, int version, string expectedMoniker)
{
// Arrange - nothing
// Act
var targetFramework = new TargetFramework(identifier, version, profile: null);
// Assert
Assert.AreEqual(expectedMoniker, targetFramework.Moniker);
}
}
}

13
ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs

@ -55,7 +55,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -55,7 +55,7 @@ namespace ICSharpCode.Decompiler.Tests
public void NewtonsoftJson_pcl_debug()
{
try {
RunWithTest("Newtonsoft.Json-pcl-debug", "Newtonsoft.Json.dll", "Newtonsoft.Json.Tests.dll");
RunWithTest("Newtonsoft.Json-pcl-debug", "Newtonsoft.Json.dll", "Newtonsoft.Json.Tests.dll", useOldProjectFormat: true);
} catch (CompilationFailedException) {
Assert.Ignore("Cannot yet re-compile PCL projects.");
}
@ -103,9 +103,9 @@ namespace ICSharpCode.Decompiler.Tests @@ -103,9 +103,9 @@ namespace ICSharpCode.Decompiler.Tests
RunWithOutput("Random Tests\\TestCases", "TestCase-1.exe");
}
void RunWithTest(string dir, string fileToRoundtrip, string fileToTest, string keyFile = null)
void RunWithTest(string dir, string fileToRoundtrip, string fileToTest, string keyFile = null, bool useOldProjectFormat = false)
{
RunInternal(dir, fileToRoundtrip, outputDir => RunTest(outputDir, fileToTest), keyFile);
RunInternal(dir, fileToRoundtrip, outputDir => RunTest(outputDir, fileToTest), keyFile, useOldProjectFormat);
}
void RunWithOutput(string dir, string fileToRoundtrip)
@ -120,7 +120,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -120,7 +120,7 @@ namespace ICSharpCode.Decompiler.Tests
RunInternal(dir, fileToRoundtrip, outputDir => { });
}
void RunInternal(string dir, string fileToRoundtrip, Action<string> testAction, string snkFilePath = null)
void RunInternal(string dir, string fileToRoundtrip, Action<string> testAction, string snkFilePath = null, bool useOldProjectFormat = false)
{
if (!Directory.Exists(TestDir)) {
Assert.Ignore($"Assembly-roundtrip test ignored: test directory '{TestDir}' needs to be checked out separately." + Environment.NewLine +
@ -158,6 +158,9 @@ namespace ICSharpCode.Decompiler.Tests @@ -158,6 +158,9 @@ namespace ICSharpCode.Decompiler.Tests
// Let's limit the roundtrip tests to C# 7.3 for now; because 8.0 is still in preview
// and the generated project doesn't build as-is.
var settings = new DecompilerSettings(LanguageVersion.CSharp7_3);
if (useOldProjectFormat) {
settings.UseSdkStyleProjectFormat = false;
}
var decompiler = new TestProjectDecompiler(projectGuid, resolver, settings);
@ -212,7 +215,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -212,7 +215,7 @@ namespace ICSharpCode.Decompiler.Tests
static void Compile(string projectFile, string outputDir)
{
var info = new ProcessStartInfo(FindMSBuild());
info.Arguments = $"/nologo /v:minimal /p:OutputPath=\"{outputDir}\" \"{projectFile}\"";
info.Arguments = $"/nologo /v:minimal /restore /p:OutputPath=\"{outputDir}\" \"{projectFile}\"";
info.CreateNoWindow = true;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;

212
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs

@ -0,0 +1,212 @@ @@ -0,0 +1,212 @@
// Copyright (c) 2020 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Xml;
using ICSharpCode.Decompiler.Metadata;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
/// <summary>
/// A <see cref="IProjectFileWriter"/> implementation that creates the projects in the SDK style format.
/// </summary>
sealed class ProjectFileWriterSdkStyle : IProjectFileWriter
{
const string AspNetCorePrefix = "Microsoft.AspNetCore";
const string PresentationFrameworkName = "PresentationFramework";
const string WindowsFormsName = "System.Windows.Forms";
const string TrueString = "True";
const string FalseString = "False";
const string AnyCpuString = "AnyCPU";
static readonly HashSet<string> ImplicitReferences = new HashSet<string> {
"mscorlib",
"netstandard",
"PresentationFramework",
"System",
"System.Diagnostics.Debug",
"System.Diagnostics.Tools",
"System.Drawing",
"System.Runtime",
"System.Runtime.Extensions",
"System.Windows.Forms",
"System.Xaml",
};
enum ProjectType { Default, WinForms, Wpf, Web }
/// <summary>
/// Creates a new instance of the <see cref="ProjectFileWriterSdkStyle"/> class.
/// </summary>
/// <returns>A new instance of the <see cref="ProjectFileWriterSdkStyle"/> class.</returns>
public static IProjectFileWriter Create() => new ProjectFileWriterSdkStyle();
/// <inheritdoc />
public void Write(
TextWriter target,
IProjectInfoProvider project,
IEnumerable<(string itemType, string fileName)> files,
PEFile module)
{
using (XmlTextWriter xmlWriter = new XmlTextWriter(target)) {
xmlWriter.Formatting = Formatting.Indented;
Write(xmlWriter, project, module);
}
}
static void Write(XmlTextWriter xml, IProjectInfoProvider project, PEFile module)
{
xml.WriteStartElement("Project");
var projectType = GetProjectType(module);
xml.WriteAttributeString("Sdk", GetSdkString(projectType));
PlaceIntoTag("PropertyGroup", xml, () => WriteAssemblyInfo(xml, module, projectType));
PlaceIntoTag("PropertyGroup", xml, () => WriteProjectInfo(xml, project));
PlaceIntoTag("ItemGroup", xml, () => WriteReferences(xml, module, project));
xml.WriteEndElement();
}
static void PlaceIntoTag(string tagName, XmlTextWriter xml, Action content)
{
xml.WriteStartElement(tagName);
try {
content();
} finally {
xml.WriteEndElement();
}
}
static void WriteAssemblyInfo(XmlTextWriter xml, PEFile module, ProjectType projectType)
{
xml.WriteElementString("AssemblyName", module.Name);
// Since we create AssemblyInfo.cs manually, we need to disable the auto-generation
xml.WriteElementString("GenerateAssemblyInfo", FalseString);
// 'Library' is default, so only need to specify output type for executables
if (!module.Reader.PEHeaders.IsDll) {
WriteOutputType(xml, module.Reader.PEHeaders.PEHeader.Subsystem);
}
WriteDesktopExtensions(xml, projectType);
string platformName = TargetServices.GetPlatformName(module);
var targetFramework = TargetServices.DetectTargetFramework(module);
if (targetFramework.Moniker == null) {
throw new NotSupportedException($"Cannot decompile this assembly to a SDK style project. Use default project format instead.");
}
xml.WriteElementString("TargetFramework", targetFramework.Moniker);
// 'AnyCPU' is default, so only need to specify platform if it differs
if (platformName != AnyCpuString) {
xml.WriteElementString("PlatformTarget", platformName);
}
if (platformName == AnyCpuString && (module.Reader.PEHeaders.CorHeader.Flags & CorFlags.Prefers32Bit) != 0) {
xml.WriteElementString("Prefer32Bit", TrueString);
}
}
static void WriteOutputType(XmlTextWriter xml, Subsystem moduleSubsystem)
{
switch (moduleSubsystem) {
case Subsystem.WindowsGui:
xml.WriteElementString("OutputType", "WinExe");
break;
case Subsystem.WindowsCui:
xml.WriteElementString("OutputType", "Exe");
break;
}
}
static void WriteDesktopExtensions(XmlTextWriter xml, ProjectType projectType)
{
if (projectType == ProjectType.Wpf) {
xml.WriteElementString("UseWPF", TrueString);
} else if (projectType == ProjectType.WinForms) {
xml.WriteElementString("UseWindowsForms", TrueString);
}
}
static void WriteProjectInfo(XmlTextWriter xml, IProjectInfoProvider project)
{
xml.WriteElementString("LangVersion", project.LanguageVersion.ToString().Replace("CSharp", "").Replace('_', '.'));
xml.WriteElementString("AllowUnsafeBlocks", TrueString);
if (project.StrongNameKeyFile != null) {
xml.WriteElementString("SignAssembly", TrueString);
xml.WriteElementString("AssemblyOriginatorKeyFile", Path.GetFileName(project.StrongNameKeyFile));
}
}
static void WriteReferences(XmlTextWriter xml, PEFile module, IProjectInfoProvider project)
{
foreach (var reference in module.AssemblyReferences.Where(r => !ImplicitReferences.Contains(r.Name))) {
xml.WriteStartElement("Reference");
xml.WriteAttributeString("Include", reference.Name);
var asembly = project.AssemblyResolver.Resolve(reference);
if (asembly != null) {
xml.WriteElementString("HintPath", asembly.FileName);
}
xml.WriteEndElement();
}
}
static string GetSdkString(ProjectType projectType)
{
switch (projectType) {
case ProjectType.WinForms:
case ProjectType.Wpf:
return "Microsoft.NET.Sdk.WindowsDesktop";
case ProjectType.Web:
return "Microsoft.NET.Sdk.Web";
default:
return "Microsoft.NET.Sdk";
}
}
static ProjectType GetProjectType(PEFile module)
{
foreach (var referenceName in module.AssemblyReferences.Select(r => r.Name)) {
if (referenceName.StartsWith(AspNetCorePrefix, StringComparison.Ordinal)) {
return ProjectType.Web;
}
if (referenceName == PresentationFrameworkName) {
return ProjectType.Wpf;
}
if (referenceName == WindowsFormsName) {
return ProjectType.WinForms;
}
}
return ProjectType.Default;
}
}
}

52
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs

@ -42,7 +42,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -42,7 +42,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
Identifier = identifier;
VersionNumber = version;
VersionString = "v" + GetVersionString(version);
VersionString = "v" + GetVersionString(version, withDots: true);
Moniker = GetTargetFrameworkMoniker(Identifier, version);
Profile = profile;
IsPortableClassLibrary = identifier == DotNetPortableIdentifier;
}
@ -52,6 +53,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -52,6 +53,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// </summary>
public string Identifier { get; }
/// <summary>
/// Gets the target framework moniker. Can be null if not supported.
/// </summary>
public string Moniker { get; }
/// <summary>
/// Gets the target framework version, e.g. "v4.5".
/// </summary>
@ -72,19 +78,61 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -72,19 +78,61 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// </summary>
public bool IsPortableClassLibrary { get; }
static string GetVersionString(int version)
static string GetTargetFrameworkMoniker(string frameworkIdentifier, int version)
{
// Reference: https://docs.microsoft.com/en-us/dotnet/standard/frameworks
switch (frameworkIdentifier) {
case null:
case ".NETFramework":
return "net" + GetVersionString(version, withDots: false);
case ".NETCoreApp":
return "netcoreapp" + GetVersionString(version, withDots: true);
case ".NETStandard":
return "netstandard" + GetVersionString(version, withDots: true);
case "Silverlight":
return "sl" + version / 100;
case ".NETCore":
return "netcore" + GetVersionString(version, withDots: false);
case "WindowsPhone":
return "wp" + GetVersionString(version, withDots: false, omitMinorWhenZero: true);
case ".NETMicroFramework":
return "netmf";
default:
return null;
}
}
static string GetVersionString(int version, bool withDots, bool omitMinorWhenZero = false)
{
int major = version / 100;
int minor = version % 100 / 10;
int patch = version % 10;
if (omitMinorWhenZero && minor == 0 && patch == 0) {
return major.ToString();
}
var versionBuilder = new StringBuilder(8);
versionBuilder.Append(major);
if (withDots) {
versionBuilder.Append('.');
}
versionBuilder.Append(minor);
if (patch != 0) {
if (withDots) {
versionBuilder.Append('.');
}
versionBuilder.Append(patch);
}

2
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs

@ -103,7 +103,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -103,7 +103,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
ProjectGuid = projectGuid;
AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver));
DebugInfoProvider = debugInfoProvider;
projectWriter = ProjectFileWriterDefault.Create();
projectWriter = Settings.UseSdkStyleProjectFormat ? ProjectFileWriterSdkStyle.Create() : ProjectFileWriterDefault.Create();
}
// per-run members

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -64,6 +64,7 @@ @@ -64,6 +64,7 @@
<Compile Include="CSharp\ProjectDecompiler\IProjectFileWriter.cs" />
<Compile Include="CSharp\OutputVisitor\GenericGrammarAmbiguityVisitor.cs" />
<Compile Include="CSharp\ProjectDecompiler\IProjectInfoProvider.cs" />
<Compile Include="CSharp\ProjectDecompiler\ProjectFileWriterSdkStyle.cs" />
<Compile Include="CSharp\ProjectDecompiler\ProjectFileWriterDefault.cs" />
<Compile Include="CSharp\RequiredNamespaceCollector.cs" />
<Compile Include="CSharp\SequencePointBuilder.cs" />

Loading…
Cancel
Save