Browse Source

Support batch PDB generation.

pull/3619/head
sonyps5201314 1 month ago
parent
commit
3ee07e4eb1
  1. 148
      ILSpy/Commands/GeneratePdbContextMenuEntry.cs
  2. 27
      ILSpy/Properties/Resources.Designer.cs
  3. 9
      ILSpy/Properties/Resources.resx
  4. 9
      ILSpy/Properties/Resources.zh-Hans.resx

148
ILSpy/Commands/GeneratePdbContextMenuEntry.cs

@ -47,19 +47,30 @@ namespace ICSharpCode.ILSpy @@ -47,19 +47,30 @@ namespace ICSharpCode.ILSpy
{
public void Execute(TextViewContext context)
{
var assembly = (context.SelectedTreeNodes?.FirstOrDefault() as AssemblyTreeNode)?.LoadedAssembly;
if (assembly == null)
var selectedNodes = context.SelectedTreeNodes?.OfType<AssemblyTreeNode>().ToArray();
if (selectedNodes == null || selectedNodes.Length == 0)
return;
GeneratePdbForAssembly(assembly, languageService, dockWorkspace);
if (selectedNodes.Length == 1)
{
var assembly = selectedNodes.First().LoadedAssembly;
if (assembly == null)
return;
GeneratePdbForAssembly(assembly, languageService, dockWorkspace);
}
else
{
GeneratePdbForAssemblies(selectedNodes.Select(n => n.LoadedAssembly), languageService, dockWorkspace);
}
}
public bool IsEnabled(TextViewContext context) => true;
public bool IsVisible(TextViewContext context)
{
return context.SelectedTreeNodes?.Length == 1
&& context.SelectedTreeNodes?.FirstOrDefault() is AssemblyTreeNode tn
&& tn.LoadedAssembly.IsLoadedAsValidAssembly;
var selectedNodes = context.SelectedTreeNodes;
return selectedNodes?.Any() == true
&& selectedNodes.All(n => n is AssemblyTreeNode asm && asm.LoadedAssembly.IsLoadedAsValidAssembly);
}
internal static void GeneratePdbForAssembly(LoadedAssembly assembly, LanguageService languageService, DockWorkspace dockWorkspace)
@ -105,6 +116,109 @@ namespace ICSharpCode.ILSpy @@ -105,6 +116,109 @@ namespace ICSharpCode.ILSpy
return output;
}, ct)).Then(dockWorkspace.ShowText).HandleExceptions();
}
internal static void GeneratePdbForAssemblies(System.Collections.Generic.IEnumerable<LoadedAssembly> assemblies, LanguageService languageService, DockWorkspace dockWorkspace)
{
var assemblyArray = assemblies?.Where(a => a != null).ToArray();
if (assemblyArray == null || assemblyArray.Length == 0)
return;
// Ensure at least one assembly supports PDB generation
if (!assemblyArray.Any(a => PortablePdbWriter.HasCodeViewDebugDirectoryEntry(a.GetMetadataFileOrNull() as PEFile)))
{
MessageBox.Show(Resources.CannotCreatePDBFile);
return;
}
// Ask for target folder
using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
{
dlg.Description = Resources.SelectPDBOutputFolder;
dlg.RootFolder = Environment.SpecialFolder.MyComputer;
dlg.ShowNewFolderButton = true;
// Show dialog on UI thread
System.Windows.Forms.DialogResult result = dlg.ShowDialog();
if (result != System.Windows.Forms.DialogResult.OK || string.IsNullOrWhiteSpace(dlg.SelectedPath))
return;
string targetFolder = dlg.SelectedPath;
DecompilationOptions options = dockWorkspace.ActiveTabPage.CreateDecompilationOptions();
dockWorkspace.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
AvalonEditTextOutput output = new AvalonEditTextOutput();
Stopwatch totalWatch = Stopwatch.StartNew();
options.CancellationToken = ct;
int total = assemblyArray.Length;
int processed = 0;
foreach (var assembly in assemblyArray)
{
if (ct.IsCancellationRequested)
{
output.WriteLine();
output.WriteLine(Resources.GenerationWasCancelled);
throw new OperationCanceledException(ct);
}
var file = assembly.GetMetadataFileOrNull() as PEFile;
if (file == null || !PortablePdbWriter.HasCodeViewDebugDirectoryEntry(file))
{
output.WriteLine(string.Format(Resources.CannotCreatePDBFile, Path.GetFileName(assembly.FileName)));
processed++;
if (options.Progress != null)
{
options.Progress.Report(new DecompilationProgress {
Title = "Generating portable PDB...",
TotalUnits = total,
UnitsCompleted = processed
});
}
continue;
}
string fileName = Path.Combine(targetFolder, WholeProjectDecompiler.CleanUpFileName(assembly.ShortName, ".pdb"));
try
{
using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write))
{
var decompiler = new CSharpDecompiler(file, assembly.GetAssemblyResolver(options.DecompilerSettings.AutoLoadAssemblyReferences), options.DecompilerSettings);
decompiler.CancellationToken = ct;
PortablePdbWriter.WritePdb(file, decompiler, options.DecompilerSettings, stream, progress: options.Progress);
}
output.WriteLine(string.Format(Resources.GeneratedPDBFile, fileName));
}
catch (OperationCanceledException)
{
output.WriteLine();
output.WriteLine(Resources.GenerationWasCancelled);
throw;
}
catch (Exception ex)
{
output.WriteLine(string.Format(Resources.GenerationFailedForAssembly, assembly.FileName, ex.Message));
}
processed++;
if (options.Progress != null)
{
options.Progress.Report(new DecompilationProgress {
Title = "Generating portable PDB...",
TotalUnits = total,
UnitsCompleted = processed
});
}
}
totalWatch.Stop();
output.WriteLine();
output.WriteLine(Resources.GenerationCompleteInSeconds, totalWatch.Elapsed.TotalSeconds.ToString("F1"));
output.WriteLine();
output.AddButton(null, Resources.OpenExplorer, delegate { Process.Start("explorer", "/select,\"" + targetFolder + "\""); });
output.WriteLine();
return output;
}, ct)).Then(dockWorkspace.ShowText).HandleExceptions();
}
}
}
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.GeneratePortable), MenuCategory = nameof(Resources.Save))]
@ -113,17 +227,27 @@ namespace ICSharpCode.ILSpy @@ -113,17 +227,27 @@ namespace ICSharpCode.ILSpy
{
public override bool CanExecute(object parameter)
{
return assemblyTreeModel.SelectedNodes?.Count() == 1
&& assemblyTreeModel.SelectedNodes?.FirstOrDefault() is AssemblyTreeNode tn
&& !tn.LoadedAssembly.HasLoadError;
return assemblyTreeModel.SelectedNodes?.Any() == true
&& assemblyTreeModel.SelectedNodes?.All(n => n is AssemblyTreeNode tn && !tn.LoadedAssembly.HasLoadError) == true;
}
public override void Execute(object parameter)
{
var assembly = (assemblyTreeModel.SelectedNodes?.FirstOrDefault() as AssemblyTreeNode)?.LoadedAssembly;
if (assembly == null)
var selectedNodes = assemblyTreeModel.SelectedNodes?.OfType<AssemblyTreeNode>().ToArray();
if (selectedNodes == null || selectedNodes.Length == 0)
return;
GeneratePdbContextMenuEntry.GeneratePdbForAssembly(assembly, languageService, dockWorkspace);
if (selectedNodes.Length == 1)
{
var assembly = selectedNodes.First().LoadedAssembly;
if (assembly == null)
return;
GeneratePdbContextMenuEntry.GeneratePdbForAssembly(assembly, languageService, dockWorkspace);
}
else
{
GeneratePdbContextMenuEntry.GeneratePdbForAssemblies(selectedNodes.Select(n => n.LoadedAssembly), languageService, dockWorkspace);
}
}
}
}

27
ILSpy/Properties/Resources.Designer.cs generated

@ -1920,6 +1920,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1920,6 +1920,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Generated PDB: {0}.
/// </summary>
public static string GeneratedPDBFile {
get {
return ResourceManager.GetString("GeneratedPDBFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generation complete in {0} seconds..
/// </summary>
@ -1938,6 +1947,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1938,6 +1947,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Failed to generate PDB for {0}: {1}.
/// </summary>
public static string GenerationFailedForAssembly {
get {
return ResourceManager.GetString("GenerationFailedForAssembly", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Go to token.
/// </summary>
@ -2620,6 +2638,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -2620,6 +2638,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Select target folder.
/// </summary>
public static string SelectPDBOutputFolder {
get {
return ResourceManager.GetString("SelectPDBOutputFolder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select version of language to output (Alt+E).
/// </summary>

9
ILSpy/Properties/Resources.resx

@ -657,9 +657,15 @@ Are you sure you want to continue?</value> @@ -657,9 +657,15 @@ Are you sure you want to continue?</value>
<data name="GeneratePortable" xml:space="preserve">
<value>Generate portable PDB</value>
</data>
<data name="GeneratedPDBFile" xml:space="preserve">
<value>Generated PDB: {0}</value>
</data>
<data name="GenerationCompleteInSeconds" xml:space="preserve">
<value>Generation complete in {0} seconds.</value>
</data>
<data name="GenerationFailedForAssembly" xml:space="preserve">
<value>Failed to generate PDB for {0}: {1}</value>
</data>
<data name="GenerationWasCancelled" xml:space="preserve">
<value>Generation was cancelled.</value>
</data>
@ -895,6 +901,9 @@ Do you want to continue?</value> @@ -895,6 +901,9 @@ Do you want to continue?</value>
<data name="SelectPDB" xml:space="preserve">
<value>Select PDB...</value>
</data>
<data name="SelectPDBOutputFolder" xml:space="preserve">
<value>Select target folder</value>
</data>
<data name="SelectVersionDropdownTooltip" xml:space="preserve">
<value>Select version of language to output (Alt+E)</value>
</data>

9
ILSpy/Properties/Resources.zh-Hans.resx

@ -610,9 +610,15 @@ @@ -610,9 +610,15 @@
<data name="GeneratePortable" xml:space="preserve">
<value>生成 Portable PDB</value>
</data>
<data name="GeneratedPDBFile" xml:space="preserve">
<value>生成的 PDB: {0}</value>
</data>
<data name="GenerationCompleteInSeconds" xml:space="preserve">
<value>生成完成,耗时 {0} 秒。</value>
</data>
<data name="GenerationFailedForAssembly" xml:space="preserve">
<value>为 {0} 生成 PDB 失败: {1}</value>
</data>
<data name="GenerationWasCancelled" xml:space="preserve">
<value>已取消生成。</value>
</data>
@ -843,6 +849,9 @@ @@ -843,6 +849,9 @@
<data name="SelectPDB" xml:space="preserve">
<value>选择 PDB...</value>
</data>
<data name="SelectPDBOutputFolder" xml:space="preserve">
<value>选择目标文件夹</value>
</data>
<data name="SelectVersionDropdownTooltip" xml:space="preserve">
<value>选择输出语言的版本</value>
</data>

Loading…
Cancel
Save