diff --git a/apple/internal/ios_rules.bzl b/apple/internal/ios_rules.bzl index 9b1ef7c6be..37577e9ecf 100644 --- a/apple/internal/ios_rules.bzl +++ b/apple/internal/ios_rules.bzl @@ -83,6 +83,7 @@ load( "AppleSupportToolchainInfo", "IosAppClipBundleInfo", "IosApplicationBundleInfo", + "IosBundleBundleInfo", "IosExtensionBundleInfo", "IosFrameworkBundleInfo", "IosImessageApplicationBundleInfo", @@ -127,8 +128,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), @@ -220,7 +221,7 @@ def _ios_application_impl(ctx): platform_prerequisites = platform_prerequisites, provisioning_profile = getattr(ctx.file, "provisioning_profile", None), rule_descriptor = rule_descriptor, - 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, @@ -372,7 +373,7 @@ 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.bundles + ctx.attr.frameworks features = features_support.compute_enabled_features( requested_features = ctx.features, unsupported_features = ctx.disabled_features, @@ -567,6 +568,174 @@ def _ios_app_clip_impl(ctx): link_result.binary_provider, ] + processor_result.providers +def _ios_bundle_impl(ctx): + """Implementation of ios_bundle.""" + extra_linkopts = [] + if ctx.attr.extension_safe: + extra_linkopts.append("-fapplication-extension") + + link_result = linking_support.register_linking_action( + ctx, + extra_linkopts = extra_linkopts, + stamp = ctx.attr.stamp, + ) + binary_artifact = link_result.binary_provider.binary + debug_outputs_provider = link_result.debug_outputs_provider + + actions = ctx.actions + apple_toolchain_info = ctx.attr._toolchain[AppleSupportToolchainInfo] + 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) + 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) + + 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.bitcode_symbols_partial( + actions = actions, + binary_artifact = binary_artifact, + debug_outputs_provider = debug_outputs_provider, + dependency_targets = ctx.attr.frameworks, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + ), + partials.clang_rt_dylibs_partial( + actions = actions, + apple_toolchain_info = apple_toolchain_info, + binary_artifact = binary_artifact, + 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 = ctx.attr.frameworks, + debug_outputs_provider = debug_outputs_provider, + dsym_info_plist_template = apple_toolchain_info.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 = ctx.attr.frameworks, + custom_bundles = {getattr(ctx.attr, "bundle_location", ""): [archive]}, + ), + partials.extension_safe_validation_partial( + is_extension_safe = ctx.attr.extension_safe, + rule_label = label, + targets_to_validate = ctx.attr.frameworks, + ), + partials.resources_partial( + actions = actions, + apple_toolchain_info = apple_toolchain_info, + 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_label = label, + targets_to_avoid = ctx.attr.frameworks, + top_level_attrs = [ + "app_icons", + "strings", + "resources", + ], + ), + partials.swift_dylibs_partial( + actions = actions, + apple_toolchain_info = apple_toolchain_info, + binary_artifact = binary_artifact, + dependency_targets = ctx.attr.frameworks, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + ), + ] + + 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( + actions = actions, + apple_toolchain_info = apple_toolchain_info, + bundle_extension = bundle_extension, + bundle_name = bundle_name, + codesignopts = codesigning_support.codesignopts_from_rule_ctx(ctx), + entitlements = entitlements, + executable_name = executable_name, + ipa_post_processor = ctx.executable.ipa_post_processor, + partials = processor_partials, + platform_prerequisites = platform_prerequisites, + predeclared_outputs = predeclared_outputs, + process_and_sign_template = apple_toolchain_info.process_and_sign_template, + provisioning_profile = getattr(ctx.file, "provisioning_profile", None), + rule_descriptor = rule_descriptor, + 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_framework_impl(ctx): """Experimental implementation of ios_framework.""" @@ -1687,6 +1856,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_extension = rule_factory.create_apple_bundling_rule( implementation = _ios_extension_impl, platform_type = "ios", diff --git a/apple/internal/partials/embedded_bundles.bzl b/apple/internal/partials/embedded_bundles.bzl index ad1d48f809..692728e0cf 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.""", @@ -120,6 +132,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. @@ -165,6 +225,7 @@ def embedded_bundles_partial( *, app_clips = [], bundle_embedded_bundles = False, + custom_bundles = {}, embeddable_targets = [], frameworks = [], platform_prerequisites, @@ -185,6 +246,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 @@ -206,6 +269,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 df488675db..e19c83b928 100644 --- a/apple/internal/processor.bzl +++ b/apple/internal/processor.bzl @@ -108,6 +108,7 @@ _LOCATION_ENUM = struct( bundle = "bundle", content = "content", framework = "framework", + loadable_bundle = "loadable_bundle", plugin = "plugin", resource = "resource", watch = "watch", @@ -199,6 +200,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, diff --git a/apple/internal/rule_factory.bzl b/apple/internal/rule_factory.bzl index b1e2f628a2..de392c31fc 100644 --- a/apple/internal/rule_factory.bzl +++ b/apple/internal/rule_factory.bzl @@ -74,6 +74,7 @@ load( "AppleTestRunnerInfo", "IosAppClipBundleInfo", "IosApplicationBundleInfo", + "IosBundleBundleInfo", "IosExtensionBundleInfo", "IosFrameworkBundleInfo", "IosImessageApplicationBundleInfo", @@ -601,6 +602,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 = """ @@ -665,6 +672,22 @@ 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. +""", + ), + "extension_safe": attr.bool( + default = False, + doc = """ +If true, compiles and links this framework with `-application-extension`, restricting the binary to +use only extension-safe APIs. +""", + ), + }) # 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 689c02789e..0831e4502b 100644 --- a/apple/internal/rule_support.bzl +++ b/apple/internal/rule_support.bzl @@ -259,6 +259,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_extension apple_product_type.app_extension: _describe_rule_type( allowed_device_families = ["iphone", "ipad"], diff --git a/apple/ios.bzl b/apple/ios.bzl index eee6c74769..f4d3070c92 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_dynamic_framework = "ios_dynamic_framework", _ios_extension = "ios_extension", _ios_framework = "ios_framework", @@ -80,6 +81,26 @@ 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", []), + extension_safe = kwargs.get("extension_safe"), + **bundling_args + ) + def ios_extension(name, **kwargs): """Builds and bundles an iOS application extension.""" bundling_args = binary_support.add_entitlements_and_swift_linkopts( diff --git a/apple/providers.bzl b/apple/providers.bzl index 8834d32226..8774ac0995 100644 --- a/apple/providers.bzl +++ b/apple/providers.bzl @@ -361,6 +361,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 = """