From 4d5e1f215afb5c8372d5c3d82ce8ef4a4a1fda60 Mon Sep 17 00:00:00 2001 From: Davis Goodin Date: Tue, 30 Jan 2018 11:28:39 -0600 Subject: [PATCH 1/2] Allow custom attributes in build manifest 'BuildIdentity' (#1882) * Allow custom attributes in BuildIdentity Also improve the required attribute mechanism to avoid redundant writes and verify dictionary contents during ToXml. (cherry picked from commit cb76b2fd323dbdc7fa46991e565669fc7fec08aa) --- .../FetchOrchestratedBuildManifestInfo.cs | 19 +++- .../WriteOrchestratedBuildManifestToFile.cs | 14 ++- .../Microsoft.DotNet.Build.Tasks.Feed.targets | 7 ++ .../PushToBlobFeed.cs | 30 +++-- .../BuildManifest/BuildManifestClientTests.cs | 10 +- .../BuildManifest/ManifestModelTests.cs | 17 ++- .../BuildManifest/Model/BlobArtifactModel.cs | 14 ++- .../BuildManifest/Model/BuildIdentity.cs | 105 +++++++++++------- .../BuildManifest/Model/BuildModel.cs | 2 +- .../BuildManifest/Model/EndpointModel.cs | 7 +- .../Model/OrchestratedBuildModel.cs | 4 +- .../Model/PackageArtifactModel.cs | 17 +-- .../Model/XElementParsingExtensions.cs | 17 ++- .../Util/EnumerableExtensions.cs | 9 ++ 14 files changed, 186 insertions(+), 86 deletions(-) diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/BuildManifest/FetchOrchestratedBuildManifestInfo.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/BuildManifest/FetchOrchestratedBuildManifestInfo.cs index 3d11c485ff..99f099e383 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/BuildManifest/FetchOrchestratedBuildManifestInfo.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/BuildManifest/FetchOrchestratedBuildManifestInfo.cs @@ -40,6 +40,9 @@ public class FetchOrchestratedBuildManifestInfo : Task [Output] public ITaskItem[] OrchestratedBlobFeedArtifacts { get; set; } + + [Output] + public ITaskItem[] OrchestratedBuilds { get; set; } public override bool Execute() { @@ -58,6 +61,9 @@ public override bool Execute() VersionsRepoPath) .Result; + OrchestratedBuildId = manifest.Identity.BuildId; + OrchestratedIdentity = manifest.Identity.ToString(); + EndpointModel[] orchestratedFeeds = manifest.Endpoints .Where(e => e.IsOrchestratedBlobFeed) .ToArray(); @@ -74,10 +80,12 @@ public override bool Execute() IEnumerable packageItems = feed.Artifacts.Packages.Select(CreateItem); IEnumerable blobItems = feed.Artifacts.Blobs.Select(CreateItem); - OrchestratedBuildId = manifest.Identity.BuildId; - OrchestratedIdentity = manifest.Identity.ToString(); OrchestratedBlobFeed = new[] { new TaskItem("Endpoint", feed.Attributes) }; OrchestratedBlobFeedArtifacts = packageItems.Concat(blobItems).ToArray(); + + IEnumerable buildItems = manifest.Builds.Select(CreateItem); + + OrchestratedBuilds = buildItems.ToArray(); } return !Log.HasLoggedErrors; @@ -93,6 +101,13 @@ private ITaskItem CreateItem(PackageArtifactModel model) return new TaskItem("Package", ArtifactMetadata(model.ToXml(), model.Attributes)); } + private ITaskItem CreateItem(BuildIdentity model) + { + return new TaskItem( + model.Name, + ArtifactMetadata(model.ToXmlBuildElement(), model.Attributes)); + } + private Dictionary ArtifactMetadata( XElement artifactXml, IDictionary attributes) diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/BuildManifest/WriteOrchestratedBuildManifestToFile.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/BuildManifest/WriteOrchestratedBuildManifestToFile.cs index d62ab9b375..5cdca452b0 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/BuildManifest/WriteOrchestratedBuildManifestToFile.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/BuildManifest/WriteOrchestratedBuildManifestToFile.cs @@ -42,11 +42,15 @@ public override bool Execute() ManifestCommit = null; } - var orchestratedBuild = new OrchestratedBuildModel(new BuildIdentity( - ManifestName, - ManifestBuildId, - ManifestBranch, - ManifestCommit)) + var identity = new BuildIdentity + { + Name = ManifestName, + BuildId = ManifestBuildId, + Branch = ManifestBranch, + Commit = ManifestCommit + }; + + var orchestratedBuild = new OrchestratedBuildModel(identity) { Endpoints = new List { diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/PackageFiles/Microsoft.DotNet.Build.Tasks.Feed.targets b/src/Microsoft.DotNet.Build.Tasks.Feed/PackageFiles/Microsoft.DotNet.Build.Tasks.Feed.targets index 7e734e6e22..3efe7fbfa4 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/PackageFiles/Microsoft.DotNet.Build.Tasks.Feed.targets +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/PackageFiles/Microsoft.DotNet.Build.Tasks.Feed.targets @@ -85,6 +85,7 @@ ManifestBuildId="$(ManifestBuildId)" ManifestBranch="$(ManifestBranch)" ManifestCommit="$(ManifestCommit)" + ManifestBuildData="$(ManifestBuildData)" SkipCreateManifest="$(SkipCreateManifest)" /> @@ -115,6 +116,7 @@ ManifestBuildId="$(ManifestBuildId)" ManifestBranch="$(ManifestBranch)" ManifestCommit="$(ManifestCommit)" + ManifestBuildData="$(ManifestBuildData)" SkipCreateManifest="$(SkipCreateManifest)" /> @@ -230,6 +232,10 @@ %(Identity): 'Package' or 'Blob', matching manifest element name. %(Xml): The raw XML string representing the artifact in the manifest. %(...): Metadata is created for each attribute on the element. + @(OrchestratedBuilds): An item for each Build in the orchestrated build manifest. + %(Identity): The name of the build. + %(Xml): The raw XML string representing the artifact in the manifest. + %(...): Metadata is created for each attribute on the element. --> @@ -251,6 +257,7 @@ + diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/PushToBlobFeed.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/PushToBlobFeed.cs index 2fb491a75d..7ecdbb1c19 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/PushToBlobFeed.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/PushToBlobFeed.cs @@ -47,6 +47,7 @@ public class PushToBlobFeed : MSBuild.Task public string ManifestBuildId { get; set; } = "no build id provided"; public string ManifestBranch { get; set; } public string ManifestCommit { get; set; } + public string ManifestBuildData { get; set; } /// /// When publishing build outputs to an orchestrated blob feed, do not change this property. @@ -158,11 +159,14 @@ private async Task PushBuildManifestAsync( else { buildModel = new BuildModel( - new BuildIdentity( - ManifestName, - ManifestBuildId, - ManifestBranch, - ManifestCommit)); + new BuildIdentity + { + Attributes = ParseManifestMetadataString(ManifestBuildData), + Name = ManifestName, + BuildId = ManifestBuildId, + Branch = ManifestBranch, + Commit = ManifestCommit + }); } buildModel.Artifacts.Blobs.AddRange(blobArtifacts); @@ -257,9 +261,17 @@ private static BlobArtifactModel CreateBlobArtifactModel(ITaskItem item) private static Dictionary ParseCustomAttributes(ITaskItem item) { - Dictionary customAttributes = item - .GetMetadata("ManifestArtifactData") - ?.Split(ManifestDataPairSeparators, StringSplitOptions.RemoveEmptyEntries) + return ParseManifestMetadataString(item.GetMetadata("ManifestArtifactData")); + } + + private static Dictionary ParseManifestMetadataString(string data) + { + if (string.IsNullOrEmpty(data)) + { + return new Dictionary(); + } + + return data.Split(ManifestDataPairSeparators, StringSplitOptions.RemoveEmptyEntries) .Select(pair => { int keyValueSeparatorIndex = pair.IndexOf('='); @@ -275,8 +287,6 @@ private static Dictionary ParseCustomAttributes(ITaskItem item) }) .Where(pair => pair != null) .ToDictionary(pair => pair.Key, pair => pair.Value); - - return customAttributes ?? new Dictionary(); } } } diff --git a/src/Microsoft.DotNet.VersionTools.Tests/BuildManifest/BuildManifestClientTests.cs b/src/Microsoft.DotNet.VersionTools.Tests/BuildManifest/BuildManifestClientTests.cs index f467814d80..7be6b2825a 100644 --- a/src/Microsoft.DotNet.VersionTools.Tests/BuildManifest/BuildManifestClientTests.cs +++ b/src/Microsoft.DotNet.VersionTools.Tests/BuildManifest/BuildManifestClientTests.cs @@ -40,7 +40,7 @@ public async Task TestPushNewBuildAsync() var mockGitHub = new Mock(MockBehavior.Strict); var client = new BuildManifestClient(mockGitHub.Object); - var build = new OrchestratedBuildModel(new BuildIdentity("orch", "123")); + var build = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "123"}); var proj = new GitHubProject("versions", "dotnet"); string @ref = "heads/master"; string basePath = "build-info/dotnet/product/cli/master"; @@ -105,7 +105,7 @@ public async Task TestPushChangeSemaphoreAsync() string message = "Test change manifest commit"; string addSemaphorePath = "add-identity.semaphore"; - var fakeExistingBuild = new OrchestratedBuildModel(new BuildIdentity("orch", "123")); + var fakeExistingBuild = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "123"}); string fakeExistingBuildString = fakeExistingBuild.ToXml().ToString(); string fakeCommitHash = "fakeCommitHash"; string fakeTreeHash = "fakeTreeHash"; @@ -173,8 +173,8 @@ public async Task TestPushConflictingChangeAsync() string message = "Test change manifest commit"; string addSemaphorePath = "add-identity.semaphore"; - var fakeExistingBuild = new OrchestratedBuildModel(new BuildIdentity("orch", "123")); - var fakeNewExistingBuild = new OrchestratedBuildModel(new BuildIdentity("orch", "456")); + var fakeExistingBuild = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "123" }); + var fakeNewExistingBuild = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "456" }); string fakeCommitHash = "fakeCommitHash"; mockGitHub @@ -208,7 +208,7 @@ public async Task TestPushConflictAsync() var mockGitHub = new Mock(MockBehavior.Strict); var client = new BuildManifestClient(mockGitHub.Object); - var build = new OrchestratedBuildModel(new BuildIdentity("orch", "123")); + var build = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "123" }); var proj = new GitHubProject("versions", "dotnet"); string @ref = "heads/master"; string basePath = "build-info/dotnet/product/cli/master"; diff --git a/src/Microsoft.DotNet.VersionTools.Tests/BuildManifest/ManifestModelTests.cs b/src/Microsoft.DotNet.VersionTools.Tests/BuildManifest/ManifestModelTests.cs index d5b5fad593..e512434846 100644 --- a/src/Microsoft.DotNet.VersionTools.Tests/BuildManifest/ManifestModelTests.cs +++ b/src/Microsoft.DotNet.VersionTools.Tests/BuildManifest/ManifestModelTests.cs @@ -43,6 +43,19 @@ public void TestExampleOrchestratedBuildManifestRoundtrip() "Model failed to output the parsed XML."); } + [Fact] + public void TestExampleCustomBuildIdentityRoundtrip() + { + XElement xml = XElement.Parse( + @""); + var model = BuildModel.Parse(xml); + XElement modelXml = model.ToXml(); + + Assert.True( + XNode.DeepEquals(xml, modelXml), + "Model failed to output the parsed XML."); + } + [Fact] public void TestPackageOnlyBuildManifest() { @@ -56,7 +69,7 @@ public void TestPackageOnlyBuildManifest() [Fact] public void TestMergeBuildManifests() { - var orchestratedModel = new OrchestratedBuildModel(new BuildIdentity("Orchestrated", "123")) + var orchestratedModel = new OrchestratedBuildModel(new BuildIdentity { Name = "Orchestrated", BuildId = "123" }) { Endpoints = new List { @@ -87,7 +100,7 @@ public void TestMergeBuildManifests() private BuildModel CreatePackageOnlyBuildManifestModel() { - return new BuildModel(new BuildIdentity("SimpleBuildManifest", "123")) + return new BuildModel(new BuildIdentity { Name = "SimpleBuildManifest", BuildId = "123" }) { Artifacts = new ArtifactSet { diff --git a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BlobArtifactModel.cs b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BlobArtifactModel.cs index 871595f1d1..95e952003f 100644 --- a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BlobArtifactModel.cs +++ b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BlobArtifactModel.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.DotNet.VersionTools.Util; using System.Collections.Generic; using System.Xml.Linq; @@ -14,11 +15,11 @@ public class BlobArtifactModel nameof(Id) }; - public Dictionary Attributes { get; set; } = new Dictionary(); + public IDictionary Attributes { get; set; } = new Dictionary(); public string Id { - get { return Attributes[nameof(Id)]; } + get { return Attributes.GetOrDefault(nameof(Id)); } set { Attributes[nameof(Id)] = value; } } @@ -26,12 +27,15 @@ public string Id public XElement ToXml() => new XElement( "Blob", - Attributes.CreateXmlAttributes(AttributeOrder)); + Attributes + .ThrowIfMissingAttributes(AttributeOrder) + .CreateXmlAttributes(AttributeOrder)); public static BlobArtifactModel Parse(XElement xml) => new BlobArtifactModel { - Id = xml.GetRequiredAttribute(nameof(Id)), - Attributes = xml.CreateAttributeDictionary() + Attributes = xml + .CreateAttributeDictionary() + .ThrowIfMissingAttributes(AttributeOrder) }; } } diff --git a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BuildIdentity.cs b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BuildIdentity.cs index a9d70e7bfc..463b96c834 100644 --- a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BuildIdentity.cs +++ b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BuildIdentity.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +using Microsoft.DotNet.VersionTools.Util; using System.Collections.Generic; using System.Xml.Linq; @@ -10,34 +10,59 @@ namespace Microsoft.DotNet.VersionTools.BuildManifest.Model { public class BuildIdentity { - public BuildIdentity( - string name, - string buildId, - string branch = null, - string commit = null) + private static readonly string[] AttributeOrder = { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException("Expected a non-empty string.", nameof(name)); - } - Name = name; - if (string.IsNullOrEmpty(buildId)) - { - throw new ArgumentException("Expected a non-empty string.", nameof(buildId)); - } - BuildId = buildId; - Branch = branch; - Commit = commit; + nameof(Name), + nameof(BuildId), + nameof(ProductVersion), + nameof(Branch), + nameof(Commit) + }; + + private static readonly string[] RequiredAttributes = + { + nameof(Name) + }; + + public IDictionary Attributes { get; set; } = new Dictionary(); + + public string Name + { + get { return Attributes.GetOrDefault(nameof(Name)); } + set { Attributes[nameof(Name)] = value; } + } + + public string BuildId + { + get { return Attributes.GetOrDefault(nameof(BuildId)); } + set { Attributes[nameof(BuildId)] = value; } + } + + public string ProductVersion + { + get { return Attributes.GetOrDefault(nameof(ProductVersion)); } + set { Attributes[nameof(ProductVersion)] = value; } + } + + public string Branch + { + get { return Attributes.GetOrDefault(nameof(Branch)); } + set { Attributes[nameof(Branch)] = value; } } - public string Name { get; } - public string BuildId { get; } - public string Branch { get; } - public string Commit { get; } + public string Commit + { + get { return Attributes.GetOrDefault(nameof(Commit)); } + set { Attributes[nameof(Commit)] = value; } + } public override string ToString() { - string s = $"{Name} '{BuildId}'"; + string s = Name; + if (!string.IsNullOrEmpty(ProductVersion)) + { + s += $" {ProductVersion}"; + } if (!string.IsNullOrEmpty(Branch)) { s += $" on '{Branch}'"; @@ -46,30 +71,24 @@ public override string ToString() { s += $" ({Commit})"; } - return s; - } - - public IEnumerable ToXml() - { - yield return new XAttribute(nameof(Name), Name); - yield return new XAttribute(nameof(BuildId), BuildId); - if (!string.IsNullOrEmpty(Branch)) - { - yield return new XAttribute(nameof(Branch), Branch); - } - if (!string.IsNullOrEmpty(Commit)) + if (!string.IsNullOrEmpty(BuildId)) { - yield return new XAttribute(nameof(Commit), Commit); + s += $" build {BuildId}"; } + return s; } - public static BuildIdentity Parse(XElement xml) + public IEnumerable ToXmlAttributes() => Attributes + .ThrowIfMissingAttributes(RequiredAttributes) + .CreateXmlAttributes(AttributeOrder); + + public XElement ToXmlBuildElement() => new XElement("Build", ToXmlAttributes()); + + public static BuildIdentity Parse(XElement xml) => new BuildIdentity { - return new BuildIdentity( - xml.GetRequiredAttribute(nameof(Name)), - xml.GetRequiredAttribute(nameof(BuildId)), - xml.Attribute(nameof(Branch))?.Value, - xml.Attribute(nameof(Commit))?.Value); - } + Attributes = xml + .CreateAttributeDictionary() + .ThrowIfMissingAttributes(RequiredAttributes) + }; } } diff --git a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BuildModel.cs b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BuildModel.cs index dbcf504a36..257008b8fe 100644 --- a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BuildModel.cs +++ b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/BuildModel.cs @@ -26,7 +26,7 @@ public BuildModel(BuildIdentity identity) public XElement ToXml() => new XElement( "Build", - Identity.ToXml(), + Identity.ToXmlAttributes(), Artifacts.ToXml()); public static BuildModel Parse(XElement xml) => new BuildModel(BuildIdentity.Parse(xml)) diff --git a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/EndpointModel.cs b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/EndpointModel.cs index aac54c9a27..635febddbf 100644 --- a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/EndpointModel.cs +++ b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/EndpointModel.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.DotNet.VersionTools.Util; using System.Collections.Generic; using System.Xml.Linq; @@ -24,19 +25,19 @@ public class EndpointModel public string Id { - get { return Attributes[nameof(Id)]; } + get { return Attributes.GetOrDefault(nameof(Id)); } set { Attributes[nameof(Id)] = value; } } public string Type { - get { return Attributes[nameof(Type)]; } + get { return Attributes.GetOrDefault(nameof(Type)); } set { Attributes[nameof(Type)] = value; } } public string Url { - get { return Attributes[nameof(Url)]; } + get { return Attributes.GetOrDefault(nameof(Url)); } set { Attributes[nameof(Url)] = value; } } diff --git a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/OrchestratedBuildModel.cs b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/OrchestratedBuildModel.cs index 92cf6c5638..a3734dff77 100644 --- a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/OrchestratedBuildModel.cs +++ b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/OrchestratedBuildModel.cs @@ -42,9 +42,9 @@ public void AddParticipantBuild(BuildModel build) public XElement ToXml() => new XElement( "OrchestratedBuild", - Identity.ToXml(), + Identity.ToXmlAttributes(), Endpoints.Select(x => x.ToXml()), - Builds.Select(x => new XElement("Build", x.ToXml()))); + Builds.Select(x => x.ToXmlBuildElement())); public static OrchestratedBuildModel Parse(XElement xml) => new OrchestratedBuildModel(BuildIdentity.Parse(xml)) { diff --git a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/PackageArtifactModel.cs b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/PackageArtifactModel.cs index 9aa2079093..970f07115a 100644 --- a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/PackageArtifactModel.cs +++ b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/PackageArtifactModel.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.DotNet.VersionTools.Util; using System.Collections.Generic; using System.Xml.Linq; @@ -15,17 +16,17 @@ public class PackageArtifactModel nameof(Version) }; - public Dictionary Attributes { get; set; } = new Dictionary(); + public IDictionary Attributes { get; set; } = new Dictionary(); public string Id { - get { return Attributes[nameof(Id)]; } + get { return Attributes.GetOrDefault(nameof(Id)); } set { Attributes[nameof(Id)] = value; } } public string Version { - get { return Attributes[nameof(Version)]; } + get { return Attributes.GetOrDefault(nameof(Version)); } set { Attributes[nameof(Version)] = value; } } @@ -33,13 +34,15 @@ public string Version public XElement ToXml() => new XElement( "Package", - Attributes.CreateXmlAttributes(AttributeOrder)); + Attributes + .ThrowIfMissingAttributes(AttributeOrder) + .CreateXmlAttributes(AttributeOrder)); public static PackageArtifactModel Parse(XElement xml) => new PackageArtifactModel { - Id = xml.GetRequiredAttribute(nameof(Id)), - Version = xml.GetRequiredAttribute(nameof(Version)), - Attributes = xml.CreateAttributeDictionary() + Attributes = xml + .CreateAttributeDictionary() + .ThrowIfMissingAttributes(AttributeOrder) }; } } diff --git a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/XElementParsingExtensions.cs b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/XElementParsingExtensions.cs index bf5d1ae5f8..10e65b6249 100644 --- a/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/XElementParsingExtensions.cs +++ b/src/Microsoft.DotNet.VersionTools/BuildManifest/Model/XElementParsingExtensions.cs @@ -28,14 +28,29 @@ public static Dictionary CreateAttributeDictionary(this XElement } public static XAttribute[] CreateXmlAttributes( - this Dictionary attributes, + this IDictionary attributes, string[] keySortOrder) { return attributes + .Where(pair => pair.Value != null) .OrderBy(pair => keySortOrder.TakeWhile(o => pair.Key != o).Count()) .ThenBy(pair => pair.Key, StringComparer.OrdinalIgnoreCase) .Select(pair => new XAttribute(pair.Key, pair.Value)) .ToArray(); } + + public static IDictionary ThrowIfMissingAttributes( + this IDictionary attributes, + IEnumerable requiredAttributes) + { + var missing = requiredAttributes?.Where(r => !attributes.ContainsKey(r)).ToArray(); + if (missing?.Any() == true) + { + throw new ArgumentException( + $"Required attribute(s) missing: {string.Join(", ", missing)}"); + } + + return attributes; + } } } diff --git a/src/Microsoft.DotNet.VersionTools/Util/EnumerableExtensions.cs b/src/Microsoft.DotNet.VersionTools/Util/EnumerableExtensions.cs index c71b6141e2..da43f5883c 100644 --- a/src/Microsoft.DotNet.VersionTools/Util/EnumerableExtensions.cs +++ b/src/Microsoft.DotNet.VersionTools/Util/EnumerableExtensions.cs @@ -13,5 +13,14 @@ public static IEnumerable NullAsEmpty(this IEnumerable source) { return source ?? Enumerable.Empty(); } + + public static TValue GetOrDefault( + this IDictionary attributes, + TKey key) + { + TValue value; + attributes.TryGetValue(key, out value); + return value; + } } } From 385190da3c72d59bd7ceba566b288d9768d4b197 Mon Sep 17 00:00:00 2001 From: Davis Goodin Date: Tue, 30 Jan 2018 11:34:08 -0600 Subject: [PATCH 2/2] Use project.json dep versions in VersionTools pkg (#1884) (cherry picked from commit a65f13fbb73db6cf9ea4da276db44f6eb271deeb) --- src/nuget/Microsoft.DotNet.VersionTools.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nuget/Microsoft.DotNet.VersionTools.nuspec b/src/nuget/Microsoft.DotNet.VersionTools.nuspec index e77a1ad2a8..66beda1ac3 100644 --- a/src/nuget/Microsoft.DotNet.VersionTools.nuspec +++ b/src/nuget/Microsoft.DotNet.VersionTools.nuspec @@ -18,8 +18,8 @@ - - + +