Begin implementation of build graph analysis
This commit is contained in:
parent
52757943ac
commit
8f25a559a5
|
|
@ -2,12 +2,12 @@
|
||||||
<Target Name="_BuildRepositories">
|
<Target Name="_BuildRepositories">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BatchedRepository Include="$(MSBuildProjectFullPath)">
|
<BatchedRepository Include="$(MSBuildProjectFullPath)">
|
||||||
<BuildGroup>%(RepositoryToBuildInOrder.Order)</BuildGroup>
|
<BuildGroup>%(RepositoryBuildOrder.Order)</BuildGroup>
|
||||||
<Repository>%(RepositoryToBuildInOrder.Identity)</Repository>
|
<Repository>%(RepositoryBuildOrder.Identity)</Repository>
|
||||||
<AdditionalProperties>
|
<AdditionalProperties>
|
||||||
RepositoryToBuild=%(RepositoryToBuildInOrder.Identity);
|
RepositoryToBuild=%(RepositoryBuildOrder.Identity);
|
||||||
BuildRepositoryRoot=%(RepositoryToBuildInOrder.RepositoryPath)\;
|
BuildRepositoryRoot=%(RepositoryBuildOrder.RepositoryPath)\;
|
||||||
CommitHash=%(RepositoryToBuildInOrder.Commit)
|
CommitHash=%(RepositoryBuildOrder.Commit)
|
||||||
</AdditionalProperties>
|
</AdditionalProperties>
|
||||||
</BatchedRepository>
|
</BatchedRepository>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
<_CloneRepositoryRoot>$(RepositoryRoot).r\</_CloneRepositoryRoot>
|
<_CloneRepositoryRoot>$(RepositoryRoot).r\</_CloneRepositoryRoot>
|
||||||
<_DependencyBuildDirectory>$(RepositoryRoot).deps\build\</_DependencyBuildDirectory>
|
<_DependencyBuildDirectory>$(RepositoryRoot).deps\build\</_DependencyBuildDirectory>
|
||||||
<_DependencyPackagesDirectory>$(_DependencyBuildDirectory)</_DependencyPackagesDirectory>
|
<_DependencyPackagesDirectory>$(_DependencyBuildDirectory)</_DependencyPackagesDirectory>
|
||||||
<_RestoreGraphSpecsDirectory>$(RepositoryRoot)obj\package-specs\</_RestoreGraphSpecsDirectory>
|
|
||||||
|
|
||||||
<_RepositoryListFileName>Repositories.props</_RepositoryListFileName>
|
<_RepositoryListFileName>Repositories.props</_RepositoryListFileName>
|
||||||
<_DefaultRepositoryList>$(MSBuildThisFileDirectory)$(_RepositoryListFileName)</_DefaultRepositoryList>
|
<_DefaultRepositoryList>$(MSBuildThisFileDirectory)$(_RepositoryListFileName)</_DefaultRepositoryList>
|
||||||
|
|
@ -20,14 +19,13 @@
|
||||||
|
|
||||||
<PrepareDependsOn>$(PrepareDependsOn);CleanUniverseArtifacts</PrepareDependsOn>
|
<PrepareDependsOn>$(PrepareDependsOn);CleanUniverseArtifacts</PrepareDependsOn>
|
||||||
<CleanDependsOn>$(CleanDependsOn);CleanUniverseArtifacts</CleanDependsOn>
|
<CleanDependsOn>$(CleanDependsOn);CleanUniverseArtifacts</CleanDependsOn>
|
||||||
<BuildDependsOn>$(BuildDependsOn);CloneRepositories;BuildRepositories</BuildDependsOn>
|
<BuildDependsOn>$(BuildDependsOn);BuildRepositories</BuildDependsOn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Import Project="$(_RepositoryListToImport)" />
|
<Import Project="$(_RepositoryListToImport)" />
|
||||||
|
|
||||||
<Target Name="CleanUniverseArtifacts">
|
<Target Name="CleanUniverseArtifacts">
|
||||||
<RemoveDir Directories="$(RepositoryRoot)obj" Condition="Exists('$(RepositoryRoot)obj')" />
|
<RemoveDir Directories="$(RepositoryRoot)obj" Condition="Exists('$(RepositoryRoot)obj')" />
|
||||||
<RemoveDir Directories="$(_CloneRepositoryRoot)" Condition="Exists('$(_CloneRepositoryRoot)')" />
|
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<Target Name="_PrepareRepositories">
|
<Target Name="_PrepareRepositories">
|
||||||
|
|
@ -107,48 +105,43 @@
|
||||||
Condition="'$(CloneRepositoryCommit)'!=''" />
|
Condition="'$(CloneRepositoryCommit)'!=''" />
|
||||||
</Target>
|
</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"
|
<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>
|
<ItemGroup>
|
||||||
<Solution Include="$(_CloneRepositoryRoot)%(_CloneRepositories.Identity)\*.sln">
|
<Solution Update="@(Solution)" Build="true" />
|
||||||
<Repository>%(Repository.Identity)</Repository>
|
<_NoBuildSolution Update="@(_NoBuildSolution)" Build="false" />
|
||||||
</Solution>
|
<Solution Include="@(_NoBuildSolution)" />
|
||||||
|
|
||||||
<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>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Error Text="No solutions were found in '$(_CloneRepositoryRoot)'" Condition="@(Solution->Count()) == 0" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<Target Name="_GenerateRestoreGraphSpecs" DependsOnTargets="_PrepareRestoreGraphSpecs" Inputs="@(GraphSpecInputs)" Outputs="@(GraphSpecOutputs)">
|
<Target Name="Graph" DependsOnTargets="ResolveSolutions">
|
||||||
<MSBuild
|
<RepoTasks.AnalyzeBuildGraph
|
||||||
Projects="@(Solution)"
|
Solutions="@(Solution)"
|
||||||
Targets="GenerateRestoreGraphFile"
|
Properties="Configuration=$(Configuration);BuildNumber=$(BuildNumber)">
|
||||||
BuildInParallel="$(BuildInParallel)" />
|
<Output TaskParameter="PackagesProduced" ItemName="PackagesProduced" />
|
||||||
</Target>
|
<Output TaskParameter="RepositoryBuildOrder" ItemName="RepositoryBuildOrder" />
|
||||||
|
</RepoTasks.AnalyzeBuildGraph>
|
||||||
|
|
||||||
<Target Name="_GenerateBuildGraph" DependsOnTargets="_GenerateRestoreGraphSpecs">
|
<Message Text="Packages that will be produced:" Importance="High" />
|
||||||
<RepoTasks.CalculateBuildGraph Repositories="@(Repository)" StartGraphAt="$(BuildGraphOf)" PackageSpecsDirectory="$(_RestoreGraphSpecsDirectory)">
|
<Message Text=" - %(PackagesProduced.Identity)/%(Version)" Importance="High" />
|
||||||
<Output TaskParameter="RepositoriesToBuildInOrder" ItemName="RepositoryToBuildInOrder" />
|
|
||||||
</RepoTasks.CalculateBuildGraph>
|
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<Target Name="_UpdateNuGetConfig">
|
<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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<UsingTask TaskName="RepoTasks.CalculateBuildGraph" AssemblyFile="$(_RepoTaskAssembly)" />
|
<UsingTask TaskName="RepoTasks.CalculateBuildGraph" AssemblyFile="$(_RepoTaskAssembly)" />
|
||||||
<UsingTask TaskName="RepoTasks.VerifyBuildGraph" AssemblyFile="$(_RepoTaskAssembly)" />
|
<UsingTask TaskName="RepoTasks.AnalyzeBuildGraph" AssemblyFile="$(_RepoTaskAssembly)" />
|
||||||
<UsingTask TaskName="RepoTasks.PinVersions" AssemblyFile="$(_RepoTaskAssembly)" />
|
<UsingTask TaskName="RepoTasks.PinVersions" AssemblyFile="$(_RepoTaskAssembly)" />
|
||||||
</Project>
|
</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