Skip to content

Commit

Permalink
Support propagating frameworks via objc/swift_library data attribute.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
stravinskii authored and swiple-rules-gardener committed Mar 1, 2023
1 parent 1d7c1a1 commit f711be0
Show file tree
Hide file tree
Showing 15 changed files with 608 additions and 153 deletions.
2 changes: 1 addition & 1 deletion apple/internal/aspects/framework_provider_aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ visibility("//apple/...")
# List of attributes through which the aspect propagates. We include `runtime_deps` here as
# these are supported by `objc_library` for frameworks that should be present in the bundle, but not
# linked against.
# TODO(b/120205406): Migrate the `runtime_deps` use case to be referenced through `data` instead.
# TODO(b/120205406): Remove `runtime_deps` support to use objc_library/swift_library `data` instead.
_FRAMEWORK_PROVIDERS_ASPECT_ATTRS = ["deps", "frameworks", "runtime_deps"]

def _framework_provider_aspect_impl(target, ctx):
Expand Down
6 changes: 5 additions & 1 deletion apple/internal/aspects/resource_aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ load(
)
load(
"@build_bazel_rules_apple//apple:providers.bzl",
"AppleFrameworkBundleInfo",
"AppleResourceInfo",
)
load(
Expand Down Expand Up @@ -238,7 +239,10 @@ def _apple_resource_aspect_impl(target, ctx):
inherited_providers.extend([
x[AppleResourceInfo]
for x in getattr(ctx.rule.attr, attr)
if AppleResourceInfo in x
if AppleResourceInfo in x and
# Filter Apple framework targets to avoid propagating and bundling
# framework resources to the top-level target (eg. ios_application).
AppleFrameworkBundleInfo not in x
])
if inherited_providers and bundle_name:
# Nest the inherited resource providers within the bundle, if one is needed for this rule.
Expand Down
2 changes: 2 additions & 0 deletions apple/internal/ios_rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ load(
load(
"@build_bazel_rules_apple//apple:providers.bzl",
"AppleBundleInfo",
"AppleFrameworkBundleInfo",
"ApplePlatformInfo",
"IosAppClipBundleInfo",
"IosApplicationBundleInfo",
Expand Down Expand Up @@ -969,6 +970,7 @@ def _ios_framework_impl(ctx):

return [
DefaultInfo(files = processor_result.output_files),
AppleFrameworkBundleInfo(),
IosFrameworkBundleInfo(),
OutputGroupInfo(
**outputs.merge_output_groups(
Expand Down
19 changes: 15 additions & 4 deletions apple/internal/partials/resources.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ def _resources_partial_impl(
provider_field_to_action = {
"asset_catalogs": (resources_support.asset_catalogs, False),
"datamodels": (resources_support.datamodels, True),
"framework": (resources_support.apple_bundle(processor.location.framework), False),
"infoplists": (resources_support.infoplists, False),
"mlmodels": (resources_support.mlmodels, False),
"plists": (resources_support.plists_and_strings, False),
Expand All @@ -369,6 +370,7 @@ def _resources_partial_impl(
# List containing all the files that the processor will bundle in their
# configured location.
bundle_files = []
bundle_zips = []

fields = resources.populated_resource_fields(final_provider)

Expand Down Expand Up @@ -433,7 +435,10 @@ def _resources_partial_impl(
processing_args["swift_module"] = swift_module

result = processing_func(**processing_args)
bundle_files.extend(result.files)
if hasattr(result, "files"):
bundle_files.extend(result.files)
if hasattr(result, "archives"):
bundle_zips.extend(result.archives)
if hasattr(result, "infoplists"):
infoplists.extend(result.infoplists)

Expand Down Expand Up @@ -489,7 +494,11 @@ def _resources_partial_impl(
),
)

return struct(bundle_files = bundle_files, providers = [final_provider])
return struct(
bundle_files = bundle_files,
bundle_zips = bundle_zips,
providers = [final_provider],
)

def resources_partial(
*,
Expand All @@ -508,7 +517,7 @@ def resources_partial(
rule_label,
targets_to_avoid = [],
top_level_infoplists = [],
top_level_resources = [],
top_level_resources = {},
version,
version_keys_required = True):
"""Constructor for the resources processing partial.
Expand Down Expand Up @@ -541,7 +550,9 @@ def resources_partial(
targets_to_avoid: List of targets containing resources that should be deduplicated from the
target being processed.
top_level_infoplists: A list of collected resources found from Info.plist attributes.
top_level_resources: A list of collected resources found from resource attributes.
top_level_resources: A dictionary of collected resources found from resource attributes,
where keys are targets, and values are list of `File`s depsets. This can be obtained
using the `apple/internal/resources.collect` API.
version: A label referencing AppleBundleVersionInfo, if provided by the rule.
version_keys_required: Whether to validate that the Info.plist version keys are correctly
configured.
Expand Down
27 changes: 27 additions & 0 deletions apple/internal/partials/support/resources_support.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,34 @@ def _noop(
processed_origins = processed_origins,
)

def _apple_bundle(bundle_type):
"""Returns a function to register bundling of Apple bundles at their appropriate location.
Args:
bundle_type: The Apple bundle type to bundle for.
Returns:
A function to register bundling of an Apple bundle.
"""
if not hasattr(processor.location, bundle_type):
fail("Bundle type location not supported: ", bundle_type)

def _bundle_at_location(*, files, **_kwargs):
bundle = files.to_list().pop()
location = getattr(processor.location, bundle_type)

# If tree artifacts are enabled, set the bundle name as
# the parent directory. Otherwise, let bundletool unzip
# the bundle directly.
if bundle.is_directory:
parent_dir = paths.basename(bundle.short_path)
return struct(files = [(location, parent_dir, files)])
else:
return struct(archives = [(location, None, files)])

return _bundle_at_location

resources_support = struct(
apple_bundle = _apple_bundle,
asset_catalogs = _asset_catalogs,
datamodels = _datamodels,
infoplists = _infoplists,
Expand Down
168 changes: 97 additions & 71 deletions apple/internal/resources.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ This file provides methods to easily:

load(
"@build_bazel_rules_apple//apple:providers.bzl",
"AppleFrameworkBundleInfo",
"AppleResourceInfo",
)
load(
Expand Down Expand Up @@ -218,65 +219,74 @@ def _bucketize_data(
if allowed_buckets:
allowed_bucket_set = {k: None for k in allowed_buckets}

for resource in resources:
# Local cache of the resource short path since it gets used quite a bit below.
resource_short_path = resource.short_path
for target, target_resources in resources.items():
for resource in target_resources:
# Local cache of the resource short path since it gets used quite a bit below.
resource_short_path = resource.short_path

if owner:
owners.append((resource_short_path, owner))
else:
unowned_resources.append(resource_short_path)
if owner:
owners.append((resource_short_path, owner))
else:
unowned_resources.append(resource_short_path)

if types.is_string(parent_dir_param) or parent_dir_param == None:
parent = parent_dir_param
else:
parent = partial.call(partial = parent_dir_param, resource = resource)
if types.is_string(parent_dir_param) or parent_dir_param == None:
parent = parent_dir_param
else:
parent = partial.call(partial = parent_dir_param, resource = resource)

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

resource_swift_module = None
resource_depset = depset([resource])

# For each type of resource, place in the appropriate bucket.
if resource_short_path.endswith(".strings") or resource_short_path.endswith(".stringsdict"):
bucket_name = "strings"
elif resource_short_path.endswith(".storyboard"):
bucket_name = "storyboards"
resource_swift_module = swift_module
elif resource_short_path.endswith(".xib"):
bucket_name = "xibs"
resource_swift_module = swift_module
elif ".xcassets/" in resource_short_path or ".xcstickers/" in resource_short_path:
bucket_name = "asset_catalogs"
elif ".xcdatamodel" in resource_short_path or ".xcmappingmodel/" in resource_short_path:
bucket_name = "datamodels"
resource_swift_module = swift_module
elif ".atlas" in resource_short_path:
bucket_name = "texture_atlases"
elif resource_short_path.endswith(".png"):
# Process standalone pngs after asset_catalogs and texture_atlases so the latter can
# bucketed correctly.
bucket_name = "pngs"
elif resource_short_path.endswith(".plist"):
bucket_name = "plists"
elif resource_short_path.endswith(".mlmodel"):
bucket_name = "mlmodels"
else:
bucket_name = "unprocessed"

# If the allowed bucket list is not empty, and the bucket is not allowed, change the bucket
# to unprocessed instead.
if allowed_bucket_set and bucket_name not in allowed_bucket_set:
bucket_name = "unprocessed"
resource_swift_module = None
resource_depset = depset([resource])

# For each type of resource, place in the appropriate bucket.
if AppleFrameworkBundleInfo in target:
if "framework.dSYM/" in resource_short_path or resource.extension == "linkmap":
# TODO(b/271168739): Propagate AppleDebugSymbolsInfo and _AppleDebugInfo providers.
# Ignore dSYM bundle and linkmap since the debug symbols partial is
# responsible for propagating this up the dependency graph.
continue
bucket_name = "framework"
elif (resource_short_path.endswith(".strings") or
resource_short_path.endswith(".stringsdict")):
bucket_name = "strings"
elif resource_short_path.endswith(".storyboard"):
bucket_name = "storyboards"
resource_swift_module = swift_module
elif resource_short_path.endswith(".xib"):
bucket_name = "xibs"
resource_swift_module = swift_module
elif ".xcassets/" in resource_short_path or ".xcstickers/" in resource_short_path:
bucket_name = "asset_catalogs"
elif ".xcdatamodel" in resource_short_path or ".xcmappingmodel/" in resource_short_path:
bucket_name = "datamodels"
resource_swift_module = swift_module
elif ".atlas" in resource_short_path:
bucket_name = "texture_atlases"
elif resource_short_path.endswith(".png"):
# Process standalone pngs after asset_catalogs and texture_atlases so the latter can
# bucketed correctly.
bucket_name = "pngs"
elif resource_short_path.endswith(".plist"):
bucket_name = "plists"
elif resource_short_path.endswith(".mlmodel"):
bucket_name = "mlmodels"
else:
bucket_name = "unprocessed"

buckets.setdefault(bucket_name, []).append(
(parent, resource_swift_module, resource_depset),
)
# If the allowed bucket list is not empty, and the bucket is not allowed, change the
# bucket to unprocessed instead.
if allowed_bucket_set and bucket_name not in allowed_bucket_set:
bucket_name = "unprocessed"
resource_swift_module = None

buckets.setdefault(bucket_name, []).append(
(parent, resource_swift_module, resource_depset),
)

return (
owners,
Expand Down Expand Up @@ -338,7 +348,9 @@ def _bucketize_typed_data(*, bucket_type, owner = None, parent_dir_param = None,
parent_dir_param: Either a string/None or a struct used to calculate the value of
parent_dir for each resource. If it is a struct, it will be considered a partial
context, and will be invoked with partial.call().
resources: List of resources to place in bucket_type.
resources: List of resources to place in bucket_type or Dictionary of resources keyed by
target to place in bucket_type. This dictionary is supported by the
`resources.collect()` API.
Returns:
A tuple with a list of owners, a list of "unowned" resources, and a dictionary with
Expand All @@ -348,7 +360,17 @@ def _bucketize_typed_data(*, bucket_type, owner = None, parent_dir_param = None,
owners = []
unowned_resources = []

for resource in resources:
all_resources = []
if type(resources) == "list":
all_resources = resources
elif type(resources) == "dict":
for target_resources in resources.values():
all_resources.extend(target_resources)
else:
fail("Internal error: 'resources' should be either a list or dictionary.\n" +
"This is most likely a rules_apple bug, please file a bug with reproduction steps")

for resource in all_resources:
resource_short_path = resource.short_path
if owner:
owners.append((resource_short_path, owner))
Expand Down Expand Up @@ -380,13 +402,15 @@ def _bucketize_typed(resources, bucket_type, *, owner = None, parent_dir_param =
parent_dir_param when available.
Args:
resources: List of resources to place in bucket_type.
bucket_type: The AppleResourceInfo field under which to collect the resources.
owner: An optional string that has a unique identifier to the target that should own the
resources. If an owner should be passed, it's usually equal to `str(ctx.label)`.
parent_dir_param: Either a string/None or a struct used to calculate the value of
parent_dir for each resource. If it is a struct, it will be considered a partial
context, and will be invoked with partial.call().
resources: List of resources to place in bucket_type or Dictionary of resources keyed by
target to place in bucket_type. This dictionary is supported by the
`resources.collect()` API.
Returns:
An AppleResourceInfo provider with resources in the given bucket.
Expand Down Expand Up @@ -543,26 +567,28 @@ def _collect(*, attr, res_attrs = [], split_attr_keys = []):
split_attr_keys: If defined, a list of 1:2+ transition keys to merge values from.
Returns:
A list with all the collected resources for the target represented by attr.
A dictionary keyed by target from the rule attr with the list of all collected resources.
"""
if not res_attrs:
return []

files = []
files_by_target = {}
for res_attr in res_attrs:
if hasattr(attr, res_attr):
file_groups = [
x.files.to_list()
for x in _get_attr_as_list(
attr = attr,
nested_attr = res_attr,
split_attr_keys = split_attr_keys,
)
if x.files
]
for file_group in file_groups:
files.extend(file_group)
return files
if not hasattr(attr, res_attr):
continue

targets_for_attr = _get_attr_as_list(
attr = attr,
nested_attr = res_attr,
split_attr_keys = split_attr_keys,
)
for target in targets_for_attr:
if not target.files:
# Target does not export any file, ignore.
continue
files_by_target.setdefault(target, []).extend(target.files.to_list())

return files_by_target

def _merge_providers(*, default_owner = None, providers, validate_all_resources_owned = False):
"""Merges multiple AppleResourceInfo providers into one.
Expand Down
2 changes: 2 additions & 0 deletions apple/internal/tvos_rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ load(
load(
"@build_bazel_rules_apple//apple:providers.bzl",
"AppleBundleInfo",
"AppleFrameworkBundleInfo",
"ApplePlatformInfo",
"TvosApplicationBundleInfo",
"TvosExtensionBundleInfo",
Expand Down Expand Up @@ -624,6 +625,7 @@ def _tvos_framework_impl(ctx):
processor_result.output_groups,
)
),
AppleFrameworkBundleInfo(),
TvosFrameworkBundleInfo(),
# TODO(b/228856372): Remove when downstream users are migrated off this provider.
link_result.debug_outputs_provider,
Expand Down
Loading

1 comment on commit f711be0

@keith
Copy link
Member

@keith keith commented on f711be0 Mar 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.