Vendor dependencies

Let's see how I like this workflow.
This commit is contained in:
John Doty 2022-12-19 08:27:18 -08:00
parent 34d1830413
commit 9c435dc440
7500 changed files with 1665121 additions and 99 deletions

View file

@ -0,0 +1,108 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load("@prelude//utils:utils.bzl", "flatten")
load(":apple_asset_catalog_compilation_options.bzl", "AppleAssetCatalogsCompilationOptions", "get_apple_asset_catalogs_compilation_options") # @unused Used as a type
load(":apple_asset_catalog_types.bzl", "AppleAssetCatalogResult", "AppleAssetCatalogSpec", "StringWithSourceTarget")
load(":apple_bundle_utility.bzl", "get_bundle_min_target_version", "get_bundle_resource_processing_options")
load(":apple_sdk.bzl", "get_apple_sdk_name")
load(":apple_sdk_metadata.bzl", "get_apple_sdk_metadata_for_sdk_name")
load(":resource_groups.bzl", "create_resource_graph")
def apple_asset_catalog_impl(ctx: "context") -> ["provider"]:
spec = AppleAssetCatalogSpec(
app_icon = StringWithSourceTarget(source = ctx.label, value = ctx.attrs.app_icon) if ctx.attrs.app_icon != None else None,
dirs = ctx.attrs.dirs,
launch_image = StringWithSourceTarget(source = ctx.label, value = ctx.attrs.launch_image) if ctx.attrs.launch_image != None else None,
)
graph = create_resource_graph(
ctx = ctx,
labels = ctx.attrs.labels,
deps = [],
exported_deps = [],
asset_catalog_spec = spec,
)
return [DefaultInfo(default_outputs = []), graph]
def compile_apple_asset_catalog(ctx: "context", specs: [AppleAssetCatalogSpec.type]) -> [AppleAssetCatalogResult.type, None]:
single_spec = _merge_asset_catalog_specs(ctx, specs)
if len(single_spec.dirs) == 0:
return None
plist = ctx.actions.declare_output("AssetCatalog.plist")
catalog = ctx.actions.declare_output("AssetCatalogCompiled")
processing_options = get_bundle_resource_processing_options(ctx)
compilation_options = get_apple_asset_catalogs_compilation_options(ctx)
command = _get_actool_command(ctx, single_spec, catalog.as_output(), plist.as_output(), compilation_options)
ctx.actions.run(command, prefer_local = processing_options.prefer_local, allow_cache_upload = processing_options.allow_cache_upload, category = "apple_asset_catalog")
return AppleAssetCatalogResult(compiled_catalog = catalog, catalog_plist = plist)
def _merge_asset_catalog_specs(ctx: "context", xs: [AppleAssetCatalogSpec.type]) -> AppleAssetCatalogSpec.type:
app_icon = _get_at_most_one_attribute(ctx, xs, "app_icon")
launch_image = _get_at_most_one_attribute(ctx, xs, "launch_image")
dirs = dedupe(flatten([x.dirs for x in xs]))
return AppleAssetCatalogSpec(app_icon = app_icon, dirs = dirs, launch_image = launch_image)
def _get_at_most_one_attribute(ctx: "context", xs: ["_record"], attr_name: str.type) -> ["StringWithSourceTarget", None]:
all_values = dedupe(filter(None, [getattr(x, attr_name) for x in xs]))
if len(all_values) > 1:
fail("At most one asset catalog in the dependencies of `{}` can have an `{}` attribute. At least 2 catalogs are providing it: `{}` and `{}`.".format(_get_target(ctx), attr_name, all_values[0].source, all_values[1].source))
elif len(all_values) == 1:
return all_values[0]
else:
return None
def _get_target(ctx: "context") -> str.type:
return ctx.label.package + ":" + ctx.label.name
def _get_actool_command(ctx: "context", info: AppleAssetCatalogSpec.type, catalog_output: "output_artifact", plist_output: "output_artifact", compilation_options: AppleAssetCatalogsCompilationOptions.type) -> "cmd_args":
external_name = get_apple_sdk_name(ctx)
target_device = get_apple_sdk_metadata_for_sdk_name(external_name).target_device_flags
actool = ctx.attrs._apple_toolchain[AppleToolchainInfo].actool
actool_command = cmd_args([
actool,
"--platform",
external_name,
"--minimum-deployment-target",
get_bundle_min_target_version(ctx),
"--compile",
catalog_output,
"--output-partial-info-plist",
plist_output,
] +
target_device +
(
["--app-icon", info.app_icon.value] if info.app_icon else []
) + (
["--launch-image", info.launch_image.value] if info.launch_image else []
) + (
["--notices"] if compilation_options.enable_notices else []
) + (
["--warnings"] if compilation_options.enable_warnings else []
) + (
["--errors"] if compilation_options.enable_errors else []
) + (
["--compress-pngs"] if compilation_options.compress_pngs else []
) +
["--optimization", compilation_options.optimization] +
["--output-format", compilation_options.output_format] +
compilation_options.extra_flags +
info.dirs)
# `actool` expects the output directory to be present.
# Use the wrapper script to create the directory first and then actually call `actool`.
wrapper_script, _ = ctx.actions.write(
"actool_wrapper.sh",
[
cmd_args(catalog_output, format = "mkdir -p {}"),
cmd_args(actool_command, delimiter = " "),
],
allow_args = True,
)
command = cmd_args(["/bin/sh", wrapper_script]).hidden([actool_command])
return command

View file

@ -0,0 +1,29 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
AppleAssetCatalogsCompilationOptions = record(
enable_notices = field(bool.type),
enable_warnings = field(bool.type),
enable_errors = field(bool.type),
compress_pngs = field(bool.type),
optimization = field(str.type),
output_format = field(str.type),
extra_flags = field([str.type]),
)
def get_apple_asset_catalogs_compilation_options(ctx: "context") -> AppleAssetCatalogsCompilationOptions.type:
options = ctx.attrs.asset_catalogs_compilation_options
return AppleAssetCatalogsCompilationOptions(
enable_notices = options.get("notices", True),
enable_warnings = options.get("warnings", True),
enable_errors = options.get("errors", True),
compress_pngs = options.get("compress_pngs", True),
optimization = options.get("optimization", "space"),
output_format = options.get("output_format", "human-readable-text"),
extra_flags = options.get("extra_flags", []),
)

View file

@ -0,0 +1,30 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
StringWithSourceTarget = record(
# Target providing the string value
source = field("label"),
value = field("string"),
)
AppleAssetCatalogSpec = record(
# At most one per given `apple_bundle` (including all transitive catalog dependencies),
# optional reference in a form of a name (extension omitted) of an .appiconset which
# contains an image set representing an application icon.
# This set should be contained in one of catalogs referenced by `dirs` attribute.
app_icon = field([StringWithSourceTarget.type, None]),
dirs = field(["artifact"]),
# Same as `app_icon` but with an application launch image semantics.
launch_image = field([StringWithSourceTarget.type, None]),
)
AppleAssetCatalogResult = record(
# Directory which contains compiled assets ready to be copied into application bundle
compiled_catalog = field("artifact"),
# .plist file to be merged into main application Info.plist file, containing information about compiled assets
catalog_plist = field("artifact"),
)

View file

@ -0,0 +1,88 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_stripping.bzl", "apple_strip_args")
load("@prelude//cxx:cxx.bzl", "get_srcs_with_flags")
load("@prelude//cxx:cxx_executable.bzl", "cxx_executable")
load("@prelude//cxx:cxx_library_utility.bzl", "cxx_attr_deps", "cxx_attr_exported_deps")
load("@prelude//cxx:cxx_types.bzl", "CxxRuleConstructorParams")
load(
"@prelude//cxx:link_groups.bzl",
"get_link_group_info",
)
load(
"@prelude//cxx:preprocessor.bzl",
"CPreprocessor",
)
load(":apple_bundle_types.bzl", "AppleMinDeploymentVersionInfo")
load(":apple_code_signing_types.bzl", "AppleEntitlementsInfo")
load(":apple_dsym.bzl", "AppleDebuggableInfo", "DEBUGINFO_SUBTARGET", "DSYM_SUBTARGET", "get_apple_dsym")
load(":apple_frameworks.bzl", "get_framework_search_path_flags")
load(":apple_link_postprocessor.bzl", "get_apple_link_postprocessor")
load(":apple_target_sdk_version.bzl", "get_min_deployment_version_for_node", "get_min_deployment_version_target_linker_flags", "get_min_deployment_version_target_preprocessor_flags")
load(":apple_utility.bzl", "get_apple_cxx_headers_layout")
load(":resource_groups.bzl", "create_resource_graph")
load(":xcode.bzl", "apple_populate_xcode_attributes")
def apple_binary_impl(ctx: "context") -> ["provider"]:
extra_link_flags = get_min_deployment_version_target_linker_flags(ctx) + _entitlements_link_flags(ctx)
framework_search_path_pre = CPreprocessor(
args = [get_framework_search_path_flags(ctx)],
)
constructor_params = CxxRuleConstructorParams(
rule_type = "apple_binary",
headers_layout = get_apple_cxx_headers_layout(ctx),
extra_exported_link_flags = extra_link_flags,
srcs = get_srcs_with_flags(ctx),
extra_preprocessors = get_min_deployment_version_target_preprocessor_flags(ctx) + [framework_search_path_pre],
strip_executable = ctx.attrs.stripped,
strip_args_factory = apple_strip_args,
cxx_populate_xcode_attributes_func = apple_populate_xcode_attributes,
link_postprocessor = get_apple_link_postprocessor(ctx),
link_group_info = get_link_group_info(ctx),
)
(cxx_output, _comp_db_info, xcode_data_info) = cxx_executable(ctx, constructor_params)
dsym_artifact = get_apple_dsym(
ctx = ctx,
executable = cxx_output.binary,
external_debug_info = cxx_output.external_debug_info,
action_identifier = cxx_output.binary.short_path,
)
cxx_output.sub_targets[DSYM_SUBTARGET] = [DefaultInfo(default_outputs = [dsym_artifact])]
cxx_output.sub_targets[DEBUGINFO_SUBTARGET] = [DefaultInfo(other_outputs = cxx_output.external_debug_info)]
min_version = get_min_deployment_version_for_node(ctx)
min_version_providers = [AppleMinDeploymentVersionInfo(version = min_version)] if min_version != None else []
resource_graph = create_resource_graph(
ctx = ctx,
labels = ctx.attrs.labels,
deps = cxx_attr_deps(ctx),
exported_deps = cxx_attr_exported_deps(ctx),
)
return [
DefaultInfo(default_outputs = [cxx_output.binary], sub_targets = cxx_output.sub_targets),
RunInfo(args = cmd_args(cxx_output.binary).hidden(cxx_output.runtime_files)),
AppleEntitlementsInfo(entitlements_file = ctx.attrs.entitlements_file),
AppleDebuggableInfo(dsyms = [dsym_artifact], external_debug_info = cxx_output.external_debug_info),
xcode_data_info,
] + [resource_graph] + min_version_providers
def _entitlements_link_flags(ctx: "context") -> [""]:
return [
"-Xlinker",
"-sectcreate",
"-Xlinker",
"__TEXT",
"-Xlinker",
"__entitlements",
"-Xlinker",
ctx.attrs.entitlements_file,
] if ctx.attrs.entitlements_file else []

View file

@ -0,0 +1,181 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load(
"@prelude//ide_integrations:xcode.bzl",
"XCODE_DATA_SUB_TARGET",
"generate_xcode_data",
)
load("@prelude//utils:utils.bzl", "expect", "flatten")
load(":apple_bundle_destination.bzl", "AppleBundleDestination")
load(":apple_bundle_part.bzl", "AppleBundlePart", "assemble_bundle", "bundle_output")
load(":apple_bundle_resources.bzl", "get_apple_bundle_resource_part_list", "get_is_watch_bundle")
load(":apple_bundle_types.bzl", "AppleBundleInfo", "AppleBundleResourceInfo")
load(":apple_bundle_utility.bzl", "get_bundle_min_target_version", "get_product_name")
load(":apple_dsym.bzl", "AppleDebuggableInfo", "DEBUGINFO_SUBTARGET", "DSYM_SUBTARGET")
load(":apple_sdk.bzl", "get_apple_sdk_name")
load(":xcode.bzl", "apple_xcode_data_add_xctoolchain")
INSTALL_DATA_SUB_TARGET = "install-data"
_INSTALL_DATA_FILE_NAME = "install_apple_data.json"
_PLIST = "plist"
_XCTOOLCHAIN_SUB_TARGET = "xctoolchain"
AppleBundlePartListConstructorParams = record(
# The binaries/executables, required to create a bundle
binaries = field([AppleBundlePart.type]),
)
AppleBundlePartListOutput = record(
# The parts to be copied into an Apple bundle, *including* binaries
parts = field([AppleBundlePart.type]),
# Part that holds the info.plist
info_plist_part = field(AppleBundlePart.type),
)
AppleBundleBinaryOutput = record(
binary = field("artifact"),
# In the case of watchkit, the `ctx.attrs.binary`'s not set, and we need to create a stub binary.
is_watchkit_stub_binary = field(bool.type, False),
)
def _get_binary(ctx: "context") -> AppleBundleBinaryOutput.type:
# No binary means we are building watchOS bundle. In v1 bundle binary is present, but its sources are empty.
if ctx.attrs.binary == None:
return AppleBundleBinaryOutput(
binary = _get_watch_kit_stub_artifact(ctx),
is_watchkit_stub_binary = True,
)
binary_info = ctx.attrs.binary[DefaultInfo].default_outputs
if len(binary_info) != 1:
fail("Expected single output artifact. Make sure the implementation of rule from `binary` attribute is correct.")
return AppleBundleBinaryOutput(binary = binary_info[0])
def _get_binary_bundle_parts(ctx: "context", binary_output: AppleBundleBinaryOutput.type) -> [AppleBundlePart.type]:
result = []
if binary_output.is_watchkit_stub_binary:
# If we're using a stub binary from watchkit, we also need to add extra part for stub.
result.append(AppleBundlePart(source = binary_output.binary, destination = AppleBundleDestination("watchkitstub"), new_name = "WK"))
result.append(AppleBundlePart(source = binary_output.binary, destination = AppleBundleDestination("executables"), new_name = get_product_name(ctx)))
return result
def _get_watch_kit_stub_artifact(ctx: "context") -> "artifact":
expect(ctx.attrs.binary == None, "Stub is useful only when binary is not set which means watchOS bundle is built.")
stub_binary = ctx.attrs._apple_toolchain[AppleToolchainInfo].watch_kit_stub_binary
if stub_binary == None:
fail("Expected Watch Kit stub binary to be provided when bundle binary is not set.")
return stub_binary
def _apple_bundle_run_validity_checks(ctx: "context"):
if ctx.attrs.extension == None:
fail("`extension` attribute is required")
def _get_debuggable_deps(ctx: "context") -> ["AppleDebuggableInfo"]:
deps = ctx.attrs.deps
# No binary means we are building watchOS bundle. In v1 bundle binary is present, but its sources are empty.
if ctx.attrs.binary:
deps.append(ctx.attrs.binary)
return filter(
None,
[dep.get(AppleDebuggableInfo) for dep in deps],
)
def get_apple_bundle_part_list(ctx: "context", params: AppleBundlePartListConstructorParams.type) -> AppleBundlePartListOutput.type:
resource_part_list = None
if hasattr(ctx.attrs, "_resource_bundle") and ctx.attrs._resource_bundle != None:
resource_info = ctx.attrs._resource_bundle[AppleBundleResourceInfo]
if resource_info != None:
resource_part_list = resource_info.resource_output
if resource_part_list == None:
resource_part_list = get_apple_bundle_resource_part_list(ctx)
return AppleBundlePartListOutput(
parts = resource_part_list.resource_parts + params.binaries,
info_plist_part = resource_part_list.info_plist_part,
)
def apple_bundle_impl(ctx: "context") -> ["provider"]:
_apple_bundle_run_validity_checks(ctx)
binary_outputs = _get_binary(ctx)
apple_bundle_part_list_output = get_apple_bundle_part_list(ctx, AppleBundlePartListConstructorParams(binaries = _get_binary_bundle_parts(ctx, binary_outputs)))
sub_targets = {}
debuggable_deps = _get_debuggable_deps(ctx)
dsym_artifacts = flatten([info.dsyms for info in debuggable_deps])
if dsym_artifacts:
sub_targets[DSYM_SUBTARGET] = [DefaultInfo(default_outputs = dsym_artifacts)]
external_debug_info = flatten([info.external_debug_info for info in debuggable_deps])
sub_targets[DEBUGINFO_SUBTARGET] = [DefaultInfo(other_outputs = external_debug_info)]
bundle = bundle_output(ctx)
assemble_bundle(ctx, bundle, apple_bundle_part_list_output.parts, apple_bundle_part_list_output.info_plist_part)
sub_targets[_PLIST] = [DefaultInfo(default_outputs = [apple_bundle_part_list_output.info_plist_part.source])]
sub_targets[_XCTOOLCHAIN_SUB_TARGET] = ctx.attrs._apple_xctoolchain.providers
# Define the xcode data sub target
xcode_data_default_info, xcode_data_info = generate_xcode_data(ctx, "apple_bundle", bundle, _xcode_populate_attributes, processed_info_plist = apple_bundle_part_list_output.info_plist_part.source)
sub_targets[XCODE_DATA_SUB_TARGET] = xcode_data_default_info
install_data = generate_install_data(ctx)
return [
DefaultInfo(default_outputs = [bundle], sub_targets = sub_targets),
AppleBundleInfo(bundle = bundle, binary_name = get_product_name(ctx), is_watchos = get_is_watch_bundle(ctx)),
AppleDebuggableInfo(dsyms = dsym_artifacts, external_debug_info = external_debug_info),
InstallInfo(
installer = ctx.attrs._apple_installer,
files = {
"app_bundle": bundle,
"options": install_data,
},
),
xcode_data_info,
]
def _xcode_populate_attributes(ctx, processed_info_plist: "artifact") -> {str.type: ""}:
data = {
"deployment_version": get_bundle_min_target_version(ctx),
"info_plist": ctx.attrs.info_plist,
"processed_info_plist": processed_info_plist,
"product_name": get_product_name(ctx),
"sdk": get_apple_sdk_name(ctx),
}
apple_xcode_data_add_xctoolchain(ctx, data)
return data
def generate_install_data(
ctx: "context",
populate_rule_specific_attributes_func: ["function", None] = None,
**kwargs) -> "artifact":
data = {
"fullyQualifiedName": ctx.label,
## TODO(T110665037): populate full path similar to bundle_spec.json
"info_plist": ctx.attrs.info_plist,
"use_idb": "true",
## TODO(T110665037): read from .buckconfig
"xcode_developer_path": "/Applications/Xcode_13.4.0_fb.app/Contents/Developer",
}
if populate_rule_specific_attributes_func:
data.update(populate_rule_specific_attributes_func(ctx, **kwargs))
return ctx.actions.write_json(_INSTALL_DATA_FILE_NAME, data)

View file

@ -0,0 +1,20 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
def _maybe_get_bool(config: str.type, default: [None, bool.type]) -> [None, bool.type]:
result = read_config("apple", config, None)
if result == None:
return default
return result.lower() == "true"
def apple_bundle_config() -> {str.type: ""}:
return {
"_codesign_type": read_config("apple", "codesign_type_override", None),
"_compile_resources_locally_override": _maybe_get_bool("compile_resources_locally_override", None),
"_incremental_bundling_enabled": _maybe_get_bool("incremental_bundling_enabled", True),
"_profile_bundling_enabled": _maybe_get_bool("profile_bundling_enabled", False),
}

View file

@ -0,0 +1,132 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
# Abstraction of a place in a resulting bundle where file or directory will be copied. Actual value
# of path relative to bundle root depends on a platform. This class is an implementation detail and
# is not exposed to user unlike `AppleResourceDestination`.
# v1 code is `com/facebook/buck/apple/AppleBundleDestination.java`
AppleBundleDestination = enum(
"resources",
"frameworks",
"executables",
"plugins",
"xpcservices",
"metadata",
"watchapp",
"headers",
"modules",
"quicklook",
"watchkitstub",
"bundleroot",
"loginitems",
)
AppleBundleDestinationPaths = record(
resources = field(str.type, ""),
frameworks = field(str.type, ""),
executables = field(str.type, ""),
plugins = field(str.type, ""),
xpcservices = field(str.type, ""),
metadata = field(str.type, ""),
watchapp = field(str.type, ""),
headers = field(str.type, ""),
modules = field(str.type, ""),
quicklook = field(str.type, ""),
watchkitstub = field(str.type, ""),
bundleroot = field(str.type, ""),
loginitems = field(str.type, ""),
)
_IOSBundleDestinationPaths = AppleBundleDestinationPaths(
frameworks = "Frameworks",
plugins = "PlugIns",
xpcservices = "XPCServices",
watchapp = "Watch",
quicklook = "Library/QuickLook",
watchkitstub = "_WatchKitStub",
)
_IOSFrameworkBundleDestinationPaths = AppleBundleDestinationPaths(
frameworks = "Frameworks",
xpcservices = "XPCServices",
headers = "Headers",
modules = "Modules",
)
macOS_content_path = "Contents"
_MacOSBundleDestinationPaths = AppleBundleDestinationPaths(
resources = paths.join(macOS_content_path, "Resources"),
frameworks = paths.join(macOS_content_path, "Frameworks"),
executables = paths.join(macOS_content_path, "MacOS"),
plugins = paths.join(macOS_content_path, "PlugIns"),
xpcservices = paths.join(macOS_content_path, "XPCServices"),
metadata = macOS_content_path,
watchapp = macOS_content_path,
headers = macOS_content_path,
modules = macOS_content_path,
quicklook = paths.join(macOS_content_path, "Library/QuickLook"),
watchkitstub = macOS_content_path,
bundleroot = macOS_content_path,
loginitems = paths.join(macOS_content_path, "Library/LoginItems"),
)
_MacOSFrameworkBundleDestinationPaths = AppleBundleDestinationPaths(
resources = "Resources",
frameworks = "Frameworks",
xpcservices = "XPCServices",
metadata = "Resources",
headers = "Headers",
modules = "Modules",
)
def _get_apple_bundle_destinations_for_sdk_name(name: str.type) -> AppleBundleDestinationPaths.type:
if name == "macosx" or name == "maccatalyst":
return _MacOSBundleDestinationPaths
else:
return _IOSBundleDestinationPaths
def _get_apple_framework_bundle_destinations_for_sdk_name(name: str.type) -> AppleBundleDestinationPaths.type:
if name == "macosx" or name == "maccatalyst":
return _MacOSFrameworkBundleDestinationPaths
else:
return _IOSFrameworkBundleDestinationPaths
def bundle_relative_path_for_destination(destination: AppleBundleDestination.type, sdk_name: str.type, extension: str.type) -> str.type:
if extension == "framework":
bundle_destinations = _get_apple_framework_bundle_destinations_for_sdk_name(sdk_name)
else:
bundle_destinations = _get_apple_bundle_destinations_for_sdk_name(sdk_name)
if destination.value == "resources":
return bundle_destinations.resources
elif destination.value == "frameworks":
return bundle_destinations.frameworks
elif destination.value == "executables":
return bundle_destinations.executables
elif destination.value == "plugins":
return bundle_destinations.plugins
elif destination.value == "xpcservices":
return bundle_destinations.xpcservices
elif destination.value == "metadata":
return bundle_destinations.metadata
elif destination.value == "watchapp":
return bundle_destinations.watchapp
elif destination.value == "headers":
return bundle_destinations.headers
elif destination.value == "modules":
return bundle_destinations.modules
elif destination.value == "quicklook":
return bundle_destinations.quicklook
elif destination.value == "watchkitstub":
return bundle_destinations.watchkitstub
elif destination.value == "bundleroot":
return bundle_destinations.bundleroot
elif destination.value == "loginitems":
return bundle_destinations.loginitems
fail("Unsupported Apple bundle destination {}".format(destination))

View file

@ -0,0 +1,90 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_bundle_config.bzl", "apple_bundle_config")
load(":apple_info_plist_substitutions_parsing.bzl", "parse_codesign_entitlements")
_RESOURCE_BUNDLE_FIELDS = [
"asset_catalogs_compilation_options",
"binary",
"default_target_platform",
"deps",
"extension",
"ibtool_flags",
"ibtool_module_flag",
"info_plist",
"info_plist_substitutions",
"product_name",
"resource_group",
"resource_group_map",
]
def apple_bundle_macro_impl(apple_bundle_rule = None, apple_resource_bundle_rule = None, **kwargs):
info_plist_substitutions = kwargs.get("info_plist_substitutions")
kwargs.update(apple_bundle_config())
resource_bundle_target_name = None
# The `apple_resource_bundle()` target will _always_ be Xcode-based, so resources can always be used
# from there. `resources_toolchain_enabled` exists only as a killswitch (or for testing/debugging purposes).
# By default, we consistently get all resources from `apple_resource_bundle()` target across all OSes and
# toolchains.
resources_toolchain_enabled = (read_config("apple", "resources_toolchain_enabled", "true").lower() == "true")
if resources_toolchain_enabled:
resource_bundle_name = kwargs["name"] + "__ResourceBundle_Private"
resource_bundle_kwargs = {
"_bundle_target_name": kwargs["name"],
"_compile_resources_locally_override": kwargs["_compile_resources_locally_override"],
}
for field_name in _RESOURCE_BUNDLE_FIELDS:
resource_bundle_kwargs[field_name] = kwargs.get(field_name)
# TODO(T125269558): Remove usage of apple_resource_bundle() once we have exec groups.
apple_resource_bundle_rule(
name = resource_bundle_name,
**resource_bundle_kwargs
)
resource_bundle_target_name = ":{}".format(resource_bundle_name)
# Splitting the resource compilation into another rule means we can have
# different exec platforms for the resource compilation and for the rest
# of the bundling process. This allows us to send resource compilations
# directly to RE.
#
# +-------------------------------------------------+
# | apple_bundle() |
# | Exec Platform: macOS/Linux |
# | +--------+ +--------+ +------------------+ |
# +---+ binary +--+ deps +-+ _resource_bundle +---+
# +--------+ +--------+ +------------------+
# | | |
# | | |
# | | +---------------+
# | | |
# | | |
# | | v
# | | +---------------------------------+
# | +-----+ | apple_resource_bundle() |
# | | | Exec Platform: macOS-only |
# | | | +--------+ +--------+ |
# | | +-----+ binary +--+ deps +------+
# | | +--------+ +--------+
# | | | |
# | | | |
# | v | |
# | +-------------------+ | |
# | | Dependencies |<--------+-----------+
# | +-------------------+ |
# | +-------------------+ |
# +------>| Binary |<--------+
# +-------------------+
apple_bundle_rule(
_codesign_entitlements = parse_codesign_entitlements(info_plist_substitutions),
_resource_bundle = resource_bundle_target_name,
**kwargs
)

View file

@ -0,0 +1,171 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
load(":apple_bundle_destination.bzl", "AppleBundleDestination", "bundle_relative_path_for_destination")
load(":apple_bundle_utility.bzl", "get_extension_attr", "get_product_name")
load(":apple_code_signing_types.bzl", "AppleEntitlementsInfo", "CodeSignType")
load(":apple_sdk.bzl", "get_apple_sdk_name")
load(":apple_sdk_metadata.bzl", "get_apple_sdk_metadata_for_sdk_name")
load(":apple_toolchain_types.bzl", "AppleToolchainInfo", "AppleToolsInfo")
# Defines where and what should be copied into
AppleBundlePart = record(
# A file of directory which content should be copied
source = field("artifact"),
# Where the source should be copied, the actual destination directory
# inside bundle depends on target platform
destination = AppleBundleDestination.type,
# New file name if it should be renamed before copying.
# Empty string value is applicable only when `source` is a directory,
# in such case only content of the directory will be copied, as opposed to the directory itself.
# When value is `None`, directory or file will be copied as it is, without renaming.
new_name = field([str.type, None], None),
# Marks parts which should be code signed separately from the whole bundle.
codesign_on_copy = field(bool.type, False),
)
def bundle_output(ctx: "context") -> "artifact":
bundle_dir_name = _get_bundle_dir_name(ctx)
output = ctx.actions.declare_output(bundle_dir_name)
return output
def assemble_bundle(ctx: "context", bundle: "artifact", parts: [AppleBundlePart.type], info_plist_part: [AppleBundlePart.type, None]) -> None:
all_parts = parts + [info_plist_part] if info_plist_part else []
spec_file = _bundle_spec_json(ctx, all_parts)
tools = ctx.attrs._apple_tools[AppleToolsInfo]
tool = tools.assemble_bundle
codesign_args = []
codesign_type = _detect_codesign_type(ctx)
if codesign_type.value in ["distribution", "adhoc"]:
codesign_args = [
"--codesign",
"--codesign-tool",
ctx.attrs._apple_toolchain[AppleToolchainInfo].codesign,
]
external_name = get_apple_sdk_name(ctx)
platform_args = ["--platform", external_name]
codesign_args.extend(platform_args)
if codesign_type.value != "adhoc":
provisioning_profiles = ctx.attrs._provisioning_profiles[DefaultInfo]
provisioning_profiles_args = ["--profiles-dir"] + provisioning_profiles.default_outputs
codesign_args.extend(provisioning_profiles_args)
# TODO(T116604880): use `apple.use_entitlements_when_adhoc_code_signing` buckconfig value
maybe_entitlements = _entitlements_file(ctx)
entitlements_args = ["--entitlements", maybe_entitlements] if maybe_entitlements else []
codesign_args.extend(entitlements_args)
identities_command = ctx.attrs._apple_toolchain[AppleToolchainInfo].codesign_identities_command
identities_command_args = ["--codesign-identities-command", cmd_args(identities_command)] if identities_command else []
codesign_args.extend(identities_command_args)
else:
codesign_args.append("--ad-hoc")
info_plist_args = [
"--info-plist-source",
info_plist_part.source,
"--info-plist-destination",
_bundle_relative_destination_path(ctx, info_plist_part),
] if info_plist_part else []
codesign_args.extend(info_plist_args)
elif codesign_type.value == "skip":
pass
else:
fail("Code sign type `{}` not supported".format(codesign_type))
command = cmd_args([
tool,
"--output",
bundle.as_output(),
"--spec",
spec_file,
] + codesign_args)
command.hidden([part.source for part in all_parts])
run_incremental_args = {}
incremental_state = ctx.actions.declare_output("incremental_state.json").as_output()
# Fallback to value from buckconfig
incremental_bundling_enabled = ctx.attrs.incremental_bundling_enabled or ctx.attrs._incremental_bundling_enabled
if incremental_bundling_enabled:
command.add("--incremental-state", incremental_state)
run_incremental_args = {
"metadata_env_var": "ACTION_METADATA",
"metadata_path": "action_metadata.json",
"no_outputs_cleanup": True,
}
category = "apple_assemble_bundle_incremental"
else:
# overwrite file with incremental state so if previous and next builds are incremental
# (as opposed to the current non-incremental one), next one won't assume there is a
# valid incremental state.
command.hidden(ctx.actions.write_json(incremental_state, {}))
category = "apple_assemble_bundle"
if ctx.attrs._profile_bundling_enabled:
profile_output = ctx.actions.declare_output("bundling_profile.txt").as_output()
command.add("--profile-output", profile_output)
force_local_bundling = codesign_type.value != "skip"
ctx.actions.run(
command,
local_only = force_local_bundling,
prefer_local = not force_local_bundling,
category = category,
**run_incremental_args
)
def _get_bundle_dir_name(ctx: "context") -> str.type:
return paths.replace_extension(get_product_name(ctx), "." + get_extension_attr(ctx))
def _bundle_relative_destination_path(ctx: "context", part: AppleBundlePart.type) -> str.type:
bundle_relative_path = bundle_relative_path_for_destination(part.destination, get_apple_sdk_name(ctx), ctx.attrs.extension)
destination_file_or_directory_name = part.new_name if part.new_name != None else paths.basename(part.source.short_path)
return paths.join(bundle_relative_path, destination_file_or_directory_name)
# Returns JSON to be passed into bundle assembling tool. It should contain a dictionary which maps bundle relative destination paths to source paths."
def _bundle_spec_json(ctx: "context", parts: [AppleBundlePart.type]) -> "artifact":
specs = []
for part in parts:
part_spec = {
"dst": _bundle_relative_destination_path(ctx, part),
"src": part.source,
}
if part.codesign_on_copy:
part_spec["codesign_on_copy"] = True
specs.append(part_spec)
return ctx.actions.write_json("bundle_spec.json", specs)
def _detect_codesign_type(ctx: "context") -> CodeSignType.type:
if ctx.attrs.extension not in ["app", "appex"]:
# Only code sign application bundles and extensions
return CodeSignType("skip")
if ctx.attrs._codesign_type:
return CodeSignType(ctx.attrs._codesign_type)
sdk_name = get_apple_sdk_name(ctx)
is_ad_hoc_sufficient = get_apple_sdk_metadata_for_sdk_name(sdk_name).is_ad_hoc_code_sign_sufficient
return CodeSignType("adhoc" if is_ad_hoc_sufficient else "distribution")
def _entitlements_file(ctx: "context") -> ["artifact", None]:
if not ctx.attrs.binary:
return None
# The `binary` attribute can be either an apple_binary or a dynamic library from apple_library
binary_entitlement_info = ctx.attrs.binary[AppleEntitlementsInfo]
if binary_entitlement_info and binary_entitlement_info.entitlements_file:
return binary_entitlement_info.entitlements_file
return ctx.attrs._codesign_entitlements

View file

@ -0,0 +1,344 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
load("@prelude//:resources.bzl", "gather_resources")
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load("@prelude//utils:utils.bzl", "expect", "flatten_dict")
load(
":apple_asset_catalog.bzl",
"compile_apple_asset_catalog",
)
load(
":apple_asset_catalog_types.bzl",
"AppleAssetCatalogSpec", # @unused Used as a type
)
load(":apple_bundle_destination.bzl", "AppleBundleDestination")
load(":apple_bundle_part.bzl", "AppleBundlePart")
load(":apple_bundle_types.bzl", "AppleBundleInfo")
load(":apple_bundle_utility.bzl", "get_bundle_resource_processing_options", "get_extension_attr", "get_product_name")
load(":apple_core_data.bzl", "compile_apple_core_data")
load(
":apple_core_data_types.bzl",
"AppleCoreDataSpec", # @unused Used as a type
)
load(":apple_info_plist.bzl", "process_info_plist", "process_plist")
load(
":apple_resource_types.bzl",
"AppleResourceDestination",
"AppleResourceSpec", # @unused Used as a type
)
load(":apple_resource_utility.bzl", "apple_bundle_destination_from_resource_destination")
load(
":resource_groups.bzl",
"create_resource_graph",
"get_filtered_resources",
"get_resource_graph_node_map_func",
"get_resource_group_info",
)
AppleBundleResourcePartListOutput = record(
# Resource parts to be copied into an Apple bundle, *excluding* binaries
resource_parts = field([AppleBundlePart.type]),
# Part that holds the info.plist
info_plist_part = field(AppleBundlePart.type),
)
def get_apple_bundle_resource_part_list(ctx: "context") -> AppleBundleResourcePartListOutput.type:
parts = []
parts.extend(_create_pkg_info_if_needed(ctx))
(resource_specs, asset_catalog_specs, core_data_specs) = _select_resources(ctx)
# If we've pulled in native/C++ resources from deps, inline them into the
# bundle under the `CxxResources` namespace.
cxx_resources = flatten_dict(gather_resources(
label = ctx.label,
deps = ctx.attrs.deps,
).values())
if cxx_resources:
cxx_res_dir = ctx.actions.copied_dir(
"CxxResources",
{
name: resource
for name, (resource, _) in cxx_resources.items()
},
)
resource_specs.append(
AppleResourceSpec(
dirs = [cxx_res_dir],
destination = AppleResourceDestination("resources"),
),
)
asset_catalog_result = compile_apple_asset_catalog(ctx, asset_catalog_specs)
if asset_catalog_result != None:
asset_catalog_part = AppleBundlePart(
source = asset_catalog_result.compiled_catalog,
destination = AppleBundleDestination("resources"),
# We only interested in directory contents
new_name = "",
)
parts.append(asset_catalog_part)
extra_plist = asset_catalog_result.catalog_plist if asset_catalog_result != None else None
info_plist_part = process_info_plist(ctx = ctx, override_input = extra_plist)
core_data_result = compile_apple_core_data(ctx, core_data_specs, get_product_name(ctx))
if core_data_result != None:
core_data_part = AppleBundlePart(
source = core_data_result,
destination = AppleBundleDestination("resources"),
# We only interested in directory contents
new_name = "",
)
parts.append(core_data_part)
parts.extend(_copy_resources(ctx, resource_specs))
parts.extend(_copy_first_level_bundles(ctx))
return AppleBundleResourcePartListOutput(
resource_parts = parts,
info_plist_part = info_plist_part,
)
# Same logic as in v1, see `buck_client/src/com/facebook/buck/apple/ApplePkgInfo.java`
def _create_pkg_info_if_needed(ctx: "context") -> [AppleBundlePart.type]:
extension = get_extension_attr(ctx)
if extension == "xpc" or extension == "qlgenerator":
return []
artifact = ctx.actions.write("PkgInfo", "APPLWRUN\n")
return [AppleBundlePart(source = artifact, destination = AppleBundleDestination("metadata"))]
def _select_resources(ctx: "context") -> (([AppleResourceSpec.type], [AppleAssetCatalogSpec.type], [AppleCoreDataSpec.type])):
resource_group_info = get_resource_group_info(ctx)
if resource_group_info:
resource_groups_deps = [mapping.root.node for group in resource_group_info.groups for mapping in group.mappings if mapping.root != None]
resource_group_mappings = resource_group_info.mappings
else:
resource_groups_deps = []
resource_group_mappings = {}
deps = ctx.attrs.deps + filter(None, [ctx.attrs.binary])
resource_graph = create_resource_graph(
ctx = ctx,
labels = [],
deps = deps + resource_groups_deps,
exported_deps = [],
)
resource_graph_node_map_func = get_resource_graph_node_map_func(resource_graph)
return get_filtered_resources(ctx.label, resource_graph_node_map_func, ctx.attrs.resource_group, resource_group_mappings)
def _copy_resources(ctx: "context", specs: [AppleResourceSpec.type]) -> [AppleBundlePart.type]:
result = []
for spec in specs:
bundle_destination = apple_bundle_destination_from_resource_destination(spec.destination)
result += [_process_apple_resource_file_if_needed(
ctx = ctx,
file = x,
destination = bundle_destination,
destination_relative_path = None,
codesign_on_copy = spec.codesign_files_on_copy,
) for x in spec.files]
result += _bundle_parts_for_dirs(spec.dirs, bundle_destination, False)
result += _bundle_parts_for_dirs(spec.content_dirs, bundle_destination, True)
result += _bundle_parts_for_variant_files(ctx, spec)
return result
def _copy_first_level_bundles(ctx: "context") -> [AppleBundlePart.type]:
first_level_bundle_infos = filter(None, [dep.get(AppleBundleInfo) for dep in ctx.attrs.deps])
return filter(None, [_copied_bundle_spec(info) for info in first_level_bundle_infos])
def _copied_bundle_spec(bundle_info: AppleBundleInfo.type) -> [None, AppleBundlePart.type]:
bundle = bundle_info.bundle
bundle_extension = paths.split_extension(bundle.short_path)[1]
if bundle_extension == ".framework":
destination = AppleBundleDestination("frameworks")
codesign_on_copy = True
elif bundle_extension == ".app":
expect(bundle_info.is_watchos != None, "Field should be set for bundles with extension {}".format(bundle_extension))
destination = AppleBundleDestination("watchapp" if bundle_info.is_watchos else "plugins")
codesign_on_copy = False
elif bundle_extension == ".appex":
destination = AppleBundleDestination("plugins")
codesign_on_copy = False
else:
fail("Extension `{}` is not yet supported.".format(bundle_extension))
return AppleBundlePart(
source = bundle,
destination = destination,
codesign_on_copy = codesign_on_copy,
)
def _bundle_parts_for_dirs(generated_dirs: ["artifact"], destination: AppleBundleDestination.type, copy_contents_only: bool.type) -> [AppleBundlePart.type]:
return [AppleBundlePart(
source = generated_dir,
destination = destination,
new_name = "" if copy_contents_only else None,
) for generated_dir in generated_dirs]
def _bundle_parts_for_variant_files(ctx: "context", spec: AppleResourceSpec.type) -> [AppleBundlePart.type]:
result = []
# By definition, all variant files go into the resources destination
bundle_destination = AppleBundleDestination("resources")
for variant_file in spec.variant_files:
variant_dest_subpath = _get_dest_subpath_for_variant_file(variant_file)
bundle_part = _process_apple_resource_file_if_needed(
ctx = ctx,
file = variant_file,
destination = bundle_destination,
destination_relative_path = variant_dest_subpath,
)
result.append(bundle_part)
for locale, variant_files in spec.named_variant_files.items():
if not locale.endswith(".lproj"):
fail("Keys for named variant files have to end with '.lproj' suffix, got {}".format(locale))
result += [
_process_apple_resource_file_if_needed(
ctx = ctx,
file = variant_file,
destination = bundle_destination,
destination_relative_path = paths.join(locale, paths.basename(variant_file.short_path)),
)
for variant_file in variant_files
]
return result
def _run_ibtool(
ctx: "context",
raw_file: "artifact",
output: "output_artifact",
action_flags: [str.type],
target_device: [None, str.type],
action_identifier: str.type,
output_is_dir: bool.type) -> None:
# TODO(T110378103): detect and add minimum deployment target automatically
# TODO(T110378113): add support for ibtool modules (turned on by `ibtool_module_flag` field of `apple_bundle` rule)
# Equivalent of `AppleProcessResources::BASE_IBTOOL_FLAGS` from v1
base_flags = ["--output-format", "human-readable-text", "--notices", "--warnings", "--errors"]
ibtool = ctx.attrs._apple_toolchain[AppleToolchainInfo].ibtool
ibtool_command = [ibtool] + base_flags + (ctx.attrs.ibtool_flags or [])
if target_device != None:
ibtool_command.extend(["--target-device", target_device])
ibtool_command.extend(action_flags)
if output_is_dir:
ibtool_command.append('"$TMPDIR"')
else:
ibtool_command.append(output)
ibtool_command.append(raw_file)
if output_is_dir:
# Sandboxing and fs isolation on RE machines results in Xcode tools failing
# when those are working in freshly created directories in buck-out.
# See https://fb.workplace.com/groups/1042353022615812/permalink/1872164996301273/
# As a workaround create a directory in tmp, use it for Xcode tools, then
# copy the result to buck-out.
wrapper_script, _ = ctx.actions.write(
"ibtool_wrapper.sh",
[
cmd_args('export TMPDIR="$(mktemp -d)"'),
cmd_args(cmd_args(ibtool_command), delimiter = " "),
cmd_args(output, format = 'mkdir -p {} && cp -r "$TMPDIR"/ {}'),
],
allow_args = True,
)
command = cmd_args(["/bin/sh", wrapper_script]).hidden([ibtool_command, output])
else:
command = ibtool_command
processing_options = get_bundle_resource_processing_options(ctx)
ctx.actions.run(command, prefer_local = processing_options.prefer_local, allow_cache_upload = processing_options.allow_cache_upload, category = "apple_ibtool", identifier = action_identifier)
def _compile_ui_resource(
ctx: "context",
raw_file: "artifact",
output: "output_artifact",
target_device: [None, str.type] = None,
output_is_dir: bool.type = False) -> None:
_run_ibtool(
ctx = ctx,
raw_file = raw_file,
output = output,
action_flags = ["--compile"],
target_device = target_device,
action_identifier = "compile_" + raw_file.basename,
output_is_dir = output_is_dir,
)
def _link_ui_resource(
ctx: "context",
raw_file: "artifact",
output: "output_artifact",
target_device: [None, str.type] = None,
output_is_dir: bool.type = False) -> None:
_run_ibtool(
ctx = ctx,
raw_file = raw_file,
output = output,
action_flags = ["--link"],
target_device = target_device,
action_identifier = "link_" + raw_file.basename,
output_is_dir = output_is_dir,
)
def _process_apple_resource_file_if_needed(
ctx: "context",
file: "artifact",
destination: AppleBundleDestination.type,
destination_relative_path: [str.type, None],
codesign_on_copy: bool.type = False) -> AppleBundlePart.type:
output_dir = "_ProcessedResources"
basename = paths.basename(file.short_path)
output_is_contents_dir = False
if basename.endswith(".plist") or basename.endswith(".stringsdict"):
processed = ctx.actions.declare_output(paths.join(output_dir, file.short_path))
process_plist(
ctx = ctx,
input = file,
output = processed.as_output(),
action_id = destination_relative_path,
)
elif basename.endswith(".storyboard"):
compiled = ctx.actions.declare_output(paths.join(output_dir, paths.replace_extension(file.short_path, ".storyboardc")))
if get_is_watch_bundle(ctx):
output_is_contents_dir = True
_compile_ui_resource(ctx = ctx, raw_file = file, output = compiled.as_output(), target_device = "watch")
processed = ctx.actions.declare_output(paths.join(output_dir, paths.replace_extension(file.short_path, "_linked_storyboard")))
_link_ui_resource(ctx = ctx, raw_file = compiled, output = processed.as_output(), target_device = "watch", output_is_dir = True)
else:
processed = compiled
_compile_ui_resource(ctx, file, processed.as_output())
elif basename.endswith(".xib"):
processed = ctx.actions.declare_output(paths.join(output_dir, paths.replace_extension(file.short_path, ".nib")))
_compile_ui_resource(ctx, file, processed.as_output())
else:
processed = file
# When name is empty string only content of the directory will be copied, as opposed to the directory itself.
# When name is `None`, directory or file will be copied as it is, without renaming.
new_name = destination_relative_path if destination_relative_path else ("" if output_is_contents_dir else None)
return AppleBundlePart(source = processed, destination = destination, new_name = new_name, codesign_on_copy = codesign_on_copy)
# Returns a path relative to the _parent_ of the lproj dir.
# For example, given a variant file with a short path of`XX/YY.lproj/ZZ`
# it would return `YY.lproj/ZZ`.
def _get_dest_subpath_for_variant_file(variant_file: "artifact") -> str.type:
dir_name = paths.basename(paths.dirname(variant_file.short_path))
if not dir_name.endswith("lproj"):
fail("Variant files have to be in a directory with name ending in '.lproj' but `{}` was not.".format(variant_file.short_path))
file_name = paths.basename(variant_file.short_path)
return paths.join(dir_name, file_name)
def get_is_watch_bundle(ctx: "context") -> bool.type:
return ctx.attrs._apple_toolchain[AppleToolchainInfo].watch_kit_stub_binary != None

View file

@ -0,0 +1,31 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# Provider flagging that result of the rule contains Apple bundle.
# It might be copied into main bundle to appropriate place if rule
# with this provider is a dependency of `apple_bundle`.
AppleBundleInfo = provider(fields = [
# Result bundle; `artifact`
"bundle",
# The name of the executable within the bundle.
# `str.type`
"binary_name",
# If the bundle was built for watchOS Apple platform, this affects packaging.
# Might be omitted for certain types of bundle (e.g. frameworks) when packaging doesn't depend on it.
# [None, `bool.type`]
"is_watchos",
])
# Provider which helps to propagate minimum deployment version up the target graph.
AppleMinDeploymentVersionInfo = provider(fields = [
# `str.type`
"version",
])
AppleBundleResourceInfo = provider(fields = [
"resource_output", # AppleBundleResourcePartListOutput.type
])

View file

@ -0,0 +1,62 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//utils:utils.bzl", "value_or")
load(":apple_bundle_types.bzl", "AppleMinDeploymentVersionInfo")
load(":apple_resource_types.bzl", "AppleResourceProcessingOptions")
load(":apple_target_sdk_version.bzl", "get_min_deployment_version_for_node")
load(":apple_toolchain_types.bzl", "AppleToolchainInfo")
# `ctx` in all functions below is expected to be of `apple_bundle` or `apple_test` rule
def _get_bundle_target_name(ctx: "context"):
if hasattr(ctx.attrs, "_bundle_target_name"):
# `apple_resource_bundle` rules are proxies for the real rules,
# so make sure we return the real target name rather the proxy one
return ctx.attrs._bundle_target_name
return ctx.attrs.name
def get_product_name(ctx: "context") -> str.type:
return ctx.attrs.product_name if hasattr(ctx.attrs, "product_name") and ctx.attrs.product_name != None else _get_bundle_target_name(ctx)
def get_extension_attr(ctx: "context") -> "":
return ctx.attrs.extension
# Derives the effective deployment target for the bundle. It's
# usually the deployment target of the binary if present,
# otherwise it falls back to other values (see implementation).
def get_bundle_min_target_version(ctx: "context") -> str.type:
binary_min_version = None
# Could be not set for e.g. watchOS bundles which have a stub
# binary that comes from the apple_toolchain(), not from the
# apple_bundle() itself (i.e., binary field will be None).
#
# TODO(T114147746): The top-level stub bundle for a watchOS app
# does not have the ability to set its deployment target via
# a binary (as that field is empty). If it contains asset
# catalogs (can it?), we need to use correct target version.
#
# The solution might to be support SDK version from
# Info.plist (T110378109).
if ctx.attrs.binary != None:
min_version_info = ctx.attrs.binary[AppleMinDeploymentVersionInfo]
if min_version_info != None:
binary_min_version = min_version_info.version
fallback_min_version = get_min_deployment_version_for_node(ctx)
min_version = binary_min_version or fallback_min_version
if min_version != None:
return min_version
# TODO(T110378109): support default value from SDK `Info.plist`
fail("Could not determine min target sdk version for bundle: {}".format(ctx.label))
def get_bundle_resource_processing_options(ctx: "context") -> AppleResourceProcessingOptions.type:
compile_resources_locally = value_or(ctx.attrs._compile_resources_locally_override, ctx.attrs._apple_toolchain[AppleToolchainInfo].compile_resources_locally)
return AppleResourceProcessingOptions(prefer_local = compile_resources_locally, allow_cache_upload = compile_resources_locally)

View file

@ -0,0 +1,18 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# Provider which exposes a field from `apple_binary` to `apple_bundle` as it might be used during code signing.
AppleEntitlementsInfo = provider(fields = [
# Optional "artifact"
"entitlements_file",
])
CodeSignType = enum(
"skip",
"adhoc",
"distribution",
)

View file

@ -0,0 +1,69 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load(":apple_bundle_utility.bzl", "get_bundle_min_target_version", "get_bundle_resource_processing_options")
load(":apple_core_data_types.bzl", "AppleCoreDataSpec")
load(":apple_sdk.bzl", "get_apple_sdk_name")
load(":resource_groups.bzl", "create_resource_graph")
def apple_core_data_impl(ctx: "context") -> ["provider"]:
spec = AppleCoreDataSpec(
path = ctx.attrs.path,
)
graph = create_resource_graph(
ctx = ctx,
labels = ctx.attrs.labels,
deps = [],
exported_deps = [],
core_data_spec = spec,
)
return [DefaultInfo(), graph]
def compile_apple_core_data(ctx: "context", specs: [AppleCoreDataSpec.type], product_name: str.type) -> ["artifact", None]:
if len(specs) == 0:
return None
output = ctx.actions.declare_output("AppleCoreDataCompiled")
# Aggregate all the coredata momc commands together
momc_commands = []
for spec in specs:
momc_command = _get_momc_command(ctx, spec, product_name, cmd_args("$TMPDIR"))
momc_commands.append(momc_command)
# Sandboxing and fs isolation on RE machines results in Xcode tools failing
# when those are working in freshly created directories in buck-out.
# See https://fb.workplace.com/groups/1042353022615812/permalink/1872164996301273/
# As a workaround create a directory in tmp, use it for Xcode tools, then
# copy the result to buck-out.
wrapper_script, _ = ctx.actions.write(
"momc_wrapper.sh",
[
cmd_args('export TMPDIR="$(mktemp -d)"'),
cmd_args(momc_commands),
cmd_args(output, format = 'mkdir -p {} && cp -r "$TMPDIR"/ {}'),
],
allow_args = True,
)
combined_command = cmd_args(["/bin/sh", wrapper_script]).hidden(momc_commands + [output.as_output()])
processing_options = get_bundle_resource_processing_options(ctx)
ctx.actions.run(combined_command, prefer_local = processing_options.prefer_local, allow_cache_upload = processing_options.allow_cache_upload, category = "apple_core_data")
return output
def _get_momc_command(ctx: "context", core_data_spec: AppleCoreDataSpec.type, product_name: str.type, output_directory: "cmd_args") -> "cmd_args":
return cmd_args([
ctx.attrs._apple_toolchain[AppleToolchainInfo].momc,
"--sdkroot",
ctx.attrs._apple_toolchain[AppleToolchainInfo].sdk_path,
"--" + get_apple_sdk_name(ctx) + "-deployment-target",
get_bundle_min_target_version(ctx),
"--module",
product_name,
core_data_spec.path,
output_directory,
], delimiter = " ")

View file

@ -0,0 +1,10 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
AppleCoreDataSpec = record(
path = field("artifact"),
)

View file

@ -0,0 +1,35 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
DSYM_SUBTARGET = "dsym"
DEBUGINFO_SUBTARGET = "debuginfo"
AppleDebuggableInfo = provider(fields = [
"dsyms", # ["artifact"]
"external_debug_info", # ["_arglike"]
])
# TODO(T110672942): Things which are still unsupported:
# - pass in dsymutil_extra_flags
# - oso_prefix
# - dsym_verification
def get_apple_dsym(ctx: "context", executable: "artifact", external_debug_info: ["_arglike"], action_identifier: "string") -> "artifact":
dsymutil = ctx.attrs._apple_toolchain[AppleToolchainInfo].dsymutil
output = ctx.actions.declare_output("{}.dSYM".format(executable.short_path))
cmd = cmd_args([dsymutil, "-o", output.as_output(), executable])
# Mach-O executables don't contain DWARF data.
# Instead, they contain paths to the object files which themselves contain DWARF data.
#
# So, those object files are needed for dsymutil to be to create the dSYM bundle.
cmd.hidden(external_debug_info)
ctx.actions.run(cmd, category = "apple_dsym", identifier = action_identifier)
return output

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,165 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
load(
"@prelude//linking:link_info.bzl",
"FrameworksLinkable",
"LinkArgs",
"LinkInfo",
"LinkInfos",
"LinkInfosTSet",
"LinkableType",
"get_link_args",
"merge_framework_linkables",
)
load("@prelude//utils:utils.bzl", "expect")
load(":apple_framework_versions.bzl", "get_framework_linker_args")
load(":apple_toolchain_types.bzl", "AppleToolchainInfo")
_IMPLICIT_SDKROOT_FRAMEWORK_SEARCH_PATHS = [
"$SDKROOT/Library/Frameworks",
"$SDKROOT/System/Library/Frameworks",
]
def create_frameworks_linkable(ctx: "context") -> [FrameworksLinkable.type, None]:
if not ctx.attrs.libraries and not ctx.attrs.frameworks:
return None
return FrameworksLinkable(
library_names = [_library_name(x) for x in ctx.attrs.libraries],
unresolved_framework_paths = _get_non_sdk_unresolved_framework_directories(ctx.attrs.frameworks),
framework_names = [to_framework_name(x) for x in ctx.attrs.frameworks],
)
def _get_apple_frameworks_linker_flags(ctx: "context", linkable: [FrameworksLinkable.type, None]) -> "cmd_args":
if not linkable:
return cmd_args()
expanded_frameworks_paths = _expand_sdk_framework_paths(ctx, linkable.unresolved_framework_paths)
flags = _get_framework_search_path_flags(expanded_frameworks_paths)
flags.add(get_framework_linker_args(ctx, linkable.framework_names))
for library_name in linkable.library_names:
flags.add("-l" + library_name)
return flags
def get_framework_search_path_flags(ctx: "context") -> "cmd_args":
unresolved_framework_dirs = _get_non_sdk_unresolved_framework_directories(ctx.attrs.frameworks)
expanded_framework_dirs = _expand_sdk_framework_paths(ctx, unresolved_framework_dirs)
return _get_framework_search_path_flags(expanded_framework_dirs)
def _get_framework_search_path_flags(frameworks: ["cmd_args"]) -> "cmd_args":
flags = cmd_args()
for directory in frameworks:
flags.add(["-F", directory])
return flags
def _get_non_sdk_unresolved_framework_directories(frameworks: [""]) -> [""]:
# We don't want to include SDK directories as those are already added via `isysroot` flag in toolchain definition.
# Adding those directly via `-F` will break building Catalyst applications as frameworks from support directory
# won't be found and those for macOS platform will be used.
return dedupe(filter(None, [_non_sdk_unresolved_framework_directory(x) for x in frameworks]))
def to_framework_name(framework_path: str.type) -> str.type:
name, ext = paths.split_extension(paths.basename(framework_path))
expect(ext == ".framework", "framework `{}` missing `.framework` suffix", framework_path)
return name
def _library_name(library: str.type) -> str.type:
name = paths.basename(library)
if not name.startswith("lib"):
fail("unexpected library: {}".format(library))
return paths.split_extension(name[3:])[0]
def _expand_sdk_framework_paths(ctx: "context", unresolved_framework_paths: [str.type]) -> ["cmd_args"]:
return [_expand_sdk_framework_path(ctx, unresolved_framework_path) for unresolved_framework_path in unresolved_framework_paths]
def _expand_sdk_framework_path(ctx: "context", framework_path: str.type) -> "cmd_args":
apple_toolchain_info = ctx.attrs._apple_toolchain[AppleToolchainInfo]
path_expansion_map = {
"$PLATFORM_DIR/": apple_toolchain_info.platform_path,
"$SDKROOT/": apple_toolchain_info.sdk_path,
}
for (trailing_path_variable, path_value) in path_expansion_map.items():
(before, separator, relative_path) = framework_path.partition(trailing_path_variable)
if separator == trailing_path_variable:
if len(before) > 0:
fail("Framework symbolic path not anchored at the beginning, tried expanding `{}`".format(framework_path))
if relative_path.count("$") > 0:
fail("Framework path contains multiple symbolic paths, tried expanding `{}`".format(framework_path))
if len(relative_path) == 0:
fail("Framework symbolic path contains no relative path to expand, tried expanding `{}`, relative path: `{}`, before: `{}`, separator `{}`".format(framework_path, relative_path, before, separator))
return cmd_args([path_value, relative_path], delimiter = "/")
if framework_path.find("$") == 0:
fail("Failed to expand framework path: {}".format(framework_path))
return cmd_args(framework_path)
def _non_sdk_unresolved_framework_directory(framework_path: str.type) -> [str.type, None]:
# We must only drop any framework paths that are part of the implicit
# framework search paths in the linker + compiler, all other paths
# must be expanded and included as part of the command.
for implicit_search_path in _IMPLICIT_SDKROOT_FRAMEWORK_SEARCH_PATHS:
if framework_path.find(implicit_search_path) == 0:
return None
return paths.dirname(framework_path)
def build_link_args_with_deduped_framework_flags(
ctx: "context",
info: "MergedLinkInfo",
frameworks_linkable: ["FrameworksLinkable", None],
link_style: "LinkStyle",
prefer_stripped: bool.type = False) -> LinkArgs.type:
frameworks_link_info = _link_info_from_frameworks_linkable(ctx, [info.frameworks[link_style], frameworks_linkable])
if not frameworks_link_info:
return get_link_args(info, link_style, prefer_stripped)
return LinkArgs(
tset = (ctx.actions.tset(
LinkInfosTSet,
value = LinkInfos(default = frameworks_link_info, stripped = frameworks_link_info),
children = [info._infos[link_style]],
), prefer_stripped),
)
def get_frameworks_link_info_by_deduping_link_infos(
ctx: "context",
infos: [[LinkInfo.type, None]],
framework_linkable: [FrameworksLinkable.type, None]) -> [LinkInfo.type, None]:
# When building a framework or executable, all frameworks used by the statically-linked
# deps in the subtree need to be linked.
#
# Without deduping, we've seen the linking step fail because the argsfile
# exceeds the acceptable size by the linker.
framework_linkables = _extract_framework_linkables(infos)
if framework_linkable:
framework_linkables.append(framework_linkable)
return _link_info_from_frameworks_linkable(ctx, framework_linkables)
def _extract_framework_linkables(link_infos: [[LinkInfo.type], None]) -> [FrameworksLinkable.type]:
frameworks_type = LinkableType("frameworks")
linkables = []
for info in link_infos:
for linkable in info.linkables:
if linkable._type == frameworks_type:
linkables.append(linkable)
return linkables
def _link_info_from_frameworks_linkable(ctx: "context", framework_linkables: [[FrameworksLinkable.type, None]]) -> [LinkInfo.type, None]:
framework_link_args = _get_apple_frameworks_linker_flags(ctx, merge_framework_linkables(framework_linkables))
return LinkInfo(
pre_flags = [framework_link_args],
) if framework_link_args else None

View file

@ -0,0 +1,139 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_bundle_destination.bzl", "AppleBundleDestination")
load(":apple_bundle_part.bzl", "AppleBundlePart")
load(":apple_bundle_utility.bzl", "get_bundle_min_target_version", "get_product_name")
load(":apple_sdk.bzl", "get_apple_sdk_name")
load(
":apple_sdk_metadata.bzl",
"AppleSdkMetadata", # @unused Used as a type
"MacOSXCatalystSdkMetadata",
"MacOSXSdkMetadata",
"WatchOSSdkMetadata",
"WatchSimulatorSdkMetadata",
"get_apple_sdk_metadata_for_sdk_name",
)
load(":apple_toolchain_types.bzl", "AppleToolchainInfo", "AppleToolsInfo")
def process_info_plist(ctx: "context", override_input: ["artifact", None]) -> AppleBundlePart.type:
input = _preprocess_info_plist(ctx)
output = ctx.actions.declare_output("Info.plist")
additional_keys = _additional_keys_as_json_file(ctx)
override_keys = _override_keys_as_json_file(ctx)
process_plist(
ctx = ctx,
input = input,
output = output.as_output(),
override_input = override_input,
additional_keys = additional_keys,
override_keys = override_keys,
)
return AppleBundlePart(source = output, destination = AppleBundleDestination("metadata"))
def _get_plist_run_options() -> {str.type: bool.type}:
return {
# Output is deterministic, so can be cached
"allow_cache_upload": True,
# plist generation is cheap and fast, RE network overhead not worth it
"prefer_local": True,
}
def _preprocess_info_plist(ctx: "context") -> "artifact":
input = ctx.attrs.info_plist
output = ctx.actions.declare_output("PreprocessedInfo.plist")
substitutions_json = _plist_substitutions_as_json_file(ctx)
apple_tools = ctx.attrs._apple_tools[AppleToolsInfo]
processor = apple_tools.info_plist_processor
command = cmd_args([
processor,
"preprocess",
"--input",
input,
"--output",
output.as_output(),
"--product-name",
get_product_name(ctx),
])
if substitutions_json != None:
command.add(["--substitutions-json", substitutions_json])
ctx.actions.run(command, category = "apple_preprocess_info_plist", **_get_plist_run_options())
return output
def _plist_substitutions_as_json_file(ctx: "context") -> ["artifact", None]:
info_plist_substitutions = ctx.attrs.info_plist_substitutions
if not info_plist_substitutions:
return None
substitutions_json = ctx.actions.write_json("plist_substitutions.json", info_plist_substitutions)
return substitutions_json
def process_plist(ctx: "context", input: "artifact", output: "output_artifact", override_input: ["artifact", None] = None, additional_keys: ["artifact", None] = None, override_keys: ["artifact", None] = None, action_id: [str.type, None] = None):
apple_tools = ctx.attrs._apple_tools[AppleToolsInfo]
processor = apple_tools.info_plist_processor
override_input_arguments = ["--override-input", override_input] if override_input != None else []
additional_keys_arguments = ["--additional-keys", additional_keys] if additional_keys != None else []
override_keys_arguments = ["--override-keys", override_keys] if override_keys != None else []
command = cmd_args([
processor,
"process",
"--input",
input,
"--output",
output,
] + override_input_arguments + additional_keys_arguments + override_keys_arguments)
ctx.actions.run(command, category = "apple_process_info_plist", identifier = action_id or input.basename, **_get_plist_run_options())
def _additional_keys_as_json_file(ctx: "context") -> "artifact":
additional_keys = _info_plist_additional_keys(ctx)
return ctx.actions.write_json("plist_additional.json", additional_keys)
def _info_plist_additional_keys(ctx: "context") -> {str.type: ""}:
sdk_name = get_apple_sdk_name(ctx)
sdk_metadata = get_apple_sdk_metadata_for_sdk_name(sdk_name)
result = _extra_mac_info_plist_keys(sdk_metadata, ctx.attrs.extension)
result["CFBundleSupportedPlatforms"] = sdk_metadata.info_plist_supported_platforms_values
result["DTPlatformName"] = sdk_name
sdk_version = ctx.attrs._apple_toolchain[AppleToolchainInfo].sdk_version
if sdk_version:
result["DTPlatformVersion"] = sdk_version
result["DTSDKName"] = sdk_name + sdk_version
sdk_build_version = ctx.attrs._apple_toolchain[AppleToolchainInfo].sdk_build_version
if sdk_build_version:
result["DTPlatformBuild"] = sdk_build_version
result["DTSDKBuild"] = sdk_build_version
xcode_build_version = ctx.attrs._apple_toolchain[AppleToolchainInfo].xcode_build_version
if xcode_build_version:
result["DTXcodeBuild"] = xcode_build_version
xcode_version = ctx.attrs._apple_toolchain[AppleToolchainInfo].xcode_version
if xcode_version:
result["DTXcode"] = xcode_version
result[sdk_metadata.min_version_plist_info_key] = get_bundle_min_target_version(ctx)
return result
def _extra_mac_info_plist_keys(sdk_metadata: AppleSdkMetadata.type, extension: str.type) -> {str.type: ""}:
if sdk_metadata.name == MacOSXSdkMetadata.name and extension == "xpc":
return {
"NSHighResolutionCapable": True,
"NSSupportsAutomaticGraphicsSwitching": True,
}
else:
return {}
def _override_keys_as_json_file(ctx: "context") -> "artifact":
override_keys = _info_plist_override_keys(ctx)
return ctx.actions.write_json("plist_override.json", override_keys)
def _info_plist_override_keys(ctx: "context") -> {str.type: ""}:
sdk_name = get_apple_sdk_name(ctx)
result = {}
if sdk_name == MacOSXSdkMetadata.name:
if ctx.attrs.extension != "xpc":
result["LSRequiresIPhoneOS"] = False
elif sdk_name not in [WatchOSSdkMetadata.name, WatchSimulatorSdkMetadata.name, MacOSXCatalystSdkMetadata.name]:
result["LSRequiresIPhoneOS"] = True
return result

View file

@ -0,0 +1,60 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# `apple_bundle.info_plist_substitutions` might contain `CODE_SIGN_ENTITLEMENTS` key which (as per v1 documentation):
#
# > Code signing will embed entitlements pointed to by the entitlements_file arg in the bundle's apple_binary.
# > This is the preferred way to specify entitlements when building with Buck.
# > If the entitlements file is not present, it falls back to the CODE_SIGN_ENTITLEMENTS entry in info_plist_substitutions.
#
# In order to properly depend on this fallback entitlements file (and manipulate it) we have to convert this text entry into the source artifact.
# We only can do that on macro layer, hence the purpose of the following code.
_SOURCE_ROOT_PREFIX = "$(SOURCE_ROOT)/"
_CODE_SIGN_ENTITLEMENTS_KEY = "CODE_SIGN_ENTITLEMENTS"
def _find_first_variable(string: str.type) -> [(str.type, (str.type, str.type)), None]:
"""
If variable like `$(FOO)` is not found in `string` returns `None`, else returns tuple
with first element equal to variable name (e.g. `FOO`) and second element equal to tuple
of part before and after this variable.
"""
expansion_start = "$("
expansion_end = ")"
variable_start = string.find(expansion_start)
if variable_start == -1:
return None
variable_end = string.find(expansion_end, variable_start)
if variable_end == -1:
fail("Expected variable expansion in string: `{}`".format(string))
variable = string[variable_start + len(expansion_start):variable_end - len(expansion_end) + 1]
prefix = string[:variable_start]
suffix = string[variable_end + 1:]
return (variable, (prefix, suffix))
def _expand_codesign_entitlements_path(info_plist_substitutions: {str.type: str.type}, path: str.type) -> str.type:
path = path.strip()
for _ in range(100):
if path.startswith(_SOURCE_ROOT_PREFIX):
path = path[len(_SOURCE_ROOT_PREFIX):]
maybe_variable = _find_first_variable(path)
if not maybe_variable:
return path
(key, (prefix, suffix)) = maybe_variable
maybe_value = info_plist_substitutions.get(key)
if not maybe_value:
fail("Expected to find value for `{}` in `info_plist_substitutions` dictionary `{}`".format(key, info_plist_substitutions))
path = prefix + maybe_value + suffix
fail("Too many iteration (loop might be present) to expand `{}` with substitutions `{}`".format(path, info_plist_substitutions))
def parse_codesign_entitlements(info_plist_substitutions: [{str.type: str.type}, None]) -> [str.type, None]:
if not info_plist_substitutions:
return None
maybe_path = info_plist_substitutions.get(_CODE_SIGN_ENTITLEMENTS_KEY)
if not maybe_path:
return None
return _expand_codesign_entitlements_path(info_plist_substitutions, maybe_path)

View file

@ -0,0 +1,216 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_dsym.bzl", "AppleDebuggableInfo", "DEBUGINFO_SUBTARGET", "DSYM_SUBTARGET", "get_apple_dsym")
load("@prelude//apple:apple_stripping.bzl", "apple_strip_args")
load("@prelude//apple:swift_compilation.bzl", "compile_swift", "get_swift_dependency_info", "get_swift_pcm_compile_info")
load("@prelude//cxx:cxx.bzl", "get_srcs_with_flags")
load("@prelude//cxx:cxx_library.bzl", "cxx_library_parameterized")
load("@prelude//cxx:cxx_library_utility.bzl", "cxx_attr_deps", "cxx_attr_exported_deps")
load("@prelude//cxx:cxx_types.bzl", "CxxRuleAdditionalParams", "CxxRuleConstructorParams", "CxxRuleProviderParams", "CxxRuleSubTargetParams")
load("@prelude//cxx:headers.bzl", "cxx_attr_exported_headers")
load(
"@prelude//cxx:linker.bzl",
"SharedLibraryFlagOverrides",
)
load(
"@prelude//cxx:preprocessor.bzl",
"CPreprocessor",
)
load("@prelude//linking:link_info.bzl", "LinkStyle")
load(":apple_bundle_types.bzl", "AppleMinDeploymentVersionInfo")
load(":apple_frameworks.bzl", "get_framework_search_path_flags")
load(":apple_link_postprocessor.bzl", "get_apple_link_postprocessor")
load(":apple_modular_utility.bzl", "MODULE_CACHE_PATH")
load(":apple_target_sdk_version.bzl", "get_min_deployment_version_for_node", "get_min_deployment_version_target_linker_flags", "get_min_deployment_version_target_preprocessor_flags")
load(":apple_utility.bzl", "get_apple_cxx_headers_layout", "get_module_name")
load(":modulemap.bzl", "preprocessor_info_for_modulemap")
load(":resource_groups.bzl", "create_resource_graph")
load(":xcode.bzl", "apple_populate_xcode_attributes")
AppleLibraryAdditionalParams = record(
# Name of the top level rule utilizing the apple_library rule.
rule_type = str.type,
# Extra flags to be passed to the linker.
extra_exported_link_flags = field(["_arglike"], []),
# Extra flags to be passed to the Swift compiler.
extra_swift_compiler_flags = field(["_arglike"], []),
# Linker flags that tell the linker to create shared libraries, overriding the default shared library flags.
# e.g. when building Apple tests, we want to link with `-bundle` instead of `-shared` to allow
# linking against the bundle loader.
shared_library_flags = field([SharedLibraryFlagOverrides.type, None], None),
# Function to use for setting Xcode attributes for the Xcode data sub target.
populate_xcode_attributes_func = field("function", apple_populate_xcode_attributes),
# Define which sub targets to generate.
generate_sub_targets = field(CxxRuleSubTargetParams.type, CxxRuleSubTargetParams()),
# Define which providers to generate.
generate_providers = field(CxxRuleProviderParams.type, CxxRuleProviderParams()),
# Forces link group linking logic, even when there's no mapping. Link group linking
# without a mapping is equivalent to statically linking the whole transitive dep graph.
force_link_group_linking = field(bool.type, False),
)
def apple_library_impl(ctx: "context") -> ["provider"]:
constructor_params, swift_providers, exported_pre = apple_library_rule_constructor_params_and_swift_providers(ctx, AppleLibraryAdditionalParams(rule_type = "apple_library"))
resource_graph = create_resource_graph(
ctx = ctx,
labels = ctx.attrs.labels,
deps = cxx_attr_deps(ctx),
exported_deps = cxx_attr_exported_deps(ctx),
)
output = cxx_library_parameterized(ctx, constructor_params)
swift_pcm_compile = get_swift_pcm_compile_info(ctx, output.propagated_exported_preprocessor_info, exported_pre)
providers = output.providers + [resource_graph] + swift_providers + ([swift_pcm_compile] if swift_pcm_compile else [])
return providers
def apple_library_rule_constructor_params_and_swift_providers(ctx: "context", params: AppleLibraryAdditionalParams.type) -> (CxxRuleConstructorParams.type, ["provider"], [CPreprocessor.type, None]):
cxx_srcs, swift_srcs = _filter_swift_srcs(ctx)
# First create a modulemap if necessary. This is required for importing
# ObjC code in Swift so must be done before Swift compilation.
exported_hdrs = cxx_attr_exported_headers(ctx, get_apple_cxx_headers_layout(ctx))
if (ctx.attrs.modular or swift_srcs) and exported_hdrs:
modulemap_pre = preprocessor_info_for_modulemap(ctx, "exported", exported_hdrs, None)
else:
modulemap_pre = None
swift_compile = compile_swift(ctx, swift_srcs, exported_hdrs, modulemap_pre, params.extra_swift_compiler_flags)
swift_object_files = swift_compile.object_files if swift_compile else []
swift_pre = CPreprocessor()
if swift_compile:
# If we have Swift we export the extended modulemap that includes
# the ObjC exported headers and the -Swift.h header.
exported_pre = swift_compile.exported_pre
# We also include the -Swift.h header to this libraries preprocessor
# info, so that we can import it unprefixed in this module.
swift_pre = swift_compile.pre
elif modulemap_pre:
# Otherwise if this library is modular we export a modulemap of
# the ObjC exported headers.
exported_pre = modulemap_pre
else:
exported_pre = None
swift_providers = swift_compile.providers if swift_compile else [get_swift_dependency_info(ctx, exported_pre, None)]
swift_argsfile = swift_compile.swift_argsfile if swift_compile else None
modular_pre = CPreprocessor(
uses_modules = ctx.attrs.uses_modules,
modular_args = [
"-fcxx-modules",
"-fmodules",
"-fmodule-name=" + get_module_name(ctx),
"-fmodules-cache-path=" + MODULE_CACHE_PATH,
# TODO(T123756899): We have to use this hack to make compilation work
# when Clang modules are enabled and using toolchains. That's because
# resource-dir is passed as a relative path (so that no abs paths appear
# in any .pcm). The compiler will then expand and generate #include paths
# that won't work unless we have the directive below.
"-I.",
],
)
framework_search_path_pre = CPreprocessor(
args = [get_framework_search_path_flags(ctx)],
)
return CxxRuleConstructorParams(
rule_type = params.rule_type,
is_test = (params.rule_type == "apple_test"),
headers_layout = get_apple_cxx_headers_layout(ctx),
extra_exported_link_flags = params.extra_exported_link_flags,
extra_link_flags = [_get_linker_flags(ctx, swift_providers)],
extra_link_input = swift_object_files,
extra_preprocessors = get_min_deployment_version_target_preprocessor_flags(ctx) + [framework_search_path_pre, swift_pre, modular_pre],
extra_exported_preprocessors = filter(None, [exported_pre]),
srcs = cxx_srcs,
additional = CxxRuleAdditionalParams(
srcs = swift_srcs,
argsfiles = [swift_argsfile] if swift_argsfile else [],
# We need to add any swift modules that we include in the link, as
# these will end up as `N_AST` entries that `dsymutil` will need to
# follow.
external_debug_info = [_get_transitive_swiftmodule_paths(swift_providers)],
),
link_style_sub_targets_and_providers_factory = _get_shared_link_style_sub_targets_and_providers,
shared_library_flags = params.shared_library_flags,
# apple_library's 'stripped' arg only applies to shared subtargets, or,
# targets with 'preferred_linkage = "shared"'
strip_executable = ctx.attrs.stripped,
strip_args_factory = apple_strip_args,
force_link_group_linking = params.force_link_group_linking,
cxx_populate_xcode_attributes_func = lambda local_ctx, **kwargs: _xcode_populate_attributes(ctx = local_ctx, swift_argsfile = swift_argsfile, populate_xcode_attributes_func = params.populate_xcode_attributes_func, **kwargs),
generate_sub_targets = params.generate_sub_targets,
generate_providers = params.generate_providers,
link_postprocessor = get_apple_link_postprocessor(ctx),
), swift_providers, exported_pre
def _filter_swift_srcs(ctx: "context") -> (["CxxSrcWithFlags"], ["CxxSrcWithFlags"]):
cxx_srcs = []
swift_srcs = []
for s in get_srcs_with_flags(ctx):
if s.file.extension == ".swift":
swift_srcs.append(s)
else:
cxx_srcs.append(s)
return cxx_srcs, swift_srcs
def _get_shared_link_style_sub_targets_and_providers(
link_style: LinkStyle.type,
ctx: "context",
executable: "artifact",
external_debug_info: ["_arglike"],
_dwp: ["artifact", None]) -> ({str.type: ["provider"]}, ["provider"]):
if link_style != LinkStyle("shared"):
return ({}, [])
min_version = get_min_deployment_version_for_node(ctx)
min_version_providers = [AppleMinDeploymentVersionInfo(version = min_version)] if min_version != None else []
dsym_artifact = get_apple_dsym(
ctx = ctx,
executable = executable,
external_debug_info = external_debug_info,
action_identifier = executable.short_path,
)
return ({
DSYM_SUBTARGET: [DefaultInfo(default_outputs = [dsym_artifact])],
DEBUGINFO_SUBTARGET: [DefaultInfo(other_outputs = external_debug_info)],
}, [AppleDebuggableInfo(dsyms = [dsym_artifact], external_debug_info = external_debug_info)] + min_version_providers)
def _get_transitive_swiftmodule_paths(swift_providers: ["provider"]) -> "cmd_args":
cmd = cmd_args()
for p in swift_providers:
if hasattr(p, "transitive_swiftmodule_paths"):
cmd.add(p.transitive_swiftmodule_paths.project_as_args("hidden"))
return cmd
def _get_linker_flags(ctx: "context", swift_providers: ["provider"]) -> "cmd_args":
cmd = cmd_args(get_min_deployment_version_target_linker_flags(ctx))
for p in swift_providers:
if hasattr(p, "transitive_swiftmodule_paths"):
cmd.add(p.transitive_swiftmodule_paths.project_as_args("linker_args"))
return cmd
def _xcode_populate_attributes(
ctx,
srcs: ["CxxSrcWithFlags"],
argsfiles_by_ext: {str.type: "artifact"},
swift_argsfile: ["CxxAdditionalArgsfileParams", None],
populate_xcode_attributes_func: "function",
**_kwargs) -> {str.type: ""}:
if swift_argsfile:
argsfiles_by_ext[swift_argsfile.extension] = swift_argsfile.file
data = populate_xcode_attributes_func(ctx, srcs = srcs, argsfiles_by_ext = argsfiles_by_ext, product_name = ctx.attrs.name)
return data

View file

@ -0,0 +1,11 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
def get_apple_link_postprocessor(ctx):
if ctx.attrs.link_postprocessor:
return cmd_args(ctx.attrs.link_postprocessor[RunInfo])
return None

View file

@ -0,0 +1,43 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(
":apple_rules_impl_utility.bzl",
"APPLE_ARCHIVE_OBJECTS_LOCALLY_OVERRIDE_ATTR_NAME",
"APPLE_LINK_BINARIES_LOCALLY_OVERRIDE_ATTR_NAME",
"APPLE_LINK_LIBRARIES_LOCALLY_OVERRIDE_ATTR_NAME",
)
_APPLE_LIBRARY_LOCAL_EXECUTION_OVERRIDES = {
APPLE_LINK_LIBRARIES_LOCALLY_OVERRIDE_ATTR_NAME: ("apple", "link_libraries_locally_override"),
APPLE_ARCHIVE_OBJECTS_LOCALLY_OVERRIDE_ATTR_NAME: ("apple", "archive_objects_locally_override"),
}
_APPLE_BINARY_LOCAL_EXECUTION_OVERRIDES = {
APPLE_LINK_BINARIES_LOCALLY_OVERRIDE_ATTR_NAME: ("apple", "link_binaries_locally_override"),
}
def apple_macro_layer_set_bool_override_attrs_from_config(attrib_map: {str.type: (str.type, str.type)}) -> {str.type: "selector"}:
attribs = {}
for (attrib_name, (config_section, config_key)) in attrib_map.items():
config_value = read_config(config_section, config_key, None)
if config_value != None:
config_truth_value = config_value.lower() == "true"
attribs[attrib_name] = select({
"DEFAULT": config_truth_value,
# Do not set attribute value for host tools
"ovr_config//platform/macos/constraints:execution-platform-transitioned": None,
})
return attribs
def apple_library_macro_impl(apple_library_rule = None, **kwargs):
kwargs.update(apple_macro_layer_set_bool_override_attrs_from_config(_APPLE_LIBRARY_LOCAL_EXECUTION_OVERRIDES))
apple_library_rule(**kwargs)
def apple_binary_macro_impl(apple_binary_rule = None, **kwargs):
kwargs.update(apple_macro_layer_set_bool_override_attrs_from_config(_APPLE_BINARY_LOCAL_EXECUTION_OVERRIDES))
apple_binary_rule(**kwargs)

View file

@ -0,0 +1,14 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# We use a fixed module cache location. This works around issues with
# multi-user setups with MobileOnDemand and allows us to share the
# module cache with Xcode, LLDB and arc focus.
#
# TODO(T123737676): This needs to be changed to use $TMPDIR in a
# wrapper for modular clang compilation.
MODULE_CACHE_PATH = "/tmp/buck-module-cache"

View file

@ -0,0 +1,23 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
def apple_package_impl(ctx: "context") -> ["provider"]:
bundle = ctx.attrs.bundle
ipa_name = "{}.ipa".format(bundle.label.name)
app = bundle[DefaultInfo].default_outputs[0]
payload = ctx.actions.copy_file("Payload", app)
package = ctx.actions.declare_output(ipa_name)
# TODO(T96496412): Add support for compression levels and SwiftSupport
# TODO(T110378117): Pull this into a shared zip utility function
zip = cmd_args(["(cd \"", cmd_args(payload).parent(), "\" && zip -X -r - \"", payload.basename, "\") > ", package.as_output()], delimiter = "")
ctx.actions.run(["sh", "-c", zip], category = "apple_package_zip")
return [DefaultInfo(default_outputs = [package])]

View file

@ -0,0 +1,29 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_resource_types.bzl", "AppleResourceDestination", "AppleResourceSpec")
load(":resource_groups.bzl", "create_resource_graph")
def apple_resource_impl(ctx: "context") -> ["provider"]:
destination = ctx.attrs.destination or "resources"
resource_spec = AppleResourceSpec(
files = ctx.attrs.files,
dirs = ctx.attrs.dirs,
content_dirs = ctx.attrs.content_dirs,
destination = AppleResourceDestination(destination),
variant_files = ctx.attrs.variants or [],
named_variant_files = ctx.attrs.named_variants or {},
codesign_files_on_copy = ctx.attrs.codesign_on_copy,
)
graph = create_resource_graph(
ctx = ctx,
labels = ctx.attrs.labels,
deps = [],
exported_deps = [],
resource_spec = resource_spec,
)
return [DefaultInfo(), graph]

View file

@ -0,0 +1,34 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# Represents the values for the `destination` field of `apple_resource`
AppleResourceDestination = enum(
"resources",
"frameworks",
"executables",
"plugins",
"xpcservices",
)
# Defines _where_ resources need to be placed in an `apple_bundle`
AppleResourceSpec = record(
files = field(["artifact"], []),
dirs = field(["artifact"], []),
content_dirs = field(["artifact"], []),
destination = AppleResourceDestination.type,
variant_files = field(["artifact"], []),
# Map from locale to list of files for that locale, e.g.
# `{ "ru.lproj" : ["Localizable.strings"] }`
named_variant_files = field({str.type: ["artifact"]}, {}),
codesign_files_on_copy = field(bool.type, False),
)
# Used when invoking `ibtool`, `actool` and `momc`
AppleResourceProcessingOptions = record(
prefer_local = field(bool.type, False),
allow_cache_upload = field(bool.type, False),
)

View file

@ -0,0 +1,15 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_bundle_destination.bzl", "AppleBundleDestination")
load(
":apple_resource_types.bzl",
"AppleResourceDestination", # @unused Used as a type
)
def apple_bundle_destination_from_resource_destination(res_destination: AppleResourceDestination.type) -> AppleBundleDestination.type:
return AppleBundleDestination(res_destination.value)

View file

@ -0,0 +1,190 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:attributes.bzl", "LinkableDepType", "Linkage")
load("@prelude//cxx:headers.bzl", "CPrecompiledHeaderInfo")
load("@prelude//cxx:omnibus.bzl", "omnibus_environment_attr")
load("@prelude//cxx/user:link_group_map.bzl", "link_group_map_attr")
load(":apple_asset_catalog.bzl", "apple_asset_catalog_impl")
load(":apple_binary.bzl", "apple_binary_impl")
load(":apple_bundle.bzl", "apple_bundle_impl")
load(":apple_code_signing_types.bzl", "CodeSignType")
load(":apple_core_data.bzl", "apple_core_data_impl")
load(":apple_library.bzl", "apple_library_impl")
load(":apple_package.bzl", "apple_package_impl")
load(":apple_resource.bzl", "apple_resource_impl")
load(
":apple_rules_impl_utility.bzl",
"APPLE_ARCHIVE_OBJECTS_LOCALLY_OVERRIDE_ATTR_NAME",
"APPLE_LINK_BINARIES_LOCALLY_OVERRIDE_ATTR_NAME",
"APPLE_LINK_LIBRARIES_LOCALLY_OVERRIDE_ATTR_NAME",
"apple_bundle_extra_attrs",
"get_apple_toolchain_attr",
"get_apple_xctoolchain_attr",
"get_apple_xctoolchain_bundle_id_attr",
)
load(":apple_test.bzl", "apple_test_impl")
load(":apple_toolchain.bzl", "apple_toolchain_impl")
load(":apple_toolchain_types.bzl", "AppleToolsInfo")
load(":prebuilt_apple_framework.bzl", "prebuilt_apple_framework_impl")
load(":swift_toolchain.bzl", "swift_toolchain_impl")
load(":xcode_postbuild_script.bzl", "xcode_postbuild_script_impl")
load(":xcode_prebuild_script.bzl", "xcode_prebuild_script_impl")
implemented_rules = {
"apple_asset_catalog": apple_asset_catalog_impl,
"apple_binary": apple_binary_impl,
"apple_bundle": apple_bundle_impl,
"apple_library": apple_library_impl,
"apple_package": apple_package_impl,
"apple_resource": apple_resource_impl,
"apple_test": apple_test_impl,
"apple_toolchain": apple_toolchain_impl,
"core_data_model": apple_core_data_impl,
"prebuilt_apple_framework": prebuilt_apple_framework_impl,
"swift_toolchain": swift_toolchain_impl,
"xcode_postbuild_script": xcode_postbuild_script_impl,
"xcode_prebuild_script": xcode_prebuild_script_impl,
}
extra_attributes = {
"apple_asset_catalog": {
"dirs": attrs.list(attrs.source(allow_directory = True), default = []),
},
"apple_binary": {
"binary_linker_flags": attrs.list(attrs.arg(), default = []),
"enable_distributed_thinlto": attrs.bool(default = False),
"extra_xcode_sources": attrs.list(attrs.source(allow_directory = True), default = []),
"link_group_map": link_group_map_attr(),
"link_postprocessor": attrs.option(attrs.exec_dep(), default = None),
"precompiled_header": attrs.option(attrs.dep(providers = [CPrecompiledHeaderInfo]), default = None),
"prefer_stripped_objects": attrs.bool(default = False),
"preferred_linkage": attrs.enum(Linkage, default = "any"),
"stripped": attrs.bool(default = False),
"_apple_toolchain": get_apple_toolchain_attr(),
"_apple_xctoolchain": get_apple_xctoolchain_attr(),
"_apple_xctoolchain_bundle_id": get_apple_xctoolchain_bundle_id_attr(),
"_omnibus_environment": omnibus_environment_attr(),
APPLE_LINK_BINARIES_LOCALLY_OVERRIDE_ATTR_NAME: attrs.option(attrs.bool(), default = None),
},
"apple_bundle": apple_bundle_extra_attrs(),
"apple_library": {
"extra_xcode_sources": attrs.list(attrs.source(allow_directory = True), default = []),
"link_group_map": link_group_map_attr(),
"link_postprocessor": attrs.option(attrs.exec_dep(), default = None),
"precompiled_header": attrs.option(attrs.dep(providers = [CPrecompiledHeaderInfo]), default = None),
"preferred_linkage": attrs.enum(Linkage, default = "any"),
"serialize_debugging_options": attrs.bool(default = True),
"stripped": attrs.bool(default = False),
"use_archive": attrs.option(attrs.bool(), default = None),
"_apple_toolchain": get_apple_toolchain_attr(),
# FIXME: prelude// should be standalone (not refer to fbsource//)
"_apple_tools": attrs.exec_dep(default = "fbsource//xplat/buck2/platform/apple:apple-tools", providers = [AppleToolsInfo]),
"_apple_xctoolchain": get_apple_xctoolchain_attr(),
"_apple_xctoolchain_bundle_id": get_apple_xctoolchain_bundle_id_attr(),
"_omnibus_environment": omnibus_environment_attr(),
APPLE_LINK_LIBRARIES_LOCALLY_OVERRIDE_ATTR_NAME: attrs.option(attrs.bool(), default = None),
APPLE_ARCHIVE_OBJECTS_LOCALLY_OVERRIDE_ATTR_NAME: attrs.option(attrs.bool(), default = None),
},
"apple_resource": {
"codesign_on_copy": attrs.bool(default = False),
"content_dirs": attrs.list(attrs.source(allow_directory = True), default = []),
"dirs": attrs.list(attrs.source(allow_directory = True), default = []),
},
# To build an `apple_test`, one needs to first build a shared `apple_library` then
# wrap this test library into an `apple_bundle`. Because of this, `apple_test` has attributes
# from both `apple_library` and `apple_bundle`.
"apple_test": {
# Expected by `apple_bundle`, for `apple_test` this field is always None.
"binary": attrs.option(attrs.dep(), default = None),
# The resulting test bundle should have .xctest extension.
"extension": attrs.string(default = "xctest"),
"extra_xcode_sources": attrs.list(attrs.source(allow_directory = True), default = []),
"link_postprocessor": attrs.option(attrs.exec_dep(), default = None),
# Used to create the shared test library. Any library deps whose `preferred_linkage` isn't "shared" will
# be treated as "static" deps and linked into the shared test library.
"link_style": attrs.enum(LinkableDepType, default = "static"),
# The test source code and lib dependencies should be built into a shared library.
"preferred_linkage": attrs.enum(Linkage, default = "shared"),
# Expected by `apple_bundle`, for `apple_test` this field is always None.
"resource_group": attrs.option(attrs.string(), default = None),
# Expected by `apple_bundle`, for `apple_test` this field is always None.
"resource_group_map": attrs.option(attrs.string(), default = None),
"stripped": attrs.bool(default = False),
"_apple_toolchain": get_apple_toolchain_attr(),
# FIXME: prelude// should be standalone (not refer to fbsource//)
"_apple_tools": attrs.exec_dep(default = "fbsource//xplat/buck2/platform/apple:apple-tools", providers = [AppleToolsInfo]),
"_apple_xctoolchain": get_apple_xctoolchain_attr(),
"_apple_xctoolchain_bundle_id": get_apple_xctoolchain_bundle_id_attr(),
"_codesign_type": attrs.option(attrs.enum(CodeSignType.values()), default = None),
"_compile_resources_locally_override": attrs.option(attrs.bool(), default = None),
"_incremental_bundling_enabled": attrs.bool(default = False),
"_omnibus_environment": omnibus_environment_attr(),
"_profile_bundling_enabled": attrs.bool(default = False),
APPLE_LINK_LIBRARIES_LOCALLY_OVERRIDE_ATTR_NAME: attrs.option(attrs.bool(), default = None),
},
"apple_toolchain": {
# The Buck v1 attribute specs defines those as `attrs.source()` but
# we want to properly handle any runnable tools that might have
# addition runtime requirements.
"actool": attrs.dep(providers = [RunInfo]),
"codesign": attrs.dep(providers = [RunInfo]),
"codesign_allocate": attrs.dep(providers = [RunInfo]),
"codesign_identities_command": attrs.option(attrs.dep(providers = [RunInfo]), default = None),
# Controls invocations of `ibtool`, `actool` and `momc`
"compile_resources_locally": attrs.bool(default = False),
"dsymutil": attrs.dep(providers = [RunInfo]),
"dwarfdump": attrs.option(attrs.dep(providers = [RunInfo]), default = None),
"ibtool": attrs.dep(providers = [RunInfo]),
"libtool": attrs.dep(providers = [RunInfo]),
"lipo": attrs.dep(providers = [RunInfo]),
"min_version": attrs.option(attrs.string(), default = None),
"momc": attrs.dep(providers = [RunInfo]),
"platform_path": attrs.option(attrs.source()), # Mark as optional until we remove `_internal_platform_path`
"sdk_path": attrs.option(attrs.source()), # Mark as optional until we remove `_internal_sdk_path`
"version": attrs.option(attrs.string(), default = None),
"xcode_build_version": attrs.option(attrs.string(), default = None),
"xcode_version": attrs.option(attrs.string(), default = None),
"xctest": attrs.dep(providers = [RunInfo]),
# TODO(T111858757): Mirror of `platform_path` but treated as a string. It allows us to
# pass abs paths during development and using the currently selected Xcode.
"_internal_platform_path": attrs.option(attrs.string()),
# TODO(T111858757): Mirror of `sdk_path` but treated as a string. It allows us to
# pass abs paths during development and using the currently selected Xcode.
"_internal_sdk_path": attrs.option(attrs.string()),
},
"core_data_model": {
"path": attrs.source(allow_directory = True),
},
"prebuilt_apple_framework": {
"framework": attrs.option(attrs.source(allow_directory = True), default = None),
"preferred_linkage": attrs.enum(Linkage, default = "any"),
"_apple_toolchain": get_apple_toolchain_attr(),
"_omnibus_environment": omnibus_environment_attr(),
},
"scene_kit_assets": {
"path": attrs.source(allow_directory = True),
},
"swift_library": {
"preferred_linkage": attrs.enum(Linkage, default = "any"),
},
"swift_toolchain": {
"architecture": attrs.option(attrs.string(), default = None), # TODO(T115173356): Make field non-optional
"platform_path": attrs.option(attrs.source()), # Mark as optional until we remove `_internal_platform_path`
"sdk_modules": attrs.list(attrs.dep(), default = []), # A list or a root target that represent a graph of sdk modules (e.g Frameworks)
"sdk_path": attrs.option(attrs.source()), # Mark as optional until we remove `_internal_sdk_path`
"swift_stdlib_tool": attrs.exec_dep(providers = [RunInfo]),
"swiftc": attrs.exec_dep(providers = [RunInfo]),
# TODO(T111858757): Mirror of `platform_path` but treated as a string. It allows us to
# pass abs paths during development and using the currently selected Xcode.
"_internal_platform_path": attrs.option(attrs.string(), default = None),
# TODO(T111858757): Mirror of `sdk_path` but treated as a string. It allows us to
# pass abs paths during development and using the currently selected Xcode.
"_internal_sdk_path": attrs.option(attrs.string(), default = None),
"_swiftc_wrapper": attrs.dep(providers = [RunInfo], default = "prelude//apple/tools:swift_exec"),
},
}

View file

@ -0,0 +1,51 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_bundle_types.bzl", "AppleBundleResourceInfo")
load("@prelude//apple:apple_code_signing_types.bzl", "CodeSignType")
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo", "AppleToolsInfo")
load("@prelude//apple/user:resource_group_map.bzl", "resource_group_map_attr")
def get_apple_toolchain_attr():
# FIXME: prelude// should be standalone (not refer to fbcode//)
return attrs.toolchain_dep(default = "fbcode//buck2/platform/toolchain:apple-default", providers = [AppleToolchainInfo])
def _get_apple_bundle_toolchain_attr():
# FIXME: prelude// should be standalone (not refer to fbcode//)
return attrs.toolchain_dep(default = "fbcode//buck2/platform/toolchain:apple-bundle", providers = [AppleToolchainInfo])
def get_apple_xctoolchain_attr():
# FIXME: prelude// should be standalone (not refer to fbcode//)
return attrs.toolchain_dep(default = "fbcode//buck2/platform/toolchain:apple-xctoolchain")
def get_apple_xctoolchain_bundle_id_attr():
# FIXME: prelude// should be standalone (not refer to fbcode//)
return attrs.toolchain_dep(default = "fbcode//buck2/platform/toolchain:apple-xctoolchain-bundle-id")
APPLE_LINK_BINARIES_LOCALLY_OVERRIDE_ATTR_NAME = "_link_binaries_locally_override"
APPLE_LINK_LIBRARIES_LOCALLY_OVERRIDE_ATTR_NAME = "_link_libraries_locally_override"
APPLE_ARCHIVE_OBJECTS_LOCALLY_OVERRIDE_ATTR_NAME = "_archive_objects_locally_override"
def apple_bundle_extra_attrs():
return {
"resource_group_map": resource_group_map_attr(),
# FIXME: prelude// should be standalone (not refer to buck//)
"_apple_installer": attrs.label(default = "buck//src/com/facebook/buck/installer/apple:apple_installer"),
"_apple_toolchain": _get_apple_bundle_toolchain_attr(),
# FIXME: prelude// should be standalone (not refer to fbsource//)
"_apple_tools": attrs.exec_dep(default = "fbsource//xplat/buck2/platform/apple:apple-tools", providers = [AppleToolsInfo]),
"_apple_xctoolchain": get_apple_xctoolchain_attr(),
"_apple_xctoolchain_bundle_id": get_apple_xctoolchain_bundle_id_attr(),
"_codesign_entitlements": attrs.option(attrs.source(), default = None),
"_codesign_type": attrs.option(attrs.enum(CodeSignType.values()), default = None),
"_compile_resources_locally_override": attrs.option(attrs.bool(), default = None),
"_incremental_bundling_enabled": attrs.bool(default = False),
"_profile_bundling_enabled": attrs.bool(default = False),
# FIXME: prelude// should be standalone (not refer to fbsource//)
"_provisioning_profiles": attrs.dep(default = "fbsource//xplat/buck2/platform/apple:provisioning_profiles"),
"_resource_bundle": attrs.option(attrs.dep(providers = [AppleBundleResourceInfo]), default = None),
}

View file

@ -0,0 +1,15 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
def get_apple_sdk_name(ctx: "context") -> str.type:
"""
Get the SDK defined on the toolchain.
Will throw if the `_apple_toolchain` is not present.
"""
return ctx.attrs._apple_toolchain[AppleToolchainInfo].sdk_name

View file

@ -0,0 +1,39 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":swift_pcm_compilation.bzl", "get_shared_pcm_compilation_args")
load(":swift_toolchain_types.bzl", "SdkUncompiledModuleInfo")
def apple_sdk_clang_module_impl(ctx: "context") -> ["provider"]:
cmd = get_shared_pcm_compilation_args(ctx.attrs.target, ctx.attrs.module_name)
module_dependency_infos = filter(None, [d.get(SdkUncompiledModuleInfo) for d in ctx.attrs.deps])
return [
DefaultInfo(),
SdkUncompiledModuleInfo(
name = ctx.attrs.name,
module_name = ctx.attrs.module_name,
is_framework = ctx.attrs.is_framework,
is_swiftmodule = False,
partial_cmd = cmd,
input_relative_path = ctx.attrs.modulemap_relative_path,
deps = module_dependency_infos,
),
]
# This rule represent a Clang module from SDK and forms a graph of dependencies between such modules.
apple_sdk_clang_module = rule(
impl = apple_sdk_clang_module_impl,
attrs = {
"deps": attrs.list(attrs.dep(), default = []),
"is_framework": attrs.bool(default = False),
# This is a real module name, contrary to `name`
# which has a special suffix to distinguish Swift and Clang modules with the same name
"module_name": attrs.string(),
"modulemap_relative_path": attrs.string(),
"target": attrs.string(),
},
)

View file

@ -0,0 +1,95 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
AppleSdkMetadata = record(
name = field(str.type),
target_device_flags = field([str.type], []),
is_ad_hoc_code_sign_sufficient = field(bool.type),
info_plist_supported_platforms_values = field([str.type]),
min_version_plist_info_key = field(str.type),
)
IPhoneOSSdkMetadata = AppleSdkMetadata(
name = "iphoneos",
target_device_flags = ["--target-device", "iphone", "--target-device", "ipad"],
is_ad_hoc_code_sign_sufficient = False,
info_plist_supported_platforms_values = ["iPhoneOS"],
min_version_plist_info_key = "MinimumOSVersion",
)
IPhoneSimulatorSdkMetadata = AppleSdkMetadata(
name = "iphonesimulator",
target_device_flags = ["--target-device", "iphone", "--target-device", "ipad"],
is_ad_hoc_code_sign_sufficient = True,
info_plist_supported_platforms_values = ["iPhoneSimulator"],
min_version_plist_info_key = "MinimumOSVersion",
)
TVOSSdkMetadata = AppleSdkMetadata(
name = "appletvos",
target_device_flags = ["--target-device", "tv"],
is_ad_hoc_code_sign_sufficient = False,
info_plist_supported_platforms_values = ["AppleTVOS"],
min_version_plist_info_key = "MinimumOSVersion",
)
TVSimulatorSdkMetadata = AppleSdkMetadata(
name = "appletvsimulator",
target_device_flags = ["--target-device", "tv"],
is_ad_hoc_code_sign_sufficient = True,
info_plist_supported_platforms_values = ["AppleTVSimulator"],
min_version_plist_info_key = "MinimumOSVersion",
)
WatchOSSdkMetadata = AppleSdkMetadata(
name = "watchos",
target_device_flags = ["--target-device", "watch"],
is_ad_hoc_code_sign_sufficient = False,
info_plist_supported_platforms_values = ["WatchOS"],
min_version_plist_info_key = "MinimumOSVersion",
)
WatchSimulatorSdkMetadata = AppleSdkMetadata(
name = "watchsimulator",
target_device_flags = ["--target-device", "watch"],
is_ad_hoc_code_sign_sufficient = True,
info_plist_supported_platforms_values = ["WatchSimulator"],
min_version_plist_info_key = "MinimumOSVersion",
)
MacOSXSdkMetadata = AppleSdkMetadata(
name = "macosx",
target_device_flags = ["--target-device", "mac"],
is_ad_hoc_code_sign_sufficient = True,
info_plist_supported_platforms_values = ["MacOSX"],
min_version_plist_info_key = "LSMinimumSystemVersion",
)
MacOSXCatalystSdkMetadata = AppleSdkMetadata(
name = "maccatalyst",
target_device_flags = ["--target-device", "ipad"],
is_ad_hoc_code_sign_sufficient = True,
info_plist_supported_platforms_values = ["MacOSX"],
min_version_plist_info_key = "LSMinimumSystemVersion",
)
_SDK_MAP = {
IPhoneOSSdkMetadata.name: IPhoneOSSdkMetadata,
IPhoneSimulatorSdkMetadata.name: IPhoneSimulatorSdkMetadata,
TVOSSdkMetadata.name: TVOSSdkMetadata,
TVSimulatorSdkMetadata.name: TVSimulatorSdkMetadata,
WatchOSSdkMetadata.name: WatchOSSdkMetadata,
WatchSimulatorSdkMetadata.name: WatchSimulatorSdkMetadata,
MacOSXSdkMetadata.name: MacOSXSdkMetadata,
MacOSXCatalystSdkMetadata.name: MacOSXCatalystSdkMetadata,
}
def get_apple_sdk_metadata_for_sdk_name(name: str.type) -> AppleSdkMetadata.type:
sdk = _SDK_MAP.get(name)
if sdk == None:
fail("unrecognized sdk name: `{}`".format(name))
return sdk

View file

@ -0,0 +1,45 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_sdk_modules_utility.bzl", "SDKDepTSet")
load(":apple_sdk_swift_module.bzl", "compile_sdk_swiftinterface")
load(":swift_pcm_compilation.bzl", "compile_swift_sdk_pcm")
# Starting from a root node, this helper function traverses a graph of uncompiled SDK modules
# to create a graph of compiled ones.
def create_sdk_modules_graph(
ctx: "context",
sdk_module_providers: {str.type: "SdkCompiledModuleInfo"},
uncompiled_sdk_module_info: "SdkUncompiledModuleInfo",
toolchain_context: struct.type):
# If input_relative_path is None then this module represents a root node of SDK modules graph.
# In such case, we need to handle only its deps.
if uncompiled_sdk_module_info.input_relative_path == None:
for uncompiled_dependency_info in uncompiled_sdk_module_info.deps:
create_sdk_modules_graph(ctx, sdk_module_providers, uncompiled_dependency_info, toolchain_context)
return
# If provider is already created, return.
if uncompiled_sdk_module_info.name in sdk_module_providers:
return
compiled_dependency_infos_tsets = []
for uncompiled_dependency_info in uncompiled_sdk_module_info.deps:
create_sdk_modules_graph(ctx, sdk_module_providers, uncompiled_dependency_info, toolchain_context)
compiled_dependency_info = sdk_module_providers[uncompiled_dependency_info.name]
sdk_dep_tset = ctx.actions.tset(
SDKDepTSet,
value = compiled_dependency_info,
children = [compiled_dependency_info.deps],
)
compiled_dependency_infos_tsets.append(sdk_dep_tset)
sdk_deps_set = ctx.actions.tset(SDKDepTSet, children = compiled_dependency_infos_tsets)
if uncompiled_sdk_module_info.is_swiftmodule:
compile_sdk_swiftinterface(ctx, toolchain_context, sdk_deps_set, uncompiled_sdk_module_info, sdk_module_providers)
else:
compile_swift_sdk_pcm(ctx, toolchain_context, sdk_deps_set, uncompiled_sdk_module_info, sdk_module_providers)

View file

@ -0,0 +1,67 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":swift_pcm_compilation_types.bzl", "SwiftPCMCompilationInfo")
def project_as_hidden(module_info: "SdkCompiledModuleInfo"):
# NOTE(cjhopman): This would probably be better done by projecting as normal args and the caller putting it in hidden.
args = cmd_args()
args.hidden(module_info.output_artifact)
return args
def project_as_clang_deps(module_info: "SdkCompiledModuleInfo"):
if module_info.is_swiftmodule:
return []
else:
return [
"-Xcc",
cmd_args(["-fmodule-file=", module_info.module_name, "=", module_info.output_artifact], delimiter = ""),
"-Xcc",
cmd_args(["-fmodule-map-file=", module_info.input_relative_path], delimiter = ""),
]
SDKDepTSet = transitive_set(args_projections = {
"clang_deps": project_as_clang_deps,
"hidden": project_as_hidden,
})
def is_sdk_modules_provided(toolchain: "SwiftToolchainInfo") -> bool.type:
no_swift_modules = toolchain.compiled_sdk_swift_modules == None or len(toolchain.compiled_sdk_swift_modules) == 0
no_clang_modules = toolchain.compiled_sdk_clang_modules == None or len(toolchain.compiled_sdk_clang_modules) == 0
if no_swift_modules and no_clang_modules:
return False
return True
def get_sdk_deps_tset(
ctx: "context",
module_name: str.type,
deps: ["dependency"],
required_modules: [str.type],
toolchain: "SwiftToolchainInfo") -> "SDKDepTSet":
if not is_sdk_modules_provided(toolchain):
fail("SDK deps are not set for swift_toolchain")
all_sdk_deps = [
d[SwiftPCMCompilationInfo].sdk_deps_set
for d in deps
if SwiftPCMCompilationInfo in d
]
# Adding all direct and transitive SDK dependencies.
for sdk_module_dep_name in ctx.attrs.sdk_modules + required_modules:
if sdk_module_dep_name not in toolchain.compiled_sdk_swift_modules and sdk_module_dep_name not in toolchain.compiled_sdk_clang_modules:
fail("{} depends on a non-existing SDK module: {}".format(module_name, sdk_module_dep_name))
sdk_compiled_module_info = toolchain.compiled_sdk_swift_modules.get(sdk_module_dep_name) or toolchain.compiled_sdk_clang_modules.get(sdk_module_dep_name)
sdk_module_with_transitive_deps_tset = ctx.actions.tset(
SDKDepTSet,
value = sdk_compiled_module_info,
children = [sdk_compiled_module_info.deps],
)
all_sdk_deps.append(sdk_module_with_transitive_deps_tset)
return ctx.actions.tset(SDKDepTSet, children = all_sdk_deps)

View file

@ -0,0 +1,118 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_utility.bzl", "expand_relative_prefixed_sdk_path", "get_disable_pch_validation_flags")
load(":swift_module_map.bzl", "write_swift_module_map")
load(":swift_toolchain_types.bzl", "SdkCompiledModuleInfo", "SdkUncompiledModuleInfo")
def compile_sdk_swiftinterface(
ctx: "context",
toolchain_context: struct.type,
sdk_deps_set: "SDKDepTSet",
uncompiled_sdk_module_info: "SdkUncompiledModuleInfo",
sdk_module_providers: {str.type: "SdkCompiledModuleInfo"}):
uncompiled_module_info_name = uncompiled_sdk_module_info.module_name
cmd = cmd_args(toolchain_context.compiler)
cmd.add(uncompiled_sdk_module_info.partial_cmd)
cmd.add(["-sdk", toolchain_context.sdk_path])
cmd.add(toolchain_context.compiler_flags)
if toolchain_context.swift_resource_dir:
cmd.add([
"-resource-dir",
toolchain_context.swift_resource_dir,
])
swift_module_map_artifact = write_swift_module_map(ctx, uncompiled_module_info_name, list(sdk_deps_set.traverse()))
cmd.add([
"-explicit-swift-module-map-file",
swift_module_map_artifact,
])
# sdk_swiftinterface_compile should explicitly depend on its deps that go to swift_modulemap
cmd.hidden(sdk_deps_set.project_as_args("hidden"))
cmd.add(sdk_deps_set.project_as_args("clang_deps"))
swiftmodule_output = ctx.actions.declare_output(uncompiled_module_info_name + ".swiftmodule")
expanded_swiftinterface_cmd = expand_relative_prefixed_sdk_path(
cmd_args(toolchain_context.sdk_path),
cmd_args(toolchain_context.swift_resource_dir),
uncompiled_sdk_module_info.input_relative_path,
)
cmd.add([
"-o",
swiftmodule_output.as_output(),
expanded_swiftinterface_cmd,
])
sdk_module_providers[uncompiled_sdk_module_info.name] = SdkCompiledModuleInfo(
name = uncompiled_sdk_module_info.name,
module_name = uncompiled_module_info_name,
is_framework = uncompiled_sdk_module_info.is_framework,
is_swiftmodule = True,
output_artifact = swiftmodule_output,
deps = sdk_deps_set,
input_relative_path = expanded_swiftinterface_cmd,
)
ctx.actions.run(cmd, category = "sdk_swiftinterface_compile", identifier = uncompiled_module_info_name)
def apple_sdk_swift_module_impl(ctx: "context") -> ["provider"]:
module_name = ctx.attrs.module_name
cmd = cmd_args([
"-frontend",
"-compile-module-from-interface",
"-disable-implicit-swift-modules",
"-serialize-parseable-module-interface-dependency-hashes",
"-disable-modules-validate-system-headers",
"-suppress-warnings",
"-module-name",
module_name,
"-target",
ctx.attrs.target,
"-Xcc",
"-fno-implicit-modules",
"-Xcc",
"-fno-implicit-module-maps",
])
cmd.add(get_disable_pch_validation_flags())
if module_name == "Swift" or module_name == "SwiftOnoneSupport":
cmd.add([
"-parse-stdlib",
])
module_dependency_infos = filter(None, [d.get(SdkUncompiledModuleInfo) for d in ctx.attrs.deps])
return [
DefaultInfo(),
SdkUncompiledModuleInfo(
name = ctx.attrs.name,
module_name = ctx.attrs.module_name,
is_framework = ctx.attrs.is_framework,
is_swiftmodule = True,
partial_cmd = cmd,
input_relative_path = ctx.attrs.swiftinterface_relative_path,
deps = module_dependency_infos,
),
]
# This rule represent a Swift module from SDK and forms a graph of dependencies between such modules.
apple_sdk_swift_module = rule(
impl = apple_sdk_swift_module_impl,
attrs = {
"deps": attrs.list(attrs.dep(), default = []),
"is_framework": attrs.bool(default = False),
# This is a real module name, contrary to `name`
# which has a special suffix to distinguish Swift and Clang modules with the same name
"module_name": attrs.string(),
# A prefixed path ($SDKROOT/$PLATFORM_DIR) to swiftinterface textual file.
"swiftinterface_relative_path": attrs.option(attrs.string(), default = None), # if `swiftinterface` is None represents a Root node.
"target": attrs.string(),
},
)

View file

@ -0,0 +1,13 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//cxx:cxx_context.bzl", "get_cxx_toolchain_info")
def apple_strip_args(ctx: "context") -> "cmd_args":
cxx_toolchain_info = get_cxx_toolchain_info(ctx)
flags = cxx_toolchain_info.strip_flags_info.strip_non_global_flags
return cmd_args(flags) if flags != None else cmd_args(["-x", "-T"])

View file

@ -0,0 +1,79 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load("@prelude//cxx:preprocessor.bzl", "CPreprocessor")
load(":apple_sdk.bzl", "get_apple_sdk_name")
# TODO(T112099448): In the future, the min version flag should live on the apple_toolchain()
# TODO(T113776898): Switch to -mtargetos= flag which should live on the apple_toolchain()
_APPLE_MIN_VERSION_FLAG_SDK_MAP = {
"iphoneos": "-mios-version-min",
"iphonesimulator": "-mios-simulator-version-min",
"macosx": "-mmacosx-version-min",
"watchos": "-mwatchos-version-min",
"watchsimulator": "-mwatchsimulator-version-min",
}
# Returns the target SDK version for apple_(binary|library) and uses
# apple_toolchain() min version as a fallback. This is the central place
# where the version for a particular node is defined, no other places
# should be accessing `attrs.target_sdk_version` or `attrs.min_version`.
def get_min_deployment_version_for_node(ctx: "context") -> [None, str.type]:
toolchain_min_version = ctx.attrs._apple_toolchain[AppleToolchainInfo].min_version
if toolchain_min_version == "":
toolchain_min_version = None
return getattr(ctx.attrs, "target_sdk_version", None) or toolchain_min_version
# Returns the min deployment flag to pass to the compiler + linker
def _get_min_deployment_version_target_flag(ctx: "context") -> [None, str.type]:
target_sdk_version = get_min_deployment_version_for_node(ctx)
if target_sdk_version == None:
return None
sdk_name = get_apple_sdk_name(ctx)
min_version_flag = _APPLE_MIN_VERSION_FLAG_SDK_MAP.get(sdk_name)
if min_version_flag == None:
fail("Could not determine min version flag for SDK {}".format(sdk_name))
return "{}={}".format(min_version_flag, target_sdk_version)
# There are two main ways in which we can pass target SDK version:
# - versioned target triple
# - unversioned target triple + version flag
#
# A versioned target triple overrides any version flags and requires
# additional flags to disable the warning/error (`-Woverriding-t-option`),
# so we prefer to use an unversioned target triple + version flag.
#
# Furthermore, we want to ensure that there's _exactly one_ version flag
# on a compiler/link line. This makes debugging easier and avoids issues
# with multiple layers each adding/overriding target SDK. It also makes
# it easier to switch to versioned target triple.
#
# There are exactly two ways in which to specify the target SDK:
# - apple_toolchain.min_version sets the default value
# - apple_(binary|library).target_sdk_version sets the per-target value
#
# apple_toolchain() rules should _never_ add any version flags because
# the rule does _not_ know whether a particular target will request a
# non-default value. Otherwise, we end up with multiple version flags,
# one added by the toolchain and then additional overrides by targets.
def get_min_deployment_version_target_linker_flags(ctx: "context") -> [str.type]:
min_version_flag = _get_min_deployment_version_target_flag(ctx)
return [min_version_flag] if min_version_flag != None else []
def get_min_deployment_version_target_preprocessor_flags(ctx: "context") -> [CPreprocessor.type]:
min_version_flag = _get_min_deployment_version_target_flag(ctx)
if min_version_flag == None:
return []
args = cmd_args(min_version_flag)
return [CPreprocessor(
args = [args],
)]

View file

@ -0,0 +1,190 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_library.bzl", "AppleLibraryAdditionalParams", "apple_library_rule_constructor_params_and_swift_providers")
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load(
"@prelude//cxx:compile.bzl",
"CxxSrcWithFlags", # @unused Used as a type
)
load("@prelude//cxx:cxx_library.bzl", "cxx_library_parameterized")
load("@prelude//cxx:cxx_types.bzl", "CxxRuleProviderParams", "CxxRuleSubTargetParams")
load(
"@prelude//cxx:linker.bzl",
"SharedLibraryFlagOverrides",
)
load(":apple_bundle.bzl", "AppleBundlePartListConstructorParams", "get_apple_bundle_part_list")
load(":apple_bundle_destination.bzl", "AppleBundleDestination")
load(":apple_bundle_part.bzl", "AppleBundlePart", "assemble_bundle", "bundle_output")
load(":apple_bundle_types.bzl", "AppleBundleInfo")
load(":xcode.bzl", "apple_populate_xcode_attributes")
def apple_test_impl(ctx: "context") -> ["provider"]:
xctest_bundle = bundle_output(ctx)
test_host_app_bundle = _get_test_host_app_bundle(ctx)
test_host_app_binary = _get_test_host_app_binary(ctx, test_host_app_bundle)
objc_bridging_header_flags = [
# Disable bridging header -> PCH compilation to mitigate an issue in Xcode 13 beta.
"-disable-bridging-pch",
"-import-objc-header",
cmd_args(ctx.attrs.bridging_header),
] if ctx.attrs.bridging_header else []
constructor_params, _, _ = apple_library_rule_constructor_params_and_swift_providers(
ctx,
AppleLibraryAdditionalParams(
rule_type = "apple_test",
extra_exported_link_flags = _get_xctest_framework_linker_flags(ctx) + _get_bundle_loader_flags(test_host_app_binary),
extra_swift_compiler_flags = _get_xctest_framework_search_paths_flags(ctx) + objc_bridging_header_flags,
shared_library_flags = SharedLibraryFlagOverrides(
# When `-bundle` is used we can't use the `-install_name` args, thus we keep this field empty.
shared_library_name_linker_flags_format = [],
# When building Apple tests, we want to link with `-bundle` instead of `-shared` to allow
# linking against the bundle loader.
shared_library_flags = ["-bundle"],
),
generate_sub_targets = CxxRuleSubTargetParams(
compilation_database = False,
headers = False,
link_group_map = False,
link_style_outputs = False,
),
generate_providers = CxxRuleProviderParams(
compilation_database = True,
default = False,
linkable_graph = False,
link_style_outputs = False,
merged_native_link_info = False,
omnibus_root = False,
preprocessors = False,
resources = False,
shared_libraries = False,
template_placeholders = False,
),
populate_xcode_attributes_func = lambda local_ctx, **kwargs: _xcode_populate_attributes(ctx = local_ctx, xctest_bundle = xctest_bundle, test_host_app_binary = test_host_app_binary, **kwargs),
# We want to statically link the transitive dep graph of the apple_test()
# which we can achieve by forcing link group linking with
# an empty mapping (i.e., default mapping).
force_link_group_linking = True,
),
)
cxx_library_output = cxx_library_parameterized(ctx, constructor_params)
binary_part = AppleBundlePart(source = cxx_library_output.default_output.default, destination = AppleBundleDestination("executables"), new_name = ctx.attrs.name)
part_list_output = get_apple_bundle_part_list(ctx, AppleBundlePartListConstructorParams(binaries = [binary_part]))
assemble_bundle(ctx, xctest_bundle, part_list_output.parts, part_list_output.info_plist_part)
sub_targets = cxx_library_output.sub_targets
# If the test has a test host, add a subtarget to build the test host app bundle.
sub_targets["test-host"] = [DefaultInfo(default_outputs = [test_host_app_bundle])] if test_host_app_bundle else [DefaultInfo()]
# When interacting with Tpx, we just pass our various inputs via env vars,
# since Tpx basiclaly wants structured output for this.
env = {"XCTEST_BUNDLE": xctest_bundle}
if test_host_app_bundle == None:
tpx_label = "tpx:apple_test:buck2:logicTest"
else:
env["HOST_APP_BUNDLE"] = test_host_app_bundle
tpx_label = "tpx:apple_test:buck2:appTest"
labels = ctx.attrs.labels + [tpx_label]
labels.append(tpx_label)
return [
DefaultInfo(default_outputs = [xctest_bundle], sub_targets = sub_targets),
ExternalRunnerTestInfo(
type = "custom", # We inherit a label via the macro layer that overrides this.
command = ["false"], # Tpx makes up its own args, we just pass params via the env.
env = env,
labels = labels,
use_project_relative_paths = True,
run_from_project_root = True,
contacts = ctx.attrs.contacts,
executor_overrides = {
"ios-simulator": CommandExecutorConfig(
local_enabled = False,
remote_enabled = True,
remote_execution_properties = {
"platform": "ios-simulator-pure-re",
"subplatform": "iPhone 8.iOS 15.0",
"xcode-version": "xcodestable",
},
remote_execution_use_case = "tpx-default",
),
"static-listing": CommandExecutorConfig(local_enabled = True, remote_enabled = False),
},
),
cxx_library_output.xcode_data_info,
cxx_library_output.cxx_compilationdb_info,
]
def _get_test_host_app_bundle(ctx: "context") -> ["artifact", None]:
""" Get the bundle for the test host app, if one exists for this test. """
if ctx.attrs.test_host_app:
# Copy the test host app bundle into test's output directory
original_bundle = ctx.attrs.test_host_app[AppleBundleInfo].bundle
test_host_app_bundle = ctx.actions.declare_output(original_bundle.basename)
ctx.actions.copy_file(test_host_app_bundle, original_bundle)
return test_host_app_bundle
return None
def _get_test_host_app_binary(ctx: "context", test_host_app_bundle: ["artifact", None]) -> ["cmd_args", None]:
""" Reference to the binary with the test host app bundle, if one exists for this test. Captures the bundle as an artifact in the cmd_args. """
if ctx.attrs.test_host_app:
return cmd_args([test_host_app_bundle, ctx.attrs.test_host_app[AppleBundleInfo].binary_name], delimiter = "/")
return None
def _get_bundle_loader_flags(binary: ["cmd_args", None]) -> [""]:
if binary:
# During linking we need to link the test shared lib against the test host binary. The
# test host binary doesn't need to be embedded in an `apple_bundle`.
return ["-bundle_loader", binary]
return []
def _xcode_populate_attributes(
ctx,
srcs: [CxxSrcWithFlags.type],
argsfiles_by_ext: {str.type: "artifact"},
xctest_bundle: "artifact",
test_host_app_binary: ["cmd_args", None],
**_kwargs) -> {str.type: ""}:
data = apple_populate_xcode_attributes(ctx = ctx, srcs = srcs, argsfiles_by_ext = argsfiles_by_ext, product_name = ctx.attrs.name)
data["output"] = xctest_bundle
if test_host_app_binary:
data["test_host_app_binary"] = test_host_app_binary
return data
def _get_xctest_framework_search_paths(ctx: "context") -> ("cmd_args", "cmd_args"):
toolchain = ctx.attrs._apple_toolchain[AppleToolchainInfo]
xctest_swiftmodule_search_path = cmd_args([toolchain.platform_path, "Developer/usr/lib"], delimiter = "/")
xctest_framework_search_path = cmd_args([toolchain.platform_path, "Developer/Library/Frameworks"], delimiter = "/")
return (xctest_swiftmodule_search_path, xctest_framework_search_path)
def _get_xctest_framework_search_paths_flags(ctx: "context") -> [["cmd_args", str.type]]:
xctest_swiftmodule_search_path, xctest_framework_search_path = _get_xctest_framework_search_paths(ctx)
return [
"-I",
xctest_swiftmodule_search_path,
"-F",
xctest_framework_search_path,
]
def _get_xctest_framework_linker_flags(ctx: "context") -> [["cmd_args", str.type]]:
xctest_swiftmodule_search_path, xctest_framework_search_path = _get_xctest_framework_search_paths(ctx)
return [
"-L",
xctest_swiftmodule_search_path,
"-F",
xctest_framework_search_path,
]

View file

@ -0,0 +1,24 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_bundle_config.bzl", "apple_bundle_config")
load(":apple_macro_layer.bzl", "apple_macro_layer_set_bool_override_attrs_from_config")
load(
":apple_rules_impl_utility.bzl",
"APPLE_LINK_LIBRARIES_LOCALLY_OVERRIDE_ATTR_NAME",
)
_APPLE_TEST_LOCAL_EXECUTION_OVERRIDES = {
APPLE_LINK_LIBRARIES_LOCALLY_OVERRIDE_ATTR_NAME: ("apple", "link_libraries_locally_override"),
}
def apple_test_macro_impl(apple_test_rule = None, **kwargs):
kwargs.update(apple_bundle_config())
kwargs.update(apple_macro_layer_set_bool_override_attrs_from_config(_APPLE_TEST_LOCAL_EXECUTION_OVERRIDES))
apple_test_rule(
**kwargs
)

View file

@ -0,0 +1,43 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load("@prelude//apple:swift_toolchain_types.bzl", "SwiftToolchainInfo")
load("@prelude//cxx:cxx_toolchain_types.bzl", "CxxPlatformInfo", "CxxToolchainInfo")
def apple_toolchain_impl(ctx: "context") -> ["provider"]:
sdk_path = ctx.attrs._internal_sdk_path or ctx.attrs.sdk_path
platform_path = ctx.attrs._internal_platform_path or ctx.attrs.platform_path
return [
DefaultInfo(),
AppleToolchainInfo(
actool = ctx.attrs.actool[RunInfo],
ibtool = ctx.attrs.ibtool[RunInfo],
dsymutil = ctx.attrs.dsymutil[RunInfo],
dwarfdump = ctx.attrs.dwarfdump[RunInfo] if ctx.attrs.dwarfdump else None,
lipo = ctx.attrs.lipo[RunInfo],
cxx_platform_info = ctx.attrs.cxx_toolchain[CxxPlatformInfo],
cxx_toolchain_info = ctx.attrs.cxx_toolchain[CxxToolchainInfo],
codesign = ctx.attrs.codesign[RunInfo],
codesign_allocate = ctx.attrs.codesign_allocate[RunInfo],
codesign_identities_command = ctx.attrs.codesign_identities_command[RunInfo] if ctx.attrs.codesign_identities_command else None,
compile_resources_locally = ctx.attrs.compile_resources_locally,
libtool = ctx.attrs.libtool[RunInfo],
momc = ctx.attrs.momc[RunInfo],
min_version = ctx.attrs.min_version,
xctest = ctx.attrs.xctest[RunInfo],
platform_path = platform_path,
sdk_name = ctx.attrs.sdk_name,
sdk_path = sdk_path,
sdk_version = ctx.attrs.version,
sdk_build_version = ctx.attrs.build_version,
swift_toolchain_info = ctx.attrs.swift_toolchain[SwiftToolchainInfo] if ctx.attrs.swift_toolchain else None,
watch_kit_stub_binary = ctx.attrs.watch_kit_stub_binary,
xcode_version = ctx.attrs.xcode_version,
xcode_build_version = ctx.attrs.xcode_build_version,
),
]

View file

@ -0,0 +1,43 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
AppleToolchainInfo = provider(fields = [
"actool", # "RunInfo"
"ibtool", # "RunInfo"
"dsymutil", # "RunInfo"
"dwarfdump", # ["RunInfo", None]
"lipo", # "RunInfo"
"cxx_platform_info", # "CxxPlatformInfo"
"cxx_toolchain_info", # "CxxToolchainInfo"
"codesign", # "RunInfo"
"codesign_allocate", # "RunInfo"
"codesign_identities_command", # ["RunInfo", None]
"compile_resources_locally", # bool.type
"libtool", # "RunInfo"
"momc", # "RunInfo"
"min_version", # [None, str.type]
"xctest", # "RunInfo"
"platform_path", # [str.type, artifact]
# SDK name to be passed to tools (e.g. actool), equivalent to ApplePlatform::getExternalName() in v1.
"sdk_name", # str.type
"sdk_path", # [str.type, artifact]
# TODO(T124581557) Make it non-optional once there is no "selected xcode" toolchain
"sdk_version", # [None, str.type]
"sdk_build_version", # "[None, str.type]"
"swift_toolchain_info", # "SwiftToolchainInfo"
"watch_kit_stub_binary", # "artifact"
"xcode_version", # "[None, str.type]"
"xcode_build_version", # "[None, str.type]"
])
AppleToolsInfo = provider(fields = [
"assemble_bundle", # RunInfo
"info_plist_processor", # RunInfo
"make_modulemap", # "RunInfo"
"make_vfsoverlay", # "RunInfo"
"swift_objc_header_postprocess", # "RunInfo"
])

View file

@ -0,0 +1,82 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load("@prelude//cxx:headers.bzl", "CxxHeadersLayout", "CxxHeadersNaming")
load("@prelude//utils:utils.bzl", "value_or")
load(":apple_target_sdk_version.bzl", "get_min_deployment_version_for_node")
_VERSION_PLACEHOLDER = "(VERSION)"
# TODO(T115177501): Make target triples part of the toolchains
# Map from SDK name -> target triple _without_ leading architecture
_TARGET_TRIPLE_MAP = {
"iphoneos": "apple-ios{}".format(_VERSION_PLACEHOLDER),
"iphonesimulator": "apple-ios{}-simulator".format(_VERSION_PLACEHOLDER),
"macosx": "apple-macosx{}".format(_VERSION_PLACEHOLDER),
"watchos": "apple-watchos{}".format(_VERSION_PLACEHOLDER),
"watchsimulator": "apple-watchos{}-simulator".format(_VERSION_PLACEHOLDER),
}
def get_apple_cxx_headers_layout(ctx: "context") -> CxxHeadersLayout.type:
namespace = value_or(ctx.attrs.header_path_prefix, ctx.attrs.name)
return CxxHeadersLayout(namespace = namespace, naming = CxxHeadersNaming("apple"))
def get_module_name(ctx: "context") -> str.type:
return ctx.attrs.module_name or ctx.attrs.header_path_prefix or ctx.attrs.name
def has_apple_toolchain(ctx: "context") -> bool.type:
return hasattr(ctx.attrs, "_apple_toolchain")
def get_versioned_target_triple(ctx: "context") -> str.type:
apple_toolchain_info = ctx.attrs._apple_toolchain[AppleToolchainInfo]
swift_toolchain_info = apple_toolchain_info.swift_toolchain_info
architecture = swift_toolchain_info.architecture
if architecture == None:
fail("Need to set `architecture` field of swift_toolchain(), target: {}".format(ctx.label))
target_sdk_version = get_min_deployment_version_for_node(ctx) or ""
sdk_name = apple_toolchain_info.sdk_name
target_triple_with_version_placeholder = _TARGET_TRIPLE_MAP.get(sdk_name)
if target_triple_with_version_placeholder == None:
fail("Could not find target triple for sdk = {}".format(sdk_name))
versioned_target_triple = target_triple_with_version_placeholder.replace(_VERSION_PLACEHOLDER, target_sdk_version)
return "{}-{}".format(architecture, versioned_target_triple)
def expand_relative_prefixed_sdk_path(
sdk_path: "cmd_args",
swift_resource_dir: "cmd_args",
path_to_expand: str.type) -> "cmd_args":
path_expansion_map = {
"$RESOURCEDIR": swift_resource_dir,
"$SDKROOT": sdk_path,
}
expanded_cmd = cmd_args()
for (path_variable, path_value) in path_expansion_map.items():
if path_to_expand.startswith(path_variable):
path = path_to_expand[len(path_variable):]
if path.find("$") == 0:
fail("Failed to expand framework path: {}".format(path))
expanded_cmd.add(cmd_args([path_value, path], delimiter = ""))
return expanded_cmd
def get_disable_pch_validation_flags() -> [str.type]:
"""
We need to disable PCH validation for some actions like Swift compilation and Swift PCM generation.
Currently, we don't have a mechanism to compile with enabled pch validation and Swift explicit modules,
which we need to be able to do while we are waiting for Anonymous targets which will allow us to solve this problem properly.
"""
return [
"-Xcc",
"-Xclang",
"-Xcc",
"-fno-validate-pch",
]

View file

@ -0,0 +1,88 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolsInfo")
load(
"@prelude//cxx:headers.bzl",
"CHeader", # @unused Used as a type
)
load(
"@prelude//cxx:preprocessor.bzl",
"CPreprocessor",
)
load(":apple_utility.bzl", "get_module_name")
def preprocessor_info_for_modulemap(ctx: "context", name: str.type, headers: [CHeader.type], swift_header: ["artifact", None]) -> "CPreprocessor":
# We don't want to name this module.modulemap to avoid implicit importing
if name == "module":
fail("Don't use the name `module` for modulemaps, this will allow for implicit importing.")
module_name = get_module_name(ctx)
# Create a map of header import path to artifact location
header_map = {}
for h in headers:
if h.namespace:
header_map["{}/{}".format(h.namespace, h.name)] = h.artifact
else:
header_map[h.name] = h.artifact
# We need to include the Swift header in the symlink tree too
swift_header_name = "{}/{}-Swift.h".format(module_name, module_name)
if swift_header:
header_map[swift_header_name] = swift_header
# Create a symlink dir for the headers to import
symlink_tree = ctx.actions.symlinked_dir(name + "_symlink_tree", header_map)
# Create a modulemap at the root of that tree
output = ctx.actions.declare_output(name + ".modulemap")
cmd = cmd_args(ctx.attrs._apple_tools[AppleToolsInfo].make_modulemap)
cmd.add([
"--output",
output.as_output(),
"--name",
get_module_name(ctx),
"--symlink-tree",
symlink_tree,
])
if swift_header:
cmd.add([
"--swift-header",
swift_header,
])
if ctx.attrs.use_submodules:
cmd.add("--use-submodules")
for hdr in sorted(header_map.keys()):
# Don't include the Swift header in the mappings, this is handled separately.
if hdr != swift_header_name:
cmd.add(hdr)
ctx.actions.run(cmd, category = "modulemap", identifier = name)
return CPreprocessor(
modular_args = _args_for_modulemap(output, symlink_tree, swift_header),
modulemap_path = cmd_args(output).hidden(cmd_args(symlink_tree)),
args = _exported_preprocessor_args(symlink_tree),
)
def _args_for_modulemap(
modulemap: "artifact",
symlink_tree: "artifact",
swift_header: ["artifact", None]) -> ["cmd_args"]:
cmd = cmd_args(modulemap, format = "-fmodule-map-file={}")
cmd.hidden(symlink_tree)
if swift_header:
cmd.hidden(swift_header)
return [cmd]
def _exported_preprocessor_args(symlink_tree: "artifact") -> ["cmd_args"]:
return [cmd_args(symlink_tree, format = "-I{}")]

View file

@ -0,0 +1,105 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(
"@prelude//cxx:cxx_library_utility.bzl",
"cxx_attr_exported_linker_flags",
"cxx_platform_supported",
)
load(
"@prelude//cxx:preprocessor.bzl",
"CPreprocessor",
"cxx_inherited_preprocessor_infos",
"cxx_merge_cpreprocessors",
)
load(
"@prelude//linking:link_groups.bzl",
"merge_link_group_lib_info",
)
load(
"@prelude//linking:link_info.bzl",
"LinkInfo",
"LinkInfos",
"LinkStyle",
"Linkage",
"create_merged_link_info",
)
load(
"@prelude//linking:linkable_graph.bzl",
"create_linkable_graph",
"create_linkable_graph_node",
"create_linkable_node",
)
load(
"@prelude//linking:shared_libraries.bzl",
"SharedLibraryInfo",
"merge_shared_libraries",
)
load("@prelude//utils:utils.bzl", "filter_and_map_idx")
load(":apple_bundle_types.bzl", "AppleBundleInfo")
load(":apple_frameworks.bzl", "to_framework_name")
def prebuilt_apple_framework_impl(ctx: "context") -> ["provider"]:
providers = []
framework_directory_artifact = ctx.attrs.framework
# Check this rule's `supported_platforms_regex` with the current platform.
if cxx_platform_supported(ctx):
# Sandbox the framework, to avoid leaking other frameworks via search paths.
framework_name = to_framework_name(framework_directory_artifact.basename)
framework_dir = ctx.actions.symlinked_dir(
"Frameworks",
{framework_name + ".framework": framework_directory_artifact},
)
# Add framework & pp info from deps.
inherited_pp_info = cxx_inherited_preprocessor_infos(ctx.attrs.deps)
providers.append(cxx_merge_cpreprocessors(
ctx,
[CPreprocessor(args = ["-F", framework_dir])],
inherited_pp_info,
))
# Add framework to link args.
# TODO(T110378120): Support shared linking for mac targets:
# https://fburl.com/code/pqrtt1qr.
args = []
args.extend(cxx_attr_exported_linker_flags(ctx))
args.extend(["-F", framework_dir])
args.extend(["-framework", framework_name])
link = LinkInfo(
name = framework_name,
pre_flags = args,
)
providers.append(create_merged_link_info(
ctx,
{link_style: LinkInfos(default = link) for link_style in LinkStyle},
))
# Create, augment and provide the linkable graph.
linkable_graph = create_linkable_graph(
ctx,
node = create_linkable_graph_node(
ctx,
linkable_node = create_linkable_node(
ctx,
preferred_linkage = Linkage("shared"),
link_infos = {LinkStyle("shared"): LinkInfos(default = link)},
),
excluded = {ctx.label: None},
),
)
providers.append(linkable_graph)
# The default output is the provided framework.
providers.append(DefaultInfo(default_outputs = [framework_directory_artifact]))
providers.append(AppleBundleInfo(bundle = framework_directory_artifact, is_watchos = None))
providers.append(merge_link_group_lib_info(deps = ctx.attrs.deps))
providers.append(merge_shared_libraries(ctx.actions, deps = filter_and_map_idx(SharedLibraryInfo, ctx.attrs.deps)))
return providers

View file

@ -0,0 +1,148 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(
"@prelude//cxx:groups.bzl",
"MATCH_ALL_LABEL",
)
load(
"@prelude//utils:graph_utils.bzl",
"breadth_first_traversal_by",
)
load(":apple_asset_catalog_types.bzl", "AppleAssetCatalogSpec")
load(":apple_core_data_types.bzl", "AppleCoreDataSpec")
load(":apple_resource_types.bzl", "AppleResourceSpec")
ResourceGroupInfo = provider(fields = [
"groups", # [Group.type]
"groups_hash", # str.type
"mappings", # {"label": str.type}
])
ResourceGraphNode = record(
label = field("label"),
# Attribute labels on the target.
labels = field([str.type], []),
# Deps of this target which might have resources transitively.
deps = field(["label"], []),
# Exported deps of this target which might have resources transitively.
exported_deps = field(["label"], []),
# Actual resource data, present when node corresponds to `apple_resource` target.
resource_spec = field([AppleResourceSpec.type, None], None),
# Actual asset catalog data, present when node corresponds to `apple_asset_catalog` target.
asset_catalog_spec = field([AppleAssetCatalogSpec.type, None], None),
# Actual core data, present when node corresponds to `core_data_model` target
core_data_spec = field([AppleCoreDataSpec.type, None], None),
)
ResourceGraphTSet = transitive_set()
ResourceGraph = provider(fields = [
"label", # "label"
"nodes", # "ResourceGraphTSet"
])
def create_resource_graph(
ctx: "context",
labels: [str.type],
deps: ["dependency"],
exported_deps: ["dependency"],
resource_spec: [AppleResourceSpec.type, None] = None,
asset_catalog_spec: [AppleAssetCatalogSpec.type, None] = None,
core_data_spec: [AppleCoreDataSpec.type, None] = None) -> ResourceGraph.type:
node = ResourceGraphNode(
label = ctx.label,
labels = labels,
deps = _with_resources_deps(deps),
exported_deps = _with_resources_deps(exported_deps),
resource_spec = resource_spec,
asset_catalog_spec = asset_catalog_spec,
core_data_spec = core_data_spec,
)
all_deps = deps + exported_deps
child_nodes = filter(None, [d.get(ResourceGraph) for d in all_deps])
return ResourceGraph(
label = ctx.label,
nodes = ctx.actions.tset(ResourceGraphTSet, value = node, children = [child_node.nodes for child_node in child_nodes]),
)
def get_resource_graph_node_map_func(graph: ResourceGraph.type):
def get_resource_graph_node_map() -> {"label": ResourceGraphNode.type}:
nodes = graph.nodes.traverse()
return {node.label: node for node in filter(None, nodes)}
return get_resource_graph_node_map
def _with_resources_deps(deps: ["dependency"]) -> ["label"]:
"""
Filters dependencies and returns only those which are relevant
to working with resources i.e. those which contains resource graph provider.
"""
graphs = filter(None, [d.get(ResourceGraph) for d in deps])
return [g.label for g in graphs]
def get_resource_group_info(ctx: "context") -> [ResourceGroupInfo.type, None]:
"""
Parses the currently analyzed context for any resource group definitions
and returns a list of all resource groups with their mappings.
"""
resource_group_map = ctx.attrs.resource_group_map
if not resource_group_map:
return None
if type(resource_group_map) == "dependency":
return resource_group_map[ResourceGroupInfo]
fail("Resource group maps must be provided as a resource_group_map rule dependency.")
def get_filtered_resources(
root: "label",
resource_graph_node_map_func,
resource_group: [str.type, None],
resource_group_mappings: [{"label": str.type}, None]) -> ([AppleResourceSpec.type], [AppleAssetCatalogSpec.type], [AppleCoreDataSpec.type]):
"""
Walks the provided DAG and collects resources matching resource groups definition.
"""
resource_graph_node_map = resource_graph_node_map_func()
def get_traversed_deps(target: "label") -> ["label"]:
node = resource_graph_node_map[target] # buildifier: disable=uninitialized
return node.exported_deps + node.deps
targets = breadth_first_traversal_by(
resource_graph_node_map,
get_traversed_deps(root),
get_traversed_deps,
)
resource_specs = []
asset_catalog_specs = []
core_data_specs = []
for target in targets:
target_resource_group = resource_group_mappings.get(target)
# Ungrouped targets belong to the unlabeled bundle
if ((not target_resource_group and not resource_group) or
# Does it match special "MATCH_ALL" mapping?
target_resource_group == MATCH_ALL_LABEL or
# Does it match currently evaluated group?
target_resource_group == resource_group):
node = resource_graph_node_map[target]
resource_spec = node.resource_spec
if resource_spec:
resource_specs.append(resource_spec)
asset_catalog_spec = node.asset_catalog_spec
if asset_catalog_spec:
asset_catalog_specs.append(asset_catalog_spec)
core_data_spec = node.core_data_spec
if core_data_spec:
core_data_specs.append(core_data_spec)
return resource_specs, asset_catalog_specs, core_data_specs

View file

@ -0,0 +1,474 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolsInfo")
load(
"@prelude//cxx:compile.bzl",
"CxxSrcWithFlags", # @unused Used as a type
)
load("@prelude//cxx:cxx_types.bzl", "CxxAdditionalArgsfileParams")
load("@prelude//cxx:headers.bzl", "CHeader")
load(
"@prelude//cxx:preprocessor.bzl",
"CPreprocessor",
"cxx_inherited_preprocessor_infos",
"cxx_merge_cpreprocessors",
)
load(":apple_sdk_modules_utility.bzl", "get_sdk_deps_tset", "is_sdk_modules_provided")
load(":apple_toolchain_types.bzl", "AppleToolchainInfo")
load(":apple_utility.bzl", "get_disable_pch_validation_flags", "get_module_name", "get_versioned_target_triple")
load(":modulemap.bzl", "preprocessor_info_for_modulemap")
load(":swift_module_map.bzl", "write_swift_module_map_with_swift_deps")
load(":swift_pcm_compilation.bzl", "compile_swift_pcm", "get_pcm_deps_tset")
def _add_swiftmodule_search_path(swiftmodule_path: "artifact"):
# Value will contain a path to the artifact,
# while we need only the folder which contains the artifact.
return ["-I", cmd_args(swiftmodule_path).parent()]
def _hidden_projection(swiftmodule_path: "artifact"):
return swiftmodule_path
def _linker_args_projection(swiftmodule_path: "artifact"):
return cmd_args(swiftmodule_path, format = "-Wl,-add_ast_path,{}")
SwiftmodulePathsTSet = transitive_set(args_projections = {
"hidden": _hidden_projection,
"linker_args": _linker_args_projection,
"module_search_path": _add_swiftmodule_search_path,
})
ExportedHeadersTSet = transitive_set()
SwiftDependencyInfo = provider(fields = [
"exported_headers", # ExportedHeadersTSet of {"module_name": [exported_headers]}
"exported_swiftmodule_paths", # SwiftmodulePathsTSet of artifact that includes only paths through exported_deps, used for compilation
"transitive_swiftmodule_paths", # SwiftmodulePathsTSet of artifact that includes all transitive paths, used for linking
])
SwiftCompilationOutput = record(
# The object files output from compilation.
object_files = field(["artifact"]),
# The swiftmodule file output from compilation.
swiftmodule = field("artifact"),
# The dependency info provider that provides the swiftmodule
# search paths required for compilation.
providers = field([["SwiftPCMCompilationInfo", "SwiftDependencyInfo"]]),
# Preprocessor info required for ObjC compilation of this library.
pre = field(CPreprocessor.type),
# Exported preprocessor info required for ObjC compilation of rdeps.
exported_pre = field(CPreprocessor.type),
# Argsfile to compile an object file which is used by some subtargets.
swift_argsfile = field("CxxAdditionalArgsfileParams"),
)
_REQUIRED_SDK_MODULES = ["Swift", "SwiftOnoneSupport", "Darwin", "_Concurrency"]
def compile_swift(
ctx: "context",
srcs: [CxxSrcWithFlags.type],
exported_headers: [CHeader.type],
objc_modulemap_pp_info: ["CPreprocessor", None],
extra_search_paths_flags: ["_arglike"] = []) -> ["SwiftCompilationOutput", None]:
if not srcs:
return None
toolchain = ctx.attrs._apple_toolchain[AppleToolchainInfo].swift_toolchain_info
module_name = get_module_name(ctx)
output_header = ctx.actions.declare_output(module_name + "-Swift.h")
output_object = ctx.actions.declare_output(module_name + ".o")
output_swiftmodule = ctx.actions.declare_output(module_name + ".swiftmodule")
shared_flags = _get_shared_flags(
ctx,
module_name,
exported_headers,
objc_modulemap_pp_info,
extra_search_paths_flags,
)
if toolchain.can_toolchain_emit_obj_c_header_textually:
_compile_swiftmodule(ctx, toolchain, shared_flags, srcs, output_swiftmodule, output_header)
else:
unprocessed_header = ctx.actions.declare_output(module_name + "-SwiftUnprocessed.h")
_compile_swiftmodule(ctx, toolchain, shared_flags, srcs, output_swiftmodule, unprocessed_header)
_perform_swift_postprocessing(ctx, module_name, unprocessed_header, output_header)
swift_argsfile = _compile_object(ctx, toolchain, shared_flags, srcs, output_object)
# Swift libraries extend the ObjC modulemaps to include the -Swift.h header
modulemap_pp_info = preprocessor_info_for_modulemap(ctx, "swift-extended", exported_headers, output_header)
exported_swift_header = CHeader(
artifact = output_header,
name = output_header.basename,
namespace = module_name,
named = False,
)
exported_pp_info = CPreprocessor(
headers = [exported_swift_header],
modular_args = modulemap_pp_info.modular_args,
args = modulemap_pp_info.args,
modulemap_path = modulemap_pp_info.modulemap_path,
)
# We also need to include the unprefixed -Swift.h header in this libraries preprocessor info
swift_header = CHeader(
artifact = output_header,
name = output_header.basename,
namespace = "",
named = False,
)
pre = CPreprocessor(headers = [swift_header])
# Pass up the swiftmodule paths for this module and its exported_deps
return SwiftCompilationOutput(
object_files = [output_object],
swiftmodule = output_swiftmodule,
providers = [get_swift_dependency_info(ctx, exported_pp_info, output_swiftmodule)],
pre = pre,
exported_pre = exported_pp_info,
swift_argsfile = swift_argsfile,
)
# Swift headers are postprocessed to make them compatible with Objective-C
# compilation that does not use -fmodules. This is a workaround for the bad
# performance of -fmodules without Explicit Modules, once Explicit Modules is
# supported, this postprocessing should be removed.
def _perform_swift_postprocessing(
ctx: "context",
module_name: "string",
unprocessed_header: "artifact",
output_header: "artifact"):
transitive_exported_headers = {
module: module_exported_headers
for exported_headers_map in _get_exported_headers_tset(ctx).traverse()
if exported_headers_map
for module, module_exported_headers in exported_headers_map.items()
}
deps_json = ctx.actions.write_json(module_name + "-Deps.json", transitive_exported_headers)
postprocess_cmd = cmd_args(ctx.attrs._apple_tools[AppleToolsInfo].swift_objc_header_postprocess)
postprocess_cmd.add([
unprocessed_header,
deps_json,
output_header.as_output(),
])
ctx.actions.run(postprocess_cmd, category = "swift_objc_header_postprocess")
# We use separate actions for swiftmodule and object file output. This
# improves build parallelism at the cost of duplicated work, but by disabling
# type checking in function bodies the swiftmodule compilation can be done much
# faster than object file output.
def _compile_swiftmodule(
ctx: "context",
toolchain: "SwiftToolchainInfo",
shared_flags: "cmd_args",
srcs: [CxxSrcWithFlags.type],
output_swiftmodule: "artifact",
output_header: "artifact") -> "CxxAdditionalArgsfileParams":
argfile_cmd = cmd_args(shared_flags)
argfile_cmd.add([
"-Xfrontend",
"-experimental-skip-non-inlinable-function-bodies-without-types",
"-emit-module",
"-emit-objc-header",
])
cmd = cmd_args([
"-emit-module-path",
output_swiftmodule.as_output(),
"-emit-objc-header-path",
output_header.as_output(),
])
return _compile_with_argsfile(ctx, "swiftmodule_compile", argfile_cmd, srcs, cmd, toolchain)
def _compile_object(
ctx: "context",
toolchain: "SwiftToolchainInfo",
shared_flags: "cmd_args",
srcs: [CxxSrcWithFlags.type],
output_object: "artifact") -> "CxxAdditionalArgsfileParams":
cmd = cmd_args([
"-emit-object",
"-o",
output_object.as_output(),
])
return _compile_with_argsfile(ctx, "swift_compile", shared_flags, srcs, cmd, toolchain)
def _compile_with_argsfile(
ctx: "context",
name: str.type,
shared_flags: "cmd_args",
srcs: [CxxSrcWithFlags.type],
additional_flags: "cmd_args",
toolchain: "SwiftToolchainInfo") -> "CxxAdditionalArgsfileParams":
shell_quoted_args = cmd_args(shared_flags, quote = "shell")
argfile, _ = ctx.actions.write(name + ".argsfile", shell_quoted_args, allow_args = True)
cmd = cmd_args(toolchain.compiler)
cmd.add(additional_flags)
cmd.add(cmd_args(["@", argfile], delimiter = ""))
cmd.add([s.file for s in srcs])
# Swift compilation on RE without explicit modules is impractically expensive
# because there's no shared module cache across different libraries.
prefer_local = not _uses_explicit_modules(ctx)
# Argsfile should also depend on all artifacts in it, otherwise they won't be materialised.
cmd.hidden([shell_quoted_args])
# If we prefer to execute locally (e.g., for perf reasons), ensure we upload to the cache,
# so that CI builds populate caches used by developer machines.
ctx.actions.run(cmd, category = name, prefer_local = prefer_local, allow_cache_upload = prefer_local)
hidden_args = [shared_flags]
return CxxAdditionalArgsfileParams(file = argfile, hidden_args = hidden_args, extension = ".swift")
def _get_shared_flags(
ctx: "context",
module_name: str.type,
objc_headers: [CHeader.type],
objc_modulemap_pp_info: ["CPreprocessor", None],
extra_search_paths_flags: ["_arglike"] = []) -> "cmd_args":
toolchain = ctx.attrs._apple_toolchain[AppleToolchainInfo].swift_toolchain_info
cmd = cmd_args()
cmd.add([
# This allows us to use a relative path for the compiler resource directory.
"-working-directory",
".",
"-sdk",
toolchain.sdk_path,
"-target",
get_versioned_target_triple(ctx),
"-wmo",
"-module-name",
module_name,
"-parse-as-library",
# Disable Clang module breadcrumbs in the DWARF info. These will not be
# debug prefix mapped and are not shareable across machines.
"-Xfrontend",
"-no-clang-module-breadcrumbs",
])
if _uses_explicit_modules(ctx):
cmd.add(get_disable_pch_validation_flags())
if toolchain.resource_dir:
cmd.add([
"-resource-dir",
toolchain.resource_dir,
])
if ctx.attrs.swift_version:
cmd.add(["-swift-version", ctx.attrs.swift_version])
if ctx.attrs.enable_cxx_interop:
cmd.add(["-enable-experimental-cxx-interop"])
serialize_debugging_options = False
if ctx.attrs.serialize_debugging_options:
if objc_headers:
# TODO(T99100029): We cannot use VFS overlays with Buck2, so we have to disable
# serializing debugging options for mixed libraries to debug successfully
warning("Mixed libraries cannot serialize debugging options, disabling for module `{}` in rule `{}`".format(module_name, ctx.label))
elif not toolchain.prefix_serialized_debugging_options:
warning("The current toolchain does not support prefixing serialized debugging options, disabling for module `{}` in rule `{}`".format(module_name, ctx.label))
else:
# Apply the debug prefix map to Swift serialized debugging info.
# This will allow for debugging remotely built swiftmodule files.
serialize_debugging_options = True
if serialize_debugging_options:
cmd.add([
"-Xfrontend",
"-serialize-debugging-options",
"-Xfrontend",
"-prefix-serialized-debugging-options",
])
else:
cmd.add([
"-Xfrontend",
"-no-serialize-debugging-options",
])
if toolchain.can_toolchain_emit_obj_c_header_textually:
cmd.add([
"-Xfrontend",
"-emit-objc-header-textually",
])
# Add flags required to import ObjC module dependencies
_add_clang_deps_flags(ctx, cmd)
_add_swift_deps_flags(ctx, cmd)
# Add flags for importing the ObjC part of this library
_add_mixed_library_flags_to_cmd(cmd, objc_headers, objc_modulemap_pp_info)
# Add toolchain and target flags last to allow for overriding defaults
cmd.add(toolchain.compiler_flags)
cmd.add(ctx.attrs.swift_compiler_flags)
cmd.add(extra_search_paths_flags)
return cmd
def _add_swift_deps_flags(ctx: "context", cmd: "cmd_args"):
# If Explicit Modules are enabled, a few things must be provided to a compilation job:
# 1. Direct and transitive SDK deps from `sdk_modules` attribute.
# 2. Direct and transitive user-defined deps.
# 3. Transitive SDK deps of user-defined deps.
# (This is the case, when a user-defined dep exports a type from SDK module,
# thus such SDK module should be implicitly visible to consumers of that custom dep)
if _uses_explicit_modules(ctx):
toolchain = ctx.attrs._apple_toolchain[AppleToolchainInfo].swift_toolchain_info
module_name = get_module_name(ctx)
sdk_deps_tset = get_sdk_deps_tset(
ctx,
module_name,
ctx.attrs.deps + ctx.attrs.exported_deps,
_REQUIRED_SDK_MODULES,
toolchain,
)
swift_deps_tset = ctx.actions.tset(
SwiftmodulePathsTSet,
children = _get_swift_paths_tsets(ctx.attrs.deps + ctx.attrs.exported_deps),
)
swift_module_map_artifact = write_swift_module_map_with_swift_deps(
ctx,
module_name,
list(sdk_deps_tset.traverse()),
list(swift_deps_tset.traverse()),
)
cmd.add([
"-Xfrontend",
"-disable-implicit-swift-modules",
"-Xfrontend",
"-explicit-swift-module-map-file",
"-Xfrontend",
swift_module_map_artifact,
])
# Add Clang sdk modules which do not go to swift modulemap
cmd.add(sdk_deps_tset.project_as_args("clang_deps"))
# Swift compilation should depend on transitive Swift modules from swift-module-map.
cmd.hidden(sdk_deps_tset.project_as_args("hidden"))
cmd.hidden(swift_deps_tset.project_as_args("hidden"))
else:
depset = ctx.actions.tset(SwiftmodulePathsTSet, children = _get_swift_paths_tsets(ctx.attrs.deps + ctx.attrs.exported_deps))
cmd.add(depset.project_as_args("module_search_path"))
def _add_clang_deps_flags(ctx: "context", cmd: "cmd_args") -> None:
# If a module uses Explicit Modules, all direct and
# transitive Clang deps have to be explicitly added.
if _uses_explicit_modules(ctx):
pcm_deps_tset = get_pcm_deps_tset(ctx, ctx.attrs.deps + ctx.attrs.exported_deps)
cmd.add(pcm_deps_tset.project_as_args("clang_deps"))
else:
inherited_preprocessor_infos = cxx_inherited_preprocessor_infos(ctx.attrs.deps + ctx.attrs.exported_deps)
preprocessors = cxx_merge_cpreprocessors(ctx, [], inherited_preprocessor_infos)
cmd.add(cmd_args(preprocessors.set.project_as_args("args"), prepend = "-Xcc"))
cmd.add(cmd_args(preprocessors.set.project_as_args("modular_args"), prepend = "-Xcc"))
cmd.add(cmd_args(preprocessors.set.project_as_args("include_dirs"), prepend = "-Xcc"))
def _add_mixed_library_flags_to_cmd(
cmd: "cmd_args",
objc_headers: [CHeader.type],
objc_modulemap_pp_info: ["CPreprocessor", None]) -> None:
if not objc_headers:
return
# TODO(T99100029): We cannot use VFS overlays to mask this import from
# the debugger as they require absolute paths. Instead we will enforce
# that mixed libraries do not have serialized debugging info and rely on
# rdeps to serialize the correct paths.
for arg in objc_modulemap_pp_info.args:
cmd.add("-Xcc")
cmd.add(arg)
for arg in objc_modulemap_pp_info.modular_args:
cmd.add("-Xcc")
cmd.add(arg)
cmd.add("-import-underlying-module")
def _get_swift_paths_tsets(deps: ["dependency"]) -> ["SwiftmodulePathsTSet"]:
return [
d[SwiftDependencyInfo].exported_swiftmodule_paths
for d in deps
if SwiftDependencyInfo in d
]
def _get_transitive_swift_paths_tsets(deps: ["dependency"]) -> ["SwiftmodulePathsTSet"]:
return [
d[SwiftDependencyInfo].transitive_swiftmodule_paths
for d in deps
if SwiftDependencyInfo in d
]
def _get_exported_headers_tset(ctx: "context", exported_headers: [["string"], None] = None) -> "ExportedHeadersTSet":
return ctx.actions.tset(
ExportedHeadersTSet,
value = {get_module_name(ctx): exported_headers} if exported_headers else None,
children = [
dep.exported_headers
for dep in [x.get(SwiftDependencyInfo) for x in ctx.attrs.exported_deps]
if dep and dep.exported_headers
],
)
def get_swift_pcm_compile_info(
ctx: "context",
propagated_exported_preprocessor_info: ["CPreprocessorInfo", None],
exported_pre: ["CPreprocessor", None]) -> ["SwiftPCMCompilationInfo", None]:
swift_toolchain = ctx.attrs._apple_toolchain[AppleToolchainInfo].swift_toolchain_info
# If a toolchain supports explicit modules, exported PP exists and a target is modular,
# let's precompile a modulemap in order to enable consumptions by Swift.
if is_sdk_modules_provided(swift_toolchain) and exported_pre and exported_pre.modulemap_path and ctx.attrs.modular:
return compile_swift_pcm(
ctx,
exported_pre,
propagated_exported_preprocessor_info,
)
return None
def get_swift_dependency_info(
ctx: "context",
exported_pre: ["CPreprocessor", None],
output_module: ["artifact", None]) -> "SwiftDependencyInfo":
all_deps = ctx.attrs.exported_deps + ctx.attrs.deps
if ctx.attrs.reexport_all_header_dependencies:
exported_deps = all_deps
else:
exported_deps = ctx.attrs.exported_deps
exported_headers = [_header_basename(header) for header in ctx.attrs.exported_headers]
exported_headers += [header.name for header in exported_pre.headers] if exported_pre else []
if output_module:
exported_swiftmodules = ctx.actions.tset(SwiftmodulePathsTSet, value = output_module, children = _get_swift_paths_tsets(exported_deps))
transitive_swiftmodules = ctx.actions.tset(SwiftmodulePathsTSet, value = output_module, children = _get_transitive_swift_paths_tsets(all_deps))
else:
exported_swiftmodules = ctx.actions.tset(SwiftmodulePathsTSet, children = _get_swift_paths_tsets(exported_deps))
transitive_swiftmodules = ctx.actions.tset(SwiftmodulePathsTSet, children = _get_transitive_swift_paths_tsets(all_deps))
return SwiftDependencyInfo(
exported_headers = _get_exported_headers_tset(ctx, exported_headers),
exported_swiftmodule_paths = exported_swiftmodules,
transitive_swiftmodule_paths = transitive_swiftmodules,
)
def _header_basename(header: ["artifact", "string"]) -> "string":
if type(header) == type(""):
return paths.basename(header)
else:
return header.basename
def _uses_explicit_modules(ctx: "context") -> bool.type:
swift_toolchain = ctx.attrs._apple_toolchain[AppleToolchainInfo].swift_toolchain_info
return ctx.attrs.uses_explicit_modules and is_sdk_modules_provided(swift_toolchain)

View file

@ -0,0 +1,40 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
def write_swift_module_map(
ctx: "context",
module_name: str.type,
sdk_deps: ["SdkCompiledModuleInfo"]) -> "artifact":
return write_swift_module_map_with_swift_deps(ctx, module_name, sdk_deps, [])
def write_swift_module_map_with_swift_deps(
ctx: "context",
module_name: str.type,
sdk_swift_deps: ["SdkCompiledModuleInfo"],
swift_deps: ["artifact"]) -> "artifact":
deps = {}
for sdk_dep in sdk_swift_deps:
if sdk_dep.is_swiftmodule:
deps[sdk_dep.module_name] = {
"isFramework": sdk_dep.is_framework,
"moduleName": sdk_dep.module_name,
"modulePath": sdk_dep.output_artifact,
}
for swift_dep in swift_deps:
# The swiftmodule filename always matches the module name
name = swift_dep.basename[:-12]
deps[name] = {
"isFramework": False,
"moduleName": name,
"modulePath": swift_dep,
}
return ctx.actions.write_json(
module_name + ".swift_module_map.json",
deps.values(),
)

View file

@ -0,0 +1,240 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_sdk_modules_utility.bzl", "get_sdk_deps_tset")
load(":apple_toolchain_types.bzl", "AppleToolchainInfo")
load(":apple_utility.bzl", "expand_relative_prefixed_sdk_path", "get_disable_pch_validation_flags", "get_module_name", "get_versioned_target_triple")
load(":swift_pcm_compilation_types.bzl", "SwiftPCMCompilationInfo")
load(":swift_toolchain_types.bzl", "SdkCompiledModuleInfo")
_REQUIRED_SDK_MODULES = ["Foundation"]
def _project_as_clang_deps(value: "SwiftPCMCompilationInfo"):
return [
"-Xcc",
cmd_args(["-fmodule-file=", value.name, "=", value.pcm_output], delimiter = ""),
"-Xcc",
cmd_args(["-fmodule-map-file=", value.exported_pre.modulemap_path], delimiter = ""),
"-Xcc",
] + value.exported_pre.args
PcmDepTSet = transitive_set(args_projections = {
"clang_deps": _project_as_clang_deps,
})
def get_pcm_deps_tset(ctx: "context", deps: ["dependency"]) -> "PcmDepTSet":
pcm_deps = [
ctx.actions.tset(
PcmDepTSet,
value = d[SwiftPCMCompilationInfo],
children = [d[SwiftPCMCompilationInfo].deps_set],
)
for d in deps
if SwiftPCMCompilationInfo in d
]
return ctx.actions.tset(PcmDepTSet, children = pcm_deps)
def compile_swift_sdk_pcm(
ctx: "context",
toolchain_context: struct.type,
sdk_deps_set: "SDKDepTSet",
uncompiled_sdk_module_info: "SdkUncompiledModuleInfo",
sdk_module_providers: {str.type: "SdkCompiledModuleInfo"}):
module_name = uncompiled_sdk_module_info.module_name
cmd = cmd_args(toolchain_context.compiler)
cmd.add(uncompiled_sdk_module_info.partial_cmd)
cmd.add(["-sdk", toolchain_context.sdk_path])
cmd.add(toolchain_context.compiler_flags)
if toolchain_context.swift_resource_dir:
cmd.add([
"-resource-dir",
toolchain_context.swift_resource_dir,
])
cmd.add(sdk_deps_set.project_as_args("clang_deps"))
expanded_modulemap_path_cmd = expand_relative_prefixed_sdk_path(
cmd_args(toolchain_context.sdk_path),
cmd_args(toolchain_context.swift_resource_dir),
uncompiled_sdk_module_info.input_relative_path,
)
pcm_output = ctx.actions.declare_output(module_name + ".pcm")
cmd.add([
"-o",
pcm_output.as_output(),
expanded_modulemap_path_cmd,
])
# For SDK modules we need to set a few more args
cmd.add([
"-Xcc",
"-Xclang",
"-Xcc",
"-emit-module",
"-Xcc",
"-Xclang",
"-Xcc",
"-fsystem-module",
])
_add_sdk_module_search_path(cmd, uncompiled_sdk_module_info, toolchain_context)
sdk_module_providers[uncompiled_sdk_module_info.name] = SdkCompiledModuleInfo(
name = uncompiled_sdk_module_info.name,
module_name = module_name,
is_framework = uncompiled_sdk_module_info.is_framework,
output_artifact = pcm_output,
is_swiftmodule = False,
deps = sdk_deps_set,
input_relative_path = expanded_modulemap_path_cmd,
)
ctx.actions.run(cmd, category = "sdk_swift_pcm_compile", identifier = module_name)
def compile_swift_pcm(
ctx: "context",
exported_pre: "CPreprocessor",
propagated_exported_preprocessor_info: ["CPreprocessorInfo", None]) -> ["SwiftPCMCompilationInfo", None]:
module_name = get_module_name(ctx)
modulemap_path = exported_pre.modulemap_path
toolchain = ctx.attrs._apple_toolchain[AppleToolchainInfo].swift_toolchain_info
cmd = cmd_args(toolchain.compiler)
cmd.add(get_shared_pcm_compilation_args(get_versioned_target_triple(ctx), module_name))
cmd.add(["-sdk", toolchain.sdk_path])
cmd.add(toolchain.compiler_flags)
sdk_deps_tset = get_sdk_deps_tset(
ctx,
module_name,
ctx.attrs.exported_deps,
_REQUIRED_SDK_MODULES,
toolchain,
)
cmd.add(sdk_deps_tset.project_as_args("clang_deps"))
if toolchain.resource_dir:
cmd.add([
"-resource-dir",
toolchain.resource_dir,
])
# To compile a pcm we only use the exported_deps as those are the only
# ones that should be transitively exported through public headers
pcm_deps_tset = get_pcm_deps_tset(ctx, ctx.attrs.exported_deps)
cmd.add(pcm_deps_tset.project_as_args("clang_deps"))
pcm_output = ctx.actions.declare_output(module_name + ".pcm")
cmd.add([
"-o",
pcm_output.as_output(),
modulemap_path,
])
# To correctly resolve modulemap's headers,
# a search path to the root of modulemap should be passed.
cmd.add([
"-Xcc",
"-I",
"-Xcc",
cmd_args(modulemap_path).parent(),
])
# When compiling pcm files, module's exported pps and inherited pps
# must be provided to an action like hmaps which are used for headers resolution.
if propagated_exported_preprocessor_info:
cmd.add(cmd_args(propagated_exported_preprocessor_info.set.project_as_args("args"), prepend = "-Xcc"))
ctx.actions.run(cmd, category = "swift_pcm_compile", identifier = module_name)
return SwiftPCMCompilationInfo(
name = module_name,
pcm_output = pcm_output,
exported_pre = exported_pre,
deps_set = ctx.actions.tset(
PcmDepTSet,
children = [pcm_deps_tset],
),
sdk_deps_set = sdk_deps_tset,
)
def get_shared_pcm_compilation_args(target: str.type, module_name: str.type) -> "cmd_args":
cmd = cmd_args()
cmd.add([
"-emit-pcm",
"-target",
target,
"-module-name",
module_name,
"-Xfrontend",
"-disable-implicit-swift-modules",
"-Xcc",
"-fno-implicit-modules",
"-Xcc",
"-fno-implicit-module-maps",
# Disable debug info in pcm files. This is required to avoid embedding absolute paths
# and ending up with mismatched pcm file sizes.
"-Xcc",
"-Xclang",
"-Xcc",
"-fmodule-format=raw",
# Embed all input files into the PCM so we don't need to include module map files when
# building remotely.
# https://github.com/apple/llvm-project/commit/fb1e7f7d1aca7bcfc341e9214bda8b554f5ae9b6
"-Xcc",
"-Xclang",
"-Xcc",
"-fmodules-embed-all-files",
# Embed all files that were read during compilation into the generated PCM.
"-Xcc",
"-Xclang",
"-Xcc",
"-fmodule-file-home-is-cwd",
# Once we have an empty working directory the compiler provided headers such as float.h
# cannot be found, so add . to the header search paths.
"-Xcc",
"-I.",
])
cmd.add(get_disable_pch_validation_flags())
return cmd
def _remove_path_components_from_right(path: str.type, count: int.type):
path_components = path.split("/")
removed_path = "/".join(path_components[0:-count])
return removed_path
def _add_sdk_module_search_path(cmd, uncompiled_sdk_module_info, toolchain_context):
modulemap_path = uncompiled_sdk_module_info.input_relative_path
# If this input is a framework we need to search above the
# current framework location, otherwise we include the
# modulemap root.
if uncompiled_sdk_module_info.is_framework:
frameworks_dir_path = _remove_path_components_from_right(modulemap_path, 3)
expanded_path = expand_relative_prefixed_sdk_path(
cmd_args(toolchain_context.sdk_path),
cmd_args(toolchain_context.swift_resource_dir),
frameworks_dir_path,
)
else:
module_root_path = _remove_path_components_from_right(modulemap_path, 1)
expanded_path = expand_relative_prefixed_sdk_path(
cmd_args(toolchain_context.sdk_path),
cmd_args(toolchain_context.swift_resource_dir),
module_root_path,
)
cmd.add([
"-Xcc",
("-F" if uncompiled_sdk_module_info.is_framework else "-I"),
"-Xcc",
cmd_args(expanded_path),
])

View file

@ -0,0 +1,14 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
SwiftPCMCompilationInfo = provider(fields = [
"name",
"pcm_output",
"exported_pre",
"deps_set",
"sdk_deps_set", # A TSet of direct and transitive SDK deps.
])

View file

@ -0,0 +1,65 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":apple_sdk_module.bzl", "create_sdk_modules_graph")
load(":swift_toolchain_types.bzl", "SdkUncompiledModuleInfo", "SwiftToolchainInfo")
def swift_toolchain_impl(ctx):
compiler = cmd_args(ctx.attrs._swiftc_wrapper[RunInfo]).add(ctx.attrs.swiftc[RunInfo])
compiler_flags = ctx.attrs.swiftc_flags
sdk_path = ctx.attrs._internal_sdk_path or ctx.attrs.sdk_path
resource_dir = ctx.attrs.resource_dir
toolchain_context = struct(
compiler = compiler,
sdk_path = sdk_path,
compiler_flags = compiler_flags,
swift_resource_dir = resource_dir,
)
compiled_sdk_module_providers = {}
sdk_uncompiled_module_infos = filter(None, [d.get(SdkUncompiledModuleInfo) for d in ctx.attrs.sdk_modules])
for uncompiled_swift_module_info in sdk_uncompiled_module_infos:
create_sdk_modules_graph(
ctx,
compiled_sdk_module_providers,
uncompiled_swift_module_info,
toolchain_context,
)
compiled_sdk_swift_module_providers = {
info.module_name: info
for _, info in compiled_sdk_module_providers.items()
if info.is_swiftmodule
}
compiled_sdk_clang_module_providers = {
info.module_name: info
for _, info in compiled_sdk_module_providers.items()
if not info.is_swiftmodule
}
return [
DefaultInfo(),
SwiftToolchainInfo(
architecture = ctx.attrs.architecture,
can_toolchain_emit_obj_c_header_textually = ctx.attrs.can_toolchain_emit_obj_c_header_textually,
# TODO(T99038725): until we add -debug-compilation-dir we need to wrap
# the Swift invocations so that we can apply a debug prefix map for
# the current directory while maintaining cache hit.
compiled_sdk_clang_modules = compiled_sdk_clang_module_providers,
compiled_sdk_swift_modules = compiled_sdk_swift_module_providers,
compiler = compiler,
compiler_flags = compiler_flags,
prefix_serialized_debugging_options = ctx.attrs.prefix_serialized_debug_info,
resource_dir = resource_dir,
sdk_path = sdk_path,
swift_stdlib_tool = ctx.attrs.swift_stdlib_tool[RunInfo],
swift_stdlib_tool_flags = ctx.attrs.swift_stdlib_tool_flags,
),
]

View file

@ -0,0 +1,46 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
#####################################################################
# Providers
SwiftToolchainInfo = provider(fields = [
"architecture",
"can_toolchain_emit_obj_c_header_textually", # bool
"compiled_sdk_clang_modules", # {str.type: SdkCompiledModuleInfo} Expose providers of compiled Clang SDK modules.
"compiled_sdk_swift_modules", # {str.type: SdkCompiledModuleInfo} Expose providers of compiled Swift SDK modules.
"compiler_flags",
"compiler",
"prefix_serialized_debugging_options", # bool
"resource_dir", # "artifact",
"sdk_path",
"swift_stdlib_tool_flags",
"swift_stdlib_tool",
])
# A provider that represents a non-yet-compiled SDK (Swift or Clang) module,
# and doesn't contain any artifacts because Swift toolchain isn't resolved yet.
SdkUncompiledModuleInfo = provider(fields = [
"name", # A name of a module with `.swift`/`.clang` suffix.
"module_name", # A real name of a module, without distinguishing suffixes.
"is_framework", # This is mostly needed for the generated Swift module map file.
"is_swiftmodule", # If True then represents a swiftinterface, otherwise Clang's modulemap.
"partial_cmd", # Partial arguments, required to compile a particular SDK module.
"input_relative_path", # A relative prefixed path to a textual swiftinterface/modulemap file within an SDK.
"deps", # [SdkUncompiledModuleInfo]
])
# A provider that represents an already-compiled SDK (Swift or Clang) module.
SdkCompiledModuleInfo = provider(fields = [
"name", # A name of a module with `.swift`/`.clang` suffix.
"module_name", # A real name of a module, without distinguishing suffixes.
"is_swiftmodule", # If True then contains a compiled swiftmodule, otherwise Clang's pcm.
"is_framework",
"output_artifact", # Compiled artifact either swiftmodule or pcm.
"input_relative_path",
"deps", # A TSet of [SdkCompiledModuleInfo]
])

View file

@ -0,0 +1,30 @@
prelude = native
prelude.python_bootstrap_binary(
name = "make_modulemap",
main = "make_modulemap.py",
visibility = ["PUBLIC"],
)
prelude.export_file(
name = "swift_exec.sh",
src = "swift_exec.sh",
)
prelude.command_alias(
name = "swift_exec",
exe = ":swift_exec.sh",
visibility = ["PUBLIC"],
)
prelude.python_bootstrap_binary(
name = "make_vfsoverlay",
main = "make_vfsoverlay.py",
visibility = ["PUBLIC"],
)
prelude.python_bootstrap_binary(
name = "swift_objc_header_postprocess",
main = "swift_objc_header_postprocess.py",
visibility = ["PUBLIC"],
)

View file

@ -0,0 +1,154 @@
#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
import argparse
import os
import re
from io import TextIOWrapper
from typing import Dict, Iterable, List
class Module:
def __init__(self, name: str) -> None:
self.name: str = name
self.headers: List[str] = []
self.submodules: Dict[str, Module] = {}
def add_header(self, src: str) -> None:
self.headers.append(src)
def get_submodule(self, name: str) -> "Module":
if name not in self.submodules:
self.submodules[name] = Module(name)
return self.submodules[name]
def render(self, f: TextIOWrapper, path_prefix: str, indent: int = 0) -> None:
space = " " * indent
f.write(f"{space}module {self.name} {{\n")
submodule_names = set()
for submodule_name in sorted(self.submodules.keys()):
submodule = self.submodules[submodule_name]
# remove any extensions for readibility
sanitized_name = os.path.splitext(submodule_name)[0]
# module names can only be ascii or _
sanitized_name = re.sub(r"[^A-Za-z0-9_]", "_", sanitized_name)
if sanitized_name[0].isdigit():
sanitized_name = "_" + sanitized_name
# avoid any collisions with other files
while sanitized_name in submodule_names:
sanitized_name += "_"
submodule_names.add(sanitized_name)
submodule.name = sanitized_name
submodule.render(f, path_prefix, indent + 4)
header_space = " " * (indent + 4)
for h in sorted(self.headers):
f.write(f'{header_space}header "{os.path.join(path_prefix, h)}"\n')
if self.headers:
f.write(f"{header_space}export *\n")
f.write(f"{space}}}\n")
def _write_single_module(
f: TextIOWrapper, name: str, headers: Iterable[str], path_prefix: str
) -> None:
module = Module(name)
for h in headers:
module.add_header(h)
module.render(f, path_prefix)
def _write_submodules(
f: TextIOWrapper, name: str, headers: Iterable[str], path_prefix: str
) -> None:
# Create a tree of nested modules, one for each path component.
root_module = Module(name)
for h in headers:
module = root_module
for i, component in enumerate(h.split(os.sep)):
if i == 0 and component == name:
# The common case is we have a singe header path prefix that matches the module name.
# In this case we add the headers directly to the root module.
pass
else:
module = module.get_submodule(component)
module.add_header(h)
root_module.render(f, path_prefix)
def _write_swift_header(f: TextIOWrapper, name: str, swift_header_path: str) -> None:
f.write(
f"""
module {name}.Swift {{
header "{swift_header_path}"
requires objc
}}
"""
)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--output", required=True, help="The path to write the modulemap to"
)
parser.add_argument("--name", required=True, help="The name of the module")
parser.add_argument(
"--swift-header", help="If this is a mixed module extend with this Swift header"
)
parser.add_argument(
"--use-submodules",
action="store_true",
help="If set produce a modulemap with per-header submodules",
)
parser.add_argument(
"--symlink-tree",
required=True,
)
parser.add_argument(
"mappings", nargs="*", default=[], help="A list of import paths"
)
args = parser.parse_args()
output_dir = os.path.dirname(args.output)
path_prefix = os.path.relpath(args.symlink_tree, output_dir)
with open(args.output, "w") as f:
if args.use_submodules:
# pyre-fixme[6]: For 4th param expected `str` but got `bytes`.
_write_submodules(f, args.name, args.mappings, path_prefix)
else:
# pyre-fixme[6]: For 4th param expected `str` but got `bytes`.
_write_single_module(f, args.name, args.mappings, path_prefix)
if args.swift_header:
swift_header_name = os.path.relpath(args.swift_header, output_dir)
_write_swift_header(
f,
args.name,
os.path.join(
"swift-extended_symlink_tree",
args.name,
str(swift_header_name),
),
)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,95 @@
#!/usr/bin/env fbpython
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
import argparse
import itertools
import json
import os
from typing import Dict, List, Tuple, TypedDict
# Example VFS overlay in JSON format
# ----------------------------------
# {
# 'version': 0,
# 'roots': [
# { 'name': 'OUT_DIR', 'type': 'directory',
# 'contents': [
# { 'name': 'module.map', 'type': 'file',
# 'external-contents': 'INPUT_DIR/actual_module2.map'
# }
# ]
# }
# ]
# }
class OverlayRoot(TypedDict):
name: str
type: str
contents: List[Dict[str, str]]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--output", required=True, help="The path to write the VFS overlay to"
)
parser.add_argument(
"mappings", nargs="*", default=[], help="A list of virtual paths to real paths"
)
args = parser.parse_args()
if len(args.mappings) % 2 != 0:
parser.error("mappings must be dest-source pairs")
# Group the mappings by containing directory
mappings: Dict[str, List[Tuple[str, str]]] = {}
for src, dst in itertools.zip_longest(*([iter(args.mappings)] * 2)):
folder, basename = os.path.split(src)
mappings.setdefault(folder, []).append((basename, dst))
with open(args.output, "w") as f:
json.dump(
{
"version": 0,
"roots": _get_roots(mappings),
},
f,
sort_keys=True,
indent=4,
)
f.write("\n")
f.flush()
def _get_roots(mappings: Dict[str, List[Tuple[str, str]]]) -> List[OverlayRoot]:
roots = []
for folder, file_maps in mappings.items():
contents = []
for src, dst in file_maps:
contents.append(
{
"name": src,
"type": "file",
"external-contents": dst,
}
)
roots.append(
{
"name": folder,
"type": "directory",
"contents": contents,
}
)
return roots
if __name__ == "__main__":
main()

View file

@ -0,0 +1,26 @@
#!/bin/bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# - Apply a debug prefix map for the current directory
# to make debug info relocatable.
# - Use $TMPDIR for the module cache location. This
# will be set to a unique location for each RE action
# which will avoid sharing modules across RE actions.
# This is necessary as the inputs to the modules will
# be transient and can be removed at any point, causing
# module validation errors to fail builds.
if [ -n "$INSIDE_RE_WORKER" ]; then
MODULE_CACHE_PATH="$TMPDIR/module-cache"
else
# When building locally we can use a shared module
# cache as the inputs should remain at a fixed
# location.
MODULE_CACHE_PATH="/tmp/buck-module-cache"
fi
exec "$@" -debug-prefix-map "$PWD"=. -module-cache-path "$MODULE_CACHE_PATH"

View file

@ -0,0 +1,307 @@
#!/usr/bin/env fbpython
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
import argparse
import json
import os
import re
import sys
from typing import Dict, Iterable, TextIO
# Out-of-date? Update with this command:
#
# xcode-select --print-path | xargs printf '%s/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/' | xargs ls | rg '^([A-Z].+)\.framework$' -r '${1}' | xargs printf ' "%s",\n' && xcode-select --print-path | xargs printf '%s/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/module.modulemap' | xargs cat | rg '^module ([a-zA-Z0-9_]*) .*$' -r '${1}'| xargs printf ' "%s",\n'
APPLE_SYSTEM_MODULES = {
"ARKit",
"AVFAudio",
"AVFoundation",
"AVKit",
"Accelerate",
"Accessibility",
"Accounts",
"AdServices",
"AdSupport",
"AddressBook",
"AddressBookUI",
"AppClip",
"AppTrackingTransparency",
"AssetsLibrary",
"AudioToolbox",
"AudioUnit",
"AuthenticationServices",
"AutomaticAssessmentConfiguration",
"BackgroundTasks",
"BusinessChat",
"CFNetwork",
"CallKit",
"CarPlay",
"ClassKit",
"ClockKit",
"CloudKit",
"Combine",
"Contacts",
"ContactsUI",
"CoreAudio",
"CoreAudioKit",
"CoreAudioTypes",
"CoreBluetooth",
"CoreData",
"CoreFoundation",
"CoreGraphics",
"CoreHaptics",
"CoreImage",
"CoreLocation",
"CoreLocationUI",
"CoreMIDI",
"CoreML",
"CoreMedia",
"CoreMotion",
"CoreNFC",
"CoreServices",
"CoreSpotlight",
"CoreTelephony",
"CoreText",
"CoreVideo",
"CryptoKit",
"CryptoTokenKit",
"DataDetection",
"DeveloperToolsSupport",
"DeviceActivity",
"DeviceCheck",
"EventKit",
"EventKitUI",
"ExposureNotification",
"ExternalAccessory",
"FamilyControls",
"FileProvider",
"FileProviderUI",
"Foundation",
"GLKit",
"GSS",
"GameController",
"GameKit",
"GameplayKit",
"GroupActivities",
"HealthKit",
"HealthKitUI",
"HomeKit",
"IOKit",
"IOSurface",
"IdentityLookup",
"IdentityLookupUI",
"ImageCaptureCore",
"ImageIO",
"Intents",
"IntentsUI",
"JavaScriptCore",
"LinkPresentation",
"LocalAuthentication",
"ManagedSettings",
"ManagedSettingsUI",
"MapKit",
"MediaAccessibility",
"MediaPlayer",
"MediaToolbox",
"MessageUI",
"Messages",
"Metal",
"MetalKit",
"MetalPerformanceShaders",
"MetalPerformanceShadersGraph",
"MetricKit",
"MobileCoreServices",
"ModelIO",
"MultipeerConnectivity",
"MusicKit",
"NaturalLanguage",
"NearbyInteraction",
"Network",
"NetworkExtension",
"NewsstandKit",
"NotificationCenter",
"OSLog",
"OpenAL",
"OpenGLES",
"PDFKit",
"PHASE",
"PassKit",
"PencilKit",
"Photos",
"PhotosUI",
"PushKit",
"QuartzCore",
"QuickLook",
"QuickLookThumbnailing",
"RealityFoundation",
"RealityKit",
"ReplayKit",
"SafariServices",
"SceneKit",
"ScreenTime",
"Security",
"SensorKit",
"ShazamKit",
"Social",
"SoundAnalysis",
"Speech",
"SpriteKit",
"StoreKit",
"SwiftUI",
"SystemConfiguration",
"TabularData",
"Twitter",
"UIKit",
"UniformTypeIdentifiers",
"UserNotifications",
"UserNotificationsUI",
"VideoSubscriberAccount",
"VideoToolbox",
"Vision",
"VisionKit",
"WatchConnectivity",
"WebKit",
"WidgetKit",
"AppleTextureEncoder",
"Compression",
"Darwin",
"asl",
"dnssd",
"os",
"os_object",
"os_workgroup",
"libkern",
"notify",
"zlib",
"SQLite3",
}
APPLE_TEST_FRAMEWORKS = {
"XCTest",
}
# These modules require specific handling, as they do not have an umbrella
# header that matches the module name, as typical Apple frameworks do.
APPLE_SYSTEM_MODULE_OVERRIDES = {
"Dispatch": ("dispatch", ("dispatch.h",)),
"ObjectiveC": ("objc", ("runtime.h",)),
}
def write_imports_for_headers(out: TextIO, prefix: str, headers: Iterable[str]) -> None:
for header in headers:
print(f"#import <{prefix}/{header}>", file=out)
def write_imports_for_modules(
out: TextIO,
postprocessing_module_name: str,
modules: Iterable[str],
deps: Dict[str, Iterable[str]],
) -> None:
# We only include the traditional textual imports when modules are disabled, so
# that the behavior with modules enabled is identical to the behavior without
# the postprocessing.
print("#else", file=out)
for module in modules:
if headers := deps.get(module):
write_imports_for_headers(out, module, headers)
elif override := APPLE_SYSTEM_MODULE_OVERRIDES.get(module):
write_imports_for_headers(out, override[0], override[1])
elif module in APPLE_SYSTEM_MODULES or module in APPLE_TEST_FRAMEWORKS:
# When we don't have an explicit override for the module, we use the module's
# name as an umbrella header. This is used for typical Apple frameworks like
# Foundation and UIKit.
write_imports_for_headers(out, module, (f"{module}.h",))
else:
print(
f"""
The module "{module}" was imported as a dependency of Swift code in "{postprocessing_module_name}", but could not be mapped to a list of header imports by Buck's Swift header postprocessing. There are two possibilities:
1. If "{module}" is an internal library, it is likely that the exported_deps of "{postprocessing_module_name}" are incorrect. Try fixing them manually or with "arc fixmydeps". This is the most likely issue.
2. If "{module}" is a system (Apple) framework, the list of Apple system modules in {os.path.basename(__file__)} is out-of-date. There is a command to fix it in that file. This issue is unlikely.
""",
file=sys.stderr,
)
sys.exit(1)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("header")
parser.add_argument("deps")
parser.add_argument("out")
args = parser.parse_args()
with open(args.deps) as f:
deps = json.load(f)
# Strips the suffix from the header name, leaving us with just the name
# of the module that we are postprocessing the header for. This is used
# for error reporting.
postprocessing_module_name = os.path.basename(args.header).split("-")[0]
# The Swift compiler's output looks like this:
#
# #if __has_feature(modules)
# #if __has_warning("-Watimport-in-framework-header")
# #pragma clang diagnostic ignored "-Watimport-in-framework-header"
# #endif
# @import ModuleA;
# @import ModuleB;
# @import ModuleC;
# #endif
#
# The implementation here balances being somewhat flexible to changes to the compiler's
# output, unlikely though they may be, with avoiding adding too much complexity and getting
# too close to implementing a full parser for Objective-C un-preprocessed header files.
with open(args.header) as header, open(args.out, "w") as out:
# When this is None, it means that we are still searching for the start of the conditional
# @import block in the generated header.
modules = None
# The Swift compiler emits an additional #if gate inside the conditional @import block, so
# we need to track whether we're in a further nested conditional so that we know when the
# main conditional block has ended.
if_level = 0
for line in header:
line = line.rstrip("\n")
# When the modules has not been set, we are still searching for the start of the
# modules @import section.
if modules is None:
if line == "#if __has_feature(modules)":
modules = []
if_level = 1
else:
if line.startswith("@import"):
# Splitting on:
# "@import ": to separate from the @import.
# Semicolon and period: to separate the main module name from submodules or EOL.
# The module name will then be the first item.
modules.append(re.split(r"@import |[;.]", line)[1])
elif line.startswith("#if"):
# This allows us to handle the Clang diagnostic #if block that the compiler inserts
# within the main #if block for modules.
if_level += 1
elif line.startswith("#endif"):
if_level -= 1
if if_level == 0:
write_imports_for_modules(
out,
postprocessing_module_name,
modules,
deps,
)
modules = None
print(line, file=out)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,52 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:attributes.bzl", "AppleBundleExtension")
load("@prelude//apple:apple_bundle_resources.bzl", "get_apple_bundle_resource_part_list")
load("@prelude//apple:apple_bundle_types.bzl", "AppleBundleResourceInfo")
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo", "AppleToolsInfo")
load("@prelude//user:rule_spec.bzl", "RuleRegistrationSpec")
load(":resource_group_map.bzl", "resource_group_map_attr")
def _get_apple_resources_toolchain_attr():
# FIXME: prelude// should be standalone (not refer to fbcode//)
return attrs.toolchain_dep(default = "fbcode//buck2/platform/toolchain:apple-resources", providers = [AppleToolchainInfo])
def _impl(ctx: "context") -> ["provider"]:
resource_output = get_apple_bundle_resource_part_list(ctx)
return [
DefaultInfo(),
AppleBundleResourceInfo(
resource_output = resource_output,
),
]
registration_spec = RuleRegistrationSpec(
name = "apple_resource_bundle",
impl = _impl,
attrs = {
"asset_catalogs_compilation_options": attrs.dict(key = attrs.string(), value = attrs.any(), default = {}),
"binary": attrs.option(attrs.dep(), default = None),
"deps": attrs.list(attrs.dep(), default = []),
"extension": attrs.one_of(attrs.enum(AppleBundleExtension), attrs.string()),
"ibtool_flags": attrs.option(attrs.list(attrs.string()), default = None),
"ibtool_module_flag": attrs.option(attrs.bool(), default = None),
"info_plist": attrs.source(),
"info_plist_substitutions": attrs.dict(key = attrs.string(), value = attrs.string(), sorted = False, default = {}),
"product_name": attrs.option(attrs.string(), default = None),
"resource_group": attrs.option(attrs.string(), default = None),
"resource_group_map": resource_group_map_attr(),
# Only include macOS hosted toolchains, so we compile resources directly on Mac RE
"_apple_toolchain": _get_apple_resources_toolchain_attr(),
# FIXME: prelude// should be standalone (not refer to fbsource//)
"_apple_tools": attrs.exec_dep(default = "fbsource//xplat/buck2/platform/apple:apple-tools", providers = [AppleToolsInfo]),
# Because `apple_resource_bundle` is a proxy for `apple_bundle`, we need to get `name`
# field of the `apple_bundle`, as it's used as a fallback value in Info.plist.
"_bundle_target_name": attrs.string(),
"_compile_resources_locally_override": attrs.option(attrs.bool(), default = None),
},
)

View file

@ -0,0 +1,51 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
load("@prelude//cxx:cxx_toolchain_types.bzl", "CxxToolchainInfo")
load("@prelude//user:rule_spec.bzl", "RuleRegistrationSpec")
def _impl(ctx: "context") -> ["provider"]:
base = ctx.attrs.base[AppleToolchainInfo]
cxx_toolchain_override = ctx.attrs.cxx_toolchain[CxxToolchainInfo]
return [
DefaultInfo(),
AppleToolchainInfo(
actool = base.actool,
ibtool = base.ibtool,
dsymutil = base.dsymutil,
dwarfdump = base.dwarfdump,
lipo = base.lipo,
cxx_platform_info = base.cxx_platform_info,
cxx_toolchain_info = cxx_toolchain_override if cxx_toolchain_override != None else base.cxx_toolchain_info,
codesign = base.codesign,
codesign_allocate = base.codesign_allocate,
compile_resources_locally = base.compile_resources_locally,
libtool = base.libtool,
momc = base.momc,
min_version = base.min_version,
xctest = base.xctest,
platform_path = base.platform_path,
sdk_name = base.sdk_name,
sdk_path = base.sdk_path,
sdk_version = base.sdk_version,
sdk_build_version = base.sdk_build_version,
swift_toolchain_info = base.swift_toolchain_info,
watch_kit_stub_binary = base.watch_kit_stub_binary,
xcode_version = base.xcode_version,
xcode_build_version = base.xcode_build_version,
),
]
registration_spec = RuleRegistrationSpec(
name = "apple_toolchain_override",
impl = _impl,
attrs = {
"base": attrs.dep(providers = [AppleToolchainInfo]),
"cxx_toolchain": attrs.dep(providers = [CxxToolchainInfo]),
},
)

View file

@ -0,0 +1,36 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolsInfo")
load("@prelude//user:rule_spec.bzl", "RuleRegistrationSpec")
def _impl(ctx: "context") -> ["provider"]:
return [
DefaultInfo(),
AppleToolsInfo(
assemble_bundle = ctx.attrs.assemble_bundle[RunInfo],
info_plist_processor = ctx.attrs.info_plist_processor[RunInfo],
make_modulemap = ctx.attrs.make_modulemap[RunInfo],
make_vfsoverlay = ctx.attrs.make_vfsoverlay[RunInfo],
swift_objc_header_postprocess = ctx.attrs.swift_objc_header_postprocess[RunInfo],
),
]
# The `apple_tools` rule exposes a set of supplementary tools
# required by the Apple rules _internally_. Such tools are not
# toolchain/SDK specific, they're just internal helper tools.
registration_spec = RuleRegistrationSpec(
name = "apple_tools",
impl = _impl,
attrs = {
"assemble_bundle": attrs.dep(providers = [RunInfo]),
"info_plist_processor": attrs.dep(providers = [RunInfo]),
"make_modulemap": attrs.dep(providers = [RunInfo]),
"make_vfsoverlay": attrs.dep(providers = [RunInfo]),
"swift_objc_header_postprocess": attrs.dep(providers = [RunInfo]),
},
)

View file

@ -0,0 +1,59 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:attributes.bzl", "AppleBundleExtension", "Traversal")
load("@prelude//apple:apple_bundle.bzl", "apple_bundle_impl")
load("@prelude//apple:apple_rules_impl_utility.bzl", "apple_bundle_extra_attrs")
load("@prelude//user:rule_spec.bzl", "RuleRegistrationSpec")
load(":watch_transition.bzl", "watch_transition")
def _apple_bundle_base_attrs():
return {
# Attributes comes from `attributes.bzl` but since it's autogenerated, we cannot easily abstract
"asset_catalogs_compilation_options": attrs.dict(key = attrs.string(), value = attrs.any(), default = {}),
"binary": attrs.option(attrs.dep(), default = None),
"codesign_flags": attrs.list(attrs.string(), default = []),
"codesign_identity": attrs.option(attrs.string(), default = None),
"contacts": attrs.list(attrs.string(), default = []),
"default_host_platform": attrs.option(attrs.configuration_label(), default = None),
"default_platform": attrs.option(attrs.string(), default = None),
"deps": attrs.list(attrs.dep(), default = []),
"extension": attrs.one_of(attrs.enum(AppleBundleExtension), attrs.string()),
"ibtool_flags": attrs.option(attrs.list(attrs.string()), default = None),
"ibtool_module_flag": attrs.option(attrs.bool(), default = None),
"incremental_bundling_enabled": attrs.option(attrs.bool(), default = None),
"info_plist": attrs.source(),
"info_plist_substitutions": attrs.dict(key = attrs.string(), value = attrs.string(), sorted = False, default = {}),
"labels": attrs.list(attrs.string(), default = []),
"licenses": attrs.list(attrs.source(), default = []),
"platform_binary": attrs.option(attrs.list(attrs.tuple(attrs.regex(), attrs.dep())), default = None),
"product_name": attrs.option(attrs.string(), default = None),
"resource_group": attrs.option(attrs.string(), default = None),
"resource_group_map": attrs.option(attrs.list(attrs.tuple(attrs.string(), attrs.list(attrs.tuple(attrs.dep(), attrs.enum(Traversal), attrs.option(attrs.string()))))), default = None),
"skip_copying_swift_stdlib": attrs.option(attrs.bool(), default = None),
"try_skip_code_signing": attrs.option(attrs.bool(), default = None),
"within_view": attrs.option(attrs.list(attrs.string())),
"xcode_product_type": attrs.option(attrs.string(), default = None),
}
def _apple_watchos_bundle_attrs():
attributes = {}
attributes.update(_apple_bundle_base_attrs())
attributes.update(apple_bundle_extra_attrs())
return attributes
def apple_watchos_bundle_impl(ctx: "context") -> ["provider"]:
# This rule is _equivalent_ to `apple_bundle` except it applies
# an incoming watchOS transition.
return apple_bundle_impl(ctx)
registration_spec = RuleRegistrationSpec(
name = "apple_watchos_bundle",
impl = apple_watchos_bundle_impl,
attrs = _apple_watchos_bundle_attrs(),
cfg = watch_transition,
)

View file

@ -0,0 +1,51 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:attributes.bzl", "Traversal")
load(
"@prelude//apple:resource_groups.bzl",
"ResourceGroupInfo",
"create_resource_graph",
"get_resource_graph_node_map_func",
)
load(
"@prelude//cxx:groups.bzl",
"compute_mappings",
"parse_groups_definitions",
)
load("@prelude//user:rule_spec.bzl", "RuleRegistrationSpec")
def v1_attrs():
return attrs.list(attrs.tuple(attrs.string(), attrs.list(attrs.tuple(attrs.dep(), attrs.enum(Traversal), attrs.option(attrs.string())))))
def resource_group_map_attr():
v2_attrs = attrs.dep(providers = [ResourceGroupInfo])
return attrs.option(attrs.one_of(v2_attrs, v1_attrs()), default = None)
def _impl(ctx: "context") -> ["provider"]:
resource_groups = parse_groups_definitions(ctx.attrs.map)
resource_groups_deps = [mapping.root.node for group in resource_groups for mapping in group.mappings]
resource_graph = create_resource_graph(
ctx = ctx,
labels = [],
deps = resource_groups_deps,
exported_deps = [],
)
resource_graph_node_map = get_resource_graph_node_map_func(resource_graph)()
mappings = compute_mappings(groups = resource_groups, graph_map = resource_graph_node_map)
return [
DefaultInfo(),
ResourceGroupInfo(groups = resource_groups, groups_hash = hash(str(resource_groups)), mappings = mappings),
]
registration_spec = RuleRegistrationSpec(
name = "resource_group_map",
impl = _impl,
attrs = {
"map": v1_attrs(),
},
)

View file

@ -0,0 +1,84 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
"""
Transition from iOS to watchOS. Used for watchOS bundle rule.
Transforms both OS and SDK constraints.
Only sanity check for source configuration is done.
"""
load("@prelude//utils:utils.bzl", "expect")
def _os_and_sdk_unrelated_constraints(platform: PlatformInfo.type, refs: struct.type) -> {"target_label": ConstraintValueInfo.type}:
return {
constraint_setting_label: constraint_setting_value
for (constraint_setting_label, constraint_setting_value) in platform.configuration.constraints.items()
if constraint_setting_label not in [refs.os[ConstraintSettingInfo].label, refs.sdk[ConstraintSettingInfo].label]
}
def _old_os_constraint_value(platform: PlatformInfo.type, refs: struct.type) -> [None, ConstraintValueInfo.type]:
return platform.configuration.constraints.get(refs.os[ConstraintSettingInfo].label)
def _old_sdk_constraint_value(platform: PlatformInfo.type, refs: struct.type) -> [None, ConstraintValueInfo.type]:
return platform.configuration.constraints.get(refs.sdk[ConstraintSettingInfo].label)
def _impl(platform: PlatformInfo.type, refs: struct.type) -> "PlatformInfo":
# This functions operates in the following way:
# - Start with all the constraints from the platform and filter out the constraints for OS and SDK.
# - If the old OS constraint was iOS or watchOS, set the new constraint to be always watchOS.
# - If the old SDK constraint was iOS, replace with the equivalent watchOS constraint.
# - Return a new platform with the updated constraints.
updated_constraints = _os_and_sdk_unrelated_constraints(platform, refs)
# Update OS constraint
old_os = _old_os_constraint_value(platform, refs)
watchos = refs.watchos[ConstraintValueInfo]
ios = refs.ios[ConstraintValueInfo]
if old_os != None:
expect(old_os.label in [watchos.label, ios.label], "If present, OS transitioned non-identically to watchOS should be `iphoneos`, got {}".format(old_os.label))
updated_constraints[refs.os[ConstraintSettingInfo].label] = watchos
# Update SDK constraint
old_sdk = _old_sdk_constraint_value(platform, refs)
watchos_device_sdk = refs.watchos_device_sdk[ConstraintValueInfo]
watchos_simulator_sdk = refs.watchos_simulator_sdk[ConstraintValueInfo]
ios_device_sdk = refs.ios_device_sdk[ConstraintValueInfo]
ios_simulator_sdk = refs.ios_simulator_sdk[ConstraintValueInfo]
is_simulator = True
if old_sdk != None:
if old_sdk.label == watchos_simulator_sdk.label:
pass
elif old_sdk.label == watchos_device_sdk.label:
is_simulator = False
elif old_sdk.label == ios_simulator_sdk.label:
pass
elif old_sdk.label == ios_device_sdk.label:
is_simulator = False
else:
fail("If present, SDK transitioned non-identically to watchOS should be either `iphoneos` or `iphonesimulator`, got {}".format(old_sdk.label))
updated_constraints[refs.sdk[ConstraintSettingInfo].label] = watchos_simulator_sdk if is_simulator else watchos_device_sdk
new_cfg = ConfigurationInfo(
constraints = updated_constraints,
values = platform.configuration.values,
)
return PlatformInfo(
label = "watch_transition",
configuration = new_cfg,
)
# FIXME: prelude// should be standalone (not refer to ovr_config//)
watch_transition = transition(impl = _impl, refs = {
"ios": "ovr_config//os/constraints:iphoneos",
"ios_device_sdk": "ovr_config//os/sdk/apple/constraints:iphoneos",
"ios_simulator_sdk": "ovr_config//os/sdk/apple/constraints:iphonesimulator",
"os": "ovr_config//os/constraints:os",
"sdk": "ovr_config//os/sdk/apple/constraints:_",
"watchos": "ovr_config//os/constraints:watchos",
"watchos_device_sdk": "ovr_config//os/sdk/apple/constraints:watchos",
"watchos_simulator_sdk": "ovr_config//os/sdk/apple/constraints:watchsimulator",
})

View file

@ -0,0 +1,62 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//apple:apple_sdk.bzl", "get_apple_sdk_name")
load("@prelude//apple:apple_target_sdk_version.bzl", "get_min_deployment_version_for_node")
load("@prelude//apple:apple_utility.bzl", "has_apple_toolchain")
load(
"@prelude//cxx:compile.bzl",
"CxxSrcWithFlags", # @unused Used as a type
)
load("@prelude//cxx:xcode.bzl", "cxx_populate_xcode_attributes")
load("@prelude//utils:utils.bzl", "expect")
def apple_populate_xcode_attributes(
ctx,
srcs: [CxxSrcWithFlags.type],
argsfiles_by_ext: {str.type: "artifact"},
product_name: str.type) -> {str.type: ""}:
data = cxx_populate_xcode_attributes(ctx = ctx, srcs = srcs, argsfiles_by_ext = argsfiles_by_ext, product_name = product_name)
if has_apple_toolchain(ctx):
data["sdk"] = get_apple_sdk_name(ctx)
data["deployment_version"] = get_min_deployment_version_for_node(ctx)
if hasattr(ctx.attrs, "swift_version"):
swift_version = ctx.attrs.swift_version
if swift_version != None:
data["swift_version"] = swift_version
apple_xcode_data_add_xctoolchain(ctx, data)
return data
def apple_xcode_data_add_xctoolchain(ctx: "context", data: {str.type: ""}):
_add_label_for_attr(ctx, "_apple_xctoolchain_bundle_id", "xctoolchain_bundle_id_target", data)
_add_output_for_attr(ctx, "_apple_xctoolchain_bundle_id", "xctoolchain_bundle_id", data)
_add_label_for_attr(ctx, "_apple_xctoolchain", "xctoolchain_bundle_target", data)
def _add_label_for_attr(ctx: "context", attr_name: str.type, field_name: str.type, data: {str.type: ""}):
xctoolchain_dep = _get_attribute_with_output(ctx, attr_name)
if xctoolchain_dep:
data[field_name] = xctoolchain_dep.label
def _add_output_for_attr(ctx: "context", attr_name: str.type, field_name: str.type, data: {str.type: ""}):
xctoolchain_dep = _get_attribute_with_output(ctx, attr_name)
if xctoolchain_dep:
default_info = xctoolchain_dep[DefaultInfo]
expect(len(default_info.default_outputs) == 1, "Expected only one output, got {}", len(default_info.default_outputs))
data[field_name] = default_info.default_outputs[0]
def _get_attribute_with_output(ctx: "context", attr_name: str.type) -> ["dependency", None]:
if hasattr(ctx.attrs, attr_name):
dep = getattr(ctx.attrs, attr_name)
default_info = dep[DefaultInfo]
if len(default_info.default_outputs) > 0:
# When there's no xctoolchain, there will be an empty `DefaultInfo`.
# So, an empty `DefaultInfo` basically signifies that there's no xctoolchain.
return dep
return None

View file

@ -0,0 +1,9 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
def xcode_postbuild_script_impl(_ctx: "context") -> ["provider"]:
return [DefaultInfo()]

View file

@ -0,0 +1,9 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
def xcode_prebuild_script_impl(_ctx: "context") -> ["provider"]:
return [DefaultInfo()]