From 311cc836de756eceece623267837162c9ca4a6f5 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Tue, 17 Jan 2023 14:09:36 -0800 Subject: [PATCH] Add Apple CC toolchain setup (#113) This migrates the Apple CC toolchain from bazel into this repo. This allows this to evolve and be updated without a full bazel udpate. Bazel's built in unix toolchain can still be used to build macOS C++ targets, but this repo handles a wider variety of Apple specific flags and requires you have Xcode installed. --- .bazelci/presubmit.yml | 32 +- BUILD | 2 + MODULE.bazel | 5 + README.md | 69 +- constraints/BUILD | 8 + crosstool/BUILD | 22 + crosstool/BUILD.toolchains | 100 + crosstool/BUILD.tpl | 134 ++ crosstool/cc_toolchain_config.bzl | 2845 ++++++++++++++++++++++++++ crosstool/libtool.sh | 141 ++ crosstool/libtool_check_unique.cc | 109 + crosstool/make_hashed_objlist.py | 58 + crosstool/osx_cc_configure.bzl | 234 +++ crosstool/setup.bzl | 109 + crosstool/wrapped_clang.cc | 429 ++++ crosstool/xcrunwrapper.sh | 44 + lib/repositories.bzl | 4 + test/shell/BUILD | 57 + test/shell/apple_common.sh | 203 ++ test/shell/apple_test.sh | 408 ++++ test/shell/integration_test_setup.sh | 55 + test/shell/objc_test.sh | 173 ++ test/shell/testenv.sh | 26 + test/shell/unittest.bash | 846 ++++++++ test/shell/unittest_utils.sh | 181 ++ test/shell/wrapped_clang_test.sh | 99 + 26 files changed, 6376 insertions(+), 17 deletions(-) create mode 100644 crosstool/BUILD create mode 100644 crosstool/BUILD.toolchains create mode 100644 crosstool/BUILD.tpl create mode 100644 crosstool/cc_toolchain_config.bzl create mode 100755 crosstool/libtool.sh create mode 100644 crosstool/libtool_check_unique.cc create mode 100644 crosstool/make_hashed_objlist.py create mode 100644 crosstool/osx_cc_configure.bzl create mode 100644 crosstool/setup.bzl create mode 100644 crosstool/wrapped_clang.cc create mode 100755 crosstool/xcrunwrapper.sh create mode 100644 test/shell/BUILD create mode 100644 test/shell/apple_common.sh create mode 100755 test/shell/apple_test.sh create mode 100755 test/shell/integration_test_setup.sh create mode 100755 test/shell/objc_test.sh create mode 100755 test/shell/testenv.sh create mode 100644 test/shell/unittest.bash create mode 100644 test/shell/unittest_utils.sh create mode 100755 test/shell/wrapped_clang_test.sh diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 2463212..78c35a6 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -5,9 +5,19 @@ x_defaults: common: &common platform: macos build_targets: - - "//..." + - "//..." test_targets: - - "//..." + - "//..." + + toolchain_flags: &toolchain_flags + build_flags: + - "--apple_crosstool_top=@local_config_apple_cc//:toolchain" + - "--crosstool_top=@local_config_apple_cc//:toolchain" + - "--host_crosstool_top=@local_config_apple_cc//:toolchain" + test_flags: + - "--apple_crosstool_top=@local_config_apple_cc//:toolchain" + - "--crosstool_top=@local_config_apple_cc//:toolchain" + - "--host_crosstool_top=@local_config_apple_cc//:toolchain" tasks: macos_latest: @@ -24,9 +34,21 @@ tasks: name: "Latest Bazel with Head Deps" bazel: latest shell_commands: - # Update the WORKSPACE to use head versions of some deps to ensure nothing - # has landed on them breaking this project. - - .bazelci/update_workspace_to_deps_heads.sh + # Update the WORKSPACE to use head versions of some deps to ensure nothing + # has landed on them breaking this project. + - .bazelci/update_workspace_to_deps_heads.sh + <<: *common + + macos_latest_vendored_toolchain: + name: "Latest Bazel vendored toolchain" + bazel: latest + <<: *common + <<: *toolchain_flags + + macos_last_green_vendored_toolchain: + name: "Last Green Bazel vendored toolchain" + bazel: last_green <<: *common + <<: *toolchain_flags buildifier: latest diff --git a/BUILD b/BUILD index f7cf094..e6f1767 100644 --- a/BUILD +++ b/BUILD @@ -24,6 +24,8 @@ filegroup( testonly = 1, srcs = [ "WORKSPACE", + "//constraints:for_bazel_tests", + "//crosstool:for_bazel_tests", "//lib:for_bazel_tests", "//rules:for_bazel_tests", "//tools:for_bazel_tests", diff --git a/MODULE.bazel b/MODULE.bazel index c8d6c69..ca4e67a 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -12,3 +12,8 @@ module( bazel_dep(name = "bazel_skylib", version = "1.3.0") bazel_dep(name = "platforms", version = "0.0.4") bazel_dep(name = "stardoc", version = "0.5.3", dev_dependency = True, repo_name = "io_bazel_stardoc") + +apple_cc_configure = use_extension("//crosstool:setup.bzl", "apple_cc_configure_extension") +use_repo(apple_cc_configure, "local_config_apple_cc", "local_config_apple_cc_toolchains") + +register_toolchains("@local_config_apple_cc_toolchains//:all") diff --git a/README.md b/README.md index 7e72caf..97be08b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,59 @@ # Apple Support for [Bazel](https://bazel.build) -This repository contains helper methods that support building rules that -target Apple platforms. See [the -docs](https://github.com/bazelbuild/apple_support/tree/master/doc) for -how you can use these helpers. Also see -[rules_apple](https://github.com/bazelbuild/rules_apple) and -[rules_swift](https://github.com/bazelbuild/rules_swift) for more Apple -platform rules. - -## Quick setup - -Copy the `WORKSPACE` snippet from [the releases -page](https://github.com/bazelbuild/apple_support/releases). +This repository contains the [Apple CC toolchain](#toolchain-setup), +Apple related [platforms](platforms/BUILD) and +[constraints](constraints/BUILD) definitions, and small helper functions +for rules authors targeting Apple platforms. + +If you want to build iOS, tvOS, watchOS, or macOS apps, use +[`rules_apple`][rules_apple]. + +If you want to build Swift use +[`rules_swift`](https://github.com/bazelbuild/rules_swift). + +See [the documentation](doc) for the helper rules provided by this +repository. + +## Installation + +Copy the `MODULE.bazel` or `WORKSPACE` snippets from [the releases +page](https://github.com/bazelbuild/apple_support/releases) into your +project. + +## Toolchain setup + +The Apple CC toolchain in by this repository provides toolchains for +building for Apple platforms besides macOS. Since Bazel 7 this toolchain +is required when targeting those platforms but the toolchain also +supports Bazel 6. + +To use the Apple CC toolchain, pull this repository into your build and +add this to your `.bazelrc`: + +```bzl +build --enable_platform_specific_config +build:macos --apple_crosstool_top=@local_config_apple_cc//:toolchain +build:macos --crosstool_top=@local_config_apple_cc//:toolchain +build:macos --host_crosstool_top=@local_config_apple_cc//:toolchain +``` + +This ensures that all rules provided by [`rules_apple`][rules_apple], as +well as other rules like `cc_binary`, all use the toolchain provided by +this repository when building on macOS. + +NOTE: This toolchain requires a full Xcode installation, not just the +Xcode Command Line Tools. If you only need to build for macOS and don't +want to require a full Xcode installation in your build, use the builtin +Unix toolchain provided by Bazel. + +### Incompatible toolchain resolution + +Bazel is currently working on migrating C++ toolchain configuration to a +new discovery method that no longer uses the `--*crosstool_top` flags. +If you would like to test this upcoming feature, or need to use this in +your build for other reasons, you can use this toolchain with +`--incompatible_enable_cc_toolchain_resolution` as long as you provide a +`platform_mappings` file. Please file any issues you find as you test +this work in progress configuration. + +[rules_apple]: https://github.com/bazelbuild/rules_apple diff --git a/constraints/BUILD b/constraints/BUILD index b86291b..c5f742c 100644 --- a/constraints/BUILD +++ b/constraints/BUILD @@ -29,3 +29,11 @@ constraint_value( name = "catalyst", constraint_setting = ":target_environment", ) + +# Consumed by bazel tests. +filegroup( + name = "for_bazel_tests", + testonly = True, + srcs = glob(["**"]), + visibility = ["//:__pkg__"], +) diff --git a/crosstool/BUILD b/crosstool/BUILD new file mode 100644 index 0000000..de7606a --- /dev/null +++ b/crosstool/BUILD @@ -0,0 +1,22 @@ +package(default_visibility = ["//visibility:public"]) + +# Files which shouldn't be publicly visible and dependencies of all objc_* or ios_* rules should be excluded. +exports_files(glob( + ["**"], +)) + +cc_binary( + name = "wrapped_clang", + testonly = True, + srcs = [ + "wrapped_clang.cc", + ], +) + +# Consumed by bazel tests. +filegroup( + name = "for_bazel_tests", + testonly = True, + srcs = glob(["**"]), + visibility = ["//:__pkg__"], +) diff --git a/crosstool/BUILD.toolchains b/crosstool/BUILD.toolchains new file mode 100644 index 0000000..4928a5d --- /dev/null +++ b/crosstool/BUILD.toolchains @@ -0,0 +1,100 @@ +package(default_visibility = ["//visibility:public"]) + +# Target constraints for each arch. +# TODO(apple-rules): Rename osx constraint to macOS. +OSX_TOOLS_CONSTRAINTS = { + "armeabi-v7a": ["@platforms//cpu:arm"], + "darwin_arm64": [ + "@platforms//os:osx", + "@platforms//cpu:arm64", + ], + "darwin_arm64e": [ + "@platforms//os:osx", + "@platforms//cpu:arm64", + ], + "darwin_x86_64": [ + "@platforms//os:osx", + "@platforms//cpu:x86_64", + ], + "ios_arm64": [ + "@platforms//os:ios", + "@platforms//cpu:arm64", + "@build_bazel_apple_support//constraints:device", + ], + "ios_arm64e": [ + "@platforms//os:ios", + "@platforms//cpu:arm64", + ], + "ios_armv7": [ + "@platforms//os:ios", + "@platforms//cpu:armv7", + ], + "ios_i386": [ + "@platforms//os:ios", + "@platforms//cpu:i386", + ], + "ios_x86_64": [ + "@platforms//os:ios", + "@platforms//cpu:x86_64", + ], + "ios_sim_arm64": [ + "@platforms//os:ios", + "@platforms//cpu:arm64", + "@build_bazel_apple_support//constraints:simulator", + ], + "tvos_arm64": [ + "@platforms//os:tvos", + "@platforms//cpu:arm64", + "@build_bazel_apple_support//constraints:device", + ], + "tvos_x86_64": [ + "@platforms//os:tvos", + "@platforms//cpu:x86_64", + ], + "tvos_sim_arm64": [ + "@platforms//os:tvos", + "@platforms//cpu:arm64", + "@build_bazel_apple_support//constraints:simulator", + ], + "watchos_arm64": [ + "@platforms//os:watchos", + "@platforms//cpu:arm64", + ], + "watchos_arm64_32": [ + "@platforms//os:watchos", + "@platforms//cpu:arm64_32", + ], + "watchos_armv7k": [ + "@platforms//os:watchos", + "@platforms//cpu:armv7k", + ], + "watchos_i386": [ + "@platforms//os:watchos", + "@platforms//cpu:i386", + ], + "watchos_x86_64": [ + "@platforms//os:watchos", + "@platforms//cpu:x86_64", + ], +} + +OSX_DEVELOPER_PLATFORM_CPUS = [ + "arm64", + "x86_64", +] + +[ + toolchain( + name = "cc-toolchain-" + arch + "-" + cpu, + exec_compatible_with = [ + # These only execute on macOS. + "@platforms//os:osx", + "@platforms//cpu:" + cpu, + ], + target_compatible_with = OSX_TOOLS_CONSTRAINTS[arch], + toolchain = "@local_config_apple_cc//:cc-compiler-" + arch, + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", + ) + for arch in OSX_TOOLS_CONSTRAINTS.keys() + for cpu in OSX_DEVELOPER_PLATFORM_CPUS +] diff --git a/crosstool/BUILD.tpl b/crosstool/BUILD.tpl new file mode 100644 index 0000000..01ccd8e --- /dev/null +++ b/crosstool/BUILD.tpl @@ -0,0 +1,134 @@ +package(default_visibility = ["//visibility:public"]) + +OSX_TOOLS_NON_DEVICE_ARCHS = [ + "darwin_x86_64", + "darwin_arm64", + "darwin_arm64e", + "ios_i386", + "ios_x86_64", + "ios_sim_arm64", + "watchos_arm64", + "watchos_i386", + "watchos_x86_64", + "tvos_x86_64", + "tvos_sim_arm64", +] + +OSX_TOOLS_ARCHS = [ + "ios_armv7", + "ios_arm64", + "ios_arm64e", + "watchos_armv7k", + "watchos_arm64_32", + "tvos_arm64", +] + OSX_TOOLS_NON_DEVICE_ARCHS + +load(":armeabi_cc_toolchain_config.bzl", "armeabi_cc_toolchain_config") +load(":cc_toolchain_config.bzl", "cc_toolchain_config") + +CC_TOOLCHAINS = [( + cpu + "|clang", + ":cc-compiler-" + cpu, +) for cpu in OSX_TOOLS_ARCHS] + [( + cpu, + ":cc-compiler-" + cpu, +) for cpu in OSX_TOOLS_ARCHS] + [ + ("k8|clang", ":cc-compiler-darwin_x86_64"), + ("darwin|clang", ":cc-compiler-darwin_x86_64"), + ("k8", ":cc-compiler-darwin_x86_64"), + ("darwin", ":cc-compiler-darwin_x86_64"), + ("armeabi-v7a|compiler", ":cc-compiler-armeabi-v7a"), + ("armeabi-v7a", ":cc-compiler-armeabi-v7a"), +] + +cc_library( + name = "malloc", +) + +filegroup( + name = "empty", + srcs = [], +) + +filegroup( + name = "cc_wrapper", + srcs = ["cc_wrapper.sh"], +) + +cc_toolchain_suite( + name = "toolchain", + toolchains = dict(CC_TOOLCHAINS), +) + +[ + filegroup( + name = "osx_tools_" + arch, + srcs = [ + ":cc_wrapper", + ":libtool", + ":libtool_check_unique", + ":make_hashed_objlist.py", + ":wrapped_clang", + ":wrapped_clang_pp", + ":xcrunwrapper.sh", + ], + ) + for arch in OSX_TOOLS_ARCHS +] + +[ + apple_cc_toolchain( + name = "cc-compiler-" + arch, + all_files = ":osx_tools_" + arch, + ar_files = ":osx_tools_" + arch, + as_files = ":osx_tools_" + arch, + compiler_files = ":osx_tools_" + arch, + dwp_files = ":empty", + linker_files = ":osx_tools_" + arch, + objcopy_files = ":empty", + strip_files = ":osx_tools_" + arch, + supports_param_files = 1, + toolchain_config = arch, + toolchain_identifier = arch, + ) + for arch in OSX_TOOLS_ARCHS +] + +# When xcode_locator fails and causes cc_autoconf_toolchains to fall back +# to the non-Xcode C++ toolchain, it uses the legacy cpu value to refer to +# the toolchain, which is "darwin" for x86_64 macOS. +alias( + name = "cc-compiler-darwin", + actual = ":cc-compiler-darwin_x86_64", +) + +[ + cc_toolchain_config( + name = arch, + compiler = "clang", + cpu = arch, + cxx_builtin_include_directories = [ +%{cxx_builtin_include_directories} + ], + tool_paths_overrides = {%{tool_paths_overrides}}, + ) + for arch in OSX_TOOLS_ARCHS +] + +# Android tooling requires a default toolchain for the armeabi-v7a cpu. +cc_toolchain( + name = "cc-compiler-armeabi-v7a", + toolchain_identifier = "stub_armeabi-v7a", + toolchain_config = ":stub_armeabi-v7a", + all_files = ":empty", + ar_files = ":empty", + as_files = ":empty", + compiler_files = ":empty", + dwp_files = ":empty", + linker_files = ":empty", + objcopy_files = ":empty", + strip_files = ":empty", + supports_param_files = 1, +) + +armeabi_cc_toolchain_config(name = "stub_armeabi-v7a") diff --git a/crosstool/cc_toolchain_config.bzl b/crosstool/cc_toolchain_config.bzl new file mode 100644 index 0000000..8282c40 --- /dev/null +++ b/crosstool/cc_toolchain_config.bzl @@ -0,0 +1,2845 @@ +# Copyright 2019 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""A C++ toolchain configuration rule for macOS.""" + +load( + "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", + "action_config", + "artifact_name_pattern", + "env_entry", + "env_set", + "feature", + "feature_set", + "flag_group", + "flag_set", + "make_variable", + "tool", + "tool_path", + "variable_with_value", + "with_feature_set", +) +load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") + +# The Xcode version from which that has support for deterministic mode +_SUPPORTS_DETERMINISTIC_MODE = "10.2" + +def _compare_versions(dv1, v2): + """Return value is <0, 0, >0 depending on DottedVersion dv1 comparison to string v2.""" + return dv1.compare_to(apple_common.dotted_version(v2)) + +def _can_use_deterministic_libtool(ctx): + """Returns `True` if the current version of `libtool` has support for deterministic mode, and `False` otherwise.""" + xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig] + xcode_version = xcode_config.xcode_version() + if _compare_versions(xcode_version, _SUPPORTS_DETERMINISTIC_MODE) >= 0: + return True + else: + return False + +def _deterministic_libtool_flags(ctx): + """Returns additional `libtool` flags to enable deterministic mode, if they are available.""" + if _can_use_deterministic_libtool(ctx): + return ["-D"] + return [] + +def _target_os_version(ctx): + platform_type = ctx.fragments.apple.single_arch_platform.platform_type + xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig] + return xcode_config.minimum_os_for_platform_type(platform_type) + +def _impl(ctx): + target_os_version = _target_os_version(ctx) + + if (ctx.attr.cpu == "ios_arm64"): + target_system_name = "arm64-apple-ios{}".format(target_os_version) + elif (ctx.attr.cpu == "tvos_arm64"): + target_system_name = "arm64-apple-tvos{}".format(target_os_version) + elif (ctx.attr.cpu == "watchos_arm64_32"): + target_system_name = "arm64_32-apple-watchos{}".format(target_os_version) + elif (ctx.attr.cpu == "ios_arm64e"): + target_system_name = "arm64e-apple-ios{}".format(target_os_version) + elif (ctx.attr.cpu == "ios_armv7"): + target_system_name = "armv7-apple-ios{}".format(target_os_version) + elif (ctx.attr.cpu == "watchos_armv7k"): + target_system_name = "armv7k-apple-watchos{}".format(target_os_version) + elif (ctx.attr.cpu == "ios_i386"): + target_system_name = "i386-apple-ios{}-simulator".format(target_os_version) + elif (ctx.attr.cpu == "watchos_i386"): + target_system_name = "i386-apple-watchos{}-simulator".format(target_os_version) + elif (ctx.attr.cpu == "ios_x86_64"): + target_system_name = "x86_64-apple-ios{}-simulator".format(target_os_version) + elif (ctx.attr.cpu == "ios_sim_arm64"): + target_system_name = "arm64-apple-ios{}-simulator".format(target_os_version) + elif (ctx.attr.cpu == "tvos_sim_arm64"): + target_system_name = "arm64-apple-tvos{}-simulator".format(target_os_version) + elif (ctx.attr.cpu == "watchos_arm64"): + target_system_name = "arm64-apple-watchos{}-simulator".format(target_os_version) + elif (ctx.attr.cpu == "darwin_x86_64"): + target_system_name = "x86_64-apple-macosx{}".format(target_os_version) + elif (ctx.attr.cpu == "darwin_arm64"): + target_system_name = "arm64-apple-macosx{}".format(target_os_version) + elif (ctx.attr.cpu == "darwin_arm64e"): + target_system_name = "arm64e-apple-macosx{}".format(target_os_version) + elif (ctx.attr.cpu == "tvos_x86_64"): + target_system_name = "x86_64-apple-tvos{}-simulator".format(target_os_version) + elif (ctx.attr.cpu == "watchos_x86_64"): + target_system_name = "x86_64-apple-watchos{}-simulator".format(target_os_version) + else: + fail("Unreachable") + + if ctx.attr.cpu.startswith("darwin_"): + target_libc = "macosx" + else: + target_libc = ctx.attr.cpu.split("_")[0] + + if ctx.attr.cpu == "darwin_x86_64": + abi_libc_version = "darwin_x86_64" + abi_version = "darwin_x86_64" + else: + abi_libc_version = "local" + abi_version = "local" + + host_system_name = "x86_64-apple-macosx" + arch = ctx.attr.cpu.split("_", 1)[-1] + if ctx.attr.cpu in ["ios_sim_arm64", "tvos_sim_arm64", "watchos_arm64"]: + arch = "arm64" + + all_link_actions = [ + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ] + + strip_action = action_config( + action_name = ACTION_NAMES.strip, + flag_sets = [ + flag_set( + flag_groups = [ + flag_group(flags = ["-S", "-o", "%{output_file}"]), + flag_group( + flags = ["%{stripopts}"], + iterate_over = "stripopts", + ), + flag_group(flags = ["%{input_file}"]), + ], + ), + ], + tools = [tool(path = "/usr/bin/strip")], + ) + + xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig] + xcode_execution_requirements = xcode_config.execution_info().keys() + + cpp_header_parsing_action = action_config( + action_name = ACTION_NAMES.cpp_header_parsing, + implies = [ + "preprocessor_defines", + "include_system_dirs", + "objc_arc", + "no_objc_arc", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "compiler_input_flags", + "compiler_output_flags", + "unfiltered_cxx_flags", + ], + tools = [ + tool( + path = "wrapped_clang", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + objc_compile_action = action_config( + action_name = ACTION_NAMES.objc_compile, + enabled = True, + flag_sets = [ + flag_set( + flag_groups = [flag_group(flags = ["-target", target_system_name])], + ), + ], + implies = [ + "compiler_input_flags", + "compiler_output_flags", + "objc_actions", + "apply_default_compiler_flags", + "apply_default_warnings", + "framework_paths", + "preprocessor_defines", + "include_system_dirs", + "objc_arc", + "no_objc_arc", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "apply_simulator_compiler_flags", + ], + tools = [ + tool( + path = "wrapped_clang", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + objcpp_executable_action = action_config( + action_name = "objc++-executable", + flag_sets = [ + flag_set( + flag_groups = [ + flag_group(flags = ["-stdlib=libc++", "-std=gnu++11"]), + flag_group(flags = ["-target", target_system_name]), + flag_group( + flags = [ + "-Xlinker", + "-objc_abi_version", + "-Xlinker", + "2", + "-fobjc-link-runtime", + "-ObjC", + ], + ), + flag_group( + flags = ["-l%{library_names}"], + iterate_over = "library_names", + ), + flag_group(flags = ["-filelist", "%{filelist}"]), + flag_group(flags = ["-o", "%{linked_binary}"]), + flag_group( + flags = ["-force_load", "%{force_load_exec_paths}"], + iterate_over = "force_load_exec_paths", + ), + flag_group( + flags = ["%{dep_linkopts}"], + iterate_over = "dep_linkopts", + ), + flag_group( + flags = ["-Wl,%{attr_linkopts}"], + iterate_over = "attr_linkopts", + ), + ], + ), + ], + implies = [ + "include_system_dirs", + "framework_paths", + "strip_debug_symbols", + "apple_env", + "apply_implicit_frameworks", + ], + tools = [ + tool( + path = "wrapped_clang_pp", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + cpp_link_dynamic_library_action = action_config( + action_name = ACTION_NAMES.cpp_link_dynamic_library, + implies = [ + "contains_objc_source", + "has_configured_linker_path", + "shared_flag", + "linkstamps", + "output_execpath_flags", + "runtime_root_flags", + "input_param_flags", + "strip_debug_symbols", + "linker_param_file", + "apple_env", + "sysroot", + "cpp_linker_flags", + ], + tools = [ + tool( + path = "cc_wrapper.sh", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + cpp_link_static_library_action = action_config( + action_name = ACTION_NAMES.cpp_link_static_library, + implies = [ + "runtime_root_flags", + "archiver_flags", + "input_param_flags", + "linker_param_file", + "apple_env", + ], + tools = [ + tool( + path = "libtool", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + c_compile_action = action_config( + action_name = ACTION_NAMES.c_compile, + implies = [ + "preprocessor_defines", + "include_system_dirs", + "objc_arc", + "no_objc_arc", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "compiler_input_flags", + "compiler_output_flags", + "unfiltered_cxx_flags", + ], + tools = [ + tool( + path = "wrapped_clang", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + cpp_compile_action = action_config( + action_name = ACTION_NAMES.cpp_compile, + implies = [ + "preprocessor_defines", + "include_system_dirs", + "objc_arc", + "no_objc_arc", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "compiler_input_flags", + "compiler_output_flags", + "unfiltered_cxx_flags", + ], + tools = [ + tool( + path = "wrapped_clang_pp", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + objcpp_compile_action = action_config( + action_name = ACTION_NAMES.objcpp_compile, + flag_sets = [ + flag_set( + flag_groups = [ + flag_group( + flags = [ + "-target", + target_system_name, + "-stdlib=libc++", + "-std=gnu++11", + ], + ), + ], + ), + ], + implies = [ + "compiler_input_flags", + "compiler_output_flags", + "apply_default_compiler_flags", + "apply_default_warnings", + "framework_paths", + "preprocessor_defines", + "include_system_dirs", + "objc_arc", + "no_objc_arc", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "apply_simulator_compiler_flags", + ], + tools = [ + tool( + path = "wrapped_clang_pp", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + assemble_action = action_config( + action_name = ACTION_NAMES.assemble, + implies = [ + "objc_arc", + "no_objc_arc", + "include_system_dirs", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "compiler_input_flags", + "compiler_output_flags", + "unfiltered_cxx_flags", + ], + tools = [ + tool( + path = "wrapped_clang", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + preprocess_assemble_action = action_config( + action_name = ACTION_NAMES.preprocess_assemble, + implies = [ + "preprocessor_defines", + "include_system_dirs", + "objc_arc", + "no_objc_arc", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "compiler_input_flags", + "compiler_output_flags", + "unfiltered_cxx_flags", + ], + tools = [ + tool( + path = "wrapped_clang", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + objc_archive_action = action_config( + action_name = "objc-archive", + flag_sets = [ + flag_set( + flag_groups = [ + flag_group( + flags = _deterministic_libtool_flags(ctx) + [ + "-no_warning_for_no_symbols", + "-static", + "-filelist", + "%{obj_list_path}", + "-arch_only", + arch, + "-syslibroot", + "%{sdk_dir}", + "-o", + "%{output_execpath}", + ], + ), + ], + ), + ], + implies = ["apple_env"], + tools = [ + tool( + path = "libtool", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + objc_executable_action = action_config( + action_name = "objc-executable", + flag_sets = [ + flag_set( + flag_groups = [ + flag_group( + flags = [ + "-Xlinker", + "-objc_abi_version", + "-Xlinker", + "2", + "-fobjc-link-runtime", + "-ObjC", + ], + ), + ], + with_features = [with_feature_set(not_features = ["kernel_extension"])], + ), + flag_set( + flag_groups = [ + flag_group(flags = ["-target", target_system_name]), + flag_group( + flags = ["-l%{library_names}"], + iterate_over = "library_names", + ), + flag_group(flags = ["-filelist", "%{filelist}"]), + flag_group(flags = ["-o", "%{linked_binary}"]), + flag_group( + flags = ["-force_load", "%{force_load_exec_paths}"], + iterate_over = "force_load_exec_paths", + ), + flag_group( + flags = ["%{dep_linkopts}"], + iterate_over = "dep_linkopts", + ), + flag_group( + flags = ["-Wl,%{attr_linkopts}"], + iterate_over = "attr_linkopts", + ), + ], + ), + ], + implies = [ + "include_system_dirs", + "framework_paths", + "strip_debug_symbols", + "apple_env", + "apply_implicit_frameworks", + ], + tools = [ + tool( + path = "wrapped_clang", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + cpp_link_executable_action = action_config( + action_name = ACTION_NAMES.cpp_link_executable, + implies = [ + "contains_objc_source", + "linkstamps", + "output_execpath_flags", + "runtime_root_flags", + "input_param_flags", + "force_pic_flags", + "strip_debug_symbols", + "linker_param_file", + "apple_env", + "sysroot", + "cpp_linker_flags", + ], + tools = [ + tool( + path = "cc_wrapper.sh", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + linkstamp_compile_action = action_config( + action_name = ACTION_NAMES.linkstamp_compile, + implies = [ + "preprocessor_defines", + "include_system_dirs", + "objc_arc", + "no_objc_arc", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "compiler_input_flags", + "compiler_output_flags", + ], + tools = [ + tool( + path = "wrapped_clang", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + cpp_module_compile_action = action_config( + action_name = ACTION_NAMES.cpp_module_compile, + implies = [ + "preprocessor_defines", + "include_system_dirs", + "objc_arc", + "no_objc_arc", + "apple_env", + "user_compile_flags", + "sysroot", + "unfiltered_compile_flags", + "compiler_input_flags", + "compiler_output_flags", + "unfiltered_cxx_flags", + ], + tools = [ + tool( + path = "wrapped_clang", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + cpp_link_nodeps_dynamic_library_action = action_config( + action_name = ACTION_NAMES.cpp_link_nodeps_dynamic_library, + implies = [ + "contains_objc_source", + "has_configured_linker_path", + "shared_flag", + "linkstamps", + "output_execpath_flags", + "runtime_root_flags", + "input_param_flags", + "strip_debug_symbols", + "linker_param_file", + "apple_env", + "sysroot", + "cpp_linker_flags", + ], + tools = [ + tool( + path = "cc_wrapper.sh", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + objc_fully_link_action = action_config( + action_name = "objc-fully-link", + flag_sets = [ + flag_set( + flag_groups = [ + flag_group( + flags = _deterministic_libtool_flags(ctx) + [ + "-no_warning_for_no_symbols", + "-static", + "-arch_only", + arch, + "-syslibroot", + "%{sdk_dir}", + "-o", + "%{fully_linked_archive_path}", + ], + ), + flag_group( + flags = ["%{objc_library_exec_paths}"], + iterate_over = "objc_library_exec_paths", + ), + flag_group( + flags = ["%{cc_library_exec_paths}"], + iterate_over = "cc_library_exec_paths", + ), + flag_group( + flags = ["%{imported_library_exec_paths}"], + iterate_over = "imported_library_exec_paths", + ), + ], + ), + ], + implies = ["apple_env"], + tools = [ + tool( + path = "libtool", + execution_requirements = xcode_execution_requirements, + ), + ], + ) + + objcopy_embed_data_action = action_config( + action_name = "objcopy_embed_data", + enabled = True, + tools = [tool(path = "/usr/bin/objcopy")], + ) + + action_configs = [ + strip_action, + c_compile_action, + cpp_compile_action, + linkstamp_compile_action, + cpp_module_compile_action, + cpp_header_parsing_action, + objc_compile_action, + objcpp_compile_action, + assemble_action, + preprocess_assemble_action, + objc_archive_action, + objc_executable_action, + objcpp_executable_action, + cpp_link_executable_action, + cpp_link_dynamic_library_action, + cpp_link_nodeps_dynamic_library_action, + cpp_link_static_library_action, + objc_fully_link_action, + objcopy_embed_data_action, + ] + + if (ctx.attr.cpu == "ios_arm64" or + ctx.attr.cpu == "ios_arm64e" or + ctx.attr.cpu == "ios_armv7" or + ctx.attr.cpu == "ios_i386" or + ctx.attr.cpu == "ios_x86_64" or + ctx.attr.cpu == "ios_sim_arm64" or + ctx.attr.cpu == "watchos_arm64_32" or + ctx.attr.cpu == "watchos_armv7k" or + ctx.attr.cpu == "watchos_i386" or + ctx.attr.cpu == "watchos_x86_64" or + ctx.attr.cpu == "watchos_arm64"): + apply_default_compiler_flags_feature = feature( + name = "apply_default_compiler_flags", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.objc_compile, ACTION_NAMES.objcpp_compile], + flag_groups = [flag_group(flags = ["-DOS_IOS", "-fno-autolink"])], + ), + ], + ) + elif (ctx.attr.cpu == "darwin_x86_64" or + ctx.attr.cpu == "darwin_arm64" or + ctx.attr.cpu == "darwin_arm64e"): + apply_default_compiler_flags_feature = feature( + name = "apply_default_compiler_flags", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.objc_compile, ACTION_NAMES.objcpp_compile], + flag_groups = [flag_group(flags = ["-DOS_MACOSX", "-fno-autolink"])], + ), + ], + ) + elif (ctx.attr.cpu == "tvos_arm64" or + ctx.attr.cpu == "tvos_x86_64" or + ctx.attr.cpu == "tvos_sim_arm64"): + apply_default_compiler_flags_feature = feature( + name = "apply_default_compiler_flags", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.objc_compile, ACTION_NAMES.objcpp_compile], + flag_groups = [flag_group(flags = ["-DOS_TVOS", "-fno-autolink"])], + ), + ], + ) + else: + apply_default_compiler_flags_feature = None + + dynamic_linking_mode_feature = feature(name = "dynamic_linking_mode") + + compile_all_modules_feature = feature(name = "compile_all_modules") + + runtime_root_flags_feature = feature( + name = "runtime_root_flags", + flag_sets = [ + flag_set( + actions = all_link_actions + + [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + flags = [ + "-Xlinker", + "-rpath", + "-Xlinker", + "@loader_path/%{runtime_library_search_directories}", + ], + iterate_over = "runtime_library_search_directories", + expand_if_available = "runtime_library_search_directories", + ), + ], + ), + flag_set( + actions = all_link_actions + + [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + flags = ["%{runtime_root_flags}"], + iterate_over = "runtime_root_flags", + expand_if_available = "runtime_root_flags", + ), + ], + ), + flag_set( + actions = all_link_actions + + [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + flags = ["%{runtime_root_entries}"], + iterate_over = "runtime_root_entries", + expand_if_available = "runtime_root_entries", + ), + ], + ), + ], + ) + + objc_arc_feature = feature( + name = "objc_arc", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-fobjc-arc"], + expand_if_available = "objc_arc", + ), + ], + ), + ], + ) + + unfiltered_cxx_flags_feature = feature( + name = "unfiltered_cxx_flags", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ], + flag_groups = [ + flag_group(flags = ["-no-canonical-prefixes", "-pthread"]), + ], + ), + ], + ) + + compiler_input_flags_feature = feature( + name = "compiler_input_flags", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-c", "%{source_file}"], + expand_if_available = "source_file", + ), + ], + ), + ], + ) + + strip_debug_symbols_feature = feature( + name = "strip_debug_symbols", + flag_sets = [ + flag_set( + actions = all_link_actions + + ["objc-executable", "objc++-executable"], + flag_groups = [ + flag_group( + flags = ["-Wl,-S"], + expand_if_available = "strip_debug_symbols", + ), + ], + ), + ], + ) + + shared_flag_feature = feature( + name = "shared_flag", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ], + flag_groups = [flag_group(flags = ["-shared"])], + ), + ], + ) + + if (ctx.attr.cpu == "ios_i386" or + ctx.attr.cpu == "ios_x86_64" or + ctx.attr.cpu == "ios_sim_arm64" or + ctx.attr.cpu == "tvos_x86_64" or + ctx.attr.cpu == "tvos_sim_arm64" or + ctx.attr.cpu == "watchos_i386" or + ctx.attr.cpu == "watchos_x86_64" or + ctx.attr.cpu == "watchos_arm64"): + apply_simulator_compiler_flags_feature = feature( + name = "apply_simulator_compiler_flags", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.objc_compile, ACTION_NAMES.objcpp_compile], + flag_groups = [ + flag_group( + flags = [ + "-fexceptions", + "-fasm-blocks", + "-fobjc-abi-version=2", + "-fobjc-legacy-dispatch", + ], + ), + ], + ), + ], + ) + else: + apply_simulator_compiler_flags_feature = feature(name = "apply_simulator_compiler_flags") + + fastbuild_feature = feature(name = "fastbuild") + + no_legacy_features_feature = feature(name = "no_legacy_features") + + user_link_flags_feature = feature( + name = "user_link_flags", + enabled = True, + flag_sets = [ + flag_set( + actions = all_link_actions + + ["objc-executable", "objc++-executable"], + flag_groups = [ + flag_group( + flags = ["%{user_link_flags}"], + iterate_over = "user_link_flags", + expand_if_available = "user_link_flags", + ), + ], + ), + ], + ) + + if (ctx.attr.cpu == "ios_arm64" or + ctx.attr.cpu == "ios_arm64e" or + ctx.attr.cpu == "ios_armv7" or + ctx.attr.cpu == "ios_i386" or + ctx.attr.cpu == "ios_x86_64" or + ctx.attr.cpu == "ios_sim_arm64" or + ctx.attr.cpu == "tvos_arm64" or + ctx.attr.cpu == "tvos_x86_64" or + ctx.attr.cpu == "tvos_sim_arm64" or + ctx.attr.cpu == "watchos_arm64_32" or + ctx.attr.cpu == "watchos_armv7k" or + ctx.attr.cpu == "watchos_i386" or + ctx.attr.cpu == "watchos_x86_64" or + ctx.attr.cpu == "watchos_arm64"): + contains_objc_source_feature = feature( + name = "contains_objc_source", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.cpp_link_executable, + ], + flag_groups = [flag_group(flags = ["-fobjc-link-runtime"])], + ), + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.cpp_link_executable, + ], + flag_groups = [flag_group(flags = ["-framework", "UIKit"])], + ), + ], + ) + elif (ctx.attr.cpu == "darwin_x86_64" or + ctx.attr.cpu == "darwin_arm64" or + ctx.attr.cpu == "darwin_arm64e"): + contains_objc_source_feature = feature( + name = "contains_objc_source", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.cpp_link_executable, + ], + flag_groups = [flag_group(flags = ["-fobjc-link-runtime"])], + ), + ], + ) + else: + contains_objc_source_feature = None + + includes_feature = feature( + name = "includes", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ACTION_NAMES.clif_match, + ], + flag_groups = [ + flag_group( + flags = ["-include", "%{includes}"], + iterate_over = "includes", + expand_if_available = "includes", + ), + ], + ), + ], + ) + + gcc_coverage_map_format_feature = feature( + name = "gcc_coverage_map_format", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-fprofile-arcs", "-ftest-coverage", "-g"], + ), + ], + ), + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.cpp_link_executable, + ], + flag_groups = [flag_group(flags = ["--coverage"])], + ), + ], + requires = [feature_set(features = ["coverage"])], + ) + + default_link_flags_feature = feature( + name = "default_link_flags", + enabled = True, + flag_sets = [ + flag_set( + actions = all_link_actions + + ["objc-executable", "objc++-executable"], + flag_groups = [ + flag_group( + flags = [ + "-no-canonical-prefixes", + "-target", + target_system_name, + ], + ), + ], + ), + ], + ) + + no_deduplicate_feature = feature( + name = "no_deduplicate", + enabled = True, + flag_sets = [ + flag_set( + actions = all_link_actions + + ["objc-executable", "objc++-executable"], + flag_groups = [ + flag_group( + flags = [ + "-Xlinker", + "-no_deduplicate", + ], + ), + ], + with_features = [ + with_feature_set(not_features = ["opt"]), + ], + ), + ], + ) + + output_execpath_flags_feature = feature( + name = "output_execpath_flags", + flag_sets = [ + flag_set( + actions = all_link_actions, + flag_groups = [ + flag_group( + flags = ["-o", "%{output_execpath}"], + expand_if_available = "output_execpath", + ), + ], + ), + ], + ) + + pic_feature = feature( + name = "pic", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.preprocess_assemble, + ], + flag_groups = [ + flag_group(flags = ["-fPIC"], expand_if_available = "pic"), + ], + ), + ], + ) + + framework_paths_feature = feature( + name = "framework_paths", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-F%{framework_include_paths}"], + iterate_over = "framework_include_paths", + ), + ], + ), + flag_set( + actions = [ + "objc-executable", + "objc++-executable", + ], + flag_groups = [ + flag_group( + flags = ["-F%{framework_paths}"], + iterate_over = "framework_paths", + ), + flag_group( + flags = ["-framework", "%{framework_names}"], + iterate_over = "framework_names", + ), + flag_group( + flags = ["-weak_framework", "%{weak_framework_names}"], + iterate_over = "weak_framework_names", + ), + ], + ), + ], + ) + + compiler_output_flags_feature = feature( + name = "compiler_output_flags", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-S"], + expand_if_available = "output_assembly_file", + ), + flag_group( + flags = ["-E"], + expand_if_available = "output_preprocess_file", + ), + flag_group( + flags = ["-o", "%{output_file}"], + expand_if_available = "output_file", + ), + ], + ), + ], + ) + + opt_feature = feature(name = "opt") + + pch_feature = feature( + name = "pch", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ], + flag_groups = [ + flag_group( + flags = [ + "-include", + "%{pch_file}", + ], + expand_if_available = "pch_file", + ), + ], + ), + ], + ) + + coverage_feature = feature(name = "coverage") + + include_system_dirs_feature = feature( + name = "include_system_dirs", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + "objc-executable", + "objc++-executable", + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ], + flag_groups = [ + flag_group( + flags = [ + "-isysroot", + "%{sdk_dir}", + "-F%{sdk_framework_dir}", + "-F%{platform_developer_framework_dir}", + ], + ), + ], + ), + ], + ) + + input_param_flags_feature = feature( + name = "input_param_flags", + flag_sets = [ + flag_set( + actions = all_link_actions + + [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + flags = ["-L%{library_search_directories}"], + iterate_over = "library_search_directories", + expand_if_available = "library_search_directories", + ), + ], + ), + flag_set( + actions = all_link_actions + + [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + flags = ["%{libopts}"], + iterate_over = "libopts", + expand_if_available = "libopts", + ), + ], + ), + flag_set( + actions = all_link_actions + + [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + flags = ["-Wl,-force_load,%{whole_archive_linker_params}"], + iterate_over = "whole_archive_linker_params", + expand_if_available = "whole_archive_linker_params", + ), + ], + ), + flag_set( + actions = all_link_actions + + [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + flags = ["%{linker_input_params}"], + iterate_over = "linker_input_params", + expand_if_available = "linker_input_params", + ), + ], + ), + flag_set( + actions = all_link_actions + + [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + iterate_over = "libraries_to_link", + flag_groups = [ + flag_group( + iterate_over = "libraries_to_link.object_files", + flag_groups = [ + flag_group( + flags = ["%{libraries_to_link.object_files}"], + expand_if_false = "libraries_to_link.is_whole_archive", + ), + flag_group( + flags = ["-Wl,-force_load,%{libraries_to_link.object_files}"], + expand_if_true = "libraries_to_link.is_whole_archive", + ), + ], + expand_if_equal = variable_with_value( + name = "libraries_to_link.type", + value = "object_file_group", + ), + ), + flag_group( + flag_groups = [ + flag_group( + flags = ["%{libraries_to_link.name}"], + expand_if_false = "libraries_to_link.is_whole_archive", + ), + flag_group( + flags = ["-Wl,-force_load,%{libraries_to_link.name}"], + expand_if_true = "libraries_to_link.is_whole_archive", + ), + ], + expand_if_equal = variable_with_value( + name = "libraries_to_link.type", + value = "object_file", + ), + ), + flag_group( + flag_groups = [ + flag_group( + flags = ["%{libraries_to_link.name}"], + expand_if_false = "libraries_to_link.is_whole_archive", + ), + flag_group( + flags = ["-Wl,-force_load,%{libraries_to_link.name}"], + expand_if_true = "libraries_to_link.is_whole_archive", + ), + ], + expand_if_equal = variable_with_value( + name = "libraries_to_link.type", + value = "interface_library", + ), + ), + flag_group( + flag_groups = [ + flag_group( + flags = ["%{libraries_to_link.name}"], + expand_if_false = "libraries_to_link.is_whole_archive", + ), + flag_group( + flags = ["-Wl,-force_load,%{libraries_to_link.name}"], + expand_if_true = "libraries_to_link.is_whole_archive", + ), + ], + expand_if_equal = variable_with_value( + name = "libraries_to_link.type", + value = "static_library", + ), + ), + flag_group( + flag_groups = [ + flag_group( + flags = ["-l%{libraries_to_link.name}"], + expand_if_false = "libraries_to_link.is_whole_archive", + ), + flag_group( + flags = ["-Wl,-force_load,-l%{libraries_to_link.name}"], + expand_if_true = "libraries_to_link.is_whole_archive", + ), + ], + expand_if_equal = variable_with_value( + name = "libraries_to_link.type", + value = "dynamic_library", + ), + ), + flag_group( + flag_groups = [ + flag_group( + flags = ["-l:%{libraries_to_link.name}"], + expand_if_false = "libraries_to_link.is_whole_archive", + ), + flag_group( + flags = ["-Wl,-force_load,-l:%{libraries_to_link.name}"], + expand_if_true = "libraries_to_link.is_whole_archive", + ), + ], + expand_if_equal = variable_with_value( + name = "libraries_to_link.type", + value = "versioned_dynamic_library", + ), + ), + ], + expand_if_available = "libraries_to_link", + ), + ], + ), + ], + ) + + per_object_debug_info_feature = feature( + name = "per_object_debug_info", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ], + flag_groups = [ + flag_group( + flags = ["-gsplit-dwarf", "-g"], + expand_if_available = "per_object_debug_info_file", + ), + ], + ), + ], + ) + + lipo_feature = feature( + name = "lipo", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile], + flag_groups = [flag_group(flags = ["-fripa"])], + ), + ], + requires = [ + feature_set(features = ["autofdo"]), + feature_set(features = ["fdo_optimize"]), + feature_set(features = ["fdo_instrument"]), + ], + ) + + apple_env_feature = feature( + name = "apple_env", + env_sets = [ + env_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + "objc-archive", + "objc-fully-link", + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.cpp_link_static_library, + "objc-executable", + "objc++-executable", + ACTION_NAMES.linkstamp_compile, + ], + env_entries = [ + env_entry( + key = "XCODE_VERSION_OVERRIDE", + value = "%{xcode_version_override_value}", + ), + env_entry( + key = "APPLE_SDK_VERSION_OVERRIDE", + value = "%{apple_sdk_version_override_value}", + ), + env_entry( + key = "APPLE_SDK_PLATFORM", + value = "%{apple_sdk_platform_value}", + ), + env_entry( + key = "ZERO_AR_DATE", + value = "1", + ), + ] + [env_entry(key = key, value = value) for key, value in ctx.attr.extra_env.items()], + ), + ], + ) + + if (ctx.attr.cpu == "ios_arm64" or + ctx.attr.cpu == "ios_arm64e" or + ctx.attr.cpu == "ios_armv7" or + ctx.attr.cpu == "ios_i386" or + ctx.attr.cpu == "ios_x86_64" or + ctx.attr.cpu == "ios_sim_arm64" or + ctx.attr.cpu == "tvos_arm64" or + ctx.attr.cpu == "tvos_x86_64" or + ctx.attr.cpu == "tvos_sim_arm64" or + ctx.attr.cpu == "watchos_arm64_32" or + ctx.attr.cpu == "watchos_armv7k" or + ctx.attr.cpu == "watchos_i386" or + ctx.attr.cpu == "watchos_x86_64" or + ctx.attr.cpu == "watchos_arm64"): + apply_implicit_frameworks_feature = feature( + name = "apply_implicit_frameworks", + flag_sets = [ + flag_set( + actions = ["objc-executable", "objc++-executable"], + flag_groups = [ + flag_group( + flags = ["-framework", "Foundation", "-framework", "UIKit"], + ), + ], + ), + ], + ) + elif (ctx.attr.cpu == "darwin_x86_64" or + ctx.attr.cpu == "darwin_arm64" or + ctx.attr.cpu == "darwin_arm64e"): + apply_implicit_frameworks_feature = feature( + name = "apply_implicit_frameworks", + flag_sets = [ + flag_set( + actions = ["objc-executable", "objc++-executable"], + flag_groups = [flag_group(flags = ["-framework", "Foundation"])], + with_features = [with_feature_set(not_features = ["kernel_extension"])], + ), + ], + ) + else: + apply_implicit_frameworks_feature = None + + dbg_feature = feature(name = "dbg") + + has_configured_linker_path_feature = feature(name = "has_configured_linker_path") + + random_seed_feature = feature( + name = "random_seed", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.cpp_module_compile, + ], + flag_groups = [ + flag_group( + flags = ["-frandom-seed=%{output_file}"], + expand_if_available = "output_file", + ), + ], + ), + ], + ) + + llvm_coverage_map_format_feature = feature( + name = "llvm_coverage_map_format", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-fprofile-instr-generate", "-fcoverage-mapping", "-g"], + ), + ], + ), + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.cpp_link_executable, + "objc-executable", + "objc++-executable", + ], + flag_groups = [flag_group(flags = ["-fprofile-instr-generate"])], + ), + ], + requires = [feature_set(features = ["coverage"])], + ) + + force_pic_flags_feature = feature( + name = "force_pic_flags", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.cpp_link_executable], + flag_groups = [ + flag_group( + flags = ["-Wl,-pie"], + expand_if_available = "force_pic", + ), + ], + ), + ], + ) + + sysroot_feature = feature( + name = "sysroot", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.clif_match, + ], + flag_groups = [ + flag_group( + flags = ["--sysroot=%{sysroot}"], + expand_if_available = "sysroot", + ), + ], + ), + ], + ) + + autofdo_feature = feature( + name = "autofdo", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile], + flag_groups = [ + flag_group( + flags = [ + "-fauto-profile=%{fdo_profile_path}", + "-fprofile-correction", + ], + expand_if_available = "fdo_profile_path", + ), + ], + ), + ], + provides = ["profile"], + ) + + link_libcpp_feature = feature( + name = "link_libc++", + enabled = True, + flag_sets = [ + flag_set( + actions = all_link_actions + + ["objc-executable", "objc++-executable"], + flag_groups = [flag_group(flags = ["-lc++"])], + with_features = [with_feature_set(not_features = ["kernel_extension"])], + ), + ], + ) + + objc_actions_feature = feature( + name = "objc_actions", + implies = [ + "objc-compile", + "objc++-compile", + "objc-fully-link", + "objc-archive", + "objc-executable", + "objc++-executable", + "assemble", + "preprocess-assemble", + "c-compile", + "c++-compile", + "c++-link-static-library", + "c++-link-dynamic-library", + "c++-link-nodeps-dynamic-library", + "c++-link-executable", + ], + ) + + module_maps_feature = feature(name = "module_maps", enabled = True) + + unfiltered_compile_flags_feature = feature( + name = "unfiltered_compile_flags", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.linkstamp_compile, + ], + flag_groups = [ + flag_group( + flags = [ + "-no-canonical-prefixes", + "-Wno-builtin-macro-redefined", + "-D__DATE__=\"redacted\"", + "-D__TIMESTAMP__=\"redacted\"", + "-D__TIME__=\"redacted\"", + "-target", + target_system_name, + ], + ), + ], + ), + ], + ) + + linker_param_file_feature = feature( + name = "linker_param_file", + flag_sets = [ + flag_set( + actions = all_link_actions + [ + ACTION_NAMES.cpp_link_static_library, + "objc-archive", + ACTION_NAMES.objc_fully_link, + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + flag_groups = [ + flag_group( + flags = ["@%{linker_param_file}"], + expand_if_available = "linker_param_file", + ), + ], + ), + ], + ) + + relative_ast_path_feature = feature( + name = "relative_ast_path", + env_sets = [ + env_set( + actions = all_link_actions + [ + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + env_entries = [ + env_entry( + key = "RELATIVE_AST_PATH", + value = "true", + ), + ], + ), + ], + ) + + archiver_flags_feature = feature( + name = "archiver_flags", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.cpp_link_static_library], + flag_groups = [ + flag_group( + flags = _deterministic_libtool_flags(ctx) + [ + "-no_warning_for_no_symbols", + "-static", + "-o", + "%{output_execpath}", + ], + expand_if_available = "output_execpath", + ), + ], + ), + ], + ) + + fdo_optimize_feature = feature( + name = "fdo_optimize", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile], + flag_groups = [ + flag_group( + flags = [ + "-fprofile-use=%{fdo_profile_path}", + "-Wno-profile-instr-unprofiled", + "-Wno-profile-instr-out-of-date", + "-fprofile-correction", + ], + expand_if_available = "fdo_profile_path", + ), + ], + ), + ], + provides = ["profile"], + ) + + no_objc_arc_feature = feature( + name = "no_objc_arc", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-fno-objc-arc"], + expand_if_available = "no_objc_arc", + ), + ], + ), + ], + ) + + cpp_linker_flags_feature = feature( + name = "cpp_linker_flags", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.cpp_link_dynamic_library, + ], + flag_groups = [ + flag_group( + flags = ["-lc++", "-target", target_system_name], + ), + ], + ), + ], + ) + + exclude_private_headers_in_module_maps_feature = feature(name = "exclude_private_headers_in_module_maps") + + debug_prefix_map_pwd_is_dot_feature = feature( + name = "debug_prefix_map_pwd_is_dot", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [flag_group(flags = ["DEBUG_PREFIX_MAP_PWD=."])], + ), + ], + ) + + remap_xcode_path_feature = feature( + name = "remap_xcode_path", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [flag_group(flags = [ + "-fdebug-prefix-map=__BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR", + ])], + ), + ], + ) + + linkstamps_feature = feature( + name = "linkstamps", + flag_sets = [ + flag_set( + actions = all_link_actions, + flag_groups = [ + flag_group( + flags = ["%{linkstamp_paths}"], + iterate_over = "linkstamp_paths", + expand_if_available = "linkstamp_paths", + ), + ], + ), + ], + ) + + include_paths_feature = feature( + name = "include_paths", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.clif_match, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-iquote", "%{quote_include_paths}"], + iterate_over = "quote_include_paths", + ), + flag_group( + flags = ["-I%{include_paths}"], + iterate_over = "include_paths", + ), + flag_group( + flags = ["-isystem", "%{system_include_paths}"], + iterate_over = "system_include_paths", + ), + ], + ), + ], + ) + + only_doth_headers_in_module_maps_feature = feature(name = "only_doth_headers_in_module_maps") + + default_compile_flags_feature = feature( + name = "default_compile_flags", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.lto_backend, + ACTION_NAMES.clif_match, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = [ + "-D_FORTIFY_SOURCE=1", + ], + ), + ], + with_features = [with_feature_set(not_features = ["asan"])], + ), + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.lto_backend, + ACTION_NAMES.clif_match, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = [ + "-fstack-protector", + "-fcolor-diagnostics", + "-Wall", + "-Wthread-safety", + "-Wself-assign", + "-fno-omit-frame-pointer", + ], + ), + ], + ), + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.lto_backend, + ACTION_NAMES.clif_match, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [flag_group(flags = ["-O0", "-DDEBUG"])], + with_features = [with_feature_set(features = ["fastbuild"])], + ), + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.lto_backend, + ACTION_NAMES.clif_match, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = [ + "-g0", + "-O2", + "-DNDEBUG", + "-DNS_BLOCK_ASSERTIONS=1", + ], + ), + ], + with_features = [with_feature_set(features = ["opt"])], + ), + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.lto_backend, + ACTION_NAMES.clif_match, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [flag_group(flags = ["-g"])], + with_features = [with_feature_set(features = ["dbg"])], + ), + flag_set( + actions = [ + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.lto_backend, + ACTION_NAMES.clif_match, + ], + flag_groups = [flag_group(flags = ["-std=c++11"])], + ), + ], + ) + + objcopy_embed_flags_feature = feature( + name = "objcopy_embed_flags", + enabled = True, + flag_sets = [ + flag_set( + actions = ["objcopy_embed_data"], + flag_groups = [flag_group(flags = ["-I", "binary"])], + ), + ], + ) + + dead_strip_feature = feature( + name = "dead_strip", + flag_sets = [ + flag_set( + actions = all_link_actions + + ["objc-executable", "objc++-executable"], + flag_groups = [ + flag_group( + flags = ["-dead_strip"], + ), + ], + ), + ], + requires = [feature_set(features = ["opt"])], + ) + + oso_prefix_feature = feature( + name = "oso_prefix_is_pwd", + flag_sets = [ + flag_set( + actions = all_link_actions + + ["objc-executable", "objc++-executable"], + flag_groups = [flag_group(flags = ["OSO_PREFIX_MAP_PWD"])], + ), + ], + ) + + generate_dsym_file_feature = feature( + name = "generate_dsym_file", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + "objc-executable", + "objc++-executable", + ], + flag_groups = [flag_group(flags = ["-g"])], + ), + flag_set( + actions = ["objc-executable", "objc++-executable"], + flag_groups = [ + flag_group( + flags = [ + "DSYM_HINT_LINKED_BINARY=%{linked_binary}", + "DSYM_HINT_DSYM_PATH=%{dsym_path}", + ], + ), + ], + ), + ], + ) + + # Kernel extensions for Apple Silicon are arm64e. + if (ctx.attr.cpu == "darwin_x86_64" or + ctx.attr.cpu == "darwin_arm64e"): + kernel_extension_feature = feature( + name = "kernel_extension", + flag_sets = [ + flag_set( + actions = ["objc-executable", "objc++-executable"], + flag_groups = [ + flag_group( + flags = [ + "-nostdlib", + "-lkmod", + "-lkmodc++", + "-lcc_kext", + "-Xlinker", + "-kext", + ], + ), + ], + ), + ], + ) + else: + kernel_extension_feature = feature(name = "kernel_extension") + + apply_default_warnings_feature = feature( + name = "apply_default_warnings", + flag_sets = [ + flag_set( + actions = [ACTION_NAMES.objc_compile, ACTION_NAMES.objcpp_compile], + flag_groups = [ + flag_group( + flags = [ + "-Wshorten-64-to-32", + "-Wbool-conversion", + "-Wconstant-conversion", + "-Wduplicate-method-match", + "-Wempty-body", + "-Wenum-conversion", + "-Wint-conversion", + "-Wunreachable-code", + "-Wmismatched-return-types", + "-Wundeclared-selector", + "-Wuninitialized", + "-Wunused-function", + "-Wunused-variable", + ], + ), + ], + ), + ], + ) + + dependency_file_feature = feature( + name = "dependency_file", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ACTION_NAMES.cpp_header_parsing, + ], + flag_groups = [ + flag_group( + flags = ["-MD", "-MF", "%{dependency_file}"], + expand_if_available = "dependency_file", + ), + ], + ), + ], + ) + + serialized_diagnostics_file_feature = feature( + name = "serialized_diagnostics_file", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ACTION_NAMES.cpp_header_parsing, + ], + flag_groups = [ + flag_group( + flags = ["--serialize-diagnostics", "%{serialized_diagnostics_file}"], + expand_if_available = "serialized_diagnostics_file", + ), + ], + ), + ], + ) + + preprocessor_defines_feature = feature( + name = "preprocessor_defines", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-D%{preprocessor_defines}"], + iterate_over = "preprocessor_defines", + ), + ], + ), + ], + ) + + fdo_instrument_feature = feature( + name = "fdo_instrument", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.cpp_link_executable, + ], + flag_groups = [ + flag_group( + flags = [ + "-fprofile-generate=%{fdo_instrument_path}", + "-fno-data-sections", + ], + expand_if_available = "fdo_instrument_path", + ), + ], + ), + ], + provides = ["profile"], + ) + + if (ctx.attr.cpu == "darwin_x86_64" or + ctx.attr.cpu == "darwin_arm64" or + ctx.attr.cpu == "darwin_arm64e"): + link_cocoa_feature = feature( + name = "link_cocoa", + flag_sets = [ + flag_set( + actions = ["objc-executable", "objc++-executable"], + flag_groups = [flag_group(flags = ["-framework", "Cocoa"])], + ), + ], + ) + else: + link_cocoa_feature = feature(name = "link_cocoa") + + user_compile_flags_feature = feature( + name = "user_compile_flags", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["%{user_compile_flags}"], + iterate_over = "user_compile_flags", + expand_if_available = "user_compile_flags", + ), + ], + ), + ], + ) + + headerpad_feature = feature( + name = "headerpad", + enabled = True, + flag_sets = [ + flag_set( + actions = all_link_actions + [ + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + flag_groups = [flag_group(flags = ["-headerpad_max_install_names"])], + with_features = [with_feature_set(not_features = [ + "bitcode_embedded", + "bitcode_embedded_markers", + ])], + ), + ], + ) + + if (ctx.attr.cpu == "ios_arm64" or + ctx.attr.cpu == "ios_arm64e" or + ctx.attr.cpu == "ios_armv7" or + ctx.attr.cpu == "tvos_arm64" or + ctx.attr.cpu == "watchos_arm64_32" or + ctx.attr.cpu == "watchos_armv7k" or + ctx.attr.cpu == "darwin_x86_64" or + ctx.attr.cpu == "darwin_arm64" or + ctx.attr.cpu == "darwin_arm64e"): + bitcode_embedded_feature = feature( + name = "bitcode_embedded", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [flag_group(flags = ["-fembed-bitcode"])], + ), + flag_set( + actions = all_link_actions + [ + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + flag_groups = [ + flag_group( + flags = [ + "-fembed-bitcode", + "-Xlinker", + "-bitcode_verify", + "-Xlinker", + "-bitcode_hide_symbols", + "-Xlinker", + "-bitcode_symbol_map", + "-Xlinker", + "%{bitcode_symbol_map_path}", + ], + expand_if_available = "bitcode_symbol_map_path", + ), + ], + ), + ], + ) + bitcode_embedded_markers_feature = feature( + name = "bitcode_embedded_markers", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [flag_group(flags = ["-fembed-bitcode-marker"])], + ), + flag_set( + actions = all_link_actions + [ + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + flag_groups = [flag_group(flags = ["-fembed-bitcode-marker"])], + ), + ], + ) + else: + bitcode_embedded_markers_feature = feature(name = "bitcode_embedded_markers") + bitcode_embedded_feature = feature(name = "bitcode_embedded") + + generate_linkmap_feature = feature( + name = "generate_linkmap", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + flag_groups = [ + flag_group( + flags = [ + "-Xlinker", + "-map", + "-Xlinker", + "%{linkmap_exec_path}", + ], + ), + ], + ), + ], + ) + + set_install_name = feature( + name = "set_install_name", + enabled = ctx.fragments.cpp.do_not_use_macos_set_install_name, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ], + flag_groups = [ + flag_group( + flags = [ + "-Wl,-install_name,@rpath/%{runtime_solib_name}", + ], + expand_if_available = "runtime_solib_name", + ), + ], + ), + ], + ) + + asan_feature = feature( + name = "asan", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group(flags = ["-fsanitize=address"]), + ], + with_features = [ + with_feature_set(features = ["asan"]), + ], + ), + flag_set( + actions = [ + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + flag_groups = [ + flag_group(flags = ["-fsanitize=address"]), + ], + with_features = [ + with_feature_set(features = ["asan"]), + ], + ), + ], + ) + + tsan_feature = feature( + name = "tsan", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group(flags = ["-fsanitize=thread"]), + ], + with_features = [ + with_feature_set(features = ["tsan"]), + ], + ), + flag_set( + actions = [ + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + flag_groups = [ + flag_group(flags = ["-fsanitize=thread"]), + ], + with_features = [ + with_feature_set(features = ["tsan"]), + ], + ), + ], + ) + + ubsan_feature = feature( + name = "ubsan", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group(flags = ["-fsanitize=undefined"]), + ], + with_features = [ + with_feature_set(features = ["ubsan"]), + ], + ), + flag_set( + actions = [ + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.objc_executable, + ACTION_NAMES.objcpp_executable, + ], + flag_groups = [ + flag_group(flags = ["-fsanitize=undefined"]), + ], + with_features = [ + with_feature_set(features = ["ubsan"]), + ], + ), + ], + ) + + default_sanitizer_flags_feature = feature( + name = "default_sanitizer_flags", + enabled = True, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = [ + "-O1", + "-gline-tables-only", + "-fno-omit-frame-pointer", + "-fno-sanitize-recover=all", + ], + ), + ], + with_features = [ + with_feature_set(features = ["asan"]), + with_feature_set(features = ["tsan"]), + with_feature_set(features = ["ubsan"]), + ], + ), + ], + ) + + archive_param_file_feature = feature(name = "archive_param_file") + + if (ctx.attr.cpu == "ios_arm64" or + ctx.attr.cpu == "ios_arm64e" or + ctx.attr.cpu == "ios_armv7" or + ctx.attr.cpu == "ios_i386" or + ctx.attr.cpu == "ios_x86_64" or + ctx.attr.cpu == "ios_sim_arm64" or + ctx.attr.cpu == "tvos_arm64" or + ctx.attr.cpu == "tvos_x86_64" or + ctx.attr.cpu == "tvos_sim_arm64" or + ctx.attr.cpu == "watchos_arm64_32" or + ctx.attr.cpu == "watchos_armv7k" or + ctx.attr.cpu == "watchos_i386" or + ctx.attr.cpu == "watchos_x86_64" or + ctx.attr.cpu == "watchos_arm64"): + features = [ + fastbuild_feature, + no_legacy_features_feature, + opt_feature, + dbg_feature, + link_libcpp_feature, + compile_all_modules_feature, + exclude_private_headers_in_module_maps_feature, + has_configured_linker_path_feature, + only_doth_headers_in_module_maps_feature, + default_compile_flags_feature, + debug_prefix_map_pwd_is_dot_feature, + remap_xcode_path_feature, + generate_dsym_file_feature, + generate_linkmap_feature, + oso_prefix_feature, + contains_objc_source_feature, + objc_actions_feature, + strip_debug_symbols_feature, + shared_flag_feature, + kernel_extension_feature, + linkstamps_feature, + output_execpath_flags_feature, + archiver_flags_feature, + runtime_root_flags_feature, + input_param_flags_feature, + force_pic_flags_feature, + pch_feature, + module_maps_feature, + apply_default_warnings_feature, + includes_feature, + include_paths_feature, + sysroot_feature, + dependency_file_feature, + serialized_diagnostics_file_feature, + pic_feature, + per_object_debug_info_feature, + preprocessor_defines_feature, + framework_paths_feature, + random_seed_feature, + fdo_instrument_feature, + fdo_optimize_feature, + autofdo_feature, + lipo_feature, + coverage_feature, + llvm_coverage_map_format_feature, + gcc_coverage_map_format_feature, + apply_default_compiler_flags_feature, + include_system_dirs_feature, + headerpad_feature, + bitcode_embedded_feature, + bitcode_embedded_markers_feature, + objc_arc_feature, + no_objc_arc_feature, + apple_env_feature, + relative_ast_path_feature, + user_link_flags_feature, + default_link_flags_feature, + no_deduplicate_feature, + dead_strip_feature, + cpp_linker_flags_feature, + apply_implicit_frameworks_feature, + link_cocoa_feature, + apply_simulator_compiler_flags_feature, + unfiltered_cxx_flags_feature, + user_compile_flags_feature, + unfiltered_compile_flags_feature, + linker_param_file_feature, + compiler_input_flags_feature, + compiler_output_flags_feature, + objcopy_embed_flags_feature, + set_install_name, + asan_feature, + tsan_feature, + ubsan_feature, + default_sanitizer_flags_feature, + archive_param_file_feature, + ] + elif (ctx.attr.cpu == "darwin_x86_64" or + ctx.attr.cpu == "darwin_arm64" or + ctx.attr.cpu == "darwin_arm64e"): + features = [ + fastbuild_feature, + no_legacy_features_feature, + opt_feature, + dbg_feature, + link_libcpp_feature, + compile_all_modules_feature, + exclude_private_headers_in_module_maps_feature, + has_configured_linker_path_feature, + only_doth_headers_in_module_maps_feature, + default_compile_flags_feature, + debug_prefix_map_pwd_is_dot_feature, + remap_xcode_path_feature, + generate_dsym_file_feature, + generate_linkmap_feature, + oso_prefix_feature, + contains_objc_source_feature, + objc_actions_feature, + strip_debug_symbols_feature, + shared_flag_feature, + kernel_extension_feature, + linkstamps_feature, + output_execpath_flags_feature, + archiver_flags_feature, + runtime_root_flags_feature, + input_param_flags_feature, + force_pic_flags_feature, + pch_feature, + module_maps_feature, + apply_default_warnings_feature, + includes_feature, + include_paths_feature, + sysroot_feature, + dependency_file_feature, + serialized_diagnostics_file_feature, + pic_feature, + per_object_debug_info_feature, + preprocessor_defines_feature, + framework_paths_feature, + random_seed_feature, + fdo_instrument_feature, + fdo_optimize_feature, + autofdo_feature, + lipo_feature, + coverage_feature, + llvm_coverage_map_format_feature, + gcc_coverage_map_format_feature, + apply_default_compiler_flags_feature, + include_system_dirs_feature, + headerpad_feature, + bitcode_embedded_feature, + bitcode_embedded_markers_feature, + objc_arc_feature, + no_objc_arc_feature, + apple_env_feature, + relative_ast_path_feature, + user_link_flags_feature, + default_link_flags_feature, + no_deduplicate_feature, + dead_strip_feature, + cpp_linker_flags_feature, + apply_implicit_frameworks_feature, + link_cocoa_feature, + apply_simulator_compiler_flags_feature, + unfiltered_cxx_flags_feature, + user_compile_flags_feature, + unfiltered_compile_flags_feature, + linker_param_file_feature, + compiler_input_flags_feature, + compiler_output_flags_feature, + objcopy_embed_flags_feature, + dynamic_linking_mode_feature, + set_install_name, + asan_feature, + tsan_feature, + ubsan_feature, + default_sanitizer_flags_feature, + archive_param_file_feature, + ] + else: + fail("Unreachable") + + # macOS artifact name patterns differ from the defaults only for dynamic + # libraries. + artifact_name_patterns = [ + artifact_name_pattern( + category_name = "dynamic_library", + prefix = "lib", + extension = ".dylib", + ), + ] + + make_variables = [ + make_variable( + name = "STACK_FRAME_UNLIMITED", + value = "-Wframe-larger-than=100000000 -Wno-vla", + ), + ] + + tool_paths = { + "ar": "libtool", + "cpp": "/usr/bin/cpp", + "dwp": "/usr/bin/dwp", + "gcc": "cc_wrapper.sh", + "gcov": "/usr/bin/gcov", + "ld": "/usr/bin/ld", + "nm": "/usr/bin/nm", + "objcopy": "/usr/bin/objcopy", + "objdump": "/usr/bin/objdump", + "strip": "/usr/bin/strip", + } + + tool_paths.update(ctx.attr.tool_paths_overrides) + + out = ctx.actions.declare_file(ctx.label.name) + ctx.actions.write(out, "Fake executable") + return [ + cc_common.create_cc_toolchain_config_info( + ctx = ctx, + features = features, + action_configs = action_configs, + artifact_name_patterns = artifact_name_patterns, + cxx_builtin_include_directories = ctx.attr.cxx_builtin_include_directories, + toolchain_identifier = ctx.attr.cpu, + host_system_name = host_system_name, + target_system_name = target_system_name, + target_cpu = ctx.attr.cpu, + target_libc = target_libc, + compiler = ctx.attr.compiler, + abi_version = abi_version, + abi_libc_version = abi_libc_version, + tool_paths = [tool_path(name = name, path = path) for (name, path) in tool_paths.items()], + make_variables = make_variables, + builtin_sysroot = None, + cc_target_os = "apple", + ), + DefaultInfo( + executable = out, + ), + ] + +cc_toolchain_config = rule( + implementation = _impl, + attrs = { + "cpu": attr.string(mandatory = True), + "compiler": attr.string(), + "cxx_builtin_include_directories": attr.string_list(), + "tool_paths_overrides": attr.string_dict(), + "extra_env": attr.string_dict(), + "_xcode_config": attr.label(default = configuration_field( + fragment = "apple", + name = "xcode_config_label", + )), + }, + provides = [CcToolchainConfigInfo], + executable = True, + fragments = ["apple", "cpp"], +) diff --git a/crosstool/libtool.sh b/crosstool/libtool.sh new file mode 100755 index 0000000..c8bcd18 --- /dev/null +++ b/crosstool/libtool.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# +# Copyright 2016 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# libtool.sh runs the command passed to it using "xcrunwrapper libtool". +# +# It creates symbolic links for all input files with a path-hash appended +# to their original name (foo.o becomes foo_{md5sum}.o). This is to circumvent +# a bug in the original libtool that arises when two input files have the same +# base name (even if they are in different directories). + +set -eu + +# A trick to allow invoking this script in multiple contexts. +if [ -z ${MY_LOCATION+x} ]; then + if [ -d "$0.runfiles/" ]; then + MY_LOCATION="$0.runfiles/bazel_tools/tools/objc" + else + MY_LOCATION="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + fi +fi + +function invoke_libtool() { + # Just invoke libtool via xcrunwrapper + "${MY_LOCATION}/xcrunwrapper.sh" libtool "$@" \ + 2> >(grep -v "the table of contents is empty (no object file members in the"` + `" library define global symbols)$" >&2) + # ^ Filtering a warning that's unlikely to indicate a real issue + # ...and not silencable via a flag. +} + +if [ ! -f "${MY_LOCATION}"/libtool_check_unique ] ; then + echo "libtool_check_unique not found. Please file an issue at github.com/bazelbuild/bazel" + exit 1 +elif "${MY_LOCATION}"/libtool_check_unique "$@"; then + # If there are no duplicate .o basenames, + # libtool can be invoked with the original arguments. + invoke_libtool "$@" + exit +fi + +TEMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/libtool.XXXXXXXX")" +trap 'rm -rf "$TEMPDIR"' EXIT + +# Creates a symbolic link to the input argument file and returns the symlink +# file path. +function hash_objfile() { + ORIGINAL_NAME="$1" + ORIGINAL_HASH="$(/sbin/md5 -qs "${ORIGINAL_NAME}")" + SYMLINK_NAME="${TEMPDIR}/$(basename "${ORIGINAL_NAME%.o}_${ORIGINAL_HASH}.o")" + if [[ ! -e "$SYMLINK_NAME" ]]; then + case "${ORIGINAL_NAME}" in + /*) ln -sf "$ORIGINAL_NAME" "$SYMLINK_NAME" ;; + *) ln -sf "$(pwd)/$ORIGINAL_NAME" "$SYMLINK_NAME" ;; + esac + fi + echo "$SYMLINK_NAME" +} + +python_executable=/usr/bin/python3 +if [[ ! -x "$python_executable" ]]; then + python_executable=python3 +fi + +ARGS=() +handle_filelist=0 +keep_next=0 + +function parse_option() { + local -r ARG="$1" + if [[ "$handle_filelist" == "1" ]]; then + handle_filelist=0 + HASHED_FILELIST="${ARG%.objlist}_hashes.objlist" + rm -f "${HASHED_FILELIST}" + # Use python helper script for fast md5 calculation of many strings. + "$python_executable" "${MY_LOCATION}/make_hashed_objlist.py" \ + "${ARG}" "${HASHED_FILELIST}" "${TEMPDIR}" + ARGS+=("${HASHED_FILELIST}") + elif [[ "$keep_next" == "1" ]]; then + keep_next=0 + ARGS+=("$ARG") + else + case "${ARG}" in + # Filelist flag, need to symlink each input in the contents of file and + # pass a new filelist which contains the symlinks. + -filelist) + handle_filelist=1 + ARGS+=("${ARG}") + ;; + @*) + path="${ARG:1}" + while IFS= read -r opt + do + parse_option "$opt" + done < "$path" || exit 1 + ;; + # Flags with no args + -static|-s|-a|-c|-L|-T|-D|-no_warning_for_no_symbols) + ARGS+=("${ARG}") + ;; + # Single-arg flags + -arch_only|-syslibroot|-o) + keep_next=1 + ARGS+=("${ARG}") + ;; + # Any remaining flags are unexpected and may ruin flag parsing. + # Add any flags here to libtool_check_unique.cc as well + -*) + echo "Unrecognized libtool flag ${ARG}" + exit 1 + ;; + # Archive inputs can remain untouched, as they come from other targets. + *.a) + ARGS+=("${ARG}") + ;; + # Remaining args are input objects + *) + ARGS+=("$(hash_objfile "${ARG}")") + ;; + esac + fi +} + +for arg in "$@"; do + parse_option "$arg" +done + +printf '%s\n' "${ARGS[@]}" > "$TEMPDIR/processed.params" +invoke_libtool "@$TEMPDIR/processed.params" diff --git a/crosstool/libtool_check_unique.cc b/crosstool/libtool_check_unique.cc new file mode 100644 index 0000000..340e9a3 --- /dev/null +++ b/crosstool/libtool_check_unique.cc @@ -0,0 +1,109 @@ +// Copyright 2020 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include // NOLINT +#include + +using std::ifstream; +using std::regex; +using std::string; +using std::unordered_set; +using std::vector; + +const regex libRegex = regex(".*\\.a$"); +const regex noArgFlags = + regex("-static|-s|-a|-c|-L|-T|-D|-no_warning_for_no_symbols"); +const regex singleArgFlags = regex("-arch_only|-syslibroot|-o"); + +string getBasename(const string &path) { + // Assumes we're on an OS with "/" as the path separator + auto idx = path.find_last_of("/"); + if (idx == string::npos) { + return path; + } + return path.substr(idx + 1); +} + +vector readFile(const string path) { + vector lines; + ifstream file(path); + string line; + while (std::getline(file, line)) { + if (!line.empty()) { + lines.push_back(line); + } + } + + return lines; +} + +unordered_set parseArgs(vector args) { + unordered_set basenames; + for (auto it = args.begin(); it != args.end(); ++it) { + const string arg = *it; + if (arg == "-filelist") { + ++it; + ifstream list(*it); + for (string line; getline(list, line);) { + const string basename = getBasename(line); + const auto pair = basenames.insert(basename); + if (!pair.second) { + exit(EXIT_FAILURE); + } + } + list.close(); + } else if (arg[0] == '@') { + string paramsFilePath(arg.substr(1)); + auto newBasenames = parseArgs(readFile(paramsFilePath)); + for (auto newBasename : newBasenames) { + const auto pair = basenames.insert(newBasename); + if (!pair.second) { + exit(EXIT_FAILURE); + } + } + } else if (regex_match(arg, noArgFlags)) { + } else if (regex_match(arg, singleArgFlags)) { + ++it; + } else if (arg[0] == '-') { + exit(EXIT_FAILURE); + // Unrecognized flag, let the wrapper deal with it, any flags added to + // libtool.sh should also be added here. + } else if (regex_match(arg, libRegex)) { + // Archive inputs can remain untouched, as they come from other targets. + } else { + const string basename = getBasename(arg); + const auto pair = basenames.insert(basename); + if (!pair.second) { + exit(EXIT_FAILURE); + } + } + } + + return basenames; +} + +// Returns 0 if there are no duplicate basenames in the object files (via +// -filelist, params files, and shell args), 1 otherwise +int main(int argc, const char *argv[]) { + vector args; + // Set i to 1 to skip executable path + for (int i = 1; argv[i] != nullptr; i++) { + args.push_back(argv[i]); + } + parseArgs(args); + return EXIT_SUCCESS; +} diff --git a/crosstool/make_hashed_objlist.py b/crosstool/make_hashed_objlist.py new file mode 100644 index 0000000..bb32642 --- /dev/null +++ b/crosstool/make_hashed_objlist.py @@ -0,0 +1,58 @@ +# pylint: disable=g-bad-file-header +# Copyright 2016 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Creates symbolic links for .o files with hashcode. + +This script reads the file list containing the input files, creates +symbolic links with a path-hash appended to their original name (foo.o +becomes foo_{md5sum}.o), then saves the list of symbolic links to another +file. + +The symbolic links are created into the given temporary directory. There is +no guarantee that we can write to the directory that contained the inputs to +this script. + +This is to circumvent a bug in the original libtool that arises when two +input files have the same base name (even if they are in different +directories). +""" + +import hashlib +import os +import sys + + +def main(): + outdir = sys.argv[3] + with open(sys.argv[1]) as obj_file_list: + with open(sys.argv[2], 'w') as hashed_obj_file_list: + for line in obj_file_list: + obj_file_path = line.rstrip('\n') + + hashed_obj_file_name = '%s_%s.o' % ( + os.path.basename(os.path.splitext(obj_file_path)[0]), + hashlib.md5(obj_file_path.encode('utf-8')).hexdigest()) + hashed_obj_file_path = os.path.join(outdir, hashed_obj_file_name) + + hashed_obj_file_list.write(hashed_obj_file_path + '\n') + + # Create symlink only if the symlink doesn't exist. + if not os.path.exists(hashed_obj_file_path): + os.symlink(os.path.abspath(obj_file_path), + hashed_obj_file_path) + + +if __name__ == '__main__': + main() diff --git a/crosstool/osx_cc_configure.bzl b/crosstool/osx_cc_configure.bzl new file mode 100644 index 0000000..94b02fe --- /dev/null +++ b/crosstool/osx_cc_configure.bzl @@ -0,0 +1,234 @@ +# pylint: disable=g-bad-file-header +# Copyright 2016 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Configuring the C++ toolchain on macOS.""" + +load("@bazel_tools//tools/osx:xcode_configure.bzl", "run_xcode_locator") +load( + "@bazel_tools//tools/cpp:lib_cc_configure.bzl", + "escape_string", + "resolve_labels", +) +load( + "@bazel_tools//tools/cpp:unix_cc_configure.bzl", + "get_env", +) + +def _get_escaped_xcode_cxx_inc_directories(repository_ctx, xcode_toolchains): + """Compute the list of default C++ include paths on Xcode-enabled darwin. + + Args: + repository_ctx: The repository context. + xcode_toolchains: A list containing the xcode toolchains available + Returns: + include_paths: A list of builtin include paths. + """ + + # Assume that everything is managed by Xcode / toolchain installations + include_dirs = [ + "/Applications/", + "/Library/", + ] + + user = repository_ctx.os.environ.get("USER") + if user: + include_dirs.append("/Users/{}/Library/".format(user)) + + # Include extra Xcode paths in case they're installed on other volumes + for toolchain in xcode_toolchains: + include_dirs.append(escape_string(toolchain.developer_dir)) + + return include_dirs + +# TODO: Remove once Xcode 12 is the minimum supported version +def _compile_cc_file_single_arch(repository_ctx, src_name, out_name): + env = repository_ctx.os.environ + xcrun_result = repository_ctx.execute([ + "env", + "-i", + "DEVELOPER_DIR={}".format(env.get("DEVELOPER_DIR", default = "")), + "xcrun", + "--sdk", + "macosx", + "clang", + "-mmacosx-version-min=10.9", + "-std=c++11", + "-lc++", + "-O3", + "-o", + out_name, + src_name, + ], 60) + if (xcrun_result.return_code != 0): + error_msg = ( + "return code {code}, stderr: {err}, stdout: {out}" + ).format( + code = xcrun_result.return_code, + err = xcrun_result.stderr, + out = xcrun_result.stdout, + ) + fail(out_name + " failed to generate. Please file an issue at " + + "https://github.com/bazelbuild/bazel/issues with the following:\n" + + error_msg) + +def _compile_cc_file(repository_ctx, src_name, out_name): + env = repository_ctx.os.environ + xcrun_result = repository_ctx.execute([ + "env", + "-i", + "DEVELOPER_DIR={}".format(env.get("DEVELOPER_DIR", default = "")), + "xcrun", + "--sdk", + "macosx", + "clang", + "-mmacosx-version-min=10.9", + "-std=c++11", + "-lc++", + "-arch", + "arm64", + "-arch", + "x86_64", + "-Wl,-no_adhoc_codesign", + "-Wl,-no_uuid", + "-O3", + "-o", + out_name, + src_name, + ], 60) + + if xcrun_result.return_code == 0: + xcrun_result = repository_ctx.execute([ + "env", + "-i", + "codesign", + "--identifier", # Required to be reproducible across archs + out_name, + "--force", + "--sign", + "-", + out_name, + ], 60) + if xcrun_result.return_code != 0: + error_msg = ( + "codesign return code {code}, stderr: {err}, stdout: {out}" + ).format( + code = xcrun_result.return_code, + err = xcrun_result.stderr, + out = xcrun_result.stdout, + ) + fail(out_name + " failed to generate. Please file an issue at " + + "https://github.com/bazelbuild/bazel/issues with the following:\n" + + error_msg) + else: + _compile_cc_file_single_arch(repository_ctx, src_name, out_name) + +def configure_osx_toolchain(repository_ctx): + """Configure C++ toolchain on macOS. + + Args: + repository_ctx: The repository context. + + Returns: + Whether or not configuration was successful + """ + paths = resolve_labels(repository_ctx, [ + "@bazel_tools//tools/cpp:armeabi_cc_toolchain_config.bzl", + "@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl", + "@build_bazel_apple_support//crosstool:libtool.sh", + "@build_bazel_apple_support//crosstool:libtool_check_unique.cc", + "@build_bazel_apple_support//crosstool:make_hashed_objlist.py", + "@build_bazel_apple_support//crosstool:xcrunwrapper.sh", + "@build_bazel_apple_support//crosstool:BUILD.tpl", + "@build_bazel_apple_support//crosstool:cc_toolchain_config.bzl", + "@build_bazel_apple_support//crosstool:wrapped_clang.cc", + "@bazel_tools//tools/osx:xcode_locator.m", + ]) + + (xcode_toolchains, xcodeloc_err) = run_xcode_locator( + repository_ctx, + paths["@bazel_tools//tools/osx:xcode_locator.m"], + ) + if not xcode_toolchains: + return False + + # For Xcode toolchains, there's no reason to use anything other than + # wrapped_clang, so that we still get the Bazel Xcode placeholder + # substitution and other behavior for actions that invoke this + # cc_wrapper.sh script. The wrapped_clang binary is already hardcoded + # into the Objective-C crosstool actions, anyway, so this ensures that + # the C++ actions behave consistently. + cc_path = '"$(/usr/bin/dirname "$0")"/wrapped_clang' + repository_ctx.template( + "cc_wrapper.sh", + paths["@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl"], + { + "%{cc}": escape_string(cc_path), + "%{env}": escape_string(get_env(repository_ctx)), + }, + ) + repository_ctx.symlink( + paths["@bazel_tools//tools/cpp:armeabi_cc_toolchain_config.bzl"], + "armeabi_cc_toolchain_config.bzl", + ) + repository_ctx.symlink( + paths["@build_bazel_apple_support//crosstool:xcrunwrapper.sh"], + "xcrunwrapper.sh", + ) + repository_ctx.symlink( + paths["@build_bazel_apple_support//crosstool:libtool.sh"], + "libtool", + ) + repository_ctx.symlink( + paths["@build_bazel_apple_support//crosstool:make_hashed_objlist.py"], + "make_hashed_objlist.py", + ) + repository_ctx.symlink( + paths["@build_bazel_apple_support//crosstool:cc_toolchain_config.bzl"], + "cc_toolchain_config.bzl", + ) + libtool_check_unique_src_path = str(repository_ctx.path( + paths["@build_bazel_apple_support//crosstool:libtool_check_unique.cc"], + )) + _compile_cc_file(repository_ctx, libtool_check_unique_src_path, "libtool_check_unique") + wrapped_clang_src_path = str(repository_ctx.path( + paths["@build_bazel_apple_support//crosstool:wrapped_clang.cc"], + )) + _compile_cc_file(repository_ctx, wrapped_clang_src_path, "wrapped_clang") + repository_ctx.symlink("wrapped_clang", "wrapped_clang_pp") + + tool_paths = {} + gcov_path = repository_ctx.os.environ.get("GCOV") + if gcov_path != None: + if not gcov_path.startswith("/"): + gcov_path = repository_ctx.which(gcov_path) + tool_paths["gcov"] = gcov_path + + escaped_include_paths = _get_escaped_xcode_cxx_inc_directories(repository_ctx, xcode_toolchains) + escaped_cxx_include_directories = [] + for path in escaped_include_paths: + escaped_cxx_include_directories.append((" \"%s\"," % path)) + if xcodeloc_err: + escaped_cxx_include_directories.append(" # Error: " + xcodeloc_err) + repository_ctx.template( + "BUILD", + paths["@build_bazel_apple_support//crosstool:BUILD.tpl"], + { + "%{cxx_builtin_include_directories}": "\n".join(escaped_cxx_include_directories), + "%{tool_paths_overrides}": ",\n ".join( + ['"%s": "%s"' % (k, v) for k, v in tool_paths.items()], + ), + }, + ) + + return True diff --git a/crosstool/setup.bzl b/crosstool/setup.bzl new file mode 100644 index 0000000..6e80d02 --- /dev/null +++ b/crosstool/setup.bzl @@ -0,0 +1,109 @@ +"""Configure the Apple CC toolchain""" + +load("//crosstool:osx_cc_configure.bzl", "configure_osx_toolchain") + +_DISABLE_ENV_VAR = "BAZEL_NO_APPLE_CPP_TOOLCHAIN" + +def _apple_cc_autoconf_toolchains_impl(repository_ctx): + """Generate BUILD file with 'toolchain' targets for the local host C++ toolchain. + + Args: + repository_ctx: repository context + """ + env = repository_ctx.os.environ + should_disable = _DISABLE_ENV_VAR in env and env[_DISABLE_ENV_VAR] == "1" + + if should_disable: + repository_ctx.file("BUILD", "# Apple CC toolchain autoconfiguration was disabled by {} env variable.".format(_DISABLE_ENV_VAR)) + elif repository_ctx.os.name.startswith("mac os"): + repository_ctx.symlink( + repository_ctx.path(Label("@build_bazel_apple_support//crosstool:BUILD.toolchains")), + "BUILD", + ) + else: + repository_ctx.file("BUILD", "# Apple CC toolchain autoconfiguration was disabled because you're not running on macOS") + +_apple_cc_autoconf_toolchains = repository_rule( + environ = [_DISABLE_ENV_VAR], + implementation = _apple_cc_autoconf_toolchains_impl, + configure = True, +) + +def _apple_cc_autoconf_impl(repository_ctx): + env = repository_ctx.os.environ + should_disable = _DISABLE_ENV_VAR in env and env[_DISABLE_ENV_VAR] == "1" + + if should_disable: + repository_ctx.file("BUILD", "# Apple CC autoconfiguration was disabled by {} env variable.".format(_DISABLE_ENV_VAR)) + elif repository_ctx.os.name.startswith("mac os"): + if not configure_osx_toolchain(repository_ctx): + fail("Failed to configure Apple CC toolchain, if you only have the command line tools installed and not Xcode, you cannot use this toolchain. You should either remove it or temporarily set '{}=1' in the environment".format(_DISABLE_ENV_VAR)) + else: + repository_ctx.file("BUILD", "# Apple CC autoconfiguration was disabled because you're not on macOS") + +MSVC_ENVVARS = [ + "BAZEL_VC", + "BAZEL_VC_FULL_VERSION", + "BAZEL_VS", + "BAZEL_WINSDK_FULL_VERSION", + "VS90COMNTOOLS", + "VS100COMNTOOLS", + "VS110COMNTOOLS", + "VS120COMNTOOLS", + "VS140COMNTOOLS", + "VS150COMNTOOLS", + "VS160COMNTOOLS", + "TMP", + "TEMP", +] + +_apple_cc_autoconf = repository_rule( + environ = [ + _DISABLE_ENV_VAR, + "ABI_LIBC_VERSION", + "ABI_VERSION", + "BAZEL_COMPILER", + "BAZEL_HOST_SYSTEM", + "BAZEL_CXXOPTS", + "BAZEL_LINKOPTS", + "BAZEL_LINKLIBS", + "BAZEL_LLVM_COV", + "BAZEL_LLVM_PROFDATA", + "BAZEL_PYTHON", + "BAZEL_SH", + "BAZEL_TARGET_CPU", + "BAZEL_TARGET_LIBC", + "BAZEL_TARGET_SYSTEM", + "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN", + "BAZEL_USE_LLVM_NATIVE_COVERAGE", + "BAZEL_LLVM", + "BAZEL_IGNORE_SYSTEM_HEADERS_VERSIONS", + "USE_CLANG_CL", + "CC", + "CC_CONFIGURE_DEBUG", + "CC_TOOLCHAIN_NAME", + "CPLUS_INCLUDE_PATH", + "DEVELOPER_DIR", + "GCOV", + "HOMEBREW_RUBY_PATH", + "SYSTEMROOT", + "USER", + ] + MSVC_ENVVARS, + implementation = _apple_cc_autoconf_impl, + configure = True, +) + +# buildifier: disable=unnamed-macro +def apple_cc_configure(): + _apple_cc_autoconf_toolchains(name = "local_config_apple_cc_toolchains") + _apple_cc_autoconf(name = "local_config_apple_cc") + native.register_toolchains( + # Use register_toolchain's target pattern expansion to register all toolchains in the package. + "@local_config_apple_cc_toolchains//:all", + ) + +def _apple_cc_configure_extension_impl(_): + _apple_cc_autoconf_toolchains(name = "local_config_apple_cc_toolchains") + _apple_cc_autoconf(name = "local_config_apple_cc") + +apple_cc_configure_extension = module_extension(implementation = _apple_cc_configure_extension_impl) diff --git a/crosstool/wrapped_clang.cc b/crosstool/wrapped_clang.cc new file mode 100644 index 0000000..044c0c8 --- /dev/null +++ b/crosstool/wrapped_clang.cc @@ -0,0 +1,429 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// wrapped_clang.cc: Pass args to 'xcrun clang' and zip dsym files. +// +// wrapped_clang passes its args to clang, but also supports a separate set of +// invocations to generate dSYM files. If "DSYM_HINT" flags are passed in, they +// are used to construct that separate set of invocations (instead of being +// passed to clang). +// The following "DSYM_HINT" flags control dsym generation. If any one if these +// are passed in, then they all must be passed in. +// "DSYM_HINT_LINKED_BINARY": Workspace-relative path to binary output of the +// link action generating the dsym file. +// "DSYM_HINT_DSYM_PATH": Workspace-relative path to dSYM dwarf file. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern char **environ; + +namespace { + +constexpr char kAddASTPathPrefix[] = "-Wl,-add_ast_path,"; + +// Returns the base name of the given filepath. For example, given +// /foo/bar/baz.txt, returns 'baz.txt'. +const char *Basename(const char *filepath) { + const char *base = strrchr(filepath, '/'); + return base ? (base + 1) : filepath; +} + +// Unescape and unquote an argument read from a line of a response file. +static std::string Unescape(const std::string &arg) { + std::string result; + auto length = arg.size(); + for (size_t i = 0; i < length; ++i) { + auto ch = arg[i]; + + // If it's a backslash, consume it and append the character that follows. + if (ch == '\\' && i + 1 < length) { + ++i; + result.push_back(arg[i]); + continue; + } + + // If it's a quote, process everything up to the matching quote, unescaping + // backslashed characters as needed. + if (ch == '"' || ch == '\'') { + auto quote = ch; + ++i; + while (i != length && arg[i] != quote) { + if (arg[i] == '\\' && i + 1 < length) { + ++i; + } + result.push_back(arg[i]); + ++i; + } + if (i == length) { + break; + } + continue; + } + + // It's a regular character. + result.push_back(ch); + } + + return result; +} + +// Converts an array of string arguments to char *arguments. +// The first arg is reduced to its basename as per execve conventions. +// Note that the lifetime of the char* arguments in the returned array +// are controlled by the lifetime of the strings in args. +std::vector ConvertToCArgs(const std::vector &args) { + std::vector c_args; + c_args.push_back(Basename(args[0].c_str())); + for (int i = 1; i < args.size(); i++) { + c_args.push_back(args[i].c_str()); + } + c_args.push_back(nullptr); + return c_args; +} + +// Spawns a subprocess for given arguments args. The first argument is used +// for the executable path. +bool RunSubProcess(const std::vector &args) { + std::vector exec_argv = ConvertToCArgs(args); + pid_t pid; + int status = posix_spawn(&pid, args[0].c_str(), nullptr, nullptr, + const_cast(exec_argv.data()), environ); + if (status == 0) { + int wait_status; + do { + wait_status = waitpid(pid, &status, 0); + } while ((wait_status == -1) && (errno == EINTR)); + if (wait_status < 0) { + std::cerr << "Error waiting on child process '" << args[0] << "'. " + << strerror(errno) << "\n"; + return false; + } + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + std::cerr << "Error in child process '" << args[0] << "'. " + << WEXITSTATUS(status) << "\n"; + return false; + } else if (WIFSIGNALED(status)) { + std::cerr << "Error in child process '" << args[0] << "'. " + << WTERMSIG(status) << "\n"; + return false; + } + } else { + std::cerr << "Error forking process '" << args[0] << "'. " + << strerror(status) << "\n"; + return false; + } + + return true; +} + +// Finds and replaces all instances of oldsub with newsub, in-place on str. +void FindAndReplace(const std::string &oldsub, const std::string &newsub, + std::string *str) { + int start = 0; + while ((start = str->find(oldsub, start)) != std::string::npos) { + str->replace(start, oldsub.length(), newsub); + start += newsub.length(); + } +} + +// If arg is of the classic flag form "foo=bar", and flagname is 'foo', sets +// str to point to a new std::string 'bar' and returns true. +// Otherwise, returns false. +bool SetArgIfFlagPresent(const std::string &arg, const std::string &flagname, + std::string *str) { + std::string prefix_string = flagname + "="; + if (arg.compare(0, prefix_string.length(), prefix_string) == 0) { + *str = arg.substr(prefix_string.length()); + return true; + } + return false; +} + +// Returns the DEVELOPER_DIR environment variable in the current process +// environment. Aborts if this variable is unset. +std::string GetMandatoryEnvVar(const std::string &var_name) { + char *env_value = getenv(var_name.c_str()); + if (env_value == nullptr) { + std::cerr << "Error: " << var_name << " not set.\n"; + exit(EXIT_FAILURE); + } + return env_value; +} + +// Returns true if `str` starts with the specified `prefix`. +bool StartsWith(const std::string &str, const std::string &prefix) { + return str.compare(0, prefix.size(), prefix) == 0; +} + +// If *`str` begins `prefix`, strip it out and return true. +// Otherwise leave *`str` unchanged and return false. +bool StripPrefixStringIfPresent(std::string *str, const std::string &prefix) { + if (StartsWith(*str, prefix)) { + *str = str->substr(prefix.size()); + return true; + } + return false; +} + +// An RAII temporary file. +class TempFile { + public: + // Create a new temporary file using the given path template string (the same + // form used by `mkstemp`). The file will automatically be deleted when the + // object goes out of scope. + static std::unique_ptr Create(const std::string &path_template) { + const char *tmpDir = getenv("TMPDIR"); + if (!tmpDir) { + tmpDir = "/tmp"; + } + size_t size = strlen(tmpDir) + path_template.size() + 2; + std::unique_ptr path(new char[size]); + snprintf(path.get(), size, "%s/%s", tmpDir, path_template.c_str()); + + if (mkstemp(path.get()) == -1) { + std::cerr << "Failed to create temporary file '" << path.get() + << "': " << strerror(errno) << "\n"; + return nullptr; + } + return std::unique_ptr(new TempFile(path.get())); + } + + // Explicitly make TempFile non-copyable and movable. + TempFile(const TempFile &) = delete; + TempFile &operator=(const TempFile &) = delete; + TempFile(TempFile &&) = default; + TempFile &operator=(TempFile &&) = default; + + ~TempFile() { remove(path_.c_str()); } + + // Gets the path to the temporary file. + std::string GetPath() const { return path_; } + + private: + explicit TempFile(const std::string &path) : path_(path) {} + + std::string path_; +}; + +static std::unique_ptr WriteResponseFile( + const std::vector &args) { + auto response_file = TempFile::Create("wrapped_clang_params.XXXXXX"); + std::ofstream response_file_stream(response_file->GetPath()); + + for (const auto &arg : args) { + // When Clang/Swift write out a response file to communicate from driver to + // frontend, they just quote every argument to be safe; we duplicate that + // instead of trying to be "smarter" and only quoting when necessary. + response_file_stream << '"'; + for (auto ch : arg) { + if (ch == '"' || ch == '\\') { + response_file_stream << '\\'; + } + response_file_stream << ch; + } + response_file_stream << "\"\n"; + } + + response_file_stream.close(); + return response_file; +} + +void ProcessArgument(const std::string arg, const std::string developer_dir, + const std::string sdk_root, const std::string cwd, + bool relative_ast_path, std::string &linked_binary, + std::string &dsym_path, + std::function consumer); + +bool ProcessResponseFile(const std::string arg, const std::string developer_dir, + const std::string sdk_root, const std::string cwd, + bool relative_ast_path, std::string &linked_binary, + std::string &dsym_path, + std::function consumer) { + auto path = arg.substr(1); + std::ifstream original_file(path); + // Ignore non-file args such as '@loader_path/...' + if (!original_file.good()) { + return false; + } + + std::string arg_from_file; + while (std::getline(original_file, arg_from_file)) { + // Arguments in response files might be quoted/escaped, so we need to + // unescape them ourselves. + ProcessArgument(Unescape(arg_from_file), developer_dir, sdk_root, cwd, + relative_ast_path, linked_binary, dsym_path, consumer); + } + + return true; +} + +std::string GetCurrentDirectory() { + // Passing null,0 causes getcwd to allocate the buffer of the correct size. + char *buffer = getcwd(nullptr, 0); + std::string cwd(buffer); + free(buffer); + return cwd; +} + +void ProcessArgument(const std::string arg, const std::string developer_dir, + const std::string sdk_root, const std::string cwd, + bool relative_ast_path, std::string &linked_binary, + std::string &dsym_path, + std::function consumer) { + auto new_arg = arg; + if (arg[0] == '@') { + if (ProcessResponseFile(arg, developer_dir, sdk_root, cwd, + relative_ast_path, linked_binary, dsym_path, + consumer)) { + return; + } + } + + if (SetArgIfFlagPresent(arg, "DSYM_HINT_LINKED_BINARY", &linked_binary)) { + return; + } + if (SetArgIfFlagPresent(arg, "DSYM_HINT_DSYM_PATH", &dsym_path)) { + return; + } + + std::string dest_dir, bitcode_symbol_map; + if (SetArgIfFlagPresent(arg, "DEBUG_PREFIX_MAP_PWD", &dest_dir)) { + new_arg = "-fdebug-prefix-map=" + cwd + "=" + dest_dir; + } + if (arg.compare("OSO_PREFIX_MAP_PWD") == 0) { + new_arg = "-Wl,-oso_prefix," + cwd + "/"; + } + + FindAndReplace("__BAZEL_XCODE_DEVELOPER_DIR__", developer_dir, &new_arg); + FindAndReplace("__BAZEL_XCODE_SDKROOT__", sdk_root, &new_arg); + + // Make the `add_ast_path` options used to embed Swift module references + // absolute to enable Swift debugging without dSYMs: see + // https://forums.swift.org/t/improving-swift-lldb-support-for-path-remappings/22694 + if (!relative_ast_path && + StripPrefixStringIfPresent(&new_arg, kAddASTPathPrefix)) { + // Only modify relative paths. + if (!StartsWith(arg, "/")) { + new_arg = std::string(kAddASTPathPrefix) + cwd + "/" + new_arg; + } else { + new_arg = std::string(kAddASTPathPrefix) + new_arg; + } + } + + consumer(new_arg); +} + +} // namespace + +int main(int argc, char *argv[]) { + std::string tool_name; + + std::string binary_name = Basename(argv[0]); + if (binary_name == "wrapped_clang_pp") { + tool_name = "clang++"; + } else if (binary_name == "wrapped_clang") { + tool_name = "clang"; + } else { + std::cerr << "Binary must either be named 'wrapped_clang' or " + "'wrapped_clang_pp', not " + << binary_name << "\n"; + return 1; + } + + std::string developer_dir = GetMandatoryEnvVar("DEVELOPER_DIR"); + std::string sdk_root = GetMandatoryEnvVar("SDKROOT"); + std::string linked_binary, dsym_path; + + const std::string cwd = GetCurrentDirectory(); + std::vector invocation_args = {"/usr/bin/xcrun", tool_name}; + std::vector processed_args = {}; + + bool relative_ast_path = getenv("RELATIVE_AST_PATH") != nullptr; + auto consumer = [&](const std::string &arg) { + processed_args.push_back(arg); + }; + for (int i = 1; i < argc; i++) { + std::string arg(argv[i]); + + ProcessArgument(arg, developer_dir, sdk_root, cwd, relative_ast_path, + linked_binary, dsym_path, consumer); + } + + // Special mode that only prints the command. Used for testing. + if (getenv("__WRAPPED_CLANG_LOG_ONLY")) { + for (const std::string &arg : invocation_args) std::cout << arg << ' '; + for (const std::string &arg : processed_args) std::cout << arg << ' '; + std::cout << "\n"; + return 0; + } + + auto response_file = WriteResponseFile(processed_args); + invocation_args.push_back("@" + response_file->GetPath()); + + // Check to see if we should postprocess with dsymutil. + bool postprocess = false; + if ((!linked_binary.empty()) || (!dsym_path.empty())) { + if ((linked_binary.empty()) || (dsym_path.empty())) { + const char *missing_dsym_flag; + if (linked_binary.empty()) { + missing_dsym_flag = "DSYM_HINT_LINKED_BINARY"; + } else { + missing_dsym_flag = "DSYM_HINT_DSYM_PATH"; + } + std::cerr << "Error in clang wrapper: If any dsym " + "hint is defined, then " + << missing_dsym_flag << " must be defined\n"; + return 1; + } else { + postprocess = true; + } + } + + if (!RunSubProcess(invocation_args)) { + return 1; + } + + if (!postprocess) { + return 0; + } + + std::vector dsymutil_args = {"/usr/bin/xcrun", + "dsymutil", + linked_binary, + "-o", + dsym_path, + "--flat", + "--no-swiftmodule-timestamp"}; + if (!RunSubProcess(dsymutil_args)) { + return 1; + } + + return 0; +} diff --git a/crosstool/xcrunwrapper.sh b/crosstool/xcrunwrapper.sh new file mode 100755 index 0000000..143ff23 --- /dev/null +++ b/crosstool/xcrunwrapper.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Copyright 2015 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# xcrunwrapper runs the command passed to it using xcrun. The first arg +# passed is the name of the tool to be invoked via xcrun. (For example, libtool +# or clang). +# xcrunwrapper replaces __BAZEL_XCODE_DEVELOPER_DIR__ with $DEVELOPER_DIR (or +# reasonable default) and __BAZEL_XCODE_SDKROOT__ with a valid path based on +# SDKROOT (or reasonable default). +# These values (__BAZEL_XCODE_*) are a shared secret withIosSdkCommands.java. + +set -eu + +TOOLNAME=$1 +shift + +# Pick values for DEVELOPER_DIR and SDKROOT as appropriate (if they weren't set) +WRAPPER_DEVDIR="${DEVELOPER_DIR:-}" +if [[ -z "${WRAPPER_DEVDIR}" ]] ; then + WRAPPER_DEVDIR="$(xcode-select -p)" +fi + +# Substitute toolkit path placeholders. +UPDATEDARGS=() +for ARG in "$@" ; do + ARG="${ARG//__BAZEL_XCODE_DEVELOPER_DIR__/${WRAPPER_DEVDIR}}" + ARG="${ARG//__BAZEL_XCODE_SDKROOT__/${SDKROOT}}" + UPDATEDARGS+=("${ARG}") +done + +/usr/bin/xcrun "${TOOLNAME}" "${UPDATEDARGS[@]}" diff --git a/lib/repositories.bzl b/lib/repositories.bzl index 90c3185..be613e9 100644 --- a/lib/repositories.bzl +++ b/lib/repositories.bzl @@ -15,6 +15,7 @@ """Definitions for handling Bazel repositories used by apple_support.""" load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("//crosstool:setup.bzl", "apple_cc_configure") def _maybe(repo_rule, name, **kwargs): """Executes the given repository rule if it hasn't been executed already. @@ -27,6 +28,7 @@ def _maybe(repo_rule, name, **kwargs): if not native.existing_rule(name): repo_rule(name = name, **kwargs) +# buildifier: disable=unnamed-macro def apple_support_dependencies(): """Fetches repository dependencies of the `apple_support` workspace. @@ -43,3 +45,5 @@ def apple_support_dependencies(): ], sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506", ) + + apple_cc_configure() diff --git a/test/shell/BUILD b/test/shell/BUILD new file mode 100644 index 0000000..3ba1b28 --- /dev/null +++ b/test/shell/BUILD @@ -0,0 +1,57 @@ +filegroup( + name = "for_bazel_tests", + testonly = True, + srcs = [ + "@build_bazel_apple_support//:for_bazel_tests", + ], +) + +sh_library( + name = "bashunit", + testonly = True, + srcs = [ + "integration_test_setup.sh", + "testenv.sh", + "unittest.bash", + "unittest_utils.sh", + ], +) + +sh_library( + name = "apple_common", + testonly = True, + srcs = ["apple_common.sh"], +) + +sh_test( + name = "objc_test", + size = "large", + srcs = ["objc_test.sh"], + data = [":for_bazel_tests"], + deps = [ + ":apple_common", + ":bashunit", + ], +) + +sh_test( + name = "apple_test", + srcs = ["apple_test.sh"], + data = [":for_bazel_tests"], + shard_count = 3, + deps = [ + ":apple_common", + ":bashunit", + ], +) + +sh_test( + name = "wrapped_clang_test", + size = "small", + srcs = ["wrapped_clang_test.sh"], + data = [ + ":bashunit", + "//crosstool:wrapped_clang", + "@bazel_tools//tools/bash/runfiles", + ], +) diff --git a/test/shell/apple_common.sh b/test/shell/apple_common.sh new file mode 100644 index 0000000..0207023 --- /dev/null +++ b/test/shell/apple_common.sh @@ -0,0 +1,203 @@ +#!/bin/bash +# +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Common Bash functions to test Apple rules in Bazel. +# + +function make_starlark_apple_binary_rule_in() { + local dir="$1"; shift + + # All of the attributes below, except for `stamp`, are required as part of the + # implied contract of `apple_common.link_multi_arch_binary` since it asks for + # attributes directly from the rule context. As these requirements are changed + # from implied attributes to function arguments, they can be removed. + cat >> "${dir}/starlark_apple_binary.bzl" < 1: + apple_env = {} + xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig] + apple_env.update(apple_common.apple_host_system_env(xcode_config)) + apple_env.update( + apple_common.target_apple_env( + xcode_config, + ctx.fragments.apple.single_arch_platform, + ), + ) + args = ctx.actions.args() + args.add('-create') + args.add_all(lipo_inputs) + args.add('-output', processed_binary) + ctx.actions.run( + arguments = [args], + env = apple_env, + executable = '/usr/bin/lipo', + execution_requirements = xcode_config.execution_info(), + inputs = lipo_inputs, + outputs = [processed_binary], + ) + else: + ctx.actions.symlink( + target_file = lipo_inputs[0], + output = processed_binary, + ) + return [ + DefaultInfo(files = depset([processed_binary])), + OutputGroupInfo(**link_result.output_groups), + link_result.debug_outputs_provider, + ] + +starlark_apple_binary = rule( + attrs = { + "_child_configuration_dummy": attr.label( + cfg = apple_common.multi_arch_split, + default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), + ), + "_xcode_config": attr.label( + default = configuration_field( + fragment = "apple", + name = "xcode_config_label", + ), + ), + "_xcrunwrapper": attr.label( + cfg = "exec", + default = Label("@bazel_tools//tools/objc:xcrunwrapper"), + executable = True, + ), + "binary_type": attr.string(default = "executable"), + "bundle_loader": attr.label(), + "deps": attr.label_list( + cfg = apple_common.multi_arch_split, + ), + "dylibs": attr.label_list(), + "linkopts": attr.string_list(), + "minimum_os_version": attr.string(), + "platform_type": attr.string(), + "stamp": attr.int(default = -1, values = [-1, 0, 1]), + }, + fragments = ["apple", "objc", "cpp"], + implementation = _starlark_apple_binary_impl, + outputs = { + # Provided for compatibility with apple_binary tests only. + "lipobin": "%{name}_lipobin", + }, +) +EOF +} + +function make_starlark_apple_static_library_rule_in() { + local dir="$1"; shift + + # All of the attributes below are required as part of the implied contract of + # `apple_common.link_multi_arch_static_library` since it asks for attributes + # directly from the rule context. As these requirements are changed from + # implied attributes to function arguments, they can be removed. + cat >> "${dir}/starlark_apple_static_library.bzl" < 1: + apple_env = {} + xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig] + apple_env.update(apple_common.apple_host_system_env(xcode_config)) + apple_env.update( + apple_common.target_apple_env( + xcode_config, + ctx.fragments.apple.single_arch_platform, + ), + ) + args = ctx.actions.args() + args.add('-create') + args.add_all(lipo_inputs) + args.add('-output', processed_library) + ctx.actions.run( + arguments = [args], + env = apple_env, + executable = '/usr/bin/lipo', + execution_requirements = xcode_config.execution_info(), + inputs = lipo_inputs, + outputs = [processed_library], + ) + else: + ctx.actions.symlink( + target_file = lipo_inputs[0], + output = processed_library, + ) + providers = [ + DefaultInfo(files = depset(files_to_build), runfiles = runfiles), + link_result.objc, + link_result.output_groups, + ] + return providers + +starlark_apple_static_library = rule( + _starlark_apple_static_library_impl, + attrs = { + '_child_configuration_dummy': attr.label( + cfg = apple_common.multi_arch_split, + default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), + ), + '_xcode_config': attr.label( + default = configuration_field( + fragment = "apple", + name = "xcode_config_label", + ), + ), + '_xcrunwrapper': attr.label( + executable = True, + cfg = 'exec', + default = Label("@bazel_tools//tools/objc:xcrunwrapper"), + ), + 'additional_linker_inputs': attr.label_list( + allow_files = True, + ), + 'avoid_deps': attr.label_list( + cfg = apple_common.multi_arch_split, + default = [], + ), + 'deps': attr.label_list( + cfg = apple_common.multi_arch_split, + ), + 'linkopts': attr.string_list(), + 'platform_type': attr.string(), + 'minimum_os_version': attr.string(), + }, + outputs = { + 'lipo_archive': '%{name}_lipo.a', + }, + cfg = apple_common.apple_crosstool_transition, + fragments = ['apple', 'objc', 'cpp',], +) +EOF +} diff --git a/test/shell/apple_test.sh b/test/shell/apple_test.sh new file mode 100755 index 0000000..a494e93 --- /dev/null +++ b/test/shell/apple_test.sh @@ -0,0 +1,408 @@ +#!/bin/bash +# +# Copyright 2015 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Load the test setup defined in the parent directory +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${CURRENT_DIR}/integration_test_setup.sh" \ + || { echo "integration_test_setup.sh not found!" >&2; exit 1; } + +source "${CURRENT_DIR}/apple_common.sh" \ + || { echo "apple_common.sh not found!" >&2; exit 1; } + +function set_up() { + # copy_examples + setup_objc_test_support + + # Find the version number for an installed Xcode. + XCODE_VERSION=$(xcodebuild -version | grep ^Xcode | cut -d' ' -f2) + + # create_new_workspace +} + +function test_apple_binary_crosstool_watchos() { + rm -rf package + mkdir -p package + make_starlark_apple_binary_rule_in package + + cat > package/BUILD < \$(@)", + tags = ["requires-darwin"], +) + +starlark_apple_binary( + name = "main_binary", + deps = [":main_lib"], + platform_type = "watchos", +) +objc_library( + name = "main_lib", + srcs = ["main.m"], + deps = [":lib_a"], +) +cc_library( + name = "cc_lib", + srcs = ["cc_lib.cc"], +) +# By depending on a library which requires it is built for watchos, this test +# verifies that dependencies of starlark_apple_binary are compiled for the +# specified platform_type. +objc_library( + name = "lib_a", + srcs = ["a.m"], + deps = [":cc_lib"], +) +EOF + cat > package/main.m < + +// Note that WKExtensionDelegate is only available in Watch SDK. +@interface TestInterfaceMain : NSObject +@end + +int main() { + return 0; +} +EOF + cat > package/a.m < + +// Note that WKExtensionDelegate is only available in Watch SDK. +@interface TestInterfaceA : NSObject +@end + +int aFunction() { + return 0; +} +EOF + cat > package/cc_lib.cc << EOF +#include + +std::string GetString() { return "h3ll0"; } +EOF + + bazel build --verbose_failures //package:lipo_out \ + --noincompatible_enable_cc_toolchain_resolution \ + --watchos_cpus=armv7k \ + --xcode_version="$XCODE_VERSION" \ + || fail "should build watch binary" + + grep "armv7k" bazel-bin/package/lipo_out \ + || fail "expected output binary to be for armv7k architecture" + + bazel build --verbose_failures //package:lipo_out \ + --noincompatible_enable_cc_toolchain_resolution \ + --watchos_cpus=i386 \ + --xcode_version="$XCODE_VERSION" \ + || fail "should build watch binary" + + grep "i386" bazel-bin/package/lipo_out \ + || fail "expected output binary to be for i386 architecture" +} + +function test_apple_static_library() { + rm -rf package + mkdir -p package + make_starlark_apple_static_library_rule_in package + + cat > package/BUILD < "package/dummy.m" < package/BUILD < package/main.m < package/BUILD < \$(@)", + tags = ["requires-darwin"], +) +EOF + touch package/a.m + cat > package/b.m < package/BUILD < "package/the main.m" < package/BUILD < \$(@)", + tags = ["requires-darwin"], +) +EOF + touch package/a.m + touch package/b.m + cat > package/main.m < package/cc_lib.cc << EOF +#include + +std::string GetString() { return "h3ll0"; } +EOF + + bazel build --verbose_failures //package:lipo_out \ + --noincompatible_enable_cc_toolchain_resolution \ + --ios_multi_cpus=i386,x86_64 \ + --xcode_version="$XCODE_VERSION" \ + || fail "should build starlark_apple_binary and obtain info via lipo" + + grep "i386 x86_64" bazel-bin/package/lipo_out \ + || fail "expected output binary to be for x86_64 architecture" +} + +function test_apple_binary_dsym_builds() { + rm -rf package + mkdir -p package + make_starlark_apple_binary_rule_in package + + cat > package/BUILD < package/main.m < package/BUILD < \$(@)", + tags = ["requires-darwin"], +) +EOF + touch package/a.m + cat > package/b.m <&2; exit 1; +} + +if type rlocation >&/dev/null; then + # If rlocation is defined, use it to look up data-dependencies. + # Load the unit test framework + source "$(rlocation build_bazel_apple_support/test/shell/unittest.bash)" \ + || print_message_and_exit "unittest.bash not found!" + # Load the test environment + source "$(rlocation build_bazel_apple_support/test/shell/testenv.sh)" \ + || print_message_and_exit "testenv.sh not found!" +else + # If rlocation is undefined, we are probably running under Blaze. + # Assume the existence of a runfiles tree. + + CURRENT_SCRIPT=${BASH_SOURCE[0]} + # Go to the directory where the script is running + cd "$(dirname ${CURRENT_SCRIPT})" \ + || print_message_and_exit "Unable to access $(dirname ${CURRENT_SCRIPT})" + + DIR=$(pwd) + # Load the unit test framework + source "$DIR/unittest.bash" || print_message_and_exit "unittest.bash not found!" + # Load the test environment + source "$DIR/testenv.sh" || print_message_and_exit "testenv.sh not found!" +fi + +# inplace-sed: a version of sed -i that actually works on Linux and Darwin. +# https://unix.stackexchange.com/questions/92895 +# https://stackoverflow.com/questions/5694228 +function inplace-sed() { + if [ $(uname) = "Darwin" ]; then + sed -i "" "$@" + else + sed -i "$@" + fi +} diff --git a/test/shell/objc_test.sh b/test/shell/objc_test.sh new file mode 100755 index 0000000..2c1c7c3 --- /dev/null +++ b/test/shell/objc_test.sh @@ -0,0 +1,173 @@ +#!/bin/bash +# +# Copyright 2015 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Load the test setup defined in the parent directory +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${CURRENT_DIR}/integration_test_setup.sh" \ + || { echo "integration_test_setup.sh not found!" >&2; exit 1; } + +source "${CURRENT_DIR}/apple_common.sh" \ + || { echo "apple_common.sh not found!" >&2; exit 1; } + +function make_lib() { + rm -rf ios + mkdir -p ios + + cat >ios/main.m < + +int main(int argc, char *argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} +EOF + + cat >ios/BUILD <"$TEST_log" 2>&1 || fail "should pass" + ls bazel-out/*/bin/ios/liblib.a \ + || fail "should generate lib.a" +} + +# Verifies contents of .a files do not contain timestamps -- if they did, the +# results would not be hermetic. +function test_archive_timestamps() { + setup_objc_test_support + + mkdir -p objclib + cat > objclib/BUILD < objclib/mysrc.m <"$TEST_log" 2>&1 \ + || fail "Should build objc_library" + + # Based on timezones, ar -tv may show the timestamp of the contents as either + # Dec 31 1969 or Jan 1 1970 -- either is fine. + # We would use 'date' here, but the format is slightly different (Jan 1 vs. + # Jan 01). + ar -tv bazel-out/*/bin/objclib/libobjclib.a \ + | grep "mysrc" | grep "Dec 31" | grep "1969" \ + || ar -tv bazel-out/*/bin/objclib/libobjclib.a \ + | grep "mysrc" | grep "Jan 1" | grep "1970" || \ + fail "Timestamp of contents of archive file should be zero" +} + +function test_strip_symbols() { + setup_objc_test_support + + rm -rf ios + mkdir -p ios + make_starlark_apple_binary_rule_in ios + + cat >ios/main.m < +/* function declaration */ +int addOne(int num); +int addOne(int num) { + return num + 1; +} + int main(int argc, char *argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return retVal; +} +EOF + + cat >ios/BUILD <"$TEST_log" 2>&1 || fail "should pass" + ls bazel-out/*/bin/ios/app_lipobin \ + || fail "should generate lipobin (stripped binary)" + ! nm bazel-out/*/bin/ios/app_lipobin | grep addOne \ + || fail "should fail to find symbol addOne" +} + +function test_cc_test_depending_on_objc() { + setup_objc_test_support + + rm -rf foo + mkdir -p foo + + cat >foo/a.cc < +int main(int argc, char** argv) { + std::cout << "Hello! I'm a test!\n"; + return 0; +} +EOF + + cat >foo/BUILD <"$TEST_log" 2>&1 || fail "should pass" +} + +run_suite "objc/ios test suite" diff --git a/test/shell/testenv.sh b/test/shell/testenv.sh new file mode 100755 index 0000000..153e064 --- /dev/null +++ b/test/shell/testenv.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +function setup_objc_test_support() { + IOS_SDK_VERSION=$(xcrun --sdk iphoneos --show-sdk-version) + export IOS_SDK_VERSION + + cat > WORKSPACE.bazel < .bazelrc <$TEST_log || fail "foo failed"; +# expect_log "blah" "Expected to see 'blah' in output of 'foo'." +# } +# +# # Test that bar works. +# function test_bar() { +# bar 2>$TEST_log || fail "bar failed"; +# expect_not_log "ERROR" "Unexpected error from 'bar'." +# ... +# assert_equals $x $y +# } +# +# run_suite "Test suite for blah" +# ------------------------------------------------------------------------ +# +# Each test function is considered to pass iff fail() is not called +# while it is active. fail() may be called directly, or indirectly +# via other assertions such as expect_log(). run_suite must be called +# at the very end. +# +# A test suite may redefine functions "set_up" and/or "tear_down"; +# these functions are executed before and after each test function, +# respectively. Similarly, "cleanup" and "timeout" may be redefined, +# and these function are called upon exit (of any kind) or a timeout. +# +# The user can pass --test_arg to blaze test to select specific tests +# to run. Specifying --test_arg multiple times allows to select several +# tests to be run in the given order. Additionally the user may define +# TESTS=(test_foo test_bar ...) to specify a subset of test functions to +# execute, for example, a working set during debugging. By default, all +# functions called test_* will be executed. +# +# This file provides utilities for assertions over the output of a +# command. The output of the command under test is directed to the +# file $TEST_log, and then the expect_log* assertions can be used to +# test for the presence of certain regular expressions in that file. +# +# The test framework is responsible for restoring the original working +# directory before each test. +# +# The order in which test functions are run is not defined, so it is +# important that tests clean up after themselves. +# +# Each test will be run in a new subshell. +# +# Functions named __* are not intended for use by clients. +# +# This framework implements the "test sharding protocol". +# + +[[ -n "$BASH_VERSION" ]] || + { echo "unittest.bash only works with bash!" >&2; exit 1; } + +export BAZEL_SHELL_TEST=1 + +DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +# Load the environment support utilities. +source "${DIR}/unittest_utils.sh" || { echo "unittest_utils.sh not found" >&2; exit 1; } + +#### Global variables: + +TEST_name="" # The name of the current test. + +TEST_log=$TEST_TMPDIR/log # The log file over which the + # expect_log* assertions work. Must + # be absolute to be robust against + # tests invoking 'cd'! + +TEST_passed="true" # The result of the current test; + # failed assertions cause this to + # become false. + +# These variables may be overridden by the test suite: + +TESTS=() # A subset or "working set" of test + # functions that should be run. By + # default, all tests called test_* are + # run. + +_TEST_FILTERS=() # List of globs to use to filter the tests. + # If non-empty, all tests matching at least one + # of the globs are run and test list provided in + # the arguments is ignored if present. + +__in_tear_down=0 # Indicates whether we are in `tear_down` phase + # of test. Used to avoid re-entering `tear_down` + # on failures within it. + +if (( $# > 0 )); then + ( + IFS=':' + echo "WARNING: Passing test names in arguments (--test_arg) is deprecated, please use --test_filter='$*' instead." >&2 + ) + + # Legacy behavior is to ignore missing regexp, but with errexit + # the following line fails without || true. + # TODO(dmarting): maybe we should revisit the way of selecting + # test with that framework (use Bazel's environment variable instead). + TESTS=($(for i in "$@"; do echo $i; done | grep ^test_ || true)) + if (( ${#TESTS[@]} == 0 )); then + echo "WARNING: Arguments do not specify tests!" >&2 + fi +fi +# TESTBRIDGE_TEST_ONLY contains the value of --test_filter, if any. We want to +# preferentially use that instead of $@ to determine which tests to run. +if [[ ${TESTBRIDGE_TEST_ONLY:-} != "" ]]; then + if (( ${#TESTS[@]} != 0 )); then + echo "WARNING: Both --test_arg and --test_filter specified, ignoring --test_arg" >&2 + TESTS=() + fi + # Split TESTBRIDGE_TEST_ONLY on colon and store it in `_TEST_FILTERS` array. + IFS=':' read -r -a _TEST_FILTERS <<< "$TESTBRIDGE_TEST_ONLY" +fi + +TEST_verbose="true" # Whether or not to be verbose. A + # command; "true" or "false" are + # acceptable. The default is: true. + +TEST_script="$0" # Full path to test script +# Check if the script path is absolute, if not prefix the PWD. +if [[ ! "$TEST_script" = /* ]]; then + TEST_script="${PWD}/$0" +fi + + +#### Internal functions + +function __show_log() { + echo "-- Test log: -----------------------------------------------------------" + [[ -e $TEST_log ]] && cat "$TEST_log" || echo "(Log file did not exist.)" + echo "------------------------------------------------------------------------" +} + +# Usage: __pad <pad-char> +# Print $title padded to 80 columns with $pad_char. +function __pad() { + local title=$1 + local pad=$2 + # Ignore the subshell error -- `head` closes the fd before reading to the + # end, therefore the subshell will get SIGPIPE while stuck in `write`. + { + echo -n "${pad}${pad} ${title} " + printf "%80s" " " | tr ' ' "$pad" + } | head -c 80 || true + echo +} + +#### Exported functions + +# Usage: init_test ... +# Deprecated. Has no effect. +function init_test() { + : +} + + +# Usage: set_up +# Called before every test function. May be redefined by the test suite. +function set_up() { + : +} + +# Usage: tear_down +# Called after every test function. May be redefined by the test suite. +function tear_down() { + : +} + +# Usage: cleanup +# Called upon eventual exit of the test suite. May be redefined by +# the test suite. +function cleanup() { + : +} + +# Usage: timeout +# Called upon early exit from a test due to timeout. +function timeout() { + : +} + +# Usage: testenv_set_up +# Called prior to set_up. For use by testenv.sh. +function testenv_set_up() { + : +} + +# Usage: testenv_tear_down +# Called after tear_down. For use by testenv.sh. +function testenv_tear_down() { + : +} + +# Usage: fail <message> [<message> ...] +# Print failure message with context information, and mark the test as +# a failure. The context includes a stacktrace including the longest sequence +# of calls outside this module. (We exclude the top and bottom portions of +# the stack because they just add noise.) Also prints the contents of +# $TEST_log. +function fail() { + __show_log >&2 + echo "${TEST_name} FAILED: $*." >&2 + # Keep the original error message if we fail in `tear_down` after a failure. + [[ "${TEST_passed}" == "true" ]] && echo "$@" >"$TEST_TMPDIR"/__fail + TEST_passed="false" + __show_stack + # Cleanup as we are leaving the subshell now + __run_tear_down_after_failure + exit 1 +} + +function __run_tear_down_after_failure() { + # Skip `tear_down` after a failure in `tear_down` to prevent infinite + # recursion. + (( __in_tear_down )) && return + __in_tear_down=1 + echo -e "\nTear down:\n" >&2 + tear_down + testenv_tear_down +} + +# Usage: warn <message> +# Print a test warning with context information. +# The context includes a stacktrace including the longest sequence +# of calls outside this module. (We exclude the top and bottom portions of +# the stack because they just add noise.) +function warn() { + __show_log >&2 + echo "${TEST_name} WARNING: $1." >&2 + __show_stack + + if [[ -n "${TEST_WARNINGS_OUTPUT_FILE:-}" ]]; then + echo "${TEST_name} WARNING: $1." >> "$TEST_WARNINGS_OUTPUT_FILE" + fi +} + +# Usage: show_stack +# Prints the portion of the stack that does not belong to this module, +# i.e. the user's code that called a failing assertion. Stack may not +# be available if Bash is reading commands from stdin; an error is +# printed in that case. +__show_stack() { + local i=0 + local trace_found=0 + + # Skip over active calls within this module: + while (( i < ${#FUNCNAME[@]} )) && [[ ${BASH_SOURCE[i]:-} == "${BASH_SOURCE[0]}" ]]; do + (( ++i )) + done + + # Show all calls until the next one within this module (typically run_suite): + while (( i < ${#FUNCNAME[@]} )) && [[ ${BASH_SOURCE[i]:-} != "${BASH_SOURCE[0]}" ]]; do + # Read online docs for BASH_LINENO to understand the strange offset. + # Undefined can occur in the BASH_SOURCE stack apparently when one exits from a subshell + echo "${BASH_SOURCE[i]:-"Unknown"}:${BASH_LINENO[i - 1]:-"Unknown"}: in call to ${FUNCNAME[i]:-"Unknown"}" >&2 + (( ++i )) + trace_found=1 + done + + (( trace_found )) || echo "[Stack trace not available]" >&2 +} + +# Usage: expect_log <regexp> [error-message] +# Asserts that $TEST_log matches regexp. Prints the contents of +# $TEST_log and the specified (optional) error message otherwise, and +# returns non-zero. +function expect_log() { + local pattern=$1 + local message=${2:-Expected regexp "$pattern" not found} + grep -sq -- "$pattern" $TEST_log && return 0 + + fail "$message" + return 1 +} + +# Usage: expect_log_warn <regexp> [error-message] +# Warns if $TEST_log does not match regexp. Prints the contents of +# $TEST_log and the specified (optional) error message on mismatch. +function expect_log_warn() { + local pattern=$1 + local message=${2:-Expected regexp "$pattern" not found} + grep -sq -- "$pattern" $TEST_log && return 0 + + warn "$message" + return 1 +} + +# Usage: expect_log_once <regexp> [error-message] +# Asserts that $TEST_log contains one line matching <regexp>. +# Prints the contents of $TEST_log and the specified (optional) +# error message otherwise, and returns non-zero. +function expect_log_once() { + local pattern=$1 + local message=${2:-Expected regexp "$pattern" not found exactly once} + expect_log_n "$pattern" 1 "$message" +} + +# Usage: expect_log_n <regexp> <count> [error-message] +# Asserts that $TEST_log contains <count> lines matching <regexp>. +# Prints the contents of $TEST_log and the specified (optional) +# error message otherwise, and returns non-zero. +function expect_log_n() { + local pattern=$1 + local expectednum=${2:-1} + local message=${3:-Expected regexp "$pattern" not found exactly $expectednum times} + local count=$(grep -sc -- "$pattern" $TEST_log) + (( count == expectednum )) && return 0 + fail "$message" + return 1 +} + +# Usage: expect_not_log <regexp> [error-message] +# Asserts that $TEST_log does not match regexp. Prints the contents +# of $TEST_log and the specified (optional) error message otherwise, and +# returns non-zero. +function expect_not_log() { + local pattern=$1 + local message=${2:-Unexpected regexp "$pattern" found} + grep -sq -- "$pattern" $TEST_log || return 0 + + fail "$message" + return 1 +} + +# Usage: expect_query_targets <arguments> +# Checks that log file contains exactly the targets in the argument list. +function expect_query_targets() { + for arg in "$@"; do + expect_log_once "^$arg$" + done + +# Checks that the number of lines started with '//' equals to the number of +# arguments provided. + expect_log_n "^//[^ ]*$" $# +} + +# Usage: expect_log_with_timeout <regexp> <timeout> [error-message] +# Waits for the given regexp in the $TEST_log for up to timeout seconds. +# Prints the contents of $TEST_log and the specified (optional) +# error message otherwise, and returns non-zero. +function expect_log_with_timeout() { + local pattern=$1 + local timeout=$2 + local message=${3:-Regexp "$pattern" not found in "$timeout" seconds} + local count=0 + while (( count < timeout )); do + grep -sq -- "$pattern" "$TEST_log" && return 0 + let count=count+1 + sleep 1 + done + + grep -sq -- "$pattern" "$TEST_log" && return 0 + fail "$message" + return 1 +} + +# Usage: expect_cmd_with_timeout <expected> <cmd> [timeout] +# Repeats the command once a second for up to timeout seconds (10s by default), +# until the output matches the expected value. Fails and returns 1 if +# the command does not return the expected value in the end. +function expect_cmd_with_timeout() { + local expected="$1" + local cmd="$2" + local timeout=${3:-10} + local count=0 + while (( count < timeout )); do + local actual="$($cmd)" + [[ "$expected" == "$actual" ]] && return 0 + (( ++count )) + sleep 1 + done + + [[ "$expected" == "$actual" ]] && return 0 + fail "Expected '${expected}' within ${timeout}s, was '${actual}'" + return 1 +} + +# Usage: assert_one_of <expected_list>... <actual> +# Asserts that actual is one of the items in expected_list +# +# Example: +# local expected=( "foo", "bar", "baz" ) +# assert_one_of $expected $actual +function assert_one_of() { + local args=("$@") + local last_arg_index=$((${#args[@]} - 1)) + local actual=${args[last_arg_index]} + unset args[last_arg_index] + for expected_item in "${args[@]}"; do + [[ "$expected_item" == "$actual" ]] && return 0 + done; + + fail "Expected one of '${args[*]}', was '$actual'" + return 1 +} + +# Usage: assert_not_one_of <expected_list>... <actual> +# Asserts that actual is not one of the items in expected_list +# +# Example: +# local unexpected=( "foo", "bar", "baz" ) +# assert_not_one_of $unexpected $actual +function assert_not_one_of() { + local args=("$@") + local last_arg_index=$((${#args[@]} - 1)) + local actual=${args[last_arg_index]} + unset args[last_arg_index] + for expected_item in "${args[@]}"; do + if [[ "$expected_item" == "$actual" ]]; then + fail "'${args[*]}' contains '$actual'" + return 1 + fi + done; + + return 0 +} + +# Usage: assert_equals <expected> <actual> +# Asserts [[ expected == actual ]]. +function assert_equals() { + local expected=$1 actual=$2 + [[ "$expected" == "$actual" ]] && return 0 + + fail "Expected '$expected', was '$actual'" + return 1 +} + +# Usage: assert_not_equals <unexpected> <actual> +# Asserts [[ unexpected != actual ]]. +function assert_not_equals() { + local unexpected=$1 actual=$2 + [[ "$unexpected" != "$actual" ]] && return 0; + + fail "Expected not '${unexpected}', was '${actual}'" + return 1 +} + +# Usage: assert_contains <regexp> <file> [error-message] +# Asserts that file matches regexp. Prints the contents of +# file and the specified (optional) error message otherwise, and +# returns non-zero. +function assert_contains() { + local pattern=$1 + local file=$2 + local message=${3:-Expected regexp "$pattern" not found in "$file"} + grep -sq -- "$pattern" "$file" && return 0 + + cat "$file" >&2 + fail "$message" + return 1 +} + +# Usage: assert_not_contains <regexp> <file> [error-message] +# Asserts that file does not match regexp. Prints the contents of +# file and the specified (optional) error message otherwise, and +# returns non-zero. +function assert_not_contains() { + local pattern=$1 + local file=$2 + local message=${3:-Expected regexp "$pattern" found in "$file"} + + if [[ -f "$file" ]]; then + grep -sq -- "$pattern" "$file" || return 0 + else + fail "$file is not a file: $message" + return 1 + fi + + cat "$file" >&2 + fail "$message" + return 1 +} + +function assert_contains_n() { + local pattern=$1 + local expectednum=${2:-1} + local file=$3 + local message=${4:-Expected regexp "$pattern" not found exactly $expectednum times} + local count + if [[ -f "$file" ]]; then + count=$(grep -sc -- "$pattern" "$file") + else + fail "$file is not a file: $message" + return 1 + fi + (( count == expectednum )) && return 0 + + cat "$file" >&2 + fail "$message" + return 1 +} + +# Updates the global variables TESTS if +# sharding is enabled, i.e. ($TEST_TOTAL_SHARDS > 0). +function __update_shards() { + [[ -z "${TEST_TOTAL_SHARDS-}" ]] && return 0 + + (( TEST_TOTAL_SHARDS > 0 )) || + { echo "Invalid total shards ${TEST_TOTAL_SHARDS}" >&2; exit 1; } + + (( TEST_SHARD_INDEX < 0 || TEST_SHARD_INDEX >= TEST_TOTAL_SHARDS )) && + { echo "Invalid shard ${TEST_SHARD_INDEX}" >&2; exit 1; } + + IFS=$'\n' read -rd $'\0' -a TESTS < <( + for test in "${TESTS[@]}"; do echo "$test"; done | + awk "NR % ${TEST_TOTAL_SHARDS} == ${TEST_SHARD_INDEX}" && + echo -en '\0') + + [[ -z "${TEST_SHARD_STATUS_FILE-}" ]] || touch "$TEST_SHARD_STATUS_FILE" +} + +# Usage: __test_terminated <signal-number> +# Handler that is called when the test terminated unexpectedly +function __test_terminated() { + __show_log >&2 + echo "$TEST_name FAILED: terminated by signal $1." >&2 + TEST_passed="false" + __show_stack + timeout + exit 1 +} + +# Usage: __test_terminated_err +# Handler that is called when the test terminated unexpectedly due to "errexit". +function __test_terminated_err() { + # When a subshell exits due to signal ERR, its parent shell also exits, + # thus the signal handler is called recursively and we print out the + # error message and stack trace multiple times. We're only interested + # in the first one though, as it contains the most information, so ignore + # all following. + if [[ -f $TEST_TMPDIR/__err_handled ]]; then + exit 1 + fi + __show_log >&2 + if [[ ! -z "$TEST_name" ]]; then + echo -n "$TEST_name " >&2 + fi + echo "FAILED: terminated because this command returned a non-zero status:" >&2 + touch $TEST_TMPDIR/__err_handled + TEST_passed="false" + __show_stack + # If $TEST_name is still empty, the test suite failed before we even started + # to run tests, so we shouldn't call tear_down. + if [[ -n "$TEST_name" ]]; then + __run_tear_down_after_failure + fi + exit 1 +} + +# Usage: __trap_with_arg <handler> <signals ...> +# Helper to install a trap handler for several signals preserving the signal +# number, so that the signal number is available to the trap handler. +function __trap_with_arg() { + func="$1" ; shift + for sig ; do + trap "$func $sig" "$sig" + done +} + +# Usage: <node> <block> +# Adds the block to the given node in the report file. Quotes in the in +# arguments need to be escaped. +function __log_to_test_report() { + local node="$1" + local block="$2" + if [[ ! -e "$XML_OUTPUT_FILE" ]]; then + local xml_header='<?xml version="1.0" encoding="UTF-8"?>' + echo "${xml_header}<testsuites></testsuites>" > "$XML_OUTPUT_FILE" + fi + + # replace match on node with block and match + # replacement expression only needs escaping for quotes + perl -e "\ +\$input = @ARGV[0]; \ +\$/=undef; \ +open FILE, '+<$XML_OUTPUT_FILE'; \ +\$content = <FILE>; \ +if (\$content =~ /($node.*)\$/) { \ + seek FILE, 0, 0; \ + print FILE \$\` . \$input . \$1; \ +}; \ +close FILE" "$block" +} + +# Usage: <total> <passed> +# Adds the test summaries to the xml nodes. +function __finish_test_report() { + local suite_name="$1" + local total="$2" + local passed="$3" + local failed=$((total - passed)) + + # Update the xml output with the suite name and total number of + # passed/failed tests. + cat "$XML_OUTPUT_FILE" | \ + sed \ + "s/<testsuites>/<testsuites tests=\"$total\" failures=\"0\" errors=\"$failed\">/" | \ + sed \ + "s/<testsuite>/<testsuite name=\"${suite_name}\" tests=\"$total\" failures=\"0\" errors=\"$failed\">/" \ + > "${XML_OUTPUT_FILE}.bak" + + rm -f "$XML_OUTPUT_FILE" + mv "${XML_OUTPUT_FILE}.bak" "$XML_OUTPUT_FILE" +} + +# Multi-platform timestamp function +UNAME=$(uname -s | tr 'A-Z' 'a-z') +if [[ "$UNAME" == "linux" ]] || [[ "$UNAME" =~ msys_nt* ]]; then + function timestamp() { + echo $(($(date +%s%N)/1000000)) + } +else + function timestamp() { + # macOS and BSDs do not have %N, so Python is the best we can do. + # LC_ALL=C works around python 3.8 and 3.9 crash on macOS when the + # filesystem encoding is unspecified (e.g. when LANG=en_US). + local PYTHON=python + command -v python3 &> /dev/null && PYTHON=python3 + LC_ALL=C "${PYTHON}" -c 'import time; print(int(round(time.time() * 1000)))' + } +fi + +function get_run_time() { + local ts_start=$1 + local ts_end=$2 + run_time_ms=$((ts_end - ts_start)) + echo $((run_time_ms / 1000)).${run_time_ms: -3} +} + +# Usage: run_tests <suite-comment> +# Must be called from the end of the user's test suite. +# Calls exit with zero on success, non-zero otherwise. +function run_suite() { + local message="$1" + # The name of the suite should be the script being run, which + # will be the filename with the ".sh" extension removed. + local suite_name="$(basename "$0")" + + echo >&2 + echo "$message" >&2 + echo >&2 + + __log_to_test_report "<\/testsuites>" "<testsuite></testsuite>" + + local total=0 + local passed=0 + + atexit "cleanup" + + # If the user didn't specify an explicit list of tests (e.g. a + # working set), use them all. + if (( ${#TESTS[@]} == 0 )); then + # Even if there aren't any tests, this needs to succeed. + local all_tests=() + IFS=$'\n' read -d $'\0' -ra all_tests < <( + declare -F | awk '{print $3}' | grep ^test_ || true; echo -en '\0') + + if (( "${#_TEST_FILTERS[@]}" == 0 )); then + # Use ${array[@]+"${array[@]}"} idiom to avoid errors when running with + # Bash version <= 4.4 with `nounset` when `all_tests` is empty ( + # https://github.com/bminor/bash/blob/a0c0a00fc419b7bc08202a79134fcd5bc0427071/CHANGES#L62-L63). + TESTS=("${all_tests[@]+${all_tests[@]}}") + else + for t in "${all_tests[@]+${all_tests[@]}}"; do + local matches=0 + for f in "${_TEST_FILTERS[@]}"; do + # We purposely want to glob match. + # shellcheck disable=SC2053 + [[ "$t" = $f ]] && matches=1 && break + done + if (( matches )); then + TESTS+=("$t") + fi + done + fi + + elif [[ -n "${TEST_WARNINGS_OUTPUT_FILE:-}" ]]; then + if grep -q "TESTS=" "$TEST_script" ; then + echo "TESTS variable overridden in sh_test. Please remove before submitting" \ + >> "$TEST_WARNINGS_OUTPUT_FILE" + fi + fi + + # Reset TESTS in the common case where it contains a single empty string. + if [[ -z "${TESTS[*]-}" ]]; then + TESTS=() + fi + local original_tests_size=${#TESTS[@]} + + __update_shards + + if [[ "${#TESTS[@]}" -ne 0 ]]; then + for TEST_name in "${TESTS[@]}"; do + >"$TEST_log" # Reset the log. + TEST_passed="true" + + (( ++total )) + if [[ "$TEST_verbose" == "true" ]]; then + date >&2 + __pad "$TEST_name" '*' >&2 + fi + + local run_time="0.0" + rm -f "${TEST_TMPDIR}"/{__ts_start,__ts_end} + + if [[ "$(type -t "$TEST_name")" == function ]]; then + # Save exit handlers eventually set. + local SAVED_ATEXIT="$ATEXIT"; + ATEXIT= + + # Run test in a subshell. + rm -f "${TEST_TMPDIR}"/__err_handled + __trap_with_arg __test_terminated INT KILL PIPE TERM ABRT FPE ILL QUIT SEGV + + # Remember -o pipefail value and disable it for the subshell result + # collection. + if [[ "${SHELLOPTS}" =~ (^|:)pipefail(:|$) ]]; then + local __opt_switch=-o + else + local __opt_switch=+o + fi + set +o pipefail + ( + set "${__opt_switch}" pipefail + # if errexit is enabled, make sure we run cleanup and collect the log. + if [[ "$-" = *e* ]]; then + set -E + trap __test_terminated_err ERR + fi + timestamp >"${TEST_TMPDIR}"/__ts_start + testenv_set_up + set_up + eval "$TEST_name" + __in_tear_down=1 + tear_down + testenv_tear_down + timestamp >"${TEST_TMPDIR}"/__ts_end + test "$TEST_passed" == "true" + ) 2>&1 | tee "${TEST_TMPDIR}"/__log + # Note that tee will prevent the control flow continuing if the test + # spawned any processes which are still running and have not closed + # their stdout. + + test_subshell_status=${PIPESTATUS[0]} + set "${__opt_switch}" pipefail + if (( test_subshell_status != 0 )); then + TEST_passed="false" + # Ensure that an end time is recorded in case the test subshell + # terminated prematurely. + [[ -f "$TEST_TMPDIR"/__ts_end ]] || timestamp >"$TEST_TMPDIR"/__ts_end + fi + + # Calculate run time for the testcase. + local ts_start + ts_start=$(<"${TEST_TMPDIR}"/__ts_start) + local ts_end + ts_end=$(<"${TEST_TMPDIR}"/__ts_end) + run_time=$(get_run_time $ts_start $ts_end) + + # Eventually restore exit handlers. + if [[ -n "$SAVED_ATEXIT" ]]; then + ATEXIT="$SAVED_ATEXIT" + trap "$ATEXIT" EXIT + fi + else # Bad test explicitly specified in $TESTS. + fail "Not a function: '$TEST_name'" + fi + + local testcase_tag="" + + local red='\033[0;31m' + local green='\033[0;32m' + local no_color='\033[0m' + + if [[ "$TEST_verbose" == "true" ]]; then + echo >&2 + fi + + if [[ "$TEST_passed" == "true" ]]; then + if [[ "$TEST_verbose" == "true" ]]; then + echo -e "${green}PASSED${no_color}: ${TEST_name}" >&2 + fi + (( ++passed )) + testcase_tag="<testcase name=\"${TEST_name}\" status=\"run\" time=\"${run_time}\" classname=\"\"></testcase>" + else + echo -e "${red}FAILED${no_color}: ${TEST_name}" >&2 + # end marker in CDATA cannot be escaped, we need to split the CDATA sections + log=$(sed 's/]]>/]]>]]><![CDATA[/g' "${TEST_TMPDIR}"/__log) + fail_msg=$(cat "${TEST_TMPDIR}"/__fail 2> /dev/null || echo "No failure message") + # Replacing '&' with '&', '<' with '<', '>' with '>', and '"' with '"' + escaped_fail_msg=$(echo "$fail_msg" | sed 's/&/\&/g' | sed 's/</\</g' | sed 's/>/\>/g' | sed 's/"/\"/g') + testcase_tag="<testcase name=\"${TEST_name}\" status=\"run\" time=\"${run_time}\" classname=\"\"><error message=\"${escaped_fail_msg}\"><![CDATA[${log}]]></error></testcase>" + fi + + if [[ "$TEST_verbose" == "true" ]]; then + echo >&2 + fi + __log_to_test_report "<\/testsuite>" "$testcase_tag" + done + fi + + __finish_test_report "$suite_name" $total $passed + __pad "${passed} / ${total} tests passed." '*' >&2 + if (( original_tests_size == 0 )); then + __pad "No tests found." '*' + exit 1 + elif (( total != passed )); then + __pad "There were errors." '*' >&2 + exit 1 + elif (( total == 0 )); then + __pad "No tests executed due to sharding. Check your test's shard_count." '*' + __pad "Succeeding anyway." '*' + fi + + exit 0 +} diff --git a/test/shell/unittest_utils.sh b/test/shell/unittest_utils.sh new file mode 100644 index 0000000..be3409e --- /dev/null +++ b/test/shell/unittest_utils.sh @@ -0,0 +1,181 @@ +# Copyright 2020 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Support for unittest.bash + +#### Set up the test environment. + +set -euo pipefail + +cat_jvm_log () { + if [[ "$log_content" =~ \ + "(error code:".*", error message: '".*"', log file: '"(.*)"')" ]]; then + echo >&2 + echo "Content of ${BASH_REMATCH[1]}:" >&2 + cat "${BASH_REMATCH[1]}" >&2 + fi +} + +# Print message in "$1" then exit with status "$2" +die () { + # second argument is optional, defaulting to 1 + local status_code=${2:-1} + # Stop capturing stdout/stderr, and dump captured output + if [[ "$CAPTURED_STD_ERR" -ne 0 || "$CAPTURED_STD_OUT" -ne 0 ]]; then + restore_outputs + if [[ "$CAPTURED_STD_OUT" -ne 0 ]]; then + cat "${TEST_TMPDIR}/captured.out" + CAPTURED_STD_OUT=0 + fi + if [[ "$CAPTURED_STD_ERR" -ne 0 ]]; then + cat "${TEST_TMPDIR}/captured.err" 1>&2 + cat_jvm_log "$(cat "${TEST_TMPDIR}/captured.err")" + CAPTURED_STD_ERR=0 + fi + fi + + if [[ -n "${1-}" ]] ; then + echo "$1" 1>&2 + fi + if [[ -n "${BASH-}" ]]; then + local caller_n=0 + while [[ $caller_n -lt 4 ]] && \ + caller_out=$(caller $caller_n 2>/dev/null); do + test $caller_n -eq 0 && echo "CALLER stack (max 4):" + echo " $caller_out" + let caller_n=caller_n+1 + done 1>&2 + fi + if [[ -n "${status_code}" && "${status_code}" -ne 0 ]]; then + exit "$status_code" + else + exit 1 + fi +} + +# Print message in "$1" then record that a non-fatal error occurred in +# ERROR_COUNT +ERROR_COUNT="${ERROR_COUNT:-0}" +error () { + if [[ -n "$1" ]] ; then + echo "$1" 1>&2 + fi + ERROR_COUNT=$(($ERROR_COUNT + 1)) +} + +# Die if "$1" != "$2", print $3 as death reason +check_eq () { + [[ "$1" = "$2" ]] || die "Check failed: '$1' == '$2' ${3:+ ($3)}" +} + +# Die if "$1" == "$2", print $3 as death reason +check_ne () { + [[ "$1" != "$2" ]] || die "Check failed: '$1' != '$2' ${3:+ ($3)}" +} + +# The structure of the following if statements is such that if '[[' fails +# (e.g., a non-number was passed in) then the check will fail. + +# Die if "$1" > "$2", print $3 as death reason +check_le () { + [[ "$1" -gt "$2" ]] || die "Check failed: '$1' <= '$2' ${3:+ ($3)}" +} + +# Die if "$1" >= "$2", print $3 as death reason +check_lt () { + [[ "$1" -lt "$2" ]] || die "Check failed: '$1' < '$2' ${3:+ ($3)}" +} + +# Die if "$1" < "$2", print $3 as death reason +check_ge () { + [[ "$1" -ge "$2" ]] || die "Check failed: '$1' >= '$2' ${3:+ ($3)}" +} + +# Die if "$1" <= "$2", print $3 as death reason +check_gt () { + [[ "$1" -gt "$2" ]] || die "Check failed: '$1' > '$2' ${3:+ ($3)}" +} + +# Die if $2 !~ $1; print $3 as death reason +check_match () +{ + expr match "$2" "$1" >/dev/null || \ + die "Check failed: '$2' does not match regex '$1' ${3:+ ($3)}" +} + +# Run command "$1" at exit. Like "trap" but multiple atexits don't +# overwrite each other. Will break if someone does call trap +# directly. So, don't do that. +ATEXIT="${ATEXIT-}" +atexit () { + if [[ -z "$ATEXIT" ]]; then + ATEXIT="$1" + else + ATEXIT="$1 ; $ATEXIT" + fi + trap "$ATEXIT" EXIT +} + +## TEST_TMPDIR +if [[ -z "${TEST_TMPDIR:-}" ]]; then + export TEST_TMPDIR="$(mktemp -d ${TMPDIR:-/tmp}/bazel-test.XXXXXXXX)" +fi +if [[ ! -e "${TEST_TMPDIR}" ]]; then + mkdir -p -m 0700 "${TEST_TMPDIR}" + # Clean TEST_TMPDIR on exit + atexit "rm -fr ${TEST_TMPDIR}" +fi + +# Functions to compare the actual output of a test to the expected +# (golden) output. +# +# Usage: +# capture_test_stdout +# ... do something ... +# diff_test_stdout "$TEST_SRCDIR/path/to/golden.out" + +# Redirect a file descriptor to a file. +CAPTURED_STD_OUT="${CAPTURED_STD_OUT:-0}" +CAPTURED_STD_ERR="${CAPTURED_STD_ERR:-0}" + +capture_test_stdout () { + exec 3>&1 # Save stdout as fd 3 + exec 4>"${TEST_TMPDIR}/captured.out" + exec 1>&4 + CAPTURED_STD_OUT=1 +} + +capture_test_stderr () { + exec 6>&2 # Save stderr as fd 6 + exec 7>"${TEST_TMPDIR}/captured.err" + exec 2>&7 + CAPTURED_STD_ERR=1 +} + +# Force XML_OUTPUT_FILE to an existing path +if [[ -z "${XML_OUTPUT_FILE:-}" ]]; then + XML_OUTPUT_FILE=${TEST_TMPDIR}/output.xml +fi + +# Functions to provide easy access to external repository outputs in the sibling +# repository layout. +# +# Usage: +# bin_dir <repository name> +# genfiles_dir <repository name> +# testlogs_dir <repository name> + +testlogs_dir() { + echo $(bazel info bazel-testlogs | sed "s|bazel-out|bazel-out/$1|") +} diff --git a/test/shell/wrapped_clang_test.sh b/test/shell/wrapped_clang_test.sh new file mode 100755 index 0000000..2a5f085 --- /dev/null +++ b/test/shell/wrapped_clang_test.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# -*- coding: utf-8 -*- + +# Copyright 2019 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Unit tests for wrapped_clang. + +# --- begin runfiles.bash initialization --- +# Copy-pasted from Bazel's Bash runfiles library (tools/bash/runfiles/runfiles.bash). +set -euo pipefail +if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ -f "$0.runfiles_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" + elif [[ -f "$0.runfiles/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" + elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + export RUNFILES_DIR="$0.runfiles" + fi +fi +if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash" +elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \ + "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)" +else + echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash" + exit 1 +fi +# --- end runfiles.bash initialization --- + + +# Load test environment +source "$(rlocation "build_bazel_apple_support/test/shell/unittest.bash")" \ + || { echo "unittest.bash not found!" >&2; exit 1; } +WRAPPED_CLANG=$(rlocation "build_bazel_apple_support/crosstool/wrapped_clang") + + +# This env var tells wrapped_clang to log its command instead of running. +export __WRAPPED_CLANG_LOG_ONLY=1 + + +# Test that add_ast_path is remapped properly. +function test_add_ast_path_remapping() { + env DEVELOPER_DIR=dummy SDKROOT=a \ + "${WRAPPED_CLANG}" "-Wl,-add_ast_path,foo" >$TEST_log || fail "wrapped_clang failed"; + expect_log "-Wl,-add_ast_path,${PWD}/foo" "Expected add_ast_path to be remapped." +} + +function test_disable_add_ast_path_remapping() { + env RELATIVE_AST_PATH=isset DEVELOPER_DIR=dummy SDKROOT=a \ + "${WRAPPED_CLANG}" "-Wl,-add_ast_path,relative/foo" >$TEST_log || fail "wrapped_clang failed"; + expect_log "-Wl,-add_ast_path,relative/foo" "Expected add_ast_path to not be remapped." +} + +# Test that __BAZEL_XCODE_DEVELOPER_DIR__ is remapped properly. +function test_developer_dir_remapping() { + env DEVELOPER_DIR=mydir SDKROOT=a \ + "${WRAPPED_CLANG}" "developer_dir=__BAZEL_XCODE_DEVELOPER_DIR__" \ + >$TEST_log || fail "wrapped_clang failed"; + expect_log "developer_dir=mydir" "Expected developer dir to be remapped." +} + +# Test that __BAZEL_XCODE_SDKROOT__ is remapped properly. +function test_sdkroot_remapping() { + env DEVELOPER_DIR=dummy SDKROOT=mysdkroot \ + "${WRAPPED_CLANG}" "sdkroot=__BAZEL_XCODE_SDKROOT__" \ + >$TEST_log || fail "wrapped_clang failed"; + expect_log "sdkroot=mysdkroot" "Expected sdkroot to be remapped." +} + +function test_params_expansion() { + params=$(mktemp) + { + echo "first" + echo "-rpath" + echo "@loader_path" + echo "sdkroot=__BAZEL_XCODE_SDKROOT__" + echo "developer_dir=__BAZEL_XCODE_DEVELOPER_DIR__" + } > "$params" + + env DEVELOPER_DIR=dummy SDKROOT=mysdkroot \ + "${WRAPPED_CLANG}" "@$params" \ + >"$TEST_log" || fail "wrapped_clang failed"; + expect_log "/usr/bin/xcrun clang first -rpath @loader_path sdkroot=mysdkroot developer_dir=dummy" +} + +run_suite "Wrapped clang tests"