From aa25401a502b4a9971edaa0097620774a7977ae4 Mon Sep 17 00:00:00 2001
From: Pranav K <prkrishn@hotmail.com>
Date: Fri, 7 Jul 2017 16:19:53 -0700
Subject: [PATCH] Make the calculation of the build graph a task so we can pass
 metadata around

---
 NuGet.Config                                  |  1 +
 Universe.sln                                  |  6 --
 build/RepositoryBuild.targets                 | 41 +++------
 build/repo.targets                            | 80 ++++++------------
 build/tasks/BuildGraph/CalculateBuildGraph.cs | 65 ++++++++++++++
 .../BuildGraph/DependencyGraphSpecProvider.cs | 21 +++++
 .../tasks}/BuildGraph/GraphBuilder.cs         |  6 +-
 .../tasks}/BuildGraph/GraphNode.cs            |  2 +-
 {tools => build/tasks}/BuildGraph/Project.cs  |  4 +-
 .../tasks}/BuildGraph/Repository.cs           | 31 +++----
 .../tasks}/BuildGraph/TopologicalSort.cs      |  2 +-
 build/tasks/RepoTasks.csproj                  | 13 +++
 build/tasks/RepoTasks.tasks                   |  7 ++
 tools/BuildGraph/BuildGraph.csproj            | 16 ----
 tools/BuildGraph/DGMLFormatter.cs             | 56 -------------
 tools/BuildGraph/GraphFormatter.cs            |  9 --
 tools/BuildGraph/MSBuildGraphFormatter.cs     | 23 -----
 tools/BuildGraph/Program.cs                   | 84 -------------------
 18 files changed, 163 insertions(+), 304 deletions(-)
 create mode 100644 build/tasks/BuildGraph/CalculateBuildGraph.cs
 create mode 100644 build/tasks/BuildGraph/DependencyGraphSpecProvider.cs
 rename {tools => build/tasks}/BuildGraph/GraphBuilder.cs (92%)
 rename {tools => build/tasks}/BuildGraph/GraphNode.cs (92%)
 rename {tools => build/tasks}/BuildGraph/Project.cs (93%)
 rename {tools => build/tasks}/BuildGraph/Repository.cs (79%)
 rename {tools => build/tasks}/BuildGraph/TopologicalSort.cs (97%)
 create mode 100644 build/tasks/RepoTasks.csproj
 create mode 100644 build/tasks/RepoTasks.tasks
 delete mode 100644 tools/BuildGraph/BuildGraph.csproj
 delete mode 100644 tools/BuildGraph/DGMLFormatter.cs
 delete mode 100644 tools/BuildGraph/GraphFormatter.cs
 delete mode 100644 tools/BuildGraph/MSBuildGraphFormatter.cs
 delete mode 100644 tools/BuildGraph/Program.cs

diff --git a/NuGet.Config b/NuGet.Config
index 7604d0051e1..cbeb8fa20d8 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -2,6 +2,7 @@
 <configuration>
   <packageSources>
     <clear />
+    <add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-release/api/v3/index.json" />
     <add key="NuGet" value="https://api.nuget.org/v3/index.json" />
   </packageSources>
 </configuration>
diff --git a/Universe.sln b/Universe.sln
index c4f59d60fe0..d1dd9a12a56 100644
--- a/Universe.sln
+++ b/Universe.sln
@@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
 VisualStudioVersion = 15.0.26228.0
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildGraph", "tools\BuildGraph\BuildGraph.csproj", "{B0621D49-4770-4552-9425-D6BD2CF0FB50}"
-EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PinVersions", "tools\PinVersions\PinVersions.csproj", "{DACA9DFB-508E-45EA-A5CF-C0F5C2BA181B}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{085280EC-7055-426A-BF9C-1B692B9599AB}"
@@ -18,10 +16,6 @@ Global
 		Release|Any CPU = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{B0621D49-4770-4552-9425-D6BD2CF0FB50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{B0621D49-4770-4552-9425-D6BD2CF0FB50}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{B0621D49-4770-4552-9425-D6BD2CF0FB50}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{B0621D49-4770-4552-9425-D6BD2CF0FB50}.Release|Any CPU.Build.0 = Release|Any CPU
 		{DACA9DFB-508E-45EA-A5CF-C0F5C2BA181B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{DACA9DFB-508E-45EA-A5CF-C0F5C2BA181B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{DACA9DFB-508E-45EA-A5CF-C0F5C2BA181B}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/build/RepositoryBuild.targets b/build/RepositoryBuild.targets
index f733ef2caef..52fe532c384 100644
--- a/build/RepositoryBuild.targets
+++ b/build/RepositoryBuild.targets
@@ -1,14 +1,13 @@
 <Project>
-  <Import Project="$(_BuildGraphFile)" />
-
-  <Target Name="BuildRepositories">
+  <Target Name="_BuildRepositories">
     <ItemGroup>
       <BatchedRepository Include="$(MSBuildProjectFullPath)">
         <BuildGroup>%(RepositoryToBuildInOrder.Order)</BuildGroup>
         <Repository>%(RepositoryToBuildInOrder.Identity)</Repository>
         <AdditionalProperties>
           RepositoryToBuild=%(RepositoryToBuildInOrder.Identity);
-          BuildRepositoryRoot=$(_CloneRepositoryRoot)%(RepositoryToBuildInOrder.Identity)\
+          BuildRepositoryRoot=%(RepositoryToBuildInOrder.RepositoryPath)\;
+          CommitHash=%(RepositoryToBuildInOrder.Commit)
         </AdditionalProperties>
       </BatchedRepository>
     </ItemGroup>
@@ -18,26 +17,6 @@
       <BatchBuilds Condition="'$(BatchBuilds)'==''">false</BatchBuilds>
     </PropertyGroup>
 
-    <Message
-      Text="Building repositories in the following batches:"
-      Importance="High"
-      Condition="'$(BatchBuilds)'=='true'" />
-
-    <Message
-      Text="%(BatchedRepository.BuildGroup): @(BatchedRepository -> '%(Repository)', ', ')"
-      Importance="High"
-      Condition="'$(BatchBuilds)'=='true'" />
-
-    <Message
-      Text="Building repositories in the following order:"
-      Importance="High"
-      Condition="'$(BatchBuilds)'!='true'" />
-
-    <Message
-      Text="%(BatchedRepository.Repository)"
-      Importance="High"
-      Condition="'$(BatchBuilds)'!='true'" />
-
     <MSBuild
       Projects="@(BatchedRepository)"
       BuildInParallel="$(BatchBuilds)"
@@ -48,6 +27,9 @@
 
   <Target Name="_BuildRepository" DependsOnTargets="_PinVersions">
     <PropertyGroup>
+      <!-- If there are duplicate properties, the properties which are defined later in the order would override the earlier ones -->
+      <RepositoryBuildArguments>$(RepositoryBuildArguments) /p:BuildNumber=$(BuildNumber) /p:Configuration=$(Configuration) /p:CommitHash=$(CommitHash)</RepositoryBuildArguments>
+
       <BuildArguments>$(_RepositoryBuildTargets) $(RepositoryBuildArguments)</BuildArguments>
       <RepositoryArtifactsRoot>$(BuildRepositoryRoot)artifacts</RepositoryArtifactsRoot>
       <RepositoryArtifactsBuildDirectory>$(RepositoryArtifactsRoot)\build\</RepositoryArtifactsBuildDirectory>
@@ -83,26 +65,27 @@
 
     <Copy
        SourceFiles="@(RepositoryArtifacts)"
-       DestinationFolder="$(UniverseBuildDir)" />
+       DestinationFolder="$(BuildDir)" />
 
     <Move
        SourceFiles="@(RepositoryMSBuildArtifacts)"
-       DestinationFolder="$(UniverseMSBuildDir)\$(RepositoryToBuild)\%(RecursiveDir)" />
+       DestinationFolder="$(ArtifactsDir)msbuild\$(RepositoryToBuild)\%(RecursiveDir)" />
 
     <Message Text="Publishing the following packages to the volatile feed: @(RepositoryNupkgs -> '%(Filename)%(Extension)', ', ')"
       Condition="'$(PublishPackages)'=='true' AND '@(RepositoryNupkgs)' != ''" />
 
     <Exec
-      Command="$(DotNetPath) $(PackagePublisherPath) -d $(RepositoryArtifactsBuildDirectory) -f $(NuGetPublishVolatileFeed)"
+      Command="$(DotNetPath) $(PackagePublisherNetCoreApp) -d $(RepositoryArtifactsBuildDirectory) -f $(NuGetPublishVolatileFeed)"
       Condition="'$(PublishPackages)'=='true' AND '@(RepositoryNupkgs)' != ''" />
 
     <Message Text="============ Done building $(RepositoryToBuild) ============" Importance="High" />
   </Target>
 
-  <Target Name="_PinVersions">
+  <Target Name="_PinVersions" DependsOnTargets="_FindDotNetPath">
+
     <PropertyGroup>
       <PinToolBinary>$(RepositoryRoot)tools\PinVersions\bin\$(Configuration)\netcoreapp1.1\PinVersions.dll</PinToolBinary>
-      <PinVersionArgs>$(DotNetPath) $(PinToolBinary) --graph-specs-root &quot;$(_RestoreGraphSpecsDirectory) &quot; -s &quot;$(UniverseBuildDir) &quot; &quot;$(BuildRepositoryRoot) &quot;</PinVersionArgs>
+      <PinVersionArgs>$(DotNetPath) $(PinToolBinary) --graph-specs-root &quot;$(_RestoreGraphSpecsDirectory) &quot; -s &quot;$(BuildDir) &quot; &quot;$(BuildRepositoryRoot) &quot;</PinVersionArgs>
       <PinVersionArgs Condition="Exists('$(_DependencyPackagesDirectory)')">$(PinVersionArgs) -s &quot;$(_DependencyPackagesDirectory) &quot;</PinVersionArgs>
     </PropertyGroup>
 
diff --git a/build/repo.targets b/build/repo.targets
index 7aa7207cf22..2aa28e05ab3 100644
--- a/build/repo.targets
+++ b/build/repo.targets
@@ -1,8 +1,9 @@
 <Project>
+  <Import Project="RepositoryBuild.targets" />
+
   <PropertyGroup>
     <NuGetPublishVolatileFeed>https://dotnet.myget.org/F/aspnetcore-volatile-dev/api/v2/package</NuGetPublishVolatileFeed>
 
-    <_BuildGraphFile>$(BuildDir)BuildGraph.proj</_BuildGraphFile>
     <_CloneRepositoryRoot>$(RepositoryRoot).r\</_CloneRepositoryRoot>
     <_DependencyBuildDirectory>$(RepositoryRoot).deps\build\</_DependencyBuildDirectory>
     <_DependencyPackagesDirectory>$(_DependencyBuildDirectory)</_DependencyPackagesDirectory>
@@ -33,7 +34,7 @@
     <RemoveDir Directories="$(_CloneRepositoryRoot)" Condition="Exists('$(_CloneRepositoryRoot)')" />
   </Target>
 
-  <Target Name="_FilterRepositories">
+  <Target Name="_PrepareRepositories">
     <ItemGroup Condition="'$(KOREBUILD_REPOSITORY_INCLUDE)'!=''">
       <_RepositoriesToInclude Include="$(KOREBUILD_REPOSITORY_INCLUDE)" />
       <Repository
@@ -49,10 +50,12 @@
     <Error Text="KOREBUILD_REPOSITORY_EXCLUDE AND KOREBUILD_REPOSITORY_INCLUDE are specified."
       Condition="'$(KOREBUILD_REPOSITORY_INCLUDE)' != '' AND '$(KOREBUILD_REPOSITORY_EXCLUDE)' != ''" />
 
-    <Message Text="%(Repository.CloneUrl)" />
+    <ItemGroup>
+      <Repository Update="%(Identity)" RepositoryPath="$(_CloneRepositoryRoot)%(Identity)" />
+    </ItemGroup>
   </Target>
 
-  <Target Name="CloneRepositories" DependsOnTargets="_FilterRepositories">
+  <Target Name="CloneRepositories" DependsOnTargets="_PrepareRepositories">
     <ItemGroup>
       <_CloneRepository Include="$(MSBuildProjectFullPath)">
         <AdditionalProperties>
@@ -72,6 +75,7 @@
     <MSBuild Projects="@(_CloneRepository)"
       Targets="_CloneRepository"
       BuildInParallel="$(BuildInParallel)" />
+
   </Target>
 
   <Target Name="_CloneRepository">
@@ -107,45 +111,9 @@
   </Target>
 
   <Target Name="BuildRepositories"
-     DependsOnTargets="_FilterRepositories;_FindDotNetPath;_GenerateRestoreGraphSpecs;_GenerateBuildGraph;_UpdateNuGetConfig;_CreateRepositoriesListWithCommits">
-
-    <PropertyGroup>
-      <!-- If there are duplicate properties, the properties which are defined later in the order would override the earlier ones -->
-      <RepositoryBuildArguments>$(RepositoryBuildArguments) /p:BuildNumber=$(BuildNumber) /p:Configuration=$(Configuration)</RepositoryBuildArguments>
-
-      <_BuildRepositoryProperties>
-        UniverseBuildDir=$(BuildDir);
-        UniverseMSBuildDir=$(ArtifactsDir)msbuild;
-        BuildInParallel=$(BuildInParallel);
-        Configuration=$(Configuration);
-        DotNetPath=$(DotNetPath);
-        KoreBuildDirectory=$(MSBuildProjectDirectory)\;
-        KoreBuildProject=$(MSBuildProjectFile);
-        RepositoryRoot=$(RepositoryRoot);
-        _BuildGraphFile=$(_BuildGraphFile);
-        _CloneRepositoryRoot=$(_CloneRepositoryRoot);
-        _DependencyPackagesDirectory=$(_DependencyPackagesDirectory);
-        _RepositoryBuildTargets=$(_RepositoryBuildTargets);
-        RepositoryBuildArguments=$(RepositoryBuildArguments);
-        _RestoreGraphSpecsDirectory=$(_RestoreGraphSpecsDirectory);
-        PackagePublisherPath=$(PackagePublisherNetCoreApp)
-      </_BuildRepositoryProperties>
-
-      <_BuildRepositoryProperties Condition="'$(PublishPackages)'=='true'">
-        $(_BuildRepositoryProperties);
-        APIKey=$(APIKey);
-        NuGetPublishVolatileFeed=$(NuGetPublishVolatileFeed);
-        PublishPackages=$(PublishPackages)
-      </_BuildRepositoryProperties>
-    </PropertyGroup>
-
-    <MSBuild
-      Projects="$(MSBuildThisFileDirectory)RepositoryBuild.targets"
-      Targets="BuildRepositories"
-      Properties="$(_BuildRepositoryProperties)" />
-  </Target>
+     DependsOnTargets="_PrepareRepositories;_FindDotNetPath;_CreateRepositoriesListWithCommits;_UpdateNuGetConfig;_GenerateBuildGraph;_BuildRepositories" />
 
-  <Target Name="_GenerateRestoreGraphSpecs" DependsOnTargets="_FindDotNetPath">
+  <Target Name="_PrepareRestoreGraphSpecs" DependsOnTargets="_PrepareRepositories">
     <ItemGroup>
       <Solution Include="$(_CloneRepositoryRoot)%(Repository.Identity)\*.sln">
         <Repository>%(Repository.Identity)</Repository>
@@ -154,22 +122,26 @@
       <Solution>
         <AdditionalProperties>RestoreGraphOutputPath=$(_RestoreGraphSpecsDirectory)%(Solution.Repository)\%(Solution.FileName)%(Solution.Extension).json</AdditionalProperties>
       </Solution>
+
+      <GraphSpecInputs Include="
+        @(Solution);
+        $(_CloneRepositoryRoot)**\*.csproj;
+        $(_CloneRepositoryRoot)**\dependencies.props" />
+      <GraphSpecOutputs Include="$(_RestoreGraphSpecsDirectory)%(Solution.Repository)\%(Solution.FileName)%(Solution.Extension).json" />
     </ItemGroup>
+  </Target>
 
+  <Target Name="_GenerateRestoreGraphSpecs" DependsOnTargets="_PrepareRestoreGraphSpecs" Inputs="@(GraphSpecInputs)" Outputs="@(GraphSpecOutputs)">
     <MSBuild
       Projects="@(Solution)"
       Targets="GenerateRestoreGraphFile"
       BuildInParallel="$(BuildInParallel)" />
   </Target>
 
-  <Target Name="_GenerateBuildGraph" DependsOnTargets="_FindDotNetPath">
-    <PropertyGroup>
-      <BuildGrapArgs>$(DotNetPath) run -r &quot;$(_CloneRepositoryRoot) &quot; --graph-specs-root &quot;$(_RestoreGraphSpecsDirectory) &quot; &quot;$(_BuildGraphFile)&quot;</BuildGrapArgs>
-      <BuildGrapArgs Condition="'$(BuildGraphOf)'!=''">$(BuildGrapArgs) --start-at $(BuildGraphOf)</BuildGrapArgs>
-    </PropertyGroup>
-    <Exec
-      Command="$(BuildGrapArgs)"
-      WorkingDirectory="$(RepositoryRoot)tools\BuildGraph\" />
+  <Target Name="_GenerateBuildGraph" DependsOnTargets="_GenerateRestoreGraphSpecs">
+    <RepoTasks.CalculateBuildGraph Repositories="@(Repository)" StartGraphAt="$(BuildGraphOf)" PackageSpecsDirectory="$(_RestoreGraphSpecsDirectory)">
+      <Output TaskParameter="RepositoriesToBuildInOrder" ItemName="RepositoryToBuildInOrder" />
+    </RepoTasks.CalculateBuildGraph>
   </Target>
 
   <Target Name="_UpdateNuGetConfig">
@@ -216,16 +188,14 @@
       -->
       <_CloneUrl>$([System.Environment]::GetEnvironmentVariable("vcsroot.%(Repository.Identity).url"))</_CloneUrl>
       <_CommitHash>$([System.Environment]::GetEnvironmentVariable("build.vcs.number.%(Repository.Identity)"))</_CommitHash>
-
-      <RepositoryCloneDirectory>$(_CloneRepositoryRoot)%(Repository.Identity)</RepositoryCloneDirectory>
     </PropertyGroup>
 
     <Warning Text="%(Repository.Identity) has not been cloned."
-      Condition="!Exists('$(RepositoryCloneDirectory)')" />
+      Condition="!Exists('%(Repository.RepositoryPath)')" />
 
     <GetGitCommitInfo
-      WorkingDirectory="$(RepositoryCloneDirectory)"
-      Condition="'$(_CommitHash)'=='' AND Exists('$(RepositoryCloneDirectory)')">
+      WorkingDirectory="%(Repository.RepositoryPath)"
+      Condition="'$(_CommitHash)'=='' AND Exists('%(Repository.RepositoryPath)')">
 
       <Output TaskParameter="CommitHash" PropertyName="_CommitHash" />
     </GetGitCommitInfo>
diff --git a/build/tasks/BuildGraph/CalculateBuildGraph.cs b/build/tasks/BuildGraph/CalculateBuildGraph.cs
new file mode 100644
index 00000000000..6e1dc6de2b4
--- /dev/null
+++ b/build/tasks/BuildGraph/CalculateBuildGraph.cs
@@ -0,0 +1,65 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using RepoTools.BuildGraph;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace RepoTasks
+{
+    public class CalculateBuildGraph : Task
+    {
+        [Required]
+        public ITaskItem[] Repositories { get; set; }
+
+        [Output]
+        public ITaskItem[] RepositoriesToBuildInOrder { get; set; }
+
+        /// <summary>
+        /// The repository at which to root the graph at
+        /// </summary>
+        public string StartGraphAt { get; set; }
+
+        /// <summary>
+        /// Directory that contains the package spec files.
+        /// </summary>
+        [Required]
+        public string PackageSpecsDirectory { get; set; }
+
+        public override bool Execute()
+        {
+            var graphSpecProvider = new DependencyGraphSpecProvider(PackageSpecsDirectory.Trim());
+
+            var repositoryPaths = Repositories.Select(r => r.GetMetadata("RepositoryPath")).ToList();
+            var repositories = Repository.ReadAllRepositories(repositoryPaths, graphSpecProvider);
+
+            var graph = GraphBuilder.Generate(repositories, StartGraphAt);
+            var repositoriesWithOrder = new List<(ITaskItem repository, int order)>();
+            foreach (var repositoryTaskItem in Repositories)
+            {
+                var repositoryName = repositoryTaskItem.ItemSpec;
+                var graphNodeRepository = graph.First(g => g.Repository.Name == repositoryName);
+                var order = TopologicalSort.GetOrder(graphNodeRepository);
+                repositoryTaskItem.SetMetadata("Order", order.ToString());
+                repositoriesWithOrder.Add((repositoryTaskItem, order));
+            }
+
+            Log.LogMessage(MessageImportance.High, "Repository build order:");
+            foreach (var buildGroup in repositoriesWithOrder.GroupBy(r => r.order).OrderBy(g => g.Key))
+            {
+                var buildGroupRepos = buildGroup.Select(b => b.repository.ItemSpec);
+                Log.LogMessage(MessageImportance.High, $"{buildGroup.Key.ToString().PadLeft(2, ' ')}: {string.Join(", ", buildGroupRepos)}");
+            }
+
+            RepositoriesToBuildInOrder = repositoriesWithOrder
+                .OrderBy(r => r.order)
+                .Select(r => r.repository)
+                .ToArray();
+
+            return true;
+        }
+    }
+}
diff --git a/build/tasks/BuildGraph/DependencyGraphSpecProvider.cs b/build/tasks/BuildGraph/DependencyGraphSpecProvider.cs
new file mode 100644
index 00000000000..805033c1c2e
--- /dev/null
+++ b/build/tasks/BuildGraph/DependencyGraphSpecProvider.cs
@@ -0,0 +1,21 @@
+using System.IO;
+using NuGet.ProjectModel;
+
+namespace RepoTools.BuildGraph
+{
+    public class DependencyGraphSpecProvider
+    {
+        readonly string _packageSpecDirectory;
+
+        public DependencyGraphSpecProvider(string packageSpecDirectory)
+        {
+            _packageSpecDirectory = packageSpecDirectory;
+        }
+
+        public DependencyGraphSpec GetDependencyGraphSpec(string repositoryName, string solutionPath)
+        {
+            var outputFile = Path.Combine(_packageSpecDirectory, repositoryName, Path.GetFileName(solutionPath) + ".json");
+            return DependencyGraphSpec.Load(outputFile);
+        }
+    }
+}
diff --git a/tools/BuildGraph/GraphBuilder.cs b/build/tasks/BuildGraph/GraphBuilder.cs
similarity index 92%
rename from tools/BuildGraph/GraphBuilder.cs
rename to build/tasks/BuildGraph/GraphBuilder.cs
index 0cf9fc7f241..81993354332 100644
--- a/tools/BuildGraph/GraphBuilder.cs
+++ b/build/tasks/BuildGraph/GraphBuilder.cs
@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 
-namespace BuildGraph
+namespace RepoTools.BuildGraph
 {
     public static class GraphBuilder
     {
@@ -19,7 +19,7 @@ namespace BuildGraph
             foreach (var project in repositories.SelectMany(r => r.AllProjects))
             {
                 var thisProjectRepositoryNode = graphNodes[project.Repository];
-                if (root != null && string.Equals(root, project.Repository.Name, StringComparison.OrdinalIgnoreCase))
+                if (!string.IsNullOrEmpty(root) && string.Equals(root, project.Repository.Name, StringComparison.OrdinalIgnoreCase))
                 {
                     searchRoot = thisProjectRepositoryNode;
                 }
@@ -58,4 +58,4 @@ namespace BuildGraph
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tools/BuildGraph/GraphNode.cs b/build/tasks/BuildGraph/GraphNode.cs
similarity index 92%
rename from tools/BuildGraph/GraphNode.cs
rename to build/tasks/BuildGraph/GraphNode.cs
index 236ff6aee5d..8c2a023ab2e 100644
--- a/tools/BuildGraph/GraphNode.cs
+++ b/build/tasks/BuildGraph/GraphNode.cs
@@ -1,7 +1,7 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 
-namespace BuildGraph
+namespace RepoTools.BuildGraph
 {
     [DebuggerDisplay("{Repository.Name}")]
     public class GraphNode
diff --git a/tools/BuildGraph/Project.cs b/build/tasks/BuildGraph/Project.cs
similarity index 93%
rename from tools/BuildGraph/Project.cs
rename to build/tasks/BuildGraph/Project.cs
index af8761e8292..10a821a336b 100644
--- a/tools/BuildGraph/Project.cs
+++ b/build/tasks/BuildGraph/Project.cs
@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 
-namespace BuildGraph
+namespace RepoTools.BuildGraph
 {
     [DebuggerDisplay("{Name}")]
     public class Project
@@ -20,4 +20,4 @@ namespace BuildGraph
 
         public ISet<string> PackageReferences { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
     }
-}
\ No newline at end of file
+}
diff --git a/tools/BuildGraph/Repository.cs b/build/tasks/BuildGraph/Repository.cs
similarity index 79%
rename from tools/BuildGraph/Repository.cs
rename to build/tasks/BuildGraph/Repository.cs
index e8893d7ff87..c71ade8555c 100644
--- a/tools/BuildGraph/Repository.cs
+++ b/build/tasks/BuildGraph/Repository.cs
@@ -6,9 +6,8 @@ using System.Linq;
 using System.Threading.Tasks;
 using NuGet.LibraryModel;
 using NuGet.ProjectModel;
-using UniverseTools;
 
-namespace BuildGraph
+namespace RepoTools.BuildGraph
 {
     [DebuggerDisplay("{Name}")]
     public class Repository : IEquatable<Repository>
@@ -26,25 +25,17 @@ namespace BuildGraph
 
         public IEnumerable<Project> AllProjects => Projects.Concat(SupportProjects);
 
-        public static IList<Repository> ReadAllRepositories(string repositoriesRoot, DependencyGraphSpecProvider provider)
+        public static IList<Repository> ReadAllRepositories(IList<string> repositoryPaths, DependencyGraphSpecProvider provider)
         {
-            var directories = new DirectoryInfo(repositoriesRoot).GetDirectories();
-            var repositories = new Repository[directories.Length];
+            var repositories = new Repository[repositoryPaths.Count];
 
-            var sw = Stopwatch.StartNew();
-            Parallel.For(0, directories.Length, new ParallelOptions { MaxDegreeOfParallelism = 6 }, i =>
+            Parallel.For(0, repositoryPaths.Count, new ParallelOptions { MaxDegreeOfParallelism = 6 }, i =>
             {
-                var directoryInfo = directories[i];
-                Console.WriteLine($"Gathering dependency information from {directoryInfo.Name}.");
-
-                var repository = Read(provider, directoryInfo.Name, directoryInfo.FullName);
+                var repositoryPath = repositoryPaths[i];
+                var repositoryName = Path.GetFileName(repositoryPath);
+                var repository = Read(provider, repositoryName, repositoryPath);
                 repositories[i] = repository;
-
-                Console.WriteLine($"Done gathering dependency information from {directoryInfo.Name}.");
             });
-            sw.Stop();
-
-            Console.WriteLine($"Done reading dependency information for all repos in {sw.Elapsed}.");
 
             return repositories;
         }
@@ -58,7 +49,8 @@ namespace BuildGraph
             var repository = new Repository(name);
 
             ReadSharedSourceProjects(Path.Combine(repositoryPath, "shared"), repository, repository.Projects);
-            var srcDirectory = Path.Combine(repositoryPath, "src");
+            var srcDirectory = Path.GetFullPath(Path.Combine(repositoryPath, "src"))
+                .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
 
             var solutionFiles = Directory.EnumerateFiles(repositoryPath, "*.sln");
             foreach (var file in solutionFiles)
@@ -67,7 +59,8 @@ namespace BuildGraph
                 var projects = spec.Projects.OrderBy(p => p.RestoreMetadata.ProjectStyle == ProjectStyle.PackageReference ? 0 : 1);
                 foreach (var specProject in projects)
                 {
-                    var projectPath = Path.GetFullPath(specProject.FilePath);
+                    var projectPath = Path.GetFullPath(specProject.FilePath)
+                        .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
 
                     var projectGroup = projectPath.StartsWith(srcDirectory, StringComparison.OrdinalIgnoreCase) ?
                         repository.Projects :
@@ -126,4 +119,4 @@ namespace BuildGraph
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tools/BuildGraph/TopologicalSort.cs b/build/tasks/BuildGraph/TopologicalSort.cs
similarity index 97%
rename from tools/BuildGraph/TopologicalSort.cs
rename to build/tasks/BuildGraph/TopologicalSort.cs
index 308d9eef772..4d362be3e4c 100644
--- a/tools/BuildGraph/TopologicalSort.cs
+++ b/build/tasks/BuildGraph/TopologicalSort.cs
@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 
-namespace BuildGraph
+namespace RepoTools.BuildGraph
 {
     public class TopologicalSort : IComparer<GraphNode>
     {
diff --git a/build/tasks/RepoTasks.csproj b/build/tasks/RepoTasks.csproj
new file mode 100644
index 00000000000..d0f925d5d18
--- /dev/null
+++ b/build/tasks/RepoTasks.csproj
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+   <Import Project="$(RepoTasksSdkPath)\Sdk.props" Condition="'$(RepoTasksSdkPath)' != '' "/>
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NuGet.ProjectModel" Version="4.0.0" />
+  </ItemGroup>
+
+  <Import Project="$(RepoTasksSdkPath)\Sdk.targets" Condition="'$(RepoTasksSdkPath)' != '' "/>
+</Project>
diff --git a/build/tasks/RepoTasks.tasks b/build/tasks/RepoTasks.tasks
new file mode 100644
index 00000000000..b858cdc4aa2
--- /dev/null
+++ b/build/tasks/RepoTasks.tasks
@@ -0,0 +1,7 @@
+<Project>
+  <PropertyGroup>
+    <_RepoTaskAssembly>$(MSBuildThisFileDirectory)bin\publish\RepoTasks.dll</_RepoTaskAssembly>
+  </PropertyGroup>
+
+  <UsingTask TaskName="RepoTasks.CalculateBuildGraph" AssemblyFile="$(_RepoTaskAssembly)" />
+</Project>
\ No newline at end of file
diff --git a/tools/BuildGraph/BuildGraph.csproj b/tools/BuildGraph/BuildGraph.csproj
deleted file mode 100644
index f330542012d..00000000000
--- a/tools/BuildGraph/BuildGraph.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <OutputType>Exe</OutputType>
-    <TargetFramework>netcoreapp1.1</TargetFramework>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <Compile Include="..\shared\*.cs" />
-  </ItemGroup>
-  
-  <ItemGroup>
-    <PackageReference Include="Microsoft.DotNet.Cli.Utils" Version="1.0.1" />
-    <PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.0" />
-  </ItemGroup>
-</Project>
\ No newline at end of file
diff --git a/tools/BuildGraph/DGMLFormatter.cs b/tools/BuildGraph/DGMLFormatter.cs
deleted file mode 100644
index 23ea409cb1c..00000000000
--- a/tools/BuildGraph/DGMLFormatter.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Xml.Linq;
-
-namespace BuildGraph
-{
-    public class DGMLFormatter : GraphFormatter
-    {
-        public override void Format(IList<GraphNode> nodes, string outputPath)
-        {
-            var xmlns = XNamespace.Get("http://schemas.microsoft.com/vs/2009/dgml");
-            var xdoc = new XDocument(
-                new XElement(xmlns + "DirectedGraph",
-                    new XElement(xmlns + "Nodes", GetNodes(xmlns, nodes).ToArray()),
-                    new XElement(xmlns + "Links", GetLinks(xmlns, nodes).ToArray()),
-                    new XElement(xmlns + "Properties", GetProperties(xmlns).ToArray())));
-
-            using (var writer = File.OpenWrite(outputPath))
-            {
-                xdoc.Save(writer);
-            }
-        }
-
-        private IEnumerable<XElement> GetLinks(XNamespace xmlns, IEnumerable<GraphNode> nodes)
-        {
-            foreach (var node in nodes)
-            {
-                foreach (var outgoing in node.Outgoing)
-                {
-                    yield return new XElement(xmlns + "Link",
-                        new XAttribute("Source", node.Repository.Name),
-                        new XAttribute("Target", outgoing.Repository.Name));
-                }
-            }
-        }
-
-        private IEnumerable<XElement> GetNodes(XNamespace xmlns, IEnumerable<GraphNode> nodes)
-        {
-            foreach (var node in nodes)
-            {
-                yield return new XElement(xmlns + "Node",
-                    new XAttribute("Id", node.Repository.Name),
-                    new XAttribute("Label", $"{node.Repository.Name}"));
-            }
-        }
-
-        private IEnumerable<XElement> GetProperties(XNamespace xmlns)
-        {
-            yield return new XElement(xmlns + "Property",
-                new XAttribute("Id", "Label"),
-                new XAttribute("Label", "Label"),
-                new XAttribute("DataType", "String"));
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/BuildGraph/GraphFormatter.cs b/tools/BuildGraph/GraphFormatter.cs
deleted file mode 100644
index 878dacd4fe6..00000000000
--- a/tools/BuildGraph/GraphFormatter.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Collections.Generic;
-
-namespace BuildGraph
-{
-    public abstract class GraphFormatter
-    {
-        public abstract void Format(IList<GraphNode> nodes, string outputPath);
-    }
-}
\ No newline at end of file
diff --git a/tools/BuildGraph/MSBuildGraphFormatter.cs b/tools/BuildGraph/MSBuildGraphFormatter.cs
deleted file mode 100644
index 4867ff217e9..00000000000
--- a/tools/BuildGraph/MSBuildGraphFormatter.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Xml.Linq;
-
-namespace BuildGraph
-{
-    public class MSBuildGraphFormatter : GraphFormatter
-    {
-        public override void Format(IList<GraphNode> nodes, string outputPath)
-        {
-            var sortedNodes = nodes.Select(node => new { Repository = node.Repository, Order = TopologicalSort.GetOrder(node) })
-                .OrderBy(item => item.Order);
-            var projectElement = new XElement("Project",
-                new XElement("ItemGroup",
-                    sortedNodes.Select(item => new XElement("RepositoryToBuildInOrder",
-                        new XAttribute("Include", item.Repository.Name),
-                        new XAttribute("Order", item.Order)))));
-
-            File.WriteAllText(outputPath, projectElement.ToString());
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/BuildGraph/Program.cs b/tools/BuildGraph/Program.cs
deleted file mode 100644
index 3271204a7c5..00000000000
--- a/tools/BuildGraph/Program.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Microsoft.Extensions.CommandLineUtils;
-using UniverseTools;
-
-namespace BuildGraph
-{
-    class Program
-    {
-        static int Main(string[] args)
-        {
-            var app = new CommandLineApplication();
-            var outputTypeOption = app.Option("--output-type",
-                "Output type of generated graph. Valid values are: msbuild, and dgml.",
-                CommandOptionType.SingleValue);
-
-            var repositoriesRootOption = app.Option("-r|--repositories-root",
-                "Directory containing repositories to calculate graph for.",
-                CommandOptionType.SingleValue);
-
-            var packageSpecsDirectoryOption = app.Option("--graph-specs-root",
-                "Directory containing package specs. (Optional)",
-                CommandOptionType.SingleValue);
-
-            var graphRoot = app.Option("--start-at",
-                "Calculate the build graph starting at the specified repo. (Optional)",
-                CommandOptionType.SingleValue);
-
-            var outputPathArgument = app.Argument("Output path", "Output path");
-
-            app.OnExecute(() =>
-            {
-                if (!repositoriesRootOption.HasValue())
-                {
-                    Console.Error.WriteLine($"Option {repositoriesRootOption.Template} must have a value.");
-                    return 1;
-                }
-
-                var outputPath = outputPathArgument.Value;
-                if (string.IsNullOrEmpty(outputPath))
-                {
-                    Console.Error.WriteLine($"Output path not specified.");
-                    return 1;
-                }
-
-                var outputDirectory = Path.GetDirectoryName(outputPath);
-                Directory.CreateDirectory(outputDirectory);
-
-                var outputType = outputTypeOption.Value() ?? "msbuild";
-
-                var graphSpecProvider = packageSpecsDirectoryOption.HasValue()
-                    ? new DependencyGraphSpecProvider(packageSpecsDirectoryOption.Value().Trim())
-                    : DependencyGraphSpecProvider.Default;
-                IList<Repository> repositories;
-                using (graphSpecProvider)
-                {
-                    repositories = Repository.ReadAllRepositories(repositoriesRootOption.Value().Trim(), graphSpecProvider);
-                }
-
-                var graph = GraphBuilder.Generate(repositories, graphRoot.Value());
-                GraphFormatter formatter;
-                switch (outputType)
-                {
-                    case "msbuild":
-                        formatter = new MSBuildGraphFormatter();
-                        break;
-                    case "dgml":
-                        formatter = new DGMLFormatter();
-                        break;
-                    default:
-                        app.Error.WriteLine($"Unknown output type: {outputType}.");
-                        return 1;
-                }
-
-                formatter.Format(graph, outputPathArgument.Value);
-
-                return 0;
-            });
-
-            return app.Execute(args);
-        }
-    }
-}
\ No newline at end of file
-- 
GitLab