Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,19 @@ public interface IProjectFileWriter
/// <param name="module">The module being decompiled.</param>
void Write(TextWriter target, IProjectInfoProvider project, IEnumerable<ProjectItemInfo> files, MetadataFile module);
}
public static class ProjectFileWriterExtensions
{
extension(IProjectFileWriter)
{
/// <summary>
/// Gets an instance of <see cref="IProjectFileWriter"/> based on the specified <paramref name="settings"/>.
/// </summary>
/// <param name="settings">The settings to use for getting the project file writer.</param>
/// <returns>An instance of <see cref="IProjectFileWriter"/>.</returns>
public static IProjectFileWriter FromSettings(DecompilerSettings settings)
{
return settings.UseSdkStyleProjectFormat ? ProjectFileWriterSdkStyle.Default : ProjectFileWriterDefault.Instance;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// <summary>
/// A <see cref="IProjectFileWriter"/> implementation that creates the projects in the default format.
/// </summary>
sealed class ProjectFileWriterDefault : IProjectFileWriter
public sealed class ProjectFileWriterDefault : IProjectFileWriter
{
/// <summary>
/// Creates a new instance of the <see cref="ProjectFileWriterDefault"/> class.
/// Gets the singleton instance of the <see cref="ProjectFileWriterDefault"/> class.
/// </summary>
/// <returns>A new instance of the <see cref="ProjectFileWriterDefault"/> class.</returns>
public static IProjectFileWriter Create() => new ProjectFileWriterDefault();
public static ProjectFileWriterDefault Instance { get; } = new();
Comment thread
siegfriedpammer marked this conversation as resolved.

ProjectFileWriterDefault()
{
}

/// <inheritdoc />
public void Write(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Util;

#nullable enable

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
public class ProjectFileWriterSdkStyle : IProjectFileWriter
{
const string AspNetCorePrefix = "Microsoft.AspNetCore";
const string PresentationFrameworkName = "PresentationFramework";
Expand All @@ -57,10 +59,9 @@ sealed class ProjectFileWriterSdkStyle : IProjectFileWriter
enum ProjectType { Default, WinForms, Wpf, Web }

/// <summary>
/// Creates a new instance of the <see cref="ProjectFileWriterSdkStyle"/> class.
/// Gets the default 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();
public static ProjectFileWriterSdkStyle Default { get; } = new();

/// <inheritdoc />
public void Write(
Expand All @@ -76,36 +77,49 @@ public void Write(
}
}

static void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable<ProjectItemInfo> files, MetadataFile module)
void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable<ProjectItemInfo> files, MetadataFile module)
{
xml.WriteStartElement("Project");

var projectType = GetProjectType(module);
xml.WriteAttributeString("Sdk", GetSdkString(projectType));

PlaceIntoTag("PropertyGroup", xml, () => WriteAssemblyInfo(xml, module, project, projectType));
PlaceIntoTag("PropertyGroup", xml, () => WriteProjectInfo(xml, project));
PlaceIntoTag("PropertyGroup", xml, () => WriteMiscellaneousPropertyGroup(xml, files));
PlaceIntoTag("ItemGroup", xml, () => WriteResources(xml, files));
PlaceIntoTag("ItemGroup", xml, () => WriteReferences(xml, module, project, projectType));

xml.WriteEndElement();
}

static void PlaceIntoTag(string tagName, XmlTextWriter xml, Action content)
{
xml.WriteStartElement(tagName);
try
using (new Group(xml, "PropertyGroup"))
{
content();
WriteAssemblyInfo(xml, module, project, projectType);
}
finally
using (new Group(xml, "PropertyGroup"))
{
xml.WriteEndElement();
WriteProjectInfo(xml, project);
}
using (new Group(xml, "PropertyGroup"))
{
WriteMiscellaneousPropertyGroup(xml, files);
}
var customProperties = GetCustomProperties(project, files, module);
if (customProperties != null)
{
using (new Group(xml, "PropertyGroup"))
{
foreach (var (name, value) in customProperties)
{
xml.WriteElementString(name, value);
}
}
}
Comment thread
siegfriedpammer marked this conversation as resolved.
using (new Group(xml, "ItemGroup"))
{
WriteResources(xml, files);
}
using (new Group(xml, "ItemGroup"))
{
WriteReferences(xml, module, project);
}

xml.WriteEndElement();
}

static void WriteAssemblyInfo(XmlTextWriter xml, MetadataFile module, IProjectInfoProvider project, ProjectType projectType)
void WriteAssemblyInfo(XmlTextWriter xml, MetadataFile module, IProjectInfoProvider project, ProjectType projectType)
{
xml.WriteElementString("AssemblyName", module.Name);

Expand All @@ -116,9 +130,9 @@ static void WriteAssemblyInfo(XmlTextWriter xml, MetadataFile module, IProjectIn
CorFlags flags;
if (module is PEFile { Reader.PEHeaders: var headers } peFile)
{
WriteOutputType(xml, headers.IsDll, headers.PEHeader.Subsystem, projectType);
WriteOutputType(xml, headers.IsDll, headers.PEHeader!.Subsystem, projectType);
platformName = TargetServices.GetPlatformName(peFile);
flags = headers.CorHeader.Flags;
flags = headers.CorHeader!.Flags;
}
else
{
Expand All @@ -129,16 +143,7 @@ static void WriteAssemblyInfo(XmlTextWriter xml, MetadataFile module, IProjectIn

WriteDesktopExtensions(xml, projectType);

var targetFramework = TargetServices.DetectTargetFramework(module);
if (targetFramework.Identifier == ".NETFramework" && targetFramework.VersionNumber == 200)
targetFramework = TargetServices.DetectTargetFrameworkNET20(module, project.AssemblyResolver, targetFramework);

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);
xml.WriteElementString("TargetFramework", GetTargetFrameworkMoniker(module, project));

// 'AnyCPU' is default, so only need to specify platform if it differs
if (platformName != AnyCpuString)
Expand All @@ -152,6 +157,27 @@ static void WriteAssemblyInfo(XmlTextWriter xml, MetadataFile module, IProjectIn
}
}

/// <summary>
/// Gets the target framework moniker for the specified module and project.
/// </summary>
/// <param name="module">The module for which to get the target framework moniker.</param>
/// <param name="project">The project information provider.</param>
/// <returns>The target framework moniker.</returns>
/// <exception cref="NotSupportedException">Thrown if the target framework moniker cannot be determined.</exception>
protected virtual string GetTargetFrameworkMoniker(MetadataFile module, IProjectInfoProvider project)
{
var targetFramework = TargetServices.DetectTargetFramework(module);
if (targetFramework.Identifier == ".NETFramework" && targetFramework.VersionNumber == 200)
targetFramework = TargetServices.DetectTargetFrameworkNET20(module, project.AssemblyResolver, targetFramework);

if (targetFramework.Moniker == null)
{
throw new NotSupportedException($"Cannot decompile this assembly to a SDK style project. Use default project format instead.");
}

return targetFramework.Moniker;
}

static void WriteOutputType(XmlTextWriter xml, bool isDll, Subsystem moduleSubsystem, ProjectType projectType)
{
if (!isDll)
Expand Down Expand Up @@ -216,6 +242,18 @@ static void WriteMiscellaneousPropertyGroup(XmlTextWriter xml, IEnumerable<Proje
// TODO: We should add CustomToolNamespace for resources, otherwise we should add empty RootNamespace
}

/// <summary>
/// Gets custom properties to be added to the project file. Override this method to provide additional properties.
/// </summary>
/// <param name="project">The project information provider.</param>
/// <param name="files">The collection of project item information.</param>
/// <param name="module">The metadata file representing the module.</param>
/// <returns>An enumerable of custom properties as name-value pairs. Null if no custom properties are provided.</returns>
protected virtual IEnumerable<(string, string)>? GetCustomProperties(IProjectInfoProvider project, IEnumerable<ProjectItemInfo> files, MetadataFile module)
{
return null;
}

static void WriteResources(XmlTextWriter xml, IEnumerable<ProjectItemInfo> files)
{
// remove phase
Expand Down Expand Up @@ -251,14 +289,28 @@ static void WriteResources(XmlTextWriter xml, IEnumerable<ProjectItemInfo> files
}
}

static void WriteReferences(XmlTextWriter xml, MetadataFile module, IProjectInfoProvider project, ProjectType projectType)
void WriteReferences(XmlTextWriter xml, MetadataFile module, IProjectInfoProvider project)
{
foreach (var reference in GetReferences(module, project))
{
WriteReference(xml, reference, project);
}
}

/// <summary>
/// Gets the assembly references for the specified module and project, excluding implicit references and shared assemblies.
/// </summary>
/// <param name="module">The module for which to get the assembly references.</param>
/// <param name="project">The project information provider.</param>
/// <returns>An enumerable of assembly references.</returns>
protected virtual IEnumerable<AssemblyReference> GetReferences(MetadataFile module, IProjectInfoProvider project)
{
bool isNetCoreApp = TargetServices.DetectTargetFramework(module).Identifier == ".NETCoreApp";
var targetPacks = new HashSet<string>();
if (isNetCoreApp)
{
targetPacks.Add("Microsoft.NETCore.App");
switch (projectType)
switch (GetProjectType(module))
{
case ProjectType.WinForms:
case ProjectType.Wpf:
Expand All @@ -270,25 +322,38 @@ static void WriteReferences(XmlTextWriter xml, MetadataFile module, IProjectInfo
break;
}
}

foreach (var reference in module.AssemblyReferences.Where(r => !ImplicitReferences.Contains(r.Name)))
foreach (var reference in module.AssemblyReferences)
{
if (isNetCoreApp && project.AssemblyReferenceClassifier.IsSharedAssembly(reference, out string runtimePack) && targetPacks.Contains(runtimePack))
if (ImplicitReferences.Contains(reference.Name))
{
continue;
}

xml.WriteStartElement("Reference");
xml.WriteAttributeString("Include", reference.Name);

var asembly = project.AssemblyResolver.Resolve(reference);
if (asembly != null && !project.AssemblyReferenceClassifier.IsGacAssembly(reference))
if (isNetCoreApp && project.AssemblyReferenceClassifier.IsSharedAssembly(reference, out string? runtimePack) && targetPacks.Contains(runtimePack))
{
xml.WriteElementString("HintPath", FileUtility.GetRelativePath(project.TargetDirectory, asembly.FileName));
continue;
}
yield return reference;
}
}

xml.WriteEndElement();
/// <summary>
/// Writes an assembly reference to the project file.
/// </summary>
/// <param name="xml">The XML writer used to write the project file.</param>
/// <param name="reference">The assembly reference to write.</param>
/// <param name="project">The project information provider.</param>
protected virtual void WriteReference(XmlTextWriter xml, AssemblyReference reference, IProjectInfoProvider project)
{
xml.WriteStartElement("Reference");
xml.WriteAttributeString("Include", reference.Name);

var assembly = project.AssemblyResolver.Resolve(reference);
if (assembly != null && !project.AssemblyReferenceClassifier.IsGacAssembly(reference))
{
xml.WriteElementString("HintPath", FileUtility.GetRelativePath(project.TargetDirectory, assembly.FileName));
}

xml.WriteEndElement();
}

static string GetSdkString(ProjectType projectType)
Expand Down Expand Up @@ -327,5 +392,21 @@ static ProjectType GetProjectType(MetadataFile module)

return ProjectType.Default;
}

readonly struct Group : IDisposable
{
readonly XmlTextWriter xml;

public Group(XmlTextWriter xml, string name)
{
this.xml = xml;
xml.WriteStartElement(name);
}

public void Dispose()
{
xml.WriteEndElement();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ protected WholeProjectDecompiler(
AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver));
AssemblyReferenceClassifier = assemblyReferenceClassifier ?? new AssemblyReferenceClassifier();
DebugInfoProvider = debugInfoProvider;
this.projectWriter = projectWriter ?? (Settings.UseSdkStyleProjectFormat ? ProjectFileWriterSdkStyle.Create() : ProjectFileWriterDefault.Create());
this.projectWriter = projectWriter ?? IProjectFileWriter.FromSettings(settings);
Comment thread
ds5678 marked this conversation as resolved.
}

// per-run members
Expand Down
Loading