diff --git a/src/Tools/BuildBoss/BuildBoss.csproj b/src/Tools/BuildBoss/BuildBoss.csproj
index 18e025bffe5567e124ab103fcb3e09d4fcbf0b2d..e531135abe8aea8b0dad4946ed177e4bfeb4effe 100644
--- a/src/Tools/BuildBoss/BuildBoss.csproj
+++ b/src/Tools/BuildBoss/BuildBoss.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs
index 0e81d015b0d6cf734be9b918b55a3e27659c5f90..88eb579291ed145a6f0fed6b737c6c6f9d378816 100644
--- a/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs
+++ b/src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs
@@ -4,6 +4,8 @@
using System.IO;
using System.IO.Packaging;
using System.Linq;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
@@ -74,6 +76,7 @@ public bool Check(TextWriter textWriter)
allGood &= CheckDesktop(textWriter, filter(isDesktop: true));
allGood &= CheckCoreClr(textWriter, filter(isDesktop: false));
allGood &= CheckCombined(textWriter, packageAssets);
+ allGood &= CheckExternalApis(textWriter);
return allGood;
IEnumerable filter(bool isDesktop) => packageAssets.Where(x => x.IsDesktop == isDesktop).Select(x => x.FileRelativeName);
@@ -147,6 +150,74 @@ private bool CheckCombined(TextWriter textWriter, IEnumerable pack
list);
}
+ ///
+ /// Verifies the VS.ExternalAPIs.Roslyn package is self consistent. Need to ensure that we insert all of the project dependencies
+ /// that we build into the package. If we miss a dependency then the VS insertion will fail. Big refactorings can often forget to
+ /// properly update this package.
+ ///
+ ///
+ ///
+ private bool CheckExternalApis(TextWriter textWriter)
+ {
+ var packageFilePath = FindNuGetPackage(Path.Combine(ArtifactsDirectory, "VSSetup", Configuration, "DevDivPackages"), "VS.ExternalAPIs.Roslyn");
+ var allGood = true;
+
+ textWriter.WriteLine("Verifying contents of VS.ExternalAPIs.Roslyn");
+ textWriter.WriteLine("\tRoot Folder");
+ verifyFolder("");
+ textWriter.WriteLine("\tRemote Debugger net20");
+ verifyFolder(@"RemoteDebugger\net20");
+ textWriter.WriteLine("\tRemote Debugger net50");
+ verifyFolder(@"RemoteDebugger\net50");
+ return allGood;
+
+ void verifyFolder(string folderRelativeName)
+ {
+ var foundDllNameSet = new HashSet(PathComparer);
+ var neededDllNameSet = new HashSet(PathComparer);
+ foreach (var part in GetPartsInFolder(packageFilePath, ""))
+ {
+ var name = part.GetName();
+ if (Path.GetExtension(name) != ".dll")
+ {
+ continue;
+ }
+
+ foundDllNameSet.Add(Path.GetFileNameWithoutExtension(name));
+ using var peReader = new PEReader(part.GetStream(FileMode.Open, FileAccess.Read));
+ var metadataReader = peReader.GetMetadataReader();
+ foreach (var handle in metadataReader.AssemblyReferences)
+ {
+ var assemblyReference = metadataReader.GetAssemblyReference(handle);
+ var assemblyName = metadataReader.GetString(assemblyReference.Name);
+ neededDllNameSet.Add(assemblyName);
+ }
+ }
+
+ if (foundDllNameSet.Count == 0)
+ {
+ allGood = false;
+ textWriter.WriteLine($"\t\tFound zero DLLs in {folderRelativeName}");
+ return;
+ }
+
+ // As a simplification we only validate the assembly names that begin with Microsoft.CodeAnalysis. This is a good
+ // hueristic for finding assemblies that we build. Can be expanded in the future if we find more assemblies that
+ // are worth validating here.
+ var neededDllNames = neededDllNameSet
+ .Where(x => x.StartsWith("Microsoft.CodeAnalysis"))
+ .OrderBy(x => x, PathComparer);
+ foreach (var name in neededDllNames)
+ {
+ if (!foundDllNameSet.Contains(name))
+ {
+ textWriter.WriteLine($"\t\tMissing dependency {name}");
+ allGood = false;
+ }
+ }
+ }
+ }
+
private bool GetPackageAssets(TextWriter textWriter, List packageAssets)
{
var allGood = true;
@@ -300,40 +371,6 @@ IEnumerable enumerateFiles()
string folderRelativePath,
IEnumerable dllFileNames)
{
- Debug.Assert(string.IsNullOrEmpty(folderRelativePath) || folderRelativePath[0] != '\\');
-
- // Get all of the assets parts that are in the specified folder. Will exclude items that
- // are in any child folder
- IEnumerable getPartsInFolder()
- {
- using (var package = Package.Open(packageFilePath, FileMode.Open, FileAccess.Read))
- {
- foreach (var part in package.GetParts())
- {
- var relativeName = part.Uri.ToString().Replace('/', '\\');
- if (string.IsNullOrEmpty(relativeName))
- {
- continue;
- }
-
- if (relativeName[0] == '\\')
- {
- relativeName = relativeName.Substring(1);
- }
-
- if (!relativeName.StartsWith(folderRelativePath, PathComparison))
- {
- continue;
- }
-
- if (IsTrackedAsset(relativeName))
- {
- yield return relativeName;
- }
- }
- }
- }
-
var map = dllFileNames
.ToDictionary(
keySelector: x => Path.Combine(folderRelativePath, x),
@@ -343,8 +380,14 @@ IEnumerable getPartsInFolder()
var packageFileName = Path.GetFileName(packageFilePath);
textWriter.WriteLine($"Verifying {packageFileName}");
- foreach (var relativeName in getPartsInFolder())
+ foreach (var part in GetPartsInFolder(packageFilePath, folderRelativePath))
{
+ var relativeName = part.GetRelativeName();
+ if (!IsTrackedAsset(relativeName))
+ {
+ continue;
+ }
+
var name = Path.GetFileName(relativeName);
if (map.TryGetValue(relativeName, out var isFound))
{
@@ -374,6 +417,33 @@ IEnumerable getPartsInFolder()
return allGood;
}
+ ///
+ /// Get all of the parts in the specified folder. Will exclude all items in child folders.
+ ///
+ private static IEnumerable GetPartsInFolder(string packageFilePath, string folderRelativePath)
+ {
+ Debug.Assert(string.IsNullOrEmpty(folderRelativePath) || folderRelativePath[0] != '\\');
+
+ using (var package = Package.Open(packageFilePath, FileMode.Open, FileAccess.Read))
+ {
+ foreach (var part in package.GetParts())
+ {
+ var relativeName = part.GetRelativeName();
+ if (string.IsNullOrEmpty(relativeName))
+ {
+ continue;
+ }
+
+ if (!relativeName.StartsWith(folderRelativePath, PathComparison))
+ {
+ continue;
+ }
+
+ yield return part;
+ }
+ }
+ }
+
private string FindNuGetPackage(string directory, string partialName)
{
var regex = $@"{partialName}.\d.*\.nupkg";
diff --git a/src/Tools/BuildBoss/Extensions.cs b/src/Tools/BuildBoss/Extensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f0911449279e3cb7e2303fde9b3b2d55b9818455
--- /dev/null
+++ b/src/Tools/BuildBoss/Extensions.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Packaging;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BuildBoss
+{
+ internal static class Extensions
+ {
+ internal static string GetRelativeName(this PackagePart part)
+ {
+ var relativeName = part.Uri.ToString().Replace('/', '\\');
+ if (!string.IsNullOrEmpty(relativeName) && relativeName[0] == '\\')
+ {
+ relativeName = relativeName.Substring(1);
+ }
+
+ return relativeName;
+ }
+
+ internal static string GetName(this PackagePart part) => Path.GetFileName(GetRelativeName(part));
+ }
+}
diff --git a/src/Tools/BuildBoss/Program.cs b/src/Tools/BuildBoss/Program.cs
index 41efe1360e1ab02598e1a2f69671123b4cce8685..ac0139902bea4384c6fa3c25860ea025fde3eab7 100644
--- a/src/Tools/BuildBoss/Program.cs
+++ b/src/Tools/BuildBoss/Program.cs
@@ -96,9 +96,9 @@ private static bool Go(string repositoryDirectory, string configuration, List