diff --git a/apple/internal/ios_rules.bzl b/apple/internal/ios_rules.bzl index 61f165d63a..f68c30f916 100644 --- a/apple/internal/ios_rules.bzl +++ b/apple/internal/ios_rules.bzl @@ -79,6 +79,7 @@ load( "AppleBinaryInfo", "IosAppClipBundleInfo", "IosApplicationBundleInfo", + "IosBundleBundleInfo", "IosExtensionBundleInfo", "IosFrameworkBundleInfo", "IosImessageApplicationBundleInfo", @@ -116,8 +117,8 @@ def _ios_application_impl(ctx): bundle_id = ctx.attr.bundle_id bundle_name, bundle_extension = bundling_support.bundle_full_name_from_rule_ctx(ctx) executable_name = bundling_support.executable_name(ctx) - bundle_verification_targets = [struct(target = ext) for ext in ctx.attr.extensions] - embeddable_targets = ctx.attr.frameworks + ctx.attr.extensions + ctx.attr.app_clips + bundle_verification_targets = [struct(target = bundle) for bundle in ctx.attr.extensions + ctx.attr.bundles] + embeddable_targets = ctx.attr.frameworks + ctx.attr.extensions + ctx.attr.app_clips + ctx.attr.bundles entitlements = entitlements_support.entitlements( entitlements_attr = getattr(ctx.attr, "entitlements", None), entitlements_file = getattr(ctx.file, "entitlements", None), @@ -208,7 +209,7 @@ def _ios_application_impl(ctx): package_symbols = True, platform_prerequisites = platform_prerequisites, rule_executables = rule_executables, - targets = ctx.attr.deps + ctx.attr.extensions + ctx.attr.frameworks, + targets = ctx.attr.bundles + ctx.attr.deps + ctx.attr.extensions + ctx.attr.frameworks, ), partials.resources_partial( actions = actions, @@ -354,13 +355,12 @@ def _ios_app_clip_impl(ctx): bundle_id = ctx.attr.bundle_id bundle_name, bundle_extension = bundling_support.bundle_full_name_from_rule_ctx(ctx) executable_name = bundling_support.executable_name(ctx) - embeddable_targets = ctx.attr.frameworks + embeddable_targets = ctx.attr.frameworks + ctx.attr.bundles features = features_support.compute_enabled_features( requested_features = ctx.features, unsupported_features = ctx.disabled_features, ) platform_prerequisites = platform_support.platform_prerequisites_from_rule_ctx(ctx) - embeddable_targets = ctx.attr.frameworks entitlements = entitlements_support.entitlements( entitlements_attr = getattr(ctx.attr, "entitlements", None), entitlements_file = getattr(ctx.file, "entitlements", None), @@ -547,6 +547,153 @@ def _ios_app_clip_impl(ctx): link_result.binary_provider, ] + processor_result.providers +def _ios_bundle_impl(ctx): + """Implementation of ios_bundle.""" + link_result = linking_support.register_linking_action(ctx) + binary_artifact = link_result.binary_provider.binary + debug_outputs_provider = link_result.debug_outputs_provider + + actions = ctx.actions + bin_root_path = ctx.bin_dir.path + bundle_id = ctx.attr.bundle_id + bundle_name, bundle_extension = bundling_support.bundle_full_name_from_rule_ctx(ctx) + executable_name = bundling_support.executable_name(ctx) + embeddable_targets = ctx.attr.frameworks + entitlements = entitlements_support.entitlements( + entitlements_attr = getattr(ctx.attr, "entitlements", None), + entitlements_file = getattr(ctx.file, "entitlements", None), + ) + features = features_support.compute_enabled_features( + requested_features = ctx.features, + unsupported_features = ctx.disabled_features, + ) + label = ctx.label + platform_prerequisites = platform_support.platform_prerequisites_from_rule_ctx(ctx) + predeclared_outputs = ctx.outputs + rule_descriptor = rule_support.rule_descriptor(ctx) + rule_executables = ctx.executable + + archive = outputs.archive( + actions = actions, + bundle_extension = bundle_extension, + bundle_name = bundle_name, + executable_name = executable_name, + platform_prerequisites = platform_prerequisites, + predeclared_outputs = predeclared_outputs, + ) + + processor_partials = [ + partials.apple_bundle_info_partial( + actions = actions, + bundle_extension = bundle_extension, + bundle_id = bundle_id, + bundle_name = bundle_name, + executable_name = executable_name, + entitlements = entitlements, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + predeclared_outputs = predeclared_outputs, + product_type = rule_descriptor.product_type, + ), + partials.binary_partial( + actions = actions, + binary_artifact = binary_artifact, + executable_name = executable_name, + label_name = label.name, + ), + partials.clang_rt_dylibs_partial( + actions = actions, + binary_artifact = binary_artifact, + clangrttool = ctx.executable._clangrttool, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + ), + partials.debug_symbols_partial( + actions = actions, + bin_root_path = bin_root_path, + bundle_extension = bundle_extension, + bundle_name = bundle_name, + debug_dependencies = embeddable_targets, + debug_outputs_provider = debug_outputs_provider, + dsym_info_plist_template = ctx.file._dsym_info_plist_template, + executable_name = executable_name, + platform_prerequisites = platform_prerequisites, + rule_label = label, + ), + partials.embedded_bundles_partial( + platform_prerequisites = platform_prerequisites, + embeddable_targets = embeddable_targets, + custom_bundles = {getattr(ctx.attr, "bundle_location", ""): [archive]}, + ), + partials.resources_partial( + actions = actions, + bundle_extension = bundle_extension, + bundle_id = bundle_id, + bundle_name = bundle_name, + environment_plist = ctx.file._environment_plist, + executable_name = executable_name, + launch_storyboard = None, + platform_prerequisites = platform_prerequisites, + plist_attrs = ["infoplists"], + rule_attrs = ctx.attr, + rule_descriptor = rule_descriptor, + rule_executables = rule_executables, + rule_label = label, + top_level_attrs = [ + "app_icons", + "strings", + "resources", + ], + ), + partials.swift_dylibs_partial( + actions = actions, + binary_artifact = binary_artifact, + dependency_targets = embeddable_targets, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + swift_stdlib_tool = ctx.executable._swift_stdlib_tool, + ), + ] + + if platform_prerequisites.platform.is_device: + processor_partials.append( + partials.provisioning_profile_partial( + actions = actions, + profile_artifact = ctx.file.provisioning_profile, + rule_label = label, + ), + ) + + processor_result = processor.process( + ctx = ctx, + actions = actions, + bundle_extension = bundle_extension, + bundle_name = bundle_name, + entitlements = entitlements, + executable_name = executable_name, + partials = processor_partials, + platform_prerequisites = platform_prerequisites, + predeclared_outputs = predeclared_outputs, + provisioning_profile = getattr(ctx.file, "provisioning_profile", None), + rule_descriptor = rule_descriptor, + rule_executables = rule_executables, + rule_label = label, + ) + + return [ + DefaultInfo( + files = processor_result.output_files, + ), + IosBundleBundleInfo(), + OutputGroupInfo( + **outputs.merge_output_groups( + link_result.output_groups, + processor_result.output_groups, + ) + ), + ] + processor_result.providers + def _ios_dylib_impl(ctx): """Implementation of the ios_dylib rule.""" link_result = linking_support.register_linking_action(ctx) @@ -1529,6 +1676,13 @@ ios_app_clip = rule_factory.create_apple_bundling_rule( doc = "Builds and bundles an iOS App Clip.", ) +ios_bundle = rule_factory.create_apple_bundling_rule( + implementation = _ios_bundle_impl, + platform_type = "ios", + product_type = apple_product_type.bundle, + doc = "Builds and bundles an iOS Loadable Bundle.", +) + ios_dylib = rule_factory.create_apple_binary_rule( implementation = _ios_dylib_impl, platform_type = "ios", diff --git a/apple/internal/partials/embedded_bundles.bzl b/apple/internal/partials/embedded_bundles.bzl index ffcba4f46b..364c2bf320 100644 --- a/apple/internal/partials/embedded_bundles.bzl +++ b/apple/internal/partials/embedded_bundles.bzl @@ -22,6 +22,14 @@ load( "@build_bazel_rules_apple//apple/internal:processor.bzl", "processor", ) +load( + "@bazel_skylib//lib:new_sets.bzl", + "sets", +) +load( + "@bazel_skylib//lib:paths.bzl", + "paths", +) load( "@bazel_skylib//lib:partial.bzl", "partial", @@ -35,6 +43,10 @@ top-level bundling rule will need to package.""", "app_clips": """ A depset with the zipped archives of bundles that need to be expanded into the AppClips section of the packaging bundle.""", + "custom_bundles": """ +A dict of depsets of zipped archives of bundles that need to be expanded into +custom locations of the packaging bundle, where the key is the path to the +custom location.""", "frameworks": """ A depset with the zipped archives of bundles that need to be expanded into the Frameworks section of the packaging bundle.""", @@ -122,6 +134,54 @@ def _embedded_bundles_partial_impl( transitive = transitive_bundles.get(bundle_type, []), ) + # Process custom bundle locations + transitive_custom_bundles = dict() + for provider in embeddable_providers: + if hasattr(provider, "custom_bundles"): + custom_bundles = getattr(provider, "custom_bundles") + for bundle_location, input_bundles in custom_bundles.items(): + transitive_custom_bundles.setdefault( + bundle_location, + default = [], + ).append(input_bundles) + + if bundle_embedded_bundles: + # If this partial is configured to embed the transitive embeddable partials, collect + # them into a list to be returned by this partial. + for bundle_location, input_bundles_array in transitive_custom_bundles.items(): + transitive_depset = depset(transitive = input_bundles_array) + + # With tree artifacts, we need to set the parent_dir of the file to be the basename + # of the file. Expanding these depsets shouldn't be too much work as there shouldn't + # be too many embedded targets per top-level bundle. + if is_experimental_tree_artifact_enabled(config_vars = config_vars): + for bundle in transitive_depset.to_list(): + bundles_to_embed.append(( + processor.location.loadable_bundle, + paths.join(bundle_location, bundle.basename), + depset([bundle]) + )) + else: + bundles_to_embed.append((processor.location.loadable_bundle, bundle_location, transitive_depset)) + + # Clear the transitive list of custom bundles since they will be packaged + # in the bundle processing this partial and do not need to be propagated. + transitive_custom_bundles = dict() + + # Construct the _AppleEmbeddableInfo provider field for the bundle type being processed. + # At this step, we inject the bundles that are inputs to this partial, since that propagates + # the info for a higher level bundle to embed this bundle. + direct_custom_bundles = input_bundles_by_type.get("custom_bundles", {}) + bundle_locations = sets.to_list(sets.make(direct_custom_bundles.keys() + transitive_custom_bundles.keys())) + if bundle_locations: + embeddedable_info_fields["custom_bundles"] = { + bundle_location: depset( + direct_custom_bundles.get(bundle_location, []), + transitive = transitive_custom_bundles.get(bundle_location, []), + ) + for bundle_location in bundle_locations + } + # Construct the output files fields. If tree artifacts is enabled, propagate the bundles to # package into bundle_files. Otherwise, propagate through bundle_zips so that they can be # extracted. @@ -167,6 +227,7 @@ def embedded_bundles_partial( *, app_clips = [], bundle_embedded_bundles = False, + custom_bundles = {}, embeddable_targets = [], frameworks = [], platform_prerequisites, @@ -187,6 +248,8 @@ def embedded_bundles_partial( bundle_embedded_bundles: If True, this target will embed all transitive embeddable_bundles _only_ propagated through the targets given in embeddable_targets. If False, the embeddable bundles will be propagated downstream for a top level target to bundle them. + custom_bundles: Dictionary of list of bundles that should be propagated downstream for a + top level target to bundle inside directories named by the dictionary key. embeddable_targets: The list of targets that propagate embeddable bundles to bundle or propagate. frameworks: List of framework bundles that should be propagated downstream for a top level @@ -208,6 +271,7 @@ def embedded_bundles_partial( _embedded_bundles_partial_impl, app_clips = app_clips, bundle_embedded_bundles = bundle_embedded_bundles, + custom_bundles = custom_bundles, embeddable_targets = embeddable_targets, frameworks = frameworks, platform_prerequisites = platform_prerequisites, diff --git a/apple/internal/processor.bzl b/apple/internal/processor.bzl index 2b1e38b90e..e2940b740c 100644 --- a/apple/internal/processor.bzl +++ b/apple/internal/processor.bzl @@ -112,6 +112,7 @@ _LOCATION_ENUM = struct( bundle = "bundle", content = "content", framework = "framework", + loadable_bundle = "loadable_bundle", plugin = "plugin", resource = "resource", watch = "watch", @@ -198,6 +199,7 @@ def _archive_paths( contents_path, rule_descriptor.bundle_locations.contents_relative_frameworks, ), + _LOCATION_ENUM.loadable_bundle: contents_path, _LOCATION_ENUM.plugin: paths.join( contents_path, rule_descriptor.bundle_locations.contents_relative_plugins, @@ -337,6 +339,7 @@ def _bundle_partial_outputs_files( source_path = source.path if (location == _LOCATION_ENUM.app_clip or location == _LOCATION_ENUM.framework or + location == _LOCATION_ENUM.loadable_bundle or location == _LOCATION_ENUM.plugin): # This fixes bundling for other bundled products built by Bazel source_path = bundletool_output_file_path( diff --git a/apple/internal/rule_factory.bzl b/apple/internal/rule_factory.bzl index a6ba1dc257..b6679d6de9 100644 --- a/apple/internal/rule_factory.bzl +++ b/apple/internal/rule_factory.bzl @@ -66,6 +66,7 @@ load( "AppleTestRunnerInfo", "IosAppClipBundleInfo", "IosApplicationBundleInfo", + "IosBundleBundleInfo", "IosExtensionBundleInfo", "IosFrameworkBundleInfo", "IosImessageApplicationBundleInfo", @@ -636,6 +637,12 @@ fashion, such as a Cocoapod. A list of iOS app clips to include in the final application bundle. """, ), + "bundles": attr.label_list( + providers = [ + [AppleBundleInfo, IosBundleBundleInfo], + ], + doc = "A list of iOS loadable bundles to include in the final application bundle.", + ), "extensions": attr.label_list( providers = [[AppleBundleInfo, IosExtensionBundleInfo]], doc = """ @@ -690,6 +697,15 @@ Info.plist under the key `UILaunchStoryboardName`. providers = required_providers, ), }) + elif rule_descriptor.product_type == apple_product_type.bundle: + attrs.append({ + "bundle_location": attr.string( + mandatory = False, + doc = """ +The directiory within the packaging bundle that this bundle should be placed. +""", + ), + }) # TODO(kaipi): Once all platforms have framework rules, move this into # _common_binary_linking_attrs(). diff --git a/apple/internal/rule_support.bzl b/apple/internal/rule_support.bzl index 4bc241552e..c964e1c610 100644 --- a/apple/internal/rule_support.bzl +++ b/apple/internal/rule_support.bzl @@ -258,6 +258,24 @@ _RULE_TYPE_DESCRIPTORS = { "@executable_path/Frameworks", ], ), + # ios_bundle + apple_product_type.bundle: _describe_rule_type( + allowed_device_families = ["iphone", "ipad"], + app_icon_parent_extension = ".xcassets", + app_icon_extension = ".appiconset", + binary_type = "loadable_bundle", + bundle_extension = ".bundle", + bundle_package_type = bundle_package_type.bundle, + deps_cfg = apple_common.multi_arch_split, + mandatory_families = True, + product_type = apple_product_type.bundle, + rpaths = [ + # Bundle binaries are loaded from the executable location and application binaries + # live in Application.app/Application + # Frameworks are packaged in Application.app/Frameworks + "@executable_path/Frameworks", + ], + ), # ios_dylib apple_product_type.dylib: _describe_rule_type( allowed_device_families = ["iphone", "ipad"], diff --git a/apple/ios.bzl b/apple/ios.bzl index 8efc221f56..1b3aba57be 100644 --- a/apple/ios.bzl +++ b/apple/ios.bzl @@ -41,6 +41,7 @@ load( "@build_bazel_rules_apple//apple/internal:ios_rules.bzl", _ios_app_clip = "ios_app_clip", _ios_application = "ios_application", + _ios_bundle = "ios_bundle", _ios_dylib = "ios_dylib", _ios_extension = "ios_extension", _ios_framework = "ios_framework", @@ -80,6 +81,25 @@ def ios_app_clip(name, **kwargs): **bundling_args ) +def ios_bundle(name, **kwargs): + # buildifier: disable=function-docstring-args + """Packages an iOS loadable bundle.""" + binary_args = dict(kwargs) + + bundling_args = binary_support.add_entitlements_and_swift_linkopts( + name, + platform_type = str(apple_common.platform_type.ios), + product_type = apple_product_type.bundle, + exported_symbols_lists = binary_args.pop("exported_symbols_lists", None), + **binary_args + ) + + _ios_bundle( + name = name, + dylibs = kwargs.get("frameworks", []), + **bundling_args + ) + def ios_dylib(name, **kwargs): # buildifier: disable=function-docstring-args """Builds a iOS dylib.""" diff --git a/apple/providers.bzl b/apple/providers.bzl index 015e9b9527..f6bcf1577f 100644 --- a/apple/providers.bzl +++ b/apple/providers.bzl @@ -298,6 +298,19 @@ is an iOS app clip should use this provider to describe that requirement. """, ) +IosBundleBundleInfo = provider( + fields = [], + doc = """ +Denotes that a target is an iOS loadable bundle. + +This provider does not contain any fields of its own at this time but is used as +a "marker" to indicate that a target is specifically an iOS loadable bundle +(and not some other Apple bundle). Rule authors who wish to require that a +dependency is an iOS loadable bundle should use this provider to describe that +requirement. +""", +) + IosExtensionBundleInfo = provider( fields = [], doc = """