From 05eb2cdf049abfadb975a2eb6a403848fa3f0bd7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 15 Apr 2022 19:43:31 +0200 Subject: [PATCH] Fix #2363: CLI support for generating a solution from multiple projects (based on code provided by @marwie in #2364) --- ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs | 91 +++++++++++++------- ICSharpCode.ILSpyCmd/ValidationAttributes.cs | 67 ++++++++++++-- 2 files changed, 124 insertions(+), 34 deletions(-) diff --git a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs index db24859ad..8debddd13 100644 --- a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs +++ b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs @@ -15,11 +15,11 @@ using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Disassembler; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpyX.PdbProvider; using McMaster.Extensions.CommandLineUtils; -// ReSharper disable All namespace ICSharpCode.ILSpyCmd { @@ -36,12 +36,11 @@ Remarks: { public static int Main(string[] args) => CommandLineApplication.Execute(args); - [FileExists] + [FilesExist] [Required] - [Argument(0, "Assembly file name", "The assembly that is being decompiled. This argument is mandatory.")] - public string InputAssemblyName { get; } + [Argument(0, "Assembly file name(s)", "The list of assemblies that is being decompiled. This argument is mandatory.")] + public string[] InputAssemblyNames { get; } - [DirectoryExists] [Option("-o|--outputdir ", "The output directory, if omitted decompiler output is written to standard out.", CommandOptionType.SingleValue)] public string OutputDirectory { get; } @@ -98,74 +97,108 @@ Remarks: TextWriter output = System.Console.Out; bool outputDirectorySpecified = !string.IsNullOrEmpty(OutputDirectory); + if (outputDirectorySpecified) + { + Directory.CreateDirectory(OutputDirectory); + } + try { if (CreateCompilableProjectFlag) { - return DecompileAsProject(InputAssemblyName, OutputDirectory); + if (InputAssemblyNames.Length == 1) + { + string projectFileName = Path.Combine(Environment.CurrentDirectory, OutputDirectory, Path.GetFileNameWithoutExtension(InputAssemblyNames[0]) + ".csproj"); + DecompileAsProject(InputAssemblyNames[0], projectFileName); + return 0; + } + var projects = new List(); + foreach (var file in InputAssemblyNames) + { + string projectFileName = Path.Combine(Environment.CurrentDirectory, OutputDirectory, Path.GetFileNameWithoutExtension(file), Path.GetFileNameWithoutExtension(file) + ".csproj"); + Directory.CreateDirectory(Path.GetDirectoryName(projectFileName)); + ProjectId projectId = DecompileAsProject(file, projectFileName); + projects.Add(new ProjectItem(projectFileName, projectId.PlatformName, projectId.Guid, projectId.TypeGuid)); + } + SolutionCreator.WriteSolutionFile(Path.Combine(Environment.CurrentDirectory, OutputDirectory, Path.GetFileNameWithoutExtension(OutputDirectory) + ".sln"), projects); + return 0; } - else if (EntityTypes.Any()) + else + { + foreach (var file in InputAssemblyNames) + { + int result = PerformPerFileAction(file); + if (result != 0) + return result; + } + return 0; + } + } + catch (Exception ex) + { + app.Error.WriteLine(ex.ToString()); + return ProgramExitCodes.EX_SOFTWARE; + } + finally + { + output.Close(); + } + + int PerformPerFileAction(string fileName) + { + if (EntityTypes.Any()) { var values = EntityTypes.SelectMany(v => v.Split(',', ';')).ToArray(); HashSet kinds = TypesParser.ParseSelection(values); if (outputDirectorySpecified) { - string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName); + string outputName = Path.GetFileNameWithoutExtension(fileName); output = File.CreateText(Path.Combine(OutputDirectory, outputName) + ".list.txt"); } - return ListContent(InputAssemblyName, output, kinds); + return ListContent(fileName, output, kinds); } else if (ShowILCodeFlag || ShowILSequencePointsFlag) { if (outputDirectorySpecified) { - string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName); + string outputName = Path.GetFileNameWithoutExtension(fileName); output = File.CreateText(Path.Combine(OutputDirectory, outputName) + ".il"); } - return ShowIL(InputAssemblyName, output); + return ShowIL(fileName, output); } else if (CreateDebugInfoFlag) { string pdbFileName = null; if (outputDirectorySpecified) { - string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName); + string outputName = Path.GetFileNameWithoutExtension(fileName); pdbFileName = Path.Combine(OutputDirectory, outputName) + ".pdb"; } else { - pdbFileName = Path.ChangeExtension(InputAssemblyName, ".pdb"); + pdbFileName = Path.ChangeExtension(fileName, ".pdb"); } - return GeneratePdbForAssembly(InputAssemblyName, pdbFileName, app); + return GeneratePdbForAssembly(fileName, pdbFileName, app); } else if (DumpPackageFlag) { - return DumpPackageAssemblies(InputAssemblyName, OutputDirectory, app); + return DumpPackageAssemblies(fileName, OutputDirectory, app); } else { if (outputDirectorySpecified) { - string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName); + string outputName = Path.GetFileNameWithoutExtension(fileName); output = File.CreateText(Path.Combine(OutputDirectory, (string.IsNullOrEmpty(TypeName) ? outputName : TypeName) + ".decompiled.cs")); } - return Decompile(InputAssemblyName, output, TypeName); + return Decompile(fileName, output, TypeName); } } - catch (Exception ex) - { - app.Error.WriteLine(ex.ToString()); - return ProgramExitCodes.EX_SOFTWARE; - } - finally - { - output.Close(); - } } DecompilerSettings GetSettings(PEFile module) @@ -217,7 +250,7 @@ Remarks: return 0; } - int DecompileAsProject(string assemblyFileName, string outputDirectory) + ProjectId DecompileAsProject(string assemblyFileName, string projectFileName) { var module = new PEFile(assemblyFileName); var resolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Metadata.DetectTargetFrameworkId()); @@ -226,8 +259,8 @@ Remarks: resolver.AddSearchDirectory(path); } var decompiler = new WholeProjectDecompiler(GetSettings(module), resolver, resolver, TryLoadPDB(module)); - decompiler.DecompileProject(module, outputDirectory); - return 0; + using (var projectFileWriter = new StreamWriter(File.OpenWrite(projectFileName))) + return decompiler.DecompileProject(module, Path.GetDirectoryName(projectFileName), projectFileWriter); } int Decompile(string assemblyFileName, TextWriter output, string typeName = null) diff --git a/ICSharpCode.ILSpyCmd/ValidationAttributes.cs b/ICSharpCode.ILSpyCmd/ValidationAttributes.cs index dd758563b..7ddd2ce80 100644 --- a/ICSharpCode.ILSpyCmd/ValidationAttributes.cs +++ b/ICSharpCode.ILSpyCmd/ValidationAttributes.cs @@ -2,6 +2,9 @@ using System.ComponentModel.DataAnnotations; using System.IO; +using McMaster.Extensions.CommandLineUtils.Abstractions; +using McMaster.Extensions.CommandLineUtils.Validation; + namespace ICSharpCode.ILSpyCmd { [AttributeUsage(AttributeTargets.Class)] @@ -27,14 +30,68 @@ namespace ICSharpCode.ILSpyCmd [AttributeUsage(AttributeTargets.Property)] public sealed class FileExistsOrNullAttribute : ValidationAttribute { - protected override ValidationResult IsValid(object value, ValidationContext context) + protected override ValidationResult IsValid(object value, ValidationContext validationContext) { - var s = value as string; - if (string.IsNullOrEmpty(s)) + var path = value as string; + if (string.IsNullOrEmpty(path)) + { return ValidationResult.Success; - if (File.Exists(s)) + } + + if (!Path.IsPathRooted(path) && validationContext.GetService(typeof(CommandLineContext)) is CommandLineContext context) + { + path = Path.Combine(context.WorkingDirectory, path); + } + + if (File.Exists(path)) + { return ValidationResult.Success; - return new ValidationResult($"File '{s}' does not exist!"); + } + + return new ValidationResult($"File '{path}' does not exist!"); + } + } + + [AttributeUsage(AttributeTargets.Property)] + public sealed class FilesExistAttribute : ValidationAttribute + { + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + switch (value) + { + case string path: + return ValidatePath(path); + case string[] paths: + { + foreach (string path in paths) + { + ValidationResult result = ValidatePath(path); + if (result != ValidationResult.Success) + return result; + } + return ValidationResult.Success; + } + default: + return new ValidationResult($"File '{value}' does not exist!"); + } + + ValidationResult ValidatePath(string path) + { + if (!string.IsNullOrWhiteSpace(path)) + { + if (!Path.IsPathRooted(path) && validationContext.GetService(typeof(CommandLineContext)) is CommandLineContext context) + { + path = Path.Combine(context.WorkingDirectory, path); + } + + if (File.Exists(path)) + { + return ValidationResult.Success; + } + } + + return new ValidationResult($"File '{path}' does not exist!"); + } } } }