Add resource satelite assembly generation

This commit is contained in:
Pavel Krymets 2015-12-14 08:33:11 -08:00
parent aebc89683e
commit 93158dc1be
20 changed files with 930 additions and 111 deletions

View file

@ -3,7 +3,7 @@
namespace Microsoft.DotNet.Cli.Utils namespace Microsoft.DotNet.Cli.Utils
{ {
public static class AnsiColorExtensions internal static class AnsiColorExtensions
{ {
public static string Black(this string text) public static string Black(this string text)
{ {

View file

@ -15,7 +15,7 @@ using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli.Utils namespace Microsoft.DotNet.Cli.Utils
{ {
public class Command internal class Command
{ {
private readonly Process _process; private readonly Process _process;
private readonly StreamForwarder _stdOut; private readonly StreamForwarder _stdOut;

View file

@ -5,7 +5,7 @@ using System.IO;
namespace Microsoft.DotNet.Cli.Utils namespace Microsoft.DotNet.Cli.Utils
{ {
public struct CommandResult internal struct CommandResult
{ {
public static readonly CommandResult Empty = new CommandResult(); public static readonly CommandResult Empty = new CommandResult();

View file

@ -0,0 +1,439 @@
using System;
using System.Collections.Generic;
namespace Microsoft.DotNet.Cli.Compiler.Common
{
/// <summary>
/// Contains a list of known culture names that can be used to create a <see cref="System.Globalization.CultureInfo"/>.
/// </summary>
internal static class CultureInfoCache
{
/// <summary>
/// This list of known cultures was generated by CultureInfoGenerator using .NET Framework 4.6 RC or later on
/// Microsoft Windows NT 6.2.9200.0.
/// As new versions of .NET Framework and Windows are released, this list should be regenerated to ensure it
/// contains the latest culture names.
/// </summary>
public static readonly HashSet<string> KnownCultureNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"",
"af",
"af-ZA",
"am",
"am-ET",
"ar",
"ar-AE",
"ar-BH",
"ar-DZ",
"ar-EG",
"ar-IQ",
"ar-JO",
"ar-KW",
"ar-LB",
"ar-LY",
"ar-MA",
"ar-OM",
"ar-QA",
"ar-SA",
"ar-SY",
"ar-TN",
"ar-YE",
"arn",
"arn-CL",
"as",
"as-IN",
"az",
"az-Cyrl",
"az-Cyrl-AZ",
"az-Latn",
"az-Latn-AZ",
"ba",
"ba-RU",
"be",
"be-BY",
"bg",
"bg-BG",
"bn",
"bn-BD",
"bn-IN",
"bo",
"bo-CN",
"br",
"br-FR",
"bs",
"bs-Cyrl",
"bs-Cyrl-BA",
"bs-Latn",
"bs-Latn-BA",
"ca",
"ca-ES",
"ca-ES-valencia",
"chr",
"chr-Cher",
"chr-Cher-US",
"co",
"co-FR",
"cs",
"cs-CZ",
"cy",
"cy-GB",
"da",
"da-DK",
"de",
"de-AT",
"de-CH",
"de-DE",
"de-LI",
"de-LU",
"dsb",
"dsb-DE",
"dv",
"dv-MV",
"el",
"el-GR",
"en",
"en-029",
"en-AU",
"en-BZ",
"en-CA",
"en-GB",
"en-HK",
"en-IE",
"en-IN",
"en-JM",
"en-MY",
"en-NZ",
"en-PH",
"en-SG",
"en-TT",
"en-US",
"en-ZA",
"en-ZW",
"es",
"es-419",
"es-AR",
"es-BO",
"es-CL",
"es-CO",
"es-CR",
"es-DO",
"es-EC",
"es-ES",
"es-GT",
"es-HN",
"es-MX",
"es-NI",
"es-PA",
"es-PE",
"es-PR",
"es-PY",
"es-SV",
"es-US",
"es-UY",
"es-VE",
"et",
"et-EE",
"eu",
"eu-ES",
"fa",
"fa-IR",
"ff",
"ff-Latn",
"ff-Latn-SN",
"fi",
"fi-FI",
"fil",
"fil-PH",
"fo",
"fo-FO",
"fr",
"fr-BE",
"fr-CA",
"fr-CD",
"fr-CH",
"fr-CI",
"fr-CM",
"fr-FR",
"fr-HT",
"fr-LU",
"fr-MA",
"fr-MC",
"fr-ML",
"fr-RE",
"fr-SN",
"fy",
"fy-NL",
"ga",
"ga-IE",
"gd",
"gd-GB",
"gl",
"gl-ES",
"gn",
"gn-PY",
"gsw",
"gsw-FR",
"gu",
"gu-IN",
"ha",
"ha-Latn",
"ha-Latn-NG",
"haw",
"haw-US",
"he",
"he-IL",
"hi",
"hi-IN",
"hr",
"hr-BA",
"hr-HR",
"hsb",
"hsb-DE",
"hu",
"hu-HU",
"hy",
"hy-AM",
"id",
"id-ID",
"ig",
"ig-NG",
"ii",
"ii-CN",
"is",
"is-IS",
"it",
"it-CH",
"it-IT",
"iu",
"iu-Cans",
"iu-Cans-CA",
"iu-Latn",
"iu-Latn-CA",
"ja",
"ja-JP",
"jv",
"jv-Latn",
"jv-Latn-ID",
"ka",
"ka-GE",
"kk",
"kk-KZ",
"kl",
"kl-GL",
"km",
"km-KH",
"kn",
"kn-IN",
"ko",
"ko-KR",
"kok",
"kok-IN",
"ku",
"ku-Arab",
"ku-Arab-IQ",
"ky",
"ky-KG",
"lb",
"lb-LU",
"lo",
"lo-LA",
"lt",
"lt-LT",
"lv",
"lv-LV",
"mg",
"mg-MG",
"mi",
"mi-NZ",
"mk",
"mk-MK",
"ml",
"ml-IN",
"mn",
"mn-Cyrl",
"mn-MN",
"mn-Mong",
"mn-Mong-CN",
"mn-Mong-MN",
"moh",
"moh-CA",
"mr",
"mr-IN",
"ms",
"ms-BN",
"ms-MY",
"mt",
"mt-MT",
"my",
"my-MM",
"nb",
"nb-NO",
"ne",
"ne-IN",
"ne-NP",
"nl",
"nl-BE",
"nl-NL",
"nn",
"nn-NO",
"no",
"nqo",
"nqo-GN",
"nso",
"nso-ZA",
"oc",
"oc-FR",
"om",
"om-ET",
"or",
"or-IN",
"pa",
"pa-Arab",
"pa-Arab-PK",
"pa-IN",
"pl",
"pl-PL",
"prs",
"prs-AF",
"ps",
"ps-AF",
"pt",
"pt-AO",
"pt-BR",
"pt-PT",
"qut",
"qut-GT",
"quz",
"quz-BO",
"quz-EC",
"quz-PE",
"rm",
"rm-CH",
"ro",
"ro-MD",
"ro-RO",
"ru",
"ru-RU",
"rw",
"rw-RW",
"sa",
"sa-IN",
"sah",
"sah-RU",
"sd",
"sd-Arab",
"sd-Arab-PK",
"se",
"se-FI",
"se-NO",
"se-SE",
"si",
"si-LK",
"sk",
"sk-SK",
"sl",
"sl-SI",
"sma",
"sma-NO",
"sma-SE",
"smj",
"smj-NO",
"smj-SE",
"smn",
"smn-FI",
"sms",
"sms-FI",
"sn",
"sn-Latn",
"sn-Latn-ZW",
"so",
"so-SO",
"sq",
"sq-AL",
"sr",
"sr-Cyrl",
"sr-Cyrl-BA",
"sr-Cyrl-CS",
"sr-Cyrl-ME",
"sr-Cyrl-RS",
"sr-Latn",
"sr-Latn-BA",
"sr-Latn-CS",
"sr-Latn-ME",
"sr-Latn-RS",
"st",
"st-ZA",
"sv",
"sv-FI",
"sv-SE",
"sw",
"sw-KE",
"syr",
"syr-SY",
"ta",
"ta-IN",
"ta-LK",
"te",
"te-IN",
"tg",
"tg-Cyrl",
"tg-Cyrl-TJ",
"th",
"th-TH",
"ti",
"ti-ER",
"ti-ET",
"tk",
"tk-TM",
"tn",
"tn-BW",
"tn-ZA",
"tr",
"tr-TR",
"ts",
"ts-ZA",
"tt",
"tt-RU",
"tzm",
"tzm-Latn",
"tzm-Latn-DZ",
"tzm-Tfng",
"tzm-Tfng-MA",
"ug",
"ug-CN",
"uk",
"uk-UA",
"ur",
"ur-IN",
"ur-PK",
"uz",
"uz-Cyrl",
"uz-Cyrl-UZ",
"uz-Latn",
"uz-Latn-UZ",
"vi",
"vi-VN",
"wo",
"wo-SN",
"xh",
"xh-ZA",
"yo",
"yo-NG",
"zgh",
"zgh-Tfng",
"zgh-Tfng-MA",
"zh",
"zh-CN",
"zh-Hans",
"zh-Hant",
"zh-HK",
"zh-MO",
"zh-SG",
"zh-TW",
"zu",
"zu-ZA",
"zh-CHS",
"zh-CHT"
};
}
}

View file

@ -0,0 +1,79 @@
// 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.Collections.Generic;
using Microsoft.DotNet.Tools.Common;
namespace Microsoft.DotNet.Cli.Compiler.Common
{
public static class ResourceUtility
{
public static string GetResourceName(string projectFolder, string resourcePath)
{
// If the file is outside of the project folder, we are assuming it is directly in the root
// otherwise, keep the folders that are inside the project
return PathUtility.IsChildOfDirectory(projectFolder, resourcePath) ?
PathUtility.GetRelativePath(projectFolder, resourcePath) :
Path.GetFileName(resourcePath);
}
public static bool IsResourceFile(string fileName)
{
var ext = Path.GetExtension(fileName);
return
IsResxFile(fileName) ||
string.Equals(ext, ".restext", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ext, ".resources", StringComparison.OrdinalIgnoreCase);
}
public static bool IsResxFile(string fileName)
{
var ext = Path.GetExtension(fileName);
return string.Equals(ext, ".resx", StringComparison.OrdinalIgnoreCase);
}
public static IEnumerable<string> GetResourcesForCulture(string cultureName, IList<string> resources)
{
var resourcesByCultureName = resources
.GroupBy(GetResourceCultureName, StringComparer.OrdinalIgnoreCase);
if (string.Equals(cultureName, "neutral", StringComparison.OrdinalIgnoreCase))
{
cultureName = string.Empty;
}
return resourcesByCultureName
.SingleOrDefault(grouping => string.Equals(grouping.Key, cultureName, StringComparison.OrdinalIgnoreCase));
}
public static string GetResourceCultureName(string res)
{
var ext = Path.GetExtension(res);
if (IsResourceFile(res))
{
var resourceBaseName = Path.GetFileNameWithoutExtension(Path.GetFileName(res));
var cultureName = Path.GetExtension(resourceBaseName);
if (string.IsNullOrEmpty(cultureName) || cultureName.Length < 3)
{
return string.Empty;
}
// Path.Extension adds a . to the file extension name; example - ".resources". Removing the "." with Substring
cultureName = cultureName.Substring(1);
if (CultureInfoCache.KnownCultureNames.Contains(cultureName))
{
return cultureName;
}
}
return string.Empty;
}
}
}

View file

@ -5,8 +5,11 @@
"NETStandard.Library": "1.0.0-rc2-23608", "NETStandard.Library": "1.0.0-rc2-23608",
"System.Linq": "4.0.1-rc2-23608", "System.Linq": "4.0.1-rc2-23608",
"System.CommandLine": "0.1.0-*", "System.CommandLine": "0.1.0-*",
"Microsoft.DotNet.ProjectModel": "1.0.0-*",
"Microsoft.DotNet.ProjectModel": "1.0.0-*" "Microsoft.DotNet.Cli.Utils": {
"type": "build",
"version": "1.0.0-*"
},
}, },
"frameworks": { "frameworks": {
"dnxcore50": { } "dnxcore50": { }

View file

@ -13,8 +13,8 @@ using Microsoft.DotNet.Cli.Compiler.Common;
using Microsoft.DotNet.Tools.Common; using Microsoft.DotNet.Tools.Common;
using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Compilation; using Microsoft.DotNet.ProjectModel.Compilation;
using NuGet.Frameworks;
using Microsoft.DotNet.ProjectModel.Utilities; using Microsoft.DotNet.ProjectModel.Utilities;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Tools.Compiler namespace Microsoft.DotNet.Tools.Compiler
{ {
@ -380,6 +380,11 @@ namespace Microsoft.DotNet.Tools.Compiler
var success = result.ExitCode == 0; var success = result.ExitCode == 0;
if (success)
{
success &= GenerateResourceAssemblies(context.ProjectFile, dependencies, outputPath, configuration);
}
if (success && !noHost && compilationOptions.EmitEntryPoint.GetValueOrDefault()) if (success && !noHost && compilationOptions.EmitEntryPoint.GetValueOrDefault())
{ {
var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current }); var runtimeContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current });
@ -628,52 +633,38 @@ namespace Microsoft.DotNet.Tools.Compiler
private static bool AddResources(Project project, List<string> compilerArgs, string intermediateOutputPath) private static bool AddResources(Project project, List<string> compilerArgs, string intermediateOutputPath)
{ {
string root = PathUtility.EnsureTrailingSlash(project.ProjectDirectory);
foreach (var resourceFile in project.Files.ResourceFiles) foreach (var resourceFile in project.Files.ResourceFiles)
{ {
string resourceName = null;
string rootNamespace = null;
var resourcePath = resourceFile.Key; var resourcePath = resourceFile.Key;
if (string.IsNullOrEmpty(resourceFile.Value)) if (!string.IsNullOrEmpty(ResourceUtility.GetResourceCultureName(resourcePath)))
{ {
// No logical name, so use the file name // Include only "neutral" resources into main assembly
resourceName = ResourcePathUtility.GetResourceName(root, resourcePath); continue;
rootNamespace = project.Name;
}
else
{
resourceName = CreateCSharpManifestResourceName.EnsureResourceExtension(resourceFile.Value, resourcePath);
rootNamespace = null;
} }
var name = CreateCSharpManifestResourceName.CreateManifestName(resourceName, rootNamespace); var name = GetResourceFileMetadataName(project, resourceFile);
var fileName = resourcePath; var fileName = resourcePath;
if (ResourcePathUtility.IsResxResourceFile(fileName)) if (ResourceUtility.IsResxFile(fileName))
{ {
var ext = Path.GetExtension(fileName); // {file}.resx -> {file}.resources
var resourcesFile = Path.Combine(intermediateOutputPath, name);
if (string.Equals(ext, ".resx", StringComparison.OrdinalIgnoreCase)) var result = Command.Create("dotnet-resgen", $"\"{fileName}\" -o \"{resourcesFile}\"")
.ForwardStdErr()
.ForwardStdOut()
.Execute();
if (result.ExitCode != 0)
{ {
// {file}.resx -> {file}.resources return false;
var resourcesFile = Path.Combine(intermediateOutputPath, name);
var result = Command.Create("dotnet-resgen", $"\"{fileName}\" \"{resourcesFile}\"")
.ForwardStdErr()
.ForwardStdOut()
.Execute();
if (result.ExitCode != 0)
{
return false;
}
// Use this as the resource name instead
fileName = resourcesFile;
} }
// Use this as the resource name instead
fileName = resourcesFile;
} }
compilerArgs.Add($"--resource:\"{fileName}\",{name}"); compilerArgs.Add($"--resource:\"{fileName}\",{name}");
@ -682,6 +673,71 @@ namespace Microsoft.DotNet.Tools.Compiler
return true; return true;
} }
private static string GetResourceFileMetadataName(Project project, KeyValuePair<string, string> resourceFile)
{
string resourceName = null;
string rootNamespace = null;
string root = PathUtility.EnsureTrailingSlash(project.ProjectDirectory);
string resourcePath = resourceFile.Key;
if (string.IsNullOrEmpty(resourceFile.Value))
{
// No logical name, so use the file name
resourceName = ResourceUtility.GetResourceName(root, resourcePath);
rootNamespace = project.Name;
}
else
{
resourceName = ResourceManifestName.EnsureResourceExtension(resourceFile.Value, resourcePath);
rootNamespace = null;
}
var name = ResourceManifestName.CreateManifestName(resourceName, rootNamespace);
return name;
}
private static bool GenerateResourceAssemblies(Project project, List<LibraryExport> dependencies, string outputPath, string configuration)
{
var references = dependencies.SelectMany(libraryExport => libraryExport.CompilationAssemblies);
foreach (var resourceFileGroup in project.Files.ResourceFiles.GroupBy(file => ResourceUtility.GetResourceCultureName(file.Key)))
{
var culture = resourceFileGroup.Key;
if (!string.IsNullOrEmpty(culture))
{
var resourceOutputPath = Path.Combine(outputPath, culture);
if (!Directory.Exists(resourceOutputPath))
{
Directory.CreateDirectory(resourceOutputPath);
}
var resourceOuputFile = Path.Combine(resourceOutputPath, project.Name + ".resources.dll");
var arguments = new List<string>();
arguments.AddRange(references.Select(r => $"-r \"{r.ResolvedPath}\""));
arguments.Add($"-o \"{resourceOuputFile}\"");
arguments.Add($"-c {culture}");
foreach (var resourceFile in resourceFileGroup)
{
var metadataName = GetResourceFileMetadataName(project, resourceFile);
arguments.Add($"\"{resourceFile.Key}\",{metadataName}");
}
var result = Command.Create("dotnet-resgen", arguments)
.ForwardStdErr()
.ForwardStdOut()
.Execute();
if (result.ExitCode != 0)
{
return false;
}
}
}
return true;
}
private static ISet<ProjectDescription> Sort(Dictionary<string, ProjectDescription> projects) private static ISet<ProjectDescription> Sort(Dictionary<string, ProjectDescription> projects)
{ {
var outputs = new HashSet<ProjectDescription>(); var outputs = new HashSet<ProjectDescription>();

View file

@ -5,10 +5,11 @@ using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using Microsoft.DotNet.Cli.Compiler.Common;
namespace Microsoft.DotNet.Tools.Compiler namespace Microsoft.DotNet.Tools.Compiler
{ {
internal static class CreateCSharpManifestResourceName internal static class ResourceManifestName
{ {
// Original source: https://raw.githubusercontent.com/Microsoft/msbuild/82177a50da735cc0443ac10fa490d69368403d71/src/XMakeTasks/CreateCSharpManifestResourceName.cs // Original source: https://raw.githubusercontent.com/Microsoft/msbuild/82177a50da735cc0443ac10fa490d69368403d71/src/XMakeTasks/CreateCSharpManifestResourceName.cs
@ -32,7 +33,7 @@ namespace Microsoft.DotNet.Tools.Compiler
// This is different from the msbuild task: we always append extensions because otherwise, // This is different from the msbuild task: we always append extensions because otherwise,
// the emitted resource doesn't have an extension and it is not the same as in the classic // the emitted resource doesn't have an extension and it is not the same as in the classic
// C# assembly // C# assembly
if (ResourcePathUtility.IsResxResourceFile(fileName)) if (ResourceUtility.IsResourceFile(fileName))
{ {
name.Append(Path.Combine(path, Path.GetFileNameWithoutExtension(fileName))); name.Append(Path.Combine(path, Path.GetFileNameWithoutExtension(fileName)));
name.Append(".resources"); name.Append(".resources");
@ -103,8 +104,9 @@ namespace Microsoft.DotNet.Tools.Compiler
return id.ToString(); return id.ToString();
} }
/// <summary> /// <summary>
/// Make a folder subname into identifier /// Make a folder subname into identifier
/// </summary> /// </summary>
private static string MakeValidSubFolderIdentifier(string subName) private static string MakeValidSubFolderIdentifier(string subName)
{ {

View file

@ -1,31 +0,0 @@
// 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 Microsoft.DotNet.Tools.Common;
namespace Microsoft.DotNet.Tools.Compiler
{
internal static class ResourcePathUtility
{
public static string GetResourceName(string projectFolder, string resourcePath)
{
// If the file is outside of the project folder, we are assuming it is directly in the root
// otherwise, keep the folders that are inside the project
return PathUtility.IsChildOfDirectory(projectFolder, resourcePath) ?
PathUtility.GetRelativePath(projectFolder, resourcePath) :
Path.GetFileName(resourcePath);
}
public static bool IsResxResourceFile(string fileName)
{
var ext = Path.GetExtension(fileName);
return
string.Equals(ext, ".resx", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ext, ".restext", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ext, ".resources", StringComparison.OrdinalIgnoreCase);
}
}
}

View file

@ -8,7 +8,6 @@
"NETStandard.Library": "1.0.0-rc2-23608", "NETStandard.Library": "1.0.0-rc2-23608",
"System.Linq": "4.0.1-rc2-23608", "System.Linq": "4.0.1-rc2-23608",
"System.Reflection.Metadata": "1.1.0", "System.Reflection.Metadata": "1.1.0",
"Microsoft.DotNet.ProjectModel": "1.0.0-*", "Microsoft.DotNet.ProjectModel": "1.0.0-*",
"Microsoft.DotNet.Compiler.Common": "1.0.0-*", "Microsoft.DotNet.Compiler.Common": "1.0.0-*",
"Microsoft.DotNet.Cli.Utils": { "Microsoft.DotNet.Cli.Utils": {

View file

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel;
using NuGet; using NuGet;
@ -19,6 +18,7 @@ using Microsoft.DotNet.ProjectModel.Files;
using Microsoft.Extensions.FileSystemGlobbing; using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.FileSystemGlobbing.Abstractions; using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
using Microsoft.DotNet.ProjectModel.Utilities; using Microsoft.DotNet.ProjectModel.Utilities;
using Microsoft.DotNet.Cli.Compiler.Common;
namespace Microsoft.DotNet.Tools.Compiler namespace Microsoft.DotNet.Tools.Compiler
{ {
@ -142,6 +142,21 @@ namespace Microsoft.DotNet.Tools.Compiler
TryAddOutputFile(packageBuilder, context, outputPath, outputName); TryAddOutputFile(packageBuilder, context, outputPath, outputName);
var resourceCultures = context.ProjectFile.Files.ResourceFiles
.Select(resourceFile => ResourceUtility.GetResourceCultureName(resourceFile.Key))
.Distinct();
foreach (var culture in resourceCultures)
{
if (string.IsNullOrEmpty(culture))
{
continue;
}
var resourceFilePath = Path.Combine(culture, $"{project.Name}.resources.dll");
TryAddOutputFile(packageBuilder, context, outputPath, resourceFilePath);
}
// REVIEW: Do we keep making symbols packages? // REVIEW: Do we keep making symbols packages?
TryAddOutputFile(packageBuilder, context, outputPath, $"{project.Name}.pdb"); TryAddOutputFile(packageBuilder, context, outputPath, $"{project.Name}.pdb");
TryAddOutputFile(packageBuilder, context, outputPath, $"{project.Name}.mdb"); TryAddOutputFile(packageBuilder, context, outputPath, $"{project.Name}.mdb");
@ -304,7 +319,7 @@ namespace Microsoft.DotNet.Tools.Compiler
string outputPath, string outputPath,
string filePath) string filePath)
{ {
var targetPath = Path.Combine("lib", context.TargetFramework.GetTwoDigitShortFolderName(), Path.GetFileName(filePath)); var targetPath = Path.Combine("lib", context.TargetFramework.GetTwoDigitShortFolderName(), filePath);
var sourcePath = Path.Combine(outputPath, filePath); var sourcePath = Path.Combine(outputPath, filePath);
if (!File.Exists(sourcePath)) if (!File.Exists(sourcePath))

View file

@ -7,10 +7,8 @@
"dependencies": { "dependencies": {
"NETStandard.Library": "1.0.0-rc2-23608", "NETStandard.Library": "1.0.0-rc2-23608",
"System.IO.Compression.ZipFile": "4.0.1-rc2-23608", "System.IO.Compression.ZipFile": "4.0.1-rc2-23608",
"Microsoft.NETCore.Targets.DNXCore": "5.0.0-beta-23511",
"System.Linq": "4.0.1-rc2-23608", "System.Linq": "4.0.1-rc2-23608",
"Microsoft.DotNet.Compiler.Common": "1.0.0-*",
"Microsoft.DotNet.ProjectModel": "1.0.0-*", "Microsoft.DotNet.ProjectModel": "1.0.0-*",
"Microsoft.DotNet.Cli.Utils": { "Microsoft.DotNet.Cli.Utils": {
"type": "build", "type": "build",
@ -23,5 +21,12 @@
}, },
"frameworks": { "frameworks": {
"dnxcore50": { } "dnxcore50": { }
},
"scripts": {
"postcompile": [
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.dll\"",
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.pdb\""
]
} }
} }

View file

@ -1,13 +1,11 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. // 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. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Resources;
using System.Xml.Linq;
using Microsoft.Dnx.Runtime.Common.CommandLine; using Microsoft.Dnx.Runtime.Common.CommandLine;
using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils;
using System;
namespace Microsoft.DotNet.Tools.Resgen namespace Microsoft.DotNet.Tools.Resgen
{ {
@ -17,53 +15,91 @@ namespace Microsoft.DotNet.Tools.Resgen
{ {
DebugHelper.HandleDebugSwitch(ref args); DebugHelper.HandleDebugSwitch(ref args);
var app = new CommandLineApplication(); var app = new CommandLineApplication(false);
app.Name = "resgen"; app.Name = "resgen";
app.FullName = "Resource compiler"; app.FullName = "Resource compiler";
app.Description = "Microsoft (R) .NET Resource Generator"; app.Description = "Microsoft (R) .NET Resource Generator";
app.HelpOption("-h|--help"); app.HelpOption("-h|--help");
var inputFile = app.Argument("<input>", "The .resx file to transform"); var ouputFile = app.Option("-o", "Output file name", CommandOptionType.SingleValue);
var outputFile = app.Argument("<output>", "The .resources file to produce"); var culture = app.Option("-c", "Ouput assembly culture", CommandOptionType.SingleValue);
var references = app.Option("-r", "Compilation references", CommandOptionType.MultipleValue);
var inputFiles = app.Argument("<input>", "Input files", true);
app.OnExecute(() => app.OnExecute(() =>
{ {
WriteResourcesFileIfNotEmpty(inputFile.Value, outputFile.Value); if (!inputFiles.Values.Any())
{
Reporter.Error.WriteLine("No input files specified");
return 1;
}
var intputResourceFiles = inputFiles.Values.Select(ParseInputFile).ToArray();
var outputResourceFile = ResourceFile.Create(ouputFile.Value());
switch (outputResourceFile.Type)
{
case ResourceFileType.Dll:
using (var outputStream = outputResourceFile.File.Create())
{
ResourceAssemblyGenerator.Generate(intputResourceFiles,
outputStream,
Path.GetFileNameWithoutExtension(outputResourceFile.File.Name),
culture.Value(),
references.Values.ToArray()
);
}
break;
case ResourceFileType.Resources:
using (var outputStream = outputResourceFile.File.Create())
{
if (intputResourceFiles.Length > 1)
{
Reporter.Error.WriteLine("Only one input file required when generating .resource output");
return 1;
}
ResourcesFileGenerator.Generate(intputResourceFiles.Single().Resource, outputStream);
}
break;
default:
Reporter.Error.WriteLine("Resx output type not supported");
return 1;
}
return 0; return 0;
}); });
return app.Execute(args); try
}
private static void WriteResourcesFileIfNotEmpty(string resxFilePath, string outputFile)
{
using (var fs = File.OpenRead(resxFilePath))
using (var outfs = File.Create(outputFile))
{ {
var document = XDocument.Load(fs); return app.Execute(args);
}
var data = document.Root.Elements("data"); catch (Exception ex)
{
if (data.Any()) #if DEBUG
{ Reporter.Error.WriteLine(ex.ToString());
WriteResourcesFile(outfs, data); #else
} Reporter.Error.WriteLine(ex.Message);
#endif
return 1;
} }
} }
private static void WriteResourcesFile(Stream outfs, IEnumerable<XElement> data) private static ResourceSource ParseInputFile(string arg)
{ {
var rw = new ResourceWriter(outfs); var seperatorIndex = arg.IndexOf(',');
string name = null;
foreach (var e in data) string metadataName = null;
if (seperatorIndex > 0)
{ {
var name = e.Attribute("name").Value; name = arg.Substring(0, seperatorIndex);
var value = e.Element("value").Value; metadataName = arg.Substring(seperatorIndex + 1);
rw.AddResource(name, value);
} }
else
rw.Generate(); {
name = arg;
metadataName = arg;
}
return new ResourceSource(ResourceFile.Create(name), metadataName);
} }
} }
} }

View file

@ -0,0 +1,85 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.Resgen
{
internal class ResourceAssemblyGenerator
{
public static void Generate(ResourceSource[] sourceFiles, Stream outputStream, string asemblyName, string culture, string[] references)
{
if (sourceFiles == null)
{
throw new ArgumentNullException(nameof(sourceFiles));
}
if (outputStream == null)
{
throw new ArgumentNullException(nameof(outputStream));
}
if (!sourceFiles.Any())
{
throw new InvalidOperationException("No source files specified");
}
var resourceDescriptions = new List<ResourceDescription>();
foreach (var resourceInputFile in sourceFiles)
{
if (resourceInputFile.Resource.Type == ResourceFileType.Resx)
{
resourceDescriptions.Add(new ResourceDescription(
resourceInputFile.MetadataName,
() => GenerateResources(resourceInputFile.Resource), true));
}
else if (resourceInputFile.Resource.Type == ResourceFileType.Resources)
{
resourceDescriptions.Add(new ResourceDescription(resourceInputFile.Resource.File.Name, () => resourceInputFile.Resource.File.OpenRead(), true));
}
else
{
throw new InvalidOperationException("Generation of resource assemblies from dll not supported");
}
}
var compilationOptions = new CSharpCompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(asemblyName,
references: references.Select(reference => MetadataReference.CreateFromFile(reference)),
options: compilationOptions);
if (!string.IsNullOrEmpty(culture))
{
compilation = compilation.AddSyntaxTrees(new[]
{
CSharpSyntaxTree.ParseText($"[assembly:System.Reflection.AssemblyCultureAttribute(\"{culture}\")]")
});
}
var result = compilation.Emit(outputStream, manifestResources: resourceDescriptions);
if (!result.Success)
{
foreach (var diagnostic in result.Diagnostics)
{
Reporter.Error.WriteLine(diagnostic.ToString());
}
throw new InvalidOperationException("Error occured while emiting assembly");
}
}
private static Stream GenerateResources(ResourceFile resourceFile)
{
var stream = new MemoryStream();
ResourcesFileGenerator.Generate(resourceFile, stream);
stream.Position = 0;
return stream;
}
}
}

View file

@ -0,0 +1,44 @@
// 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;
namespace Microsoft.DotNet.Tools.Resgen
{
internal struct ResourceFile
{
public ResourceFile(FileInfo file, ResourceFileType type)
{
File = file;
Type = type;
}
public FileInfo File { get; }
public ResourceFileType Type { get; }
public static ResourceFile Create(string fileName)
{
var fileInfo = new FileInfo(fileName);
ResourceFileType type;
switch (fileInfo.Extension.ToLowerInvariant())
{
case ".resx":
type = ResourceFileType.Resx;
break;
case ".resources":
type = ResourceFileType.Resources;
break;
case ".dll":
type = ResourceFileType.Dll;
break;
default:
throw new InvalidOperationException("Unsupported resource file");
}
return new ResourceFile(fileInfo, type);
}
}
}

View file

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.DotNet.Tools.Resgen
{
internal enum ResourceFileType
{
Resx,
Resources,
Dll
}
}

View file

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.DotNet.Tools.Resgen
{
internal struct ResourceSource
{
public ResourceSource(ResourceFile resource, string metadataName)
{
Resource = resource;
MetadataName = metadataName;
}
public ResourceFile Resource { get; }
public string MetadataName { get; }
}
}

View file

@ -0,0 +1,37 @@
// 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.Resources;
using System.Xml.Linq;
namespace Microsoft.DotNet.Tools.Resgen
{
internal class ResourcesFileGenerator
{
public static void Generate(ResourceFile sourceFile, Stream outputStream)
{
if (outputStream == null) throw new ArgumentNullException(nameof(outputStream));
using (var input = sourceFile.File.OpenRead())
{
var document = XDocument.Load(input);
var data = document.Root.Elements("data");
if (data.Any())
{
var rw = new ResourceWriter(outputStream);
foreach (var e in data)
{
var name = e.Attribute("name").Value;
var value = e.Element("value").Value;
rw.AddResource(name, value);
}
rw.Generate();
}
}
}
}
}

View file

@ -10,6 +10,7 @@
"System.Xml.XDocument": "4.0.11-rc2-23608", "System.Xml.XDocument": "4.0.11-rc2-23608",
"System.Resources.ReaderWriter": "4.0.0-rc2-23608", "System.Resources.ReaderWriter": "4.0.0-rc2-23608",
"Microsoft.CodeAnalysis.CSharp": "1.1.1",
"Microsoft.DotNet.Cli.Utils": { "Microsoft.DotNet.Cli.Utils": {
"type": "build", "type": "build",
"version": "1.0.0-*" "version": "1.0.0-*"

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.23107" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.23107</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>482b1045-a1fa-4063-a0d9-a8107a91a016</ProjectGuid>
<RootNamespace>Microsoft.Extensions.DependencyModel</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>