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() {