Skip to content

Commit

Permalink
AssemblyScanner improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
danielmarbach committed Aug 31, 2023
1 parent a5bcece commit 367de49
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 60 deletions.
18 changes: 12 additions & 6 deletions src/NServiceBus.Core.Tests/AssemblyScanner/AssemblyScannerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,13 @@ public void Skipped_dlls_should_be_excluded()

var scanner = new AssemblyScanner(DynamicAssembly.TestAssemblyDirectory)
{
CoreAssemblyName = busAssembly.DynamicName
CoreAssemblyName = busAssembly.DynamicName,
AssembliesToSkip = new[]
{
excludedAssembly1.DynamicName, // without file extension
excludedAssembly2.FileName // with file extension
}
};
scanner.AssembliesToSkip.Add(excludedAssembly1.DynamicName); // without file extension
scanner.AssembliesToSkip.Add(excludedAssembly2.FileName); // with file extension

var result = scanner.GetScannableAssemblies();
Assert.That(result.SkippedFiles.Any(s => s.FilePath == excludedAssembly1.FilePath));
Expand All @@ -294,10 +297,13 @@ public void Skipped_exes_should_be_excluded()

var scanner = new AssemblyScanner(DynamicAssembly.TestAssemblyDirectory)
{
CoreAssemblyName = busAssembly.DynamicName
CoreAssemblyName = busAssembly.DynamicName,
AssembliesToSkip = new[]
{
excludedAssembly1.DynamicName, // without file extension
excludedAssembly2.FileName // with file extension
}
};
scanner.AssembliesToSkip.Add(excludedAssembly1.DynamicName); // without file extension
scanner.AssembliesToSkip.Add(excludedAssembly2.FileName); // with file extension

var result = scanner.GetScannableAssemblies();
Assert.That(result.SkippedFiles.Any(s => s.FilePath == excludedAssembly1.FilePath));
Expand Down
95 changes: 41 additions & 54 deletions src/NServiceBus.Core/Hosting/Helpers/AssemblyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,17 @@ internal AssemblyScanner(Assembly assemblyToScan)
/// </summary>
public bool ScanFileSystemAssemblies { get; set; } = true;

internal string CoreAssemblyName { get; set; } = NServicebusCoreAssemblyName;
internal string CoreAssemblyName { get; set; } = NServiceBusCoreAssemblyName;

internal IReadOnlyCollection<string> AssembliesToSkip
{
set => assembliesToSkip = new HashSet<string>(value.Select(Path.GetFileNameWithoutExtension), StringComparer.OrdinalIgnoreCase);
}

internal IReadOnlyCollection<Type> TypesToSkip
{
set => typesToSkip = new HashSet<Type>(value);
}

internal string AdditionalAssemblyScanningPath { get; set; }

Expand Down Expand Up @@ -246,7 +256,7 @@ internal static string FormatReflectionTypeLoadException(string fileName, Reflec
{
var sb = new StringBuilder($"Could not enumerate all types for '{fileName}'.");

if (!e.LoaderExceptions.Any())
if (e.LoaderExceptions.Length == 0)
{
sb.NewLine($"Exception message: {e}");
return sb.ToString();
Expand Down Expand Up @@ -327,65 +337,43 @@ static List<FileInfo> ScanDirectoryForAssemblyFiles(string directoryToScan, bool
var searchOption = scanNestedDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
foreach (var searchPattern in FileSearchPatternsToUse)
{
foreach (var info in baseDir.GetFiles(searchPattern, searchOption))
{
fileInfo.Add(info);
}
fileInfo.AddRange(baseDir.GetFiles(searchPattern, searchOption));
}
return fileInfo;
}

bool IsExcluded(string assemblyNameOrFileName)
{
var isExplicitlyExcluded = AssembliesToSkip.Any(excluded => IsMatch(excluded, assemblyNameOrFileName));
if (isExplicitlyExcluded)
{
return true;
}

var isExcludedByDefault = DefaultAssemblyExclusions.Any(exclusion => IsMatch(exclusion, assemblyNameOrFileName));
if (isExcludedByDefault)
{
return true;
}

return false;
}

static bool IsMatch(string expression1, string expression2)
{
return DistillLowerAssemblyName(expression1) == DistillLowerAssemblyName(expression2);
}
bool IsExcluded(string assemblyNameOrFileNameWithoutExtension) =>
assembliesToSkip.Contains(assemblyNameOrFileNameWithoutExtension) ||
DefaultAssemblyExclusions.Contains(assemblyNameOrFileNameWithoutExtension);

bool IsAllowedType(Type type)
// The parameter and return types of this method are deliberately using the most concrete types
// to avoid unnecessary allocations
List<Type> FilterAllowedTypes(Type[] types)
{
return type != null &&
!type.IsValueType &&
!IsCompilerGenerated(type) &&
!TypesToSkip.Contains(type);
}

static bool IsCompilerGenerated(Type type)
{
return type.GetCustomAttribute<CompilerGeneratedAttribute>(false) != null;
}

static string DistillLowerAssemblyName(string assemblyOrFileName)
{
var lowerAssemblyName = assemblyOrFileName.ToLowerInvariant();
if (lowerAssemblyName.EndsWith(".dll") || lowerAssemblyName.EndsWith(".exe"))
// assume the majority of types will be allowed to preallocate the list
var allowedTypes = new List<Type>(types.Length);
foreach (var typeToAdd in types)
{
lowerAssemblyName = lowerAssemblyName.Substring(0, lowerAssemblyName.Length - 4);
if (IsAllowedType(typeToAdd))
{
allowedTypes.Add(typeToAdd);
}
}
return lowerAssemblyName;
return allowedTypes;
}

bool IsAllowedType(Type type) =>
type != null &&
!type.IsValueType &&
Attribute.GetCustomAttribute(type, typeof(CompilerGeneratedAttribute), false) == null &&
!typesToSkip.Contains(type);

void AddTypesToResult(Assembly assembly, AssemblyScannerResults results)
{
try
{
//will throw if assembly cannot be loaded
results.Types.AddRange(assembly.GetTypes().Where(IsAllowedType));
var types = assembly.GetTypes();
results.Types.AddRange(FilterAllowedTypes(types));
}
catch (ReflectionTypeLoadException e)
{
Expand All @@ -398,7 +386,7 @@ void AddTypesToResult(Assembly assembly, AssemblyScannerResults results)
}

LogManager.GetLogger<AssemblyScanner>().Warn(errorMessage);
results.Types.AddRange(e.Types.Where(IsAllowedType));
results.Types.AddRange(FilterAllowedTypes(e.Types));
}
results.Assemblies.Add(assembly);
}
Expand Down Expand Up @@ -431,21 +419,20 @@ bool ShouldScanDependencies(Assembly assembly)
}

AssemblyValidator assemblyValidator = new AssemblyValidator();
internal List<string> AssembliesToSkip = new List<string>();
internal bool ScanNestedDirectories;
internal List<Type> TypesToSkip = new List<Type>();
Assembly assemblyToScan;
string baseDirectoryToScan;
const string NServicebusCoreAssemblyName = "NServiceBus.Core";
HashSet<Type> typesToSkip = new HashSet<Type>();
HashSet<string> assembliesToSkip = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
const string NServiceBusCoreAssemblyName = "NServiceBus.Core";

static string[] FileSearchPatternsToUse =
static readonly string[] FileSearchPatternsToUse =
{
"*.dll",
"*.exe"
};

//TODO: delete when we make message scanning lazy #1617
static string[] DefaultAssemblyExclusions =
static readonly HashSet<string> DefaultAssemblyExclusions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
// NSB Build-Dependencies
"nunit",
Expand Down

0 comments on commit 367de49

Please sign in to comment.