From 080e692cc1774add754e98dc71389b5c76e50172 Mon Sep 17 00:00:00 2001 From: Justin Emgarten Date: Thu, 14 Jul 2016 16:23:51 -0700 Subject: [PATCH] Allow netstandard to install contentFiles under the any tfm By default ContentModel translates 'any' to 'dotnet', this works for the lib folder but for scenarios such as contentFiles any does refer to all frameworks. This change updates contentModel to allow per pattern token replacement tables. Fixes https://github.com/NuGet/Home/issues/3118 --- .../NuGet.Client/ManagedCodeConventions.cs | 172 +++++++++++------- .../ContentPropertyDefinition.cs | 109 +++++------ .../ContentQueryDefinition.cs | 19 +- .../Infrastructure/Parser.cs | 10 +- .../NuGet.ContentModel/PatternTable.cs | 71 ++++++++ .../NuGet.ContentModel/PatternTableEntry.cs | 42 +++++ .../SelectionCriteriaBuilder.cs | 2 +- .../ContentModelContentFilesTests.cs | 50 +++++ .../NuGet.Client.Test/ContentModelTests.cs | 156 ++++++++++++++++ .../NuGet.Client.Test/PatternTableTests.cs | 66 +++++++ .../NuGet.Commands.Test/ContentFilesTests.cs | 70 +++++++ 11 files changed, 632 insertions(+), 135 deletions(-) create mode 100644 src/NuGet.Core/NuGet.ContentModel/PatternTable.cs create mode 100644 src/NuGet.Core/NuGet.ContentModel/PatternTableEntry.cs create mode 100644 test/NuGet.Core.Tests/NuGet.Client.Test/ContentModelTests.cs create mode 100644 test/NuGet.Core.Tests/NuGet.Client.Test/PatternTableTests.cs diff --git a/src/NuGet.Core/NuGet.Client/ManagedCodeConventions.cs b/src/NuGet.Core/NuGet.Client/ManagedCodeConventions.cs index b0811ee5c4c..43b08461b43 100644 --- a/src/NuGet.Core/NuGet.Client/ManagedCodeConventions.cs +++ b/src/NuGet.Core/NuGet.Client/ManagedCodeConventions.cs @@ -17,23 +17,14 @@ namespace NuGet.Client /// public class ManagedCodeConventions { - private static readonly ContentPropertyDefinition TfmProperty = new ContentPropertyDefinition(PropertyNames.TargetFrameworkMoniker, - table: new Dictionary() - { - { "any", FrameworkConstants.CommonFrameworks.DotNet } - }, - parser: TargetFrameworkName_Parser, - compatibilityTest: TargetFrameworkName_CompatibilityTest, - compareTest: TargetFrameworkName_NearestCompareTest); - private static readonly ContentPropertyDefinition LocaleProperty = new ContentPropertyDefinition(PropertyNames.Locale, parser: Locale_Parser); private static readonly ContentPropertyDefinition AnyProperty = new ContentPropertyDefinition( PropertyNames.AnyValue, - parser: o => o); // Identity parser, all strings are valid for any + parser: (o, t) => o); // Identity parser, all strings are valid for any private static readonly ContentPropertyDefinition AssemblyProperty = new ContentPropertyDefinition(PropertyNames.ManagedAssembly, - parser: o => o.Equals(PackagingCoreConstants.EmptyFolder, StringComparison.Ordinal) ? o : null, // Accept "_._" as a pseudo-assembly + parser: (o, t) => o.Equals(PackagingCoreConstants.EmptyFolder, StringComparison.Ordinal) ? o : null, // Accept "_._" as a pseudo-assembly fileExtensions: new[] { ".dll", ".winmd", ".exe" }); private static readonly ContentPropertyDefinition MSBuildProperty = new ContentPropertyDefinition(PropertyNames.MSBuild, fileExtensions: new[] { ".targets", ".props" }); private static readonly ContentPropertyDefinition SatelliteAssemblyProperty = new ContentPropertyDefinition(PropertyNames.SatelliteAssembly, fileExtensions: new[] { ".resources.dll" }); @@ -42,6 +33,19 @@ public class ManagedCodeConventions PropertyNames.CodeLanguage, parser: CodeLanguage_Parser); + private static readonly Dictionary DefaultTfmAny = new Dictionary + { + { PropertyNames.TargetFrameworkMoniker, AnyFramework.Instance } + }; + + private static readonly PatternTable DotnetAnyTable = new PatternTable(new[] + { + new PatternTableEntry( + PropertyNames.TargetFrameworkMoniker, + "any", + FrameworkConstants.CommonFrameworks.DotNet) + }); + private RuntimeGraph _runtimeGraph; public ManagedCodeCriteria Criteria { get; } @@ -53,7 +57,6 @@ public ManagedCodeConventions(RuntimeGraph runtimeGraph) _runtimeGraph = runtimeGraph; var props = new Dictionary(); - props[TfmProperty.Name] = TfmProperty; props[AnyProperty.Name] = AnyProperty; props[AssemblyProperty.Name] = AssemblyProperty; props[LocaleProperty.Name] = LocaleProperty; @@ -63,9 +66,15 @@ public ManagedCodeConventions(RuntimeGraph runtimeGraph) props[PropertyNames.RuntimeIdentifier] = new ContentPropertyDefinition( PropertyNames.RuntimeIdentifier, - parser: o => o, // Identity parser, all strings are valid runtime ids :) + parser: (o, t) => o, // Identity parser, all strings are valid runtime ids :) compatibilityTest: RuntimeIdentifier_CompatibilityTest); + props[PropertyNames.TargetFrameworkMoniker] = new ContentPropertyDefinition( + PropertyNames.TargetFrameworkMoniker, + parser: TargetFrameworkName_Parser, + compatibilityTest: TargetFrameworkName_CompatibilityTest, + compareTest: TargetFrameworkName_NearestCompareTest); + Properties = new ReadOnlyDictionary(props); Criteria = new ManagedCodeCriteria(this); @@ -92,14 +101,32 @@ private bool RuntimeIdentifier_CompatibilityTest(object criteria, object availab } } - private static object CodeLanguage_Parser(string name) + private static object CodeLanguage_Parser(string name, PatternTable table) { + if (table != null) + { + object val; + if (table.TryLookup(PropertyNames.CodeLanguage, name, out val)) + { + return val; + } + } + // Code language values must be alpha numeric. return name.All(c => char.IsLetterOrDigit(c)) ? name : null; } - private static object Locale_Parser(string name) + private static object Locale_Parser(string name, PatternTable table) { + if (table != null) + { + object val; + if (table.TryLookup(PropertyNames.Locale, name, out val)) + { + return val; + } + } + if (name.Length == 2) { return name; @@ -112,16 +139,44 @@ private static object Locale_Parser(string name) return null; } - private static object TargetFrameworkName_Parser(string name) + private static object TargetFrameworkName_Parser( + string name, + PatternTable table) { - var result = NuGetFramework.Parse(name); + object obj = null; + + // Check for replacements + if (table != null) + { + if (table.TryLookup(PropertyNames.TargetFrameworkMoniker, name, out obj)) + { + return obj; + } + } + + return TargetFrameworkName_ParserCore(name); + } + + private static NuGetFramework TargetFrameworkName_ParserCore(string name) + { + var result = NuGetFramework.ParseFolder(name); if (!result.IsUnsupported) { return result; } - return new NuGetFramework(name, new Version(0, 0)); + // Everything should be in the folder format, but fallback to + // full parsing for legacy support. + result = NuGetFramework.ParseFrameworkName(name, DefaultFrameworkNameProvider.Instance); + + if (!result.IsUnsupported) + { + return result; + } + + // For unknown frameworks return the name as is. + return new NuGetFramework(name, FrameworkConstants.EmptyVersion); } private static bool TargetFrameworkName_CompatibilityTest(object criteria, object available) @@ -290,107 +345,96 @@ internal ManagedCodePatterns(ManagedCodeConventions conventions) conventions.Properties, groupPatterns: new PatternDefinition[] { - "{any}/{tfm}/{any?}", - "runtimes/{rid}/{any}/{tfm}/{any?}", + new PatternDefinition("{any}/{tfm}/{any?}", table: DotnetAnyTable), + new PatternDefinition("runtimes/{rid}/{any}/{tfm}/{any?}", table: DotnetAnyTable), }, pathPatterns: new PatternDefinition[] { - "{any}/{tfm}/{any?}", - "runtimes/{rid}/{any}/{tfm}/{any?}", + new PatternDefinition("{any}/{tfm}/{any?}", table: DotnetAnyTable), + new PatternDefinition("runtimes/{rid}/{any}/{tfm}/{any?}", table: DotnetAnyTable), }); RuntimeAssemblies = new PatternSet( conventions.Properties, groupPatterns: new PatternDefinition[] { - "runtimes/{rid}/lib/{tfm}/{any?}", - "lib/{tfm}/{any?}", - new PatternDefinition("lib/{assembly?}", defaults: new Dictionary - { - { "tfm", new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Net, FrameworkConstants.EmptyVersion) } - }) - }, + new PatternDefinition("runtimes/{rid}/lib/{tfm}/{any?}", table: DotnetAnyTable), + new PatternDefinition("lib/{tfm}/{any?}", table: DotnetAnyTable), + new PatternDefinition("lib/{assembly?}", table: DotnetAnyTable, + defaults: new Dictionary + { + { "tfm", new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Net, FrameworkConstants.EmptyVersion) } + }) + }, pathPatterns: new PatternDefinition[] { - "runtimes/{rid}/lib/{tfm}/{assembly}", - "lib/{tfm}/{assembly}", - new PatternDefinition("lib/{assembly}", defaults: new Dictionary - { - { "tfm", new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Net, FrameworkConstants.EmptyVersion) } - }) - }); + new PatternDefinition("runtimes/{rid}/lib/{tfm}/{assembly}", table: DotnetAnyTable), + new PatternDefinition("lib/{tfm}/{assembly}", table: DotnetAnyTable), + new PatternDefinition("lib/{assembly}", table: DotnetAnyTable, defaults: new Dictionary + { + { "tfm", new NuGetFramework(FrameworkConstants.FrameworkIdentifiers.Net, FrameworkConstants.EmptyVersion) } + }) + }); CompileAssemblies = new PatternSet( conventions.Properties, groupPatterns: new PatternDefinition[] { - "ref/{tfm}/{any?}", + new PatternDefinition("ref/{tfm}/{any?}", table: DotnetAnyTable), }, pathPatterns: new PatternDefinition[] { - "ref/{tfm}/{assembly}", + new PatternDefinition("ref/{tfm}/{assembly}", table: DotnetAnyTable), }); NativeLibraries = new PatternSet( conventions.Properties, groupPatterns: new PatternDefinition[] { - "runtimes/{rid}/nativeassets/{tfm}/{any?}", - new PatternDefinition("runtimes/{rid}/native/{any?}", defaults: new Dictionary - { - { "tfm", AnyFramework.Instance } - }) + new PatternDefinition("runtimes/{rid}/nativeassets/{tfm}/{any?}", table: DotnetAnyTable), + new PatternDefinition("runtimes/{rid}/native/{any?}", table: null, defaults: DefaultTfmAny) }, pathPatterns: new PatternDefinition[] { - "runtimes/{rid}/nativeassets/{tfm}/{any}", - new PatternDefinition("runtimes/{rid}/native/{any}", defaults: new Dictionary - { - { "tfm", AnyFramework.Instance } - }) + new PatternDefinition("runtimes/{rid}/nativeassets/{tfm}/{any}", table: DotnetAnyTable), + new PatternDefinition("runtimes/{rid}/native/{any}", table: null, defaults: DefaultTfmAny) }); ResourceAssemblies = new PatternSet( conventions.Properties, groupPatterns: new PatternDefinition[] { - "runtimes/{rid}/lib/{tfm}/{locale?}/{any?}", - "lib/{tfm}/{locale?}/{any?}" + new PatternDefinition("runtimes/{rid}/lib/{tfm}/{locale?}/{any?}", table: DotnetAnyTable), + new PatternDefinition("lib/{tfm}/{locale?}/{any?}", table: DotnetAnyTable), }, pathPatterns: new PatternDefinition[] { - "runtimes/{rid}/lib/{tfm}/{locale}/{satelliteAssembly}", - "lib/{tfm}/{locale}/{satelliteAssembly}" + new PatternDefinition("runtimes/{rid}/lib/{tfm}/{locale}/{satelliteAssembly}", table: DotnetAnyTable), + new PatternDefinition("lib/{tfm}/{locale}/{satelliteAssembly}", table: DotnetAnyTable), }); MSBuildFiles = new PatternSet( conventions.Properties, groupPatterns: new PatternDefinition[] { - "build/{tfm}/{msbuild?}", - new PatternDefinition("build/{msbuild?}", defaults: new Dictionary - { - { "tfm", AnyFramework.Instance } - }) + new PatternDefinition("build/{tfm}/{msbuild?}", table: DotnetAnyTable), + new PatternDefinition("build/{msbuild?}", table: null, defaults: DefaultTfmAny) }, pathPatterns: new PatternDefinition[] { - "build/{tfm}/{msbuild}", - new PatternDefinition("build/{msbuild}", defaults: new Dictionary - { - { "tfm", AnyFramework.Instance } - }) + new PatternDefinition("build/{tfm}/{msbuild}", table: DotnetAnyTable), + new PatternDefinition("build/{msbuild}", table: null, defaults: DefaultTfmAny) }); ContentFiles = new PatternSet( conventions.Properties, groupPatterns: new PatternDefinition[] { - "contentFiles/{codeLanguage}/{tfm}/{any?}" + new PatternDefinition("contentFiles/{codeLanguage}/{tfm}/{any?}"), }, pathPatterns: new PatternDefinition[] { - "contentFiles/{codeLanguage}/{tfm}/{any?}" + new PatternDefinition("contentFiles/{codeLanguage}/{tfm}/{any?}"), }); } } diff --git a/src/NuGet.Core/NuGet.ContentModel/ContentPropertyDefinition.cs b/src/NuGet.Core/NuGet.ContentModel/ContentPropertyDefinition.cs index 73abed45c7a..54f1b1e3ef2 100644 --- a/src/NuGet.Core/NuGet.ContentModel/ContentPropertyDefinition.cs +++ b/src/NuGet.Core/NuGet.ContentModel/ContentPropertyDefinition.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; namespace NuGet.ContentModel @@ -14,96 +13,84 @@ namespace NuGet.ContentModel /// public class ContentPropertyDefinition { - public ContentPropertyDefinition(string name) - : this(name, null, null, null, null, null, false) - { - } - - public ContentPropertyDefinition(string name, IDictionary table) - : this(name, table, null, null, null, null, false) - { - } + private static readonly char[] SlashChars = new char[] { '/', '\\' }; - public ContentPropertyDefinition(string name, Func parser) - : this(name, null, parser, null, null, null, false) - { - } - - public ContentPropertyDefinition(string name, Func compatibilityTest) - : this(name, null, null, compatibilityTest, null, null, false) - { - } - - public ContentPropertyDefinition(string name, IDictionary table, Func parser) - : this(name, table, parser, null, null, null, false) + public ContentPropertyDefinition(string name) + : this(name, null, null, null, null, false) { } - public ContentPropertyDefinition(string name, IDictionary table, Func compatibilityTest) - : this(name, table, null, compatibilityTest, null, null, false) + public ContentPropertyDefinition( + string name, + Func parser) + : this(name, parser, null, null, null, false) { } - public ContentPropertyDefinition(string name, Func parser, Func compatibilityTest) - : this(name, null, parser, compatibilityTest, null, null, false) + public ContentPropertyDefinition( + string name, + Func compatibilityTest) + : this(name, null, compatibilityTest, null, null, false) { } - public ContentPropertyDefinition(string name, IDictionary table, Func parser, Func compatibilityTest) - : this(name, table, parser, compatibilityTest, null, null, false) + public ContentPropertyDefinition( + string name, + Func parser, + Func compatibilityTest) + : this(name, parser, compatibilityTest, null, null, false) { } public ContentPropertyDefinition(string name, - IDictionary table, - Func parser, + Func parser, Func compatibilityTest, Func compareTest) - : this(name, table, parser, compatibilityTest, compareTest, null, false) + : this(name, parser, compatibilityTest, compareTest, null, false) { } - public ContentPropertyDefinition(string name, IEnumerable fileExtensions) - : this(name, null, null, null, null, fileExtensions, false) + public ContentPropertyDefinition( + string name, + IEnumerable fileExtensions) + : this(name, null, null, null, fileExtensions, false) { } - public ContentPropertyDefinition(string name, Func parser, IEnumerable fileExtensions) - : this(name, null, parser, null, null, fileExtensions, false) + public ContentPropertyDefinition( + string name, + Func parser, + IEnumerable fileExtensions) + : this(name, parser, null, null, fileExtensions, false) { } - public ContentPropertyDefinition(string name, IEnumerable fileExtensions, bool allowSubfolders) - : this(name, null, null, null, null, fileExtensions, allowSubfolders) + public ContentPropertyDefinition( + string name, + IEnumerable fileExtensions, + bool allowSubfolders) + : this(name, null, null, null, fileExtensions, allowSubfolders) { } - public ContentPropertyDefinition(string name, Func parser, IEnumerable fileExtensions, bool allowSubfolders) - : this(name, null, parser, null, null, fileExtensions, allowSubfolders) + public ContentPropertyDefinition( + string name, + Func parser, + IEnumerable fileExtensions, + bool allowSubfolders) + : this(name, parser, null, null, fileExtensions, allowSubfolders) { } public ContentPropertyDefinition( string name, - IDictionary table, - Func parser, + Func parser, Func compatibilityTest, Func compareTest, IEnumerable fileExtensions, bool allowSubfolders) { Name = name; - - if (table == null) - { - table = new Dictionary(); - } - else - { - table = new Dictionary(table); // Copies the contents of the dictionary... though we can't control the mutability of the objects :( - } - Table = new ReadOnlyDictionary(table); // Wraps the dictionary in a read-only container. Does NOT copy! - Parser = parser; CompatibilityTest = compatibilityTest ?? Equals; CompareTest = compareTest; @@ -113,15 +100,13 @@ public ContentPropertyDefinition(string name, Func parser, IEnum public string Name { get; } - public IDictionary Table { get; } - public List FileExtensions { get; } public bool FileExtensionAllowSubFolders { get; } - public Func Parser { get; } + public Func Parser { get; } - public virtual bool TryLookup(string name, out object value) + public virtual bool TryLookup(string name, PatternTable table, out object value) { if (name == null) { @@ -129,17 +114,11 @@ public virtual bool TryLookup(string name, out object value) return false; } - if (Table != null - && Table.TryGetValue(name, out value)) - { - return true; - } - if (FileExtensions != null - && FileExtensions.Any()) + && FileExtensions.Count > 0) { if (FileExtensionAllowSubFolders == true - || name.IndexOfAny(new[] { '/', '\\' }) == -1) + || name.IndexOfAny(SlashChars) == -1) { foreach (var fileExtension in FileExtensions) { @@ -154,7 +133,7 @@ public virtual bool TryLookup(string name, out object value) if (Parser != null) { - value = Parser.Invoke(name); + value = Parser.Invoke(name, table); if (value != null) { return true; diff --git a/src/NuGet.Core/NuGet.ContentModel/ContentQueryDefinition.cs b/src/NuGet.Core/NuGet.ContentModel/ContentQueryDefinition.cs index 852799a8472..ac33ef43824 100644 --- a/src/NuGet.Core/NuGet.ContentModel/ContentQueryDefinition.cs +++ b/src/NuGet.Core/NuGet.ContentModel/ContentQueryDefinition.cs @@ -49,14 +49,29 @@ public class PatternDefinition public string Pattern { get; } public IReadOnlyDictionary Defaults { get; } + /// + /// Replacement token table. + /// + public PatternTable Table { get; } + public PatternDefinition(string pattern) - : this(pattern, Enumerable.Empty>()) + : this(pattern, table: null, defaults: Enumerable.Empty>()) { } - public PatternDefinition(string pattern, IEnumerable> defaults) + public PatternDefinition(string pattern, PatternTable table) + : this(pattern, table, Enumerable.Empty>()) + { + } + + public PatternDefinition( + string pattern, + PatternTable table, + IEnumerable> defaults) { Pattern = pattern; + + Table = table; Defaults = new ReadOnlyDictionary(defaults.ToDictionary(p => p.Key, p => p.Value)); } diff --git a/src/NuGet.Core/NuGet.ContentModel/Infrastructure/Parser.cs b/src/NuGet.Core/NuGet.ContentModel/Infrastructure/Parser.cs index c15f020283c..52981e2f291 100644 --- a/src/NuGet.Core/NuGet.ContentModel/Infrastructure/Parser.cs +++ b/src/NuGet.Core/NuGet.ContentModel/Infrastructure/Parser.cs @@ -10,9 +10,11 @@ public class PatternExpression { private readonly List _segments = new List(); private readonly IReadOnlyDictionary _defaults; + private readonly PatternTable _table; public PatternExpression(PatternDefinition pattern) { + _table = pattern.Table; _defaults = pattern.Defaults; Initialize(pattern.Pattern); } @@ -37,7 +39,7 @@ private void Initialize(string pattern) var endName = endToken - (matchOnly ? 1 : 0); var tokenName = pattern.Substring(beginName, endName - beginName); - _segments.Add(new TokenSegment(tokenName, delimiter, matchOnly)); + _segments.Add(new TokenSegment(tokenName, delimiter, matchOnly, _table)); } scanIndex = endToken + 1; } @@ -113,12 +115,14 @@ private class TokenSegment : Segment private readonly string _token; private readonly char _delimiter; private readonly bool _matchOnly; + private readonly PatternTable _table; - public TokenSegment(string token, char delimiter, bool matchOnly) + public TokenSegment(string token, char delimiter, bool matchOnly, PatternTable table) { _token = token; _delimiter = delimiter; _matchOnly = matchOnly; + _table = table; } internal override bool TryMatch(ContentItem item, IReadOnlyDictionary propertyDefinitions, int startIndex, out int endIndex) @@ -138,7 +142,7 @@ internal override bool TryMatch(ContentItem item, IReadOnlyDictionary + /// Replacement token table organized by property. + /// + public class PatternTable + { + private readonly Dictionary> _table + = new Dictionary>(StringComparer.Ordinal); + + public PatternTable() + : this(Enumerable.Empty()) + { + } + + public PatternTable(IEnumerable entries) + { + if (entries == null) + { + throw new ArgumentNullException(nameof(entries)); + } + + foreach (var entry in entries) + { + Dictionary byProp; + if (!_table.TryGetValue(entry.PropertyName, out byProp)) + { + byProp = new Dictionary(StringComparer.Ordinal); + _table.Add(entry.PropertyName, byProp); + } + + byProp.Add(entry.Name, entry.Value); + } + } + + /// + /// Lookup a token and get the replacement if it exists. + /// + /// Property moniker + /// Token name + /// Replacement value + public bool TryLookup(string propertyName, string name, out object value) + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + Dictionary byProp; + if (_table.TryGetValue(propertyName, out byProp)) + { + return byProp.TryGetValue(name, out value); + } + + value = null; + return false; + } + } +} diff --git a/src/NuGet.Core/NuGet.ContentModel/PatternTableEntry.cs b/src/NuGet.Core/NuGet.ContentModel/PatternTableEntry.cs new file mode 100644 index 00000000000..425eb59de80 --- /dev/null +++ b/src/NuGet.Core/NuGet.ContentModel/PatternTableEntry.cs @@ -0,0 +1,42 @@ +// 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 NuGet.ContentModel +{ + public class PatternTableEntry + { + /// + /// PropertyName moniker + /// + public string PropertyName { get; } + + /// + /// Item name + /// + public string Name { get; } + + /// + /// Item replacement value + /// + public object Value { get; } + + public PatternTableEntry(string propertyName, string name, object value) + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + PropertyName = propertyName; + Name = name; + Value = value; + } + } +} diff --git a/src/NuGet.Core/NuGet.ContentModel/SelectionCriteriaBuilder.cs b/src/NuGet.Core/NuGet.ContentModel/SelectionCriteriaBuilder.cs index ee870303920..ee662e0bc16 100644 --- a/src/NuGet.Core/NuGet.ContentModel/SelectionCriteriaBuilder.cs +++ b/src/NuGet.Core/NuGet.ContentModel/SelectionCriteriaBuilder.cs @@ -56,7 +56,7 @@ internal SelectionCriteriaEntryBuilder(SelectionCriteriaBuilder builder, Selecti else { object valueLookup; - if (propertyDefinition.TryLookup(value, out valueLookup)) + if (propertyDefinition.TryLookup(value, table: null, value: out valueLookup)) { Entry.Properties[key] = valueLookup; } diff --git a/test/NuGet.Core.Tests/NuGet.Client.Test/ContentModelContentFilesTests.cs b/test/NuGet.Core.Tests/NuGet.Client.Test/ContentModelContentFilesTests.cs index a4859f7bc54..7f3124b164d 100644 --- a/test/NuGet.Core.Tests/NuGet.Client.Test/ContentModelContentFilesTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Client.Test/ContentModelContentFilesTests.cs @@ -9,6 +9,56 @@ namespace NuGet.Client.Test { public class ContentModelContentFilesTests { + [Fact] + public void ContentFiles_NetStandardAny() + { + // Arrange + var conventions = new ManagedCodeConventions( + new RuntimeGraph( + new List() { new CompatibilityProfile("netstandard10.app") })); + + var criteria = conventions.Criteria.ForFramework(NuGetFramework.Parse("netstandard1.0")); + + var collection = new ContentItemCollection(); + collection.Load(new string[] + { + "contentFiles/any/any/config1.xml" + }); + + // Act + var contentFileGroup = collection.FindBestItemGroup(criteria, conventions.Patterns.ContentFiles); + var file = contentFileGroup.Items.Single().Path; + + // Assert + Assert.Equal("contentFiles/any/any/config1.xml", file); + } + + [Fact] + public void ContentFiles_NetStandardFallbackToAny() + { + // Arrange + var conventions = new ManagedCodeConventions( + new RuntimeGraph( + new List() { new CompatibilityProfile("netstandard13.app") })); + + var criteria = conventions.Criteria.ForFramework(NuGetFramework.Parse("netstandard1.3")); + + var collection = new ContentItemCollection(); + collection.Load(new string[] + { + "contentFiles/any/any/config1.xml", + "contentFiles/any/dotnet/config1.xml", + "contentFiles/any/netstandard1.4/config1.xml" + }); + + // Act + var contentFileGroup = collection.FindBestItemGroup(criteria, conventions.Patterns.ContentFiles); + var file = contentFileGroup.Items.Single().Path; + + // Assert + Assert.Equal("contentFiles/any/any/config1.xml", file); + } + [Fact] public void ContentFiles_BasicContentModelCheck() { diff --git a/test/NuGet.Core.Tests/NuGet.Client.Test/ContentModelTests.cs b/test/NuGet.Core.Tests/NuGet.Client.Test/ContentModelTests.cs new file mode 100644 index 00000000000..82ecc546190 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Client.Test/ContentModelTests.cs @@ -0,0 +1,156 @@ +using System.Collections.Generic; +using System.Linq; +using NuGet.ContentModel; +using NuGet.Frameworks; +using NuGet.RuntimeModel; +using Xunit; + +namespace NuGet.Client.Test +{ + public class ContentModelTests + { + [Fact] + public void ContentModel_LibAnyMapsToDotnet() + { + // Arrange + var conventions = new ManagedCodeConventions( + new RuntimeGraph( + new List() { new CompatibilityProfile("net46.app") })); + + var collection = new ContentItemCollection(); + collection.Load(new string[] + { + "lib/any/a.dll", + }); + + // Act + var groups = collection.FindItemGroups(conventions.Patterns.RuntimeAssemblies) + .OrderBy(group => ((NuGetFramework)group.Properties["tfm"]).GetShortFolderName()) + .ToList(); + + // Assert + Assert.Equal(1, groups.Count); + Assert.Equal(FrameworkConstants.CommonFrameworks.DotNet, (NuGetFramework)groups[0].Properties["tfm"]); + } + + [Fact] + public void ContentModel_RefAnyMapsToDotnet() + { + // Arrange + var conventions = new ManagedCodeConventions( + new RuntimeGraph( + new List() { new CompatibilityProfile("net46.app") })); + + var collection = new ContentItemCollection(); + collection.Load(new string[] + { + "ref/any/a.dll", + }); + + // Act + var groups = collection.FindItemGroups(conventions.Patterns.CompileAssemblies) + .OrderBy(group => ((NuGetFramework)group.Properties["tfm"]).GetShortFolderName()) + .ToList(); + + // Assert + Assert.Equal(1, groups.Count); + Assert.Equal(FrameworkConstants.CommonFrameworks.DotNet, (NuGetFramework)groups[0].Properties["tfm"]); + } + + [Fact] + public void ContentModel_RuntimesAnyMapsToDotnet() + { + // Arrange + var conventions = new ManagedCodeConventions( + new RuntimeGraph( + new List() { new CompatibilityProfile("net46.app") })); + + var collection = new ContentItemCollection(); + collection.Load(new string[] + { + "runtimes/any/lib/any/a.dll", + }); + + // Act + var groups = collection.FindItemGroups(conventions.Patterns.RuntimeAssemblies) + .OrderBy(group => ((NuGetFramework)group.Properties["tfm"]).GetShortFolderName()) + .ToList(); + + // Assert + Assert.Equal(1, groups.Count); + Assert.Equal(FrameworkConstants.CommonFrameworks.DotNet, (NuGetFramework)groups[0].Properties["tfm"]); + } + + [Fact] + public void ContentModel_ResourcesAnyMapsToDotnet() + { + // Arrange + var conventions = new ManagedCodeConventions( + new RuntimeGraph( + new List() { new CompatibilityProfile("net46.app") })); + + var collection = new ContentItemCollection(); + collection.Load(new string[] + { + "lib/any/en-us/a.resources.dll", + }); + + // Act + var groups = collection.FindItemGroups(conventions.Patterns.ResourceAssemblies) + .OrderBy(group => ((NuGetFramework)group.Properties["tfm"]).GetShortFolderName()) + .ToList(); + + // Assert + Assert.Equal(1, groups.Count); + Assert.Equal(FrameworkConstants.CommonFrameworks.DotNet, (NuGetFramework)groups[0].Properties["tfm"]); + } + + [Fact] + public void ContentModel_MSBuildAnyMapsToDotnet() + { + // Arrange + var conventions = new ManagedCodeConventions( + new RuntimeGraph( + new List() { new CompatibilityProfile("net46.app") })); + + var collection = new ContentItemCollection(); + collection.Load(new string[] + { + "build/any/a.targets", + }); + + // Act + var groups = collection.FindItemGroups(conventions.Patterns.MSBuildFiles) + .OrderBy(group => ((NuGetFramework)group.Properties["tfm"]).GetShortFolderName()) + .ToList(); + + // Assert + Assert.Equal(1, groups.Count); + Assert.Equal(FrameworkConstants.CommonFrameworks.DotNet, (NuGetFramework)groups[0].Properties["tfm"]); + } + + [Fact] + public void ContentModel_ContentFilesAnyMapsToAny() + { + // Arrange + var conventions = new ManagedCodeConventions( + new RuntimeGraph( + new List() { new CompatibilityProfile("net46.app") })); + + var collection = new ContentItemCollection(); + collection.Load(new string[] + { + "contentFiles/any/any/a.txt", + }); + + // Act + var groups = collection.FindItemGroups(conventions.Patterns.ContentFiles) + .OrderBy(group => ((NuGetFramework)group.Properties["tfm"]).GetShortFolderName()) + .ToList(); + + // Assert + Assert.Equal(1, groups.Count); + Assert.Equal(NuGetFramework.AnyFramework, (NuGetFramework)groups[0].Properties["tfm"]); + } + } +} diff --git a/test/NuGet.Core.Tests/NuGet.Client.Test/PatternTableTests.cs b/test/NuGet.Core.Tests/NuGet.Client.Test/PatternTableTests.cs new file mode 100644 index 00000000000..5741f886495 --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Client.Test/PatternTableTests.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using NuGet.ContentModel; +using NuGet.Frameworks; +using Xunit; + +namespace NuGet.Client.Test +{ + public class PatternTableTests + { + [Fact] + public void PatternTable_LookupWhenEmpty() + { + // Arrange + var table = new PatternTable(); + + // Act + object obj; + var b = table.TryLookup("tfm", "any", out obj); + + // Assert + Assert.False(b); + Assert.Null(obj); + } + + [Fact] + public void PatternTable_LookupWithValue() + { + // Arrange + var dotnet = NuGetFramework.Parse("dotnet"); + var data = new List() + { + new PatternTableEntry("tfm", "any", dotnet) + }; + + var table = new PatternTable(data); + + // Act + object obj; + var b = table.TryLookup("tfm", "any", out obj); + + // Assert + Assert.True(b); + Assert.Equal(dotnet, obj); + } + + [Fact] + public void PatternTable_LookupWithMiss() + { + // Arrange + var data = new List() + { + new PatternTableEntry("tfm", "dotnet", NuGetFramework.Parse("dotnet")) + }; + + var table = new PatternTable(data); + + // Act + object obj; + var b = table.TryLookup("tfm", "any", out obj); + + // Assert + Assert.False(b); + Assert.Null(obj); + } + } +} diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/ContentFilesTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/ContentFilesTests.cs index 3f52d1f39fa..b9a7e1d33f8 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/ContentFilesTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/ContentFilesTests.cs @@ -17,6 +17,76 @@ namespace NuGet.Commands.Test { public class ContentFilesTests { + [Fact] + public async Task ContentFiles_VerifyContentFilesAreAddedForNetstandard() + { + // Arrange + var logger = new TestLogger(); + var framework = "netstandard1.0"; + + using (var workingDir = TestFileSystemUtility.CreateRandomTestFolder()) + { + var repository = Path.Combine(workingDir, "repository"); + Directory.CreateDirectory(repository); + var projectDir = Path.Combine(workingDir, "project"); + Directory.CreateDirectory(projectDir); + var packagesDir = Path.Combine(workingDir, "packages"); + Directory.CreateDirectory(packagesDir); + + var file = new FileInfo(Path.Combine(repository, "packageA.1.0.0.nupkg")); + + using (var zip = new ZipArchive(File.Create(file.FullName), ZipArchiveMode.Create)) + { + zip.AddEntry("contentFiles/any/any/test.txt", new byte[] { 0 }); + zip.AddEntry("contentFiles/any/net45/test.txt", new byte[] { 0 }); + + zip.AddEntry("packageA.nuspec", @" + + + packageA + 1.0.0 + + <contentFiles> + <files include=""**/*.*"" copyToOutput=""TRUE"" flatten=""true"" /> + </contentFiles> + </metadata> + </package>", Encoding.UTF8); + } + + var sources = new List<PackageSource>(); + sources.Add(new PackageSource(repository)); + + var configJson = JObject.Parse(@"{ + ""dependencies"": { + ""packageA"": ""1.0.0"" + }, + ""frameworks"": { + ""_FRAMEWORK_"": {} + } + }".Replace("_FRAMEWORK_", framework)); + + var specPath = Path.Combine(projectDir, "TestProject", "project.json"); + var spec = JsonPackageSpecReader.GetPackageSpec(configJson.ToString(), "TestProject", specPath); + + var request = new RestoreRequest(spec, sources, packagesDir, logger); + request.LockFilePath = Path.Combine(projectDir, "project.lock.json"); + + var command = new RestoreCommand(request); + + // Act + var result = await command.ExecuteAsync(); + await result.CommitAsync(logger, CancellationToken.None); + + var target = result.LockFile.GetTarget(NuGetFramework.Parse(framework), null); + + var helperCsItem = target.Libraries.Single().ContentFiles + .SingleOrDefault(item => item.Path == "contentFiles/any/any/test.txt"); + + // Assert + Assert.NotNull(helperCsItem); + } + } + [Fact] public async Task ContentFiles_VerifyLockFileContainsCorrectPPOutputPath() {