From 852446e859108c0f8e9e61c195dccda77a1a1886 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Fri, 15 Apr 2016 14:45:51 -0700 Subject: [PATCH] Improve resource file support (#2511) * Add satellite assemblies to deps file with locale data * Publish satellite assemblies to output during publish * Copy satellite assemblies from project-to-project dependencies on build and publish --- .../ResourcesTests/TestApp/.noautobuild | 0 .../ResourcesTests/TestApp/Program.cs | 12 ++ .../ResourcesTests/TestApp/project.json | 19 +++ .../TestLibraryWithResources/.noautobuild | 0 .../TestLibraryWithResources/Program.cs | 20 +++ .../TestLibraryWithResources/Strings.fr.resx | 123 ++++++++++++++++++ .../TestLibraryWithResources/Strings.resx | 123 ++++++++++++++++++ .../TestLibraryWithResources/project.json | 11 ++ .../project.json | 2 +- scripts/dotnet-cli-build/CompileTargets.cs | 5 +- .../Executable.cs | 10 ++ .../Compilation/LibraryExporter.cs | 22 +++- .../CompilationOutputFiles.cs | 6 +- .../Constants.cs | 2 + .../Graph/LockFileItem.cs | 17 ++- .../ResourceFile.cs | 19 +++ .../commands/dotnet-publish/PublishCommand.cs | 18 ++- .../framework/project.json.template | 2 +- .../LibraryExporterPackageTests.cs | 28 ++++ .../DependencyContextBuilderTests.cs | 18 +++ test/dotnet-build.Tests/BuildOutputTests.cs | 37 ++++++ .../PublishAppWithDependencies.cs | 40 ++++++ 22 files changed, 519 insertions(+), 15 deletions(-) create mode 100644 TestAssets/TestProjects/ResourcesTests/TestApp/.noautobuild create mode 100644 TestAssets/TestProjects/ResourcesTests/TestApp/Program.cs create mode 100644 TestAssets/TestProjects/ResourcesTests/TestApp/project.json create mode 100644 TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/.noautobuild create mode 100644 TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Program.cs create mode 100644 TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Strings.fr.resx create mode 100644 TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Strings.resx create mode 100644 TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/project.json create mode 100644 src/Microsoft.DotNet.ProjectModel/ResourceFile.cs diff --git a/TestAssets/TestProjects/ResourcesTests/TestApp/.noautobuild b/TestAssets/TestProjects/ResourcesTests/TestApp/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/ResourcesTests/TestApp/Program.cs b/TestAssets/TestProjects/ResourcesTests/TestApp/Program.cs new file mode 100644 index 000000000..51233cffa --- /dev/null +++ b/TestAssets/TestProjects/ResourcesTests/TestApp/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace ConsoleApplication +{ + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/TestAssets/TestProjects/ResourcesTests/TestApp/project.json b/TestAssets/TestProjects/ResourcesTests/TestApp/project.json new file mode 100644 index 000000000..68470fbf4 --- /dev/null +++ b/TestAssets/TestProjects/ResourcesTests/TestApp/project.json @@ -0,0 +1,19 @@ +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "Microsoft.Data.OData": "5.6.4", + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0-rc2-24008" + }, + "TestLibraryWithResources": { "target": "project" } + }, + "frameworks": { + "netcoreapp1.0": { + "imports": [ "portable-net45+win8" ] + } + } +} diff --git a/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/.noautobuild b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Program.cs b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Program.cs new file mode 100644 index 000000000..3b5a088e1 --- /dev/null +++ b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Program.cs @@ -0,0 +1,20 @@ +using System; +using System.Resources; +using System.Reflection; +using System.Globalization; + +namespace TestProjectWithCultureSpecificResource +{ + public class Program + { + public static void Main(string[] args) + { + var rm = new ResourceManager( + "TestProjectWithCultureSpecificResource.Strings", + typeof(Program).GetTypeInfo().Assembly); + + Console.WriteLine(rm.GetString("hello")); + Console.WriteLine(rm.GetString("hello", new CultureInfo("fr"))); + } + } +} diff --git a/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Strings.fr.resx b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Strings.fr.resx new file mode 100644 index 000000000..f569847fa --- /dev/null +++ b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Strings.fr.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bonjour! + + \ No newline at end of file diff --git a/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Strings.resx b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Strings.resx new file mode 100644 index 000000000..bd249e209 --- /dev/null +++ b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/Strings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Hello World! + + \ No newline at end of file diff --git a/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/project.json b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/project.json new file mode 100644 index 000000000..6e68954d6 --- /dev/null +++ b/TestAssets/TestProjects/ResourcesTests/TestLibraryWithResources/project.json @@ -0,0 +1,11 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24008", + }, + "frameworks": { + "netstandard1.5": { + "imports": "dnxcore50" + } + } +} diff --git a/TestAssets/TestProjects/TestProjectWithCultureSpecificResource/project.json b/TestAssets/TestProjects/TestProjectWithCultureSpecificResource/project.json index 9ab4c5256..4b1d0a3e8 100644 --- a/TestAssets/TestProjects/TestProjectWithCultureSpecificResource/project.json +++ b/TestAssets/TestProjects/TestProjectWithCultureSpecificResource/project.json @@ -4,7 +4,7 @@ "emitEntryPoint": true }, "dependencies": { - "NETStandard.Library": "1.5.0-rc2-24015" + "NETStandard.Library": "1.5.0-rc2-24008" }, "frameworks": { "netstandardapp1.5": { diff --git a/scripts/dotnet-cli-build/CompileTargets.cs b/scripts/dotnet-cli-build/CompileTargets.cs index 267cf7e49..e562d5f65 100644 --- a/scripts/dotnet-cli-build/CompileTargets.cs +++ b/scripts/dotnet-cli-build/CompileTargets.cs @@ -343,7 +343,7 @@ namespace Microsoft.DotNet.Cli.Build } string SharedFrameworkSourceRoot = GenerateSharedFrameworkProject(c, SharedFrameworkTemplateSourceRoot, sharedFrameworkRid); - + dotnetCli.Restore("--verbosity", "verbose", "--disable-parallel", "--infer-runtimes", "--fallbacksource", Dirs.Corehost) .WorkingDirectory(SharedFrameworkSourceRoot) .Execute() @@ -358,12 +358,9 @@ namespace Microsoft.DotNet.Cli.Build Utils.DeleteDirectory(SharedFrameworkNameAndVersionRoot); } - string publishFramework = "dnxcore50"; // Temporary, use "netcoreapp" when we update nuget. - dotnetCli.Publish( "--output", SharedFrameworkNameAndVersionRoot, "-r", sharedFrameworkRid, - "-f", publishFramework, SharedFrameworkSourceRoot).Execute().EnsureSuccessful(); // Clean up artifacts that dotnet-publish generates which we don't need diff --git a/src/Microsoft.DotNet.Compiler.Common/Executable.cs b/src/Microsoft.DotNet.Compiler.Common/Executable.cs index 38a1b79d2..f5d6c7763 100644 --- a/src/Microsoft.DotNet.Compiler.Common/Executable.cs +++ b/src/Microsoft.DotNet.Compiler.Common/Executable.cs @@ -97,6 +97,16 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common { libraryExport.RuntimeAssemblyGroups.GetDefaultAssets().CopyTo(_runtimeOutputPath); libraryExport.NativeLibraryGroups.GetDefaultAssets().CopyTo(_runtimeOutputPath); + + foreach(var group in libraryExport.ResourceAssemblies.GroupBy(r => r.Locale)) + { + var localeSpecificDir = Path.Combine(_runtimeOutputPath, group.Key); + if(!Directory.Exists(localeSpecificDir)) + { + Directory.CreateDirectory(localeSpecificDir); + } + group.Select(r => r.Asset).CopyTo(localeSpecificDir); + } } } diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs index f9e25ce98..5966f46e8 100644 --- a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs @@ -196,6 +196,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation var builder = LibraryExportBuilder.Create(library); builder.AddNativeLibraryGroup(new LibraryAssetGroup(PopulateAssets(library, library.NativeLibraries))); builder.AddRuntimeAssemblyGroup(new LibraryAssetGroup(PopulateAssets(library, library.RuntimeAssemblies))); + builder.WithResourceAssemblies(PopulateResources(library, library.ResourceAssemblies)); builder.WithCompilationAssemblies(PopulateAssets(library, library.CompileTimeAssemblies)); if (library.Identity.Type.Equals(LibraryType.Package)) @@ -322,6 +323,10 @@ namespace Microsoft.DotNet.ProjectModel.Compilation builder.AddRuntimeAssemblyGroup(new LibraryAssetGroup(new[] { compilationAssemblyAsset })); builder.WithRuntimeAssets(CollectAssets(outputPaths.CompilationFiles)); } + + builder.WithResourceAssemblies(outputPaths.CompilationFiles.Resources().Select(r => new LibraryResourceAssembly( + LibraryAsset.CreateFromAbsolutePath(outputPaths.CompilationFiles.BasePath, r.Path), + r.Locale))); } builder.WithSourceReferences(project.Project.Files.SharedFiles.Select(f => @@ -334,7 +339,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation private IEnumerable CollectAssets(CompilationOutputFiles files) { var assemblyPath = files.Assembly; - foreach (var path in files.All()) + foreach (var path in files.All().Except(files.Resources().Select(r => r.Path))) { if (string.Equals(assemblyPath, path)) { @@ -458,6 +463,21 @@ namespace Microsoft.DotNet.ProjectModel.Compilation return analyzerRefs; } + private IEnumerable PopulateResources(TargetLibraryWithAssets library, IEnumerable section) + { + foreach (var assemblyPath in section.Where(a => !PackageDependencyProvider.IsPlaceholderFile(a.Path))) + { + string locale; + if(!assemblyPath.Properties.TryGetValue(Constants.LocaleLockFilePropertyName, out locale)) + { + locale = null; + } + yield return new LibraryResourceAssembly( + LibraryAsset.CreateFromRelativePath(library.Path, assemblyPath.Path), + locale); + } + } + private IEnumerable PopulateAssets(TargetLibraryWithAssets library, IEnumerable section) { foreach (var assemblyPath in section.Where(a => !PackageDependencyProvider.IsPlaceholderFile(a.Path))) diff --git a/src/Microsoft.DotNet.ProjectModel/CompilationOutputFiles.cs b/src/Microsoft.DotNet.ProjectModel/CompilationOutputFiles.cs index ddfbf5b86..90eeec5a7 100644 --- a/src/Microsoft.DotNet.ProjectModel/CompilationOutputFiles.cs +++ b/src/Microsoft.DotNet.ProjectModel/CompilationOutputFiles.cs @@ -56,7 +56,7 @@ namespace Microsoft.DotNet.ProjectModel public string OutputExtension { get; } - public virtual IEnumerable Resources() + public virtual IEnumerable Resources() { var resourceNames = Project.Files.ResourceFiles .Select(f => ResourceUtility.GetResourceCultureName(f.Key)) @@ -65,7 +65,7 @@ namespace Microsoft.DotNet.ProjectModel foreach (var resourceName in resourceNames) { - yield return Path.Combine(BasePath, resourceName, Project.Name + ".resources" + FileNameSuffixes.DotNet.DynamicLib); + yield return new ResourceFile(Path.Combine(BasePath, resourceName, Project.Name + ".resources" + FileNameSuffixes.DotNet.DynamicLib), resourceName); } } @@ -80,7 +80,7 @@ namespace Microsoft.DotNet.ProjectModel } foreach (var resource in Resources()) { - yield return resource; + yield return resource.Path; } } } diff --git a/src/Microsoft.DotNet.ProjectModel/Constants.cs b/src/Microsoft.DotNet.ProjectModel/Constants.cs index 92c06fc15..5b0100a00 100644 --- a/src/Microsoft.DotNet.ProjectModel/Constants.cs +++ b/src/Microsoft.DotNet.ProjectModel/Constants.cs @@ -10,6 +10,8 @@ namespace Microsoft.DotNet.ProjectModel public static readonly string DefaultOutputDirectory = "bin"; public static readonly string DefaultConfiguration = "Debug"; + public static readonly string LocaleLockFilePropertyName = "locale"; + public static readonly Version Version50 = new Version(5, 0); } } diff --git a/src/Microsoft.DotNet.ProjectModel/Graph/LockFileItem.cs b/src/Microsoft.DotNet.ProjectModel/Graph/LockFileItem.cs index c147dfe21..fcd040ede 100644 --- a/src/Microsoft.DotNet.ProjectModel/Graph/LockFileItem.cs +++ b/src/Microsoft.DotNet.ProjectModel/Graph/LockFileItem.cs @@ -7,9 +7,24 @@ namespace Microsoft.DotNet.ProjectModel.Graph { public class LockFileItem { + public LockFileItem() + { + Properties = new Dictionary();; + } + + public LockFileItem(string path) : this() + { + Path = path; + } + + public LockFileItem(string path, IDictionary properties) : this(path) + { + Properties = new Dictionary(properties); + } + public string Path { get; set; } - public IDictionary Properties { get; } = new Dictionary(); + public IDictionary Properties { get; } public static implicit operator string (LockFileItem item) => item.Path; diff --git a/src/Microsoft.DotNet.ProjectModel/ResourceFile.cs b/src/Microsoft.DotNet.ProjectModel/ResourceFile.cs new file mode 100644 index 000000000..73518481b --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/ResourceFile.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.ProjectModel +{ + public class ResourceFile + { + public string Path { get; } + public string Locale { get; } + + public ResourceFile(string path, string locale) + { + Path = path; + Locale = locale; + } + } +} diff --git a/src/dotnet/commands/dotnet-publish/PublishCommand.cs b/src/dotnet/commands/dotnet-publish/PublishCommand.cs index f035550f2..8e14cd154 100644 --- a/src/dotnet/commands/dotnet-publish/PublishCommand.cs +++ b/src/dotnet/commands/dotnet-publish/PublishCommand.cs @@ -147,6 +147,16 @@ namespace Microsoft.DotNet.Tools.Publish var runtimeAssetsToCopy = export.RuntimeAssets.Where(a => ShouldCopyExportRuntimeAsset(context, buildOutputPaths, export, a)); runtimeAssetsToCopy.StructuredCopyTo(outputPath, outputPaths.IntermediateOutputDirectoryPath); + + foreach(var resourceAsset in export.ResourceAssemblies) + { + var dir = Path.Combine(outputPath, resourceAsset.Locale); + if(!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + File.Copy(resourceAsset.Asset.ResolvedPath, Path.Combine(dir, resourceAsset.Asset.FileName), overwrite: true); + } } if (context.ProjectFile.HasRuntimeOutput(configuration) && !context.TargetFramework.IsDesktop()) @@ -281,7 +291,7 @@ namespace Microsoft.DotNet.Tools.Publish { Directory.CreateDirectory(refsPath); } - + // Do not copy compilation assembly if it's in runtime assemblies var runtimeAssemblies = new HashSet(export.RuntimeAssemblyGroups.GetDefaultAssets()); foreach (var compilationAssembly in export.CompilationAssemblies) @@ -343,7 +353,7 @@ namespace Microsoft.DotNet.Tools.Publish return hostFile; } } - + Reporter.Verbose.WriteLine($"failed to resolve published host in: {outputPath}"); return null; } @@ -416,12 +426,12 @@ namespace Microsoft.DotNet.Tools.Publish ProjectContext.CreateContextForEachFramework(projectPath) : new[] { ProjectContext.Create(projectPath, framework) }; - var runtimes = !string.IsNullOrEmpty(runtime) ? + var runtimes = !string.IsNullOrEmpty(runtime) ? new [] {runtime} : PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers(); return allContexts.Select(c => c.CreateRuntimeContext(runtimes)); } - + private static void CopyContents(ProjectContext context, string outputPath) { var contentFiles = context.ProjectFile.Files.GetContentFiles(); diff --git a/src/sharedframework/framework/project.json.template b/src/sharedframework/framework/project.json.template index fde657ccc..d458d4322 100644 --- a/src/sharedframework/framework/project.json.template +++ b/src/sharedframework/framework/project.json.template @@ -10,7 +10,7 @@ "$(RID)": {} }, "frameworks": { - "dnxcore50": { + "netcoreapp1.0": { "imports": [ "portable-net45+win8" ] diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs b/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs index 0a555e7b0..ea5571cc2 100644 --- a/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs +++ b/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs @@ -126,6 +126,34 @@ namespace Microsoft.DotNet.ProjectModel.Tests runtimeAsset.ResolvedPath.Should().Be(Path.Combine(PackagePath, "lib/Something.OSX.dll")); } + [Fact] + public void ExportsPackageResourceAssemblies() + { + var description = CreateDescription( + new LockFileTargetLibrary() + { + ResourceAssemblies = new List() + { + new LockFileItem("resources/en-US/Res.dll", new Dictionary() { { "locale", "en-US"} }), + new LockFileItem("resources/ru-RU/Res.dll", new Dictionary() { { "locale", "ru-RU" } }), + } + }); + + var result = ExportSingle(description); + result.ResourceAssemblies.Should().HaveCount(2); + var asset = result.ResourceAssemblies.Should().Contain(g => g.Locale == "en-US").Subject.Asset; + asset.Name.Should().Be("Res"); + asset.Transform.Should().BeNull(); + asset.RelativePath.Should().Be("resources/en-US/Res.dll"); + asset.ResolvedPath.Should().Be(Path.Combine(PackagePath, "resources/en-US/Res.dll")); + + asset = result.ResourceAssemblies.Should().Contain(g => g.Locale == "ru-RU").Subject.Asset; + asset.Name.Should().Be("Res"); + asset.Transform.Should().BeNull(); + asset.RelativePath.Should().Be("resources/ru-RU/Res.dll"); + asset.ResolvedPath.Should().Be(Path.Combine(PackagePath, "resources/ru-RU/Res.dll")); + } + [Fact] public void ExportsSources() { diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextBuilderTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextBuilderTests.cs index 45966e37c..987adbc10 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextBuilderTests.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextBuilderTests.cs @@ -252,6 +252,24 @@ namespace Microsoft.Extensions.DependencyModel.Tests asm.Assemblies.Should().OnlyContain(a => a == "System.Collections.dll"); } + [Fact] + public void FillsResources() + { + var context = Build(runtimeExports: new[] + { + Export(PackageDescription("Pack.Age", version: new NuGetVersion(1, 2, 3)), + resourceAssemblies: new [] + { + new LibraryResourceAssembly(new LibraryAsset("Dll", "resources/en-US/Pack.Age.dll", ""), "en-US") + }) + }); + + context.RuntimeLibraries.Should().HaveCount(1); + + var lib = context.RuntimeLibraries.Should().Contain(l => l.Name == "Pack.Age").Subject; + lib.ResourceAssemblies.Should().OnlyContain(l => l.Locale == "en-US" && l.Path == "resources/en-US/Pack.Age.dll"); + } + [Fact] public void ReferenceAssembliesPathRelativeToDefaultRoot() { diff --git a/test/dotnet-build.Tests/BuildOutputTests.cs b/test/dotnet-build.Tests/BuildOutputTests.cs index bf294f5b3..90125de87 100644 --- a/test/dotnet-build.Tests/BuildOutputTests.cs +++ b/test/dotnet-build.Tests/BuildOutputTests.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -8,6 +9,7 @@ using FluentAssertions; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.Tools.Test.Utilities; using Microsoft.Extensions.PlatformAbstractions; +using Newtonsoft.Json.Linq; using NuGet.Frameworks; using Xunit; @@ -212,6 +214,41 @@ namespace Microsoft.DotNet.Tools.Builder.Tests } } + [Fact] + public void PackageReferenceWithResourcesTest() + { + var testInstance = TestAssetsManager.CreateTestInstance("ResourcesTests") + .WithLockFiles(); + + var projectRoot = Path.Combine(testInstance.TestRoot, "TestApp"); + + var cmd = new BuildCommand(projectRoot); + var result = cmd.Execute(); + result.Should().Pass(); + + var outputDir = new DirectoryInfo(Path.Combine(projectRoot, "bin", "Debug", "netcoreapp1.0")); + + outputDir.Should().HaveFile("TestLibraryWithResources.dll"); + outputDir.Sub("fr").Should().HaveFile("TestLibraryWithResources.resources.dll"); + + var depsJson = JObject.Parse(File.ReadAllText(Path.Combine(outputDir.FullName, $"{Path.GetFileNameWithoutExtension(cmd.GetOutputExecutableName())}.deps.json"))); + + foreach (var library in new[] { Tuple.Create("Microsoft.Data.OData", "5.6.4"), Tuple.Create("TestLibraryWithResources", "1.0.0") }) + { + var resources = depsJson["targets"][".NETCoreApp,Version=v1.0"][library.Item1 + "/" + library.Item2]["resources"]; + + resources.Should().NotBeNull(); + + foreach (var item in resources.Children()) + { + var locale = item.Value["locale"]; + locale.Should().NotBeNull(); + + item.Name.Should().EndWith($"{locale}/{library.Item1}.resources.dll"); + } + } + } + [Fact] public void ResourceTest() { diff --git a/test/dotnet-publish.Tests/PublishAppWithDependencies.cs b/test/dotnet-publish.Tests/PublishAppWithDependencies.cs index df2907f82..244692b17 100644 --- a/test/dotnet-publish.Tests/PublishAppWithDependencies.cs +++ b/test/dotnet-publish.Tests/PublishAppWithDependencies.cs @@ -42,5 +42,45 @@ namespace Microsoft.DotNet.Tools.Publish.Tests .Should() .HaveFile("config.xml"); } + + [Fact] + public void PublishTestAppWithReferencesToResources() + { + var testInstance = TestAssetsManager.CreateTestInstance("ResourcesTests") + .WithLockFiles(); + + var projectRoot = Path.Combine(testInstance.TestRoot, "TestApp"); + + var publishCommand = new PublishCommand(projectRoot); + var publishResult = publishCommand.Execute(); + + publishResult.Should().Pass(); + + var publishDir = publishCommand.GetOutputDirectory(portable: true); + + publishDir.Should().HaveFiles(new[] + { + "TestApp.dll", + "TestApp.deps.json" + }); + + foreach (var culture in new[] { "de", "es", "fr", "it", "ja", "ko", "ru", "zh-Hans", "zh-Hant" }) + { + var cultureDir = publishDir.Sub(culture); + + // Provided by packages + cultureDir.Should().HaveFiles(new[] { + "Microsoft.Data.Edm.resources.dll", + "Microsoft.Data.OData.resources.dll", + "System.Spatial.resources.dll" + }); + + // Check for the project-to-project one + if (culture == "fr") + { + cultureDir.Should().HaveFile("TestLibraryWithResources.resources.dll"); + } + } + } } }