Build - Verify NuGet

This commit is contained in:
Stephan
2017-07-10 12:32:06 +02:00
parent 5c136483cc
commit 5fe38d6aaf
7 changed files with 453 additions and 247 deletions

View File

@@ -81,6 +81,16 @@ function Get-UmbracoBuildEnv
mv "$file" $semver
Remove-Directory $dir
}
try
{
[Reflection.Assembly]::LoadFile($semver) > $null
}
catch
{
Write-Error -Exception $_.Exception -Message "Failed to load $semver"
break
}
# ensure we have node
$node = "$path\node-v6.9.1-win-x86"

View File

@@ -6,16 +6,6 @@ function Get-UmbracoVersion
{
$uenv = Get-UmbracoBuildEnv
try
{
[Reflection.Assembly]::LoadFile($uenv.Semver) > $null
}
catch
{
Write-Error -Exception $_.Exception -Message "Failed to load $uenv.Semver"
break
}
# parse SolutionInfo and retrieve the version string
$filepath = "$($uenv.SolutionRoot)\src\SolutionInfo.cs"
$text = [System.IO.File]::ReadAllText($filepath)

View File

@@ -20,6 +20,7 @@
. "$PSScriptRoot\Get-UmbracoBuildEnv.ps1"
. "$PSScriptRoot\Set-UmbracoVersion.ps1"
. "$PSScriptRoot\Get-UmbracoVersion.ps1"
. "$PSScriptRoot\Verify-NuGet.ps1"
. "$PSScriptRoot\Build-UmbracoDocs.ps1"
@@ -534,6 +535,7 @@ function Build-Umbraco
# not running tests...
Prepare-Packages $uenv
Package-Zip $uenv
Verify-NuGet $uenv
Prepare-NuGet $uenv
Package-NuGet $uenv $version
}
@@ -551,5 +553,6 @@ Export-ModuleMember -function Set-UmbracoVersion
Export-ModuleMember -function Get-UmbracoVersion
Export-ModuleMember -function Build-Umbraco
Export-ModuleMember -function Build-UmbracoDocs
Export-ModuleMember -function Verify-NuGet
#eof

View File

@@ -0,0 +1,434 @@
#
# Verify-NuGet
#
function Format-Dependency
{
param ( $d )
$m = $d.Id + " "
if ($d.MinInclude) { $m = $m + "[" }
else { $m = $m + "(" }
$m = $m + $d.MinVersion
if ($d.MaxVersion -ne $d.MinVersion) { $m = $m + "," + $d.MaxVersion }
if ($d.MaxInclude) { $m = $m + "]" }
else { $m = $m + ")" }
return $m
}
function Write-NuSpec
{
param ( $name, $deps )
Write-Host ""
Write-Host "$name NuSpec dependencies:"
foreach ($d in $deps)
{
$m = Format-Dependency $d
Write-Host " $m"
}
}
function Write-Package
{
param ( $name, $pkgs )
Write-Host ""
Write-Host "$name packages:"
foreach ($p in $pkgs)
{
Write-Host " $($p.Id) $($p.Version)"
}
}
function Verify-NuGet
{
param (
$uenv # an Umbraco build environment (see Get-UmbracoBuildEnv)
)
if ($uenv -eq $null)
{
$uenv = Get-UmbracoBuildEnv
}
$source = @"
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using Semver;
namespace Umbraco.Build
{
public class NuGet
{
public static Dependency[] GetNuSpecDependencies(string filename)
{
NuSpec nuspec;
var serializer = new XmlSerializer(typeof(NuSpec));
using (var reader = new StreamReader(filename))
{
nuspec = (NuSpec) serializer.Deserialize(reader);
}
var nudeps = nuspec.Metadata.Dependencies;
var deps = new List<Dependency>();
foreach (var nudep in nudeps)
{
var dep = new Dependency();
dep.Id = nudep.Id;
var parts = nudep.Version.Split(',');
if (parts.Length == 1)
{
dep.MinInclude = parts[0].StartsWith("[");
dep.MaxInclude = parts[0].EndsWith("]");
SemVersion version;
if (!SemVersion.TryParse(parts[0].Substring(1, parts[0].Length-2).Trim(), out version)) continue;
dep.MinVersion = dep.MaxVersion = version; //parts[0].Substring(1, parts[0].Length-2).Trim();
}
else
{
SemVersion version;
if (!SemVersion.TryParse(parts[0].Substring(1).Trim(), out version)) continue;
dep.MinVersion = version; //parts[0].Substring(1).Trim();
if (!SemVersion.TryParse(parts[1].Substring(0, parts[1].Length-1).Trim(), out version)) continue;
dep.MaxVersion = version; //parts[1].Substring(0, parts[1].Length-1).Trim();
dep.MinInclude = parts[0].StartsWith("[");
dep.MaxInclude = parts[1].EndsWith("]");
}
deps.Add(dep);
}
return deps.ToArray();
}
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(/*this*/ IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> knownKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (knownKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
public static Package[] GetProjectsPackages(string src, string[] projects)
{
var l = new List<Package>();
foreach (var project in projects)
{
var path = Path.Combine(src, project);
var packageConfig = Path.Combine(path, "packages.config");
if (File.Exists(packageConfig))
ReadPackagesConfig(packageConfig, l);
var csprojs = Directory.GetFiles(path, "*.csproj");
foreach (var csproj in csprojs)
{
ReadCsProj(csproj, l);
}
}
IEnumerable<Package> p = l.OrderBy(x => x.Id);
p = DistinctBy(p, x => x.Id + ":::" + x.Version);
return p.ToArray();
}
public static object[] GetPackageErrors(Package[] pkgs)
{
return pkgs
.GroupBy(x => x.Id)
.Where(x => x.Count() > 1)
.ToArray();
}
public static object[] GetNuSpecErrors(Package[] pkgs, Dependency[] deps)
{
var d = pkgs.ToDictionary(x => x.Id, x => x.Version);
return deps
.Select(x =>
{
SemVersion v;
if (!d.TryGetValue(x.Id, out v)) return null;
var ok = true;
/*
if (x.MinInclude)
{
if (v < x.MinVersion) ok = false;
}
else
{
if (v <= x.MinVersion) ok = false;
}
if (x.MaxInclude)
{
if (v > x.MaxVersion) ok = false;
}
else
{
if (v >= x.MaxVersion) ok = false;
}
*/
if (!x.MinInclude || v != x.MinVersion) ok = false;
return ok ? null : new { Dependency = x, Version = v };
})
.Where(x => x != null)
.ToArray();
}
/*
public static Package[] GetProjectPackages(string path)
{
var l = new List<Package>();
var packageConfig = Path.Combine(path, "packages.config");
if (File.Exists(packageConfig))
ReadPackagesConfig(packageConfig, l);
var csprojs = Directory.GetFiles(path, "*.csproj");
foreach (var csproj in csprojs)
{
ReadCsProj(csproj, l);
}
return l.ToArray();
}
*/
public static string GetDirectoryName(string filename)
{
return Path.GetFileName(Path.GetDirectoryName(filename));
}
public static void ReadPackagesConfig(string filename, List<Package> packages)
{
//Console.WriteLine("read " + filename);
PackagesConfigPackages pkgs;
var serializer = new XmlSerializer(typeof(PackagesConfigPackages));
using (var reader = new StreamReader(filename))
{
pkgs = (PackagesConfigPackages) serializer.Deserialize(reader);
}
foreach (var p in pkgs.Packages)
{
SemVersion version;
if (!SemVersion.TryParse(p.Version, out version)) continue;
packages.Add(new Package { Id = p.Id, Version = version, Project = GetDirectoryName(filename) });
}
}
public static void ReadCsProj(string filename, List<Package> packages)
{
//Console.WriteLine("read " + filename);
// if xmlns then it's not a VS2017 with PackageReference
var text = File.ReadAllLines(filename);
var line = text.FirstOrDefault(x => x.Contains("<Project"));
if (line == null) return;
if (line.Contains("xmlns")) return;
CsProjProject proj;
var serializer = new XmlSerializer(typeof(CsProjProject));
using (var reader = new StreamReader(filename))
{
proj = (CsProjProject) serializer.Deserialize(reader);
}
foreach (var p in proj.ItemGroups.Where(x => x.Packages != null).SelectMany(x => x.Packages))
{
var sversion = p.VersionE ?? p.VersionA;
SemVersion version;
if (!SemVersion.TryParse(sversion, out version)) continue;
packages.Add(new Package { Id = p.Id, Version = version, Project = GetDirectoryName(filename) });
}
}
public class Dependency
{
public string Id { get; set; }
public SemVersion MinVersion { get; set; }
public SemVersion MaxVersion { get; set; }
public bool MinInclude { get; set; }
public bool MaxInclude { get; set; }
}
public class Package
{
public string Id { get; set; }
public SemVersion Version { get; set; }
public string Project { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd")]
[XmlRoot(Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", IsNullable = false, ElementName = "package")]
public class NuSpec
{
[XmlElement("metadata")]
public NuSpecMetadata Metadata { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "metadata")]
public class NuSpecMetadata
{
[XmlArray("dependencies")]
[XmlArrayItem("dependency", IsNullable = false)]
public NuSpecDependency[] Dependencies { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "dependencies")]
public class NuSpecDependency
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false, ElementName = "packages")]
public class PackagesConfigPackages
{
[XmlElement("package")]
public PackagesConfigPackage[] Packages { get; set; }
}
[XmlType(AnonymousType = true, TypeName = "package")]
public class PackagesConfigPackage
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false, ElementName = "Project")]
public class CsProjProject
{
[XmlElement("ItemGroup")]
public CsProjItemGroup[] ItemGroups { get; set; }
}
[XmlType(AnonymousType = true, TypeName = "ItemGroup")]
public class CsProjItemGroup
{
[XmlElement("PackageReference")]
public CsProjPackageReference[] Packages { get; set; }
}
[XmlType(AnonymousType = true, TypeName = "PackageReference")]
public class CsProjPackageReference
{
[XmlAttribute(AttributeName = "Include")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "Version")]
public string VersionA { get; set; }
[XmlElement("Version")]
public string VersionE { get; set;}
}
}
}
"@
Write-Host "Verify NuGet consistency"
$assem = (
"System.Xml",
"System.Core", # "System.Collections.Generic"
"System.Linq",
"System.Xml.Serialization",
"System.IO",
"System.Globalization",
$uenv.Semver
)
add-type -referencedAssemblies $assem -typeDefinition $source -language CSharp
if (-not $?) { break }
$nuspecs = (
"UmbracoCms",
"UmbracoCms.Core",
"UmbracoCms.Compat7"
)
$projects = (
"Umbraco.Core",
"Umbraco.Web",
"Umbraco.Web.UI",
"UmbracoExamine",
"Umbraco.Compat7"#,
#"Umbraco.Tests",
#"Umbraco.Tests.Benchmarks"
)
$src = "$($uenv.SolutionRoot)\src"
$pkgs = [Umbraco.Build.NuGet]::GetProjectsPackages($src, $projects)
if (-not $?) { break }
#Write-Package "All" $pkgs
$errs = [Umbraco.Build.NuGet]::GetPackageErrors($pkgs)
if (-not $?) { break }
if ($errs.Length -gt 0)
{
Write-Host ""
}
foreach ($err in $errs)
{
Write-Host $err.Key
foreach ($e in $err)
{
Write-Host " $($e.Version) required by $($e.Project)"
}
}
if ($errs.Length -gt 0)
{
Write-Error "Found non-consolidated package dependencies"
break
}
$nuerr = $false
$nupath = "$($uenv.SolutionRoot)\build\NuSpecs"
foreach ($nuspec in $nuspecs)
{
$deps = [Umbraco.Build.NuGet]::GetNuSpecDependencies("$nupath\$nuspec.nuspec")
if (-not $?) { break }
#Write-NuSpec $nuspec $deps
$errs = [Umbraco.Build.NuGet]::GetNuSpecErrors($pkgs, $deps)
if (-not $?) { break }
if ($errs.Length -gt 0)
{
Write-Host ""
Write-Host "$nuspec requires:"
$nuerr = $true
}
foreach ($err in $errs)
{
$m = Format-Dependency $err.Dependency
Write-Host " $m but projects require $($err.Version)"
}
}
if ($nuerr)
{
Write-Error "Found inconsistent NuGet dependencies"
break
}
}

View File

@@ -102,6 +102,12 @@ Builds umbraco documentation. Temporary files are generated in `build.tmp` while
Some log files, such as MsBuild logs, are produced in `build.tmp` too. The `build` directory should remain clean during a build.
## Verify-NuGet
Verifies that projects all require the same version of their dependencies, and that NuSpec files require versions that are consistent with projects. Example:
Verify-NuGet
# VSTS
Continuous integration, nightly builds and release builds run on VSTS.

View File

@@ -1,236 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using NUnit.Framework;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Dependencies
{
[TestFixture]
public class NuGet
{
// note
// these tests assume that the test suite runs from the project's ~/bin directory
// instead, we want to be able to pass the required paths as parameters
// NUnit 3.x supports TestContext.TestParameters, alas we are still running 2.x
//
// so instead
//
// furthermore, all these tests should be parts of the build and fail the build
// in case the NuGet things are not consisted. Also, v8 uses <PackageReference>
// wherever possible so we will also have to deal with it eventually.
[Test]
public void NuGet_Package_Versions_Are_The_Same_In_All_Package_Config_Files()
{
var packagesAndVersions = GetNuGetPackagesInSolution();
Assert.IsTrue(packagesAndVersions.Any());
var failTest = false;
foreach (var package in packagesAndVersions.OrderBy(x => x.ConfigFilePath))
{
var matchingPackages = packagesAndVersions.Where(x => string.Equals(x.PackageName, package.PackageName, StringComparison.InvariantCultureIgnoreCase));
foreach (var matchingPackage in matchingPackages)
{
if (package.PackageVersion == matchingPackage.PackageVersion)
continue;
Debug.WriteLine("Package '{0}' with version {1} in {2} doesn't match with version {3} in {4}",
package.PackageName, package.PackageVersion, package.ConfigFilePath,
matchingPackage.PackageVersion, matchingPackage.ConfigFilePath);
failTest = true;
}
}
Assert.IsFalse(failTest);
}
[Test]
public void NuSpec_File_Matches_Installed_Dependencies()
{
var nuspecsDirectory = NuSpecsDirectory;
Assert.IsNotNull(nuspecsDirectory);
var packagesAndVersions = GetNuGetPackagesInSolution();
Assert.IsTrue(packagesAndVersions.Any());
Console.WriteLine("NuSpecs: " + nuspecsDirectory.FullName);
var failTest = false;
var nuspecFiles = nuspecsDirectory.GetFiles().Where(x => x.Extension == ".nuspec").Select(x => x.FullName);
foreach (var filename in nuspecFiles)
{
var serializer = new XmlSerializer(typeof(NuSpec));
using (var reader = new StreamReader(filename))
{
var nuspec = (NuSpec)serializer.Deserialize(reader);
var dependencies = nuspec.MetaData.dependencies;
//UmbracoCms.Core has version "[$version$]" - ignore
foreach (var dependency in dependencies.Where(x => x.Id != "UmbracoCms.Core"))
{
var dependencyVersionRange = dependency.Version.Replace("[", string.Empty).Replace("(", string.Empty).Split(',');
var dependencyMinimumVersion = dependencyVersionRange.First().Trim();
var matchingPackages = packagesAndVersions.Where(x => string.Equals(x.PackageName, dependency.Id,
StringComparison.InvariantCultureIgnoreCase)).ToList();
if (matchingPackages.Any() == false)
continue;
// NuGet_Package_Versions_Are_The_Same_In_All_Package_Config_Files test
// guarantees that all packages have one version, solutionwide, so it's okay to take First() here
if (dependencyMinimumVersion != matchingPackages.First().PackageVersion)
{
Console.WriteLine("NuSpec dependency '{0}' with minimum version {1} doesn't match with version {2} in the solution",
dependency.Id, dependencyMinimumVersion, matchingPackages.First().PackageVersion);
failTest = true;
}
}
}
}
Assert.IsFalse(failTest);
}
private List<PackageVersionMatcher> GetNuGetPackagesInSolution()
{
var packagesAndVersions = new List<PackageVersionMatcher>();
var sourceDirectory = SourceDirectory;
if (sourceDirectory == null) return packagesAndVersions;
var packageConfigFiles = new List<FileInfo>();
var packageDirectories =
sourceDirectory.GetDirectories().Where(x =>
x.Name.StartsWith("Umbraco.Tests") == false &&
x.Name.StartsWith("Umbraco.MSBuild.Tasks") == false).ToArray();
foreach (var directory in packageDirectories)
{
var packagesFiles = directory.EnumerateFiles("packages.config");
packageConfigFiles.AddRange(packagesFiles);
}
foreach (var file in packageConfigFiles)
{
//read all and de-duplicate packages
var serializer = new XmlSerializer(typeof(PackageConfigEntries));
using (var reader = new StreamReader(file.FullName))
{
var packages = (PackageConfigEntries)serializer.Deserialize(reader);
packagesAndVersions.AddRange(packages.Package.Select(package =>
new PackageVersionMatcher
{
PackageName = package.Id,
PackageVersion = package.Version,
ConfigFilePath = file.FullName
}));
}
}
return packagesAndVersions;
}
private DirectoryInfo SourceDirectory
{
get
{
// UMBRACO_TMP is set by VSTS and points to build.tmp
var tmp = Environment.GetEnvironmentVariable("UMBRACO_TMP");
if (tmp == null) return SolutionDirectory;
var path = Path.Combine(tmp, "..\\src");
path = Path.GetFullPath(path); // sanitize
var dir = new DirectoryInfo(path);
return dir.Exists ? dir : null;
}
}
private DirectoryInfo NuSpecsDirectory
{
get
{
// UMBRACO_TMP is set by VSTS and points to build.tmp
var tmp = Environment.GetEnvironmentVariable("UMBRACO_TMP");
if (tmp == null)
{
var solutionDirectory = SolutionDirectory;
if (solutionDirectory == null) return null;
return new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "..\\build\\NuSpecs"));
}
var path = Path.Combine(tmp, "..\\build\\NuSpecs");
path = Path.GetFullPath(path); // sanitize
var dir = new DirectoryInfo(path);
return dir.Exists ? dir : null;
}
}
private DirectoryInfo SolutionDirectory
{
get
{
var testsDirectory = new FileInfo(TestHelper.MapPathForTest("~/"));
if (testsDirectory.Directory == null)
return null;
if (testsDirectory.Directory.Parent == null || testsDirectory.Directory.Parent.Parent == null)
return null;
var solutionDirectory = testsDirectory.Directory.Parent.Parent.Parent;
return solutionDirectory;
}
}
}
public class PackageVersionMatcher
{
public string ConfigFilePath { get; set; }
public string PackageName { get; set; }
public string PackageVersion { get; set; }
}
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false, ElementName = "packages")]
public class PackageConfigEntries
{
[XmlElement("package")]
public PackagesPackage[] Package { get; set; }
}
[XmlType(AnonymousType = true, TypeName = "package")]
public class PackagesPackage
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd")]
[XmlRoot(Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", IsNullable = false, ElementName = "package")]
public class NuSpec
{
[XmlElement("metadata")]
public Metadata MetaData { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "metadata")]
public class Metadata
{
[XmlArrayItem("dependency", IsNullable = false)]
//TODO: breaks when renamed to capital D
public Dependency[] dependencies { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "dependencies")]
public class Dependency
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
}

View File

@@ -203,7 +203,6 @@
<Compile Include="CoreThings\CallContextTests.cs" />
<Compile Include="Components\ComponentTests.cs" />
<Compile Include="CoreXml\RenamedRootNavigatorTests.cs" />
<Compile Include="Dependencies\NuGet.cs" />
<Compile Include="IO\ShadowFileSystemTests.cs" />
<Compile Include="Issues\U9560.cs" />
<Compile Include="Integration\ContentEventsTests.cs" />