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