Skip to content

Commit

Permalink
Added ConsoleTreeVisualizer. Further refactoring.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mike Hadlow committed Oct 10, 2019
1 parent e4136a5 commit f60398a
Show file tree
Hide file tree
Showing 9 changed files with 363 additions and 116 deletions.
1 change: 1 addition & 0 deletions AsmSpy.CommandLine/AsmSpy.CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
</Compile>
<Compile Include="BindingRedirectExport.cs" />
<Compile Include="ConsoleLogger.cs" />
<Compile Include="ConsoleTreeVisualizer.cs" />
<Compile Include="ConsoleVisualizer.cs" />
<Compile Include="DgmlExport.cs" />
<Compile Include="DotExport.cs" />
Expand Down
87 changes: 87 additions & 0 deletions AsmSpy.CommandLine/ConsoleTreeVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using AsmSpy.Core;
using Microsoft.Extensions.CommandLineUtils;
using System;
using System.Linq;
using static System.Console;

namespace AsmSpy.CommandLine
{
public class ConsoleTreeVisualizer : IDependencyVisualizer
{
CommandOption noConsole;

public void CreateOption(CommandLineApplication commandLineApplication)
{
noConsole = commandLineApplication.Option("-tr|--tree", "Output a console tree view of dependencies.", CommandOptionType.NoValue);
}

public bool IsConfigured() => noConsole.HasValue();

public void Visualize(DependencyAnalyzerResult result, ILogger logger, VisualizerOptions visualizerOptions)
{

foreach(var root in result.Roots)
{
WalkDependencies(root);
}


void WalkDependencies(IAssemblyReferenceInfo assembly, string tab = "", bool lastParent = true)
{
var label = lastParent ? endNodeLabel : nodeLabel;
var currentForgroundColor = ForegroundColor;
Write($"{tab}{label}");
ForegroundColor = SelectConsoleColor(assembly.AssemblySource);

var alternativeVersion = assembly.AlternativeFoundVersion == null
? ""
: $" -> {assembly.AlternativeFoundVersion.AssemblyName.Version.ToString()}";

WriteLine($"{assembly.AssemblyName.Name} {assembly.AssemblyName.Version.ToString()}{alternativeVersion}");
ForegroundColor = currentForgroundColor;

assembly = assembly.AlternativeFoundVersion ?? assembly;

var count = 1;
var totalChildren = assembly.References.Count();
foreach(var dependency in assembly.References)
{
if(dependency.AssemblySource == AssemblySource.GlobalAssemblyCache && visualizerOptions.SkipSystem)
{
continue;
}
var parentLast = count++ == totalChildren;
var parentLabel = lastParent ? tabLabel : continuationLabel;
WalkDependencies(dependency, tab + parentLabel, parentLast);
}
}
}

private ConsoleColor SelectConsoleColor(AssemblySource assemblySource)
{
switch (assemblySource)
{
case AssemblySource.NotFound:
return AssemblyNotFoundColor;
case AssemblySource.Local:
return AssemblyLocalColor;
case AssemblySource.GlobalAssemblyCache:
return AssemblyGlobalAssemblyCacheColor;
case AssemblySource.Unknown:
return AssemblyUnknownColor;
default:
throw new InvalidOperationException("Unknown AssemblySource value.");
}
}

private const ConsoleColor AssemblyNotFoundColor = ConsoleColor.Red;
private const ConsoleColor AssemblyLocalColor = ConsoleColor.Green;
private const ConsoleColor AssemblyGlobalAssemblyCacheColor = ConsoleColor.Yellow;
private const ConsoleColor AssemblyUnknownColor = ConsoleColor.Magenta;

private const string tabLabel = " ";
private const string nodeLabel = "├──";
private const string endNodeLabel = "└──";
private const string continuationLabel = "│  ";
}
}
97 changes: 57 additions & 40 deletions AsmSpy.CommandLine/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,46 +35,57 @@ public static int Main(string[] args)

commandLineApplication.OnExecute(() =>
{
var visualizerOptions = new VisualizerOptions(
skipSystem: nonsystem.HasValue(),
onlyConflicts: !all.HasValue(),
referencedStartsWith: referencedStartsWith.HasValue() ? referencedStartsWith.Value() : string.Empty
);
var consoleLogger = new ConsoleLogger(!silent.HasValue());
var finalResult = GetFileList(directoryOrFile, includeSubDirectories, consoleLogger)
.Bind(fileList => GetAppDomainWithBindingRedirects(configurationFile)
.Map(appDomain => DependencyAnalyzer.Analyze(
fileList,
appDomain,
consoleLogger,
visualizerOptions)))
.Map(result => RunVisualizers(result, consoleLogger, visualizerOptions))
.Bind(FailOnMissingAssemblies);
switch(finalResult)
try
{
case Failure<bool> fail:
consoleLogger.LogError(fail.Message);
return -1;
case Success<bool> succeed:
return 0;
default:
throw new InvalidOperationException("Unexpected result type");
}
var visualizerOptions = new VisualizerOptions(
skipSystem: nonsystem.HasValue(),
onlyConflicts: !all.HasValue(),
referencedStartsWith: referencedStartsWith.HasValue() ? referencedStartsWith.Value() : string.Empty
);
var consoleLogger = new ConsoleLogger(!silent.HasValue());
var finalResult = GetFileList(directoryOrFile, includeSubDirectories, consoleLogger)
.Bind(x => GetAppDomainWithBindingRedirects(configurationFile)
.Map(appDomain => DependencyAnalyzer.Analyze(
x.FileList,
appDomain,
consoleLogger,
visualizerOptions,
x.RootFileName)))
.Map(result => RunVisualizers(result, consoleLogger, visualizerOptions))
.Bind(FailOnMissingAssemblies);
switch (finalResult)
{
case Failure<bool> fail:
consoleLogger.LogError(fail.Message);
return -1;
case Success<bool> succeed:
return 0;
default:
throw new InvalidOperationException("Unexpected result type");
}
DependencyAnalyzerResult RunVisualizers(DependencyAnalyzerResult dependencyAnalyzerResult, ILogger logger, VisualizerOptions options)
{
foreach(var visualizer in dependencyVisualizers.Where(x => x.IsConfigured()))
DependencyAnalyzerResult RunVisualizers(DependencyAnalyzerResult dependencyAnalyzerResult, ILogger logger, VisualizerOptions options)
{
visualizer.Visualize(dependencyAnalyzerResult, logger, options);
foreach (var visualizer in dependencyVisualizers.Where(x => x.IsConfigured()))
{
visualizer.Visualize(dependencyAnalyzerResult, logger, options);
}
return dependencyAnalyzerResult;
}
return dependencyAnalyzerResult;
}
Result<bool> FailOnMissingAssemblies(DependencyAnalyzerResult dependencyAnalyzerResult)
=> failOnMissing.HasValue() && dependencyAnalyzerResult.MissingAssemblies.Any() ? "Missing Assemblies" : Result<bool>.Succeed(true);
Result<bool> FailOnMissingAssemblies(DependencyAnalyzerResult dependencyAnalyzerResult)
=> failOnMissing.HasValue() && dependencyAnalyzerResult.MissingAssemblies.Any()
? "Missing Assemblies"
: Result<bool>.Succeed(true);
}
catch(Exception exception)
{
Console.WriteLine(exception.ToString());
return -1;
}
});

try
Expand All @@ -99,27 +110,32 @@ Result<bool> FailOnMissingAssemblies(DependencyAnalyzerResult dependencyAnalyzer
}
}

private static Result<List<FileInfo>> GetFileList(CommandArgument directoryOrFile, CommandOption includeSubDirectories, ILogger logger)
private static Result<(List<FileInfo> FileList, string RootFileName)> GetFileList(CommandArgument directoryOrFile, CommandOption includeSubDirectories, ILogger logger)
{
var searchPattern = includeSubDirectories.HasValue() ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var directoryOrFilePath = directoryOrFile.Value;
var directoryPath = Path.GetDirectoryName(directoryOrFilePath);
var directoryPath = directoryOrFilePath;

if (!File.Exists(directoryOrFilePath) && !Directory.Exists(directoryOrFilePath))
{
return (string.Format(CultureInfo.InvariantCulture, "Directory or file: '{0}' does not exist.", directoryOrFilePath));
}

var rootFileName = "";
if (File.Exists(directoryOrFilePath))
{
var fileName = Path.GetFileName(directoryOrFilePath);
logger.LogMessage($"Root assembly specified: '{fileName}'");
rootFileName = Path.GetFileName(directoryOrFilePath);
logger.LogMessage($"Root assembly specified: '{rootFileName}'");
directoryPath = Path.GetDirectoryName(directoryOrFilePath);
}

var directoryInfo = new DirectoryInfo(directoryPath);

logger.LogMessage($"Checking for local assemblies in: '{directoryInfo}', {searchPattern}");
return directoryInfo.GetFiles("*.dll", searchPattern).Concat(directoryInfo.GetFiles("*.exe", searchPattern)).ToList();

var fileList = directoryInfo.GetFiles("*.dll", searchPattern).Concat(directoryInfo.GetFiles("*.exe", searchPattern)).ToList();

return (fileList, rootFileName);
}

public static Result<AppDomain> GetAppDomainWithBindingRedirects(CommandOption configurationFile)
Expand Down Expand Up @@ -147,6 +163,7 @@ public static Result<AppDomain> GetAppDomainWithBindingRedirects(CommandOption c
private static IDependencyVisualizer[] GetDependencyVisualizers() => new IDependencyVisualizer[]
{
new ConsoleVisualizer(),
new ConsoleTreeVisualizer(),
new DgmlExport(),
new XmlExport(),
new DotExport(),
Expand Down
4 changes: 2 additions & 2 deletions AsmSpy.Core.Tests/DependencyAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public class DependencyAnalyzerTests
private readonly ITestOutputHelper output;
private readonly TestLogger logger;

private IEnumerable<FileInfo> filesToAnalyse;
private VisualizerOptions options = new VisualizerOptions(false, false, "");
private readonly IEnumerable<FileInfo> filesToAnalyse;
private readonly VisualizerOptions options = new VisualizerOptions(false, false, "");

public DependencyAnalyzerTests(ITestOutputHelper output)
{
Expand Down
36 changes: 32 additions & 4 deletions AsmSpy.Core/AssemblyReferenceInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

Expand All @@ -16,11 +17,18 @@ public class AssemblyReferenceInfo : IAssemblyReferenceInfo
public virtual ICollection<IAssemblyReferenceInfo> ReferencedBy => _referencedBy.ToArray();
public virtual ICollection<IAssemblyReferenceInfo> References => _references.ToArray();
public bool IsSystem => AssemblyInformationProvider.IsSystemAssembly(AssemblyName);
public string FileName { get; }
public bool ReferencedByRoot { get; set; } = false;
public AssemblyReferenceInfo AlternativeFoundVersion { get; private set; }

public AssemblyReferenceInfo(AssemblyName assemblyName, AssemblyName redirectedAssemblyName)
public AssemblyReferenceInfo(AssemblyName assemblyName, AssemblyName redirectedAssemblyName, string fileName = "")
{
AssemblyName = assemblyName;
RedirectedAssemblyName = redirectedAssemblyName;
AssemblyName = assemblyName
?? throw new ArgumentNullException(nameof(assemblyName));
RedirectedAssemblyName = redirectedAssemblyName
?? throw new ArgumentNullException(nameof(redirectedAssemblyName));
FileName = fileName
?? throw new ArgumentNullException(nameof(fileName));
}

public virtual void AddReference(IAssemblyReferenceInfo info)
Expand All @@ -39,6 +47,26 @@ public virtual void AddReferencedBy(IAssemblyReferenceInfo info)
}
}

public void SetAlternativeFoundVersion(AssemblyReferenceInfo alternativeVersion)
{
if(alternativeVersion.AssemblyName.Name != AssemblyName.Name)
{
throw new InvalidOperationException(
$"Alternative version to {AssemblyName.Name}, must have the same Name, but is {alternativeVersion.AssemblyName.Name}");
}
if(ReflectionOnlyAssembly != null)
{
throw new InvalidOperationException(
$"AssemblyReferenceInfo for {AssemblyName.Name} has a ReflectionOnlyAssembly, so an alternative should not be set.");
}
if(AssemblySource != AssemblySource.NotFound)
{
throw new InvalidOperationException(
$"AssemblyReferenceInfo.AssemblySource for {AssemblyName.Name} is not 'NotFound', so an alternative should not be set.");
}
AlternativeFoundVersion = alternativeVersion;
}

public override int GetHashCode()
{
return AssemblyName.FullName.GetHashCode();
Expand Down
Loading

0 comments on commit f60398a

Please sign in to comment.