diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs
index f9c1c33512..ebbe969172 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs
@@ -38,4 +38,19 @@ public interface IProjectFileWriter
/// The module being decompiled.
void Write(TextWriter target, IProjectInfoProvider project, IEnumerable files, MetadataFile module);
}
+ public static class ProjectFileWriterExtensions
+ {
+ extension(IProjectFileWriter)
+ {
+ ///
+ /// Gets an instance of based on the specified .
+ ///
+ /// The settings to use for getting the project file writer.
+ /// An instance of .
+ public static IProjectFileWriter FromSettings(DecompilerSettings settings)
+ {
+ return settings.UseSdkStyleProjectFormat ? ProjectFileWriterSdkStyle.Default : ProjectFileWriterDefault.Instance;
+ }
+ }
+ }
}
diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
index 4447be86dd..78f815feb5 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
@@ -32,13 +32,16 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
///
/// A implementation that creates the projects in the default format.
///
- sealed class ProjectFileWriterDefault : IProjectFileWriter
+ public sealed class ProjectFileWriterDefault : IProjectFileWriter
{
///
- /// Creates a new instance of the class.
+ /// Gets the singleton instance of the class.
///
- /// A new instance of the class.
- public static IProjectFileWriter Create() => new ProjectFileWriterDefault();
+ public static ProjectFileWriterDefault Instance { get; } = new();
+
+ ProjectFileWriterDefault()
+ {
+ }
///
public void Write(
diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs
index 0f79025246..e7d75c568d 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs
@@ -26,12 +26,14 @@
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Util;
+#nullable enable
+
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
///
/// A implementation that creates the projects in the SDK style format.
///
- sealed class ProjectFileWriterSdkStyle : IProjectFileWriter
+ public class ProjectFileWriterSdkStyle : IProjectFileWriter
{
const string AspNetCorePrefix = "Microsoft.AspNetCore";
const string PresentationFrameworkName = "PresentationFramework";
@@ -57,10 +59,9 @@ sealed class ProjectFileWriterSdkStyle : IProjectFileWriter
enum ProjectType { Default, WinForms, Wpf, Web }
///
- /// Creates a new instance of the class.
+ /// Gets the default instance of the class.
///
- /// A new instance of the class.
- public static IProjectFileWriter Create() => new ProjectFileWriterSdkStyle();
+ public static ProjectFileWriterSdkStyle Default { get; } = new();
///
public void Write(
@@ -76,36 +77,49 @@ public void Write(
}
}
- static void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable files, MetadataFile module)
+ void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable 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);
+ }
+ }
+ }
+ 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);
@@ -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
{
@@ -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)
@@ -152,6 +157,27 @@ static void WriteAssemblyInfo(XmlTextWriter xml, MetadataFile module, IProjectIn
}
}
+ ///
+ /// Gets the target framework moniker for the specified module and project.
+ ///
+ /// The module for which to get the target framework moniker.
+ /// The project information provider.
+ /// The target framework moniker.
+ /// Thrown if the target framework moniker cannot be determined.
+ 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)
@@ -216,6 +242,18 @@ static void WriteMiscellaneousPropertyGroup(XmlTextWriter xml, IEnumerable
+ /// Gets custom properties to be added to the project file. Override this method to provide additional properties.
+ ///
+ /// The project information provider.
+ /// The collection of project item information.
+ /// The metadata file representing the module.
+ /// An enumerable of custom properties as name-value pairs. Null if no custom properties are provided.
+ protected virtual IEnumerable<(string, string)>? GetCustomProperties(IProjectInfoProvider project, IEnumerable files, MetadataFile module)
+ {
+ return null;
+ }
+
static void WriteResources(XmlTextWriter xml, IEnumerable files)
{
// remove phase
@@ -251,14 +289,28 @@ static void WriteResources(XmlTextWriter xml, IEnumerable 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);
+ }
+ }
+
+ ///
+ /// Gets the assembly references for the specified module and project, excluding implicit references and shared assemblies.
+ ///
+ /// The module for which to get the assembly references.
+ /// The project information provider.
+ /// An enumerable of assembly references.
+ protected virtual IEnumerable GetReferences(MetadataFile module, IProjectInfoProvider project)
{
bool isNetCoreApp = TargetServices.DetectTargetFramework(module).Identifier == ".NETCoreApp";
var targetPacks = new HashSet();
if (isNetCoreApp)
{
targetPacks.Add("Microsoft.NETCore.App");
- switch (projectType)
+ switch (GetProjectType(module))
{
case ProjectType.WinForms:
case ProjectType.Wpf:
@@ -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();
+ ///
+ /// Writes an assembly reference to the project file.
+ ///
+ /// The XML writer used to write the project file.
+ /// The assembly reference to write.
+ /// The project information provider.
+ 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)
@@ -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();
+ }
+ }
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
index 18061b9d63..067c030109 100644
--- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
@@ -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);
}
// per-run members