From b9fd8e26d95332f621fe9b0e9f930c5d2864a981 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 16 Jul 2025 16:24:29 -0700 Subject: [PATCH 1/3] Add LocalPath property to RuntimeFile and ResourceAssembly for writing/reading deps.json --- .../Microsoft.Extensions.DependencyModel.cs | 4 ++++ .../src/DependencyContextJsonReader.cs | 22 +++++++++++++++---- .../src/DependencyContextStrings.cs | 2 ++ .../src/DependencyContextWriter.cs | 15 +++++++++++++ .../src/ResourceAssembly.cs | 9 ++++++++ .../src/RuntimeFile.cs | 10 +++++++++ 6 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/ref/Microsoft.Extensions.DependencyModel.cs b/src/libraries/Microsoft.Extensions.DependencyModel/ref/Microsoft.Extensions.DependencyModel.cs index f7ae0c80486eef..22d31173134c5b 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/ref/Microsoft.Extensions.DependencyModel.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/ref/Microsoft.Extensions.DependencyModel.cs @@ -127,8 +127,10 @@ public Library(string type, string name, string version, string? hash, System.Co public partial class ResourceAssembly { public ResourceAssembly(string path, string locale) { } + public ResourceAssembly(string path, string locale, string? localPath) { } public string Locale { get { throw null; } set { } } public string Path { get { throw null; } set { } } + public string? LocalPath { get { throw null; } } } public partial class RuntimeAssembly { @@ -156,9 +158,11 @@ public RuntimeFallbacks(string runtime, params string?[] fallbacks) { } public partial class RuntimeFile { public RuntimeFile(string path, string? assemblyVersion, string? fileVersion) { } + public RuntimeFile(string path, string? assemblyVersion, string? fileVersion, string? localPath) { } public string? AssemblyVersion { get { throw null; } } public string? FileVersion { get { throw null; } } public string Path { get { throw null; } } + public string? LocalPath { get { throw null; } } } public partial class RuntimeLibrary : Microsoft.Extensions.DependencyModel.Library { diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextJsonReader.cs b/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextJsonReader.cs index 8dfcb8d89b6ba5..af6b584fab09cd 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextJsonReader.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextJsonReader.cs @@ -507,6 +507,7 @@ private static List ReadRuntimeFiles(ref Utf8JsonReader reader) { string? assemblyVersion = null; string? fileVersion = null; + string? localPath = null; string? path = reader.GetString(); @@ -527,12 +528,15 @@ private static List ReadRuntimeFiles(ref Utf8JsonReader reader) case DependencyContextStrings.FileVersionPropertyName: fileVersion = propertyValue; break; + case DependencyContextStrings.LocalPathPropertyName: + localPath = propertyValue; + break; } } reader.CheckEndObject(); - runtimeFiles.Add(new RuntimeFile(path, assemblyVersion, fileVersion)); + runtimeFiles.Add(new RuntimeFile(path, assemblyVersion, fileVersion, localPath)); } reader.CheckEndObject(); @@ -578,6 +582,9 @@ private List ReadTargetLibraryRuntimeTargets(ref Utf8Jso case DependencyContextStrings.FileVersionPropertyName: runtimeTarget.FileVersion = propertyValue; break; + case DependencyContextStrings.LocalPathPropertyName: + runtimeTarget.LocalPath = propertyValue; + break; } } @@ -607,6 +614,7 @@ private List ReadTargetLibraryResources(ref Utf8JsonReader rea } string? locale = null; + string? localPath = null; reader.ReadStartObject(); @@ -616,13 +624,17 @@ private List ReadTargetLibraryResources(ref Utf8JsonReader rea { locale = propertyValue; } + else if (propertyName == DependencyContextStrings.LocalPathPropertyName) + { + localPath = propertyValue; + } } reader.CheckEndObject(); if (locale != null) { - resources.Add(new ResourceAssembly(path, Pool(locale))); + resources.Add(new ResourceAssembly(path, Pool(locale), localPath)); } } @@ -786,7 +798,7 @@ IEnumerable CreateLibrariesNotNull(IEnumerable libraries { RuntimeFile[] groupRuntimeAssemblies = ridGroup .Where(e => e.Type == DependencyContextStrings.RuntimeAssetType) - .Select(e => new RuntimeFile(e.Path, e.AssemblyVersion, e.FileVersion)) + .Select(e => new RuntimeFile(e.Path, e.AssemblyVersion, e.FileVersion, e.LocalPath)) .ToArray(); if (groupRuntimeAssemblies.Length != 0) @@ -798,7 +810,7 @@ IEnumerable CreateLibrariesNotNull(IEnumerable libraries RuntimeFile[] groupNativeLibraries = ridGroup .Where(e => e.Type == DependencyContextStrings.NativeAssetType) - .Select(e => new RuntimeFile(e.Path, e.AssemblyVersion, e.FileVersion)) + .Select(e => new RuntimeFile(e.Path, e.AssemblyVersion, e.FileVersion, e.LocalPath)) .ToArray(); if (groupNativeLibraries.Length != 0) @@ -909,6 +921,8 @@ private struct RuntimeTargetEntryStub public string? AssemblyVersion; public string? FileVersion; + + public string? LocalPath; } private struct LibraryStub diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextStrings.cs b/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextStrings.cs index 09802702197491..fcdf9beba015d8 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextStrings.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextStrings.cs @@ -86,5 +86,7 @@ internal static class DependencyContextStrings internal const string AssemblyVersionPropertyName = "assemblyVersion"; internal const string FileVersionPropertyName = "fileVersion"; + + internal const string LocalPathPropertyName = "localPath"; } } diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextWriter.cs b/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextWriter.cs index d4247d937067a7..86c1a548b491b8 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextWriter.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/src/DependencyContextWriter.cs @@ -222,6 +222,11 @@ private static void AddResourceAssemblies(IReadOnlyList resour ResourceAssembly resourceAssembly = resourceAssemblies[i]; jsonWriter.WriteStartObject(NormalizePath(resourceAssembly.Path)); jsonWriter.WriteString(DependencyContextStrings.LocalePropertyName, resourceAssembly.Locale); + if (resourceAssembly.LocalPath != null) + { + jsonWriter.WriteString(DependencyContextStrings.LocalPathPropertyName, NormalizePath(resourceAssembly.LocalPath)); + } + jsonWriter.WriteEndObject(); } jsonWriter.WriteEndObject(); @@ -363,6 +368,11 @@ private static void AddRuntimeSpecificAssets(IEnumerable assets, st jsonWriter.WriteString(DependencyContextStrings.FileVersionPropertyName, asset.FileVersion); } + if (asset.LocalPath != null) + { + jsonWriter.WriteString(DependencyContextStrings.LocalPathPropertyName, NormalizePath(asset.LocalPath)); + } + jsonWriter.WriteEndObject(); } } @@ -396,6 +406,11 @@ private static void WriteAssetList(string key, IEnumerable runtimeF jsonWriter.WriteString(DependencyContextStrings.FileVersionPropertyName, runtimeFile.FileVersion); } + if (runtimeFile.LocalPath != null) + { + jsonWriter.WriteString(DependencyContextStrings.LocalPathPropertyName, NormalizePath(runtimeFile.LocalPath)); + } + jsonWriter.WriteEndObject(); } diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs b/src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs index 04d480837c8ad0..ce12fd74a840be 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs @@ -8,6 +8,10 @@ namespace Microsoft.Extensions.DependencyModel public class ResourceAssembly { public ResourceAssembly(string path, string locale) + : this(path, locale, null) + { } + + public ResourceAssembly(string path, string locale, string? localPath) { if (string.IsNullOrEmpty(path)) { @@ -19,11 +23,16 @@ public ResourceAssembly(string path, string locale) } Locale = locale; Path = path; + LocalPath = localPath; } public string Locale { get; set; } + // Depending on the source of the runtime file, this path may be relative to the + // a referenced NuGet package's root or to the app/component root. public string Path { get; set; } + // Path relative to the app/component represented by the dependency context + public string? LocalPath { get; } } } diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/src/RuntimeFile.cs b/src/libraries/Microsoft.Extensions.DependencyModel/src/RuntimeFile.cs index 16a4524865ad45..1b9fa096835946 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/src/RuntimeFile.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/src/RuntimeFile.cs @@ -8,6 +8,10 @@ namespace Microsoft.Extensions.DependencyModel public class RuntimeFile { public RuntimeFile(string path, string? assemblyVersion, string? fileVersion) + : this(path, assemblyVersion, fileVersion, null) + { } + + public RuntimeFile(string path, string? assemblyVersion, string? fileVersion, string? localPath) { if (string.IsNullOrEmpty(path)) { @@ -17,12 +21,18 @@ public RuntimeFile(string path, string? assemblyVersion, string? fileVersion) Path = path; AssemblyVersion = assemblyVersion; FileVersion = fileVersion; + LocalPath = localPath; } + // Depending on the source of the runtime file, this path may be relative to the + // a referenced NuGet package's root or to the app/component root. public string Path { get; } public string? AssemblyVersion { get; } public string? FileVersion { get; } + + // Path relative to the app/component represented by the dependency context + public string? LocalPath { get; } } } From 39b0164d260f1a61be1f2f86722a5fe5547813a5 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 24 Jul 2025 11:49:05 -0700 Subject: [PATCH 2/3] Add tests --- .../tests/DependencyContextJsonReaderTest.cs | 118 ++++++ .../tests/DependencyContextJsonWriterTests.cs | 381 ++++++++---------- 2 files changed, 293 insertions(+), 206 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/tests/DependencyContextJsonReaderTest.cs b/src/libraries/Microsoft.Extensions.DependencyModel/tests/DependencyContextJsonReaderTest.cs index e16ec056079a97..b1e2e126391f52 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/tests/DependencyContextJsonReaderTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/tests/DependencyContextJsonReaderTest.cs @@ -835,5 +835,123 @@ public void FailsToReadEmptyLibraryType() } ")); } + + [Fact] + public void ReadsRuntimeFilesWithLocalPath() + { + var context = Read( +@"{ + ""targets"": { + "".NETCoreApp,Version=v5.0"": { + ""System.Banana/1.0.0"": { + ""runtime"": { + ""lib/System.Banana.dll"": { + ""assemblyVersion"": ""1.2.3"", + ""fileVersion"": ""4.5.6"", + ""localPath"": ""local/path/System.Banana.dll"" + } + } + } + } + }, + ""libraries"": { + ""System.Banana/1.0.0"": { + ""type"": ""package"", + ""serviceable"": false, + ""sha512"": ""HASH-System.Banana"" + } + } +}"); + + var runtimeLibrary = context.RuntimeLibraries.Should().ContainSingle().Subject; + var runtimeFile = runtimeLibrary.RuntimeAssemblyGroups.GetDefaultRuntimeFileAssets().Single(); + + Assert.Equal("lib/System.Banana.dll", runtimeFile.Path); + Assert.Equal("1.2.3", runtimeFile.AssemblyVersion); + Assert.Equal("4.5.6", runtimeFile.FileVersion); + Assert.Equal("local/path/System.Banana.dll", runtimeFile.LocalPath); + } + + [Fact] + public void ReadsRuntimeTargetsWithLocalPath() + { + var context = Read( +@"{ + ""targets"": { + "".NETCoreApp,Version=v5.0/unix"": { + ""System.Banana/1.0.0"": { + ""runtimeTargets"": { + ""runtimes/unix/lib/System.Banana.dll"": { + ""rid"": ""unix"", + ""assetType"": ""runtime"", + ""assemblyVersion"": ""1.2.3"", + ""fileVersion"": ""4.5.6"", + ""localPath"": ""unix/custom/System.Banana.dll"" + }, + ""runtimes/linux-x64/native/native.so"": { + ""rid"": ""linux-x64"", + ""assetType"": ""native"", + ""assemblyVersion"": ""1.2.3"", + ""fileVersion"": ""4.5.6"", + ""localPath"": ""local/path/linux-x64/native.so"" + } + } + } + } + }, + ""libraries"": { + ""System.Banana/1.0.0"": { + ""type"": ""package"", + ""serviceable"": false, + ""sha512"": ""HASH-System.Banana"" + } + } +}"); + + var runtimeLibrary = context.RuntimeLibraries.Should().ContainSingle().Subject; + var runtimeFile = runtimeLibrary.RuntimeAssemblyGroups.GetRuntimeFileAssets("unix").Single(); + Assert.Equal("runtimes/unix/lib/System.Banana.dll", runtimeFile.Path); + Assert.Equal("1.2.3", runtimeFile.AssemblyVersion); + Assert.Equal("4.5.6", runtimeFile.FileVersion); + Assert.Equal("unix/custom/System.Banana.dll", runtimeFile.LocalPath); + + runtimeFile = runtimeLibrary.NativeLibraryGroups.GetRuntimeFileAssets("linux-x64").Single(); + Assert.Equal("runtimes/linux-x64/native/native.so", runtimeFile.Path); + Assert.Equal("local/path/linux-x64/native.so", runtimeFile.LocalPath); + } + + [Fact] + public void ReadsResourceAssembliesWithLocalPath() + { + var context = Read( +@"{ + ""targets"": { + "".NETCoreApp,Version=v5.0"": { + ""System.Banana/1.0.0"": { + ""resources"": { + ""fr/System.Banana.resources.dll"": { + ""locale"": ""fr"", + ""localPath"": ""local/path/fr/System.Banana.resources.dll"" + } + } + } + } + }, + ""libraries"": { + ""System.Banana/1.0.0"": { + ""type"": ""package"", + ""serviceable"": false, + ""sha512"": ""HASH-System.Banana"" + } + } +}"); + + var runtimeLibrary = context.RuntimeLibraries.Should().ContainSingle().Subject; + var resourceAssembly = runtimeLibrary.ResourceAssemblies.Single(); + + Assert.Equal("fr/System.Banana.resources.dll", resourceAssembly.Path); + Assert.Equal("fr", resourceAssembly.Locale); + Assert.Equal("local/path/fr/System.Banana.resources.dll", resourceAssembly.LocalPath); + } } } diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/tests/DependencyContextJsonWriterTests.cs b/src/libraries/Microsoft.Extensions.DependencyModel/tests/DependencyContextJsonWriterTests.cs index 3e306f0a46bdd5..93fa5ec7dc9c52 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/tests/DependencyContextJsonWriterTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/tests/DependencyContextJsonWriterTests.cs @@ -277,95 +277,6 @@ public void ExcludesPathAndHashPath() library.Should().NotHaveProperty("hashPath"); } - [Fact] - public void WritesRuntimeLibrariesToRuntimeTarget() - { - var group = new RuntimeAssetGroup("win7-x64", "Banana.Win7-x64.dll"); - WritesRuntimeLibrariesToRuntimeTargetCore(group); - } - - [Fact] - public void WritesRuntimeLibrariesToRuntimeTargetWithAssemblyVersions() - { - RuntimeFile[] runtimeFile = { new RuntimeFile("Banana.Win7-x64.dll", "1.2.3", "7.8.9") }; - var group = new RuntimeAssetGroup("win7-x64", runtimeFile); - - var runtimeAssembly = WritesRuntimeLibrariesToRuntimeTargetCore(group); - runtimeAssembly.Should().HavePropertyValue("assemblyVersion", "1.2.3"); - runtimeAssembly.Should().HavePropertyValue("fileVersion", "7.8.9"); - } - - private JObject WritesRuntimeLibrariesToRuntimeTargetCore(RuntimeAssetGroup group) - { - var result = Save(Create( - "Target", - "runtime", - true, - runtimeLibraries: new[] - { - new RuntimeLibrary( - "package", - "PackageName", - "1.2.3", - "HASH", - new [] { - new RuntimeAssetGroup(string.Empty, "Banana.dll"), - group - }, - new [] { - new RuntimeAssetGroup(string.Empty, "runtimes\\linux\\native\\native.so"), - new RuntimeAssetGroup("win7-x64", "native\\Banana.Win7-x64.so") - }, - new [] { new ResourceAssembly("en-US\\Banana.Resource.dll", "en-US")}, - new [] { - new Dependency("Fruits.Abstract.dll","2.0.0") - }, - true, - "PackagePath", - "PackageHashPath", - "placeHolderManifest.xml" - ), - })); - - // targets - var targets = result.Should().HavePropertyAsObject("targets").Subject; - var target = targets.Should().HavePropertyAsObject("Target").Subject; - var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; - var dependencies = library.Should().HavePropertyAsObject("dependencies").Subject; - dependencies.Should().HavePropertyValue("Fruits.Abstract.dll", "2.0.0"); - - library.Should().HavePropertyAsObject("runtime") - .Subject.Should().HaveProperty("Banana.dll"); - library.Should().HavePropertyAsObject("native") - .Subject.Should().HaveProperty("runtimes/linux/native/native.so"); - - var runtimeTargets = library.Should().HavePropertyAsObject("runtimeTargets").Subject; - - var runtimeAssembly = runtimeTargets.Should().HavePropertyAsObject("Banana.Win7-x64.dll").Subject; - runtimeAssembly.Should().HavePropertyValue("rid", "win7-x64"); - runtimeAssembly.Should().HavePropertyValue("assetType", "runtime"); - - var nativeLibrary = runtimeTargets.Should().HavePropertyAsObject("native/Banana.Win7-x64.so").Subject; - nativeLibrary.Should().HavePropertyValue("rid", "win7-x64"); - nativeLibrary.Should().HavePropertyValue("assetType", "native"); - - var resourceAssemblies = library.Should().HavePropertyAsObject("resources").Subject; - var resourceAssembly = resourceAssemblies.Should().HavePropertyAsObject("en-US/Banana.Resource.dll").Subject; - resourceAssembly.Should().HavePropertyValue("locale", "en-US"); - - //libraries - var libraries = result.Should().HavePropertyAsObject("libraries").Subject; - library = libraries.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; - library.Should().HavePropertyValue("sha512", "HASH"); - library.Should().HavePropertyValue("type", "package"); - library.Should().HavePropertyValue("serviceable", true); - library.Should().HavePropertyValue("path", "PackagePath"); - library.Should().HavePropertyValue("hashPath", "PackageHashPath"); - library.Should().HavePropertyValue("runtimeStoreManifestName", "placeHolderManifest.xml"); - - return runtimeAssembly; - } - [Fact] public void WritesRuntimePackLibrariesWithFrameworkName() { @@ -513,35 +424,105 @@ public void MergesRuntimeAndCompileLibrariesForPortable() library.Should().HavePropertyValue("runtimeStoreManifestName", "placeHolderManifest.xml"); } - [Fact] - public void WritesRuntimeTargetForNonPortableLegacy() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WritesRuntimeAssembly_PathOnly(bool isPortable) { - var group = new RuntimeAssetGroup(string.Empty, "Banana.dll"); - var assetGroup = WritesRuntimeTarget(group); + List groups = [ new RuntimeAssetGroup(string.Empty, "Banana.dll") ]; + if (isPortable) + groups.Add(new RuntimeAssetGroup("win-x64", "Banana.win-x64.dll")); - var files = assetGroup.Should().HavePropertyAsObject("runtime").Subject; - files.Should().HaveProperty("Banana.dll"); + WritesRuntimeAssembly(isPortable, groups); } - [Fact] - public void WritesRuntimeTargetForNonPortable() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WritesRuntimeAssembly_Versions(bool isPortable) + { + List groups = [ + new RuntimeAssetGroup(string.Empty, [new RuntimeFile("Banana.dll", "1.2.3", "7.8.9")]) + ]; + if (isPortable) + groups.Add(new RuntimeAssetGroup("win-x64", [ new RuntimeFile("Banana.win-x64.dll", "1.2.3", "7.8.9") ])); + + WritesRuntimeAssembly(isPortable, groups); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WritesRuntimeAssembly_LocalPath(bool isPortable) { - RuntimeFile[] runtimeFiles = { new RuntimeFile("Banana.dll", "1.2.3", "7.8.9") }; - var group = new RuntimeAssetGroup(string.Empty, runtimeFiles); - var assetGroup = WritesRuntimeTarget(group); - - var files = assetGroup.Should().HavePropertyAsObject("runtime").Subject; - var file = files.Should().HavePropertyAsObject("Banana.dll").Subject; - file.Should().HavePropertyValue("assemblyVersion", "1.2.3"); - file.Should().HavePropertyValue("fileVersion", "7.8.9"); + List groups = [ + new RuntimeAssetGroup(string.Empty, [ new RuntimeFile("Banana.dll", "1.2.3", "7.8.9", "local/path/Banana.dll")]) + ]; + if (isPortable) + groups.Add(new RuntimeAssetGroup("win-x64", [new RuntimeFile("Banana.win-x64.dll", "1.2.3", "7.8.9", "local/path/Banana.win-x64.dll")])); + + WritesRuntimeAssembly(isPortable, groups); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WritesNativeLibrary_PathOnly(bool isPortable) + { + List groups = [new RuntimeAssetGroup(string.Empty, "native.so")]; + if (isPortable) + groups.Add(new RuntimeAssetGroup("linux-x64", "runtimes/linux-x64/native/native.linux-64.so")); + + WritesNativeLibrary(isPortable, groups); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WritesNativeLibrary_LocalPath(bool isPortable) + { + List groups = [ + new RuntimeAssetGroup(string.Empty, [new RuntimeFile("native.so", null, null, "local/path/native.so")]) + ]; + if (isPortable) + groups.Add(new RuntimeAssetGroup("linux-x64", [new RuntimeFile("runtimes/linux-x64/native/native.linux-64.so", null, null, "local/path/native.so")])); + + WritesNativeLibrary(isPortable, groups); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WritesResourceAssembly_PathOnly(bool isPortable) + { + ResourceAssembly resourceAssembly = new ResourceAssembly("fr/Fruits.resources.dll", "fr"); + WritesResourceAssembly(isPortable, [resourceAssembly]); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void WritesResourceAssembly_LocalPath(bool isPortable) + { + ResourceAssembly resourceAssembly = new ResourceAssembly("fr/Fruits.resources.dll", "fr", "local/path/fr/Fruits.resources.dll"); + WritesResourceAssembly(isPortable, [resourceAssembly]); } - private JObject WritesRuntimeTarget(RuntimeAssetGroup group) + private void WritesRuntimeAssembly(bool isPortable, IReadOnlyList runtimeAssemblies) + => WritesRuntimeLibrary(isPortable, runtimeAssemblies, [], []); + + private void WritesNativeLibrary(bool isPortable, IReadOnlyList nativeLibraries) + => WritesRuntimeLibrary(isPortable, [], nativeLibraries, []); + + private void WritesResourceAssembly(bool isPortable, ResourceAssembly[] resourceAssemblies) + => WritesRuntimeLibrary(isPortable, [], [], resourceAssemblies); + + private void WritesRuntimeLibrary(bool isPortable, IReadOnlyList runtimeAssemblies, IReadOnlyList nativeLibraries, ResourceAssembly[] resourceAssemblies) { var result = Save(Create( "Target", "runtime", - false, + isPortable, runtimeLibraries: new[] { new RuntimeLibrary( @@ -549,17 +530,11 @@ private JObject WritesRuntimeTarget(RuntimeAssetGroup group) "PackageName", "1.2.3", "HASH", - new [] { - group - }, - new [] { - new RuntimeAssetGroup(string.Empty, "runtimes\\osx\\native\\native.dylib") - }, - new ResourceAssembly[] {}, - new [] { - new Dependency("Fruits.Abstract.dll","2.0.0") - }, - true, + runtimeAssemblies, + nativeLibraries, + resourceAssemblies, + dependencies: [], + serviceable: true, "PackagePath", "PackageHashPath" ), @@ -567,23 +542,90 @@ private JObject WritesRuntimeTarget(RuntimeAssetGroup group) // targets var targets = result.Should().HavePropertyAsObject("targets").Subject; - var target = targets.Should().HavePropertyAsObject("Target/runtime").Subject; - var assetGroup = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; - var dependencies = assetGroup.Should().HavePropertyAsObject("dependencies").Subject; - dependencies.Should().HavePropertyValue("Fruits.Abstract.dll", "2.0.0"); - assetGroup.Should().HavePropertyAsObject("native") - .Subject.Should().HaveProperty("runtimes/osx/native/native.dylib"); + var target = targets.Should().HavePropertyAsObject($"Target{(isPortable ? "" : "/runtime")}").Subject; + var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; - //libraries - var libraries = result.Should().HavePropertyAsObject("libraries").Subject; - var library = libraries.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; - library.Should().HavePropertyValue("sha512", "HASH"); - library.Should().HavePropertyValue("type", "package"); - library.Should().HavePropertyValue("serviceable", true); - library.Should().HavePropertyValue("path", "PackagePath"); - library.Should().HavePropertyValue("hashPath", "PackageHashPath"); + ValidateRuntimeAssetGroups(library, runtimeAssemblies, DependencyContextStrings.RuntimeAssetType); + ValidateRuntimeAssetGroups(library, nativeLibraries, DependencyContextStrings.NativeAssetType); + + if (resourceAssemblies.Length == 0) + { + library.Should().NotHaveProperty(DependencyContextStrings.ResourceAssembliesPropertyName); + } + else + { + var resources = library.Should().HavePropertyAsObject(DependencyContextStrings.ResourceAssembliesPropertyName).Subject; + foreach (var resource in resourceAssemblies) + { + var resourceJson = resources.Should().HavePropertyAsObject(resource.Path).Subject; + resourceJson.Should().HavePropertyValue(DependencyContextStrings.LocalePropertyName, resource.Locale); + if (string.IsNullOrEmpty(resource.LocalPath)) + { + resourceJson.Should().NotHaveProperty(DependencyContextStrings.LocalPathPropertyName); + } + else + { + resourceJson.Should().HavePropertyValue(DependencyContextStrings.LocalPathPropertyName, resource.LocalPath); + } + } + } + } + + private void ValidateRuntimeAssetGroups(JObject library, IReadOnlyList groups, string assetType) + { + if (groups.Count == 0) + library.Should().NotHaveProperty(assetType); + + foreach (var group in groups) + { + bool hasRuntimeId = !string.IsNullOrEmpty(group.Runtime); + var files = library.Should().HavePropertyAsObject(hasRuntimeId ? DependencyContextStrings.RuntimeTargetsPropertyName : assetType).Subject; + foreach (var file in group.RuntimeFiles) + { + var fileJson = files.Should().HavePropertyAsObject(file.Path).Subject; + ValidateRuntimeFile(file, fileJson); + if (hasRuntimeId) + { + fileJson.Should().HavePropertyValue(DependencyContextStrings.RidPropertyName, group.Runtime); + fileJson.Should().HavePropertyValue(DependencyContextStrings.AssetTypePropertyName, assetType); + } + else + { + fileJson.Should().NotHaveProperty(DependencyContextStrings.RidPropertyName); + fileJson.Should().NotHaveProperty(DependencyContextStrings.AssetTypePropertyName); + } + } + } + } + + private void ValidateRuntimeFile(RuntimeFile file, JObject fileJson) + { + if (string.IsNullOrEmpty(file.AssemblyVersion)) + { + fileJson.Should().NotHaveProperty(DependencyContextStrings.AssemblyVersionPropertyName); + } + else + { + fileJson.Should().HavePropertyValue(DependencyContextStrings.AssemblyVersionPropertyName, file.AssemblyVersion); + } + + if (string.IsNullOrEmpty(file.FileVersion)) + { + fileJson.Should().NotHaveProperty(DependencyContextStrings.FileVersionPropertyName); + } + else + { + fileJson.Should().HavePropertyValue(DependencyContextStrings.FileVersionPropertyName, file.FileVersion); + } - return assetGroup; + if (string.IsNullOrEmpty(file.LocalPath)) + { + fileJson.Should().NotHaveProperty(DependencyContextStrings.LocalPathPropertyName); + } + else + { + fileJson.Should().HavePropertyValue(DependencyContextStrings.LocalPathPropertyName, file.LocalPath); + } } [Fact] @@ -640,78 +682,6 @@ public void WritesPlaceholderRuntimeTargetsForEmptyGroups() osxNative.Should().HavePropertyValue("assetType", "native"); } - [Fact] - public void WritesResourceAssembliesForNonPortable() - { - var result = Save(Create( - "Target", - "runtime", - false, - runtimeLibraries: new[] - { - new RuntimeLibrary( - "package", - "PackageName", - "1.2.3", - "HASH", - new RuntimeAssetGroup[] { }, - new RuntimeAssetGroup[] { }, - new [] - { - new ResourceAssembly("en-US/Fruits.resources.dll", "en-US") - }, - new Dependency[] { }, - true, - "PackagePath", - "PackageHashPath" - ), - })); - - var targets = result.Should().HavePropertyAsObject("targets").Subject; - var target = targets.Should().HavePropertyAsObject("Target/runtime").Subject; - var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; - var resources = library.Should().HavePropertyAsObject("resources").Subject; - var resource = resources.Should().HavePropertyAsObject("en-US/Fruits.resources.dll").Subject; - resource.Should().HavePropertyValue("locale", "en-US"); - } - - - [Fact] - public void WritesResourceAssembliesForPortable() - { - var result = Save(Create( - "Target", - "runtime", - true, - runtimeLibraries: new[] - { - new RuntimeLibrary( - "package", - "PackageName", - "1.2.3", - "HASH", - new RuntimeAssetGroup[] { }, - new RuntimeAssetGroup[] { }, - new [] - { - new ResourceAssembly("en-US/Fruits.resources.dll", "en-US") - }, - new Dependency[] { }, - true, - "PackagePath", - "PackageHashPath" - ), - })); - - var targets = result.Should().HavePropertyAsObject("targets").Subject; - var target = targets.Should().HavePropertyAsObject("Target").Subject; - var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; - var resources = library.Should().HavePropertyAsObject("resources").Subject; - var resource = resources.Should().HavePropertyAsObject("en-US/Fruits.resources.dll").Subject; - resource.Should().HavePropertyValue("locale", "en-US"); - } - - [Fact] public void WriteCompilationOnlyAttributeIfOnlyCompilationLibraryProvided() { @@ -743,7 +713,6 @@ public void WriteCompilationOnlyAttributeIfOnlyCompilationLibraryProvided() library.Should().HavePropertyValue("compileOnly", true); } - [Fact] public void WritesCompilationOptions() { From 9a83ed6587ab3ea4b05e1a86c741ada043a656bd Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 24 Jul 2025 12:47:54 -0700 Subject: [PATCH 3/3] Update src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/ResourceAssembly.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs b/src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs index ce12fd74a840be..9916e19207f826 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs +++ b/src/libraries/Microsoft.Extensions.DependencyModel/src/ResourceAssembly.cs @@ -28,7 +28,7 @@ public ResourceAssembly(string path, string locale, string? localPath) public string Locale { get; set; } - // Depending on the source of the runtime file, this path may be relative to the + // Depending on the source of the resource assembly, this path may be relative to the // a referenced NuGet package's root or to the app/component root. public string Path { get; set; }