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
This commit is contained in:
parent
777e75f0a9
commit
852446e859
22 changed files with 519 additions and 15 deletions
12
TestAssets/TestProjects/ResourcesTests/TestApp/Program.cs
Normal file
12
TestAssets/TestProjects/ResourcesTests/TestApp/Program.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace ConsoleApplication
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
}
|
||||
}
|
||||
}
|
19
TestAssets/TestProjects/ResourcesTests/TestApp/project.json
Normal file
19
TestAssets/TestProjects/ResourcesTests/TestApp/project.json
Normal file
|
@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="hello" xml:space="preserve">
|
||||
<value>Bonjour!</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="hello" xml:space="preserve">
|
||||
<value>Hello World!</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.5.0-rc2-24008",
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard1.5": {
|
||||
"imports": "dnxcore50"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
"emitEntryPoint": true
|
||||
},
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.5.0-rc2-24015"
|
||||
"NETStandard.Library": "1.5.0-rc2-24008"
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandardapp1.5": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<LibraryAsset> 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<LibraryResourceAssembly> PopulateResources(TargetLibraryWithAssets library, IEnumerable<LockFileItem> 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<LibraryAsset> PopulateAssets(TargetLibraryWithAssets library, IEnumerable<LockFileItem> section)
|
||||
{
|
||||
foreach (var assemblyPath in section.Where(a => !PackageDependencyProvider.IsPlaceholderFile(a.Path)))
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace Microsoft.DotNet.ProjectModel
|
|||
|
||||
public string OutputExtension { get; }
|
||||
|
||||
public virtual IEnumerable<string> Resources()
|
||||
public virtual IEnumerable<ResourceFile> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,24 @@ namespace Microsoft.DotNet.ProjectModel.Graph
|
|||
{
|
||||
public class LockFileItem
|
||||
{
|
||||
public LockFileItem()
|
||||
{
|
||||
Properties = new Dictionary<string, string>();;
|
||||
}
|
||||
|
||||
public LockFileItem(string path) : this()
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public LockFileItem(string path, IDictionary<string, string> properties) : this(path)
|
||||
{
|
||||
Properties = new Dictionary<string, string>(properties);
|
||||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
public IDictionary<string, string> Properties { get; } = new Dictionary<string, string>();
|
||||
public IDictionary<string, string> Properties { get; }
|
||||
|
||||
public static implicit operator string (LockFileItem item) => item.Path;
|
||||
|
||||
|
|
19
src/Microsoft.DotNet.ProjectModel/ResourceFile.cs
Normal file
19
src/Microsoft.DotNet.ProjectModel/ResourceFile.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<LibraryAsset>(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();
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"$(RID)": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"dnxcore50": {
|
||||
"netcoreapp1.0": {
|
||||
"imports": [
|
||||
"portable-net45+win8"
|
||||
]
|
||||
|
|
|
@ -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<LockFileItem>()
|
||||
{
|
||||
new LockFileItem("resources/en-US/Res.dll", new Dictionary<string, string>() { { "locale", "en-US"} }),
|
||||
new LockFileItem("resources/ru-RU/Res.dll", new Dictionary<string, string>() { { "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()
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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<JProperty>())
|
||||
{
|
||||
var locale = item.Value["locale"];
|
||||
locale.Should().NotBeNull();
|
||||
|
||||
item.Name.Should().EndWith($"{locale}/{library.Item1}.resources.dll");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResourceTest()
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue