Skip to content

Instantly share code, notes, and snippets.

@valerysntx
Created December 16, 2018 15:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save valerysntx/c3ea8747ed4ec1aa94b7764cdfa1cd81 to your computer and use it in GitHub Desktop.
Save valerysntx/c3ea8747ed4ec1aa94b7764cdfa1cd81 to your computer and use it in GitHub Desktop.
Scan Indirect Project Dependencies Recursively
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- All the stuff normally found in the project, then in the AfterBuild event... -->
<Target Name="AfterBuild">
<!-- Here's the call to the custom task to get the list of dependencies -->
<ScanIndirectDependencies StartFolder="$(MSBuildProjectDirectory)"
StartProjectReferences="@(ProjectReference)"
Configuration="$(Configuration)">
<Output TaskParameter="IndirectDependencies" ItemName="IndirectDependenciesToCopy" />
</ScanIndirectDependencies>
<!-- Only copy the file in if we won't stomp something already there -->
<Copy SourceFiles="%(IndirectDependenciesToCopy.FullPath)"
DestinationFolder="$(OutputPath)"
Condition="!Exists('$(OutputPath)\%(IndirectDependenciesToCopy.Filename)%(IndirectDependenciesToCopy.Extension)')" />
</Target>
<!-- THE CUSTOM TASK! -->
<UsingTask TaskName="ScanIndirectDependencies" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<StartFolder Required="true" />
<StartProjectReferences ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<Configuration Required="true" />
<IndirectDependencies ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Xml"/>
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Using Namespace="System" />
<Using Namespace="System.Collections.Generic" />
<Using Namespace="System.IO" />
<Using Namespace="System.Linq" />
<Using Namespace="System.Xml" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var projectReferences = new List<string>();
var toScan = new List<string>(StartProjectReferences.Select(p => Path.GetFullPath(Path.Combine(StartFolder, p.ItemSpec))));
var indirectDependencies = new List<string>();
bool rescan;
do{
rescan = false;
foreach(var projectReference in toScan.ToArray())
{
if(projectReferences.Contains(projectReference))
{
toScan.Remove(projectReference);
continue;
}
Log.LogMessage(MessageImportance.Low, "Scanning project reference for other project references: {0}", projectReference);
var doc = new XmlDocument();
doc.Load(projectReference);
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("msb", "http://schemas.microsoft.com/developer/msbuild/2003");
var projectDirectory = Path.GetDirectoryName(projectReference);
// Find all project references we haven't already seen
var newReferences = doc
.SelectNodes("/msb:Project/msb:ItemGroup/msb:ProjectReference/@Include", nsmgr)
.Cast<XmlAttribute>()
.Select(a => Path.GetFullPath(Path.Combine(projectDirectory, a.Value)));
if(newReferences.Count() > 0)
{
Log.LogMessage(MessageImportance.Low, "Found new referenced projects: {0}", String.Join(", ", newReferences));
}
toScan.Remove(projectReference);
projectReferences.Add(projectReference);
// Add any new references to the list to scan and mark the flag
// so we run through the scanning loop again.
toScan.AddRange(newReferences);
rescan = true;
// Include the assembly that the project reference generates.
var outputLocation = Path.Combine(Path.Combine(projectDirectory, "bin"), Configuration);
var localAsm = Path.GetFullPath(Path.Combine(outputLocation, doc.SelectSingleNode("/msb:Project/msb:PropertyGroup/msb:AssemblyName", nsmgr).InnerText + ".dll"));
if(!indirectDependencies.Contains(localAsm) && File.Exists(localAsm))
{
Log.LogMessage(MessageImportance.Low, "Added project assembly: {0}", localAsm);
indirectDependencies.Add(localAsm);
}
// Include third-party assemblies referenced by file location.
var externalReferences = doc
.SelectNodes("/msb:Project/msb:ItemGroup/msb:Reference/msb:HintPath", nsmgr)
.Cast<XmlElement>()
.Select(a => Path.GetFullPath(Path.Combine(projectDirectory, a.InnerText.Trim())))
.Where(e => !indirectDependencies.Contains(e));
Log.LogMessage(MessageImportance.Low, "Found new indirect references: {0}", String.Join(", ", externalReferences));
indirectDependencies.AddRange(externalReferences);
}
} while(rescan);
// Expand to include pdb and xml.
var xml = indirectDependencies.Select(f => Path.Combine(Path.GetDirectoryName(f), Path.GetFileNameWithoutExtension(f) + ".xml")).Where(f => File.Exists(f)).ToArray();
var pdb = indirectDependencies.Select(f => Path.Combine(Path.GetDirectoryName(f), Path.GetFileNameWithoutExtension(f) + ".pdb")).Where(f => File.Exists(f)).ToArray();
indirectDependencies.AddRange(xml);
indirectDependencies.AddRange(pdb);
Log.LogMessage("Located indirect references:\n{0}", String.Join(Environment.NewLine, indirectDependencies));
// Finally, assign the output parameter.
IndirectDependencies = indirectDependencies.Select(i => new TaskItem(i)).ToArray();
]]>
</Code>
</Task>
</UsingTask>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment