Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(localization): Add support satellite resource assemblies publishing (backport #665) #675

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/linker-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ By default, some features are linked out as those are not likely to be used in a
- `EventSourceSupport`
- `EnableUnsafeUTF7Encoding`
- `HttpActivityPropagationSupport`
- `InvariantGlobalization`

If you need to enable any of those features, you can set the following in your csproj first `PropertyGroup`:
```xml
<EventSourceSupport>true</EventSourceSupport>
```

Setting `InvariantGlobalization` to true will remove all satellite assemblies from the final package.
7 changes: 7 additions & 0 deletions src/Uno.Wasm.Bootstrap/ShellTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public partial class ShellTask_v0 : Microsoft.Build.Utilities.Task
[Microsoft.Build.Framework.Required]
public bool MonoILLinker { get; set; }

public bool InvariantGlobalization { get; set; } = false;

public bool EmccLinkOptimization { get; set; }

public string? EmccLinkOptimizationLevel { get; set; }
Expand Down Expand Up @@ -881,6 +883,11 @@ private void RunPackager()
packagerParams.Add($"\"--linker-optimization-level={GetEmccLinkerOptimizationLevel()}\"");
packagerParams.Add($"\"{AlignPath(Path.GetFullPath(Assembly))}\"");

if (InvariantGlobalization)
{
packagerParams.Add("--invariant-globalization");
}

var packagerResponseFile = Path.Combine(workAotPath, "packager.rsp");
File.WriteAllLines(packagerResponseFile, packagerParams.Select(p => p.Replace("\\", "/")));

Expand Down
116 changes: 108 additions & 8 deletions src/Uno.Wasm.Packager/packager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public bool DefaultValue {
}

class Driver {
static bool enable_debug, enable_linker;
static bool enable_debug, enable_linker, invariant_globalization;
static string app_prefix, framework_prefix, bcl_tools_prefix, bcl_facades_prefix, out_prefix;
static List<string> bcl_prefixes;
static HashSet<string> asm_map = new HashSet<string> ();
Expand Down Expand Up @@ -98,6 +98,9 @@ class AssemblyData {
public string final_path;
// Whenever to AOT this assembly
public bool aot;

// If not null, this is a satellite assembly
public string culture;
}

static List<AssemblyData> assemblies = new List<AssemblyData> ();
Expand Down Expand Up @@ -255,7 +258,10 @@ static void Import (string ra, AssemblyKind kind) {
return;
}

if (!asm_map.Add (Path.GetFullPath (ra)))
var assemblyFullPath = Path.GetFullPath(ra);
var assemblyDirectory = Path.GetDirectoryName(assemblyFullPath);

if (!asm_map.Add (assemblyFullPath))
return;
Console.WriteLine($"Resolving {ra}");
ReaderParameters rp = new ReaderParameters();
Expand Down Expand Up @@ -317,14 +323,48 @@ static void Import (string ra, AssemblyKind kind) {
}
else
{
Console.WriteLine($"Coukd not resolve {ar.Name}");
Console.WriteLine($"Could not resolve {ar.Name}");
}
}
}

// Resolving satellite assemblies
if(!invariant_globalization && kind == AssemblyKind.User)
{
string resourceFile = GetAssemblyResourceFileName(assemblyFullPath);

foreach (var subDirectory in Directory.EnumerateDirectories(assemblyDirectory))
{
var satelliteAssembly = Path.Combine(subDirectory, resourceFile);
if (!File.Exists(satelliteAssembly))
{
continue;
}

string cultureName = subDirectory.Substring(subDirectory.LastIndexOf(Path.DirectorySeparatorChar) + 1);
string culturePath = Path.Combine(assemblyDirectory, cultureName);

var satelliteData = new AssemblyData() {
name = resourceFile.Replace(".dll", ""),
src_path = satelliteAssembly.Replace("\\", "/"),
culture = cultureName,
aot = false
};

assemblies.Add(satelliteData);

file_list.Add(satelliteAssembly);

Console.WriteLine($"Added satellite assembly {cultureName}/{resourceFile}");
}
}

Console.WriteLine($"Resolved {ra}");
}

static string GetAssemblyResourceFileName(string assembly)
=> Path.GetFileNameWithoutExtension(assembly) + ".resources.dll";

void GenDriver (string builddir, List<string> profilers, ExecMode ee_mode, bool link_icalls) {
var symbols = new List<string> ();
foreach (var adata in assemblies) {
Expand Down Expand Up @@ -408,6 +448,7 @@ class WasmOptions {
public bool EnableDedup = true;
public bool EmccLinkOptimizations = false;
public bool EnableWasmExceptions = false;
public bool InvariantGlobalization = false;
}

int Run (string[] args) {
Expand Down Expand Up @@ -544,6 +585,7 @@ int Run (string[] args) {
AddFlag (p, new BoolFlag ("collation", "enable unicode collation support", opts.EnableCollation, b => opts.EnableCollation = b));
AddFlag (p, new BoolFlag ("icu", "enable .NET 5+ ICU", opts.EnableICU, b => opts.EnableICU = b));
AddFlag (p, new BoolFlag ("emcc-link-optimization", "enable emcc link-time optimizations", opts.EmccLinkOptimizations, b => opts.EmccLinkOptimizations = b));
AddFlag (p, new BoolFlag ("invariant-globalization", "enables invariant globalization", opts.InvariantGlobalization, b => opts.InvariantGlobalization = b));
p.Add(new ResponseFileSource());

var new_args = p.Parse (args).ToArray ();
Expand Down Expand Up @@ -584,6 +626,7 @@ int Run (string[] args) {
enable_threads = opts.EnableThreads;
enable_dynamic_runtime = opts.EnableDynamicRuntime;
enable_simd = opts.Simd;
invariant_globalization = opts.InvariantGlobalization;

// Dedup is disabled by default https://github.com/dotnet/runtime/issues/48814
enable_dedup = opts.EnableDedup;
Expand Down Expand Up @@ -740,6 +783,14 @@ int Run (string[] args) {
foreach (var ass in assemblies) {
if (aot_assemblies == "" || to_aot.ContainsKey (ass.name)) {
ass.aot = true;

if(ass.culture is not null)
{
// Satellite assemblies cannot be AOTed as they're
// implicitly duplicates.
ass.aot = false;
}

to_aot.Remove (ass.name);
}
}
Expand Down Expand Up @@ -774,7 +825,15 @@ int Run (string[] args) {
Directory.Delete (bcl_dir, true);
Directory.CreateDirectory (bcl_dir);
foreach (var f in file_list) {
CopyFile(f, Path.Combine (bcl_dir, Path.GetFileName (f)), copyType);

var fileName = Path.GetFileName(f);

if(IsResourceAssembly(f, out var culture))
{
fileName = Path.Combine(culture, fileName);
}

CopyFile(f, Path.Combine (bcl_dir, fileName), copyType);
}
}

Expand Down Expand Up @@ -885,7 +944,20 @@ int Run (string[] args) {
_ => throw new Exception($"Unsupported asset type")
};

return $" {{ \"name\": \"{Path.GetFileName(f)}\", \"behavior\":\"{assetType}\", \"url\":\"{Path.GetFileName(f)}\" }}";
string cultureField = null;
string culturePathPrefix = null;

if (assetType is "assembly")
{
if(IsResourceAssembly(f, out var culture))
{
assetType = "resource";
cultureField = $", \"culture\":\"{Path.GetFileName(Path.GetDirectoryName(f))}\"";
culturePathPrefix = $"{culture}/";
}
}

return $" {{ \"name\": \"{Path.GetFileName(f)}\", \"behavior\":\"{assetType}\", \"url\":\"{culturePathPrefix}{Path.GetFileName(f)}\" {cultureField} }}";
}));
var debugLevel = enable_debug ? " -1" : "0";

Expand Down Expand Up @@ -944,7 +1016,7 @@ int Run (string[] args) {
if (assembly == null)
continue;
string filename = Path.GetFileName (assembly);
if (filenames.ContainsKey (filename)) {
if (filenames.ContainsKey (filename) && !filename.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase)) {
Console.WriteLine ("Duplicate input assembly: " + assembly + " " + filenames [filename]);
return 1;
}
Expand Down Expand Up @@ -1470,6 +1542,12 @@ int Run (string[] args) {
if (assembly == null)
continue;
string filename = Path.GetFileName (assembly);

if(a.culture is not null)
{
filename = Path.Combine(a.culture, filename);
}

var filename_noext = Path.GetFileNameWithoutExtension (filename);
string filename_pdb = Path.ChangeExtension (filename, "pdb");
var source_file_path = Path.GetFullPath (assembly);
Expand Down Expand Up @@ -1587,7 +1665,7 @@ int Run (string[] args) {

if (link_icalls) {
string icall_assemblies = "";
foreach (var a in assemblies) {
foreach (var a in assemblies.Where(a => a.culture is null)) {
if (a.name == "mscorlib" || a.name == "System")
icall_assemblies += $"{a.linkout_path} ";
}
Expand All @@ -1596,7 +1674,7 @@ int Run (string[] args) {
}
if (gen_pinvoke) {
string pinvoke_assemblies = "";
foreach (var a in assemblies)
foreach (var a in assemblies.Where(a => a.culture is null))
pinvoke_assemblies += $"{a.linkout_path} ";

ninja.WriteLine ($"build $builddir/pinvoke-table.h: cpifdiff $builddir/pinvoke-table.h.tmp");
Expand Down Expand Up @@ -1700,9 +1778,31 @@ int Run (string[] args) {
return 0;
}

private bool IsResourceAssembly(string f, out string culture)
{
if (f.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase))
{
var originalAssembly = Path.GetFileName(f.Replace(".resources.dll", ".dll", StringComparison.OrdinalIgnoreCase));

var resourceAssemblyDirectory = Path.GetDirectoryName(Path.GetDirectoryName(f));
if (File.Exists(Path.Combine(resourceAssemblyDirectory, originalAssembly)))
{
culture = Path.GetFileName(Path.GetDirectoryName(f));

return true;
}
}

culture = null;
return false;
}

static void CopyFile(string sourceFileName, string destFileName, CopyType copyType, string typeFile = "")
{
Console.WriteLine($"{typeFile}cp: {copyType} - {sourceFileName} -> {destFileName}");

Directory.CreateDirectory(Path.GetDirectoryName(destFileName));

switch (copyType)
{
case CopyType.Always:
Expand Down
5 changes: 5 additions & 0 deletions src/Uno.Wasm.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Globalization;

namespace Uno.Wasm.Sample
{
Expand Down Expand Up @@ -51,6 +52,10 @@ static void Main(string[] args)

Console.WriteLine(typeof(Microsoft.Extensions.Logging.Abstractions.NullLogger));

var r = new System.Resources.ResourceManager("FxResources.System.Web.Services.Description.SR", typeof(System.Web.Services.Description.Binding).Assembly);
Console.WriteLine($"Res(en): {r.GetString("WebDescriptionMissing", new CultureInfo("en-US"))}");
Console.WriteLine($"Res(fr): {r.GetString("WebDescriptionMissing", new CultureInfo("fr-CA"))}");

_t = new Timer(_ => {
Console.WriteLine("message");
}, null, 5000, 5000);
Expand Down
Loading