diff --git a/build/GenerateCode.targets b/build/GenerateCode.targets
new file mode 100644
index 0000000000..069e358ae6
--- /dev/null
+++ b/build/GenerateCode.targets
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/build/repo.targets b/build/repo.targets
index cb419361ca..2f1ecdeaa5 100644
--- a/build/repo.targets
+++ b/build/repo.targets
@@ -6,6 +6,7 @@
+
diff --git a/build/tasks/CodeGen/DirectedGraphXml.cs b/build/tasks/CodeGen/DirectedGraphXml.cs
new file mode 100644
index 0000000000..e90d66ca6b
--- /dev/null
+++ b/build/tasks/CodeGen/DirectedGraphXml.cs
@@ -0,0 +1,42 @@
+
+// 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.Xml.Linq;
+
+namespace RepoTasks.CodeGen
+{
+ class DirectedGraphXml
+ {
+ private readonly XNamespace _ns = "http://schemas.microsoft.com/vs/2009/dgml";
+ private readonly XDocument _doc;
+ private readonly XElement _nodes;
+ private readonly XElement _links;
+
+ public DirectedGraphXml()
+ {
+ _doc = new XDocument(new XElement(_ns + "DirectedGraph"));
+ _nodes = new XElement(_ns + "Nodes");
+ _links = new XElement(_ns + "Links");
+ _doc.Root.Add(_nodes);
+ _doc.Root.Add(_links);
+ }
+
+ public void AddNode(string id)
+ {
+ _nodes.Add(new XElement(_ns + "Node", new XAttribute("Id", id), new XAttribute("Label", id)));
+ }
+
+ public void AddLink(string source, string target)
+ {
+ _links.Add(new XElement(_ns + "Link",
+ new XAttribute("Source", source),
+ new XAttribute("Target", target)));
+ }
+
+ public void Save(string path)
+ {
+ _doc.Save(path);
+ }
+ }
+}
diff --git a/build/tasks/CodeGen/GenerateSubmoduleGraph.cs b/build/tasks/CodeGen/GenerateSubmoduleGraph.cs
new file mode 100644
index 0000000000..7940b5cba2
--- /dev/null
+++ b/build/tasks/CodeGen/GenerateSubmoduleGraph.cs
@@ -0,0 +1,226 @@
+
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Text;
+using System.Threading;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using NuGet.Frameworks;
+using NuGet.Versioning;
+using RepoTools.BuildGraph;
+using RepoTasks.ProjectModel;
+using RepoTasks.Utilities;
+using RepoTasks.CodeGen;
+using NuGet.Packaging.Core;
+
+namespace RepoTasks
+{
+ public class GenerateSubmoduleGraph : Task, ICancelableTask
+ {
+ private readonly CancellationTokenSource _cts = new CancellationTokenSource();
+
+ ///
+ /// Repositories that we are building new versions of.
+ ///
+ [Required]
+ public ITaskItem[] Solutions { get; set; }
+
+ [Required]
+ public ITaskItem[] Artifacts { get; set; }
+
+ [Required]
+ public ITaskItem[] Repositories { get; set; }
+
+ [Required]
+ public string RepositoryRoot { get; set; }
+
+ [Required]
+ public string Properties { get; set; }
+
+ public void Cancel()
+ {
+ _cts.Cancel();
+ }
+
+ public override bool Execute()
+ {
+ var packageArtifacts = Artifacts.Select(ArtifactInfo.Parse)
+ .OfType()
+ .Where(p => !p.IsSymbolsArtifact)
+ .ToDictionary(p => p.PackageInfo.Id, p => p, StringComparer.OrdinalIgnoreCase);
+
+ var factory = new SolutionInfoFactory(Log, BuildEngine5);
+ var props = MSBuildListSplitter.GetNamedProperties(Properties);
+
+ Log.LogMessage(MessageImportance.High, $"Beginning cross-repo analysis on {Solutions.Length} solutions. Hang tight...");
+
+ if (!props.TryGetValue("Configuration", out var defaultConfig))
+ {
+ defaultConfig = "Debug";
+ }
+
+ var solutions = factory.Create(Solutions, props, defaultConfig, _cts.Token).OrderBy(f => f.Directory).ToList();
+ Log.LogMessage($"Found {solutions.Count} and {solutions.Sum(p => p.Projects.Count)} projects");
+
+ if (_cts.IsCancellationRequested)
+ {
+ return false;
+ }
+
+ return GenerateGraph(packageArtifacts, solutions);
+ }
+
+ private bool GenerateGraph(IDictionary packageArtifacts, IReadOnlyList solutions)
+ {
+ var repoGraph = new AdjacencyMatrix(solutions.Count);
+ var packageToProjectMap = new Dictionary();
+
+ for (var i = 0; i < solutions.Count; i++)
+ {
+ var sln = repoGraph[i] = solutions[i];
+
+ foreach (var proj in sln.Projects)
+ {
+ if (!proj.IsPackable || proj.FullPath.Contains("samples"))
+ {
+ continue;
+ }
+
+ var id = new PackageIdentity(proj.PackageId, new NuGetVersion(proj.PackageVersion));
+
+ if (packageToProjectMap.TryGetValue(id, out var otherProj))
+ {
+ Log.LogError($"Both {proj.FullPath} and {otherProj.FullPath} produce {id}");
+ continue;
+ }
+
+ packageToProjectMap.Add(id, proj);
+ }
+
+ var sharedSrc = Path.Combine(sln.Directory, "shared");
+ if (Directory.Exists(sharedSrc))
+ {
+ foreach (var dir in Directory.GetDirectories(sharedSrc, "*.Sources"))
+ {
+ var id = Path.GetFileName(dir);
+ var artifactInfo = packageArtifacts[id];
+ var sharedSrcProj = new ProjectInfo(dir,
+ Array.Empty(),
+ Array.Empty(),
+ true,
+ artifactInfo.PackageInfo.Id,
+ artifactInfo.PackageInfo.Version.ToNormalizedString());
+ sharedSrcProj.SolutionInfo = sln;
+ var identity = new PackageIdentity(artifactInfo.PackageInfo.Id, artifactInfo.PackageInfo.Version);
+ packageToProjectMap.Add(identity, sharedSrcProj);
+ }
+ }
+ }
+
+ if (Log.HasLoggedErrors)
+ {
+ return false;
+ }
+
+ for (var i = 0; i < solutions.Count; i++)
+ {
+ var sln = repoGraph[i];
+
+ var deps = from proj in sln.Projects
+ from tfm in proj.Frameworks
+ from dep in tfm.Dependencies.Values
+ select dep;
+
+ foreach (var dep in deps)
+ {
+ if (packageToProjectMap.TryGetValue(new PackageIdentity(dep.Id, new NuGetVersion(dep.Version)), out var target))
+ {
+ var j = repoGraph.FindIndex(target.SolutionInfo);
+ repoGraph.SetLink(i, j);
+ }
+ }
+
+ var toolDeps = from proj in sln.Projects
+ from tool in proj.Tools
+ select tool;
+
+ foreach (var toolDep in toolDeps)
+ {
+ if (packageToProjectMap.TryGetValue(new PackageIdentity(toolDep.Id, new NuGetVersion(toolDep.Version)), out var target))
+ {
+ var j = repoGraph.FindIndex(target.SolutionInfo);
+ repoGraph.SetLink(i, j);
+ }
+ }
+ }
+
+ CreateDgml(repoGraph);
+ return !Log.HasLoggedErrors;
+ }
+
+
+ private void CreateDgml(AdjacencyMatrix repoGraph)
+ {
+ var dgml = new DirectedGraphXml();
+
+ for (var i = 0; i < repoGraph.Count; i++)
+ {
+ var node = repoGraph[i];
+ var nodeName = Path.GetFileName(node.Directory);
+ dgml.AddNode(nodeName);
+
+ for (var j = 0; j < repoGraph.Count; j++)
+ {
+ if (j == i) continue;
+ if (repoGraph.HasLink(i, j))
+ {
+ var target = repoGraph[j];
+ var targetName = Path.GetFileName(target.Directory);
+ dgml.AddLink(nodeName, targetName);
+ }
+ }
+ }
+
+ dgml.Save(Path.Combine(RepositoryRoot, "modules", "SubmoduleGraph.dgml"));
+ }
+
+ private class AdjacencyMatrix
+ {
+ private readonly bool[,] _matrix;
+ private readonly SolutionInfo[] _items;
+
+ public AdjacencyMatrix(int size)
+ {
+ _matrix = new bool[size, size];
+ _items = new SolutionInfo[size];
+ Count = size;
+ }
+
+ public SolutionInfo this[int idx]
+ {
+ get => _items[idx];
+ set => _items[idx] = value;
+ }
+
+ public int FindIndex(SolutionInfo item)
+ {
+ return Array.FindIndex(_items, t => t.Equals(item));
+ }
+
+ public int Count { get; }
+
+ public bool HasLink(int source, int target) => _matrix[source, target];
+
+ public void SetLink(int source, int target)
+ {
+ _matrix[source, target] = true;
+ }
+ }
+ }
+}
diff --git a/build/tasks/CodeGen/RepositoryProject.cs b/build/tasks/CodeGen/RepositoryProject.cs
new file mode 100644
index 0000000000..1cb3b76391
--- /dev/null
+++ b/build/tasks/CodeGen/RepositoryProject.cs
@@ -0,0 +1,46 @@
+// 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.IO;
+using System.Text;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
+
+namespace RepoTasks.CodeGen
+{
+ class RepositoryProject
+ {
+ private readonly ProjectRootElement _doc;
+
+ public RepositoryProject(string repositoryRoot)
+ {
+ _doc = ProjectRootElement.Create(NewProjectFileOptions.None);
+ var import = _doc.CreateImportElement(@"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props");
+ var propGroup = _doc.AddPropertyGroup();
+ if (repositoryRoot[repositoryRoot.Length - 1] != '\\')
+ {
+ repositoryRoot += '\\';
+ }
+ propGroup.AddProperty("RepositoryRoot", repositoryRoot);
+ _doc.AddItemGroup();
+ _doc.PrependChild(import);
+ _doc.AddImport(@"$(MSBuildToolsPath)\Microsoft.Common.targets");
+ }
+
+ public void AddProjectReference(string path)
+ {
+ _doc.AddItem("ProjectReference", path);
+ }
+
+ public void AddProperty(string name, string value)
+ {
+ _doc.AddProperty(name, value);
+ }
+
+ public void Save(string filePath)
+ {
+ _doc.Save(filePath, Encoding.UTF8);
+ }
+ }
+}
diff --git a/build/tasks/ProjectModel/ProjectInfo.cs b/build/tasks/ProjectModel/ProjectInfo.cs
index 1dd4339185..4f4b7a3a9c 100644
--- a/build/tasks/ProjectModel/ProjectInfo.cs
+++ b/build/tasks/ProjectModel/ProjectInfo.cs
@@ -10,7 +10,6 @@ namespace RepoTasks.ProjectModel
internal class ProjectInfo
{
public ProjectInfo(string fullPath,
- string projectExtensionsPath,
IReadOnlyList frameworks,
IReadOnlyList tools,
bool isPackable,
@@ -28,7 +27,6 @@ namespace RepoTasks.ProjectModel
FullPath = fullPath;
FileName = Path.GetFileName(fullPath);
Directory = Path.GetDirectoryName(FullPath);
- ProjectExtensionsPath = projectExtensionsPath ?? Path.Combine(Directory, "obj");
IsPackable = isPackable;
PackageId = packageId;
PackageVersion = packageVersion;
@@ -36,12 +34,13 @@ namespace RepoTasks.ProjectModel
public string FullPath { get; }
public string FileName { get; }
- public string ProjectExtensionsPath { get; }
public string Directory { get; }
public string PackageId { get; }
public string PackageVersion { get; }
public bool IsPackable { get; }
+ public SolutionInfo SolutionInfo { get; set; }
+
public IReadOnlyList Frameworks { get; }
public IReadOnlyList Tools { get; }
}
diff --git a/build/tasks/ProjectModel/ProjectInfoFactory.cs b/build/tasks/ProjectModel/ProjectInfoFactory.cs
index 2acf90be1c..42c0bd3fee 100644
--- a/build/tasks/ProjectModel/ProjectInfoFactory.cs
+++ b/build/tasks/ProjectModel/ProjectInfoFactory.cs
@@ -27,7 +27,6 @@ namespace RepoTasks.ProjectModel
{
var project = GetProject(path, projectCollection);
var instance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
- var projExtPath = instance.GetPropertyValue("MSBuildProjectExtensionsPath");
var targetFrameworks = instance.GetPropertyValue("TargetFrameworks");
var targetFramework = instance.GetPropertyValue("TargetFramework");
@@ -63,7 +62,6 @@ namespace RepoTasks.ProjectModel
var packageVersion = instance.GetPropertyValue("PackageVersion");
return new ProjectInfo(path,
- projExtPath,
frameworks,
tools,
isPackable,
diff --git a/build/tasks/ProjectModel/SolutionInfo.cs b/build/tasks/ProjectModel/SolutionInfo.cs
index 8b9710081e..21af5e5291 100644
--- a/build/tasks/ProjectModel/SolutionInfo.cs
+++ b/build/tasks/ProjectModel/SolutionInfo.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
namespace RepoTasks.ProjectModel
{
@@ -21,13 +22,20 @@ namespace RepoTasks.ProjectModel
}
FullPath = fullPath;
+ Directory = Path.GetDirectoryName(fullPath);
ConfigName = configName;
Projects = projects ?? throw new ArgumentNullException(nameof(projects));
ShouldBuild = shouldBuild;
Shipped = shipped;
+
+ foreach (var proj in Projects)
+ {
+ proj.SolutionInfo = this;
+ }
}
public string FullPath { get; }
+ public string Directory { get; }
public string ConfigName { get; }
public IReadOnlyList Projects { get; }
public bool ShouldBuild { get; }
diff --git a/build/tasks/RepoTasks.csproj b/build/tasks/RepoTasks.csproj
index fbfd560a7c..7c95128fdd 100644
--- a/build/tasks/RepoTasks.csproj
+++ b/build/tasks/RepoTasks.csproj
@@ -4,7 +4,7 @@
- netstandard2.0
+ netcoreapp2.0
diff --git a/build/tasks/RepoTasks.tasks b/build/tasks/RepoTasks.tasks
index 7e014a92a3..6f2032d491 100644
--- a/build/tasks/RepoTasks.tasks
+++ b/build/tasks/RepoTasks.tasks
@@ -9,6 +9,7 @@
+
diff --git a/build/tasks/tasks.sln b/build/tasks/tasks.sln
new file mode 100644
index 0000000000..917ff4ff83
--- /dev/null
+++ b/build/tasks/tasks.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RepoTasks", "RepoTasks.csproj", "{A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|x64.Build.0 = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|x86.Build.0 = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|x64.ActiveCfg = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|x64.Build.0 = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|x86.ActiveCfg = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/modules/Scaffolding b/modules/Scaffolding
index 6baf0d2715..6558238ab8 160000
--- a/modules/Scaffolding
+++ b/modules/Scaffolding
@@ -1 +1 @@
-Subproject commit 6baf0d2715a825c49e7f91189c7ae9ffcb2b4a18
+Subproject commit 6558238ab8c2add9b75b8022d1445ed20c71c7a1
diff --git a/modules/SubmoduleGraph.dgml b/modules/SubmoduleGraph.dgml
new file mode 100644
index 0000000000..cdcd0a92cd
--- /dev/null
+++ b/modules/SubmoduleGraph.dgml
@@ -0,0 +1,421 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file