diff --git a/apple/BUILD b/apple/BUILD index 55bbd64a3c..f051e24ee2 100644 --- a/apple/BUILD +++ b/apple/BUILD @@ -72,6 +72,14 @@ bzl_library( ], ) +bzl_library( + name = "apple_archive", + srcs = ["apple_archive.bzl"], + deps = [ + "//apple/internal:apple_archive", + ], +) + bzl_library( name = "aspects", srcs = ["aspects.bzl"], diff --git a/apple/apple_archive.bzl b/apple/apple_archive.bzl new file mode 100644 index 0000000000..5e1dd3faca --- /dev/null +++ b/apple/apple_archive.bzl @@ -0,0 +1,24 @@ +# Copyright 2025 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. + +""" +Rules for creating iOS Package Archive (.ipa). +""" + +load( + "//apple/internal:apple_archive.bzl", + _apple_archive = "apple_archive", +) + +apple_archive = _apple_archive diff --git a/apple/internal/BUILD b/apple/internal/BUILD index e77d5b93d4..3a89fe2e16 100644 --- a/apple/internal/BUILD +++ b/apple/internal/BUILD @@ -75,6 +75,24 @@ bzl_library( ], ) +bzl_library( + name = "apple_archive", + srcs = ["apple_archive.bzl"], + visibility = [ + "//apple:__subpackages__", + ], + deps = [ + ":apple_toolchains", + ":providers", + "//apple:providers", + "//apple/internal/providers:apple_messages_stub_info", + "//apple/internal/providers:apple_swift_dylibs_info", + "//apple/internal/providers:apple_symbols_file_info", + "//apple/internal/providers:apple_watchos_stub_info", + "//apple/internal/utils:defines", + ], +) + bzl_library( name = "apple_universal_binary", srcs = ["apple_universal_binary.bzl"], diff --git a/apple/internal/apple_archive.bzl b/apple/internal/apple_archive.bzl new file mode 100644 index 0000000000..e537cc68c5 --- /dev/null +++ b/apple/internal/apple_archive.bzl @@ -0,0 +1,351 @@ +""" +Rule for packaging a bundle into an Apple archive. +""" + +load( + "//apple:providers.bzl", + "AppleBundleInfo", + "AppleCodesigningDossierInfo", +) +load( + "//apple/internal:apple_toolchains.bzl", + "AppleXPlatToolsToolchainInfo", +) +load( + "//apple/internal:providers.bzl", + "new_applebundleinfo", +) +load( + "//apple/internal/providers:apple_messages_stub_info.bzl", + "AppleMessagesStubInfo", +) +load( + "//apple/internal/providers:apple_swift_dylibs_info.bzl", + "AppleSwiftDylibsInfo", +) +load( + "//apple/internal/providers:apple_symbols_file_info.bzl", + "AppleSymbolsFileInfo", +) +load( + "//apple/internal/providers:apple_watchos_stub_info.bzl", + "AppleWatchosStubInfo", +) +load( + "//apple/internal/utils:defines.bzl", + "defines", +) + +def _should_compress_archive(ctx): + """Determines if the archive should be compressed based on defines and compilation mode.""" + return defines.bool_value( + config_vars = ctx.var, + define_name = "apple.compress_ipa", + default = (ctx.var.get("COMPILATION_MODE") == "opt"), + ) + +def _collect_symbols_files(ctx, bundle_merge_files): + """Collects symbols files and adds them to bundle_merge_files. + + Args: + ctx: The rule context. + bundle_merge_files: List to append symbols merge structs to. + + Returns: + List of symbols input files. + """ + symbols_inputs = [] + if ctx.attr.include_symbols and AppleSymbolsFileInfo in ctx.attr.bundle: + symbols_info = ctx.attr.bundle[AppleSymbolsFileInfo] + symbols_inputs = symbols_info.symbols_output_dirs.to_list() + for symbols_dir in symbols_inputs: + bundle_merge_files.append( + struct( + src = symbols_dir.path, + dest = "Symbols", + ), + ) + return symbols_inputs + +def _collect_swift_support_files(ctx, bundle_merge_files): + """Collects Swift support files and adds them to bundle_merge_files. + + Args: + ctx: The rule context. + bundle_merge_files: List to append Swift support merge structs to. + + Returns: + List of Swift support input files. + """ + swift_support_inputs = [] + if AppleSwiftDylibsInfo in ctx.attr.bundle: + swift_dylibs_info = ctx.attr.bundle[AppleSwiftDylibsInfo] + for platform_name, swift_support_dir in swift_dylibs_info.swift_support_files: + swift_support_inputs.append(swift_support_dir) + bundle_merge_files.append( + struct( + src = swift_support_dir.path, + dest = "SwiftSupport/%s" % platform_name, + ), + ) + return swift_support_inputs + +def _collect_watchos_stub_files(ctx, bundle_merge_files): + """Collects WatchOS stub files and adds them to bundle_merge_files. + + Args: + ctx: The rule context. + bundle_merge_files: List to append WatchOS stub merge structs to. + + Returns: + List of WatchOS stub input files. + """ + watchos_stub_inputs = [] + if AppleWatchosStubInfo in ctx.attr.bundle: + watchos_stub_info = ctx.attr.bundle[AppleWatchosStubInfo] + watchos_stub_inputs.append(watchos_stub_info.binary) + bundle_merge_files.append( + struct( + src = watchos_stub_info.binary.path, + dest = "WatchKitSupport2/WK", + ), + ) + return watchos_stub_inputs + +def _collect_messages_stub_files(ctx, bundle_merge_files): + """Collects iMessage stub files and adds them to bundle_merge_files. + + Args: + ctx: The rule context. + bundle_merge_files: List to append messages stub merge structs to. + + Returns: + List of messages stub input files. + """ + messages_stub_inputs = [] + if AppleMessagesStubInfo in ctx.attr.bundle: + messages_stub_info = ctx.attr.bundle[AppleMessagesStubInfo] + if messages_stub_info.messages_application_support: + messages_stub_inputs.append(messages_stub_info.messages_application_support) + bundle_merge_files.append( + struct( + src = messages_stub_info.messages_application_support.path, + dest = "MessagesApplicationSupport/MessagesApplicationSupportStub", + ), + ) + if messages_stub_info.messages_extension_support: + messages_stub_inputs.append(messages_stub_info.messages_extension_support) + bundle_merge_files.append( + struct( + src = messages_stub_info.messages_extension_support.path, + dest = "MessagesApplicationExtensionSupport/MessagesApplicationExtensionSupportStub", + ), + ) + return messages_stub_inputs + +def _create_archive_file(ctx, bundle_info, bundletool, should_compress, all_inputs): + """Creates the archive file using bundletool. + + Args: + ctx: The rule context. + bundle_info: The AppleBundleInfo provider. + bundletool: The bundletool executable. + should_compress: Whether to compress the archive. + all_inputs: Tuple of (bundle_merge_files, symbols_inputs, swift_support_inputs, + watchos_stub_inputs, messages_stub_inputs). + + Returns: + The declared archive file. + """ + bundle_merge_files, symbols_inputs, swift_support_inputs, watchos_stub_inputs, messages_stub_inputs = all_inputs + + archive = ctx.actions.declare_file("%s.ipa" % bundle_info.bundle_name) + + control = struct( + bundle_merge_files = bundle_merge_files, + output = archive.path, + compress = should_compress, + ) + + control_file = ctx.actions.declare_file("%s_control.json" % ctx.label.name) + ctx.actions.write( + output = control_file, + content = json.encode(control), + ) + + ctx.actions.run( + executable = bundletool, + arguments = [control_file.path], + inputs = [ + control_file, + bundle_info.archive, + ] + symbols_inputs + swift_support_inputs + watchos_stub_inputs + messages_stub_inputs, + outputs = [archive], + mnemonic = "CreateIPA", + ) + + return archive + +def _create_combined_dossier_zip(ctx, bundle_info, bundletool, archive, dossier_zip): + """Creates a combined zip file containing both the IPA and dossier. + + Args: + ctx: The rule context. + bundle_info: The AppleBundleInfo provider. + bundletool: The bundletool executable. + archive: The archive file. + dossier_zip: The dossier zip file. + + Returns: + The combined zip file. + """ + combined_zip = ctx.actions.declare_file("%s_dossier_with_bundle.zip" % bundle_info.bundle_name) + + control = struct( + bundle_merge_zips = [ + struct(src = archive.path, dest = "bundle"), + struct(src = dossier_zip.path, dest = "dossier"), + ], + output = combined_zip.path, + ) + + control_file = ctx.actions.declare_file("%s_combined_control.json" % ctx.label.name) + ctx.actions.write( + output = control_file, + content = json.encode(control), + ) + + ctx.actions.run( + executable = bundletool, + arguments = [control_file.path], + inputs = [control_file, archive, dossier_zip], + outputs = [combined_zip], + mnemonic = "CreateCombinedDossierZip", + ) + + return combined_zip + +def _create_apple_bundle_info(bundle_info, archive): + """Creates an AppleBundleInfo provider for the archive. + + Args: + bundle_info: The original AppleBundleInfo from the bundle. + archive: The archive file. + + Returns: + An AppleBundleInfo provider. + """ + return new_applebundleinfo( + archive = archive, + archive_root = bundle_info.archive_root, + binary = bundle_info.binary, + bundle_extension = bundle_info.bundle_extension, + bundle_id = bundle_info.bundle_id, + bundle_name = bundle_info.bundle_name, + entitlements = bundle_info.entitlements, + executable_name = bundle_info.executable_name, + extension_safe = bundle_info.extension_safe, + infoplist = bundle_info.infoplist, + minimum_deployment_os_version = bundle_info.minimum_deployment_os_version, + minimum_os_version = bundle_info.minimum_os_version, + platform_type = bundle_info.platform_type, + product_type = bundle_info.product_type, + uses_swift = bundle_info.uses_swift, + ) + +def _apple_archive_impl(ctx): + """ + Implementation for apple_archive. + + This rule uses the providers from the bundle target to re-package it into a .ipa. + The .ipa is a directory that contains the Payload/*.app bundle. + """ + bundle_info = ctx.attr.bundle[AppleBundleInfo] + xplat_tools = ctx.attr._xplat_tools[AppleXPlatToolsToolchainInfo] + bundletool = xplat_tools.bundletool + + should_compress = _should_compress_archive(ctx) + + bundle_merge_files = [ + struct( + src = bundle_info.archive.path, + dest = "Payload/%s%s" % (bundle_info.bundle_name, bundle_info.bundle_extension), + ), + ] + + symbols_inputs = _collect_symbols_files(ctx, bundle_merge_files) + swift_support_inputs = _collect_swift_support_files(ctx, bundle_merge_files) + watchos_stub_inputs = _collect_watchos_stub_files(ctx, bundle_merge_files) + messages_stub_inputs = _collect_messages_stub_files(ctx, bundle_merge_files) + + all_inputs = (bundle_merge_files, symbols_inputs, swift_support_inputs, watchos_stub_inputs, messages_stub_inputs) + archive = _create_archive_file(ctx, bundle_info, bundletool, should_compress, all_inputs) + + output_groups = {} + if AppleCodesigningDossierInfo in ctx.attr.bundle: + dossier_info = ctx.attr.bundle[AppleCodesigningDossierInfo] + dossier_zip = dossier_info.dossier + combined_zip = _create_combined_dossier_zip(ctx, bundle_info, bundletool, archive, dossier_zip) + output_groups["combined_dossier_zip"] = depset([combined_zip]) + + apple_archive_bundle_info = _create_apple_bundle_info(bundle_info, archive) + + return [ + DefaultInfo(files = depset([archive])), + apple_archive_bundle_info, + OutputGroupInfo(**output_groups), + ] + +apple_archive = rule( + implementation = _apple_archive_impl, + attrs = { + "bundle": attr.label( + providers = [ + AppleBundleInfo, + ], + doc = """\ +The label to a target to re-package into a .ipa. For example, an +`ios_application` target. + """, + ), + "include_symbols": attr.bool( + default = False, + doc = """ + If true, collects `$UUID.symbols` + files from all `{binary: .dSYM, ...}` pairs for the application and its + dependencies, then packages them under the `Symbols/` directory in the + final .ipa. + """, + ), + "_xplat_tools": attr.label( + default = Label("//apple/internal:xplat_tools_toolchain"), + providers = [AppleXPlatToolsToolchainInfo], + doc = """\ +A reference too xcplat_toolchain. + """, + ), + }, + doc = """\ +Re-packages an Apple bundle into a .ipa. + +This rule uses the providers from the bundle target to construct the required +metadata for the .ipa. + +Example: + +````starlark +load("//apple:apple_archive.bzl", "apple_archive") + +ios_application( + name = "App", + bundle_id = "com.example.my.app", + ... +) + +apple_archive( + name = "App.ipa", + bundle = ":App", +) +```` + """, +) diff --git a/apple/internal/experimental.bzl b/apple/internal/experimental.bzl index a234947a22..a327ea0d93 100644 --- a/apple/internal/experimental.bzl +++ b/apple/internal/experimental.bzl @@ -14,6 +14,10 @@ """Temporary file to centralize configuration of the experimental bundling logic.""" +load( + "//apple/internal:apple_product_type.bzl", + "apple_product_type", +) load( "//apple/internal/utils:defines.bzl", "defines", @@ -23,13 +27,15 @@ load( def is_experimental_tree_artifact_enabled( *, config_vars = None, - platform_prerequisites = None): + platform_prerequisites = None, + rule_descriptor = None): """Returns whether tree artifact outputs experiment is enabled. Args: config_vars: A reference to configuration variables, typically from `ctx.var`. platform_prerequisites: Struct containing information on the platform being targeted, if one exists for the rule. + rule_descriptor: A rule descriptor for platform and product types from the rule context. Returns: True if tree artifact outputs are enabled (via --define or build setting), False otherwise. """ @@ -39,6 +45,17 @@ def is_experimental_tree_artifact_enabled( if platform_prerequisites and platform_prerequisites.build_settings.use_tree_artifacts_outputs: return True + # Enable tree artifacts by default for iOS/tvOS/visionOS applications + # These will produce .app bundles that can be wrapped into .ipa files + if rule_descriptor and platform_prerequisites: + if (platform_prerequisites.platform.platform_type, rule_descriptor.product_type) in [ + (str(apple_common.platform_type.ios), apple_product_type.application), + (str(apple_common.platform_type.ios), apple_product_type.messages_application), + (str(apple_common.platform_type.tvos), apple_product_type.application), + (str(apple_common.platform_type.visionos), apple_product_type.application), + ]: + return True + return defines.bool_value( config_vars = platform_prerequisites.config_vars if platform_prerequisites else config_vars, define_name = "apple.experimental.tree_artifact_outputs", diff --git a/apple/internal/ios_rules.bzl b/apple/internal/ios_rules.bzl index e8fcec5967..8c2fdfc5e2 100644 --- a/apple/internal/ios_rules.bzl +++ b/apple/internal/ios_rules.bzl @@ -408,7 +408,7 @@ def _ios_application_impl(ctx): dependency_targets = embeddable_targets + ctx.attr.deps, dsym_binaries = debug_outputs.dsym_binaries, label_name = label.name, - include_symbols_in_bundle = ctx.attr.include_symbols_in_bundle, + include_symbols_in_bundle = False, platform_prerequisites = platform_prerequisites, ), ] @@ -2629,15 +2629,6 @@ A list of framework targets (see [`ios_framework`](https://github.com/bazelbuild/rules_apple/blob/main/doc/rules-ios.md#ios_framework)) that this target depends on. """, - ), - "include_symbols_in_bundle": attr.bool( - default = False, - doc = """ - If true and --output_groups=+dsyms is specified, generates `$UUID.symbols` - files from all `{binary: .dSYM, ...}` pairs for the application and its - dependencies, then packages them under the `Symbols/` directory in the - final application bundle. - """, ), "launch_storyboard": attr.label( allow_single_file = [".storyboard", ".xib"], diff --git a/apple/internal/outputs.bzl b/apple/internal/outputs.bzl index 07ffeb4e12..7fcd1906f9 100644 --- a/apple/internal/outputs.bzl +++ b/apple/internal/outputs.bzl @@ -46,6 +46,7 @@ def _archive( tree_artifact_enabled = is_experimental_tree_artifact_enabled( platform_prerequisites = platform_prerequisites, + rule_descriptor = rule_descriptor, ) if tree_artifact_enabled: if bundle_name != label_name: @@ -126,6 +127,7 @@ def _has_different_embedding_archive(*, platform_prerequisites, rule_descriptor) """Returns True if this target exposes a different archive when embedded in another target.""" tree_artifact_enabled = is_experimental_tree_artifact_enabled( platform_prerequisites = platform_prerequisites, + rule_descriptor = rule_descriptor, ) if tree_artifact_enabled: return False diff --git a/apple/internal/partials/BUILD b/apple/internal/partials/BUILD index d03d124ddc..f2120a1f56 100644 --- a/apple/internal/partials/BUILD +++ b/apple/internal/partials/BUILD @@ -56,6 +56,7 @@ bzl_library( deps = [ "//apple/internal:intermediates", "//apple/internal:processor", + "//apple/internal/providers:apple_symbols_file_info", "@bazel_skylib//lib:partial", "@bazel_skylib//lib:shell", "@build_bazel_apple_support//lib:apple_support", @@ -264,6 +265,7 @@ bzl_library( deps = [ "//apple/internal:intermediates", "//apple/internal:processor", + "//apple/internal/providers:apple_messages_stub_info", "@bazel_skylib//lib:partial", ], ) @@ -325,6 +327,7 @@ bzl_library( deps = [ "//apple/internal:intermediates", "//apple/internal:processor", + "//apple/internal/providers:apple_swift_dylibs_info", "//apple/internal/utils:defines", "@bazel_skylib//lib:partial", "@bazel_skylib//lib:paths", @@ -369,6 +372,7 @@ bzl_library( deps = [ "//apple/internal:intermediates", "//apple/internal:processor", + "//apple/internal/providers:apple_watchos_stub_info", "@bazel_skylib//lib:partial", ], ) diff --git a/apple/internal/partials/apple_symbols_file.bzl b/apple/internal/partials/apple_symbols_file.bzl index 1a5cfb3553..a67fba968c 100644 --- a/apple/internal/partials/apple_symbols_file.bzl +++ b/apple/internal/partials/apple_symbols_file.bzl @@ -35,12 +35,9 @@ load( "//apple/internal:processor.bzl", "processor", ) - -_AppleSymbolsFileInfo = provider( - doc = "Private provider to propagate the transitive .symbols `File`s.", - fields = { - "symbols_output_dirs": "Depset of `File`s containing directories of $UUID.symbols files for transitive dependencies.", - }, +load( + "//apple/internal/providers:apple_symbols_file_info.bzl", + "AppleSymbolsFileInfo", ) def _apple_symbols_file_partial_impl( @@ -89,9 +86,9 @@ def _apple_symbols_file_partial_impl( transitive_output_files = depset( direct = outputs, transitive = [ - x[_AppleSymbolsFileInfo].symbols_output_dirs + x[AppleSymbolsFileInfo].symbols_output_dirs for x in dependency_targets - if _AppleSymbolsFileInfo in x + if AppleSymbolsFileInfo in x ], ) @@ -102,7 +99,7 @@ def _apple_symbols_file_partial_impl( return struct( bundle_files = bundle_files, - providers = [_AppleSymbolsFileInfo(symbols_output_dirs = transitive_output_files)], + providers = [AppleSymbolsFileInfo(symbols_output_dirs = transitive_output_files)], ) def apple_symbols_file_partial( diff --git a/apple/internal/partials/codesigning_dossier.bzl b/apple/internal/partials/codesigning_dossier.bzl index c552a0cc9a..9a1510b03c 100644 --- a/apple/internal/partials/codesigning_dossier.bzl +++ b/apple/internal/partials/codesigning_dossier.bzl @@ -161,7 +161,8 @@ def _create_combined_zip_artifact( label_name, output_combined_zip, output_discriminator, - platform_prerequisites): + platform_prerequisites, + rule_descriptor): """Generates a zip file with the IPA contents in one subdirectory and the dossier in another. Args: @@ -205,6 +206,7 @@ def _create_combined_zip_artifact( tree_artifact_is_enabled = is_experimental_tree_artifact_enabled( platform_prerequisites = platform_prerequisites, + rule_descriptor = rule_descriptor, ) if tree_artifact_is_enabled: @@ -332,6 +334,7 @@ def _codesigning_dossier_partial_impl( output_combined_zip = output_combined_zip, output_discriminator = output_discriminator, platform_prerequisites = platform_prerequisites, + rule_descriptor = rule_descriptor, ) return struct( diff --git a/apple/internal/partials/messages_stub.bzl b/apple/internal/partials/messages_stub.bzl index d9f3da16d2..d2d27b650b 100644 --- a/apple/internal/partials/messages_stub.bzl +++ b/apple/internal/partials/messages_stub.bzl @@ -26,10 +26,15 @@ load( "//apple/internal:processor.bzl", "processor", ) +load( + "//apple/internal/providers:apple_messages_stub_info.bzl", + "AppleMessagesStubInfo", +) -_AppleMessagesStubInfo = provider( +# Private provider for internal propagation from extensions to parent app. +_AppleMessagesPrivateStubInfo = provider( doc = """ -Private provider to propagate the messages stub that needs to be package in the iOS archive. +Private provider to propagate the messages stub that needs to be packaged in the iOS archive. """, fields = { "binary": """ @@ -54,20 +59,23 @@ def _messages_stub_partial_impl( if package_messages_support: extension_binaries = [ - x[_AppleMessagesStubInfo].binary + x[_AppleMessagesPrivateStubInfo].binary for x in extensions - if _AppleMessagesStubInfo in x + if _AppleMessagesPrivateStubInfo in x ] + extension_support_file = None if extension_binaries: + extension_support_file = extension_binaries[0] bundle_files.append( ( processor.location.archive, "MessagesApplicationExtensionSupport", - depset([extension_binaries[0]]), + depset([extension_support_file]), ), ) + app_support_file = None if binary_artifact: intermediate_file = intermediates.file( actions = actions, @@ -79,6 +87,7 @@ def _messages_stub_partial_impl( target_file = binary_artifact, output = intermediate_file, ) + app_support_file = intermediate_file bundle_files.append( ( @@ -88,6 +97,13 @@ def _messages_stub_partial_impl( ), ) + # We propagate the public provider for ipa rule access + if app_support_file or extension_support_file: + providers.append(AppleMessagesStubInfo( + messages_application_support = app_support_file, + messages_extension_support = extension_support_file, + )) + elif binary_artifact: intermediate_file = intermediates.file( actions = actions, @@ -99,7 +115,7 @@ def _messages_stub_partial_impl( target_file = binary_artifact, output = intermediate_file, ) - providers.append(_AppleMessagesStubInfo(binary = intermediate_file)) + providers.append(_AppleMessagesPrivateStubInfo(binary = intermediate_file)) return struct( bundle_files = bundle_files, diff --git a/apple/internal/partials/swift_dylibs.bzl b/apple/internal/partials/swift_dylibs.bzl index 127060aecf..03f342cc31 100644 --- a/apple/internal/partials/swift_dylibs.bzl +++ b/apple/internal/partials/swift_dylibs.bzl @@ -34,29 +34,15 @@ load( "//apple/internal:processor.bzl", "processor", ) +load( + "//apple/internal/providers:apple_swift_dylibs_info.bzl", + "AppleSwiftDylibsInfo", +) load( "//apple/internal/utils:defines.bzl", "defines", ) -_AppleSwiftDylibsInfo = provider( - doc = """ -Private provider to propagate the transitive binary `File`s that depend on -Swift. -""", - fields = { - "binary": """ -Depset of binary `File`s containing the transitive dependency binaries that use -Swift. -""", - "swift_support_files": """ -List of 2-element tuples that represent which files should be bundled as part of the SwiftSupport -archive directory. The first element of the tuple is the platform name, and the second element is a -File object that represents a directory containing the Swift dylibs to package for that platform. -""", - }, -) - # Minimum OS versions for which we no longer need to potentially bundle any # Swift dylibs with the application. The first cutoff point was when the # platforms bundled the standard libraries, the second was when they started @@ -125,11 +111,11 @@ def _swift_dylibs_partial_impl( transitive_binary_sets = [] transitive_swift_support_files = [] for dependency in dependency_targets: - if _AppleSwiftDylibsInfo not in dependency: - # Skip targets without the _AppleSwiftDylibsInfo provider, as they don't use Swift + if AppleSwiftDylibsInfo not in dependency: + # Skip targets without the AppleSwiftDylibsInfo provider, as they don't use Swift # (i.e. sticker extensions that have stubs). continue - provider = dependency[_AppleSwiftDylibsInfo] + provider = dependency[AppleSwiftDylibsInfo] transitive_binary_sets.append(provider.binary) transitive_swift_support_files.extend(provider.swift_support_files) @@ -222,7 +208,7 @@ def _swift_dylibs_partial_impl( return struct( bundle_files = bundle_files, - providers = [_AppleSwiftDylibsInfo( + providers = [AppleSwiftDylibsInfo( binary = propagated_binaries, swift_support_files = transitive_swift_support_files, )], diff --git a/apple/internal/partials/watchos_stub.bzl b/apple/internal/partials/watchos_stub.bzl index 22a36cf111..ee6cb982ec 100644 --- a/apple/internal/partials/watchos_stub.bzl +++ b/apple/internal/partials/watchos_stub.bzl @@ -26,17 +26,9 @@ load( "//apple/internal:processor.bzl", "processor", ) - -_AppleWatchosStubInfo = provider( - doc = """ -Private provider to propagate the watchOS stub that needs to be package in the iOS archive. -""", - fields = { - "binary": """ -File artifact that contains a reference to the stub binary that needs to be packaged in the iOS -archive. -""", - }, +load( + "//apple/internal/providers:apple_watchos_stub_info.bzl", + "AppleWatchosStubInfo", ) def _watchos_stub_partial_impl( @@ -65,13 +57,14 @@ def _watchos_stub_partial_impl( bundle_files.append( (processor.location.bundle, "_WatchKitStub", depset([intermediate_file])), ) - providers.append(_AppleWatchosStubInfo(binary = intermediate_file)) + providers.append(AppleWatchosStubInfo(binary = intermediate_file)) if watch_application: - binary_artifact = watch_application[_AppleWatchosStubInfo].binary + binary_artifact = watch_application[AppleWatchosStubInfo].binary bundle_files.append( (processor.location.archive, "WatchKitSupport2", depset([binary_artifact])), ) + providers.append(AppleWatchosStubInfo(binary = binary_artifact)) return struct( bundle_files = bundle_files, diff --git a/apple/internal/processor.bzl b/apple/internal/processor.bzl index cd785eacb2..6b5fe9df8a 100644 --- a/apple/internal/processor.bzl +++ b/apple/internal/processor.bzl @@ -297,6 +297,7 @@ def _bundle_partial_outputs_files( tree_artifact_is_enabled = is_experimental_tree_artifact_enabled( platform_prerequisites = platform_prerequisites, + rule_descriptor = rule_descriptor, ) location_to_paths = _archive_paths( @@ -508,6 +509,7 @@ def _bundle_post_process_and_sign( """ tree_artifact_is_enabled = is_experimental_tree_artifact_enabled( platform_prerequisites = platform_prerequisites, + rule_descriptor = rule_descriptor, ) archive_paths = _archive_paths( bundle_extension = bundle_extension, diff --git a/apple/internal/providers/BUILD b/apple/internal/providers/BUILD index 3decfb86d9..89b1170411 100644 --- a/apple/internal/providers/BUILD +++ b/apple/internal/providers/BUILD @@ -31,6 +31,38 @@ bzl_library( ], ) +bzl_library( + name = "apple_symbols_file_info", + srcs = ["apple_symbols_file_info.bzl"], + visibility = [ + "//apple/internal:__subpackages__", + ], +) + +bzl_library( + name = "apple_swift_dylibs_info", + srcs = ["apple_swift_dylibs_info.bzl"], + visibility = [ + "//apple/internal:__subpackages__", + ], +) + +bzl_library( + name = "apple_watchos_stub_info", + srcs = ["apple_watchos_stub_info.bzl"], + visibility = [ + "//apple/internal:__subpackages__", + ], +) + +bzl_library( + name = "apple_messages_stub_info", + srcs = ["apple_messages_stub_info.bzl"], + visibility = [ + "//apple/internal:__subpackages__", + ], +) + bzl_library( name = "embeddable_info", srcs = ["embeddable_info.bzl"], diff --git a/apple/internal/providers/apple_messages_stub_info.bzl b/apple/internal/providers/apple_messages_stub_info.bzl new file mode 100644 index 0000000000..ec0d0a3aa1 --- /dev/null +++ b/apple/internal/providers/apple_messages_stub_info.bzl @@ -0,0 +1,31 @@ +# Copyright 2025 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. + +"""AppleMessagesStubInfo implementation.""" + +visibility("//apple/internal/...") + +AppleMessagesStubInfo = provider( + doc = """ +Provider for iMessage application stub binaries that need to be packaged in the archive. +""", + fields = { + "messages_application_support": """ +File for the MessagesApplicationSupport stub binary (for iMessage apps). May be None. +""", + "messages_extension_support": """ +File for the MessagesApplicationExtensionSupport stub binary (from extensions). May be None. +""", + }, +) diff --git a/apple/internal/providers/apple_swift_dylibs_info.bzl b/apple/internal/providers/apple_swift_dylibs_info.bzl new file mode 100644 index 0000000000..4d1bed2d04 --- /dev/null +++ b/apple/internal/providers/apple_swift_dylibs_info.bzl @@ -0,0 +1,35 @@ +# Copyright 2025 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. + +"""AppleSwiftDylibsInfo implementation..""" + +visibility("//apple/internal/...") + +AppleSwiftDylibsInfo = provider( + doc = """ +Internal provider to propagate the transitive binary `File`s that depend on +Swift. +""", + fields = { + "binary": """ +Depset of binary `File`s containing the transitive dependency binaries that use +Swift. +""", + "swift_support_files": """ +List of 2-element tuples that represent which files should be bundled as part of the SwiftSupport +archive directory. The first element of the tuple is the platform name, and the second element is a +File object that represents a directory containing the Swift dylibs to package for that platform. +""", + }, +) diff --git a/apple/internal/providers/apple_symbols_file_info.bzl b/apple/internal/providers/apple_symbols_file_info.bzl new file mode 100644 index 0000000000..177e3d6e9f --- /dev/null +++ b/apple/internal/providers/apple_symbols_file_info.bzl @@ -0,0 +1,28 @@ +# Copyright 2025 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. + +"""AppleSymbolsFileInfo implementation..""" + +visibility("//apple/internal/...") + +AppleSymbolsFileInfo = provider( + doc = """ +Provider containing .symbols files for crash symbolication. +""", + fields = { + "symbols_output_dirs": """ +Depset of Files containing directories of $UUID.symbols files. +""", + }, +) diff --git a/apple/internal/providers/apple_watchos_stub_info.bzl b/apple/internal/providers/apple_watchos_stub_info.bzl new file mode 100644 index 0000000000..f48e557dac --- /dev/null +++ b/apple/internal/providers/apple_watchos_stub_info.bzl @@ -0,0 +1,29 @@ +# Copyright 2025 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. + +"""AppleWatchosStubInfo implementation..""" + +visibility("//apple/internal/...") + +AppleWatchosStubInfo = provider( + doc = """ +Internal provider to propagate the watchOS stub that needs to be package in the iOS archive. +""", + fields = { + "binary": """ +File artifact that contains a reference to the stub binary that needs to be packaged in the iOS +archive. +""", + }, +) diff --git a/doc/BUILD.bazel b/doc/BUILD.bazel index 02c1978bd6..0750dd3c76 100644 --- a/doc/BUILD.bazel +++ b/doc/BUILD.bazel @@ -9,6 +9,7 @@ _PLAIN_DOC_SRCS = [ _RULES_DOC_SRCS = [ "apple", + "apple_archive", "docc", "dtrace", "header_map", diff --git a/doc/rules-apple_archive.md b/doc/rules-apple_archive.md new file mode 100755 index 0000000000..7a0d1c2df6 --- /dev/null +++ b/doc/rules-apple_archive.md @@ -0,0 +1,46 @@ + + +Rules for creating iOS Package Archive (.ipa). + + + +## apple_archive + +
+load("@rules_apple//apple:apple_archive.bzl", "apple_archive")
+
+apple_archive(name, bundle, include_symbols)
+
+ +Re-packages an Apple bundle into a .ipa. + +This rule uses the providers from the bundle target to construct the required +metadata for the .ipa. + +Example: + +````starlark +load("//apple:apple_archive.bzl", "apple_archive") + +ios_application( + name = "App", + bundle_id = "com.example.my.app", + ... +) + +apple_archive( + name = "App.ipa", + bundle = ":App", +) +```` + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| bundle | The label to a target to re-package into a .ipa. For example, an `ios_application` target. | Label | optional | `None` | +| include_symbols | If true, collects `$UUID.symbols` files from all `{binary: .dSYM, ...}` pairs for the application and its dependencies, then packages them under the `Symbols/` directory in the final .ipa. | Boolean | optional | `False` | + + diff --git a/doc/rules-ios.md b/doc/rules-ios.md index 9c2fa3890a..5debf211d7 100644 --- a/doc/rules-ios.md +++ b/doc/rules-ios.md @@ -66,11 +66,11 @@ load("@rules_apple//apple:ios.doc.bzl", "ios_application") ios_application(name, deps, resources, additional_linker_inputs, alternate_icons, app_clips, app_icons, app_intents, bundle_id, bundle_id_suffix, bundle_name, codesign_inputs, codesignopts, entitlements, entitlements_validation, executable_name, - exported_symbols_lists, extensions, families, frameworks, include_symbols_in_bundle, - infoplists, ipa_post_processor, launch_images, launch_storyboard, linkopts, - locales_to_include, minimum_deployment_os_version, minimum_os_version, platform_type, - primary_app_icon, provisioning_profile, sdk_frameworks, settings_bundle, - shared_capabilities, stamp, strings, version, watch_application) + exported_symbols_lists, extensions, families, frameworks, infoplists, + ipa_post_processor, launch_images, launch_storyboard, linkopts, locales_to_include, + minimum_deployment_os_version, minimum_os_version, platform_type, primary_app_icon, + provisioning_profile, sdk_frameworks, settings_bundle, shared_capabilities, stamp, + strings, version, watch_application) Builds and bundles an iOS Application. @@ -100,7 +100,6 @@ Builds and bundles an iOS Application. | extensions | A list of iOS application extensions to include in the final application bundle. | List of labels | optional | `[]` | | families | A list of device families supported by this rule. 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/main/doc/rules-ios.md#ios_framework)) that this target depends on. | List of labels | optional | `[]` | -| include_symbols_in_bundle | If true and --output_groups=+dsyms is specified, generates `$UUID.symbols` files from all `{binary: .dSYM, ...}` pairs for the application and its dependencies, then packages them under the `Symbols/` directory in the final application bundle. | 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/main/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` | | launch_images | Files that comprise the launch images for the application. Each file must have a containing directory named `*.xcassets/*.launchimage` and there may be only one such `.launchimage` directory in the list. | List of labels | optional | `[]` | diff --git a/test/ios_application_resources_test.sh b/test/ios_application_resources_test.sh index 89194c7978..35cfcafd39 100755 --- a/test/ios_application_resources_test.sh +++ b/test/ios_application_resources_test.sh @@ -29,6 +29,7 @@ function tear_down() { # Creates common source, targets, and basic plist for iOS applications. function create_common_files() { cat > app/BUILD < app/app_res/foo.txt echo shared_res > app/shared_res/foo.txt - do_build ios //app:app && fail "Should fail" + do_build ios //app:app_ipa && fail "Should fail" expect_log "Multiple files would be placed at \".*foo.txt\" in the bundle, which is not allowed" @@ -219,6 +240,7 @@ function test_texture_atlas_bundled_with_app() { create_common_files cat > app/BUILD < app/BUILD < app/BUILD < app/BUILD < app/BUILD < app/BUILD < app/BUILD < app/BUILD < app/BUILD < app/BUILD < app/AppDelegate.swift < app/BUILD < app/bogus.mobileprovision < app/BUILD < app/Foo.h < app/BUILD < app/main.m < app/BUILD < app/main.m < app/BUILD < app/main.m < app/BUILD < app/main.m < app/BUILD < app/main.m <