From 04a6e152ff6bf055434eb9069c566e790cab8586 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Wed, 23 Mar 2022 16:14:26 -0700 Subject: [PATCH 01/40] Add example toolchains setup --- BUILD | 5 +++ BUILD.toolchains | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ WORKSPACE | 4 ++ main.c | 1 + tcsetup.bzl | 38 +++++++++++++++++++ 5 files changed, 144 insertions(+) create mode 100644 BUILD.toolchains create mode 100644 main.c create mode 100644 tcsetup.bzl diff --git a/BUILD b/BUILD index f7cf0949..c5ebb68b 100644 --- a/BUILD +++ b/BUILD @@ -33,3 +33,8 @@ filegroup( # a temporary workspace for the tests. visibility = ["//visibility:public"], ) + +cc_binary( + name = "main", + srcs = ["main.c"], +) diff --git a/BUILD.toolchains b/BUILD.toolchains new file mode 100644 index 00000000..114f36d2 --- /dev/null +++ b/BUILD.toolchains @@ -0,0 +1,96 @@ +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", + ], + "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", + ], + "tvos_arm64": [ + "@platforms//os:tvos", + "@platforms//cpu:arm64", + ], + "tvos_x86_64": [ + "@platforms//os:tvos", + "@platforms//cpu:x86_64", + ], + "tvos_sim_arm64": [ + "@platforms//os:tvos", + "@platforms//cpu:arm64", + ], + "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_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/WORKSPACE b/WORKSPACE index a35c3010..219f206c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -24,3 +24,7 @@ http_archive( "https://github.com/bazelbuild/stardoc/releases/download/0.5.0/stardoc-0.5.0.tar.gz", ], ) + +load("//:tcsetup.bzl", "apple_cc_configure") + +apple_cc_configure() diff --git a/main.c b/main.c new file mode 100644 index 00000000..c7367190 --- /dev/null +++ b/main.c @@ -0,0 +1 @@ +int main() { return 1;} diff --git a/tcsetup.bzl b/tcsetup.bzl new file mode 100644 index 00000000..951c9b05 --- /dev/null +++ b/tcsetup.bzl @@ -0,0 +1,38 @@ +"""TODO""" + +def _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 we try to find C++ toolchain at all? If not, we don't have to generate toolchains for C++ at all. + should_detect_cpp_toolchain = "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN" not in env or env["BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN"] != "1" + + if not should_detect_cpp_toolchain: + repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN env variable.") + elif repository_ctx.os.name.startswith("mac os"): + repository_ctx.symlink( + repository_ctx.path(Label("//:BUILD.toolchains")), + "BUILD", + ) + else: + repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled because you're not running on macOS") + +apple_cc_autoconf_toolchains = repository_rule( + environ = [ + "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN", + ], + implementation = _impl, + configure = True, +) + +# buildifier: disable=unnamed-macro +def apple_cc_configure(): + apple_cc_autoconf_toolchains(name = "local_config_apple_cc_toolchains") + native.register_toolchains( + # Use register_toolchain's target pattern expansion to register all toolchains in the package. + "@local_config_apple_cc_toolchains//:all", + ) From 92d9c690bf1270f4f1c178222f3c899b0a2cf3cf Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Mon, 31 Oct 2022 17:12:16 -0700 Subject: [PATCH 02/40] Add other var back --- tcsetup.bzl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tcsetup.bzl b/tcsetup.bzl index 951c9b05..f68c909a 100644 --- a/tcsetup.bzl +++ b/tcsetup.bzl @@ -11,8 +11,13 @@ def _impl(repository_ctx): # Should we try to find C++ toolchain at all? If not, we don't have to generate toolchains for C++ at all. should_detect_cpp_toolchain = "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN" not in env or env["BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN"] != "1" + # Should we unconditionally *not* use xcode? If so, we don't have to run Xcode locator ever. + should_use_cpp_only_toolchain = "BAZEL_USE_CPP_ONLY_TOOLCHAIN" in env and env["BAZEL_USE_CPP_ONLY_TOOLCHAIN"] == "1" + if not should_detect_cpp_toolchain: repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN env variable.") + elif should_use_cpp_only_toolchain: + repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_USE_CPP_ONLY_TOOLCHAIN env variable.") elif repository_ctx.os.name.startswith("mac os"): repository_ctx.symlink( repository_ctx.path(Label("//:BUILD.toolchains")), @@ -24,6 +29,7 @@ def _impl(repository_ctx): apple_cc_autoconf_toolchains = repository_rule( environ = [ "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN", + "BAZEL_USE_CPP_ONLY_TOOLCHAIN", ], implementation = _impl, configure = True, From 8e953adc03dc65e9ec26d6ee479730ff75944dac Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Tue, 1 Nov 2022 15:49:24 -0700 Subject: [PATCH 03/40] Initial mv of crosstool setup --- crosstool/BUILD | 39 + crosstool/BUILD.tpl | 120 ++ crosstool/cc_toolchain_config.bzl | 2929 +++++++++++++++++++++++++++++ crosstool/libtool.sh | 141 ++ crosstool/libtool_check_unique.cc | 109 ++ crosstool/make_hashed_objlist.py | 58 + crosstool/osx_archs.bzl | 38 + crosstool/osx_cc_configure.bzl | 242 +++ crosstool/wrapped_clang.cc | 429 +++++ crosstool/wrapped_clang_test.sh | 99 + crosstool/xcrunwrapper.sh | 44 + 11 files changed, 4248 insertions(+) create mode 100644 crosstool/BUILD 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_archs.bzl create mode 100644 crosstool/osx_cc_configure.bzl create mode 100644 crosstool/wrapped_clang.cc create mode 100755 crosstool/wrapped_clang_test.sh create mode 100755 crosstool/xcrunwrapper.sh diff --git a/crosstool/BUILD b/crosstool/BUILD new file mode 100644 index 00000000..d3d8d068 --- /dev/null +++ b/crosstool/BUILD @@ -0,0 +1,39 @@ +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( + ["**"], + exclude = ["wrapped_clang_test.sh"], +)) + +filegroup( + name = "srcs", + srcs = glob(["**"]), +) + +filegroup( + name = "bzl_srcs", + srcs = glob(["*.bzl"]), + visibility = ["//tools/osx:__pkg__"], +) + +cc_binary( + name = "wrapped_clang", + testonly = True, + srcs = [ + "wrapped_clang.cc", + ], +) + +sh_test( + name = "wrapped_clang_test", + size = "small", + srcs = [ + "wrapped_clang_test.sh", + ], + data = [ + ":wrapped_clang", + "//src/test/shell:bashunit", + "@bazel_tools//tools/bash/runfiles", + ], +) diff --git a/crosstool/BUILD.tpl b/crosstool/BUILD.tpl new file mode 100644 index 00000000..420db4d2 --- /dev/null +++ b/crosstool/BUILD.tpl @@ -0,0 +1,120 @@ +package(default_visibility = ["//visibility:public"]) + +load("@bazel_tools//tools/osx/crosstool:osx_archs.bzl", "OSX_TOOLS_ARCHS") +load("@rules_cc//cc:defs.bzl", "cc_toolchain_suite", "cc_library") +load(":armeabi_cc_toolchain_config.bzl", "armeabi_cc_toolchain_config") +load(":cc_toolchain_config.bzl", "cc_toolchain_config") + +# Reexporting osx_arch.bzl for backwards compatibility +# Originally this file was present in @local_config_cc, but with the split in +# https://github.com/bazelbuild/bazel/pull/8459 we had to move the file to +# @local_config_cc_toolchains. This alias is there to keep the code backwards +# compatible (and serves no other purpose). +alias(name = "osx_archs.bzl", actual = "@bazel_tools//tools/osx/crosstool:osx_archs.bzl") + +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 00000000..1a21d5ea --- /dev/null +++ b/crosstool/cc_toolchain_config.bzl @@ -0,0 +1,2929 @@ +# 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_compile_actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.clif_match, + ACTION_NAMES.lto_backend, + ] + + all_cpp_compile_actions = [ + 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.clif_match, + ] + + preprocessor_compile_actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.cpp_header_parsing, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.clif_match, + ] + + codegen_compile_actions = [ + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.linkstamp_compile, + ACTION_NAMES.assemble, + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.cpp_module_codegen, + ACTION_NAMES.lto_backend, + ] + + 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, + 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") + + supports_pic_feature = feature(name = "supports_pic", enabled = True) + + 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"])], + ) + + if (ctx.attr.cpu == "darwin_x86_64" or + ctx.attr.cpu == "darwin_arm64" or + ctx.attr.cpu == "darwin_arm64e"): + 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, + ], + ), + ], + ), + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ], + flag_groups = [flag_group(flags = ["-undefined", "dynamic_lookup"])], + ), + flag_set( + actions = [ + ACTION_NAMES.cpp_link_executable, + "objc-executable", + "objc++-executable", + ], + flag_groups = [flag_group(flags = ["-undefined", "dynamic_lookup"])], + with_features = [with_feature_set(features = ["dynamic_linking_mode"])], + ), + ], + ) + else: + 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, + ACTION_NAMES.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 00000000..c8bcd183 --- /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 00000000..340e9a3c --- /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 00000000..bb326423 --- /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_archs.bzl b/crosstool/osx_archs.bzl new file mode 100644 index 00000000..aeb82ac6 --- /dev/null +++ b/crosstool/osx_archs.bzl @@ -0,0 +1,38 @@ +"""Information regarding crosstool-supported architectures.""" +# 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. + +# List of architectures supported by osx crosstool. +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 diff --git a/crosstool/osx_cc_configure.bzl b/crosstool/osx_cc_configure.bzl new file mode 100644 index 00000000..74cc5dc5 --- /dev/null +++ b/crosstool/osx_cc_configure.bzl @@ -0,0 +1,242 @@ +# 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", + "configure_unix_toolchain", + "get_env", +) + +def _get_escaped_xcode_cxx_inc_directories(repository_ctx, cc, xcode_toolchains): + """Compute the list of default C++ include paths on Xcode-enabled darwin. + + Args: + repository_ctx: The repository context. + cc: The default C++ compiler on the local system. + 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, cpu_value, overriden_tools): + """Configure C++ toolchain on macOS. + + Args: + repository_ctx: The repository context. + overriden_tools: dictionary of overridden tools. + """ + paths = resolve_labels(repository_ctx, [ + "@bazel_tools//tools/cpp:armeabi_cc_toolchain_config.bzl", + "@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl", + "@bazel_tools//tools/objc:libtool.sh", + "@bazel_tools//tools/objc:libtool_check_unique.cc", + "@bazel_tools//tools/objc:make_hashed_objlist.py", + "@bazel_tools//tools/objc:xcrunwrapper.sh", + "@bazel_tools//tools/osx/crosstool:BUILD.tpl", + "@bazel_tools//tools/osx/crosstool:cc_toolchain_config.bzl", + "@bazel_tools//tools/osx/crosstool:wrapped_clang.cc", + "@bazel_tools//tools/osx:xcode_locator.m", + ]) + + env = repository_ctx.os.environ + should_use_xcode = "BAZEL_USE_XCODE_TOOLCHAIN" in env and env["BAZEL_USE_XCODE_TOOLCHAIN"] == "1" + xcode_toolchains = [] + + # Make the following logic in sync with //tools/cpp:cc_configure.bzl#cc_autoconf_toolchains_impl + (xcode_toolchains, xcodeloc_err) = run_xcode_locator( + repository_ctx, + paths["@bazel_tools//tools/osx:xcode_locator.m"], + ) + if should_use_xcode and not xcode_toolchains: + fail("BAZEL_USE_XCODE_TOOLCHAIN is set to 1 but Bazel couldn't find Xcode installed on the " + + "system. Verify that 'xcode-select -p' is correct.") + if xcode_toolchains: + # 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 = repository_ctx.path("wrapped_clang") + + 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["@bazel_tools//tools/objc:xcrunwrapper.sh"], + "xcrunwrapper.sh", + ) + repository_ctx.symlink( + paths["@bazel_tools//tools/objc:libtool.sh"], + "libtool", + ) + repository_ctx.symlink( + paths["@bazel_tools//tools/objc:make_hashed_objlist.py"], + "make_hashed_objlist.py", + ) + repository_ctx.symlink( + paths["@bazel_tools//tools/osx/crosstool:cc_toolchain_config.bzl"], + "cc_toolchain_config.bzl", + ) + libtool_check_unique_src_path = str(repository_ctx.path( + paths["@bazel_tools//tools/objc: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["@bazel_tools//tools/osx/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, cc, 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["@bazel_tools//tools/osx/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()], + ), + }, + ) + else: + configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools = overriden_tools) diff --git a/crosstool/wrapped_clang.cc b/crosstool/wrapped_clang.cc new file mode 100644 index 00000000..044c0c8f --- /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/wrapped_clang_test.sh b/crosstool/wrapped_clang_test.sh new file mode 100755 index 00000000..484b2dc2 --- /dev/null +++ b/crosstool/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 "io_bazel/src/test/shell/unittest.bash")" \ + || { echo "unittest.bash not found!" >&2; exit 1; } +WRAPPED_CLANG=$(rlocation "io_bazel/tools/osx/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" diff --git a/crosstool/xcrunwrapper.sh b/crosstool/xcrunwrapper.sh new file mode 100755 index 00000000..143ff23d --- /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[@]}" From a5bb6c370841ca0a1673ca799608a6d733a23523 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Tue, 1 Nov 2022 16:10:38 -0700 Subject: [PATCH 04/40] First pass setup --- tcsetup.bzl | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/tcsetup.bzl b/tcsetup.bzl index f68c909a..dc0ddc1d 100644 --- a/tcsetup.bzl +++ b/tcsetup.bzl @@ -1,5 +1,7 @@ """TODO""" +load("//crosstool:osx_cc_configure.bzl", "configure_osx_toolchain") + def _impl(repository_ctx): """Generate BUILD file with 'toolchain' targets for the local host C++ toolchain. @@ -26,7 +28,7 @@ def _impl(repository_ctx): else: repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled because you're not running on macOS") -apple_cc_autoconf_toolchains = repository_rule( +_apple_cc_autoconf_toolchains = repository_rule( environ = [ "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN", "BAZEL_USE_CPP_ONLY_TOOLCHAIN", @@ -35,9 +37,65 @@ apple_cc_autoconf_toolchains = repository_rule( configure = True, ) +def _apple_cc_autoconf_impl(repository_ctx): + env = repository_ctx.os.environ + + # Should we try to find C++ toolchain at all? If not, we don't have to generate toolchains for C++ at all. + should_detect_cpp_toolchain = "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN" not in env or env["BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN"] != "1" + + # Should we unconditionally *not* use xcode? If so, we don't have to run Xcode locator ever. + should_use_cpp_only_toolchain = "BAZEL_USE_CPP_ONLY_TOOLCHAIN" in env and env["BAZEL_USE_CPP_ONLY_TOOLCHAIN"] == "1" + + if not should_detect_cpp_toolchain: + repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN env variable.") + elif should_use_cpp_only_toolchain: + repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_USE_CPP_ONLY_TOOLCHAIN env variable.") + elif repository_ctx.os.name.startswith("mac os"): + print("here?") + configure_osx_toolchain(repository_ctx) + + # TODO: call into upstream + +_apple_cc_autoconf = repository_rule( + # TODO: Remove unnecessary env vars + environ = [ + "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", + ], + 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_toolchains(name = "local_config_apple_cc_toolchains") + _apple_cc_autoconf(name = "local_config_cc") native.register_toolchains( # Use register_toolchain's target pattern expansion to register all toolchains in the package. "@local_config_apple_cc_toolchains//:all", From 6bb507ed0fee4cfb02314dacc27f1ca5ac22b8dd Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Tue, 1 Nov 2022 16:12:42 -0700 Subject: [PATCH 05/40] Remove separate osx_archs.bzl --- crosstool/BUILD.tpl | 31 +++++++++++++++++++++++-------- crosstool/osx_archs.bzl | 38 -------------------------------------- 2 files changed, 23 insertions(+), 46 deletions(-) delete mode 100644 crosstool/osx_archs.bzl diff --git a/crosstool/BUILD.tpl b/crosstool/BUILD.tpl index 420db4d2..4f04e35c 100644 --- a/crosstool/BUILD.tpl +++ b/crosstool/BUILD.tpl @@ -1,17 +1,32 @@ package(default_visibility = ["//visibility:public"]) -load("@bazel_tools//tools/osx/crosstool:osx_archs.bzl", "OSX_TOOLS_ARCHS") +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("@rules_cc//cc:defs.bzl", "cc_toolchain_suite", "cc_library") load(":armeabi_cc_toolchain_config.bzl", "armeabi_cc_toolchain_config") load(":cc_toolchain_config.bzl", "cc_toolchain_config") -# Reexporting osx_arch.bzl for backwards compatibility -# Originally this file was present in @local_config_cc, but with the split in -# https://github.com/bazelbuild/bazel/pull/8459 we had to move the file to -# @local_config_cc_toolchains. This alias is there to keep the code backwards -# compatible (and serves no other purpose). -alias(name = "osx_archs.bzl", actual = "@bazel_tools//tools/osx/crosstool:osx_archs.bzl") - CC_TOOLCHAINS = [( cpu + "|clang", ":cc-compiler-" + cpu, diff --git a/crosstool/osx_archs.bzl b/crosstool/osx_archs.bzl deleted file mode 100644 index aeb82ac6..00000000 --- a/crosstool/osx_archs.bzl +++ /dev/null @@ -1,38 +0,0 @@ -"""Information regarding crosstool-supported architectures.""" -# 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. - -# List of architectures supported by osx crosstool. -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 From 029317d863d71659aa8ea42c2e6206dd1f23c73b Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Tue, 1 Nov 2022 16:38:50 -0700 Subject: [PATCH 06/40] Fixup for moved files, fallback to builtin version --- crosstool/osx_cc_configure.bzl | 35 +++++++++++++++++----------------- tcsetup.bzl | 26 ++++++++++++++++++++----- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/crosstool/osx_cc_configure.bzl b/crosstool/osx_cc_configure.bzl index 74cc5dc5..f40a04e2 100644 --- a/crosstool/osx_cc_configure.bzl +++ b/crosstool/osx_cc_configure.bzl @@ -22,7 +22,6 @@ load( ) load( "@bazel_tools//tools/cpp:unix_cc_configure.bzl", - "configure_unix_toolchain", "get_env", ) @@ -135,23 +134,22 @@ def _compile_cc_file(repository_ctx, src_name, out_name): else: _compile_cc_file_single_arch(repository_ctx, src_name, out_name) -def configure_osx_toolchain(repository_ctx, cpu_value, overriden_tools): +def configure_osx_toolchain(repository_ctx): """Configure C++ toolchain on macOS. Args: repository_ctx: The repository context. - overriden_tools: dictionary of overridden tools. """ paths = resolve_labels(repository_ctx, [ "@bazel_tools//tools/cpp:armeabi_cc_toolchain_config.bzl", "@bazel_tools//tools/cpp:osx_cc_wrapper.sh.tpl", - "@bazel_tools//tools/objc:libtool.sh", - "@bazel_tools//tools/objc:libtool_check_unique.cc", - "@bazel_tools//tools/objc:make_hashed_objlist.py", - "@bazel_tools//tools/objc:xcrunwrapper.sh", - "@bazel_tools//tools/osx/crosstool:BUILD.tpl", - "@bazel_tools//tools/osx/crosstool:cc_toolchain_config.bzl", - "@bazel_tools//tools/osx/crosstool:wrapped_clang.cc", + "@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", ]) @@ -190,27 +188,27 @@ def configure_osx_toolchain(repository_ctx, cpu_value, overriden_tools): "armeabi_cc_toolchain_config.bzl", ) repository_ctx.symlink( - paths["@bazel_tools//tools/objc:xcrunwrapper.sh"], + paths["@build_bazel_apple_support//crosstool:xcrunwrapper.sh"], "xcrunwrapper.sh", ) repository_ctx.symlink( - paths["@bazel_tools//tools/objc:libtool.sh"], + paths["@build_bazel_apple_support//crosstool:libtool.sh"], "libtool", ) repository_ctx.symlink( - paths["@bazel_tools//tools/objc:make_hashed_objlist.py"], + paths["@build_bazel_apple_support//crosstool:make_hashed_objlist.py"], "make_hashed_objlist.py", ) repository_ctx.symlink( - paths["@bazel_tools//tools/osx/crosstool:cc_toolchain_config.bzl"], + paths["@build_bazel_apple_support//crosstool:cc_toolchain_config.bzl"], "cc_toolchain_config.bzl", ) libtool_check_unique_src_path = str(repository_ctx.path( - paths["@bazel_tools//tools/objc:libtool_check_unique.cc"], + 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["@bazel_tools//tools/osx/crosstool:wrapped_clang.cc"], + 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") @@ -230,7 +228,7 @@ def configure_osx_toolchain(repository_ctx, cpu_value, overriden_tools): escaped_cxx_include_directories.append(" # Error: " + xcodeloc_err) repository_ctx.template( "BUILD", - paths["@bazel_tools//tools/osx/crosstool:BUILD.tpl"], + paths["@build_bazel_apple_support//crosstool:BUILD.tpl"], { "%{cxx_builtin_include_directories}": "\n".join(escaped_cxx_include_directories), "%{tool_paths_overrides}": ",\n ".join( @@ -238,5 +236,6 @@ def configure_osx_toolchain(repository_ctx, cpu_value, overriden_tools): ), }, ) + return True else: - configure_unix_toolchain(repository_ctx, cpu_value, overriden_tools = overriden_tools) + return False diff --git a/tcsetup.bzl b/tcsetup.bzl index dc0ddc1d..7a160d5f 100644 --- a/tcsetup.bzl +++ b/tcsetup.bzl @@ -1,5 +1,6 @@ """TODO""" +load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_autoconf_impl") load("//crosstool:osx_cc_configure.bzl", "configure_osx_toolchain") def _impl(repository_ctx): @@ -51,13 +52,28 @@ def _apple_cc_autoconf_impl(repository_ctx): elif should_use_cpp_only_toolchain: repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_USE_CPP_ONLY_TOOLCHAIN env variable.") elif repository_ctx.os.name.startswith("mac os"): - print("here?") - configure_osx_toolchain(repository_ctx) + if not configure_osx_toolchain(repository_ctx): + cc_autoconf_impl(repository_ctx, {}) + else: + cc_autoconf_impl(repository_ctx, {}) - # TODO: call into upstream +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( - # TODO: Remove unnecessary env vars environ = [ "ABI_LIBC_VERSION", "ABI_VERSION", @@ -87,7 +103,7 @@ _apple_cc_autoconf = repository_rule( "HOMEBREW_RUBY_PATH", "SYSTEMROOT", "USER", - ], + ] + MSVC_ENVVARS, implementation = _apple_cc_autoconf_impl, configure = True, ) From a0afb2d38957ce07854dcc6d4fb21086db905cf9 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Tue, 1 Nov 2022 16:49:55 -0700 Subject: [PATCH 07/40] Make crosstool tests pass We can likely simplify this later --- crosstool/BUILD | 25 - test/BUILD | 14 + test/unittest.bash | 846 ++++++++++++++++++++++ test/unittest_utils.sh | 181 +++++ {crosstool => test}/wrapped_clang_test.sh | 4 +- 5 files changed, 1043 insertions(+), 27 deletions(-) create mode 100644 test/unittest.bash create mode 100755 test/unittest_utils.sh rename {crosstool => test}/wrapped_clang_test.sh (96%) diff --git a/crosstool/BUILD b/crosstool/BUILD index d3d8d068..66a6c03f 100644 --- a/crosstool/BUILD +++ b/crosstool/BUILD @@ -3,20 +3,8 @@ 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( ["**"], - exclude = ["wrapped_clang_test.sh"], )) -filegroup( - name = "srcs", - srcs = glob(["**"]), -) - -filegroup( - name = "bzl_srcs", - srcs = glob(["*.bzl"]), - visibility = ["//tools/osx:__pkg__"], -) - cc_binary( name = "wrapped_clang", testonly = True, @@ -24,16 +12,3 @@ cc_binary( "wrapped_clang.cc", ], ) - -sh_test( - name = "wrapped_clang_test", - size = "small", - srcs = [ - "wrapped_clang_test.sh", - ], - data = [ - ":wrapped_clang", - "//src/test/shell:bashunit", - "@bazel_tools//tools/bash/runfiles", - ], -) diff --git a/test/BUILD b/test/BUILD index ec444900..67c692dc 100644 --- a/test/BUILD +++ b/test/BUILD @@ -66,3 +66,17 @@ universal_binary_test( cpu = "darwin_arm64", target_under_test = "//test/test_data:multi_arch_cc_binary", ) + +sh_test( + name = "wrapped_clang_test", + size = "small", + srcs = [ + "wrapped_clang_test.sh", + ], + data = [ + "unittest.bash", + "unittest_utils.sh", + "//crosstool:wrapped_clang", + "@bazel_tools//tools/bash/runfiles", + ], +) diff --git a/test/unittest.bash b/test/unittest.bash new file mode 100644 index 00000000..b54fa631 --- /dev/null +++ b/test/unittest.bash @@ -0,0 +1,846 @@ +#!/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. +# +# Common utility file for Bazel shell tests +# +# unittest.bash: a unit test framework in Bash. +# +# A typical test suite looks like so: +# +# ------------------------------------------------------------------------ +# #!/bin/bash +# +# source path/to/unittest.bash || exit 1 +# +# # Test that foo works. +# function test_foo() { +# foo >$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/unittest_utils.sh b/test/unittest_utils.sh new file mode 100755 index 00000000..be3409e2 --- /dev/null +++ b/test/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/crosstool/wrapped_clang_test.sh b/test/wrapped_clang_test.sh similarity index 96% rename from crosstool/wrapped_clang_test.sh rename to test/wrapped_clang_test.sh index 484b2dc2..2d38059f 100755 --- a/crosstool/wrapped_clang_test.sh +++ b/test/wrapped_clang_test.sh @@ -42,9 +42,9 @@ fi # Load test environment -source "$(rlocation "io_bazel/src/test/shell/unittest.bash")" \ +source "$(rlocation "build_bazel_apple_support/test/unittest.bash")" \ || { echo "unittest.bash not found!" >&2; exit 1; } -WRAPPED_CLANG=$(rlocation "io_bazel/tools/osx/crosstool/wrapped_clang") +WRAPPED_CLANG=$(rlocation "build_bazel_apple_support/crosstool/wrapped_clang") # This env var tells wrapped_clang to log its command instead of running. From c395f9e38d7cf98b8ffe72518e0a9b0d9c6c4039 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Tue, 1 Nov 2022 16:53:15 -0700 Subject: [PATCH 08/40] Fix buildifier --- crosstool/cc_toolchain_config.bzl | 50 +++---------------------------- crosstool/osx_cc_configure.bzl | 10 +++---- 2 files changed, 9 insertions(+), 51 deletions(-) diff --git a/crosstool/cc_toolchain_config.bzl b/crosstool/cc_toolchain_config.bzl index 1a21d5ea..3843929e 100644 --- a/crosstool/cc_toolchain_config.bzl +++ b/crosstool/cc_toolchain_config.bzl @@ -39,8 +39,7 @@ def _compare_versions(dv1, 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.""" + """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: @@ -49,8 +48,7 @@ def _can_use_deterministic_libtool(ctx): return False def _deterministic_libtool_flags(ctx): - """Returns additional `libtool` flags to enable deterministic mode, if they - are available.""" + """Returns additional `libtool` flags to enable deterministic mode, if they are available.""" if _can_use_deterministic_libtool(ctx): return ["-D"] return [] @@ -117,48 +115,6 @@ def _impl(ctx): if ctx.attr.cpu in ["ios_sim_arm64", "tvos_sim_arm64", "watchos_arm64"]: arch = "arm64" - all_compile_actions = [ - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.clif_match, - ACTION_NAMES.lto_backend, - ] - - all_cpp_compile_actions = [ - 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.clif_match, - ] - - preprocessor_compile_actions = [ - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.clif_match, - ] - - codegen_compile_actions = [ - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ] - all_link_actions = [ ACTION_NAMES.cpp_link_executable, ACTION_NAMES.cpp_link_dynamic_library, @@ -2765,6 +2721,7 @@ def _impl(ctx): ubsan_feature, default_sanitizer_flags_feature, archive_param_file_feature, + supports_pic_feature, ] elif (ctx.attr.cpu == "darwin_x86_64" or ctx.attr.cpu == "darwin_arm64" or @@ -2847,6 +2804,7 @@ def _impl(ctx): ubsan_feature, default_sanitizer_flags_feature, archive_param_file_feature, + supports_pic_feature, ] else: fail("Unreachable") diff --git a/crosstool/osx_cc_configure.bzl b/crosstool/osx_cc_configure.bzl index f40a04e2..67dc120e 100644 --- a/crosstool/osx_cc_configure.bzl +++ b/crosstool/osx_cc_configure.bzl @@ -25,12 +25,11 @@ load( "get_env", ) -def _get_escaped_xcode_cxx_inc_directories(repository_ctx, cc, xcode_toolchains): +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. - cc: The default C++ compiler on the local system. xcode_toolchains: A list containing the xcode toolchains available Returns: include_paths: A list of builtin include paths. @@ -139,6 +138,9 @@ def configure_osx_toolchain(repository_ctx): 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", @@ -172,8 +174,6 @@ def configure_osx_toolchain(repository_ctx): # 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 = repository_ctx.path("wrapped_clang") - cc_path = '"$(/usr/bin/dirname "$0")"/wrapped_clang' repository_ctx.template( "cc_wrapper.sh", @@ -220,7 +220,7 @@ def configure_osx_toolchain(repository_ctx): gcov_path = repository_ctx.which(gcov_path) tool_paths["gcov"] = gcov_path - escaped_include_paths = _get_escaped_xcode_cxx_inc_directories(repository_ctx, cc, xcode_toolchains) + 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)) From 245e0520a5b24c4904e9113bf74b1468ba8691c1 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Fri, 6 Jan 2023 13:49:09 -0800 Subject: [PATCH 09/40] sync with upstream --- crosstool/cc_toolchain_config.bzl | 41 +++---------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/crosstool/cc_toolchain_config.bzl b/crosstool/cc_toolchain_config.bzl index 3843929e..8edf776e 100644 --- a/crosstool/cc_toolchain_config.bzl +++ b/crosstool/cc_toolchain_config.bzl @@ -39,7 +39,8 @@ def _compare_versions(dv1, 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.""" + """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: @@ -48,7 +49,8 @@ def _can_use_deterministic_libtool(ctx): return False def _deterministic_libtool_flags(ctx): - """Returns additional `libtool` flags to enable deterministic mode, if they are available.""" + """Returns additional `libtool` flags to enable deterministic mode, if they + are available.""" if _can_use_deterministic_libtool(ctx): return ["-D"] return [] @@ -421,37 +423,6 @@ def _impl(ctx): ], ) - 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 = [ @@ -659,7 +630,6 @@ def _impl(ctx): objcpp_compile_action, assemble_action, preprocess_assemble_action, - objc_archive_action, objc_executable_action, objcpp_executable_action, cpp_link_executable_action, @@ -1486,7 +1456,6 @@ def _impl(ctx): 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, @@ -1700,7 +1669,6 @@ def _impl(ctx): "objc-compile", "objc++-compile", "objc-fully-link", - "objc-archive", "objc-executable", "objc++-executable", "assemble", @@ -1753,7 +1721,6 @@ def _impl(ctx): flag_set( actions = all_link_actions + [ ACTION_NAMES.cpp_link_static_library, - ACTION_NAMES.objc_archive, ACTION_NAMES.objc_fully_link, ACTION_NAMES.objc_executable, ACTION_NAMES.objcpp_executable, From 28f9f2e5c1db31aa70c6fdf6141146009eacefb8 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Mon, 9 Jan 2023 13:53:18 -0800 Subject: [PATCH 10/40] Use @local_config_apple_cc for repo --- BUILD.toolchains | 2 +- tcsetup.bzl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.toolchains b/BUILD.toolchains index 114f36d2..60be4ac1 100644 --- a/BUILD.toolchains +++ b/BUILD.toolchains @@ -88,7 +88,7 @@ OSX_DEVELOPER_PLATFORM_CPUS = [ "@platforms//cpu:" + cpu, ], target_compatible_with = OSX_TOOLS_CONSTRAINTS[arch], - toolchain = "@local_config_cc//:cc-compiler-" + arch, + toolchain = "@local_config_apple_cc//:cc-compiler-" + arch, toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", ) for arch in OSX_TOOLS_CONSTRAINTS.keys() diff --git a/tcsetup.bzl b/tcsetup.bzl index 7a160d5f..e53cd9e0 100644 --- a/tcsetup.bzl +++ b/tcsetup.bzl @@ -111,7 +111,7 @@ _apple_cc_autoconf = repository_rule( # buildifier: disable=unnamed-macro def apple_cc_configure(): _apple_cc_autoconf_toolchains(name = "local_config_apple_cc_toolchains") - _apple_cc_autoconf(name = "local_config_cc") + _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", From a982d7b17cf1ad4e160b4dc755f4c7562cd3a318 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Mon, 9 Jan 2023 13:53:31 -0800 Subject: [PATCH 11/40] buildifier --- crosstool/cc_toolchain_config.bzl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crosstool/cc_toolchain_config.bzl b/crosstool/cc_toolchain_config.bzl index 8edf776e..366594ad 100644 --- a/crosstool/cc_toolchain_config.bzl +++ b/crosstool/cc_toolchain_config.bzl @@ -39,8 +39,7 @@ def _compare_versions(dv1, 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.""" + """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: @@ -49,8 +48,7 @@ def _can_use_deterministic_libtool(ctx): return False def _deterministic_libtool_flags(ctx): - """Returns additional `libtool` flags to enable deterministic mode, if they - are available.""" + """Returns additional `libtool` flags to enable deterministic mode, if they are available.""" if _can_use_deterministic_libtool(ctx): return ["-D"] return [] From 5c2112dcff6ed1ba4f58b872fd2a2f28e2e82ebe Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Mon, 9 Jan 2023 13:54:27 -0800 Subject: [PATCH 12/40] Add some initial device vs sim constraints --- BUILD.toolchains | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BUILD.toolchains b/BUILD.toolchains index 60be4ac1..4928a5dc 100644 --- a/BUILD.toolchains +++ b/BUILD.toolchains @@ -19,6 +19,7 @@ OSX_TOOLS_CONSTRAINTS = { "ios_arm64": [ "@platforms//os:ios", "@platforms//cpu:arm64", + "@build_bazel_apple_support//constraints:device", ], "ios_arm64e": [ "@platforms//os:ios", @@ -39,10 +40,12 @@ OSX_TOOLS_CONSTRAINTS = { "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", @@ -51,6 +54,7 @@ OSX_TOOLS_CONSTRAINTS = { "tvos_sim_arm64": [ "@platforms//os:tvos", "@platforms//cpu:arm64", + "@build_bazel_apple_support//constraints:simulator", ], "watchos_arm64": [ "@platforms//os:watchos", From 7e7eb22bef76482d81b7dbe3ef89f608111cd606 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Mon, 9 Jan 2023 14:42:36 -0800 Subject: [PATCH 13/40] Add initial tests --- test/shell/BUILD | 35 ++ test/shell/apple_common.sh | 203 +++++++ test/shell/apple_test.sh | 236 ++++++++ test/shell/integration_test_setup.sh | 55 ++ test/shell/objc_test.sh | 201 +++++++ test/shell/testenv.sh | 8 + test/shell/unittest.bash | 846 +++++++++++++++++++++++++++ test/shell/unittest_utils.sh | 181 ++++++ 8 files changed, 1765 insertions(+) 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 diff --git a/test/shell/BUILD b/test/shell/BUILD new file mode 100644 index 00000000..ff9d1760 --- /dev/null +++ b/test/shell/BUILD @@ -0,0 +1,35 @@ +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"], + deps = [ + ":apple_common", + ":bashunit", + ], +) + +sh_test( + name = "apple_test", + srcs = ["apple_test.sh"], + data = [ + ":apple_common", + ":bashunit", + ], +) diff --git a/test/shell/apple_common.sh b/test/shell/apple_common.sh new file mode 100644 index 00000000..0207023a --- /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" <<EOF +def _starlark_apple_binary_impl(ctx): + link_result = apple_common.link_multi_arch_binary( + ctx = ctx, + stamp = ctx.attr.stamp, + ) + processed_binary = ctx.actions.declare_file( + '{}_lipobin'.format(ctx.label.name) + ) + lipo_inputs = [output.binary for output in link_result.outputs] + if len(lipo_inputs) > 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" <<EOF +def _starlark_apple_static_library_impl(ctx): + if not hasattr(apple_common.platform_type, ctx.attr.platform_type): + fail('Unsupported platform type \"{}\"'.format(ctx.attr.platform_type)) + link_result = apple_common.link_multi_arch_static_library(ctx = ctx) + processed_library = ctx.actions.declare_file( + '{}_lipo.a'.format(ctx.label.name) + ) + files_to_build = [processed_library] + runfiles = ctx.runfiles( + files = files_to_build, + collect_default = True, + collect_data = True, + ) + lipo_inputs = [output.library for output in link_result.outputs] + if len(lipo_inputs) > 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 00000000..eef710e6 --- /dev/null +++ b/test/shell/apple_test.sh @@ -0,0 +1,236 @@ +#!/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 <<EOF +load("//package:starlark_apple_binary.bzl", "starlark_apple_binary") +genrule( + name = "lipo_run", + srcs = [":main_binary_lipobin"], + outs = ["lipo_out"], + cmd = + "set -e && " + + "lipo -info \$(location :main_binary_lipobin) > \$(@)", + 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 <<EOF +#import <WatchKit/WatchKit.h> + +// Note that WKExtensionDelegate is only available in Watch SDK. +@interface TestInterfaceMain : NSObject <WKExtensionDelegate> +@end + +int main() { + return 0; +} +EOF + cat > package/a.m <<EOF +#import <WatchKit/WatchKit.h> + +// Note that WKExtensionDelegate is only available in Watch SDK. +@interface TestInterfaceA : NSObject <WKExtensionDelegate> +@end + +int aFunction() { + return 0; +} +EOF + cat > package/cc_lib.cc << EOF +#include <string> + +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 <<EOF +load( + "//package:starlark_apple_static_library.bzl", + "starlark_apple_static_library", +) +starlark_apple_static_library( + name = "static_lib", + deps = [":dummy_lib"], + platform_type = "ios", + minimum_os_version = "10.0", +) +objc_library( + name = "dummy_lib", + srcs = ["dummy.m"], +) +EOF + cat > "package/dummy.m" <<EOF +static int dummy __attribute__((unused,used)) = 0; +EOF + + bazel build --verbose_failures //package:static_lib \ + --noincompatible_enable_cc_toolchain_resolution \ + --ios_multi_cpus=i386,x86_64 \ + --ios_minimum_os=8.0 \ + --xcode_version="$XCODE_VERSION" \ + || fail "should build starlark_apple_static_library" +} + +function test_apple_binary_dsym_builds() { + rm -rf package + mkdir -p package + make_starlark_apple_binary_rule_in package + + cat > package/BUILD <<EOF +load("//package:starlark_apple_binary.bzl", "starlark_apple_binary") +starlark_apple_binary( + name = "main_binary", + deps = [":main_lib"], + platform_type = "ios", + minimum_os_version = "10.0", +) +objc_library( + name = "main_lib", + srcs = ["main.m"], +) +EOF + cat > package/main.m <<EOF +int main() { + return 0; +} +EOF + + bazel build --verbose_failures //package:main_binary \ + --noincompatible_enable_cc_toolchain_resolution \ + --ios_multi_cpus=i386,x86_64 \ + --xcode_version="$XCODE_VERSION" \ + --apple_generate_dsym=true \ + || fail "should build starlark_apple_binary with dSYMs" +} + +function test_additive_cpus_flag() { + rm -rf package + mkdir -p package + make_starlark_apple_binary_rule_in package + + cat > package/BUILD <<EOF +load("//package:starlark_apple_binary.bzl", "starlark_apple_binary") +objc_library( + name = "lib_a", + srcs = ["a.m"], +) +objc_library( + name = "lib_b", + srcs = ["b.m"], +) +starlark_apple_binary( + name = "main_binary", + deps = [":lib_a", ":lib_b"], + platform_type = "ios", + minimum_os_version = "10.0", +) +genrule( + name = "lipo_run", + srcs = [":main_binary_lipobin"], + outs = ["lipo_out"], + cmd = + "set -e && " + + "lipo -info \$(location :main_binary_lipobin) > \$(@)", + tags = ["requires-darwin"], +) +EOF + touch package/a.m + cat > package/b.m <<EOF +int main() { + return 0; +} +EOF + + bazel build --verbose_failures --xcode_version="$XCODE_VERSION" \ + //package:lipo_out \ + --noincompatible_enable_cc_toolchain_resolution \ + --ios_multi_cpus=i386 --ios_multi_cpus=x86_64 \ + || 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 contain 2 architectures" +} + + +run_suite "apple_tests" diff --git a/test/shell/integration_test_setup.sh b/test/shell/integration_test_setup.sh new file mode 100755 index 00000000..020932c6 --- /dev/null +++ b/test/shell/integration_test_setup.sh @@ -0,0 +1,55 @@ +#!/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. +# + +function print_message_and_exit() { + echo $1 >&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 00000000..da4bd49b --- /dev/null +++ b/test/shell/objc_test.sh @@ -0,0 +1,201 @@ +#!/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 <<EOF +#import <UIKit/UIKit.h> + +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 <<EOF +objc_library(name = "lib", + non_arc_srcs = ['main.m']) +EOF +} + +function test_build_app() { + setup_objc_test_support + make_lib + + bazel build --verbose_failures --apple_platform_type=ios \ + --noincompatible_enable_cc_toolchain_resolution \ + --ios_sdk_version="$IOS_SDK_VERSION" \ + //ios:lib >"$TEST_log" 2>&1 || fail "should pass" + ls bazel-out/*/bin/ios/liblib.a \ + || fail "should generate lib.a" +} + +function test_invalid_ios_sdk_version() { + setup_objc_test_support + make_lib + + ! bazel build --verbose_failures --apple_platform_type=ios \ + --ios_sdk_version=2.34 \ + //ios:lib >"$TEST_log" 2>&1 || fail "should fail" + expect_log "SDK \"iphonesimulator2.34\" cannot be located." +} + +function test_xcodelocator_embedded_tool() { + rm -rf ios + mkdir -p ios + + cat >ios/BUILD <<EOF +genrule( + name = "invoke_tool", + srcs = ["@bazel_tools//tools/osx:xcode-locator"], + outs = ["tool_output"], + cmd = "\$< > \$@", + tags = ["requires-darwin"], +) +EOF + + bazel build --verbose_failures //ios:invoke_tool >"$TEST_log" 2>&1 \ + || fail "should be able to resolve xcode-locator" +} + +# 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 <<EOF +objc_library( + name = "objclib", + srcs = ["mysrc.m"], +) +EOF + + cat > objclib/mysrc.m <<EOF +int aFunction() { + return 0; +} +EOF + + bazel build --verbose_failures --apple_platform_type=ios \ + --ios_sdk_version="$IOS_SDK_VERSION" //objclib:objclib >"$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 <<EOF +#import <UIKit/UIKit.h> +/* 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 <<EOF +load("//ios:starlark_apple_binary.bzl", "starlark_apple_binary") +starlark_apple_binary(name = 'app', + deps = [':main'], + platform_type = 'ios') +objc_library(name = 'main', + non_arc_srcs = ['main.m']) +EOF + + bazel build --verbose_failures \ + --noincompatible_enable_cc_toolchain_resolution \ + --apple_platform_type=ios \ + --ios_sdk_version="$IOS_SDK_VERSION" \ + --objc_enable_binary_stripping=true \ + --compilation_mode=opt \ + //ios:app >"$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 <<EOF +#include <iostream> +int main(int argc, char** argv) { + std::cout << "Hello! I'm a test!\n"; + return 0; +} +EOF + + cat >foo/BUILD <<EOF +cc_library( + name = "a", + srcs = ["a.cc"], +) + +objc_library( + name = "b", + deps = [ + ":a", + ], +) + +cc_test( + name = "d", + deps = [":b"], +) +EOF + + bazel test --verbose_failures \ + //foo:d>"$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 00000000..9763626d --- /dev/null +++ b/test/shell/testenv.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Sets up Objective-C tools. Mac only. +function setup_objc_test_support() { + IOS_SDK_VERSION=$(xcrun --sdk iphoneos --show-sdk-version) + + touch WORKSPACE +} diff --git a/test/shell/unittest.bash b/test/shell/unittest.bash new file mode 100644 index 00000000..b54fa631 --- /dev/null +++ b/test/shell/unittest.bash @@ -0,0 +1,846 @@ +#!/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. +# +# Common utility file for Bazel shell tests +# +# unittest.bash: a unit test framework in Bash. +# +# A typical test suite looks like so: +# +# ------------------------------------------------------------------------ +# #!/bin/bash +# +# source path/to/unittest.bash || exit 1 +# +# # Test that foo works. +# function test_foo() { +# foo >$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 <title> <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 00000000..be3409e2 --- /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|") +} From f9464299fa7bb3da352bda9c9ec9e95db0755e3a Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Mon, 9 Jan 2023 17:25:44 -0800 Subject: [PATCH 14/40] Move more tests --- test/shell/BUILD | 13 ++- test/shell/apple_test.sh | 181 ++++++++++++++++++++++++++++++++++++++- test/shell/objc_test.sh | 28 ------ test/shell/testenv.sh | 17 +++- 4 files changed, 204 insertions(+), 35 deletions(-) diff --git a/test/shell/BUILD b/test/shell/BUILD index ff9d1760..93a2da01 100644 --- a/test/shell/BUILD +++ b/test/shell/BUILD @@ -1,3 +1,11 @@ +filegroup( + name = "for_bazel_tests", + testonly = True, + srcs = [ + "@build_bazel_apple_support//:for_bazel_tests", + ], +) + sh_library( name = "bashunit", testonly = True, @@ -19,6 +27,7 @@ sh_test( name = "objc_test", size = "large", srcs = ["objc_test.sh"], + data = [":for_bazel_tests"], deps = [ ":apple_common", ":bashunit", @@ -28,7 +37,9 @@ sh_test( sh_test( name = "apple_test", srcs = ["apple_test.sh"], - data = [ + data = [":for_bazel_tests"], + shard_count = 3, + deps = [ ":apple_common", ":bashunit", ], diff --git a/test/shell/apple_test.sh b/test/shell/apple_test.sh index eef710e6..fe2db588 100755 --- a/test/shell/apple_test.sh +++ b/test/shell/apple_test.sh @@ -119,7 +119,8 @@ EOF || fail "expected output binary to be for i386 architecture" } -function test_apple_static_library() { +# TODO: Fix and re-enable +function DISABLED_test_apple_static_library() { rm -rf package mkdir -p package make_starlark_apple_static_library_rule_in package @@ -162,8 +163,8 @@ load("//package:starlark_apple_binary.bzl", "starlark_apple_binary") starlark_apple_binary( name = "main_binary", deps = [":main_lib"], - platform_type = "ios", - minimum_os_version = "10.0", + platform_type = "macos", + minimum_os_version = "12.0", ) objc_library( name = "main_lib", @@ -178,7 +179,6 @@ EOF bazel build --verbose_failures //package:main_binary \ --noincompatible_enable_cc_toolchain_resolution \ - --ios_multi_cpus=i386,x86_64 \ --xcode_version="$XCODE_VERSION" \ --apple_generate_dsym=true \ || fail "should build starlark_apple_binary with dSYMs" @@ -232,5 +232,178 @@ EOF || fail "expected output binary to contain 2 architectures" } +function test_apple_binary_spaces() { + rm -rf package + mkdir -p package + make_starlark_apple_binary_rule_in package + + cat > package/BUILD <<EOF +load("//package:starlark_apple_binary.bzl", "starlark_apple_binary") +starlark_apple_binary( + name = "main_binary", + deps = [":main_lib"], + platform_type = "ios", + minimum_os_version = "10.0", +) +objc_library( + name = "main_lib", + srcs = ["the main.m"], +) +EOF + cat > "package/the main.m" <<EOF +int main() { + return 0; +} +EOF + + bazel build --verbose_failures //package:main_binary \ + --noincompatible_enable_cc_toolchain_resolution \ + --ios_multi_cpus=i386,x86_64 \ + --xcode_version="$XCODE_VERSION" \ + --apple_generate_dsym=true \ + || fail "should build starlark_apple_binary with dSYMs" +} + +function test_apple_binary_crosstool_ios() { + rm -rf package + mkdir -p package + make_starlark_apple_binary_rule_in package + + cat > package/BUILD <<EOF +load("//package:starlark_apple_binary.bzl", "starlark_apple_binary") +objc_library( + name = "lib_a", + srcs = ["a.m"], +) +objc_library( + name = "lib_b", + srcs = ["b.m"], + deps = [":cc_lib"], +) +cc_library( + name = "cc_lib", + srcs = ["cc_lib.cc"], +) +starlark_apple_binary( + name = "main_binary", + deps = [":main_lib"], + platform_type = "ios", + minimum_os_version = "10.0", +) +objc_library( + name = "main_lib", + deps = [":lib_a", ":lib_b"], + srcs = ["main.m"], +) +genrule( + name = "lipo_run", + srcs = [":main_binary_lipobin"], + outs = ["lipo_out"], + cmd = + "set -e && " + + "lipo -info \$(location :main_binary_lipobin) > \$(@)", + tags = ["requires-darwin"], +) +EOF + touch package/a.m + touch package/b.m + cat > package/main.m <<EOF +int main() { + return 0; +} +EOF + cat > package/cc_lib.cc << EOF +#include <string> + +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 <<EOF +load("//package:starlark_apple_binary.bzl", "starlark_apple_binary") +starlark_apple_binary( + name = "main_binary", + deps = [":main_lib"], + platform_type = "ios", + minimum_os_version = "10.0", +) +objc_library( + name = "main_lib", + srcs = ["main.m"], +) +EOF + cat > package/main.m <<EOF +int main() { + return 0; +} +EOF + + bazel build --verbose_failures //package:main_binary \ + --noincompatible_enable_cc_toolchain_resolution \ + --xcode_version=$XCODE_VERSION \ + --apple_generate_dsym=true \ + || fail "should build starlark_apple_binary with dSYMs" +} + +function test_fat_binary_no_srcs() { + rm -rf package + mkdir -p package + make_starlark_apple_binary_rule_in package + + cat > package/BUILD <<EOF +load("//package:starlark_apple_binary.bzl", "starlark_apple_binary") +objc_library( + name = "lib_a", + srcs = ["a.m"], +) +objc_library( + name = "lib_b", + srcs = ["b.m"], +) +starlark_apple_binary( + name = "main_binary", + deps = [":lib_a", ":lib_b"], + platform_type = "ios", + minimum_os_version = "10.0", +) +genrule( + name = "lipo_run", + srcs = [":main_binary_lipobin"], + outs = ["lipo_out"], + cmd = + "set -e && " + + "lipo -info \$(location :main_binary_lipobin) > \$(@)", + tags = ["requires-darwin"], +) +EOF + touch package/a.m + cat > package/b.m <<EOF +int main() { + return 0; +} +EOF + + bazel build --verbose_failures --xcode_version=$XCODE_VERSION \ + //package:lipo_out --ios_multi_cpus=i386,x86_64 \ + --noincompatible_enable_cc_toolchain_resolution \ + || fail "should build starlark_apple_binary and obtain info via lipo" + + cat bazel-bin/package/lipo_out | grep "i386 x86_64" \ + || fail "expected output binary to contain 2 architectures" +} run_suite "apple_tests" diff --git a/test/shell/objc_test.sh b/test/shell/objc_test.sh index da4bd49b..2c1c7c37 100755 --- a/test/shell/objc_test.sh +++ b/test/shell/objc_test.sh @@ -55,34 +55,6 @@ function test_build_app() { || fail "should generate lib.a" } -function test_invalid_ios_sdk_version() { - setup_objc_test_support - make_lib - - ! bazel build --verbose_failures --apple_platform_type=ios \ - --ios_sdk_version=2.34 \ - //ios:lib >"$TEST_log" 2>&1 || fail "should fail" - expect_log "SDK \"iphonesimulator2.34\" cannot be located." -} - -function test_xcodelocator_embedded_tool() { - rm -rf ios - mkdir -p ios - - cat >ios/BUILD <<EOF -genrule( - name = "invoke_tool", - srcs = ["@bazel_tools//tools/osx:xcode-locator"], - outs = ["tool_output"], - cmd = "\$< > \$@", - tags = ["requires-darwin"], -) -EOF - - bazel build --verbose_failures //ios:invoke_tool >"$TEST_log" 2>&1 \ - || fail "should be able to resolve xcode-locator" -} - # Verifies contents of .a files do not contain timestamps -- if they did, the # results would not be hermetic. function test_archive_timestamps() { diff --git a/test/shell/testenv.sh b/test/shell/testenv.sh index 9763626d..e863f439 100755 --- a/test/shell/testenv.sh +++ b/test/shell/testenv.sh @@ -1,8 +1,21 @@ #!/bin/bash -# Sets up Objective-C tools. Mac only. function setup_objc_test_support() { IOS_SDK_VERSION=$(xcrun --sdk iphoneos --show-sdk-version) + export IOS_SDK_VERSION - touch WORKSPACE + cat > WORKSPACE.bazel <<EOF +local_repository( + name = 'build_bazel_apple_support', + path = '$(rlocation build_bazel_apple_support)', +) + +load("@build_bazel_apple_support//:tcsetup.bzl", "apple_cc_configure") + +apple_cc_configure() +EOF + + cat > .bazelrc <<EOF +build --apple_crosstool_top=@local_config_apple_cc//:toolchain +EOF } From 978e770f473d1ac6ec8b580e638b49ed35694639 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Mon, 9 Jan 2023 17:26:01 -0800 Subject: [PATCH 15/40] Enable objc_compile for detection --- crosstool/cc_toolchain_config.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/crosstool/cc_toolchain_config.bzl b/crosstool/cc_toolchain_config.bzl index 366594ad..ce7e66ec 100644 --- a/crosstool/cc_toolchain_config.bzl +++ b/crosstool/cc_toolchain_config.bzl @@ -166,6 +166,7 @@ def _impl(ctx): 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])], From b80b58811d3ebd20cfbdf4bef4e0f3d5b5abb6cd Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Mon, 9 Jan 2023 17:26:20 -0800 Subject: [PATCH 16/40] Add test filegroups --- BUILD | 5 +++++ constraints/BUILD | 8 ++++++++ crosstool/BUILD | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/BUILD b/BUILD index c5ebb68b..a8928f34 100644 --- a/BUILD +++ b/BUILD @@ -24,6 +24,11 @@ filegroup( testonly = 1, srcs = [ "WORKSPACE", + ":BUILD", + ":BUILD.toolchains", + ":tcsetup.bzl", + "//constraints:for_bazel_tests", + "//crosstool:for_bazel_tests", "//lib:for_bazel_tests", "//rules:for_bazel_tests", "//tools:for_bazel_tests", diff --git a/constraints/BUILD b/constraints/BUILD index b86291b5..c5f742cf 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 index 66a6c03f..de7606ac 100644 --- a/crosstool/BUILD +++ b/crosstool/BUILD @@ -12,3 +12,11 @@ cc_binary( "wrapped_clang.cc", ], ) + +# Consumed by bazel tests. +filegroup( + name = "for_bazel_tests", + testonly = True, + srcs = glob(["**"]), + visibility = ["//:__pkg__"], +) From 4d1691cf7eb85e89c34943286dd505bf4fe09c7a Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Tue, 10 Jan 2023 14:44:58 -0800 Subject: [PATCH 17/40] Move wrapped_clang test --- test/BUILD | 14 -------------- test/shell/BUILD | 11 +++++++++++ test/{ => shell}/wrapped_clang_test.sh | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) rename test/{ => shell}/wrapped_clang_test.sh (98%) diff --git a/test/BUILD b/test/BUILD index 67c692dc..ec444900 100644 --- a/test/BUILD +++ b/test/BUILD @@ -66,17 +66,3 @@ universal_binary_test( cpu = "darwin_arm64", target_under_test = "//test/test_data:multi_arch_cc_binary", ) - -sh_test( - name = "wrapped_clang_test", - size = "small", - srcs = [ - "wrapped_clang_test.sh", - ], - data = [ - "unittest.bash", - "unittest_utils.sh", - "//crosstool:wrapped_clang", - "@bazel_tools//tools/bash/runfiles", - ], -) diff --git a/test/shell/BUILD b/test/shell/BUILD index 93a2da01..3ba1b286 100644 --- a/test/shell/BUILD +++ b/test/shell/BUILD @@ -44,3 +44,14 @@ sh_test( ":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/wrapped_clang_test.sh b/test/shell/wrapped_clang_test.sh similarity index 98% rename from test/wrapped_clang_test.sh rename to test/shell/wrapped_clang_test.sh index 2d38059f..2a5f0855 100755 --- a/test/wrapped_clang_test.sh +++ b/test/shell/wrapped_clang_test.sh @@ -42,7 +42,7 @@ fi # Load test environment -source "$(rlocation "build_bazel_apple_support/test/unittest.bash")" \ +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") From a024f2670de044b5a83d0eb569a809ef7f01a59c Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Tue, 10 Jan 2023 14:48:17 -0800 Subject: [PATCH 18/40] rm --- test/unittest.bash | 846 ----------------------------------------- test/unittest_utils.sh | 181 --------- 2 files changed, 1027 deletions(-) delete mode 100644 test/unittest.bash delete mode 100755 test/unittest_utils.sh diff --git a/test/unittest.bash b/test/unittest.bash deleted file mode 100644 index b54fa631..00000000 --- a/test/unittest.bash +++ /dev/null @@ -1,846 +0,0 @@ -#!/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. -# -# Common utility file for Bazel shell tests -# -# unittest.bash: a unit test framework in Bash. -# -# A typical test suite looks like so: -# -# ------------------------------------------------------------------------ -# #!/bin/bash -# -# source path/to/unittest.bash || exit 1 -# -# # Test that foo works. -# function test_foo() { -# foo >$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 <title> <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/unittest_utils.sh b/test/unittest_utils.sh deleted file mode 100755 index be3409e2..00000000 --- a/test/unittest_utils.sh +++ /dev/null @@ -1,181 +0,0 @@ -# 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|") -} From 7450436717cbd723ef0c896a3856f581b8519dcc Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Tue, 10 Jan 2023 15:30:42 -0800 Subject: [PATCH 19/40] Add objc-archive back for now --- crosstool/cc_toolchain_config.bzl | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crosstool/cc_toolchain_config.bzl b/crosstool/cc_toolchain_config.bzl index ce7e66ec..13b7c8ed 100644 --- a/crosstool/cc_toolchain_config.bzl +++ b/crosstool/cc_toolchain_config.bzl @@ -422,6 +422,37 @@ def _impl(ctx): ], ) + 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 = [ @@ -629,6 +660,7 @@ def _impl(ctx): objcpp_compile_action, assemble_action, preprocess_assemble_action, + objc_archive_action, objc_executable_action, objcpp_executable_action, cpp_link_executable_action, @@ -1455,6 +1487,7 @@ def _impl(ctx): 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, @@ -1668,6 +1701,7 @@ def _impl(ctx): "objc-compile", "objc++-compile", "objc-fully-link", + "objc-archive", "objc-executable", "objc++-executable", "assemble", @@ -1720,6 +1754,7 @@ def _impl(ctx): 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, From 2b6373e501e6f63091cb4b92793336ecfda71dc1 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Tue, 10 Jan 2023 16:53:50 -0800 Subject: [PATCH 20/40] Remove supports_pic This was dead code and i assumed that was a bug, but the bug was it being there at all https://github.com/bazelbuild/bazel/issues/12439 --- crosstool/cc_toolchain_config.bzl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crosstool/cc_toolchain_config.bzl b/crosstool/cc_toolchain_config.bzl index 13b7c8ed..266e693c 100644 --- a/crosstool/cc_toolchain_config.bzl +++ b/crosstool/cc_toolchain_config.bzl @@ -893,8 +893,6 @@ def _impl(ctx): else: apply_simulator_compiler_flags_feature = feature(name = "apply_simulator_compiler_flags") - supports_pic_feature = feature(name = "supports_pic", enabled = True) - fastbuild_feature = feature(name = "fastbuild") no_legacy_features_feature = feature(name = "no_legacy_features") @@ -2722,7 +2720,6 @@ def _impl(ctx): ubsan_feature, default_sanitizer_flags_feature, archive_param_file_feature, - supports_pic_feature, ] elif (ctx.attr.cpu == "darwin_x86_64" or ctx.attr.cpu == "darwin_arm64" or @@ -2805,7 +2802,6 @@ def _impl(ctx): ubsan_feature, default_sanitizer_flags_feature, archive_param_file_feature, - supports_pic_feature, ] else: fail("Unreachable") From 12d0ad4074a8b962ace122c007df2d4ac950470b Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Tue, 10 Jan 2023 18:00:32 -0800 Subject: [PATCH 21/40] Add new CI jobs --- .bazelci/presubmit.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 24632123..c975e4ad 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -29,4 +29,18 @@ tasks: - .bazelci/update_workspace_to_deps_heads.sh <<: *common + macos_latest_vendored_toolchain: + name: "Latest Bazel" + bazel: latest + <<: *common + build_flags: + - "--crosstool_top=@local_config_apple_cc//:toolchain" + + macos_last_green_vendored_toolchain: + name: "Last Green Bazel" + bazel: last_green + <<: *common + build_flags: + - "--crosstool_top=@local_config_apple_cc//:toolchain" + buildifier: latest From 46b4d51c8be32f51acf605bc216b7bb549da94f9 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Tue, 10 Jan 2023 18:00:53 -0800 Subject: [PATCH 22/40] remove test target --- BUILD | 5 ----- main.c | 1 - 2 files changed, 6 deletions(-) delete mode 100644 main.c diff --git a/BUILD b/BUILD index a8928f34..8225b315 100644 --- a/BUILD +++ b/BUILD @@ -38,8 +38,3 @@ filegroup( # a temporary workspace for the tests. visibility = ["//visibility:public"], ) - -cc_binary( - name = "main", - srcs = ["main.c"], -) diff --git a/main.c b/main.c deleted file mode 100644 index c7367190..00000000 --- a/main.c +++ /dev/null @@ -1 +0,0 @@ -int main() { return 1;} From 8eb4d6b7859ea6b2414bb348bb02376dac24a44f Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Wed, 11 Jan 2023 10:08:10 -0800 Subject: [PATCH 23/40] all options --- .bazelci/presubmit.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index c975e4ad..a402ed2c 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -34,13 +34,17 @@ tasks: bazel: latest <<: *common 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" macos_last_green_vendored_toolchain: name: "Last Green Bazel" bazel: last_green <<: *common 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" buildifier: latest From 3365b622b619cbdab2f3428dcbe60128da7a7630 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Wed, 11 Jan 2023 11:41:06 -0800 Subject: [PATCH 24/40] Move to correct location --- BUILD | 3 --- WORKSPACE | 4 ---- BUILD.toolchains => crosstool/BUILD.toolchains | 0 tcsetup.bzl => crosstool/setup.bzl | 2 +- lib/repositories.bzl | 3 +++ test/shell/testenv.sh | 7 +++++-- 6 files changed, 9 insertions(+), 10 deletions(-) rename BUILD.toolchains => crosstool/BUILD.toolchains (100%) rename tcsetup.bzl => crosstool/setup.bzl (97%) diff --git a/BUILD b/BUILD index 8225b315..e6f17678 100644 --- a/BUILD +++ b/BUILD @@ -24,9 +24,6 @@ filegroup( testonly = 1, srcs = [ "WORKSPACE", - ":BUILD", - ":BUILD.toolchains", - ":tcsetup.bzl", "//constraints:for_bazel_tests", "//crosstool:for_bazel_tests", "//lib:for_bazel_tests", diff --git a/WORKSPACE b/WORKSPACE index 219f206c..a35c3010 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -24,7 +24,3 @@ http_archive( "https://github.com/bazelbuild/stardoc/releases/download/0.5.0/stardoc-0.5.0.tar.gz", ], ) - -load("//:tcsetup.bzl", "apple_cc_configure") - -apple_cc_configure() diff --git a/BUILD.toolchains b/crosstool/BUILD.toolchains similarity index 100% rename from BUILD.toolchains rename to crosstool/BUILD.toolchains diff --git a/tcsetup.bzl b/crosstool/setup.bzl similarity index 97% rename from tcsetup.bzl rename to crosstool/setup.bzl index e53cd9e0..3fe97bea 100644 --- a/tcsetup.bzl +++ b/crosstool/setup.bzl @@ -23,7 +23,7 @@ def _impl(repository_ctx): repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_USE_CPP_ONLY_TOOLCHAIN env variable.") elif repository_ctx.os.name.startswith("mac os"): repository_ctx.symlink( - repository_ctx.path(Label("//:BUILD.toolchains")), + repository_ctx.path(Label("@build_bazel_apple_support//crosstool:BUILD.toolchains")), "BUILD", ) else: diff --git a/lib/repositories.bzl b/lib/repositories.bzl index 90c31854..6c93539b 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. @@ -43,3 +44,5 @@ def apple_support_dependencies(): ], sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506", ) + + apple_cc_configure() diff --git a/test/shell/testenv.sh b/test/shell/testenv.sh index e863f439..de15bc24 100755 --- a/test/shell/testenv.sh +++ b/test/shell/testenv.sh @@ -10,9 +10,12 @@ local_repository( path = '$(rlocation build_bazel_apple_support)', ) -load("@build_bazel_apple_support//:tcsetup.bzl", "apple_cc_configure") +load( + "@build_bazel_apple_support//lib:repositories.bzl", + "apple_support_dependencies", +) -apple_cc_configure() +apple_support_dependencies() EOF cat > .bazelrc <<EOF From 04624769faf9de4b56bc2cb1e197c28f2e54d402 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Wed, 11 Jan 2023 12:11:01 -0800 Subject: [PATCH 25/40] sync upstream no dynamic_lookup --- crosstool/cc_toolchain_config.bzl | 77 ++++++++----------------------- 1 file changed, 19 insertions(+), 58 deletions(-) diff --git a/crosstool/cc_toolchain_config.bzl b/crosstool/cc_toolchain_config.bzl index 266e693c..8282c400 100644 --- a/crosstool/cc_toolchain_config.bzl +++ b/crosstool/cc_toolchain_config.bzl @@ -1026,64 +1026,25 @@ def _impl(ctx): requires = [feature_set(features = ["coverage"])], ) - if (ctx.attr.cpu == "darwin_x86_64" or - ctx.attr.cpu == "darwin_arm64" or - ctx.attr.cpu == "darwin_arm64e"): - 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, - ], - ), - ], - ), - flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ], - flag_groups = [flag_group(flags = ["-undefined", "dynamic_lookup"])], - ), - flag_set( - actions = [ - ACTION_NAMES.cpp_link_executable, - "objc-executable", - "objc++-executable", - ], - flag_groups = [flag_group(flags = ["-undefined", "dynamic_lookup"])], - with_features = [with_feature_set(features = ["dynamic_linking_mode"])], - ), - ], - ) - else: - 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, - ], - ), - ], - ), - ], - ) + 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", From 76acb02ffc635d6699b17278e3de1aead20bce78 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 08:49:57 -0800 Subject: [PATCH 26/40] Remove unnecessary Xcode logic If you're calling this you want Xcode --- crosstool/osx_cc_configure.bzl | 151 ++++++++++++++++----------------- 1 file changed, 72 insertions(+), 79 deletions(-) diff --git a/crosstool/osx_cc_configure.bzl b/crosstool/osx_cc_configure.bzl index 67dc120e..94b02fe6 100644 --- a/crosstool/osx_cc_configure.bzl +++ b/crosstool/osx_cc_configure.bzl @@ -155,87 +155,80 @@ def configure_osx_toolchain(repository_ctx): "@bazel_tools//tools/osx:xcode_locator.m", ]) - env = repository_ctx.os.environ - should_use_xcode = "BAZEL_USE_XCODE_TOOLCHAIN" in env and env["BAZEL_USE_XCODE_TOOLCHAIN"] == "1" - xcode_toolchains = [] - - # Make the following logic in sync with //tools/cpp:cc_configure.bzl#cc_autoconf_toolchains_impl (xcode_toolchains, xcodeloc_err) = run_xcode_locator( repository_ctx, paths["@bazel_tools//tools/osx:xcode_locator.m"], ) - if should_use_xcode and not xcode_toolchains: - fail("BAZEL_USE_XCODE_TOOLCHAIN is set to 1 but Bazel couldn't find Xcode installed on the " + - "system. Verify that 'xcode-select -p' is correct.") - if xcode_toolchains: - # 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 - else: + 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 From eec7cd763bd2f44cd870127189bc798286f6081c Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 08:57:53 -0800 Subject: [PATCH 27/40] Fix setup to not mirror upstream --- crosstool/setup.bzl | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/crosstool/setup.bzl b/crosstool/setup.bzl index 3fe97bea..ce5a9550 100644 --- a/crosstool/setup.bzl +++ b/crosstool/setup.bzl @@ -1,8 +1,9 @@ """TODO""" -load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_autoconf_impl") load("//crosstool:osx_cc_configure.bzl", "configure_osx_toolchain") +_DISABLE_ENV_VAR = "BAZEL_NO_APPLE_CPP_TOOLCHAIN" + def _impl(repository_ctx): """Generate BUILD file with 'toolchain' targets for the local host C++ toolchain. @@ -10,52 +11,35 @@ def _impl(repository_ctx): repository_ctx: repository context """ env = repository_ctx.os.environ + should_disable = _DISABLE_ENV_VAR in env and env[_DISABLE_ENV_VAR] == "1" - # Should we try to find C++ toolchain at all? If not, we don't have to generate toolchains for C++ at all. - should_detect_cpp_toolchain = "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN" not in env or env["BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN"] != "1" - - # Should we unconditionally *not* use xcode? If so, we don't have to run Xcode locator ever. - should_use_cpp_only_toolchain = "BAZEL_USE_CPP_ONLY_TOOLCHAIN" in env and env["BAZEL_USE_CPP_ONLY_TOOLCHAIN"] == "1" - - if not should_detect_cpp_toolchain: - repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN env variable.") - elif should_use_cpp_only_toolchain: - repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_USE_CPP_ONLY_TOOLCHAIN env variable.") + 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 C++ toolchain autoconfiguration was disabled because you're not running on macOS") + repository_ctx.file("BUILD", "# Apple CC toolchain autoconfiguration was disabled because you're not running on macOS") _apple_cc_autoconf_toolchains = repository_rule( - environ = [ - "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN", - "BAZEL_USE_CPP_ONLY_TOOLCHAIN", - ], + environ = [_DISABLE_ENV_VAR], implementation = _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" - # Should we try to find C++ toolchain at all? If not, we don't have to generate toolchains for C++ at all. - should_detect_cpp_toolchain = "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN" not in env or env["BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN"] != "1" - - # Should we unconditionally *not* use xcode? If so, we don't have to run Xcode locator ever. - should_use_cpp_only_toolchain = "BAZEL_USE_CPP_ONLY_TOOLCHAIN" in env and env["BAZEL_USE_CPP_ONLY_TOOLCHAIN"] == "1" - - if not should_detect_cpp_toolchain: - repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN env variable.") - elif should_use_cpp_only_toolchain: - repository_ctx.file("BUILD", "# Apple C++ toolchain autoconfiguration was disabled by BAZEL_USE_CPP_ONLY_TOOLCHAIN env variable.") + 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): - cc_autoconf_impl(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: - cc_autoconf_impl(repository_ctx, {}) + repository_ctx.file("BUILD", "# Apple CC autoconfiguration was disabled because you're not on macOS") MSVC_ENVVARS = [ "BAZEL_VC", From 2889c5be65fa0e8a11e33a2bdf0885158de52a98 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 09:04:52 -0800 Subject: [PATCH 28/40] no rules_cc --- crosstool/BUILD.tpl | 1 - 1 file changed, 1 deletion(-) diff --git a/crosstool/BUILD.tpl b/crosstool/BUILD.tpl index 4f04e35c..01ccd8e5 100644 --- a/crosstool/BUILD.tpl +++ b/crosstool/BUILD.tpl @@ -23,7 +23,6 @@ OSX_TOOLS_ARCHS = [ "tvos_arm64", ] + OSX_TOOLS_NON_DEVICE_ARCHS -load("@rules_cc//cc:defs.bzl", "cc_toolchain_suite", "cc_library") load(":armeabi_cc_toolchain_config.bzl", "armeabi_cc_toolchain_config") load(":cc_toolchain_config.bzl", "cc_toolchain_config") From 1d59f22bad37ff609116d635276e13ff39da21ea Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 09:05:08 -0800 Subject: [PATCH 29/40] bzlmod support --- MODULE.bazel | 5 +++++ crosstool/setup.bzl | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index 8c930cd1..28f54128 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -11,3 +11,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/crosstool/setup.bzl b/crosstool/setup.bzl index ce5a9550..cfda090a 100644 --- a/crosstool/setup.bzl +++ b/crosstool/setup.bzl @@ -100,3 +100,9 @@ def apple_cc_configure(): # 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) From 48d65d5dc01058eb91895041a314a87c4159b215 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:05:03 -0800 Subject: [PATCH 30/40] minor todos --- crosstool/setup.bzl | 2 +- test/shell/apple_test.sh | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crosstool/setup.bzl b/crosstool/setup.bzl index cfda090a..360ffffa 100644 --- a/crosstool/setup.bzl +++ b/crosstool/setup.bzl @@ -1,4 +1,4 @@ -"""TODO""" +"""Configure the Apple CC toolchain""" load("//crosstool:osx_cc_configure.bzl", "configure_osx_toolchain") diff --git a/test/shell/apple_test.sh b/test/shell/apple_test.sh index fe2db588..a494e93a 100755 --- a/test/shell/apple_test.sh +++ b/test/shell/apple_test.sh @@ -119,8 +119,7 @@ EOF || fail "expected output binary to be for i386 architecture" } -# TODO: Fix and re-enable -function DISABLED_test_apple_static_library() { +function test_apple_static_library() { rm -rf package mkdir -p package make_starlark_apple_static_library_rule_in package From 022491445ee4e11576395e9eeeaa9b78c5552a56 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:13:41 -0800 Subject: [PATCH 31/40] lint --- lib/repositories.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/repositories.bzl b/lib/repositories.bzl index 6c93539b..be613e96 100644 --- a/lib/repositories.bzl +++ b/lib/repositories.bzl @@ -28,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. From 121c3c896925358c9f4a4c0b286869ee65482827 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:14:32 -0800 Subject: [PATCH 32/40] format --- .bazelci/presubmit.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index a402ed2c..70b55f66 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -5,9 +5,9 @@ x_defaults: common: &common platform: macos build_targets: - - "//..." + - "//..." test_targets: - - "//..." + - "//..." tasks: macos_latest: @@ -24,9 +24,9 @@ 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: From c3588f4cc1cd75b234f5ca6cf01885932ae8b13d Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:15:17 -0800 Subject: [PATCH 33/40] names --- .bazelci/presubmit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 70b55f66..4e91214a 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -30,7 +30,7 @@ tasks: <<: *common macos_latest_vendored_toolchain: - name: "Latest Bazel" + name: "Latest Bazel vendored toolchain" bazel: latest <<: *common build_flags: @@ -39,7 +39,7 @@ tasks: - "--host_crosstool_top=@local_config_apple_cc//:toolchain" macos_last_green_vendored_toolchain: - name: "Last Green Bazel" + name: "Last Green Bazel vendored toolchain" bazel: last_green <<: *common build_flags: From 526a361402dd94f939aafae0b160c3cd3c2fa7c4 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:17:32 -0800 Subject: [PATCH 34/40] dup --- .bazelci/presubmit.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 4e91214a..78c35a6f 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -9,6 +9,16 @@ x_defaults: 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: name: "Latest Bazel" @@ -33,18 +43,12 @@ tasks: name: "Latest Bazel vendored toolchain" bazel: latest <<: *common - 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" + <<: *toolchain_flags macos_last_green_vendored_toolchain: name: "Last Green Bazel vendored toolchain" bazel: last_green <<: *common - 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" + <<: *toolchain_flags buildifier: latest From d1f735d9a9793c8c739c29895a62f99ae3540553 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:17:55 -0800 Subject: [PATCH 35/40] all --- test/shell/testenv.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/shell/testenv.sh b/test/shell/testenv.sh index de15bc24..153e0640 100755 --- a/test/shell/testenv.sh +++ b/test/shell/testenv.sh @@ -20,5 +20,7 @@ EOF cat > .bazelrc <<EOF build --apple_crosstool_top=@local_config_apple_cc//:toolchain +build --crosstool_top=@local_config_apple_cc//:toolchain +build --host_crosstool_top=@local_config_apple_cc//:toolchain EOF } From 2a3bb1896bdf86d059cd80a3cf9a823588bf0bcb Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:37:49 -0800 Subject: [PATCH 36/40] update readme --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7e72caf1..30927fb9 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,58 @@ # 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. + +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 From badab2b07e9fcd6be158beef03f43d7d1a764a73 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:38:41 -0800 Subject: [PATCH 37/40] more --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30927fb9..97be08b1 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ project. 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. +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`: From c54b4d7f8c9803a0158ca64cc7f34658d7081e0d Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Thu, 12 Jan 2023 13:47:06 -0800 Subject: [PATCH 38/40] add env var --- crosstool/setup.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/crosstool/setup.bzl b/crosstool/setup.bzl index 360ffffa..f801dbf6 100644 --- a/crosstool/setup.bzl +++ b/crosstool/setup.bzl @@ -59,6 +59,7 @@ MSVC_ENVVARS = [ _apple_cc_autoconf = repository_rule( environ = [ + _DISABLE_ENV_VAR, "ABI_LIBC_VERSION", "ABI_VERSION", "BAZEL_COMPILER", From 77ad32fdf397257903403ff49a26acca99e337c1 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Fri, 13 Jan 2023 06:43:23 -0800 Subject: [PATCH 39/40] Update crosstool/setup.bzl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thi Doãn <t@thi.im> --- crosstool/setup.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crosstool/setup.bzl b/crosstool/setup.bzl index f801dbf6..93b782d5 100644 --- a/crosstool/setup.bzl +++ b/crosstool/setup.bzl @@ -4,7 +4,7 @@ load("//crosstool:osx_cc_configure.bzl", "configure_osx_toolchain") _DISABLE_ENV_VAR = "BAZEL_NO_APPLE_CPP_TOOLCHAIN" -def _impl(repository_ctx): +def _apple_cc_autoconf_toolchains_impl(repository_ctx): """Generate BUILD file with 'toolchain' targets for the local host C++ toolchain. Args: From 7a01a5ea420f93f127e502ca98877651f9932d17 Mon Sep 17 00:00:00 2001 From: Keith Smiley <keithbsmiley@gmail.com> Date: Fri, 13 Jan 2023 06:43:30 -0800 Subject: [PATCH 40/40] Update crosstool/setup.bzl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Thi Doãn <t@thi.im> --- crosstool/setup.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crosstool/setup.bzl b/crosstool/setup.bzl index 93b782d5..6e80d02d 100644 --- a/crosstool/setup.bzl +++ b/crosstool/setup.bzl @@ -25,7 +25,7 @@ def _apple_cc_autoconf_toolchains_impl(repository_ctx): _apple_cc_autoconf_toolchains = repository_rule( environ = [_DISABLE_ENV_VAR], - implementation = _impl, + implementation = _apple_cc_autoconf_toolchains_impl, configure = True, )