diff --git a/build/dependencies.props b/build/dependencies.props new file mode 100644 index 0000000000..7094a9050b --- /dev/null +++ b/build/dependencies.props @@ -0,0 +1,178 @@ + + + + + + + + + + + + false + + + + + + https://dotnet.myget.org/F/roslyn/api/v3/index.json + + + + + + KRB2004 + + + + KRB2004 + + + + + + KRB2004 + + + + + + + + https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json + + + + + + + + + https://dotnet.myget.org/F/aspnetcoremodule/api/v3/index.json + + + + + + + + + https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json + + + + + + + + + + https://api.nuget.org/v3/index.json + + + + + + + + + + + + + + + + + KRB2004 + + + + + KRB2004 + + + + KRB2004 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KRB2004 + + + + KRB2004 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KRB2004 + + + + KRB2004 + + + + + + KRB2004 + + + + KRB2004 + + + diff --git a/build/repo.beforecommon.props b/build/repo.beforecommon.props new file mode 100644 index 0000000000..0550358981 --- /dev/null +++ b/build/repo.beforecommon.props @@ -0,0 +1,10 @@ + + + + true + + diff --git a/build/repo.props b/build/repo.props index a98645c000..63a8d665d0 100644 --- a/build/repo.props +++ b/build/repo.props @@ -5,4 +5,5 @@ + diff --git a/build/repo.targets b/build/repo.targets index f6fb26a5c9..2ddd3f45e9 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -75,9 +75,9 @@ - + BuildInParallel="$(BuildInParallel)" /> @@ -161,6 +161,7 @@ @@ -199,13 +200,15 @@ DestinationFolder="$(ArtifactsDir)" /> - + + + PackageFiles="@(ShippingPackageFiles)" + ExternalDependencies="@(ExternalDependency);@(ShippedExternalDependency)" /> diff --git a/build/tasks/AnalyzeBuildGraph.cs b/build/tasks/AnalyzeBuildGraph.cs index 7776f07319..1119b21dcf 100644 --- a/build/tasks/AnalyzeBuildGraph.cs +++ b/build/tasks/AnalyzeBuildGraph.cs @@ -31,6 +31,9 @@ namespace RepoTasks [Required] public ITaskItem[] Artifacts { get; set; } + [Required] + public ITaskItem[] Dependencies { get; set; } + // Artifacts that already shipped from repos [Required] public ITaskItem[] ShippedArtifacts { get; set; } @@ -95,6 +98,21 @@ namespace RepoTasks // ensure versions cascade var buildPackageMap = packages.ToDictionary(p => p.PackageInfo.Id, p => p, StringComparer.OrdinalIgnoreCase); + var dependencyMap = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var dep in Dependencies) + { + if (!dependencyMap.TryGetValue(dep.ItemSpec, out var versions)) + { + dependencyMap[dep.ItemSpec] = versions = new List(); + } + else if (dep.GetMetadata("NoWarn") == null || dep.GetMetadata("NoWarn").IndexOf("KRB" + KoreBuildErrors.MultipleExternalDependencyVersions) < 0) + { + Log.LogKoreBuildWarning( + KoreBuildErrors.MultipleExternalDependencyVersions, + message: $"Multiple versions of external dependency '{dep.ItemSpec}' are defined. In most cases, there should only be one version of external dependencies."); + } + versions.Add(dep.GetMetadata("Version")); + } var inconsistentVersions = new List(); var reposThatShouldPatch = new HashSet(); @@ -107,7 +125,25 @@ namespace RepoTasks { if (!buildPackageMap.TryGetValue(dependency.Key, out var package)) { - // this dependency is not a PackageReference to something that we build in Universe + // This dependency is not one of the packages that will be compiled by this run of Universe. + + var matchesExternalDependency = false; + if (shippedPackageMap.TryGetValue(dependency.Key, out var shippedPackage)) + { + matchesExternalDependency = shippedPackage.PackageInfo.Version.Equals(NuGetVersion.Parse(dependency.Value.Version)); + } + else if (dependencyMap.TryGetValue(dependency.Key, out var externalVersions)) + { + matchesExternalDependency = externalVersions.Contains(dependency.Value.Version); + } + + if (!matchesExternalDependency) + { + Log.LogKoreBuildError( + project.FullPath, + KoreBuildErrors.UndefinedExternalDependency, + message: $"Undefined external dependency on {dependency.Key}/{dependency.Value.Version}"); + } continue; } diff --git a/build/tasks/Utilities/KoreBuildErrors.cs b/build/tasks/Utilities/KoreBuildErrors.cs index 7751985c6f..b3693e5011 100644 --- a/build/tasks/Utilities/KoreBuildErrors.cs +++ b/build/tasks/Utilities/KoreBuildErrors.cs @@ -15,12 +15,14 @@ namespace RepoTasks.Utilities public const int RepoVersionDoesNotMatchProjectVersion = 2001; public const int RepoPackageVersionDoesNotMatchProjectPackageVersion = 2002; public const int DuplicatePackageReference = 2003; + public const int MultipleExternalDependencyVersions = 2004; // NuGet errors public const int InvalidNuspecFile = 4001; public const int PackageReferenceHasVersion = 4002; public const int DotNetCliReferenceReferenceHasVersion = 4003; public const int PackageVersionNotFoundInLineup = 4004; + public const int UndefinedExternalDependency = 4005; // Other unknown errors public const int PolicyFailedToApply = 5000; diff --git a/build/tasks/VerifyCoherentVersions.cs b/build/tasks/VerifyCoherentVersions.cs index 6001041d0e..1ea4afcfce 100644 --- a/build/tasks/VerifyCoherentVersions.cs +++ b/build/tasks/VerifyCoherentVersions.cs @@ -18,11 +18,27 @@ 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() { var packageLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var dependencyMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var dep in ExternalDependencies) + { + if (!dependencyMap.TryGetValue(dep.ItemSpec, out var externalDep)) + { + dependencyMap[dep.ItemSpec] = externalDep = new ExternalDependency(); + } + externalDep.IsPrivate = bool.TryParse(dep.GetMetadata("Private"), out var isPrivate) && isPrivate; + externalDep.AllowedVersions.Add(dep.GetMetadata("Version")); + } + foreach (var file in PackageFiles) { PackageInfo package; @@ -47,51 +63,51 @@ namespace RepoTasks packageLookup[package.Id] = package; } - var dependencyIssues = new List(); foreach (var packageInfo in packageLookup.Values) { - dependencyIssues.AddRange(Visit(packageLookup, packageInfo)); - } - - var success = true; - foreach (var mismatch in dependencyIssues) - { - var message = $"{mismatch.Info.Id} depends on {mismatch.Dependency.Id} " + - $"v{mismatch.Dependency.VersionRange} ({mismatch.TargetFramework}) when the latest build is v{mismatch.Info.Version}."; - Log.LogError(message); - success = false; + Visit(packageLookup, dependencyMap, packageInfo); } Log.LogMessage(MessageImportance.High, $"Verified {PackageFiles.Length} package(s) have coherent versions"); - return success; + return !Log.HasLoggedErrors; } - private class DependencyWithIssue + private class ExternalDependency { - public PackageDependency Dependency { get; set; } - public PackageInfo Info { get; set; } - public NuGetFramework TargetFramework { get; set; } + public List AllowedVersions { get; } = new List(); + public bool IsPrivate { get; set; } } - private IEnumerable Visit(IDictionary packageLookup, PackageInfo packageInfo) + private void Visit( + IReadOnlyDictionary packageLookup, + IReadOnlyDictionary dependencyMap, + PackageInfo packageInfo) { Log.LogMessage(MessageImportance.Low, $"Processing package {packageInfo.Id}"); try { - var issues = new List(); foreach (var dependencySet in packageInfo.DependencyGroups) { - // If the package doens't target any frameworks, just accept it - if (dependencySet.TargetFramework == null) - { - continue; - } - foreach (var dependency in dependencySet.Packages) { - if (!packageLookup.TryGetValue(dependency.Id, out var dependencyPackageInfo)) + PackageInfo dependencyPackageInfo; + var depVersion = dependency.VersionRange.MinVersion.ToString(); + if (dependencyMap.TryGetValue(dependency.Id, out var externalDepInfo)) { - // External dependency + if (externalDepInfo.IsPrivate) + { + Log.LogError($"Package {packageInfo.Id} has an external dependency on {dependency.Id}/{depVersion} which is marked as Private=true."); + } + else if (!externalDepInfo.AllowedVersions.Any(a => depVersion.Equals(a))) + { + Log.LogError($"Package {packageInfo.Id} has an external dependency on the wrong version of {dependency.Id}. " + + $"It uses {depVersion} but only {string.Join(" or ", externalDepInfo.AllowedVersions)} 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}"); continue; } @@ -100,21 +116,15 @@ namespace RepoTasks // For any dependency in the universe // Add a mismatch if the min version doesn't work out // (we only really care about >= minVersion) - issues.Add(new DependencyWithIssue - { - Dependency = dependency, - TargetFramework = dependencySet.TargetFramework, - Info = dependencyPackageInfo - }); + Log.LogError($"{packageInfo.Id} depends on {dependency.Id} " + + $"{dependency.VersionRange} ({dependencySet.TargetFramework}) when the latest build is {depVersion}."); } } } - return issues; } - catch + catch (Exception ex) { - Log.LogError($"Unable to verify package {packageInfo.Id}"); - throw; + Log.LogError($"Unexpected error while attempting to verify package {packageInfo.Id}.\r\n{ex}"); } } }