227 lines
7.6 KiB
C#
227 lines
7.6 KiB
C#
|
|
// 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();
|
|
|
|
/// <summary>
|
|
/// Repositories that we are building new versions of.
|
|
/// </summary>
|
|
[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<ArtifactInfo.Package>()
|
|
.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<string, ArtifactInfo.Package> packageArtifacts, IReadOnlyList<SolutionInfo> solutions)
|
|
{
|
|
var repoGraph = new AdjacencyMatrix(solutions.Count);
|
|
var packageToProjectMap = new Dictionary<PackageIdentity, ProjectInfo>();
|
|
|
|
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<ProjectFrameworkInfo>(),
|
|
Array.Empty<DotNetCliReferenceInfo>(),
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|