aspnetcore/build/tasks/VerifyCoherentVersions.cs

134 lines
5.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.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using NuGet.Frameworks;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Versioning;
using RepoTasks.ProjectModel;
namespace RepoTasks
{
public class VerifyCoherentVersions : Microsoft.Build.Utilities.Task
{
[Required]
public ITaskItem[] PackageFiles { get; set; }
[Required]
public ITaskItem[] ExternalDependencies { get; set; }
public override bool Execute()
{
if (PackageFiles.Length == 0)
{
Log.LogError("Did not find any packages to verify for version coherence");
return false;
}
var packageLookup = new Dictionary<string, PackageInfo>(StringComparer.OrdinalIgnoreCase);
var dependencyMap = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
foreach (var dep in ExternalDependencies)
{
if (!dependencyMap.TryGetValue(dep.ItemSpec, out var list))
{
dependencyMap[dep.ItemSpec] = list = new List<string>();
}
list.Add(dep.GetMetadata("Version"));
}
foreach (var file in PackageFiles)
{
PackageInfo package;
using (var reader = new PackageArchiveReader(file.ItemSpec))
{
var identity = reader.GetIdentity();
var metadata = new PackageBuilder(reader.GetNuspec(), basePath: null);
package = new PackageInfo(identity.Id, identity.Version,
source: Path.GetDirectoryName(file.ItemSpec),
dependencyGroups: metadata.DependencyGroups.ToArray());
}
if (packageLookup.TryGetValue(package.Id, out var existingPackage))
{
Log.LogError("Multiple copies of the following package were found: " +
Environment.NewLine +
existingPackage +
Environment.NewLine +
package);
continue;
}
packageLookup[package.Id] = package;
}
foreach (var packageInfo in packageLookup.Values)
{
Visit(packageLookup, dependencyMap, packageInfo);
}
Log.LogMessage(MessageImportance.High, $"Verified {PackageFiles.Length} package(s) have coherent versions");
return !Log.HasLoggedErrors;
}
private void Visit(
IReadOnlyDictionary<string, PackageInfo> packageLookup,
IReadOnlyDictionary<string, List<string>> dependencyMap,
PackageInfo packageInfo)
{
Log.LogMessage(MessageImportance.Low, $"Processing package {packageInfo.Id}");
try
{
foreach (var dependencySet in packageInfo.DependencyGroups)
{
foreach (var dependency in dependencySet.Packages)
{
PackageInfo dependencyPackageInfo;
var depVersion = dependency.VersionRange.MinVersion.ToString();
if (dependencyMap.TryGetValue(dependency.Id, out var externalDepVersions))
{
var matchedVersion = externalDepVersions.FirstOrDefault(d => depVersion.Equals(d));
if (matchedVersion == null)
{
var versions = string.Join(" or ", externalDepVersions);
Log.LogError($"Package {packageInfo.Id} has an external dependency on the wrong version of {dependency.Id}. "
+ $"It uses {depVersion} but only {versions} is allowed.");
}
continue;
}
else if (!packageLookup.TryGetValue(dependency.Id, out dependencyPackageInfo))
{
Log.LogError($"Package {packageInfo.Id} has an undefined external dependency on {dependency.Id}/{depVersion}. " +
"If the package is built in aspnet/Universe, make sure it is also marked as 'ship'. " +
"If it is an external dependency, add it as a new ExternalDependency.");
continue;
}
if (dependencyPackageInfo.Version != dependency.VersionRange.MinVersion)
{
// For any dependency in the universe
// Add a mismatch if the min version doesn't work out
// (we only really care about >= minVersion)
Log.LogError($"{packageInfo.Id} depends on {dependency.Id} " +
$"{dependency.VersionRange} ({dependencySet.TargetFramework}) when the latest build is {dependencyPackageInfo.Version}.");
}
}
}
}
catch (Exception ex)
{
Log.LogError($"Unexpected error while attempting to verify package {packageInfo.Id}.\r\n{ex}");
}
}
}
}