diff --git a/apple/internal/apple_product_type.bzl b/apple/internal/apple_product_type.bzl index 76c8bce2e5..b76012c5a2 100644 --- a/apple/internal/apple_product_type.bzl +++ b/apple/internal/apple_product_type.bzl @@ -89,6 +89,7 @@ apple_product_type = struct( app_extension = "com.apple.product-type.app-extension", bundle = "com.apple.product-type.bundle", dylib = "com.apple.product-type.library.dynamic", + extensionkit_extension = "com.apple.product-type.extensionkit-extension", framework = "com.apple.product-type.framework", kernel_extension = "com.apple.product-type.kernel-extension", messages_application = "com.apple.product-type.application.messages", diff --git a/apple/internal/ios_rules.bzl b/apple/internal/ios_rules.bzl index 86cd88293e..bc8395fce9 100644 --- a/apple/internal/ios_rules.bzl +++ b/apple/internal/ios_rules.bzl @@ -950,13 +950,24 @@ def _ios_extension_impl(ctx): ], ) + product_type = rule_descriptor.product_type + if ctx.attr.extensionkit_extension: + bundle_location = processor.location.extension + product_type = apple_product_type.extensionkit_extension + extensionkit_keys_required = True + nsextension_keys_required = False + else: + bundle_location = processor.location.plugin + extensionkit_keys_required = False + nsextension_keys_required = True + entitlements = entitlements_support.process_entitlements( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, bundle_id = bundle_id, entitlements_file = ctx.file.entitlements, platform_prerequisites = platform_prerequisites, - product_type = rule_descriptor.product_type, + product_type = product_type, provisioning_profile = provisioning_profile, rule_label = label, validation_mode = ctx.attr.entitlements_validation, @@ -990,11 +1001,18 @@ def _ios_extension_impl(ctx): predeclared_outputs = predeclared_outputs, ) + if ctx.attr.extensionkit_extension: + plugins = [] + extensions = [archive_for_embedding] + else: + plugins = [archive_for_embedding] + extensions = [] + processor_partials = [ partials.app_assets_validation_partial( app_icons = ctx.files.app_icons, platform_prerequisites = platform_prerequisites, - product_type = rule_descriptor.product_type, + product_type = product_type, ), partials.apple_bundle_info_partial( actions = actions, @@ -1007,7 +1025,7 @@ def _ios_extension_impl(ctx): label_name = label.name, platform_prerequisites = platform_prerequisites, predeclared_outputs = predeclared_outputs, - product_type = rule_descriptor.product_type, + product_type = product_type, ), partials.binary_partial( actions = actions, @@ -1028,7 +1046,7 @@ def _ios_extension_impl(ctx): actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, bundle_extension = bundle_extension, - bundle_location = processor.location.plugin, + bundle_location = bundle_location, bundle_name = bundle_name, embed_target_dossiers = False, embedded_targets = ctx.attr.frameworks, @@ -1061,7 +1079,8 @@ def _ios_extension_impl(ctx): partials.embedded_bundles_partial( embeddable_targets = ctx.attr.frameworks, platform_prerequisites = platform_prerequisites, - plugins = [archive_for_embedding], + plugins = plugins, + extensions = extensions, ), partials.extension_safe_validation_partial( is_extension_safe = True, @@ -1076,7 +1095,9 @@ def _ios_extension_impl(ctx): bundle_name = bundle_name, environment_plist = ctx.file._environment_plist, executable_name = executable_name, + extensionkit_keys_required = extensionkit_keys_required, launch_storyboard = None, + nsextension_keys_required = nsextension_keys_required, platform_prerequisites = platform_prerequisites, resource_deps = resource_deps, rule_descriptor = rule_descriptor, diff --git a/apple/internal/macos_rules.bzl b/apple/internal/macos_rules.bzl index c1d7ab7571..8097063a8f 100644 --- a/apple/internal/macos_rules.bzl +++ b/apple/internal/macos_rules.bzl @@ -562,13 +562,24 @@ def _macos_extension_impl(ctx): ], ) + product_type = rule_descriptor.product_type + if ctx.attr.extensionkit_extension: + bundle_location = processor.location.extension + product_type = apple_product_type.extensionkit_extension + extensionkit_keys_required = True + nsextension_keys_required = False + else: + bundle_location = processor.location.plugin + extensionkit_keys_required = False + nsextension_keys_required = True + entitlements = entitlements_support.process_entitlements( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, bundle_id = bundle_id, entitlements_file = ctx.file.entitlements, platform_prerequisites = platform_prerequisites, - product_type = rule_descriptor.product_type, + product_type = product_type, provisioning_profile = provisioning_profile, rule_label = label, validation_mode = ctx.attr.entitlements_validation, @@ -583,7 +594,7 @@ def _macos_extension_impl(ctx): binary_artifact = link_result.binary debug_outputs = linking_support.debug_outputs_by_architecture(link_result.outputs) - archive = outputs.archive( + archive_for_embedding = outputs.archive( actions = actions, bundle_name = bundle_name, bundle_extension = bundle_extension, @@ -591,6 +602,13 @@ def _macos_extension_impl(ctx): predeclared_outputs = predeclared_outputs, ) + if ctx.attr.extensionkit_extension: + plugins = [] + extensions = [archive_for_embedding] + else: + plugins = [archive_for_embedding] + extensions = [] + processor_partials = [ partials.apple_bundle_info_partial( actions = actions, @@ -603,7 +621,7 @@ def _macos_extension_impl(ctx): label_name = label.name, platform_prerequisites = platform_prerequisites, predeclared_outputs = predeclared_outputs, - product_type = rule_descriptor.product_type, + product_type = product_type, ), partials.binary_partial( actions = actions, @@ -625,7 +643,7 @@ def _macos_extension_impl(ctx): actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, bundle_extension = bundle_extension, - bundle_location = processor.location.plugin, + bundle_location = bundle_location, bundle_name = bundle_name, embed_target_dossiers = False, entitlements = entitlements.codesigning, @@ -647,7 +665,8 @@ def _macos_extension_impl(ctx): ), partials.embedded_bundles_partial( platform_prerequisites = platform_prerequisites, - plugins = [archive], + plugins = plugins, + extensions = extensions, ), partials.macos_additional_contents_partial( additional_contents = ctx.attr.additional_contents, @@ -660,7 +679,9 @@ def _macos_extension_impl(ctx): bundle_name = bundle_name, environment_plist = ctx.file._environment_plist, executable_name = executable_name, + extensionkit_keys_required = extensionkit_keys_required, launch_storyboard = None, + nsextension_keys_required = nsextension_keys_required, platform_prerequisites = platform_prerequisites, resource_deps = resource_deps, rule_descriptor = rule_descriptor, diff --git a/apple/internal/partials/codesigning_dossier.bzl b/apple/internal/partials/codesigning_dossier.bzl index 2dff2de88b..8c12add9f6 100644 --- a/apple/internal/partials/codesigning_dossier.bzl +++ b/apple/internal/partials/codesigning_dossier.bzl @@ -48,6 +48,7 @@ Depset of structs with codesigning dossier information to be embedded in another _VALID_LOCATIONS = sets.make([ processor.location.app_clip, + processor.location.extension, processor.location.framework, processor.location.plugin, processor.location.watch, @@ -77,6 +78,7 @@ def _location_map(rule_descriptor): resolved = rule_descriptor.bundle_locations return { processor.location.app_clip: resolved.contents_relative_app_clips, + processor.location.extension: resolved.contents_relative_extensions, processor.location.framework: resolved.contents_relative_frameworks, processor.location.plugin: resolved.contents_relative_plugins, processor.location.watch: resolved.contents_relative_watch, diff --git a/apple/internal/partials/embedded_bundles.bzl b/apple/internal/partials/embedded_bundles.bzl index d1347742d7..5c0e1704c5 100644 --- a/apple/internal/partials/embedded_bundles.bzl +++ b/apple/internal/partials/embedded_bundles.bzl @@ -50,6 +50,7 @@ def _embedded_bundles_partial_impl( # Map of embedded bundle type to their final location in the top-level bundle. bundle_type_to_location = { "app_clips": processor.location.app_clip, + "extensions": processor.location.extension, "frameworks": processor.location.framework, "plugins": processor.location.plugin, "watch_bundles": processor.location.watch, @@ -145,6 +146,7 @@ def embedded_bundles_partial( app_clips = [], bundle_embedded_bundles = False, embeddable_targets = [], + extensions = [], frameworks = [], platform_prerequisites, plugins = [], @@ -166,6 +168,8 @@ def embedded_bundles_partial( embeddable bundles will be propagated downstream for a top level target to bundle them. embeddable_targets: The list of targets that propagate embeddable bundles to bundle or propagate. + extensions: List of extension bundles that should be propagated downstream for a top level + target to bundle inside `Extensions`. frameworks: List of framework bundles that should be propagated downstream for a top level target to bundle inside `Frameworks`. platform_prerequisites: Struct containing information on the platform being targeted. @@ -186,6 +190,7 @@ def embedded_bundles_partial( app_clips = app_clips, bundle_embedded_bundles = bundle_embedded_bundles, embeddable_targets = embeddable_targets, + extensions = extensions, frameworks = frameworks, platform_prerequisites = platform_prerequisites, plugins = plugins, diff --git a/apple/internal/partials/resources.bzl b/apple/internal/partials/resources.bzl index a24a5f622a..582f5523da 100644 --- a/apple/internal/partials/resources.bzl +++ b/apple/internal/partials/resources.bzl @@ -14,7 +14,7 @@ """Partial implementations for resource processing. -Resources are procesed according to type, by a series of methods that deal with the specifics for +Resources are processed according to type, by a series of methods that deal with the specifics for each resource type. Each of this methods returns a struct, which always have a `files` field containing resource tuples as described in processor.bzl. Optionally, the structs can also have an `infoplists` field containing a list of plists that should be merged into the root Info.plist. @@ -152,7 +152,9 @@ def _resources_partial_impl( executable_name, bundle_verification_targets, environment_plist, + extensionkit_keys_required, launch_storyboard, + nsextension_keys_required, output_discriminator, platform_prerequisites, resource_deps, @@ -317,8 +319,10 @@ def _resources_partial_impl( child_plists = bundle_verification_infoplists, child_required_values = bundle_verification_required_values, environment_plist = environment_plist, + extensionkit_keys_required = extensionkit_keys_required, input_plists = infoplists, launch_storyboard = launch_storyboard, + nsextension_keys_required = nsextension_keys_required, out_infoplist = out_infoplist, output_discriminator = output_discriminator, platform_prerequisites = platform_prerequisites, @@ -346,7 +350,9 @@ def resources_partial( executable_name, bundle_verification_targets = [], environment_plist, + extensionkit_keys_required = False, launch_storyboard, + nsextension_keys_required = False, output_discriminator = None, platform_prerequisites, resource_deps, @@ -371,14 +377,18 @@ def resources_partial( occur. bundle_name: The name of the output bundle. executable_name: The name of the output executable. - bundle_verification_targets: List of structs that reference embedable targets that need to + bundle_verification_targets: List of structs that reference embeddable targets that need to be validated. The structs must have a `target` field with the target containing an Info.plist file that will be validated. The structs may also have a `parent_bundle_id_reference` field that contains the plist path, in list form, to the plist entry that must contain this target's bundle ID. environment_plist: File referencing a plist with the required variables about the versions the target is being built for and with. + extensionkit_keys_required: Whether to validate that the Info.plist ExtensionKit keys are correctly + configured. launch_storyboard: A file to be used as a launch screen for the application. + nsextension_keys_required: Whether to validate that the Info.plist ExtensionKit keys are correctly + configured. output_discriminator: A string to differentiate between different target intermediate files or `None`. platform_prerequisites: Struct containing information on the platform being targeted. @@ -406,9 +416,11 @@ def resources_partial( bundle_id = bundle_id, bundle_name = bundle_name, executable_name = executable_name, + extensionkit_keys_required = extensionkit_keys_required, bundle_verification_targets = bundle_verification_targets, environment_plist = environment_plist, launch_storyboard = launch_storyboard, + nsextension_keys_required = nsextension_keys_required, output_discriminator = output_discriminator, platform_prerequisites = platform_prerequisites, resource_deps = resource_deps, diff --git a/apple/internal/processor.bzl b/apple/internal/processor.bzl index f47e08f9e1..fddba51ae3 100644 --- a/apple/internal/processor.bzl +++ b/apple/internal/processor.bzl @@ -44,6 +44,7 @@ Location types can be: - binary: Files are to be placed in the binary section of the bundle. - bundle: Files are to be placed at the root of the bundle. - content: Files are to be placed in the contents section of the bundle. + - extension: Files are to be placed in the Extensions section of the bundle. - framework: Files are to be placed in the Frameworks section of the bundle. - plugin: Files are to be placed in the PlugIns section of the bundle. - resources: Files are to be placed in the resources section of the bundle. @@ -107,6 +108,7 @@ _LOCATION_ENUM = struct( binary = "binary", bundle = "bundle", content = "content", + extension = "extension", framework = "framework", plugin = "plugin", resource = "resource", @@ -195,6 +197,10 @@ def _archive_paths( ), _LOCATION_ENUM.bundle: bundle_path, _LOCATION_ENUM.content: contents_path, + _LOCATION_ENUM.extension: paths.join( + contents_path, + rule_descriptor.bundle_locations.contents_relative_extensions, + ), _LOCATION_ENUM.framework: paths.join( contents_path, rule_descriptor.bundle_locations.contents_relative_frameworks, diff --git a/apple/internal/providers/embeddable_info.bzl b/apple/internal/providers/embeddable_info.bzl index 0b9c9af2ff..fb64c03165 100644 --- a/apple/internal/providers/embeddable_info.bzl +++ b/apple/internal/providers/embeddable_info.bzl @@ -18,6 +18,9 @@ _APPLE_EMBEDDABLE_INFO_FIELDS = { "app_clips": """ A depset with the zipped archives of bundles that need to be expanded into the AppClips section of the packaging bundle.""", + "extensions": """ +A depset with the zipped archives of bundles that need to be expanded into the +Extensions section of the packaging bundle.""", "frameworks": """ A depset with the zipped archives of bundles that need to be expanded into the Frameworks section of the packaging bundle.""", diff --git a/apple/internal/resource_actions/plist.bzl b/apple/internal/resource_actions/plist.bzl index 8fde0eecc5..1b3d9718df 100644 --- a/apple/internal/resource_actions/plist.bzl +++ b/apple/internal/resource_actions/plist.bzl @@ -192,9 +192,11 @@ def merge_root_infoplists( child_plists = [], child_required_values = [], environment_plist, + extensionkit_keys_required = False, include_executable_name = True, input_plists, launch_storyboard, + nsextension_keys_required = False, output_discriminator, output_plist, output_pkginfo, @@ -223,11 +225,17 @@ def merge_root_infoplists( pair, see plisttool's `child_plist_required_values`, as this is passed straight through to it. environment_plist: An executable file referencing the environment_plist tool. + extensionkit_keys_required: If True, the merged Info.plist file must include + an EXAppExtensionAttributes dictionary containing EXExtensionPointIdentifier. + The presence of an NSExtension entry will raise an error. include_executable_name: If True, the executable name will be added to the plist in the `CFBundleExecutable` key. This is mainly intended for plists embedded in a command line tool which don't need this value. input_plists: The root plist files to merge. launch_storyboard: A file to be used as a launch screen for the application. + nsextension_keys_required: If True, the merged Info.plist file must include + an NSExtension dictionary containing NSExtensionPointIdentifier. + The presence of an EXAppExtensionAttributes entry will raise an error. output_discriminator: A string to differentiate between different target intermediate files or `None`. output_pkginfo: The file reference for the PkgInfo file. Can be None if not @@ -298,6 +306,12 @@ def merge_root_infoplists( **{str(p.owner): v for (p, v) in child_required_values} ) + if extensionkit_keys_required: + info_plist_options["extensionkit_keys_required"] = True + + if nsextension_keys_required: + info_plist_options["nsextension_keys_required"] = True + if (version != None and AppleBundleVersionInfo in version): version_info = version[AppleBundleVersionInfo] input_files.append(version_info.version_file) diff --git a/apple/internal/resources.bzl b/apple/internal/resources.bzl index bd9ed2ac34..6df1dac7c8 100644 --- a/apple/internal/resources.bzl +++ b/apple/internal/resources.bzl @@ -155,6 +155,7 @@ def _get_attr_as_list(*, attr, nested_attr, split_attr_keys): attr = attr, nested_attr = nested_attr, )) + else: # Search the attribute within each split key if any split keys were defined. for split_attr_key in split_attr_keys: diff --git a/apple/internal/rule_factory.bzl b/apple/internal/rule_factory.bzl index 67d377b877..0d27fad90a 100644 --- a/apple/internal/rule_factory.bzl +++ b/apple/internal/rule_factory.bzl @@ -694,6 +694,16 @@ Info.plist under the key `UILaunchStoryboardName`. default = Label("@build_bazel_rules_apple//apple/internal/templates:ios_sim_template"), ), }) + elif rule_descriptor.product_type == apple_product_type.app_extension: + attrs.append({ + "extensionkit_extension": attr.bool( + default = False, + doc = """ +If `True`, this extension is an ExtensionKit Extension instead of an App Extension. +The bundle is installed into the Extensions directory instead of PlugIns. +""", + ), + }) elif _is_test_product_type(rule_descriptor.product_type): required_providers = [ [AppleBundleInfo, IosApplicationBundleInfo], @@ -833,6 +843,17 @@ set, then the default extension is determined by the application's product_type. ), }) + elif rule_descriptor.product_type == apple_product_type.app_extension: + attrs.append({ + "extensionkit_extension": attr.bool( + default = False, + doc = """ +If `True`, this extension is an ExtensionKit Extension instead of an App Extension. +The bundle is installed into the Extensions directory instead of PlugIns. +""", + ), + }) + elif _is_test_product_type(rule_descriptor.product_type): test_host_mandatory = rule_descriptor.product_type == apple_product_type.ui_test_bundle attrs.append({ @@ -873,6 +894,16 @@ def _get_tvos_attrs(rule_descriptor): default = Label("@build_bazel_rules_apple//apple/internal/templates:ios_sim_template"), ), }) + elif rule_descriptor.product_type == apple_product_type.app_extension: + attrs.append({ + "extensionkit_extension": attr.bool( + default = False, + doc = """ +If `True`, this extension is an ExtensionKit Extension instead of an App Extension. +The bundle is installed into the Extensions directory instead of PlugIns. +""", + ), + }) elif rule_descriptor.product_type == apple_product_type.framework: attrs.append({ # TODO(kaipi): This attribute is not publicly documented, but it is tested in @@ -993,6 +1024,15 @@ If `True`, this extension is an App Extension instead of a WatchKit Extension. It links the extension with the application extension point (`_NSExtensionMain`) instead of the WatchKit extension point (`_WKExtensionMain`), and has the `app_extension` `product_type` instead of `watch2_extension`. +""", + ), + "extensionkit_extension": attr.bool( + default = False, + doc = """ +If `True`, this extension is an ExtensionKit Extension instead of a WatchKit Extension. +It links the extension with the application extension point (`_NSExtensionMain`) +instead of the WatchKit extension point (`_WKExtensionMain`), and has the +`extensionkit_extension` `product_type` instead of `watch2_extension`. """, ), }) diff --git a/apple/internal/rule_support.bzl b/apple/internal/rule_support.bzl index 0c8d1f3766..a4cda582c6 100644 --- a/apple/internal/rule_support.bzl +++ b/apple/internal/rule_support.bzl @@ -48,6 +48,7 @@ def _describe_bundle_locations( bundle_relative_contents = "", contents_relative_app_clips = "AppClips", contents_relative_binary = "", + contents_relative_extensions = "Extensions", contents_relative_frameworks = "Frameworks", contents_relative_plugins = "PlugIns", contents_relative_resources = "", @@ -59,6 +60,7 @@ def _describe_bundle_locations( bundle_relative_contents = bundle_relative_contents, contents_relative_app_clips = contents_relative_app_clips, contents_relative_binary = contents_relative_binary, + contents_relative_extensions = contents_relative_extensions, contents_relative_frameworks = contents_relative_frameworks, contents_relative_plugins = contents_relative_plugins, contents_relative_resources = contents_relative_resources, diff --git a/apple/internal/tvos_rules.bzl b/apple/internal/tvos_rules.bzl index 849ded7d21..53df98795e 100644 --- a/apple/internal/tvos_rules.bzl +++ b/apple/internal/tvos_rules.bzl @@ -900,13 +900,24 @@ def _tvos_extension_impl(ctx): ], ) + product_type = rule_descriptor.product_type + if ctx.attr.extensionkit_extension: + bundle_location = processor.location.extension + product_type = apple_product_type.extensionkit_extension + extensionkit_keys_required = True + nsextension_keys_required = False + else: + bundle_location = processor.location.plugin + extensionkit_keys_required = False + nsextension_keys_required = True + entitlements = entitlements_support.process_entitlements( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, bundle_id = bundle_id, entitlements_file = ctx.file.entitlements, platform_prerequisites = platform_prerequisites, - product_type = rule_descriptor.product_type, + product_type = product_type, provisioning_profile = provisioning_profile, rule_label = label, validation_mode = ctx.attr.entitlements_validation, @@ -922,7 +933,7 @@ def _tvos_extension_impl(ctx): binary_artifact = link_result.binary debug_outputs = linking_support.debug_outputs_by_architecture(link_result.outputs) - archive = outputs.archive( + archive_for_embedding = outputs.archive( actions = actions, bundle_extension = bundle_extension, bundle_name = bundle_name, @@ -930,6 +941,13 @@ def _tvos_extension_impl(ctx): predeclared_outputs = predeclared_outputs, ) + if ctx.attr.extensionkit_extension: + plugins = [] + extensions = [archive_for_embedding] + else: + plugins = [archive_for_embedding] + extensions = [] + processor_partials = [ partials.apple_bundle_info_partial( actions = actions, @@ -942,7 +960,7 @@ def _tvos_extension_impl(ctx): label_name = label.name, platform_prerequisites = platform_prerequisites, predeclared_outputs = predeclared_outputs, - product_type = rule_descriptor.product_type, + product_type = product_type, ), partials.binary_partial( actions = actions, @@ -972,7 +990,7 @@ def _tvos_extension_impl(ctx): actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, bundle_extension = bundle_extension, - bundle_location = processor.location.plugin, + bundle_location = bundle_location, bundle_name = bundle_name, embed_target_dossiers = False, embedded_targets = ctx.attr.frameworks, @@ -996,7 +1014,8 @@ def _tvos_extension_impl(ctx): partials.embedded_bundles_partial( embeddable_targets = ctx.attr.frameworks, platform_prerequisites = platform_prerequisites, - plugins = [archive], + plugins = plugins, + extensions = extensions, ), partials.extension_safe_validation_partial( is_extension_safe = True, @@ -1011,7 +1030,9 @@ def _tvos_extension_impl(ctx): bundle_name = bundle_name, environment_plist = ctx.file._environment_plist, executable_name = executable_name, + extensionkit_keys_required = extensionkit_keys_required, launch_storyboard = None, + nsextension_keys_required = nsextension_keys_required, platform_prerequisites = platform_prerequisites, resource_deps = resource_deps, rule_descriptor = rule_descriptor, diff --git a/apple/internal/watchos_rules.bzl b/apple/internal/watchos_rules.bzl index 39bbcbd000..82c8e68f00 100644 --- a/apple/internal/watchos_rules.bzl +++ b/apple/internal/watchos_rules.bzl @@ -858,6 +858,15 @@ def _watchos_extension_impl(ctx): ], ) product_type = rule_descriptor.product_type + if ctx.attr.extensionkit_extension: + bundle_location = processor.location.extension + product_type = apple_product_type.extensionkit_extension + extensionkit_keys_required = True + nsextension_keys_required = False + else: + bundle_location = processor.location.plugin + extensionkit_keys_required = False + nsextension_keys_required = True # Xcode 11 requires this flag to be passed to the linker, but it is not accepted by earlier # versions. @@ -868,6 +877,9 @@ def _watchos_extension_impl(ctx): if ctx.attr.application_extension: extra_linkopts = ["-e", "_NSExtensionMain"] product_type = apple_product_type.app_extension + elif ctx.attr.extensionkit_extension: + extra_linkopts = ["-e", "_NSExtensionMain"] + product_type = apple_product_type.extensionkit_extension else: extra_linkopts = ["-e", "_WKExtensionMain"] @@ -913,7 +925,7 @@ def _watchos_extension_impl(ctx): binary_artifact = link_result.binary debug_outputs = linking_support.debug_outputs_by_architecture(link_result.outputs) - archive = outputs.archive( + archive_for_embedding = outputs.archive( actions = actions, bundle_extension = bundle_extension, bundle_name = bundle_name, @@ -921,6 +933,13 @@ def _watchos_extension_impl(ctx): predeclared_outputs = predeclared_outputs, ) + if ctx.attr.extensionkit_extension: + plugins = [] + extensions = [archive_for_embedding] + else: + plugins = [archive_for_embedding] + extensions = [] + bundle_verification_targets = [struct(target = ext) for ext in ctx.attr.extensions] processor_partials = [ @@ -965,7 +984,7 @@ def _watchos_extension_impl(ctx): actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, bundle_extension = bundle_extension, - bundle_location = processor.location.plugin, + bundle_location = bundle_location, bundle_name = bundle_name, embed_target_dossiers = True, embedded_targets = embeddable_targets, @@ -990,7 +1009,8 @@ def _watchos_extension_impl(ctx): bundle_embedded_bundles = True, platform_prerequisites = platform_prerequisites, embeddable_targets = embeddable_targets, - plugins = [archive], + plugins = plugins, + extensions = extensions, ), partials.extension_safe_validation_partial( is_extension_safe = True, @@ -1019,7 +1039,9 @@ def _watchos_extension_impl(ctx): bundle_name = bundle_name, environment_plist = ctx.file._environment_plist, executable_name = executable_name, + extensionkit_keys_required = extensionkit_keys_required, launch_storyboard = None, + nsextension_keys_required = nsextension_keys_required, platform_prerequisites = platform_prerequisites, resource_deps = resource_deps, rule_descriptor = rule_descriptor, diff --git a/doc/rules-ios.md b/doc/rules-ios.md index 015bc309a4..c9b402aeae 100644 --- a/doc/rules-ios.md +++ b/doc/rules-ios.md @@ -194,9 +194,9 @@ Builds and bundles an iOS dynamic framework that is consumable by Xcode.
 ios_extension(name, additional_linker_inputs, app_icons, bundle_id, bundle_name, codesign_inputs,
               codesignopts, deps, entitlements, entitlements_validation, executable_name,
-              exported_symbols_lists, families, frameworks, infoplists, ipa_post_processor, linkopts,
-              minimum_deployment_os_version, minimum_os_version, platform_type, provisioning_profile,
-              resources, sdk_frameworks, stamp, strings, version)
+              exported_symbols_lists, extensionkit_extension, families, frameworks, infoplists,
+              ipa_post_processor, linkopts, minimum_deployment_os_version, minimum_os_version,
+              platform_type, provisioning_profile, resources, sdk_frameworks, stamp, strings, version)
 
Builds and bundles an iOS Application Extension. @@ -223,6 +223,7 @@ However, iOS 14 introduced Widget Extensions that use a traditional `main` entry | entitlements_validation | An [entitlements_validation_mode](/doc/types.md#entitlements-validation-mode) to control the validation of the requested entitlements against the provisioning profile to ensure they are supported. | String | optional | "loose" | | executable_name | The desired name of the executable, if the bundle has an executable. If this attribute is not set, then the name of the bundle_name attribute will be used if it is set; if not, then the name of the target will be used instead. | String | optional | "" | | exported_symbols_lists | A list of targets containing exported symbols lists files for the linker to control symbol resolution.

Each file is expected to have a list of global symbol names that will remain as global symbols in the compiled binary owned by this framework. All other global symbols will be treated as if they were marked as __private_extern__ (aka visibility=hidden) and will not be global in the output file.

See the man page documentation for ld(1) on macOS for more details. | List of labels | optional | [] | +| extensionkit_extension | If True, this extension is an ExtensionKit Extension instead of an App Extension. The bundle is installed into the Extensions directory instead of PlugIns. | Boolean | optional | False | | families | A list of device families supported by this extension. Valid values are iphone and ipad; at least one must be specified. | List of strings | required | | | frameworks | A list of framework targets (see [ios_framework](https://github.com/bazelbuild/rules_apple/blob/master/doc/rules-ios.md#ios_framework)) that this target depends on. | List of labels | optional | [] | | infoplists | A list of .plist files that will be merged to form the Info.plist for this target. At least one file must be specified. Please see [Info.plist Handling](https://github.com/bazelbuild/rules_apple/blob/master/doc/common_info.md#infoplist-handling) for what is supported. | List of labels | required | | diff --git a/doc/rules-macos.md b/doc/rules-macos.md index dc4150b41d..943c745d79 100644 --- a/doc/rules-macos.md +++ b/doc/rules-macos.md @@ -225,9 +225,10 @@ Builds a macOS Dylib binary.
 macos_extension(name, additional_contents, additional_linker_inputs, app_icons, bundle_id,
                 bundle_name, codesign_inputs, codesignopts, deps, entitlements,
-                entitlements_validation, executable_name, exported_symbols_lists, infoplists,
-                ipa_post_processor, linkopts, minimum_deployment_os_version, minimum_os_version,
-                platform_type, provisioning_profile, resources, stamp, strings, version)
+                entitlements_validation, executable_name, exported_symbols_lists,
+                extensionkit_extension, infoplists, ipa_post_processor, linkopts,
+                minimum_deployment_os_version, minimum_os_version, platform_type,
+                provisioning_profile, resources, stamp, strings, version)
 
Builds and bundles a macOS Application Extension. @@ -255,6 +256,7 @@ point (typically expressed through Swift's `@main` attribute). | entitlements_validation | An [entitlements_validation_mode](/doc/types.md#entitlements-validation-mode) to control the validation of the requested entitlements against the provisioning profile to ensure they are supported. | String | optional | "loose" | | executable_name | The desired name of the executable, if the bundle has an executable. If this attribute is not set, then the name of the bundle_name attribute will be used if it is set; if not, then the name of the target will be used instead. | String | optional | "" | | exported_symbols_lists | A list of targets containing exported symbols lists files for the linker to control symbol resolution.

Each file is expected to have a list of global symbol names that will remain as global symbols in the compiled binary owned by this framework. All other global symbols will be treated as if they were marked as __private_extern__ (aka visibility=hidden) and will not be global in the output file.

See the man page documentation for ld(1) on macOS for more details. | List of labels | optional | [] | +| extensionkit_extension | If True, this extension is an ExtensionKit Extension instead of an App Extension. The bundle is installed into the Extensions directory instead of PlugIns. | Boolean | optional | False | | infoplists | A list of .plist files that will be merged to form the Info.plist for this target. At least one file must be specified. Please see [Info.plist Handling](https://github.com/bazelbuild/rules_apple/blob/master/doc/common_info.md#infoplist-handling) for what is supported. | List of labels | required | | | ipa_post_processor | A tool that edits this target's archive after it is assembled but before it is signed. The tool is invoked with a single command-line argument that denotes the path to a directory containing the unzipped contents of the archive; this target's bundle will be the directory's only contents.

Any changes made by the tool must be made in this directory, and the tool's execution must be hermetic given these inputs to ensure that the result can be safely cached. | Label | optional | None | | linkopts | A list of strings representing extra flags that should be passed to the linker. | List of strings | optional | [] | diff --git a/doc/rules-tvos.md b/doc/rules-tvos.md index 18e486f206..3ba0681f37 100644 --- a/doc/rules-tvos.md +++ b/doc/rules-tvos.md @@ -138,9 +138,9 @@ Builds and bundles a tvOS dynamic framework that is consumable by Xcode.
 tvos_extension(name, additional_linker_inputs, bundle_id, bundle_name, codesign_inputs,
                codesignopts, deps, entitlements, entitlements_validation, executable_name,
-               exported_symbols_lists, frameworks, infoplists, ipa_post_processor, linkopts,
-               minimum_deployment_os_version, minimum_os_version, platform_type, provisioning_profile,
-               resources, stamp, strings, version)
+               exported_symbols_lists, extensionkit_extension, frameworks, infoplists,
+               ipa_post_processor, linkopts, minimum_deployment_os_version, minimum_os_version,
+               platform_type, provisioning_profile, resources, stamp, strings, version)
 
Builds and bundles a tvOS Extension. @@ -161,6 +161,7 @@ Builds and bundles a tvOS Extension. | entitlements_validation | An [entitlements_validation_mode](/doc/types.md#entitlements-validation-mode) to control the validation of the requested entitlements against the provisioning profile to ensure they are supported. | String | optional | "loose" | | executable_name | The desired name of the executable, if the bundle has an executable. If this attribute is not set, then the name of the bundle_name attribute will be used if it is set; if not, then the name of the target will be used instead. | String | optional | "" | | exported_symbols_lists | A list of targets containing exported symbols lists files for the linker to control symbol resolution.

Each file is expected to have a list of global symbol names that will remain as global symbols in the compiled binary owned by this framework. All other global symbols will be treated as if they were marked as __private_extern__ (aka visibility=hidden) and will not be global in the output file.

See the man page documentation for ld(1) on macOS for more details. | List of labels | optional | [] | +| extensionkit_extension | If True, this extension is an ExtensionKit Extension instead of an App Extension. The bundle is installed into the Extensions directory instead of PlugIns. | Boolean | optional | False | | frameworks | A list of framework targets (see [tvos_framework](https://github.com/bazelbuild/rules_apple/blob/master/doc/rules-tvos.md#tvos_framework)) that this target depends on. | List of labels | optional | [] | | infoplists | A list of .plist files that will be merged to form the Info.plist for this target. At least one file must be specified. Please see [Info.plist Handling](https://github.com/bazelbuild/rules_apple/blob/master/doc/common_info.md#infoplist-handling) for what is supported. | List of labels | required | | | ipa_post_processor | A tool that edits this target's archive after it is assembled but before it is signed. The tool is invoked with a single command-line argument that denotes the path to a directory containing the unzipped contents of the archive; this target's bundle will be the directory's only contents.

Any changes made by the tool must be made in this directory, and the tool's execution must be hermetic given these inputs to ensure that the result can be safely cached. | Label | optional | None | diff --git a/doc/rules-watchos.md b/doc/rules-watchos.md index 0dd21f78fc..1719b30d7b 100644 --- a/doc/rules-watchos.md +++ b/doc/rules-watchos.md @@ -128,9 +128,10 @@ Builds and bundles a watchOS dynamic framework that is consumable by Xcode.
 watchos_extension(name, additional_linker_inputs, application_extension, bundle_id, bundle_name,
                   codesign_inputs, codesignopts, deps, entitlements, entitlements_validation,
-                  executable_name, exported_symbols_lists, extensions, frameworks, infoplists,
-                  ipa_post_processor, linkopts, minimum_deployment_os_version, minimum_os_version,
-                  platform_type, provisioning_profile, resources, stamp, strings, version)
+                  executable_name, exported_symbols_lists, extensionkit_extension, extensions,
+                  frameworks, infoplists, ipa_post_processor, linkopts, minimum_deployment_os_version,
+                  minimum_os_version, platform_type, provisioning_profile, resources, stamp, strings,
+                  version)
 
Builds and bundles an watchOS Extension. @@ -156,6 +157,7 @@ so these bundling rules do not support that version of the platform. | entitlements_validation | An [entitlements_validation_mode](/doc/types.md#entitlements-validation-mode) to control the validation of the requested entitlements against the provisioning profile to ensure they are supported. | String | optional | "loose" | | executable_name | The desired name of the executable, if the bundle has an executable. If this attribute is not set, then the name of the bundle_name attribute will be used if it is set; if not, then the name of the target will be used instead. | String | optional | "" | | exported_symbols_lists | A list of targets containing exported symbols lists files for the linker to control symbol resolution.

Each file is expected to have a list of global symbol names that will remain as global symbols in the compiled binary owned by this framework. All other global symbols will be treated as if they were marked as __private_extern__ (aka visibility=hidden) and will not be global in the output file.

See the man page documentation for ld(1) on macOS for more details. | List of labels | optional | [] | +| extensionkit_extension | If True, this extension is an ExtensionKit Extension instead of a WatchKit Extension. It links the extension with the application extension point (_NSExtensionMain) instead of the WatchKit extension point (_WKExtensionMain), and has the extensionkit_extension product_type instead of watch2_extension. | Boolean | optional | False | | extensions | A list of watchOS application extensions to include in the final watch extension bundle. | List of labels | optional | [] | | frameworks | A list of framework targets (see [watchos_framework](https://github.com/bazelbuild/rules_apple/blob/master/doc/rules-watchos.md#watchos_framework)) that this target depends on. | List of labels | optional | [] | | infoplists | A list of .plist files that will be merged to form the Info.plist for this target. At least one file must be specified. Please see [Info.plist Handling](https://github.com/bazelbuild/rules_apple/blob/master/doc/common_info.md#infoplist-handling) for what is supported. | List of labels | required | | diff --git a/examples/macos/HelloToday/Ext-Info.plist b/examples/macos/HelloToday/Ext-Info.plist index 9f74bf6149..acdb876167 100644 --- a/examples/macos/HelloToday/Ext-Info.plist +++ b/examples/macos/HelloToday/Ext-Info.plist @@ -24,7 +24,7 @@ 2.0 NSExtensionPointIdentifier - com.apple.widget-extension + com.apple.widgetkit-extension NSExtensionPrincipalClass TodayViewController com.apple.notificationcenter.widget.description diff --git a/test/BUILD b/test/BUILD index 0e3a871f98..e7224c1698 100644 --- a/test/BUILD +++ b/test/BUILD @@ -149,6 +149,17 @@ apple_multi_shell_test( ], ) +apple_multi_shell_test( + name = "ios_extensionkit_extension_test", + size = "medium", + src = "ios_extensionkit_extension_test.sh", + configurations = IOS_CONFIGURATIONS, + data = [ + "//test/testdata/binaries:empty_dylib", + "//test/testdata/binaries:empty_staticlib", + ], +) + apple_multi_shell_test( name = "ios_imessage_test", size = "medium", diff --git a/test/ios_extension_test.sh b/test/ios_extension_test.sh index 9ec3845344..af3f4a7899 100755 --- a/test/ios_extension_test.sh +++ b/test/ios_extension_test.sh @@ -85,7 +85,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -240,7 +240,7 @@ function test_missing_version_fails() { CFBundleShortVersionString = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -265,7 +265,7 @@ function test_missing_short_version_fails() { CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -337,7 +337,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -407,7 +407,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -477,7 +477,7 @@ EOF CFBundleVersion = "1.1"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -486,6 +486,55 @@ EOF expect_log "While processing target \"//app:app\"; the CFBundleVersion of the child target \"//app:ext\" should be the same as its parent's version string \"1.0\", but found \"1.1\"." } +# Test that a known ExtensionKit extension point provides a warning. +function test_known_extensionkit_warning() { + create_common_files + create_minimal_ios_application_with_extension + + # Replace the file, but without CFBundleVersion. + cat > app/Info-Ext.plist < app/Info-Ext.plist < app/BUILD < app/Foo.h < +// This dummy class is needed to generate code in the extension target, +// which does not take main() from here, rather from an SDK. +@interface Foo: NSObject +- (void)doSomething; +@end +EOF + + cat > app/main.m < +#import "app/Foo.h" +@implementation Foo +- (void)doSomething { } +@end +int main(int argc, char **argv) { + return 0; +} +EOF + + cat > app/Info-App.plist < app/Info-Ext.plist <> app/BUILD <> app/BUILD <> app/BUILD <> app/BUILD < +# +# Creates minimal iOS application and extension targets that depends on a +# framework import target. The `dynamic` argument should be `True` or `False` +# and will be used to populate the framework's `is_dynamic` attribute. +function create_minimal_ios_application_and_extension_with_framework_import() { + readonly framework_type="$1" + readonly import_rule="$2" + + cat >> app/BUILD < app/fmwk.framework/Info.plist < app/fmwk.framework/resource.txt < app/fmwk.framework/Headers/fmwk.h < app/fmwk.framework/Headers/module.modulemap < app/Info-Ext.plist < app/Info-Ext.plist < app/BUILD < app/main.m < app/Info-App.plist < app/Info-Ext.plist < app/BUILD < app/main.m < app/Info-App.plist < app/Info-Ext.plist < app/BUILD < app/main.m < app/Info-App.plist < app/Info-Ext.plist < app/Info-Ext.plist < app/Info-Ext.plist < app/BUILD < app/upperlib.m < app/main.m < app/Info-Ext.plist <> app/BUILD < + + + + EXAppExtensionAttributes + + EXExtensionPointIdentifier + com.apple.app-intents + + + \ No newline at end of file diff --git a/test/starlark_tests/resources/Info-nsextension.plist b/test/starlark_tests/resources/Info-nsextension.plist new file mode 100644 index 0000000000..778bd73d06 --- /dev/null +++ b/test/starlark_tests/resources/Info-nsextension.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + \ No newline at end of file diff --git a/test/starlark_tests/resources/WatchosExtensionKitInfo.plist b/test/starlark_tests/resources/WatchosExtensionKitInfo.plist new file mode 100644 index 0000000000..9cb0278fd7 --- /dev/null +++ b/test/starlark_tests/resources/WatchosExtensionKitInfo.plist @@ -0,0 +1,21 @@ + + + + + CFBundleName + $(PRODUCT_NAME) + CFBundleVersion + 1.0 + CFBundleShortVersionString + 1.0 + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + EXAppExtensionAttributes + + EXExtensionPointIdentifier + com.apple.app-intents + + + \ No newline at end of file diff --git a/test/starlark_tests/targets_under_test/ios/BUILD b/test/starlark_tests/targets_under_test/ios/BUILD index e79f6bfe05..32505a7403 100644 --- a/test/starlark_tests/targets_under_test/ios/BUILD +++ b/test/starlark_tests/targets_under_test/ios/BUILD @@ -652,6 +652,7 @@ ios_extension( frameworks = [":fmwk_min_os_baseline"], infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_ios.nplus1, tags = common.fixture_tags, @@ -1098,6 +1099,7 @@ ios_extension( ], infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_ios.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", @@ -1117,6 +1119,7 @@ ios_extension( infoplists = [ "//test/starlark_tests/resources:Info.plist", "//test/starlark_tests/resources:Another.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_ios.baseline, tags = common.fixture_tags, @@ -1138,6 +1141,7 @@ ios_extension( ], infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_ios.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", @@ -1157,6 +1161,179 @@ ios_extension( ], infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", + ], + minimum_os_version = common.min_os_ios.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:swift_main_lib", + ], +) + +# --------------------------------------------------------------------------------------- + +ios_application( + name = "app_with_exappextension", + bundle_id = "com.google.example", + extensions = [":exappextension"], + families = [ + "iphone", + "ipad", + ], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + ], + minimum_os_version = common.min_os_ios.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +ios_application( + name = "app_with_swift_exappextension", + bundle_id = "com.google.example", + extensions = [":swift_exappextension"], + families = [ + "iphone", + "ipad", + ], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + ], + minimum_os_version = common.min_os_ios.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +ios_application( + name = "app_with_exappextension_and_fmwk_provisioned", + bundle_id = "com.google.example", + extensions = [":exappextension_with_fmwk_provisioned"], + families = [ + "iphone", + "ipad", + ], + frameworks = [ + ":fmwk_with_provisioning", + ], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + ], + ipa_post_processor = "//test/starlark_tests/targets_under_test/apple:ipa_post_processor_verify_codesigning", + minimum_os_version = common.min_os_ios.baseline, + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +ios_application( + name = "app_with_exappextension_and_fmwk_and_symbols_in_bundle", + bundle_id = "com.google.example", + extensions = [":exappextension_with_fmwk_provisioned"], + families = [ + "iphone", + "ipad", + ], + frameworks = [ + ":fmwk_with_provisioning", + ], + include_symbols_in_bundle = True, + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + ], + ipa_post_processor = "//test/starlark_tests/targets_under_test/apple:ipa_post_processor_verify_codesigning", + minimum_os_version = common.min_os_ios.baseline, + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + "//test/starlark_tests/targets_under_test/apple:iOSImportedDynamicFrameworkWithDebugInfo", + ], +) + +ios_extension( + name = "exappextension", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + families = [ + "iphone", + "ipad", + ], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-extensionkit.plist", + ], + minimum_os_version = common.min_os_ios.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +ios_extension( + name = "exappextension_multiple_infoplists", + bundle_id = "com.google.example.exappextension", + extensionkit_extension = True, + families = [ + "iphone", + "ipad", + ], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Another.plist", + "//test/starlark_tests/resources:Info-extensionkit.plist", + ], + minimum_os_version = common.min_os_ios.baseline, + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +ios_extension( + name = "exappextension_with_fmwk_provisioned", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + families = [ + "iphone", + "ipad", + ], + frameworks = [ + ":fmwk_with_provisioning", + ], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-extensionkit.plist", + ], + minimum_os_version = common.min_os_ios.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +ios_extension( + name = "swift_exappextension", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + families = [ + "iphone", + "ipad", + ], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-extensionkit.plist", ], minimum_os_version = common.min_os_ios.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", @@ -3462,6 +3639,7 @@ ios_extension( ], infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_ios.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", @@ -3645,6 +3823,7 @@ ios_extension( families = ["iphone"], infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_ios.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", diff --git a/test/starlark_tests/targets_under_test/macos/BUILD b/test/starlark_tests/targets_under_test/macos/BUILD index 4222b527f2..c1da92e9aa 100644 --- a/test/starlark_tests/targets_under_test/macos/BUILD +++ b/test/starlark_tests/targets_under_test/macos/BUILD @@ -176,6 +176,7 @@ macos_extension( bundle_id = "com.google.example.ext", infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_macos.baseline, tags = common.fixture_tags, @@ -216,6 +217,56 @@ macos_application( # --------------------------------------------------------------------------------------- +macos_application( + name = "app_with_exappextension", + bundle_id = "com.google.example", + extensions = [":exappextension"], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + ], + minimum_os_version = common.min_os_macos.baseline, + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +macos_extension( + name = "exappextension", + additional_contents = { + "//test/starlark_tests/resources:additional.txt": "Additional", + "//test/starlark_tests/resources:all_nested": "Nested", + }, + bundle_id = "com.google.example.exappextension", + extensionkit_extension = True, + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-extensionkit.plist", + ], + minimum_os_version = common.min_os_macos.baseline, + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +macos_application( + name = "app_with_exappextension_and_symbols_in_bundle", + bundle_id = "com.google.example", + extensions = [":exappextension"], + include_symbols_in_bundle = True, + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + ], + minimum_os_version = common.min_os_macos.baseline, + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +# --------------------------------------------------------------------------------------- + objc_library( name = "dynamic_fmwk_depending_lib", tags = common.fixture_tags, diff --git a/test/starlark_tests/targets_under_test/tvos/BUILD b/test/starlark_tests/targets_under_test/tvos/BUILD index c971540428..58f7ef961d 100644 --- a/test/starlark_tests/targets_under_test/tvos/BUILD +++ b/test/starlark_tests/targets_under_test/tvos/BUILD @@ -91,6 +91,7 @@ tvos_extension( entitlements = "//test/starlark_tests/resources:entitlements.plist", infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_tvos.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", @@ -115,10 +116,75 @@ tvos_extension( name = "swift_ext", bundle_id = "com.google.example.ext", entitlements = "//test/starlark_tests/resources:entitlements.plist", + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", + ], + minimum_os_version = common.min_os_tvos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:swift_main_lib", + ], +) + +# --------------------------------------------------------------------------------------- + +tvos_application( + name = "app_with_exappextension", + bundle_id = "com.google.example", + extensions = [":exappextension"], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + ], + minimum_os_version = common.min_os_tvos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +tvos_extension( + name = "exappextension", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-extensionkit.plist", + ], + minimum_os_version = common.min_os_tvos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +tvos_application( + name = "app_with_swift_exappextension", + bundle_id = "com.google.example", + extensions = [":swift_exappextension"], infoplists = ["//test/starlark_tests/resources:Info.plist"], minimum_os_version = common.min_os_tvos.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", tags = common.fixture_tags, + deps = ["//test/starlark_tests/resources:objc_main_lib"], +) + +tvos_extension( + name = "swift_exappextension", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-extensionkit.plist", + ], + minimum_os_version = common.min_os_tvos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, deps = [ "//test/starlark_tests/resources:swift_main_lib", ], @@ -943,6 +1009,7 @@ tvos_extension( entitlements = "//test/starlark_tests/resources:entitlements.plist", infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_tvos.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", diff --git a/test/starlark_tests/targets_under_test/watchos/BUILD b/test/starlark_tests/targets_under_test/watchos/BUILD index db894535f9..ee7f100646 100644 --- a/test/starlark_tests/targets_under_test/watchos/BUILD +++ b/test/starlark_tests/targets_under_test/watchos/BUILD @@ -245,6 +245,207 @@ ios_application( # --------------------------------------------------------------------------------------- +watchos_application( + name = "app_with_exappextension", + app_icons = ["//test/starlark_tests/resources:WatchAppIcon.xcassets"], + bundle_id = "com.google.example", + extension = ":exappextension", + infoplists = [ + "//test/starlark_tests/resources:WatchosAppInfo.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, +) + +watchos_application( + name = "app_with_exappextension_with_imported_fmwk", + app_icons = ["//test/starlark_tests/resources:WatchAppIcon.xcassets"], + bundle_id = "com.google.example", + extension = ":exappextension_with_imported_fmwk", + infoplists = [ + "//test/starlark_tests/resources:WatchosAppInfo.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + tags = common.fixture_tags, +) + +watchos_application( + name = "app_arm64_support_exappextension", + app_icons = ["//test/starlark_tests/resources:WatchAppIcon.xcassets"], + bundle_id = "com.google.example", + extension = ":exappextension_arm64_support", + infoplists = [ + "//test/starlark_tests/resources:WatchosAppInfo.plist", + ], + minimum_os_version = common.min_os_watchos.arm64_support, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, +) + +watchos_extension( + name = "exappextension", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + resources = [ + "//test/starlark_tests/resources:complications_watchos", + "//test/starlark_tests/resources:example_filegroup", + "//test/starlark_tests/resources:localization", + "//test/starlark_tests/resources:resource_bundle", + ], + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:watchkit_ext_main_lib", + ], +) + +watchos_extension( + name = "exappextension_with_fmwk", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + frameworks = [":fmwk"], + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:watchkit_ext_main_lib", + ], +) + +watchos_extension( + name = "exappextension_with_two_fmwk_provisioned", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + frameworks = [ + ":fmwk_with_provisioning", + ":second_fmwk_with_provisioning", + ], + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + ipa_post_processor = "//test/starlark_tests/targets_under_test/apple:ipa_post_processor_verify_codesigning", + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:watchkit_ext_main_lib", + ], +) + +watchos_extension( + name = "exappextension_with_fmwk_with_fmwk_provisioned", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + frameworks = [":fmwk_with_fmwk_with_provisioning"], + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + ipa_post_processor = "//test/starlark_tests/targets_under_test/apple:ipa_post_processor_verify_codesigning", + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:watchkit_ext_main_lib", + ], +) + +watchos_extension( + name = "exappextension_with_fmwk_with_fmwk", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + frameworks = [":fmwk_with_fmwk"], + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:watchkit_ext_main_lib", + ], +) + +watchos_extension( + name = "exappextension_with_imported_fmwk", + bundle_id = "com.google.example.exappextension", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + ipa_post_processor = "//test/starlark_tests/targets_under_test/apple:ipa_post_processor_verify_codesigning", + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + ":dynamic_fmwk_depending_lib", + "//test/starlark_tests/resources:watchkit_ext_main_lib", + ], +) + +watchos_extension( + name = "exappextension_with_runtime_framework_using_import_static_lib_dep", + bundle_id = "com.google.example", + extensionkit_extension = True, + frameworks = [":fmwk_with_imported_static_framework"], + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + ipa_post_processor = "//test/starlark_tests/targets_under_test/apple:ipa_post_processor_verify_codesigning", + minimum_os_version = common.min_os_watchos.baseline, + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +watchos_extension( + name = "exappextension_multiple_infoplists", + bundle_id = "com.google.example.exappextension", + extensionkit_extension = True, + infoplists = [ + "//test/starlark_tests/resources:Another.plist", + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + tags = common.fixture_tags, +) + +watchos_extension( + name = "exappextension_arm64_support", + bundle_id = "com.google.example.exappextension", + extensionkit_extension = True, + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + minimum_os_version = common.min_os_watchos.arm64_support, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + resources = [ + "//test/starlark_tests/resources:example_filegroup", + "//test/starlark_tests/resources:localization", + "//test/starlark_tests/resources:resource_bundle", + ], + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:watchkit_ext_main_lib", + ], +) + +# --------------------------------------------------------------------------------------- + watchos_framework( name = "fmwk", bundle_id = "com.google.example.framework", @@ -451,6 +652,7 @@ watchos_extension( entitlements = "//test/starlark_tests/resources:entitlements.plist", infoplists = [ "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-nsextension.plist", ], minimum_os_version = common.min_os_watchos.baseline, provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", @@ -493,6 +695,74 @@ watchos_application( # --------------------------------------------------------------------------------------- +ios_application( + name = "ios_watchos_with_watchos_exappextension", + bundle_id = "com.google", + bundle_name = "companion", + families = ["iphone"], + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + ], + minimum_os_version = common.min_os_ios.arm_sim_support, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + watch_application = ":watchos_app_with_exappextension", + deps = [ + ":swift_lib", + ], +) + +watchos_extension( + name = "watchos_exappextension", + bundle_id = "com.google.example.watchosext.nestedwatchosext", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + infoplists = [ + "//test/starlark_tests/resources:Info.plist", + "//test/starlark_tests/resources:Info-extensionkit.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +watchos_extension( + name = "watchos_exappextension_with_exappextension", + bundle_id = "com.google.example.watchosext", + bundle_name = "ext", + entitlements = "//test/starlark_tests/resources:entitlements.plist", + extensionkit_extension = True, + extensions = [":watchos_exappextension"], + infoplists = [ + "//test/starlark_tests/resources:WatchosExtensionKitInfo.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +watchos_application( + name = "watchos_app_with_exappextension", + app_icons = ["//test/starlark_tests/resources:WatchAppIcon.xcassets"], + bundle_id = "com.google.example", + bundle_name = "app", + extension = ":watchos_exappextension_with_exappextension", + infoplists = [ + "//test/starlark_tests/resources:WatchosAppInfo.plist", + ], + minimum_os_version = common.min_os_watchos.baseline, + provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision", + tags = common.fixture_tags, +) + +# --------------------------------------------------------------------------------------- + ios_application( name = "ios_with_swift_watchos_no_swift", bundle_id = "com.google", diff --git a/test/starlark_tests/tvos_extensionkit_extension_tests.bzl b/test/starlark_tests/tvos_extensionkit_extension_tests.bzl new file mode 100644 index 0000000000..9416b73fc2 --- /dev/null +++ b/test/starlark_tests/tvos_extensionkit_extension_tests.bzl @@ -0,0 +1,145 @@ +# Copyright 2019 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""tvos_extensionkit_extension Starlark tests.""" + +load( + ":common.bzl", + "common", +) +load( + ":rules/apple_verification_test.bzl", + "apple_verification_test", +) +load( + ":rules/common_verification_tests.bzl", + "archive_contents_test", + "bitcode_symbol_map_test", +) +load( + ":rules/dsyms_test.bzl", + "dsyms_test", +) +load( + ":rules/infoplist_contents_test.bzl", + "infoplist_contents_test", +) + +def tvos_extensionkit_extension_test_suite(name): + """Test suite for tvos_extensionkit_extension. + + Args: + name: the base name to be used in things created by this macro + """ + apple_verification_test( + name = "{}_codesign_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/tvos:exappextension", + verifier_script = "verifier_scripts/codesign_verifier.sh", + tags = [name], + ) + + dsyms_test( + name = "{}_dsyms_test".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/tvos:exappextension", + expected_direct_dsyms = ["exappextension.appex"], + expected_transitive_dsyms = ["exappextension.appex"], + tags = [name], + ) + + infoplist_contents_test( + name = "{}_plist_test".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/tvos:exappextension", + expected_values = { + "BuildMachineOSBuild": "*", + "CFBundleExecutable": "exappextension", + "CFBundleIdentifier": "com.google.example.exappextension", + "CFBundleName": "exappextension", + "CFBundlePackageType": "XPC!", + "CFBundleSupportedPlatforms:0": "AppleTVSimulator*", + "DTCompiler": "com.apple.compilers.llvm.clang.1_0", + "DTPlatformBuild": "*", + "DTPlatformName": "appletvsimulator*", + "DTPlatformVersion": "*", + "DTSDKBuild": "*", + "DTSDKName": "appletvsimulator*", + "DTXcode": "*", + "DTXcodeBuild": "*", + "MinimumOSVersion": common.min_os_tvos.baseline, + "UIDeviceFamily:0": "3", + }, + tags = [name], + ) + + # Tests that the archive contains Bitcode symbol maps when Bitcode is + # enabled. + bitcode_symbol_map_test( + name = "{}_archive_contains_bitcode_symbol_maps_test".format(name), + binary_paths = [ + "Payload/app_with_exappextension.app/app_with_exappextension", + "Payload/app_with_exappextension.app/Extensions/exappextension.appex/exappextension", + ], + target_under_test = "//test/starlark_tests/targets_under_test/tvos:app_with_exappextension", + tags = [name], + ) + + # Tests that the provisioning profile is present when built for device. + archive_contents_test( + name = "{}_contains_provisioning_profile_test".format(name), + build_type = "device", + target_under_test = "//test/starlark_tests/targets_under_test/tvos:exappextension", + contains = [ + "$BUNDLE_ROOT/embedded.mobileprovision", + ], + tags = [name], + ) + + archive_contents_test( + name = "{}_correct_rpath_header_value_test".format(name), + build_type = "device", + binary_test_file = "$CONTENT_ROOT/exappextension", + macho_load_commands_contain = [ + "path @executable_path/Frameworks (offset 12)", + "path @executable_path/../../Frameworks (offset 12)", + ], + target_under_test = "//test/starlark_tests/targets_under_test/tvos:exappextension", + tags = [name], + ) + + # Verify that Swift dylibs are packaged with the application, not with the extension, when only + # an extension uses Swift. And to be safe, verify that they aren't packaged with the extension. + archive_contents_test( + name = "{}_device_swift_dylibs_present".format(name), + build_type = "device", + target_under_test = "//test/starlark_tests/targets_under_test/tvos:app_with_swift_exappextension", + not_contains = ["$BUNDLE_ROOT/Extensions/exappextension.appex/Frameworks/libswiftCore.dylib"], + contains = [ + "$BUNDLE_ROOT/Frameworks/libswiftCore.dylib", + "$ARCHIVE_ROOT/SwiftSupport/appletvos/libswiftCore.dylib", + ], + tags = [name], + ) + archive_contents_test( + name = "{}_simulator_swift_dylibs_present".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/tvos:app_with_swift_exappextension", + contains = ["$BUNDLE_ROOT/Frameworks/libswiftCore.dylib"], + not_contains = ["$BUNDLE_ROOT/Extensions/exappextension.appex/Frameworks/libswiftCore.dylib"], + tags = [name], + ) + + native.test_suite( + name = name, + tags = [name], + ) diff --git a/test/starlark_tests/watchos_extensionkit_extension_tests.bzl b/test/starlark_tests/watchos_extensionkit_extension_tests.bzl new file mode 100644 index 0000000000..ff369e2a0b --- /dev/null +++ b/test/starlark_tests/watchos_extensionkit_extension_tests.bzl @@ -0,0 +1,260 @@ +# Copyright 2019 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""watchos_extensionkit_extension Starlark tests.""" + +load( + "@build_bazel_rules_apple//apple/internal:apple_product_type.bzl", # buildifier: disable=bzl-visibility + "apple_product_type", +) # buildifier: disable=bzl-visibility +load( + ":common.bzl", + "common", +) +load( + ":rules/apple_verification_test.bzl", + "apple_verification_test", +) +load( + ":rules/product_type_test.bzl", + "product_type_test", +) +load( + ":rules/common_verification_tests.bzl", + "archive_contents_test", + "bitcode_symbol_map_test", + "entry_point_test", +) +load( + ":rules/infoplist_contents_test.bzl", + "infoplist_contents_test", +) +load( + ":rules/linkmap_test.bzl", + "linkmap_test", +) + +def watchos_extensionkit_extension_test_suite(name): + """Test suite for watchos_extensionkit_extension. + + Args: + name: the base name to be used in things created by this macro + """ + apple_verification_test( + name = "{}_codesign_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + verifier_script = "verifier_scripts/codesign_verifier.sh", + tags = [name], + ) + + apple_verification_test( + name = "{}_imported_fmwk_codesign_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension_with_imported_fmwk", + verifier_script = "verifier_scripts/codesign_verifier.sh", + tags = [name], + ) + + apple_verification_test( + name = "{}_entitlements_simulator_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + verifier_script = "verifier_scripts/entitlements_verifier.sh", + tags = [name], + ) + + apple_verification_test( + name = "{}_entitlements_device_test".format(name), + build_type = "device", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + verifier_script = "verifier_scripts/entitlements_verifier.sh", + tags = [name], + ) + + archive_contents_test( + name = "{}_resources_simulator_test".format(name), + build_type = "simulator", + contains = [ + "$RESOURCE_ROOT/resource_bundle.bundle/Info.plist", + "$RESOURCE_ROOT/Another.plist", + "$RESOURCE_ROOT/Assets.car", + ], + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + tags = [name], + ) + + archive_contents_test( + name = "{}_strings_simulator_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + contains = [ + "$RESOURCE_ROOT/localization.bundle/en.lproj/files.stringsdict", + "$RESOURCE_ROOT/localization.bundle/en.lproj/greetings.strings", + ], + tags = [name], + ) + + archive_contents_test( + name = "{}_imported_fmwk_simulator_test".format(name), + build_type = "simulator", + contains = [ + "$RESOURCE_ROOT/Frameworks/generated_watchos_dynamic_fmwk.framework/generated_watchos_dynamic_fmwk", + "$RESOURCE_ROOT/Frameworks/generated_watchos_dynamic_fmwk.framework/Info.plist", + ], + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension_with_imported_fmwk", + tags = [name], + ) + + infoplist_contents_test( + name = "{}_plist_test".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + expected_values = { + "BuildMachineOSBuild": "*", + "CFBundleExecutable": "exappextension", + "CFBundleIdentifier": "com.google.example.exappextension", + "CFBundleName": "exappextension", + "CFBundlePackageType": "XPC!", + "CFBundleSupportedPlatforms:0": "WatchSimulator*", + "DTCompiler": "com.apple.compilers.llvm.clang.1_0", + "DTPlatformBuild": "*", + "DTPlatformName": "watchsimulator*", + "DTPlatformVersion": "*", + "DTSDKBuild": "*", + "DTSDKName": "watchsimulator*", + "DTXcode": "*", + "DTXcodeBuild": "*", + "MinimumOSVersion": common.min_os_watchos.baseline, + "EXAppExtensionAttributes:EXExtensionPointIdentifier": "com.apple.app-intents", + "UIDeviceFamily:0": "4", + }, + tags = [name], + ) + + infoplist_contents_test( + name = "{}_multiple_plist_test".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension_multiple_infoplists", + expected_values = { + "AnotherKey": "AnotherValue", + "CFBundleExecutable": "exappextension_multiple_infoplists", + }, + tags = [name], + ) + + # Tests that the archive contains Bitcode symbol maps when Bitcode is + # enabled. We have to test this by building a companion iOS application, + # since the symbol maps are only included in a top-level archive for + # distribution. + bitcode_symbol_map_test( + name = "{}_archive_contains_bitcode_symbol_maps_test".format(name), + binary_paths = [ + "Payload/app_companion.app/Watch/app.app/Extensions/exappextension.appex/exappextension", + ], + target_under_test = "//test/starlark_tests/targets_under_test/watchos:app_companion", + tags = [name], + ) + + # Tests that the linkmap outputs are produced when `--objc_generate_linkmap` + # is present. + linkmap_test( + name = "{}_linkmap_test".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + tags = [name], + ) + + # Tests that the provisioning profile is present when built for device. + archive_contents_test( + name = "{}_contains_provisioning_profile_test".format(name), + build_type = "device", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + contains = [ + "$BUNDLE_ROOT/embedded.mobileprovision", + ], + tags = [name], + ) + + archive_contents_test( + name = "{}_correct_rpath_header_value_test".format(name), + build_type = "device", + binary_test_file = "$CONTENT_ROOT/exappextension", + macho_load_commands_contain = [ + "path @executable_path/Frameworks (offset 12)", + "path @executable_path/../../Frameworks (offset 12)", + ], + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + tags = [name], + ) + + entry_point_test( + name = "{}_entry_point_test".format(name), + build_type = "simulator", + entry_point = "_NSExtensionMain", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + tags = [name], + ) + + entry_point_test( + name = "{}_entry_point_exappextension_test".format(name), + build_type = "simulator", + entry_point = "_NSExtensionMain", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:watchos_exappextension", + tags = [name], + ) + + product_type_test( + name = "{}_product_type_watchkit_extension".format(name), + expected_product_type = apple_product_type.extensionkit_extension, + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension", + tags = [name], + ) + + product_type_test( + name = "{}_product_type_exappextension".format(name), + expected_product_type = apple_product_type.extensionkit_extension, + target_under_test = "//test/starlark_tests/targets_under_test/watchos:watchos_exappextension", + ) + + # Test that the output binary omits the 32 bit watchOS slice when built for a minimum OS that + # does not support 32 bit architectures. + archive_contents_test( + name = "{}_watchos_binary_contents_dropping_32_bit_device_archs_test".format(name), + build_type = "device", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension_arm64_support", + cpus = { + "watchos_cpus": ["armv7k", "arm64_32"], + }, + binary_test_file = "$BINARY", + binary_not_contains_architectures = ["armv7k"], + tags = [name], + ) + + # Test that the watchOS output binary still contains the 64 bit Arm slice when built for a + # minimum OS that does not support 32 bit architectures. + archive_contents_test( + name = "{}_watchos_binary_contents_retains_arm64_32_when_dropping_32_bit_device_archs_test".format(name), + build_type = "device", + target_under_test = "//test/starlark_tests/targets_under_test/watchos:exappextension_arm64_support", + cpus = { + "watchos_cpus": ["armv7k", "arm64_32"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "arm64_32", + macho_load_commands_contain = ["cmd LC_BUILD_VERSION"], + tags = [name], + ) + + native.test_suite( + name = name, + tags = [name], + ) diff --git a/test/tvos_extension_test.sh b/test/tvos_extension_test.sh index ab989d87e2..77ed4458cb 100755 --- a/test/tvos_extension_test.sh +++ b/test/tvos_extension_test.sh @@ -100,7 +100,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -118,7 +118,7 @@ function test_missing_version_fails() { CFBundleShortVersionString = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -141,7 +141,7 @@ function test_missing_short_version_fails() { CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -219,7 +219,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -295,7 +295,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -371,7 +371,7 @@ EOF CFBundleVersion = "1.1"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF diff --git a/test/tvos_framework_test.sh b/test/tvos_framework_test.sh index fc58fece8c..4b90f052c3 100755 --- a/test/tvos_framework_test.sh +++ b/test/tvos_framework_test.sh @@ -137,7 +137,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -349,7 +349,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -776,7 +776,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF @@ -1539,7 +1539,7 @@ EOF CFBundleVersion = "1.0"; NSExtension = { NSExtensionPrincipalClass = "DummyValue"; - NSExtensionPointIdentifier = "com.apple.widget-extension"; + NSExtensionPointIdentifier = "com.apple.widgetkit-extension"; }; } EOF diff --git a/tools/plisttool/plisttool.py b/tools/plisttool/plisttool.py index c1bd5c8ebb..1078886bf4 100644 --- a/tools/plisttool/plisttool.py +++ b/tools/plisttool/plisttool.py @@ -70,12 +70,18 @@ compared against the final compiled plist for consistency. The keys of the dictionary are the labels of the targets to which the associated plists belong. See below for the details of how these are validated. - child_plist_required_values: If present, a dictionary constaining the + child_plist_required_values: If present, a dictionary containing the entries for key/value pairs a child is required to have. This dictionary is keyed by the label of the child targets (just like the `child_plists`), and the valures are a list of key/value pairs. The key/value pairs are encoded as a list of exactly two items, the key is actually an array of keys, so it can walk into the child plist. + extensionkit_keys_required: If True, the merged Info.plist file must include + an EXAppExtensionAttributes dictionary containing EXExtensionPointIdentifier. + The presence of an NSExtension entry will raise an error. + nsextension_keys_required: If True, the merged Info.plist file must include + an NSExtension dictionary containing NSExtensionPointIdentifier. + The presence of an EXAppExtensionAttributes entry will raise an error. If info_plist_options is present, validation will be performed on the output file after merging is complete. If any of the following conditions are not @@ -185,10 +191,14 @@ 'has the wrong value for "%s"; expected %r, but found %r.' ) -MISSING_VERSION_KEY_MSG = ( +MISSING_PLIST_KEY_MSG = ( 'Target "%s" is missing %s.' ) +MISSING_PLIST_KEY_IN_PARENT_MSG = ( + 'Target "%s" is missing %s in dictionary %s.' +) + INVALID_VERSION_KEY_VALUE_MSG = ( 'Target "%s" has a %s that doesn\'t meet Apple\'s guidelines: "%s". See ' 'https://developer.apple.com/library/content/technotes/tn2420/_index.html' @@ -196,6 +206,89 @@ 'https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html' ) +UNEXPECTED_EXAPPEXTENSIONATTRIBUTES = ( + 'Target %s has an unexpected key, EXAppExtensionAttributes, for product type com.apple.product-type.app-extension. ' + 'Plugin extensions expect key NSExtension. ' + 'To build an app extension with ExtensionKit, set `extensionkit_extension = True`. ' +) + +UNEXPECTED_NSEXTENSION = ( + 'Target %s has an unexpected key, NSExtension, for product type com.apple.product-type.extensionkit-extension. ' + 'ExtensionKit extensions expect key EXAppExtensionAttributes. ' + 'To build an app extension without ExtensionKit, set `extensionkit_extension = False`. ' +) + +KNOWN_EXTENSIONKIT_EXTENSION_POINT_IDENTIFIERS = ( + 'com.apple.appintents-extension', + 'com.apple.background-asset-downloader-extension', + 'com.apple.deviceactivityui.report-extension', + 'com.apple.discovery-extension' +) + +KNOWN_NSEXTENSION_EXTENSION_POINT_IDENTIFIERS = ( + 'com.apple.AppSSO.idp-extension', + 'com.apple.AudioUnit', + 'com.apple.AudioUnit-UI', + 'com.apple.FinderSync', + 'com.apple.ManagedSettings.shield-action-service', + 'com.apple.ManagedSettingsUI.shield-configuration-service', + 'com.apple.Safari.content-blocker', + 'com.apple.Safari.extension', + 'com.apple.Safari.web-extension', + 'com.apple.authentication-services-account-authentication-modification-ui', + 'com.apple.authentication-services-credential-provider-ui', + 'com.apple.broadcast-services-setupui', + 'com.apple.broadcast-services-upload', + 'com.apple.calendar.virtualconference', + 'com.apple.callkit.call-directory', + 'com.apple.classkit.context-provider', + 'com.apple.ctk-tokens', + 'com.apple.deviceactivity.monitor-extension', + 'com.apple.dt.Xcode.extension.source-editor', + 'com.apple.email.extension', + 'com.apple.fileprovider-actionsui', + 'com.apple.fileprovider-nonui', + 'com.apple.identitylookup.classification-ui', + 'com.apple.identitylookup.message-filter', + 'com.apple.intents-service', + 'com.apple.intents-ui-service', + 'com.apple.keyboard-service', + 'com.apple.location.push.service', + 'com.apple.matter.support.extension.device-setup', + 'com.apple.message-payload-provider', + 'com.apple.message-payload-provider', + 'com.apple.networkextension.app-proxy', + 'com.apple.networkextension.dns-proxy', + 'com.apple.networkextension.filter-control', + 'com.apple.networkextension.filter-data', + 'com.apple.networkextension.packet-tunnel', + 'com.apple.photo-editing', + 'com.apple.photo-project', + 'com.apple.printing.discovery', + 'com.apple.quicklook.preview', + 'com.apple.quicklook.thumbnail', + 'com.apple.services', + 'com.apple.share-services', + 'com.apple.spotlight.import', + 'com.apple.tv-top-shelf', + 'com.apple.ui-services', + 'com.apple.usernotifications.content-extension', + 'com.apple.usernotifications.service', + 'com.apple.widgetkit-extension', +) + +KNOWN_EXTENSIONKIT_WARNING = ( + 'Target %s with extension point %s is known to be an ExtensionKit App Extension. ' + 'The target rule may be misconfigured. ' + 'ExtensionKit App Extension targets expect the attribute `extensionkit_extension = True`.' +) + +KNOWN_NSEXTENSION_WARNING = ( + 'Target %s with extension point %s is known to be an NSExtension. ' + 'The target rule may be misconfigured. ' + 'NSExtension targets expect the attribute `extensionkit_extension = False`.' +) + PLUTIL_CONVERSION_TO_XML_FAILED_MSG = ( 'While processing target "%s", plutil failed (%d) to convert "%s" to xml.' ) @@ -343,8 +436,8 @@ # All valid keys in the info_plist_options control structure. _INFO_PLIST_OPTIONS_KEYS = frozenset([ - 'child_plists', 'child_plist_required_values', 'pkginfo', 'version_file', - 'version_keys_required', + 'child_plists', 'child_plist_required_values', 'extensionkit_keys_required', + 'nsextension_keys_required', 'pkginfo', 'version_file', 'version_keys_required', ]) # All valid keys in the entitlements_options control structure. @@ -897,11 +990,25 @@ def update_plist(self, out_plist, subs_engine): out_plist['CFBundleShortVersionString'] = short_version_string def validate_plist(self, plist): + self._validate_plist_version(plist) + self._validate_child_plist_required_values(plist) + self._validate_plist_extensionkit(plist) + self._validate_plist_nsextension(plist) + + def _validate_plist_version(self, plist): + """When `version_keys_required`, checks that the given plist has version keys. Validates version keys. + + Args: + plist: The dictionary representing final plist. + Raises: + PlistToolError: For any issues found. + """ + if self.options.get('version_keys_required'): for k in ('CFBundleVersion', 'CFBundleShortVersionString'): # This also errors if they are there but the empty string or zero. if not plist.get(k, None): - raise PlistToolError(MISSING_VERSION_KEY_MSG % (self.target, k)) + raise PlistToolError(MISSING_PLIST_KEY_MSG % (self.target, k)) # If the version keys are set, they must be valid (even if they were # not required). @@ -913,6 +1020,15 @@ def validate_plist(self, plist): raise PlistToolError(INVALID_VERSION_KEY_VALUE_MSG % ( self.target, k, v)) + def _validate_child_plist_required_values(self, plist): + """Validates required values. + + Args: + plist: The dictionary representing final plist. + Raises: + PlistToolError: For any issues found. + """ + child_plists = self.options.get('child_plists') child_plist_required_values = self.options.get( 'child_plist_required_values') @@ -928,6 +1044,133 @@ def validate_plist(self, plist): else: self._write_pkginfo(pkginfo_file, plist) + def _validate_plist_extensionkit(self, plist): + """When `extensionkit_keys_required`, checks that the given plist is valid for an ExtensionKit App Extension. + + ExtensionKit App Extensions expect a `EXAppExtensionAttributes` dictionary containing an `EXExtensionPointIdentifier` entry. + If the given extension point is not recognized as an ExtensionKit extension, but known to be an NSExtension, provide a + warning with a hint to resolve; this is not a blocking error, since supported extension point strings may change over time. + If an NSExtension key is found, raise an error. If the ExtensionKit keys are not found, raise an error. + + Args: + plist: The dictionary representing final plist. + Raises: + PlistToolError: For any issues found. + """ + + if not self.options.get('extensionkit_keys_required'): + return + + # Check for extension point identifiers within any keys, so we can surface useful warnings before errors. + unchecked_extension_point_identifier = self._any_extension_point_identifier(plist) + known_extension_point = False + for known_extension_point_identifier in KNOWN_EXTENSIONKIT_EXTENSION_POINT_IDENTIFIERS: + if unchecked_extension_point_identifier == known_extension_point_identifier: + # This is a known ExtensionKit extension point, skip checking against known NSExtension extension points, + # since the lists may overlap + known_extension_point = True + + if not known_extension_point: + for known_extension_point_identifier in KNOWN_NSEXTENSION_EXTENSION_POINT_IDENTIFIERS: + if unchecked_extension_point_identifier == known_extension_point_identifier: + # This is a known NSExtension extension point, and we can provide a useful warning. + # Don't raise a blocking error, since Apple may change available extension points in the future. + print(KNOWN_NSEXTENSION_WARNING % (self.target, unchecked_extension_point_identifier)) + + # Explicitly check against None, since an empty dictionary should raise an error + if plist.get('NSExtension', None) is not None: + raise PlistToolError(UNEXPECTED_NSEXTENSION % self.target) + + self._validate_extension_point_identifier(self.target, plist, 'EXAppExtensionAttributes', 'EXExtensionPointIdentifier') + + def _validate_plist_nsextension(self, plist): + """When `nsextension_keys_required`, checks that the given plist is valid for an NSExtension App Extension. + + NSExtension App Extensions expect a `NSExtension` dictionary containing an `NSExtensionPointIdentifier` entry. + If the given extension point is not recognized as an NSExtension, but known to be an ExtensionKit Extension, provide a + warning with a hint to resolve; this is not a blocking error, since supported extension point strings may change over time. + If an EXAppExtensionAttributes key is found, raise an error. If the NSExtension keys are not found, raise an error. + + Args: + plist: The dictionary representing final plist. + Raises: + PlistToolError: For any issues found. + """ + + if not self.options.get('nsextension_keys_required'): + return + + # Check for extension point identifiers within any keys, so we can surface useful warnings before errors. + unchecked_extension_point_identifier = self._any_extension_point_identifier(plist) + known_extension_point = False + for known_extension_point_identifier in KNOWN_NSEXTENSION_EXTENSION_POINT_IDENTIFIERS: + if unchecked_extension_point_identifier == known_extension_point_identifier: + # This is a known NSExtension extension point, skip checking against known ExtensionKit extension points, + # since the lists may overlap + known_extension_point = True + + if not known_extension_point: + for known_extension_point_identifier in KNOWN_EXTENSIONKIT_EXTENSION_POINT_IDENTIFIERS: + if unchecked_extension_point_identifier == known_extension_point_identifier: + # This is a known ExtensionKit extension point, and we can provide a useful warning. + # Don't raise a blocking error, since Apple may change available extension points in the future. + print(KNOWN_EXTENSIONKIT_WARNING % (self.target, unchecked_extension_point_identifier)) + + # Explicitly check against None, since an empty dictionary should raise an error + if plist.get('EXAppExtensionAttributes', None) is not None: + raise PlistToolError(UNEXPECTED_EXAPPEXTENSIONATTRIBUTES % self.target) + + self._validate_extension_point_identifier(self.target, plist, 'NSExtension', 'NSExtensionPointIdentifier') + + @staticmethod + def _any_extension_point_identifier(plist): + """Finds any extension point identifier within an Info.plist. + + The extension point identifier may not exist in the correct key, but this can be used to provide actionable warnings. + + Args: + plist: The dictionary representing final plist. + Returns: + An extension point identifier string + """ + nsextension_parent = plist.get('NSExtension', None) + if nsextension_parent: + nsextension_point_identifier = nsextension_parent.get('NSExtensionPointIdentifier') + if nsextension_point_identifier: + return nsextension_point_identifier + + exappextension_parent = plist.get('EXAppExtensionAttributes', None) + if exappextension_parent: + exappextension_point_identifier = exappextension_parent.get('EXExtensionPointIdentifier') + if exappextension_point_identifier: + return exappextension_point_identifier + + return None + + @staticmethod + def _validate_extension_point_identifier(target, plist, parent_key, extension_point_identifier_key): + """Finds an extension point identifier within the given keys. + + The extension point identifier may not exist in the correct key, but this can be used to provide actionable warnings. + + Args: + target: The name of the target being processed. + plist: The dictionary representing final plist. + parent_key: The top-level plist key containing the extension point identifier (NSExtension or EXAppExtensionAttributes) + extension_point_identifier_key: The key within `parent` (NSExtensionPointIdentifier or EXExtensionPointIdentifier) + Raises: + PlistToolError: For missing keys. + Returns: + An extension point identifier string + """ + parent = plist.get(parent_key, None) + if not parent: + raise PlistToolError(MISSING_PLIST_KEY_MSG % (target, parent_key)) + + extension_point_identifier = parent.get(extension_point_identifier_key, None) + if not extension_point_identifier: + raise PlistToolError(MISSING_PLIST_KEY_IN_PARENT_MSG % (target, extension_point_identifier_key, parent_key)) + @staticmethod def _validate_children(plist, child_plists, child_required_values, target): """Validates a target's plist is consistent with its children. diff --git a/tools/plisttool/plisttool_unittest.py b/tools/plisttool/plisttool_unittest.py index d594a69087..7910804786 100644 --- a/tools/plisttool/plisttool_unittest.py +++ b/tools/plisttool/plisttool_unittest.py @@ -1091,7 +1091,7 @@ def test_unknown_info_plist_options_keys_raise(self): def test_missing_version(self): with self.assertRaisesRegex( plisttool.PlistToolError, - re.escape(plisttool.MISSING_VERSION_KEY_MSG % ( + re.escape(plisttool.MISSING_PLIST_KEY_MSG % ( _testing_target, 'CFBundleVersion'))): plist = {'CFBundleShortVersionString': '1.0'} _plisttool_result({ @@ -1104,7 +1104,7 @@ def test_missing_version(self): def test_missing_short_version(self): with self.assertRaisesRegex( plisttool.PlistToolError, - re.escape(plisttool.MISSING_VERSION_KEY_MSG % ( + re.escape(plisttool.MISSING_PLIST_KEY_MSG % ( _testing_target, 'CFBundleShortVersionString'))): plist = {'CFBundleVersion': '1.0'} _plisttool_result({ @@ -1117,7 +1117,7 @@ def test_missing_short_version(self): def test_empty_version(self): with self.assertRaisesRegex( plisttool.PlistToolError, - re.escape(plisttool.MISSING_VERSION_KEY_MSG % ( + re.escape(plisttool.MISSING_PLIST_KEY_MSG % ( _testing_target, 'CFBundleVersion'))): plist = { 'CFBundleShortVersionString': '1.0', @@ -1133,7 +1133,7 @@ def test_empty_version(self): def test_empty_short_version(self): with self.assertRaisesRegex( plisttool.PlistToolError, - re.escape(plisttool.MISSING_VERSION_KEY_MSG % ( + re.escape(plisttool.MISSING_PLIST_KEY_MSG % ( _testing_target, 'CFBundleShortVersionString'))): plist = { 'CFBundleShortVersionString': '', @@ -1197,6 +1197,186 @@ def test_entitlements_options_var_subs(self): }, }, {'Foo': 'abc123.'}) + def test_invalid_nsextension_attribute_in_extensionkit(self): + with self.assertRaisesRegex( + plisttool.PlistToolError, + re.escape(plisttool.UNEXPECTED_NSEXTENSION % _testing_target)): + plist = { + 'NSExtension': { + 'NSExtensionPointIdentifier': 'com.apple.email.extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'extensionkit_keys_required': True, + }, + }) + + def test_empty_nsextension_attribute_in_extensionkit(self): + with self.assertRaisesRegex( + plisttool.PlistToolError, + re.escape(plisttool.UNEXPECTED_NSEXTENSION % _testing_target)): + plist = { + 'NSExtension': { }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'extensionkit_keys_required': True, + }, + }) + + def test_extra_nsextension_attribute_in_extensionkit(self): + with self.assertRaisesRegex( + plisttool.PlistToolError, + re.escape(plisttool.UNEXPECTED_NSEXTENSION % _testing_target)): + plist = { + 'EXAppExtensionAttributes': { + 'EXExtensionPointIdentifier': 'com.apple.appintents-extension', + }, + 'NSExtension': { + 'NSExtensionPointIdentifier': 'com.apple.appintents-extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'extensionkit_keys_required': True, + }, + }) + + def test_valid_extensionkit(self): + plist = { + 'EXAppExtensionAttributes': { + 'EXExtensionPointIdentifier': 'com.apple.appintents-extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'extensionkit_keys_required': True, + }, + }) + + def test_valid_extensionkit_with_known_nsextension(self): + # Although `com.apple.email.extension` is a known NSExtension, don't raise an error, + # since this may change in the future. + plist = { + 'EXAppExtensionAttributes': { + 'EXExtensionPointIdentifier': 'com.apple.email.extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'extensionkit_keys_required': True, + }, + }) + + def test_valid_extensionkit_with_unknown_extension_point(self): + plist = { + 'EXAppExtensionAttributes': { + 'EXExtensionPointIdentifier': 'com.example.extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'extensionkit_keys_required': True, + }, + }) + + def test_invalid_extensionkit_attribute_in_nsextension(self): + with self.assertRaisesRegex( + plisttool.PlistToolError, + re.escape(plisttool.UNEXPECTED_EXAPPEXTENSIONATTRIBUTES % _testing_target)): + plist = { + 'EXAppExtensionAttributes': { + 'EXExtensionPointIdentifier': 'com.apple.appintents-extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'nsextension_keys_required': True, + }, + }) + + def test_empty_extensionkit_attribute_in_nsextension(self): + with self.assertRaisesRegex( + plisttool.PlistToolError, + re.escape(plisttool.UNEXPECTED_EXAPPEXTENSIONATTRIBUTES % _testing_target)): + plist = { + 'EXAppExtensionAttributes': { }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'nsextension_keys_required': True, + }, + }) + + def test_extra_extensionkit_attribute_in_nsextension(self): + with self.assertRaisesRegex( + plisttool.PlistToolError, + re.escape(plisttool.UNEXPECTED_EXAPPEXTENSIONATTRIBUTES % _testing_target)): + plist = { + 'EXAppExtensionAttributes': { + 'EXExtensionPointIdentifier': 'com.apple.email.extension', + }, + 'NSExtension': { + 'NSExtensionPointIdentifier': 'com.apple.email.extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'nsextension_keys_required': True, + }, + }) + + def test_valid_nsextension(self): + plist = { + 'NSExtension': { + 'NSExtensionPointIdentifier': 'com.apple.email.extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'nsextension_keys_required': True, + }, + }) + + def test_valid_nsextension_with_known_extensionkit(self): + # Although `com.apple.appintents-extension` is a known ExtensionKit extension, + # don't raise an error, since this may change in the future. + plist = { + 'NSExtension': { + 'NSExtensionPointIdentifier': 'com.apple.appintents-extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'nsextension_keys_required': True, + }, + }) + + def test_valid_nsextension_with_unknown_extension_point(self): + plist = { + 'NSExtension': { + 'NSExtensionPointIdentifier': 'com.apple.some-new-extension', + }, + } + _plisttool_result({ + 'plists': [plist], + 'info_plist_options': { + 'nsextension_keys_required': True, + }, + }) + def test_entitlements_options_raw_subs(self): plist1 = {'Bar': 'abc123.*'} self._assert_plisttool_result({