Skip to content

Commit a6d374a

Browse files
authored
Add Resource Assembly handling (#492)
* Add Resource Assembly handling ## What? Adds handling of resource assemblies in NuGet packages. * Remove commented code * Update docs
1 parent 913d132 commit a6d374a

File tree

16 files changed

+184
-7
lines changed

16 files changed

+184
-7
lines changed

docs/defs.md

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dotnet/paket.rules_dotnet_dev_nuget_packages.bzl

Lines changed: 4 additions & 0 deletions
Large diffs are not rendered by default.

dotnet/private/common.bzl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def collect_transitive_runfiles(ctx, assembly_runtime_info, deps):
366366
Returns:
367367
A runfiles object that includes the transitive dependencies of the target
368368
"""
369-
runfiles = ctx.runfiles(files = assembly_runtime_info.data + assembly_runtime_info.native + assembly_runtime_info.xml_docs + assembly_runtime_info.libs)
369+
runfiles = ctx.runfiles(files = assembly_runtime_info.data + assembly_runtime_info.native + assembly_runtime_info.xml_docs + assembly_runtime_info.libs + assembly_runtime_info.resource_assemblies)
370370

371371
transitive_runfiles = []
372372
for dep in deps:
@@ -526,6 +526,19 @@ def framework_preprocessor_symbols(tfm):
526526

527527
return defines
528528

529+
def _get_resource_assembly_locale(file):
530+
"""Gets the locale of a resource assembly file.
531+
532+
The locale is the path fragment before the file name:
533+
e.g. <TFM>/<locale>/assembly.resources.dll
534+
535+
Args:
536+
file: The resource assembly file.
537+
Returns:
538+
The locale of the resource assembly file.
539+
"""
540+
return file.dirname.split("/")[-1]
541+
529542
# For deps.json spec see: https://github.com/dotnet/sdk/blob/main/documentation/specs/runtime-configuration-file.md
530543
def generate_depsjson(
531544
ctx,
@@ -637,6 +650,10 @@ def generate_depsjson(
637650
} for dll in runtime_dep.libs}
638651
target_fragment["native"] = {native_file.basename if not use_relative_paths else to_rlocation_path(ctx, native_file): {} for native_file in runtime_dep.native}
639652

653+
target_fragment["resources"] = {(resource_assembly.basename if not use_relative_paths else to_rlocation_path(ctx, resource_assembly)): {
654+
"locale": _get_resource_assembly_locale(resource_assembly),
655+
} for resource_assembly in runtime_dep.resource_assemblies}
656+
640657
base["libraries"][library_name] = library_fragment
641658
base["targets"][runtime_target][library_name] = target_fragment
642659

dotnet/private/providers.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ DotnetAssemblyRuntimeInfo = provider(
3333
"xml_docs": "list[File]: The XML documentation files of the assembly",
3434
"native": "list[File]: Native runtime files",
3535
"data": "list[File]: Runtime data files",
36+
"resource_assemblies": "list[File]: Resource assemblies",
3637
"appsetting_files": "list[File]: Appsetting files",
3738
"nuget_info": "NugetInfo",
3839
"deps": "depset[DotnetAssemblyRuntimeInfo]: The direct and transitive runtime dependencies of the assembly",

dotnet/private/rules/csharp/actions/csharp_assembly.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ def AssemblyAction(
350350
name = assembly_name,
351351
version = "1.0.0", #TODO: Maybe make this configurable?
352352
libs = [out_dll] if not (is_analyzer or is_language_specific_analyzer) else [],
353+
resource_assemblies = [],
353354
pdbs = [out_pdb] if out_pdb else [],
354355
xml_docs = [out_xml] if out_xml else [],
355356
data = data,

dotnet/private/rules/fsharp/actions/fsharp_assembly.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ def AssemblyAction(
303303
name = assembly_name,
304304
version = "1.0.0", #TODO: Maybe make this configurable?
305305
libs = [out_dll],
306+
resource_assemblies = [],
306307
pdbs = [out_pdb] if out_pdb else [],
307308
xml_docs = [out_xml] if out_xml else [],
308309
data = data,

dotnet/private/rules/nuget/imports.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def _import_library(ctx):
6161
name = ctx.attr.library_name,
6262
version = ctx.attr.version,
6363
libs = ctx.files.libs,
64+
resource_assemblies = ctx.files.resource_assemblies,
6465
# TODO: PDBs from nuget packages should also be forwarded
6566
pdbs = [],
6667
xml_docs = [],
@@ -126,6 +127,11 @@ import_library = rule(
126127
allow_files = True, # [".dll"] currently does not work with empty file groups
127128
allow_empty = True,
128129
),
130+
"resource_assemblies": attr.label_list(
131+
doc = "Resource assemblies",
132+
allow_files = True, # [".dll"] currently does not work with empty file groups
133+
allow_empty = True,
134+
),
129135
"deps": attr.label_list(
130136
doc = "Other DLLs that this DLL depends on.",
131137
providers = [DotnetAssemblyRuntimeInfo, DotnetAssemblyCompileInfo],
@@ -181,6 +187,7 @@ def _import_dll(ctx):
181187
name = ctx.file.dll.basename[:-4],
182188
version = ctx.attr.version,
183189
libs = [ctx.file.dll],
190+
resource_assemblies = [],
184191
pdbs = [],
185192
xml_docs = [],
186193
native = [],

dotnet/private/rules/nuget/nuget_archive.bzl

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ def _replace_non_standard_tfm(tfm):
102102

103103
return tfm
104104

105+
# A resource assembly is located at lib/<tfm>/<locale>/<assembly>.resources.dll
106+
def _process_resource_assembly(groups, tfm, file):
107+
if groups["resource_assemblies"].get(tfm) == None:
108+
groups["resource_assemblies"][tfm] = []
109+
110+
groups["resource_assemblies"][tfm].append(file)
111+
112+
return
113+
105114
# This function processes a package file that has the following format:
106115
# <group>/<tfm>/<file>
107116
def _process_group_with_tfm(groups, group_name, file):
@@ -114,7 +123,7 @@ def _process_group_with_tfm(groups, group_name, file):
114123
return
115124

116125
# If the folder is empty we do nothing
117-
if file.find("/", tfm_end + 1) != -1:
126+
if file.endswith(tfm) or file.endswith(tfm + "/"):
118127
return
119128

120129
group = groups[group_name]
@@ -128,7 +137,11 @@ def _process_group_with_tfm(groups, group_name, file):
128137
if file.endswith("_._"):
129138
return
130139

131-
if not file.endswith(".dll") or file.endswith(".resources.dll"):
140+
if group_name == "lib" and file.endswith(".resources.dll"):
141+
_process_resource_assembly(groups, tfm, file)
142+
return
143+
144+
if not file.endswith(".dll"):
132145
return
133146

134147
group[tfm].append(file)
@@ -284,7 +297,6 @@ def _process_runtimes_file(groups, file):
284297
return
285298

286299
def _process_key_and_file(groups, key, file):
287-
# todo resource dlls
288300
if key == "lib":
289301
_process_group_with_tfm(groups, key, file)
290302
elif key == "ref":
@@ -391,6 +403,9 @@ def _nuget_archive_impl(ctx):
391403
},
392404
# Format: lib/<TFM>/<assembly>.dll
393405
"lib": {},
406+
# Resource assemblies: https://learn.microsoft.com/en-us/nuget/create-packages/creating-localized-packages
407+
# Format: lib/<TFM>/<locale>/<assembly>.resources.<dll|xml>
408+
"resource_assemblies": {},
394409
# Format: ref/<TFM>/<assembly>.dll
395410
"ref": {},
396411
# See https://github.com/fsharp/fslang-design/blob/main/tooling/FST-1003-loading-type-provider-design-time-components.md
@@ -494,6 +509,7 @@ load("@rules_dotnet//dotnet/private/rules/nuget:nuget_archive.bzl", "tfm_filegro
494509
""" + "\n".join([
495510
_create_framework_select("libs", libs) or "filegroup(name = \"libs\", srcs = [])",
496511
_create_framework_select("refs", refs) or "filegroup(name = \"refs\", srcs = [])",
512+
_create_framework_select("resource_assemblies", groups["resource_assemblies"]) or "filegroup(name = \"resource_assemblies\", srcs = [])",
497513
"filegroup(name = \"analyzers\", srcs = [%s])" % ",".join(["\n \"%s\"" % a for a in groups.get("analyzers")["dotnet"]]),
498514
"filegroup(name = \"analyzers_csharp\", srcs = [%s])" % ",".join(["\n \"%s\"" % a for a in groups.get("analyzers")["dotnet/cs"]]),
499515
"filegroup(name = \"analyzers_fsharp\", srcs = [%s])" % ",".join(["\n \"%s\"" % a for a in groups.get("analyzers")["dotnet/fs"]]),

dotnet/private/rules/nuget/template.BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import_library(
1515
native = ["@{PREFIX}.{ID_LOWER}.v{VERSION}//:native"],
1616
nupkg = "@{PREFIX}.{ID_LOWER}.v{VERSION}//:{ID_LOWER}.{VERSION}.nupkg",
1717
refs = ["@{PREFIX}.{ID_LOWER}.v{VERSION}//:refs"],
18+
resource_assemblies = ["@{PREFIX}.{ID_LOWER}.v{VERSION}//:resource_assemblies"],
1819
sha512 = "{SHA_512}",
1920
targeting_pack_overrides = {TARGETING_PACK_OVERRIDES},
2021
version = "{VERSION}",

dotnet/private/rules/publish_binary/publish_binary.bzl

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def _copy_file(script_body, src, dst, is_windows):
1818

1919
def _get_assembly_files(assembly_info, transitive_runtime_deps, deps_json_struct):
2020
libs = [] + assembly_info.libs
21+
resource_assemblies = [] + assembly_info.resource_assemblies
2122
native = [] + assembly_info.native
2223
data = [] + assembly_info.data
2324
appsetting_files = assembly_info.appsetting_files.to_list()
@@ -37,7 +38,8 @@ def _get_assembly_files(assembly_info, transitive_runtime_deps, deps_json_struct
3738
libs.append(file)
3839

3940
data += dep.data
40-
return (libs, native, data, appsetting_files)
41+
resource_assemblies += dep.resource_assemblies
42+
return (libs, resource_assemblies, native, data, appsetting_files)
4143

4244
def _copy_to_publish(ctx, runtime_identifier, runtime_pack_info, binary_info, assembly_info, transitive_runtime_deps, deps_json_struct):
4345
is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo])
@@ -50,7 +52,7 @@ def _copy_to_publish(ctx, runtime_identifier, runtime_pack_info, binary_info, as
5052

5153
_copy_file(script_body, binary_info.dll, main_dll_copy, is_windows = is_windows)
5254

53-
(libs, native, data, appsetting_files) = _get_assembly_files(assembly_info, transitive_runtime_deps, deps_json_struct)
55+
(libs, resource_assemblies, native, data, appsetting_files) = _get_assembly_files(assembly_info, transitive_runtime_deps, deps_json_struct)
5456

5557
# All managed DLLs are copied next to the app host in the publish directory
5658
for file in libs:
@@ -61,6 +63,17 @@ def _copy_to_publish(ctx, runtime_identifier, runtime_pack_info, binary_info, as
6163
inputs.append(file)
6264
_copy_file(script_body, file, output, is_windows = is_windows)
6365

66+
# Resource assemblies are copied next to the app host in the publish directory in a folder
67+
# that has the same name as the locale of the resource assembly.
68+
# Example: `de/MyAssembly.resources.dll`
69+
for file in resource_assemblies:
70+
locale = file.dirname.split("/")[-1]
71+
output_dir = "{}/publish/{}/{}/{}".format(ctx.label.name, runtime_identifier, locale, file.basename)
72+
output = ctx.actions.declare_file(output_dir)
73+
outputs.append(output)
74+
inputs.append(file)
75+
_copy_file(script_body, file, output, is_windows = is_windows)
76+
6477
# When publishing a self-contained binary, we need to copy the native DLLs to the
6578
# publish directory as well.
6679
for file in native:

0 commit comments

Comments
 (0)