Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rules_ios] Rebase onto upstream 4.8.2, squash patches, and fix / cleanup custom patches #15

Merged
merged 13 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/preflight_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set -e
# - XCODE_VERSION: The version of Xcode to use.

# GitHub runners are hitting 'module not found pkg_resources' required by prepare_sim.py
pip3 install setuptools --break-system-packages
pip3 install setuptools==69.5.1 --break-system-packages

# If flag --no-bzlmod is passed, writes a user.bazelrc file to disable Bzlmod.
if [[ "$*" == *--no-bzlmod* ]]; then
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ bazel_dep(
bazel_dep(
name = "rules_swift",
version = "1.18.0",
max_compatibility_level = 2,
repo_name = "build_bazel_rules_swift",
)
bazel_dep(
Expand Down
1 change: 1 addition & 0 deletions rules/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ bzl_library(
visibility = ["//visibility:public"],
deps = [
":features",
"//rules:utils.bzl",
"//rules/internal:objc_provider_utils",
"@build_bazel_rules_apple//apple",
],
Expand Down
98 changes: 75 additions & 23 deletions rules/framework.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ load("//rules:providers.bzl", "AvoidDepsInfo", "FrameworkInfo")
load("//rules:transition_support.bzl", "transition_support")
load("//rules:utils.bzl", "is_bazel_7")
load("//rules/internal:objc_provider_utils.bzl", "objc_provider_utils")
load("@bazel_skylib//lib:collections.bzl", "collections")
load("@bazel_skylib//lib:partial.bzl", "partial")
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
load("@bazel_skylib//lib:types.bzl", "types")
load("@build_bazel_rules_apple//apple/internal:apple_product_type.bzl", "apple_product_type")
load("@build_bazel_rules_apple//apple/internal:features_support.bzl", "features_support")
load("@build_bazel_rules_apple//apple/internal:linking_support.bzl", "linking_support")
Expand All @@ -31,6 +33,7 @@ load(
"apple_resource_aspect",
)
load("//rules:force_load_direct_deps.bzl", "force_load_direct_deps")
load("//rules:header_paths.bzl", "header_paths")

_APPLE_FRAMEWORK_PACKAGING_KWARGS = [
"visibility",
Expand All @@ -43,13 +46,38 @@ _APPLE_FRAMEWORK_PACKAGING_KWARGS = [
"exported_symbols_lists",
]

_HEADER_EXTS = {
"srcs": (".h", ".hh", ".hpp"),
"public_headers": (".inc", ".h", ".hh", ".hpp"),
"private_headers": (".inc", ".h", ".hh", ".hpp"),
}

def _generate_headers_mapping(headers_mapping, kwargs):
if types.is_dict(headers_mapping):
return headers_mapping

to_map = []
for attr in headers_mapping.attrs:
exts = _HEADER_EXTS[attr]
to_map += [h for h in kwargs.get(attr, []) if h.endswith(exts)]

headers = collections.uniq(to_map)
if headers_mapping.op == "strip":
return header_paths.mapped_without_prefix(headers, headers_mapping.pattern)
elif headers_mapping.op == "add":
return {h: headers_mapping.pattern + h for h in headers}
else:
fail("Invalid headers_mapping `{}`".format(headers_mapping))


def apple_framework(
name,
apple_library = apple_library,
infoplists = [],
infoplists_by_build_setting = {},
xcconfig = {},
xcconfig_by_build_setting = {},
headers_mapping = {},
**kwargs):
"""Builds and packages an Apple framework.

Expand All @@ -72,6 +100,18 @@ def apple_framework(

If '//conditions:default' is not set the value in 'xcconfig'
is set as default.
headers_mapping: Either a dictionary, or the value from add_prefix / strip_prefix.

If a dictionary, a mapping of {str: str}, where the key is a
path to a header, and the value where that header should be
placed in Headers, PrivateHeaders, umbrella headers, hmaps, etc.

If the result of header_paths.add_prefix, then the attributes
specified will have the prefix appended to the beginning.

If the result of header_paths.strip_prefix, then the
attributes specified will have the prefix reoved from the
beginning.
**kwargs: Arguments passed to the apple_library and apple_framework_packaging rules as appropriate.
"""
framework_packaging_kwargs = {arg: kwargs.pop(arg) for arg in _APPLE_FRAMEWORK_PACKAGING_KWARGS if arg in kwargs}
Expand All @@ -96,11 +136,14 @@ def apple_framework(

testonly = kwargs.pop("testonly", False)

if headers_mapping:
headers_mapping = _generate_headers_mapping(headers_mapping, kwargs)
library = apple_library(
name = name,
testonly = testonly,
xcconfig = xcconfig,
xcconfig_by_build_setting = xcconfig_by_build_setting,
headers_mapping = headers_mapping,
**kwargs
)

Expand Down Expand Up @@ -155,6 +198,7 @@ def apple_framework(
testonly = testonly,
minimum_os_version = minimum_os_version,
platform_type = platform_type,
headers_mapping = headers_mapping,
**framework_packaging_kwargs
)

Expand Down Expand Up @@ -188,35 +232,39 @@ def _find_framework_dir(outputs):
return prefix + ".framework"
return None

def _framework_packaging_symlink_headers(ctx, inputs, outputs):
inputs_by_basename = {input.basename: input for input in inputs}
def _framework_packaging_symlink_headers(ctx, inputs, outputs, headers_mapping):
inputs_by_mapped_name = {header_paths.get_mapped_path(input, headers_mapping): input for input in inputs}

# If this check is true it means that multiple inputs have the same 'basename',
# an additional check is done to see if that was caused by 'action_inputs' containing
# two different paths to the same file
#
# In that case fails with a msg listing the differences found
if len(inputs_by_basename) != len(inputs):
inputs_by_basename_paths = [x.path for x in inputs_by_basename.values()]
inputs_with_duplicated_basename = [x for x in inputs if not x.path in inputs_by_basename_paths]
if len(inputs_by_mapped_name) != len(inputs):
inputs_by_mapped_name_paths = [x.path for x in inputs_by_mapped_name.values()]
inputs_with_duplicated_basename = [x for x in inputs if not x.path in inputs_by_mapped_name_paths]
if len(inputs_with_duplicated_basename) > 0:
# TODO: Fix this error message
fail("""
[Error] Multiple files with the same name exists.\n
See below for the list of paths found for each basename:\n
{}
""".format({x.basename: (x.path, inputs_by_basename[x.basename].path) for x in inputs_with_duplicated_basename}))
""".format({
header_paths.get_mapped_path(x, headers_mapping):
(x.path, inputs_by_mapped_name[header_paths.get_mapped_path(x, headers_mapping)].path)
for x in inputs_with_duplicated_basename
}))

# If no error occurs create symlinks for each output with
# each input as 'target_file'
output_input_dict = {output: inputs_by_basename[output.basename] for output in outputs}
for (output, input) in output_input_dict.items():
for (input, output) in zip(inputs, outputs):
ctx.actions.symlink(output = output, target_file = input)

def _framework_packaging_single(ctx, action, inputs, output, manifest = None):
outputs = _framework_packaging_multi(ctx, action, inputs, [output], manifest = manifest)
def _framework_packaging_single(ctx, action, inputs, output, manifest = None, headers_mapping = {}):
outputs = _framework_packaging_multi(ctx, action, inputs, [output], manifest = manifest, headers_mapping = headers_mapping)
return outputs[0] if outputs else None

def _framework_packaging_multi(ctx, action, inputs, outputs, manifest = None):
def _framework_packaging_multi(ctx, action, inputs, outputs, manifest = None, headers_mapping = {}):
if not inputs:
return []
if inputs == [None]:
Expand All @@ -240,7 +288,7 @@ def _framework_packaging_multi(ctx, action, inputs, outputs, manifest = None):
args.add_all("--outputs", outputs)

if action in ["header", "private_header"]:
_framework_packaging_symlink_headers(ctx, inputs, outputs)
_framework_packaging_symlink_headers(ctx, inputs, outputs, headers_mapping)
else:
ctx.actions.run(
executable = ctx.executable._framework_packaging,
Expand Down Expand Up @@ -311,6 +359,7 @@ def _get_virtual_framework_info(ctx, framework_files, compilation_context_fields
def _get_framework_files(ctx, deps):
framework_name = ctx.attr.framework_name
bundle_extension = ctx.attr.bundle_extension
headers_mapping = header_paths.stringify_mapping(ctx.attr.headers_mapping)

# declare framework directory
framework_dir = "%s/%s.%s" % (ctx.attr.name, framework_name, bundle_extension)
Expand Down Expand Up @@ -383,7 +432,8 @@ def _get_framework_files(ctx, deps):
if PrivateHeadersInfo in dep:
for hdr in dep[PrivateHeadersInfo].headers.to_list():
private_headers_in.append(hdr)
destination = paths.join(framework_dir, "PrivateHeaders", hdr.basename)
mapped_path = header_paths.get_mapped_path(hdr, headers_mapping)
destination = paths.join(framework_dir, "PrivateHeaders", mapped_path)
private_headers_out.append(destination)

has_header = False
Expand All @@ -393,7 +443,8 @@ def _get_framework_files(ctx, deps):
if not hdr.is_directory and hdr.path.endswith((".h", ".hh", ".hpp")):
has_header = True
headers_in.append(hdr)
destination = paths.join(framework_dir, "Headers", hdr.basename)
mapped_path = header_paths.get_mapped_path(hdr, headers_mapping)
destination = paths.join(framework_dir, "Headers", mapped_path)
headers_out.append(destination)
elif hdr.path.endswith(".modulemap"):
modulemap_in = hdr
Expand Down Expand Up @@ -440,19 +491,19 @@ def _get_framework_files(ctx, deps):
# so inputs that do not depend on compilation
# are available before those that do,
# improving parallelism
binary_out = _framework_packaging_single(ctx, "binary", binaries_in, binary_out, framework_manifest)
headers_out = _framework_packaging_multi(ctx, "header", headers_in, headers_out, framework_manifest)
private_headers_out = _framework_packaging_multi(ctx, "private_header", private_headers_in, private_headers_out, framework_manifest)
binary_out = _framework_packaging_single(ctx, "binary", binaries_in, binary_out, framework_manifest, headers_mapping)
headers_out = _framework_packaging_multi(ctx, "header", headers_in, headers_out, framework_manifest, headers_mapping)
private_headers_out = _framework_packaging_multi(ctx, "private_header", private_headers_in, private_headers_out, framework_manifest, headers_mapping)

# Instead of creating a symlink of the modulemap, we need to copy it to modulemap_out.
# It's a hacky fix to guarantee running the clean action before compiling objc files depending on this framework in non-sandboxed mode.
# Otherwise, stale header files under framework_root will cause compilation failure in non-sandboxed mode.
modulemap_out = _framework_packaging_single(ctx, "modulemap", [modulemap_in], modulemap_out, framework_manifest)
swiftmodule_out = _framework_packaging_single(ctx, "swiftmodule", [swiftmodule_in], swiftmodule_out, framework_manifest)
swiftinterface_out = _framework_packaging_single(ctx, "swiftinterface", [swiftinterface_in], swiftinterface_out, framework_manifest)
swiftdoc_out = _framework_packaging_single(ctx, "swiftdoc", [swiftdoc_in], swiftdoc_out, framework_manifest)
infoplist_out = _framework_packaging_single(ctx, "infoplist", [infoplist_in], infoplist_out, framework_manifest)
symbol_graph_out = _framework_packaging_single(ctx, "symbol_graph", [symbol_graph_in], symbol_graph_out, framework_manifest)
modulemap_out = _framework_packaging_single(ctx, "modulemap", [modulemap_in], modulemap_out, framework_manifest, headers_mapping)
swiftmodule_out = _framework_packaging_single(ctx, "swiftmodule", [swiftmodule_in], swiftmodule_out, framework_manifest, headers_mapping)
swiftinterface_out = _framework_packaging_single(ctx, "swiftinterface", [swiftinterface_in], swiftinterface_out, framework_manifest, headers_mapping)
swiftdoc_out = _framework_packaging_single(ctx, "swiftdoc", [swiftdoc_in], swiftdoc_out, framework_manifest, headers_mapping)
infoplist_out = _framework_packaging_single(ctx, "infoplist", [infoplist_in], infoplist_out, framework_manifest, headers_mapping)
symbol_graph_out = _framework_packaging_single(ctx, "symbol_graph", [symbol_graph_in], symbol_graph_out, framework_manifest, headers_mapping)

outputs = struct(
binary = binary_out,
Expand Down Expand Up @@ -1299,6 +1350,7 @@ The C++ toolchain from which linking flags and other tools needed by the Swift
toolchain (such as `clang`) will be retrieved.
""",
),
"headers_mapping": attr.label_keyed_string_dict(allow_files=True),
},
doc = "Packages compiled code into an Apple .framework package",
)
2 changes: 1 addition & 1 deletion rules/framework/vfs_overlay.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ def make_vfsoverlay(ctx, hdrs, module_map, private_hdrs, has_swift, swiftmodules
vfs_info = _make_vfs_info(framework_name, data)
if merge_vfsoverlays:
vfs_info = _merge_vfs_infos(vfs_info, merge_vfsoverlays)
roots = _roots_from_datas(vfs_prefix, target_triple, vfs_info.values() + [data])
roots = _roots_from_datas(vfs_prefix, target_triple, vfs_info.values())
else:
roots = _make_root(
vfs_prefix = vfs_prefix,
Expand Down
127 changes: 127 additions & 0 deletions rules/header_paths.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
load("@bazel_skylib//lib:paths.bzl", "paths")

def _stringify_mapping(headers_mapping):
"""
Convert a mapping of {Target: string} to a mapping of {string:string}

- Target is a target pointing to a single source file
- the value in `headers_mapping` is the path within Headers/PrivateHeaders to use
for the file.
"""
ret = {}
for (t, dest) in headers_mapping.items():
files = t.files.to_list()
if len(files) != 1:
fail("{} should be a single file".format(t))
f = files[0]
if not f.is_source:
fail("{} should be a single source file, not a generated file".format(f))
ret[f.owner.name] = dest
return ret

def _get_mapped_path(hdr, headers_mapping):
"""
Get the relative destination path for a File

This will return the value of mapping, or the basename of the file if no
mapping exists or if `hdr` is not a source file.
"""
if hdr.is_source:
return headers_mapping.get(hdr.owner.name, hdr.basename)
else:
return hdr.basename

def _get_string_mapped_path(hdr, headers_mapping):
return headers_mapping.get(hdr, paths.basename(hdr))

def _mapped_without_prefix(files, prefix):
"""
Convert a list of source files to a mapping of those files -> paths with
`prefix` removed
"""
ret = {}
for file in files:
if file.startswith(prefix):
ret[file] = file[len(prefix):]
else:
fail("File `{}` does not start with prefix `{}`".format(file, prefix))
return ret

def _glob_and_strip_prefix(src_dirs, suffix = ".h"):
"""
Takes a list of directories, and converts them to a mapping of src -> dest

For each src_dir, all files ending in `suffix`, recursively, are remapped.
The remapping is from the original path to the last component of `src_dir` +
the remainder of that file's path. The most topologically deep path gets
precedence in the returned dictionary.

For example, for ReactCommon,
`glob_and_strip_prefix(["react", "react/renderer/imagemanager/platform/ios/react"])`

might return something like:

{
...
# Matched from src_dir = "react", glob "react/**/*.h"
"react/debug/flags.h": "react/debug/flags.h",
...
# Matched from src_dir = "react/renderer/imagemanager/platform/ios/react"
# glob "react/renderer/imagemanager/platform/ios/react/**/*.h"
"react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.h": "react/renderer/imagemanager/RCTImageManager.h",
...
}

This is mostly useful for merging several directories of globs into a single
mapping for use by `cc_headers_symlinks` in `@rules_ios//:apple_static_library.bzl`
"""
ret = {}
for src_dir in sorted(src_dirs):
top_level = paths.basename(src_dir)
prefix = paths.dirname(src_dir)
to_strip = len(prefix) + 1 if prefix else 0
for file in native.glob(["{}/**/*{}".format(src_dir, suffix)]):
ret[file] = file[to_strip:]
return ret

def _strip_prefix(prefix, attrs=("srcs", "private_headers", "public_headers")):
"""
When remapping headers, strip the given prefix from the destination for all headers
in the specified attrs.

Only supported as the headers_mapping attribute in apple_framework
"""
return struct(pattern = prefix, attrs = attrs, op = "strip")

def _add_prefix(prefix, attrs=("srcs", "private_headers", "public_headers")):
"""
When remapping headers, add the given prefix to the destination for all headers
in the specified attrs.

Only supported as the headers_mapping attribute in apple_framework
"""
return struct(pattern = prefix, attrs = attrs, op = "add")

def _identity_mapping(attrs=("srcs", "private_headers", "public_headers")):
"""
Create a mapping that ensures that paths are not changed

Normally apple_framework moves all headers to a Headers/PrivateHeaders directory,
and often we just to make sure the files stay put in those directories with the
full on-filesystem layout.

This replaces doing something like `{h: h for h in HEADERS}`
"""
return struct(pattern = "", attrs = attrs, op = "strip")


header_paths = struct(
stringify_mapping = _stringify_mapping,
get_mapped_path = _get_mapped_path,
get_string_mapped_path = _get_string_mapped_path,
mapped_without_prefix = _mapped_without_prefix,
glob_and_strip_prefix = _glob_and_strip_prefix,
add_prefix = _add_prefix,
strip_prefix = _strip_prefix,
identity_mapping = _identity_mapping,
)
Loading
Loading