Begin implementation of build graph analysis

This commit is contained in:
Nate McMaster 2017-09-14 17:13:57 -07:00
parent 52757943ac
commit 8f25a559a5
15 changed files with 636 additions and 81 deletions

View File

@ -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>

View File

@ -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">

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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")));
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}