Skip to content

Commit f711be0

Browse files
stravinskiiswiple-rules-gardener
authored andcommitted
Support propagating frameworks via objc/swift_library data attribute.
Adds support to propagate Apple frameworks (ie. `ios_framework`, `tvos_framework`) through the `data` attribute from transitive libraries (ie. `objc_library` and `swift_library`) using the resource aspect. Currently, Apple frameworks (`*_framework` targets) are supported using the `runtime_deps` attribute for `objc_library` targets through the framework provider aspect (support was added at 1199da0 and 889131a). This support will be deprecated on a follow-up change. PiperOrigin-RevId: 513139752
1 parent 1d7c1a1 commit f711be0

File tree

15 files changed

+608
-153
lines changed

15 files changed

+608
-153
lines changed

apple/internal/aspects/framework_provider_aspect.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ visibility("//apple/...")
3030
# List of attributes through which the aspect propagates. We include `runtime_deps` here as
3131
# these are supported by `objc_library` for frameworks that should be present in the bundle, but not
3232
# linked against.
33-
# TODO(b/120205406): Migrate the `runtime_deps` use case to be referenced through `data` instead.
33+
# TODO(b/120205406): Remove `runtime_deps` support to use objc_library/swift_library `data` instead.
3434
_FRAMEWORK_PROVIDERS_ASPECT_ATTRS = ["deps", "frameworks", "runtime_deps"]
3535

3636
def _framework_provider_aspect_impl(target, ctx):

apple/internal/aspects/resource_aspect.bzl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ load(
3838
)
3939
load(
4040
"@build_bazel_rules_apple//apple:providers.bzl",
41+
"AppleFrameworkBundleInfo",
4142
"AppleResourceInfo",
4243
)
4344
load(
@@ -238,7 +239,10 @@ def _apple_resource_aspect_impl(target, ctx):
238239
inherited_providers.extend([
239240
x[AppleResourceInfo]
240241
for x in getattr(ctx.rule.attr, attr)
241-
if AppleResourceInfo in x
242+
if AppleResourceInfo in x and
243+
# Filter Apple framework targets to avoid propagating and bundling
244+
# framework resources to the top-level target (eg. ios_application).
245+
AppleFrameworkBundleInfo not in x
242246
])
243247
if inherited_providers and bundle_name:
244248
# Nest the inherited resource providers within the bundle, if one is needed for this rule.

apple/internal/ios_rules.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ load(
106106
load(
107107
"@build_bazel_rules_apple//apple:providers.bzl",
108108
"AppleBundleInfo",
109+
"AppleFrameworkBundleInfo",
109110
"ApplePlatformInfo",
110111
"IosAppClipBundleInfo",
111112
"IosApplicationBundleInfo",
@@ -969,6 +970,7 @@ def _ios_framework_impl(ctx):
969970

970971
return [
971972
DefaultInfo(files = processor_result.output_files),
973+
AppleFrameworkBundleInfo(),
972974
IosFrameworkBundleInfo(),
973975
OutputGroupInfo(
974976
**outputs.merge_output_groups(

apple/internal/partials/resources.bzl

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ def _resources_partial_impl(
354354
provider_field_to_action = {
355355
"asset_catalogs": (resources_support.asset_catalogs, False),
356356
"datamodels": (resources_support.datamodels, True),
357+
"framework": (resources_support.apple_bundle(processor.location.framework), False),
357358
"infoplists": (resources_support.infoplists, False),
358359
"mlmodels": (resources_support.mlmodels, False),
359360
"plists": (resources_support.plists_and_strings, False),
@@ -369,6 +370,7 @@ def _resources_partial_impl(
369370
# List containing all the files that the processor will bundle in their
370371
# configured location.
371372
bundle_files = []
373+
bundle_zips = []
372374

373375
fields = resources.populated_resource_fields(final_provider)
374376

@@ -433,7 +435,10 @@ def _resources_partial_impl(
433435
processing_args["swift_module"] = swift_module
434436

435437
result = processing_func(**processing_args)
436-
bundle_files.extend(result.files)
438+
if hasattr(result, "files"):
439+
bundle_files.extend(result.files)
440+
if hasattr(result, "archives"):
441+
bundle_zips.extend(result.archives)
437442
if hasattr(result, "infoplists"):
438443
infoplists.extend(result.infoplists)
439444

@@ -489,7 +494,11 @@ def _resources_partial_impl(
489494
),
490495
)
491496

492-
return struct(bundle_files = bundle_files, providers = [final_provider])
497+
return struct(
498+
bundle_files = bundle_files,
499+
bundle_zips = bundle_zips,
500+
providers = [final_provider],
501+
)
493502

494503
def resources_partial(
495504
*,
@@ -508,7 +517,7 @@ def resources_partial(
508517
rule_label,
509518
targets_to_avoid = [],
510519
top_level_infoplists = [],
511-
top_level_resources = [],
520+
top_level_resources = {},
512521
version,
513522
version_keys_required = True):
514523
"""Constructor for the resources processing partial.
@@ -541,7 +550,9 @@ def resources_partial(
541550
targets_to_avoid: List of targets containing resources that should be deduplicated from the
542551
target being processed.
543552
top_level_infoplists: A list of collected resources found from Info.plist attributes.
544-
top_level_resources: A list of collected resources found from resource attributes.
553+
top_level_resources: A dictionary of collected resources found from resource attributes,
554+
where keys are targets, and values are list of `File`s depsets. This can be obtained
555+
using the `apple/internal/resources.collect` API.
545556
version: A label referencing AppleBundleVersionInfo, if provided by the rule.
546557
version_keys_required: Whether to validate that the Info.plist version keys are correctly
547558
configured.

apple/internal/partials/support/resources_support.bzl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,34 @@ def _noop(
645645
processed_origins = processed_origins,
646646
)
647647

648+
def _apple_bundle(bundle_type):
649+
"""Returns a function to register bundling of Apple bundles at their appropriate location.
650+
651+
Args:
652+
bundle_type: The Apple bundle type to bundle for.
653+
Returns:
654+
A function to register bundling of an Apple bundle.
655+
"""
656+
if not hasattr(processor.location, bundle_type):
657+
fail("Bundle type location not supported: ", bundle_type)
658+
659+
def _bundle_at_location(*, files, **_kwargs):
660+
bundle = files.to_list().pop()
661+
location = getattr(processor.location, bundle_type)
662+
663+
# If tree artifacts are enabled, set the bundle name as
664+
# the parent directory. Otherwise, let bundletool unzip
665+
# the bundle directly.
666+
if bundle.is_directory:
667+
parent_dir = paths.basename(bundle.short_path)
668+
return struct(files = [(location, parent_dir, files)])
669+
else:
670+
return struct(archives = [(location, None, files)])
671+
672+
return _bundle_at_location
673+
648674
resources_support = struct(
675+
apple_bundle = _apple_bundle,
649676
asset_catalogs = _asset_catalogs,
650677
datamodels = _datamodels,
651678
infoplists = _infoplists,

apple/internal/resources.bzl

Lines changed: 97 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ This file provides methods to easily:
6565

6666
load(
6767
"@build_bazel_rules_apple//apple:providers.bzl",
68+
"AppleFrameworkBundleInfo",
6869
"AppleResourceInfo",
6970
)
7071
load(
@@ -218,65 +219,74 @@ def _bucketize_data(
218219
if allowed_buckets:
219220
allowed_bucket_set = {k: None for k in allowed_buckets}
220221

221-
for resource in resources:
222-
# Local cache of the resource short path since it gets used quite a bit below.
223-
resource_short_path = resource.short_path
222+
for target, target_resources in resources.items():
223+
for resource in target_resources:
224+
# Local cache of the resource short path since it gets used quite a bit below.
225+
resource_short_path = resource.short_path
224226

225-
if owner:
226-
owners.append((resource_short_path, owner))
227-
else:
228-
unowned_resources.append(resource_short_path)
227+
if owner:
228+
owners.append((resource_short_path, owner))
229+
else:
230+
unowned_resources.append(resource_short_path)
229231

230-
if types.is_string(parent_dir_param) or parent_dir_param == None:
231-
parent = parent_dir_param
232-
else:
233-
parent = partial.call(partial = parent_dir_param, resource = resource)
232+
if types.is_string(parent_dir_param) or parent_dir_param == None:
233+
parent = parent_dir_param
234+
else:
235+
parent = partial.call(partial = parent_dir_param, resource = resource)
234236

235-
# Special case for localized. If .lproj/ is in the path of the resource (and the parent
236-
# doesn't already have it) append the lproj component to the current parent.
237-
if ".lproj/" in resource_short_path and (not parent or ".lproj" not in parent):
238-
lproj_path = bundle_paths.farthest_parent(resource_short_path, "lproj")
239-
parent = paths.join(parent or "", paths.basename(lproj_path))
237+
# Special case for localized. If .lproj/ is in the path of the resource (and the parent
238+
# doesn't already have it) append the lproj component to the current parent.
239+
if ".lproj/" in resource_short_path and (not parent or ".lproj" not in parent):
240+
lproj_path = bundle_paths.farthest_parent(resource_short_path, "lproj")
241+
parent = paths.join(parent or "", paths.basename(lproj_path))
240242

241-
resource_swift_module = None
242-
resource_depset = depset([resource])
243-
244-
# For each type of resource, place in the appropriate bucket.
245-
if resource_short_path.endswith(".strings") or resource_short_path.endswith(".stringsdict"):
246-
bucket_name = "strings"
247-
elif resource_short_path.endswith(".storyboard"):
248-
bucket_name = "storyboards"
249-
resource_swift_module = swift_module
250-
elif resource_short_path.endswith(".xib"):
251-
bucket_name = "xibs"
252-
resource_swift_module = swift_module
253-
elif ".xcassets/" in resource_short_path or ".xcstickers/" in resource_short_path:
254-
bucket_name = "asset_catalogs"
255-
elif ".xcdatamodel" in resource_short_path or ".xcmappingmodel/" in resource_short_path:
256-
bucket_name = "datamodels"
257-
resource_swift_module = swift_module
258-
elif ".atlas" in resource_short_path:
259-
bucket_name = "texture_atlases"
260-
elif resource_short_path.endswith(".png"):
261-
# Process standalone pngs after asset_catalogs and texture_atlases so the latter can
262-
# bucketed correctly.
263-
bucket_name = "pngs"
264-
elif resource_short_path.endswith(".plist"):
265-
bucket_name = "plists"
266-
elif resource_short_path.endswith(".mlmodel"):
267-
bucket_name = "mlmodels"
268-
else:
269-
bucket_name = "unprocessed"
270-
271-
# If the allowed bucket list is not empty, and the bucket is not allowed, change the bucket
272-
# to unprocessed instead.
273-
if allowed_bucket_set and bucket_name not in allowed_bucket_set:
274-
bucket_name = "unprocessed"
275243
resource_swift_module = None
244+
resource_depset = depset([resource])
245+
246+
# For each type of resource, place in the appropriate bucket.
247+
if AppleFrameworkBundleInfo in target:
248+
if "framework.dSYM/" in resource_short_path or resource.extension == "linkmap":
249+
# TODO(b/271168739): Propagate AppleDebugSymbolsInfo and _AppleDebugInfo providers.
250+
# Ignore dSYM bundle and linkmap since the debug symbols partial is
251+
# responsible for propagating this up the dependency graph.
252+
continue
253+
bucket_name = "framework"
254+
elif (resource_short_path.endswith(".strings") or
255+
resource_short_path.endswith(".stringsdict")):
256+
bucket_name = "strings"
257+
elif resource_short_path.endswith(".storyboard"):
258+
bucket_name = "storyboards"
259+
resource_swift_module = swift_module
260+
elif resource_short_path.endswith(".xib"):
261+
bucket_name = "xibs"
262+
resource_swift_module = swift_module
263+
elif ".xcassets/" in resource_short_path or ".xcstickers/" in resource_short_path:
264+
bucket_name = "asset_catalogs"
265+
elif ".xcdatamodel" in resource_short_path or ".xcmappingmodel/" in resource_short_path:
266+
bucket_name = "datamodels"
267+
resource_swift_module = swift_module
268+
elif ".atlas" in resource_short_path:
269+
bucket_name = "texture_atlases"
270+
elif resource_short_path.endswith(".png"):
271+
# Process standalone pngs after asset_catalogs and texture_atlases so the latter can
272+
# bucketed correctly.
273+
bucket_name = "pngs"
274+
elif resource_short_path.endswith(".plist"):
275+
bucket_name = "plists"
276+
elif resource_short_path.endswith(".mlmodel"):
277+
bucket_name = "mlmodels"
278+
else:
279+
bucket_name = "unprocessed"
276280

277-
buckets.setdefault(bucket_name, []).append(
278-
(parent, resource_swift_module, resource_depset),
279-
)
281+
# If the allowed bucket list is not empty, and the bucket is not allowed, change the
282+
# bucket to unprocessed instead.
283+
if allowed_bucket_set and bucket_name not in allowed_bucket_set:
284+
bucket_name = "unprocessed"
285+
resource_swift_module = None
286+
287+
buckets.setdefault(bucket_name, []).append(
288+
(parent, resource_swift_module, resource_depset),
289+
)
280290

281291
return (
282292
owners,
@@ -338,7 +348,9 @@ def _bucketize_typed_data(*, bucket_type, owner = None, parent_dir_param = None,
338348
parent_dir_param: Either a string/None or a struct used to calculate the value of
339349
parent_dir for each resource. If it is a struct, it will be considered a partial
340350
context, and will be invoked with partial.call().
341-
resources: List of resources to place in bucket_type.
351+
resources: List of resources to place in bucket_type or Dictionary of resources keyed by
352+
target to place in bucket_type. This dictionary is supported by the
353+
`resources.collect()` API.
342354
343355
Returns:
344356
A tuple with a list of owners, a list of "unowned" resources, and a dictionary with
@@ -348,7 +360,17 @@ def _bucketize_typed_data(*, bucket_type, owner = None, parent_dir_param = None,
348360
owners = []
349361
unowned_resources = []
350362

351-
for resource in resources:
363+
all_resources = []
364+
if type(resources) == "list":
365+
all_resources = resources
366+
elif type(resources) == "dict":
367+
for target_resources in resources.values():
368+
all_resources.extend(target_resources)
369+
else:
370+
fail("Internal error: 'resources' should be either a list or dictionary.\n" +
371+
"This is most likely a rules_apple bug, please file a bug with reproduction steps")
372+
373+
for resource in all_resources:
352374
resource_short_path = resource.short_path
353375
if owner:
354376
owners.append((resource_short_path, owner))
@@ -380,13 +402,15 @@ def _bucketize_typed(resources, bucket_type, *, owner = None, parent_dir_param =
380402
parent_dir_param when available.
381403
382404
Args:
383-
resources: List of resources to place in bucket_type.
384405
bucket_type: The AppleResourceInfo field under which to collect the resources.
385406
owner: An optional string that has a unique identifier to the target that should own the
386407
resources. If an owner should be passed, it's usually equal to `str(ctx.label)`.
387408
parent_dir_param: Either a string/None or a struct used to calculate the value of
388409
parent_dir for each resource. If it is a struct, it will be considered a partial
389410
context, and will be invoked with partial.call().
411+
resources: List of resources to place in bucket_type or Dictionary of resources keyed by
412+
target to place in bucket_type. This dictionary is supported by the
413+
`resources.collect()` API.
390414
391415
Returns:
392416
An AppleResourceInfo provider with resources in the given bucket.
@@ -543,26 +567,28 @@ def _collect(*, attr, res_attrs = [], split_attr_keys = []):
543567
split_attr_keys: If defined, a list of 1:2+ transition keys to merge values from.
544568
545569
Returns:
546-
A list with all the collected resources for the target represented by attr.
570+
A dictionary keyed by target from the rule attr with the list of all collected resources.
547571
"""
548572
if not res_attrs:
549573
return []
550574

551-
files = []
575+
files_by_target = {}
552576
for res_attr in res_attrs:
553-
if hasattr(attr, res_attr):
554-
file_groups = [
555-
x.files.to_list()
556-
for x in _get_attr_as_list(
557-
attr = attr,
558-
nested_attr = res_attr,
559-
split_attr_keys = split_attr_keys,
560-
)
561-
if x.files
562-
]
563-
for file_group in file_groups:
564-
files.extend(file_group)
565-
return files
577+
if not hasattr(attr, res_attr):
578+
continue
579+
580+
targets_for_attr = _get_attr_as_list(
581+
attr = attr,
582+
nested_attr = res_attr,
583+
split_attr_keys = split_attr_keys,
584+
)
585+
for target in targets_for_attr:
586+
if not target.files:
587+
# Target does not export any file, ignore.
588+
continue
589+
files_by_target.setdefault(target, []).extend(target.files.to_list())
590+
591+
return files_by_target
566592

567593
def _merge_providers(*, default_owner = None, providers, validate_all_resources_owned = False):
568594
"""Merges multiple AppleResourceInfo providers into one.

apple/internal/tvos_rules.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ load(
102102
load(
103103
"@build_bazel_rules_apple//apple:providers.bzl",
104104
"AppleBundleInfo",
105+
"AppleFrameworkBundleInfo",
105106
"ApplePlatformInfo",
106107
"TvosApplicationBundleInfo",
107108
"TvosExtensionBundleInfo",
@@ -624,6 +625,7 @@ def _tvos_framework_impl(ctx):
624625
processor_result.output_groups,
625626
)
626627
),
628+
AppleFrameworkBundleInfo(),
627629
TvosFrameworkBundleInfo(),
628630
# TODO(b/228856372): Remove when downstream users are migrated off this provider.
629631
link_result.debug_outputs_provider,

0 commit comments

Comments
 (0)