Begin implementation of build graph analysis
This commit is contained in:
parent
52757943ac
commit
8f25a559a5
|
|
@ -2,12 +2,12 @@
|
|||
<Target Name="_BuildRepositories">
|
||||
<ItemGroup>
|
||||
<BatchedRepository Include="$(MSBuildProjectFullPath)">
|
||||
<BuildGroup>%(RepositoryToBuildInOrder.Order)</BuildGroup>
|
||||
<Repository>%(RepositoryToBuildInOrder.Identity)</Repository>
|
||||
<BuildGroup>%(RepositoryBuildOrder.Order)</BuildGroup>
|
||||
<Repository>%(RepositoryBuildOrder.Identity)</Repository>
|
||||
<AdditionalProperties>
|
||||
RepositoryToBuild=%(RepositoryToBuildInOrder.Identity);
|
||||
BuildRepositoryRoot=%(RepositoryToBuildInOrder.RepositoryPath)\;
|
||||
CommitHash=%(RepositoryToBuildInOrder.Commit)
|
||||
RepositoryToBuild=%(RepositoryBuildOrder.Identity);
|
||||
BuildRepositoryRoot=%(RepositoryBuildOrder.RepositoryPath)\;
|
||||
CommitHash=%(RepositoryBuildOrder.Commit)
|
||||
</AdditionalProperties>
|
||||
</BatchedRepository>
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<_CloneRepositoryRoot>$(RepositoryRoot).r\</_CloneRepositoryRoot>
|
||||
<_DependencyBuildDirectory>$(RepositoryRoot).deps\build\</_DependencyBuildDirectory>
|
||||
<_DependencyPackagesDirectory>$(_DependencyBuildDirectory)</_DependencyPackagesDirectory>
|
||||
<_RestoreGraphSpecsDirectory>$(RepositoryRoot)obj\package-specs\</_RestoreGraphSpecsDirectory>
|
||||
|
||||
<_RepositoryListFileName>Repositories.props</_RepositoryListFileName>
|
||||
<_DefaultRepositoryList>$(MSBuildThisFileDirectory)$(_RepositoryListFileName)</_DefaultRepositoryList>
|
||||
|
|
@ -20,14 +19,13 @@
|
|||
|
||||
<PrepareDependsOn>$(PrepareDependsOn);CleanUniverseArtifacts</PrepareDependsOn>
|
||||
<CleanDependsOn>$(CleanDependsOn);CleanUniverseArtifacts</CleanDependsOn>
|
||||
<BuildDependsOn>$(BuildDependsOn);CloneRepositories;BuildRepositories</BuildDependsOn>
|
||||
<BuildDependsOn>$(BuildDependsOn);BuildRepositories</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(_RepositoryListToImport)" />
|
||||
|
||||
<Target Name="CleanUniverseArtifacts">
|
||||
<RemoveDir Directories="$(RepositoryRoot)obj" Condition="Exists('$(RepositoryRoot)obj')" />
|
||||
<RemoveDir Directories="$(_CloneRepositoryRoot)" Condition="Exists('$(_CloneRepositoryRoot)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_PrepareRepositories">
|
||||
|
|
@ -107,48 +105,43 @@
|
|||
Condition="'$(CloneRepositoryCommit)'!=''" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PrepareBuildGraph" DependsOnTargets="_PrepareRepositories">
|
||||
<RepoTasks.VerifyBuildGraph
|
||||
BuildRepositories="@(Repositories)"
|
||||
NoBuildRepositories="@(VerifyRepositories)">
|
||||
|
||||
<!-- TODO pass this into the PinVersion step -->
|
||||
<Output TaskParameter="PackagesToBeProduced" ItemName="PackagesToBeProduced" />
|
||||
</RepoTasks.VerifyBuildGraph>
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildRepositories"
|
||||
DependsOnTargets="_PrepareRepositories;_CreateRepositoriesListWithCommits;_UpdateNuGetConfig;_GenerateBuildGraph;_BuildRepositories" />
|
||||
DependsOnTargets="_PrepareRepositories;_CreateRepositoriesListWithCommits;_UpdateNuGetConfig;Graph;_BuildRepositories" />
|
||||
|
||||
<Target Name="ResolveSolutions" DependsOnTargets="_PrepareRepositories">
|
||||
<MSBuild Projects="$(MSBuildProjectFullPath)"
|
||||
Targets="ResolveSolutions"
|
||||
Properties="RepositoryRoot=$(_CloneRepositoryRoot)%(Repository.Identity)\;Configuration=$(Configuration);BuildNumber=$(BuildNumber)"
|
||||
ContinueOnError="WarnAndContinue">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="Solution" />
|
||||
</MSBuild>
|
||||
|
||||
<MSBuild Projects="$(MSBuildProjectFullPath)"
|
||||
Targets="ResolveSolutions"
|
||||
Properties="RepositoryRoot=$(_CloneRepositoryRoot)%(VerifyRepository.Identity)\;Configuration=$(Configuration);BuildNumber=$(BuildNumber)"
|
||||
ContinueOnError="WarnAndContinue">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_NoBuildSolution" />
|
||||
</MSBuild>
|
||||
|
||||
<Target Name="_PrepareRestoreGraphSpecs" DependsOnTargets="_PrepareRepositories">
|
||||
<ItemGroup>
|
||||
<Solution Include="$(_CloneRepositoryRoot)%(_CloneRepositories.Identity)\*.sln">
|
||||
<Repository>%(Repository.Identity)</Repository>
|
||||
</Solution>
|
||||
|
||||
<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" />
|
||||
<Solution Update="@(Solution)" Build="true" />
|
||||
<_NoBuildSolution Update="@(_NoBuildSolution)" Build="false" />
|
||||
<Solution Include="@(_NoBuildSolution)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Error Text="No solutions were found in '$(_CloneRepositoryRoot)'" Condition="@(Solution->Count()) == 0" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_GenerateRestoreGraphSpecs" DependsOnTargets="_PrepareRestoreGraphSpecs" Inputs="@(GraphSpecInputs)" Outputs="@(GraphSpecOutputs)">
|
||||
<MSBuild
|
||||
Projects="@(Solution)"
|
||||
Targets="GenerateRestoreGraphFile"
|
||||
BuildInParallel="$(BuildInParallel)" />
|
||||
</Target>
|
||||
<Target Name="Graph" DependsOnTargets="ResolveSolutions">
|
||||
<RepoTasks.AnalyzeBuildGraph
|
||||
Solutions="@(Solution)"
|
||||
Properties="Configuration=$(Configuration);BuildNumber=$(BuildNumber)">
|
||||
<Output TaskParameter="PackagesProduced" ItemName="PackagesProduced" />
|
||||
<Output TaskParameter="RepositoryBuildOrder" ItemName="RepositoryBuildOrder" />
|
||||
</RepoTasks.AnalyzeBuildGraph>
|
||||
|
||||
<Target Name="_GenerateBuildGraph" DependsOnTargets="_GenerateRestoreGraphSpecs">
|
||||
<RepoTasks.CalculateBuildGraph Repositories="@(Repository)" StartGraphAt="$(BuildGraphOf)" PackageSpecsDirectory="$(_RestoreGraphSpecsDirectory)">
|
||||
<Output TaskParameter="RepositoriesToBuildInOrder" ItemName="RepositoryToBuildInOrder" />
|
||||
</RepoTasks.CalculateBuildGraph>
|
||||
<Message Text="Packages that will be produced:" Importance="High" />
|
||||
<Message Text=" - %(PackagesProduced.Identity)/%(Version)" Importance="High" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_UpdateNuGetConfig">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
// 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.Threading;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using RepoTasks.ProjectModel;
|
||||
using RepoTasks.Utilities;
|
||||
|
||||
namespace RepoTasks
|
||||
{
|
||||
public class AnalyzeBuildGraph : 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 string Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// New packages we are compiling. Used in the pin tool.
|
||||
/// </summary>
|
||||
[Output]
|
||||
public ITaskItem[] PackagesProduced { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The order in which to build repositories
|
||||
/// </summary>
|
||||
[Output]
|
||||
public ITaskItem[] RepositoryBuildOrder { get; set; }
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cts.Cancel();
|
||||
}
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
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...");
|
||||
|
||||
var solutions = factory.Create(Solutions, props, _cts.Token);
|
||||
Log.LogMessage($"Found {solutions.Count} and {solutions.Sum(p => p.Projects.Count)} projects");
|
||||
|
||||
if (_cts.IsCancellationRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PackagesProduced = solutions
|
||||
.Where(s => s.ShouldBuild)
|
||||
.SelectMany(p => p.Projects)
|
||||
.Where(p => p.IsPackable)
|
||||
.Select(p => new TaskItem(p.PackageId, new Hashtable
|
||||
{
|
||||
["Version"] = p.PackageVersion
|
||||
}))
|
||||
.ToArray();
|
||||
|
||||
return !Log.HasLoggedErrors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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;
|
||||
|
||||
namespace RepoTasks.ProjectModel
|
||||
{
|
||||
internal class DotNetCliReferenceInfo
|
||||
{
|
||||
public DotNetCliReferenceInfo(string id, string version)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new ArgumentException(nameof(id));
|
||||
}
|
||||
|
||||
Id = id;
|
||||
Version = version;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string Version { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// 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;
|
||||
|
||||
namespace RepoTasks.ProjectModel
|
||||
{
|
||||
internal class PackageReferenceInfo
|
||||
{
|
||||
public PackageReferenceInfo(string id, string version, bool isImplicitlyDefined, IReadOnlyList<string> noWarn)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new ArgumentException(nameof(id));
|
||||
}
|
||||
|
||||
Id = id;
|
||||
Version = version;
|
||||
IsImplicitlyDefined = isImplicitlyDefined;
|
||||
NoWarn = noWarn;
|
||||
}
|
||||
|
||||
public string Id { get; }
|
||||
public string Version { get; }
|
||||
public bool IsImplicitlyDefined { get; }
|
||||
public IReadOnlyList<string> NoWarn { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// 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 NuGet.Frameworks;
|
||||
|
||||
namespace RepoTasks.ProjectModel
|
||||
{
|
||||
internal class ProjectFrameworkInfo
|
||||
{
|
||||
public ProjectFrameworkInfo(NuGetFramework targetFramework, IReadOnlyDictionary<string, PackageReferenceInfo> dependencies)
|
||||
{
|
||||
TargetFramework = targetFramework ?? throw new ArgumentNullException(nameof(targetFramework));
|
||||
Dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies));
|
||||
}
|
||||
|
||||
public NuGetFramework TargetFramework { get; }
|
||||
public IReadOnlyDictionary<string, PackageReferenceInfo> Dependencies { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// 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.IO;
|
||||
|
||||
namespace RepoTasks.ProjectModel
|
||||
{
|
||||
internal class ProjectInfo
|
||||
{
|
||||
public ProjectInfo(string fullPath,
|
||||
string projectExtensionsPath,
|
||||
IReadOnlyList<ProjectFrameworkInfo> frameworks,
|
||||
IReadOnlyList<DotNetCliReferenceInfo> tools,
|
||||
bool isPackable,
|
||||
string packageId,
|
||||
string packageVersion)
|
||||
{
|
||||
if (!Path.IsPathRooted(fullPath))
|
||||
{
|
||||
throw new ArgumentException("Path must be absolute", nameof(fullPath));
|
||||
}
|
||||
|
||||
Frameworks = frameworks ?? throw new ArgumentNullException(nameof(frameworks));
|
||||
Tools = tools ?? throw new ArgumentNullException(nameof(tools));
|
||||
|
||||
FullPath = fullPath;
|
||||
FileName = Path.GetFileName(fullPath);
|
||||
Directory = Path.GetDirectoryName(FullPath);
|
||||
ProjectExtensionsPath = projectExtensionsPath ?? Path.Combine(Directory, "obj");
|
||||
IsPackable = isPackable;
|
||||
PackageId = packageId;
|
||||
PackageVersion = packageVersion;
|
||||
}
|
||||
|
||||
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 IReadOnlyList<ProjectFrameworkInfo> Frameworks { get; }
|
||||
public IReadOnlyList<DotNetCliReferenceInfo> Tools { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
// 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.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Construction;
|
||||
using Microsoft.Build.Evaluation;
|
||||
using Microsoft.Build.Execution;
|
||||
using NuGet.Frameworks;
|
||||
using RepoTasks.Utilities;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace RepoTasks.ProjectModel
|
||||
{
|
||||
internal class ProjectInfoFactory
|
||||
{
|
||||
private readonly TaskLoggingHelper _logger;
|
||||
|
||||
public ProjectInfoFactory(TaskLoggingHelper logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public ProjectInfo Create(string path, ProjectCollection projectCollection)
|
||||
{
|
||||
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");
|
||||
|
||||
var frameworks = new List<ProjectFrameworkInfo>();
|
||||
if (!string.IsNullOrEmpty(targetFrameworks) && string.IsNullOrEmpty(targetFramework))
|
||||
{
|
||||
// multi targeting
|
||||
foreach (var tfm in targetFrameworks.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
project.SetGlobalProperty("TargetFramework", tfm);
|
||||
var innerBuild = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
|
||||
|
||||
var tfmInfo = new ProjectFrameworkInfo(NuGetFramework.Parse(tfm), GetDependencies(innerBuild));
|
||||
|
||||
frameworks.Add(tfmInfo);
|
||||
}
|
||||
|
||||
project.RemoveGlobalProperty("TargetFramework");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(targetFramework))
|
||||
{
|
||||
var tfmInfo = new ProjectFrameworkInfo(NuGetFramework.Parse(targetFramework), GetDependencies(instance));
|
||||
|
||||
frameworks.Add(tfmInfo);
|
||||
}
|
||||
|
||||
var projectDir = Path.GetDirectoryName(path);
|
||||
|
||||
var tools = GetTools(instance).ToArray();
|
||||
bool.TryParse(instance.GetPropertyValue("IsPackable"), out var isPackable);
|
||||
var packageId = instance.GetPropertyValue("PackageId");
|
||||
var packageVersion = instance.GetPropertyValue("PackageVersion");
|
||||
|
||||
return new ProjectInfo(path,
|
||||
projExtPath,
|
||||
frameworks,
|
||||
tools,
|
||||
isPackable,
|
||||
packageId,
|
||||
packageVersion);
|
||||
}
|
||||
|
||||
private static object _projLock = new object();
|
||||
|
||||
private static Project GetProject(string path, ProjectCollection projectCollection)
|
||||
{
|
||||
var projects = projectCollection.GetLoadedProjects(path);
|
||||
foreach(var proj in projects)
|
||||
{
|
||||
if (proj.GetPropertyValue("DesignTimeBuild") == "true")
|
||||
{
|
||||
return proj;
|
||||
}
|
||||
}
|
||||
|
||||
var xml = ProjectRootElement.Open(path, projectCollection);
|
||||
var globalProps = new Dictionary<string, string>()
|
||||
{
|
||||
["DesignTimeBuild"] = "true",
|
||||
};
|
||||
|
||||
var project = new Project(xml,
|
||||
globalProps,
|
||||
toolsVersion: "15.0",
|
||||
projectCollection: projectCollection)
|
||||
{
|
||||
IsBuildEnabled = false
|
||||
};
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, PackageReferenceInfo> GetDependencies(ProjectInstance project)
|
||||
{
|
||||
var references = new Dictionary<string, PackageReferenceInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var item in project.GetItems("PackageReference"))
|
||||
{
|
||||
bool.TryParse(item.GetMetadataValue("IsImplicitlyDefined"), out var isImplicit);
|
||||
var noWarn = item.GetMetadataValue("NoWarn");
|
||||
IReadOnlyList<string> noWarnItems = string.IsNullOrEmpty(noWarn)
|
||||
? Array.Empty<string>()
|
||||
: MSBuildListSplitter.SplitItemList(noWarn).ToArray();
|
||||
|
||||
var info = new PackageReferenceInfo(item.EvaluatedInclude, item.GetMetadataValue("Version"), isImplicit, noWarnItems);
|
||||
|
||||
if (references.ContainsKey(info.Id))
|
||||
{
|
||||
_logger.LogKoreBuildWarning(project.ProjectFileLocation.File, KoreBuildErrors.DuplicatePackageReference, $"Found a duplicate PackageReference for {info.Id}. Restore results may be unpredictable.");
|
||||
}
|
||||
|
||||
references[info.Id] = info;
|
||||
}
|
||||
|
||||
return references;
|
||||
}
|
||||
|
||||
private static IEnumerable<DotNetCliReferenceInfo> GetTools(ProjectInstance project)
|
||||
{
|
||||
return project.GetItems("DotNetCliToolReference").Select(item =>
|
||||
new DotNetCliReferenceInfo(item.EvaluatedInclude, item.GetMetadataValue("Version")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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;
|
||||
|
||||
namespace RepoTasks.ProjectModel
|
||||
{
|
||||
internal class SolutionInfo
|
||||
{
|
||||
public SolutionInfo(string fullPath, string configName, IReadOnlyList<ProjectInfo> projects, bool shouldBuild)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fullPath))
|
||||
{
|
||||
throw new ArgumentException(nameof(fullPath));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(configName))
|
||||
{
|
||||
throw new ArgumentException(nameof(configName));
|
||||
}
|
||||
|
||||
FullPath = fullPath;
|
||||
ConfigName = configName;
|
||||
Projects = projects ?? throw new ArgumentNullException(nameof(projects));
|
||||
ShouldBuild = shouldBuild;
|
||||
}
|
||||
|
||||
public string FullPath { get; }
|
||||
public string ConfigName { get; }
|
||||
public IReadOnlyList<ProjectInfo> Projects { get; }
|
||||
public bool ShouldBuild { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
// 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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Build.Construction;
|
||||
using Microsoft.Build.Evaluation;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using RepoTasks.Utilities;
|
||||
|
||||
namespace RepoTasks.ProjectModel
|
||||
{
|
||||
internal class SolutionInfoFactory
|
||||
{
|
||||
private readonly TaskLoggingHelper _logger;
|
||||
private readonly IBuildEngine4 _buildEngine;
|
||||
|
||||
public SolutionInfoFactory(TaskLoggingHelper logger, IBuildEngine4 buildEngine)
|
||||
{
|
||||
_logger = logger;
|
||||
_buildEngine = buildEngine;
|
||||
}
|
||||
|
||||
public IReadOnlyList<SolutionInfo> Create(IEnumerable<ITaskItem> solutionItems, IDictionary<string, string> properties, CancellationToken ct)
|
||||
{
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
var solutions = new ConcurrentBag<SolutionInfo>();
|
||||
|
||||
Parallel.ForEach(solutionItems, solution =>
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var solutionFile = solution.ItemSpec.Replace('\\', '/');
|
||||
var solutionProps = new Dictionary<string, string>(properties, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var prop in MSBuildListSplitter.GetNamedProperties(solution.GetMetadata("AdditionalProperties")))
|
||||
{
|
||||
solutionProps[prop.Key] = prop.Value;
|
||||
}
|
||||
|
||||
if (solutionProps.TryGetValue("Configuration", out var configName))
|
||||
{
|
||||
solutionProps["Configuration"] = configName = "Debug";
|
||||
}
|
||||
|
||||
var key = $"SlnInfo:{solutionFile}:{configName}";
|
||||
var obj = _buildEngine.GetRegisteredTaskObject(key, RegisteredTaskObjectLifetime.Build);
|
||||
|
||||
if (obj is SolutionInfo cachedSlnInfo)
|
||||
{
|
||||
solutions.Add(cachedSlnInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogMessage($"Analyzing {solutionFile} ({configName})");
|
||||
var projects = new ConcurrentBag<ProjectInfo>();
|
||||
var projectFiles = GetProjectsForSolutionConfig(solutionFile, configName);
|
||||
using (var projCollection = new ProjectCollection(solutionProps) { IsBuildEnabled = false })
|
||||
{
|
||||
Parallel.ForEach(projectFiles, projectFile =>
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
projects.Add(new ProjectInfoFactory(_logger).Create(projectFile, projCollection));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogErrorFromException(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool.TryParse(solution.GetMetadata("Build"), out var shouldBuild);
|
||||
|
||||
var solutionInfo = new SolutionInfo(
|
||||
solutionFile,
|
||||
configName,
|
||||
projects.ToArray(),
|
||||
shouldBuild);
|
||||
|
||||
_buildEngine.RegisterTaskObject(key, solutionInfo, RegisteredTaskObjectLifetime.Build, allowEarlyCollection: true);
|
||||
|
||||
solutions.Add(solutionInfo);
|
||||
});
|
||||
|
||||
timer.Stop();
|
||||
_logger.LogMessage(MessageImportance.Normal, $"Finished design-time build in {timer.ElapsedMilliseconds}ms");
|
||||
return solutions.ToArray();
|
||||
}
|
||||
|
||||
private IList<string> GetProjectsForSolutionConfig(string filePath, string configName)
|
||||
{
|
||||
var sln = SolutionFile.Parse(filePath);
|
||||
|
||||
if (string.IsNullOrEmpty(configName))
|
||||
{
|
||||
configName = sln.GetDefaultConfigurationName();
|
||||
}
|
||||
|
||||
var projects = new List<string>();
|
||||
|
||||
var config = sln.SolutionConfigurations.FirstOrDefault(c => c.ConfigurationName == configName);
|
||||
if (config == null)
|
||||
{
|
||||
throw new InvalidOperationException($"A solution configuration by the name of '{configName}' was not found in '{filePath}'");
|
||||
}
|
||||
|
||||
foreach (var project in sln.ProjectsInOrder
|
||||
.Where(p =>
|
||||
p.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat // skips solution folders
|
||||
&& p.ProjectConfigurations.TryGetValue(config.FullName, out var projectConfig)
|
||||
&& projectConfig.IncludeInBuild))
|
||||
{
|
||||
projects.Add(project.AbsolutePath.Replace('\\', '/'));
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,6 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<UsingTask TaskName="RepoTasks.CalculateBuildGraph" AssemblyFile="$(_RepoTaskAssembly)" />
|
||||
<UsingTask TaskName="RepoTasks.VerifyBuildGraph" AssemblyFile="$(_RepoTaskAssembly)" />
|
||||
<UsingTask TaskName="RepoTasks.AnalyzeBuildGraph" AssemblyFile="$(_RepoTaskAssembly)" />
|
||||
<UsingTask TaskName="RepoTasks.PinVersions" AssemblyFile="$(_RepoTaskAssembly)" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
// 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.
|
||||
|
||||
namespace RepoTasks.Utilities
|
||||
{
|
||||
public static class KoreBuildErrors
|
||||
{
|
||||
public const string Prefix = "KRB";
|
||||
|
||||
// Typically used in repos in Directory.Build.targets
|
||||
public const int PackagesHaveNotYetBeenPinned = 1001;
|
||||
|
||||
// Warnings
|
||||
public const int DotNetAssetVersionIsFloating = 2000;
|
||||
public const int RepoVersionDoesNotMatchProjectVersion = 2001;
|
||||
public const int RepoPackageVersionDoesNotMatchProjectPackageVersion = 2002;
|
||||
public const int DuplicatePackageReference = 2003;
|
||||
|
||||
// NuGet errors
|
||||
public const int InvalidNuspecFile = 4001;
|
||||
public const int PackageReferenceHasVersion = 4002;
|
||||
public const int DotNetCliReferenceReferenceHasVersion = 4003;
|
||||
public const int PackageVersionNotFoundInLineup = 4004;
|
||||
|
||||
// Other unknown errors
|
||||
public const int PolicyFailedToApply = 5000;
|
||||
public const int UnknownPolicyType = 5001;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// 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 Microsoft.Build.Utilities;
|
||||
|
||||
namespace RepoTasks.Utilities
|
||||
{
|
||||
public static class LoggingExtensions
|
||||
{
|
||||
public static void LogKoreBuildError(this TaskLoggingHelper logger, int code, string message, params object[] messageArgs)
|
||||
=> LogKoreBuildError(logger, null, code, message, messageArgs: messageArgs);
|
||||
|
||||
public static void LogKoreBuildError(this TaskLoggingHelper logger, string filename, int code, string message, params object[] messageArgs)
|
||||
{
|
||||
logger.LogError(null, KoreBuildErrors.Prefix + code, null, filename, 0, 0, 0, 0, message, messageArgs: messageArgs);
|
||||
}
|
||||
|
||||
public static void LogKoreBuildWarning(this TaskLoggingHelper logger, int code, string message, params object[] messageArgs)
|
||||
=> LogKoreBuildWarning(logger, null, code, message, messageArgs: messageArgs);
|
||||
|
||||
public static void LogKoreBuildWarning(this TaskLoggingHelper logger, string filename, int code, string message, params object[] messageArgs)
|
||||
{
|
||||
logger.LogWarning(null, KoreBuildErrors.Prefix + code, null, filename, 0, 0, 0, 0, message, messageArgs: messageArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// 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;
|
||||
|
||||
namespace RepoTasks.Utilities
|
||||
{
|
||||
internal static class MSBuildListSplitter
|
||||
{
|
||||
private static readonly char[] SemiColon = { ';' };
|
||||
|
||||
public static IEnumerable<string> SplitItemList(string value)
|
||||
{
|
||||
return string.IsNullOrEmpty(value)
|
||||
? Enumerable.Empty<string>()
|
||||
: value.Split(SemiColon, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> GetNamedProperties(string input)
|
||||
{
|
||||
var values = new Dictionary<string, string>();
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
foreach (var item in input.Split(SemiColon, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var splitIdx = item.IndexOf('=');
|
||||
if (splitIdx <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = item.Substring(0, splitIdx).Trim();
|
||||
var value = item.Substring(splitIdx + 1);
|
||||
values[key] = value;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// 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 Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace RepoTasks
|
||||
{
|
||||
public class VerifyBuildGraph : Task
|
||||
{
|
||||
/// <summary>
|
||||
/// Repositories that we are building new versions of.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ITaskItem[] BuildRepositories { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Repos that have already been build and released. We don't compile and build them,
|
||||
/// but we still want to be sure their packages are accounted for in our graph calculations.
|
||||
/// </summary>
|
||||
public ITaskItem[] NoBuildRepositories { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// New packages we are compiling. Used in the pin tool.
|
||||
/// </summary>
|
||||
[Output]
|
||||
public ITaskItem[] PackagesToBeProduced { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue