diff --git a/patches/build_bazel_rules_apple.patch b/patches/build_bazel_rules_apple.patch new file mode 100644 index 0000000..0fa44d0 --- /dev/null +++ b/patches/build_bazel_rules_apple.patch @@ -0,0 +1,43 @@ +diff --git a/apple/internal/ios_rules.bzl b/apple/internal/ios_rules.bzl +index 9b0d1270..73dfb375 100644 +--- a/apple/internal/ios_rules.bzl ++++ b/apple/internal/ios_rules.bzl +@@ -927,6 +927,7 @@ def _ios_extension_impl(ctx): + attr = ctx.attr, + res_attrs = [ + "app_icons", ++ "resources", + "strings", + ], + ) +diff --git a/tools/plisttool/plisttool.py b/tools/plisttool/plisttool.py +index ecc84f28..2cd295cc 100644 +--- a/tools/plisttool/plisttool.py ++++ b/tools/plisttool/plisttool.py +@@ -294,15 +294,26 @@ ENTITLEMENTS_VALUE_NOT_IN_LIST = ( + + _ENTITLEMENTS_TO_VALIDATE_WITH_PROFILE = ( + 'aps-environment', ++ 'com.apple.developer.applesignin', ++ 'com.apple.developer.carplay-audio', ++ 'com.apple.developer.carplay-charging', ++ 'com.apple.developer.carplay-maps', ++ 'com.apple.developer.carplay-messaging', ++ 'com.apple.developer.carplay-parking', ++ 'com.apple.developer.carplay-quick-ordering', ++ 'com.apple.developer.playable-content', + 'com.apple.developer.networking.wifi-info', + 'com.apple.developer.passkit.pass-presentation-suppression', + 'com.apple.developer.payment-pass-provisioning', ++ 'com.apple.developer.proximity-reader.payment.acceptance', + 'com.apple.developer.siri', + 'com.apple.developer.usernotifications.critical-alerts', + 'com.apple.developer.usernotifications.time-sensitive', + # Keys which have a list of potential values in the profile, but only one in + # the entitlements that must be in the profile's list of values + 'com.apple.developer.devicecheck.appattest-environment', ++ 'com.apple.storekit.request-data', ++ 'com.apple.developer.storekit.request-data', + ) + + ENTITLEMENTS_BETA_REPORTS_ACTIVE_MISMATCH = ( diff --git a/patches/build_bazel_rules_apple_filesystem.patch b/patches/build_bazel_rules_apple_filesystem.patch new file mode 100644 index 0000000..2dc9119 --- /dev/null +++ b/patches/build_bazel_rules_apple_filesystem.patch @@ -0,0 +1,49 @@ +diff --git a/tools/bundletool/bundletool_experimental.py b/tools/bundletool/bundletool_experimental.py +index 78f4923e..ab6d1d19 100644 +--- a/tools/bundletool/bundletool_experimental.py ++++ b/tools/bundletool/bundletool_experimental.py +@@ -46,21 +46,23 @@ following keys: + bundle is complete but before it is signed. + """ + ++import errno + import filecmp + import json + import os + import shutil + import sys + import zipfile +-from ctypes import cdll, c_char_p, c_int ++from ctypes import CDLL, c_char_p, c_int, get_errno + + _CLONEFILE = None ++_USE_CLONEFILE = sys.platform == "darwin" + def _load_clonefile(): + global _CLONEFILE + if _CLONEFILE: + return _CLONEFILE + +- system = cdll.LoadLibrary('/usr/lib/libSystem.dylib') ++ system = CDLL('/usr/lib/libSystem.dylib', use_errno=True) + _CLONEFILE = system.clonefile + _CLONEFILE.argtypes = [c_char_p, c_char_p, c_int] # src, dest, flags + _CLONEFILE.restype = c_int # 0 on success +@@ -212,11 +214,16 @@ class Bundler(object): + raise BundleConflictError(dest) + + self._makedirs_safely(os.path.dirname(full_dest)) +- if sys.platform == "darwin": ++ global _USE_CLONEFILE ++ if _USE_CLONEFILE: + clonefile = _load_clonefile() + result = clonefile(src.encode(), full_dest.encode(), 0) + if result != 0: +- raise Exception(f"failed to clonefile {src} to {full_dest}") ++ if get_errno() in (errno.EXDEV, errno.ENOTSUP): ++ _USE_CLONEFILE = False ++ shutil.copy(src, full_dest) ++ else: ++ raise Exception(f"failed to clonefile {src} to {full_dest}") + else: + shutil.copy(src, full_dest) + os.chmod(full_dest, 0o755 if executable else 0o644) diff --git a/rules/framework.bzl b/rules/framework.bzl index 050e685..420824b 100644 --- a/rules/framework.bzl +++ b/rules/framework.bzl @@ -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") @@ -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", @@ -43,6 +46,30 @@ _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, @@ -50,6 +77,7 @@ def apple_framework( infoplists_by_build_setting = {}, xcconfig = {}, xcconfig_by_build_setting = {}, + headers_mapping = {}, **kwargs): """Builds and packages an Apple framework. @@ -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} @@ -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 ) @@ -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 ) @@ -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]: @@ -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, @@ -331,6 +379,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) @@ -403,7 +452,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 @@ -413,7 +463,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 @@ -460,19 +511,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, @@ -1344,6 +1395,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", ) diff --git a/rules/header_paths.bzl b/rules/header_paths.bzl new file mode 100644 index 0000000..1e69820 --- /dev/null +++ b/rules/header_paths.bzl @@ -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, +) diff --git a/rules/hmap.bzl b/rules/hmap.bzl index 8e26bb7..7c3a828 100644 --- a/rules/hmap.bzl +++ b/rules/hmap.bzl @@ -17,7 +17,9 @@ def _make_hmap(actions, headermap_builder, output, namespace, hdrs_lists): headermap_builder: an executable pointing to @bazel_build_rules_ios//rules/hmap:hmaptool output: the output file that will contain the built hmap namespace: the prefix to be used for header imports - hdrs_lists: an array of enumerables containing headers to be added to the hmap + hdrs_lists: an array of enumerables containing a tuple of (src, dest) where + src is the file to be added to the hmap, and dest is the relative + path where this header should be added to the hmap """ args = actions.args() @@ -27,7 +29,9 @@ def _make_hmap(actions, headermap_builder, output, namespace, hdrs_lists): args.add("--output", output) for hdrs in hdrs_lists: - args.add_all(hdrs) + for hdr in hdrs: + args.add(hdr[0]) + args.add(hdr[1]) args.set_param_file_format(format = "multiline") args.use_param_file("@%s") @@ -51,13 +55,21 @@ def _make_headermap_impl(ctx): :return: provider with the info for this rule """ - hdrs_lists = [ctx.files.hdrs] + hdrs_lists = [zip(ctx.files.hdrs, ctx.attr.hdr_dests)] for provider in ctx.attr.direct_hdr_providers: if apple_common.Objc in provider: - hdrs_lists.append(getattr(provider[apple_common.Objc], "direct_headers", [])) + extra_headers = [ + (s, s.basename) + for s in getattr(provider[apple_common.Objc], "direct_headers", []) + ] + hdrs_lists.append(extra_headers) if CcInfo in provider: - hdrs_lists.append(provider[CcInfo].compilation_context.direct_headers) + extra_headers = [ + (s, s.basename) + for s in provider[CcInfo].compilation_context.direct_headers + ] + hdrs_lists.append(extra_headers) if len(hdrs_lists) == 1: # means neither apple_common.Objc nor CcInfo in hdr provider target @@ -110,6 +122,10 @@ headermap = rule( allow_files = True, doc = "The list of headers included in the headermap", ), + "hdr_dests": attr.string_list( + mandatory = True, + doc = "The list of relative paths for each entry in hdrs", + ), "direct_hdr_providers": attr.label_list( mandatory = False, doc = "Targets whose direct headers should be added to the list of hdrs", diff --git a/rules/hmap/hmapbuild.c b/rules/hmap/hmapbuild.c index 5ceb688..668f2cf 100644 --- a/rules/hmap/hmapbuild.c +++ b/rules/hmap/hmapbuild.c @@ -30,7 +30,7 @@ static void debug(char *format, ...); static inline void chomp(char *s); static void add_entry(mapping **hashmap, char *key, char *value); -static void add_header(mapping **hashmap, char *name_space, char *header); +static void add_header(mapping **hashmap, char *name_space, char *header, char *dest); static void parse_args(mapping **hashmap, char **av, int ac); static void parse_param_file(mapping **hashmap, char *file); @@ -110,24 +110,21 @@ static void parse_args(mapping **entries, char **av, int ac) { av += optind; // all remaining arguments are the actual headers - for (; *av; av++) { + for (; *av; av+=2) { if (**av == '@') { // param file parse_param_file(entries, *av); continue; } - add_header(entries, cli_args.name_space, *av); + if(!*(av+1)) { + fprintf(stderr, "ERROR: header '%s' did not have a mapping as the next argument\n", *av); + } + add_header(entries, cli_args.name_space, *av, *(av+1)); } } -static void add_header(mapping **hashmap, char *name_space, char *header) { - char *bn = strdup(basename(header)); - if (bn == NULL) { - fprintf(stderr, - "Failed to parse '%s': could not extract basename: %s\n", - header, strerror(errno)); - exit(1); - } +static void add_header(mapping **hashmap, char *name_space, char *header, char* dest) { + char *bn = strdup(dest); add_entry(hashmap, bn, strdup(header)); if (name_space) { char *key = NULL; diff --git a/rules/library.bzl b/rules/library.bzl index 2bf39f6..6678d95 100644 --- a/rules/library.bzl +++ b/rules/library.bzl @@ -16,6 +16,7 @@ load("//rules/library:resources.bzl", "wrap_resources_in_filegroup") load("//rules/library:xcconfig.bzl", "copts_by_build_setting_with_defaults") load("//rules:import_middleman.bzl", "import_middleman") load("//rules:utils.bzl", "bundle_identifier_for_bundle") +load("//rules:header_paths.bzl", "header_paths") PrivateHeadersInfo = provider( doc = "Propagates private headers, so they can be accessed if necessary", @@ -104,13 +105,14 @@ extend_modulemap = rule( doc = "Extends a modulemap with a Swift submodule", ) -def _write_modulemap(name, library_tools, umbrella_header = None, public_headers = [], private_headers = [], module_name = None, framework = False, **kwargs): +def _write_modulemap(name, library_tools, umbrella_header = None, public_headers = [], private_headers = [], module_name = None, framework = False, system_module = True, **kwargs): basename = "{}.modulemap".format(name) destination = paths.join(name + "-modulemap", basename) + system_module_text = "[system] " if system_module else "" if not module_name: module_name = name content = """\ -module {module_name} {{ +module {module_name} {system_module_text} {{ umbrella header "{umbrella_header}" export * @@ -119,6 +121,7 @@ module {module_name} {{ """.format( module_name = module_name, umbrella_header = umbrella_header, + system_module_text = system_module_text, ) if framework: content = "framework " + content @@ -135,9 +138,12 @@ def _write_umbrella_header( name, generate_default_umbrella_header, public_headers = [], - module_name = None): + module_name = None, + headers_mapping = {}, + objc_public_headers = None): basename = "{name}-umbrella.h".format(name = name) destination = paths.join(name + "-modulemap", basename) + if not module_name: module_name = name @@ -163,8 +169,21 @@ def _write_umbrella_header( """ - for header in public_headers: - content += "#import \"{header}\"\n".format(header = paths.basename(header)) + if objc_public_headers != None: + content += "#if defined(__cplusplus)\n" + + for header in public_headers: + content += "#import \"{header}\"\n".format(header = header_paths.get_string_mapped_path(header, headers_mapping)) + + content += "#else\n" + + for header in objc_public_headers: + content += "#import \"{header}\"\n".format(header = header_paths.get_string_mapped_path(header, headers_mapping)) + + content += "#endif" + else: + for header in public_headers: + content += "#import \"{header}\"\n".format(header = header_paths.get_string_mapped_path(header, headers_mapping)) content += """ {extern_keyword} double {module_name}VersionNumber; @@ -476,6 +495,8 @@ def apple_library( xcconfig_by_build_setting = {}, objc_defines = [], swift_defines = [], + headers_mapping = {}, + objc_public_headers = None, **kwargs): """Create libraries for native source code on Apple platforms. @@ -499,6 +520,13 @@ def apple_library( the respective bazel build setting is resolved during the analysis phase. objc_defines: A list of Objective-C defines to add to the compilation command line. They should be in the form KEY=VALUE or simply KEY and are passed not only to the compiler for this target (as copts are) but also to all objc_ dependers of this target. swift_defines: A list of Swift defines to add to the compilation command line. Swift defines do not have values, so strings in this list should be simple identifiers and not KEY=VALUE pairs. (only expections are KEY=1 and KEY=0). These flags are added for the target and every target that depends on it. + headers_mapping: A mapping of {str: str}, where the key is a path to a header, + and the value where that header should be placed in hmaps, + umbrella headers, etc. + objc_public_headers: If not `None`, then a list of headers from `public_headers` + that are safe for objective c to consume. Anything other + headers in `public_headers` will be gated in the umbrella + header on the __cplusplus macro **kwargs: keyword arguments. Returns: @@ -599,6 +627,7 @@ def apple_library( testonly = kwargs.pop("testonly", False) features = kwargs.pop("features", []) extension_safe = kwargs.pop("extension_safe", None) + system_module = kwargs.pop("system_module", True) # Set extra linkopt for application extension safety if extension_safe: @@ -606,6 +635,7 @@ def apple_library( objc_copts += ["-fapplication-extension"] swift_copts += ["-application-extension"] + # Collect the swift_library related kwargs, these are typically only set when provided to allow # for wider compatibility with rule_swift versions. swift_kwargs = {arg: kwargs.pop(arg) for arg in _SWIFT_LIBRARY_KWARGS if arg in kwargs} @@ -839,6 +869,8 @@ def apple_library( generate_default_umbrella_header = generate_default_umbrella_header, public_headers = objc_hdrs, module_name = module_name, + headers_mapping = headers_mapping, + objc_public_headers = objc_public_headers, ) if umbrella_header: objc_hdrs.append(umbrella_header) @@ -850,6 +882,7 @@ def apple_library( private_headers = objc_private_hdrs, module_name = module_name, framework = True, + system_module = system_module, **kwargs ) @@ -888,7 +921,12 @@ def apple_library( if len(objc_hdrs) > 0: public_hmap_name = name + "_public_hmap" + public_hdrs = objc_hdrs + public_hdrs_dests = [ + header_paths.get_string_mapped_path(header, headers_mapping) + for header in public_hdrs + ] # Public hmaps are for vendored static libs to export their header only. # Other dependencies' headermaps will be generated by li_ios_framework # rules. @@ -897,20 +935,28 @@ def apple_library( namespace = namespace, hdrs = objc_hdrs, tags = _MANUAL, + hdr_dests = public_hdrs_dests, ) private_dep_names.append(public_hmap_name) - additional_objc_copts, additional_swift_copts, additional_cc_copts = _append_headermap_copts(public_hmap_name, "-I", additional_objc_copts, additional_swift_copts, additional_cc_copts) + additional_objc_copts, additional_swift_copts, additional_cc_copts = _append_headermap_copts(public_hmap_name, "-idirafter", additional_objc_copts, additional_swift_copts, additional_cc_copts) if len(objc_non_exported_hdrs + objc_private_hdrs) > 0: private_hmap_name = name + "_private_hmap" + private_hdrs = objc_non_exported_hdrs + objc_private_hdrs + objc_hdrs + private_hdrs_dests = [ + header_paths.get_string_mapped_path(header, headers_mapping) + for header in private_hdrs + ] + headermap( name = private_hmap_name, hdrs = objc_non_exported_hdrs + objc_private_hdrs, + hdr_dests = private_hdrs_dests, tags = _MANUAL, ) private_dep_names.append(private_hmap_name) - additional_objc_copts, additional_swift_copts, additional_cc_copts = _append_headermap_copts(private_hmap_name, "-I", additional_objc_copts, additional_swift_copts, additional_cc_copts) + additional_objc_copts, additional_swift_copts, additional_cc_copts = _append_headermap_copts(private_hmap_name, "-idirafter", additional_objc_copts, additional_swift_copts, additional_cc_copts) ## END HMAP @@ -931,7 +977,7 @@ def apple_library( additional_swift_copts += ["-swift-version", swift_version] if has_swift_sources: - additional_swift_copts += ["-Xcc", "-I."] + additional_swift_copts.extend(("-Xcc", "-idirafter.")) swiftc_inputs = other_inputs + objc_hdrs + objc_private_hdrs if module_map: # Frameworks find the modulemap file via the framework vfs overlay @@ -964,7 +1010,11 @@ def apple_library( module_name = module_name, tags = _MANUAL, ) + # NOTE(nmj): Need to make sure that both the extended and the regular + # modulemap are included as inputs, otherwise swiftc compilation + # will fail. module_map = "%s.extended.modulemap" % name + swiftc_inputs.append(module_map) # Note: this needs to go here, in order to virtualize the extended module framework_vfs_overlay( @@ -1039,6 +1089,7 @@ def apple_library( name = swift_doublequote_hmap_name, namespace = namespace, hdrs = [], + hdr_dests = [], direct_hdr_providers = [swift_libname], tags = _MANUAL, testonly = testonly, @@ -1052,6 +1103,7 @@ def apple_library( name = swift_angle_bracket_hmap_name, namespace = namespace, hdrs = [], + hdr_dests = [], direct_hdr_providers = [swift_libname], tags = _MANUAL, testonly = testonly, @@ -1060,7 +1112,7 @@ def apple_library( additional_objc_copts, additional_swift_copts, additional_cc_copts = _append_headermap_copts(swift_angle_bracket_hmap_name, "-I", additional_objc_copts, additional_swift_copts, additional_cc_copts) if cpp_sources: - additional_cc_copts.append("-I.") + additional_cc_copts.append("-idirafter.") native.objc_library( name = cpp_libname, srcs = cpp_sources + objc_private_hdrs + objc_non_exported_hdrs, @@ -1074,7 +1126,7 @@ def apple_library( ) lib_names.append(cpp_libname) - additional_objc_copts.append("-I.") + additional_objc_copts.append("-idirafter.") index_while_building_objc_copts = select({ "@build_bazel_rules_ios//:use_global_index_store": [ # Note: this won't work work for remote caching yet. It uses a diff --git a/rules/repositories.bzl b/rules/repositories.bzl index 0373e47..4d2ec93 100644 --- a/rules/repositories.bzl +++ b/rules/repositories.bzl @@ -92,8 +92,8 @@ def _rules_ios_bzlmod_dependencies(): _maybe( http_archive, name = "build_bazel_rules_apple", - sha256 = "b4df908ec14868369021182ab191dbd1f40830c9b300650d5dc389e0b9266c8d", - url = "https://github.com/bazelbuild/rules_apple/releases/download/3.5.1/rules_apple.3.5.1.tar.gz", + sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + url = "https://github.com/bazelbuild/rules_apple/releases/download/3.16.1/rules_apple.3.16.1.tar.gz", ) _maybe( http_archive, diff --git a/rules/static_library.bzl b/rules/static_library.bzl new file mode 100644 index 0000000..eac11a2 --- /dev/null +++ b/rules/static_library.bzl @@ -0,0 +1,196 @@ +load("@bazel_skylib//lib:paths.bzl", "paths") +load("//rules:library.bzl", "apple_library") + +def apple_static_library( + name, + apple_library = apple_library, + public_headers = [], + public_headers_to_name = {}, + deps = [], + visibility = [], + testonly = False, + system_module = True, + **kwargs): + + """ + A thin wrapper around `apple_library` that exposes a single target to depend on + and allows additionally exposing renamed headers. + + public_headers_to_name is a dictionary that maps input files to their output + location within the link tree. This link tree then is added as a dep to the + final library. This allows us to effectively "move" headers and #include them + at whatever path we want. + + Every other attribute is passed through to applie_library/objc_library as is + from **kwargs. + + Examples: + In the case below, we declare "include/fmt/chorno.h" as a public header, + but we remap it so that '#include ' works. + + ``` + apple_static_library( + name = "Texture", + srcs = glob([ + "Source/**/*.h", + "Source/**/*.mm", + "Source/TextKit/*.h", + ]), + module_name = "AsyncDisplayKit", + platforms = {"ios": "9.0"}, + public_headers = [ + "Source/ASBlockTypes.h", + "Source/ASButtonNode+Private.h", + "Source/ASButtonNode+Yoga.h", + "Source/ASButtonNode.h", + "Source/ASCellNode.h", + "Source/ASCollectionNode+Beta.h", + "Source/ASCollectionNode.h", + "Source/ASCollections.h", + "Source/ASCollectionView.h", + "Source/ASCollectionViewLayoutFacilitatorProtocol.h", + "Source/ASCollectionViewProtocols.h", + "Source/ASConfiguration.h", + "Source/ASConfigurationDelegate.h", + "Source/ASConfigurationInternal.h", + "Source/ASContextTransitioning.h", + "Source/ASControlNode+Subclasses.h", + "Source/ASControlNode.h", + "Source/ASDisplayNode+Beta.h", + "Source/ASDisplayNode+Convenience.h", + "Source/ASDisplayNode+InterfaceState.h", + "Source/ASDisplayNode+LayoutSpec.h", + "Source/ASDisplayNode+Subclasses.h", + "Source/ASDisplayNode+Yoga.h", + ... + "Source/Details/ASAbstractLayoutController.h", + "Source/Details/ASBasicImageDownloader.h", + "Source/Details/ASBatchContext.h", + ... + "Source/Debug/AsyncDisplayKit+Tips.h", + "Source/TextKit/ASTextNodeTypes.h", + "Source/TextKit/ASTextKitComponents.h", + ], + public_headers_to_name = { + "Source/ASBlockTypes.h": "Texture/AsyncDisplayKit/ASBlockTypes.h", + "Source/ASButtonNode+Private.h": "Texture/AsyncDisplayKit/ASButtonNode+Private.h", + "Source/ASButtonNode+Yoga.h": "Texture/AsyncDisplayKit/ASButtonNode+Yoga.h", + ... + "Source/Details/ASAbstractLayoutController.h": "Texture/AsyncDisplayKit/ASAbstractLayoutController.h", + "Source/Details/ASBasicImageDownloader.h": "Texture/AsyncDisplayKit/ASBasicImageDownloader.h", + "Source/Details/ASBatchContext.h": "Texture/AsyncDisplayKit/ASBatchContext.h", + ... + "Source/Debug/AsyncDisplayKit+Tips.h": "Texture/AsyncDisplayKit/AsyncDisplayKit+Tips.h", + "Source/TextKit/ASTextNodeTypes.h": "Texture/AsyncDisplayKit/ASTextNodeTypes.h", + "Source/TextKit/ASTextKitComponents.h": "Texture/AsyncDisplayKit/ASTextKitComponents.h", + }, + sdk_dylibs = ["c++"], + sdk_frameworks = [ + "AVFoundation", + "AssetsLibrary", + "CoreLocation", + "CoreMedia", + "MapKit", + "Photos", + ], + visibility = ["//visibility:public"], + xcconfig = { + "CLANG_CXX_LANGUAGE_STANDARD": "c++11", + "CLANG_CXX_LIBRARY": "libc++", + "GCC_PREPROCESSOR_DEFINITIONS": [ + "AS_USE_ASSETS_LIBRARY=1", + "AS_USE_MAPKIT=1", + "AS_USE_PHOTOS=1", + "AS_USE_VIDEO=1", + ], + }, + deps = ["@PINRemoteImage"], + ) + ``` + """ + + extra_deps = [] + + # TODO(nmj): We'll likely need to add a way to set up a private headers link tree + # that others can include as a dependency too. This is just a thing + # that some Pods do. + if public_headers_to_name: + public_headers_symlinks_name = "{}_public_headers_symlinks".format(name) + cc_headers_symlinks( + name = public_headers_symlinks_name, + hdrs = public_headers_to_name, + system_module = system_module, + visibility = visibility, + ) + extra_deps.append(public_headers_symlinks_name) + + library = apple_library( + name = name, + public_headers = public_headers, + visibility=visibility, + testonly=testonly, + deps = deps + extra_deps, + system_module = system_module, + **kwargs + ) + + native.objc_library( + name = name, + deps = library.deps, + data = library.data if library.data else [], + linkopts = library.linkopts, + testonly = kwargs.get("testonly", False), + visibility = visibility, + ) + +def _cc_headers_symlinks_impl(ctx): + if not ctx.attr.hdrs: + return [] + + outputs = [] + public_headers_dir = None + for hdr, sub_path in ctx.attr.hdrs.items(): + output = ctx.actions.declare_file(paths.join(ctx.attr.name, sub_path)) + outputs.append(output) + if public_headers_dir == None: + public_headers_dir = output.path[:-(len(sub_path) + 1)] + ctx.actions.symlink(output = output, target_file = hdr.files.to_list()[0]) + + output_depset = depset(outputs) + if ctx.attr.system_module: + compilation_context = cc_common.create_compilation_context( + headers = output_depset, + system_includes = depset([ + public_headers_dir, + ]), + ) + else: + compilation_context = cc_common.create_compilation_context( + headers = output_depset, + includes = depset([ + public_headers_dir, + ]), + ) + return [ + DefaultInfo(files = output_depset), + CcInfo(compilation_context = compilation_context), + ] + +cc_headers_symlinks = rule( + implementation = _cc_headers_symlinks_impl, + doc = ( + "Rule that actually creates symlink trees for header remapping. " + + "Exports them with -I or -isystem and can be taken as a dep in " + + "rules that handle CcInfo" + ), + attrs = { + "hdrs": attr.label_keyed_string_dict( + allow_files = True, + doc = "Mapping of source file paths to relative path where the file should life in the link tree" + ), + "system_module": attr.bool( + default = True, + doc = "Whether the symlink headers dir should be exported as a system_include" + ), + }, +)