diff --git a/test/internal/opts/process_compiler_opts_tests.bzl b/test/internal/opts/process_compiler_opts_tests.bzl index 2a3334571d..4ec7a78867 100644 --- a/test/internal/opts/process_compiler_opts_tests.bzl +++ b/test/internal/opts/process_compiler_opts_tests.bzl @@ -31,11 +31,13 @@ def _process_compiler_opts_test_impl(ctx): cxxopts = cxxopts, cxx_args = [], swiftcopts = swiftcopts, + swift_args = [], build_mode = ctx.attr.build_mode, cpp_fragment = _cpp_fragment_stub(ctx.attr.cpp_fragment), package_bin_dir = ctx.attr.package_bin_dir, build_settings = build_settings, cc_compiler_params_processor = None, + swift_compiler_params_processor = None, ) string_build_settings = stringify_dict(build_settings) diff --git a/tools/params_processors/BUILD b/tools/params_processors/BUILD index dad8a0131c..f6bb127239 100644 --- a/tools/params_processors/BUILD +++ b/tools/params_processors/BUILD @@ -43,6 +43,33 @@ py_binary( visibility = ["//visibility:public"], ) +py_library( + name = "swift_compiler_params_processor_library", + srcs = ["swift_compiler_params_processor.py"], + srcs_version = "PY3", + # TODO: Restrict visibility + visibility = ["//visibility:public"], +) + +py_binary( + name = "swift_compiler_params_processor", + srcs = ["swift_compiler_params_processor.py"], + python_version = "PY3", + srcs_version = "PY3", + # TODO: Restrict visibility + visibility = ["//visibility:public"], + deps = [":swift_compiler_params_processor_library"], +) + +py_test( + name = "swift_compiler_params_processor_tests", + srcs = ["swift_compiler_params_processor_tests.py"], + deps = [ + ":swift_compiler_params_processor_library", + "//:py_init_shim", + ], +) + # Release filegroup( diff --git a/tools/params_processors/swift_compiler_params_processor.py b/tools/params_processors/swift_compiler_params_processor.py new file mode 100755 index 0000000000..fff94f82d5 --- /dev/null +++ b/tools/params_processors/swift_compiler_params_processor.py @@ -0,0 +1,343 @@ +#!/usr/bin/python3 + +import sys +from typing import Iterator, List, Optional + + +# Swift compiler flags that we don't want to propagate to Xcode. +# The values are the number of flags to skip, 1 being the flag itself, 2 being +# another flag right after it, etc. +_SWIFTC_SKIP_OPTS = { + # Xcode sets output paths + "-emit-module-path": 2, + "-emit-object": 1, + "-output-file-map": 2, + + # Xcode sets these, and no way to unset them + "-enable-bare-slash-regex": 1, + "-module-name": 2, + "-num-threads": 2, + "-parse-as-library": 1, + "-sdk": 2, + "-target": 2, + + # We want to use Xcode's normal PCM handling + "-module-cache-path": 2, + + # We want Xcode's normal debug handling + "-debug-prefix-map": 2, + "-file-prefix-map": 2, + "-gline-tables-only": 1, + + # We want to use Xcode's normal indexing handling + "-index-ignore-system-modules": 1, + "-index-store-path": 2, + + # We set Xcode build settings to control these + "-enable-batch-mode": 1, + + # We don't want to translate this for BwX + "-emit-symbol-graph-dir": 2, + + # These are handled in `opts.bzl` + "-emit-objc-header-path": 2, + "-enable-testing": 1, + "-g": 1, + "-incremental": 1, + "-no-whole-module-optimization": 1, + "-strict-concurrency": 2, + "-swift-version": 2, + "-whole-module-optimization": 1, + "-wmo": 1, + + # This is rules_swift specific, and we don't want to translate it for BwX + "-Xwrapped-swift": 1, +} + +_SWIFTC_SKIP_COMPOUND_OPTS = { + "-Xfrontend": { + # We want Xcode to control coloring + "-color-diagnostics": 1, + + # We want Xcode's normal debug handling + "-no-clang-module-breadcrumbs": 1, + "-no-serialize-debugging-options": 1, + "-serialize-debugging-options": 1, + + # We don't want to translate this for BwX + "-emit-symbol-graph": 1, + }, +} + +_CLANG_SEARCH_PATHS = { + "-iquote": None, + "-isystem": None, + "-I": None, +} + + +def _is_relative_path(path: str) -> bool: + return not path.startswith("/") and not path.startswith("__BAZEL_") + + +def _process_clang_opt( + opt: str, + previous_opt: str, + previous_clang_opt: str, + is_bwx: bool) -> Optional[str]: + if opt == "-Xcc": + return opt + + is_clang_opt = previous_opt == "-Xcc" + + if not (is_clang_opt or is_bwx): + return None + + if opt.startswith("-fmodule-map-file="): + path = opt[18:] + is_relative = _is_relative_path(path) + if is_bwx and (is_clang_opt or is_relative): + if path == ".": + bwx_opt = "-fmodule-map-file=$(PROJECT_DIR)" + elif is_relative: + bwx_opt = "-fmodule-map-file=$(PROJECT_DIR)/" + path + else: + bwx_opt = opt + return bwx_opt + return opt + if opt.startswith("-iquote"): + path = opt[7:] + if not path: + return opt + is_relative = _is_relative_path(path) + if is_bwx and (is_clang_opt or is_relative): + if path == ".": + bwx_opt = "-iquote$(PROJECT_DIR)" + elif is_relative: + bwx_opt = "-iquote$(PROJECT_DIR)/" + path + else: + bwx_opt = opt + return bwx_opt + return opt + if opt.startswith("-I"): + path = opt[2:] + if not path: + return opt + is_relative = _is_relative_path(path) + if is_bwx and (is_clang_opt or is_relative): + if path == ".": + bwx_opt = "-I$(PROJECT_DIR)" + elif is_relative: + bwx_opt = "-I$(PROJECT_DIR)/" + path + else: + bwx_opt = opt + return bwx_opt + return opt + if opt.startswith("-isystem"): + path = opt[8:] + if not path: + return opt + is_relative = _is_relative_path(path) + if is_bwx and (is_clang_opt or is_relative): + if path == ".": + bwx_opt = "-isystem$(PROJECT_DIR)" + elif is_relative: + bwx_opt = "-isystem$(PROJECT_DIR)/" + path + else: + bwx_opt = opt + return bwx_opt + return opt + if is_bwx and (previous_opt in _CLANG_SEARCH_PATHS or + previous_clang_opt in _CLANG_SEARCH_PATHS): + if opt == ".": + bwx_opt = "$(PROJECT_DIR)" + elif _is_relative_path(opt): + bwx_opt = "$(PROJECT_DIR)/" + opt + else: + bwx_opt = opt + return bwx_opt + if is_clang_opt: + # -vfsoverlay doesn't apply `-working_directory=`, so we need to + # prefix it ourselves + if previous_clang_opt == "-ivfsoverlay": + if opt[0] != "/": + opt = "$(CURRENT_EXECUTION_ROOT)/" + opt + elif opt.startswith("-ivfsoverlay"): + value = opt[12:] + if not value.startswith("/"): + opt = "-ivfsoverlay$(CURRENT_EXECUTION_ROOT)/" + value + + # We do this check here, to prevent the `-O` logic below + # from incorrectly detecting this situation + return opt + + return None + + +def _inner_process_swiftcopts( + *, + opt: str, + previous_opt: str, + previous_frontend_opt: str, + previous_clang_opt: str, + is_bwx: bool) -> Optional[str]: + clang_opt = _process_clang_opt( + opt = opt, + previous_opt = previous_opt, + previous_clang_opt = previous_clang_opt, + is_bwx = is_bwx, + ) + if clang_opt: + return clang_opt + + if opt.startswith("-O"): + # Handled in `opts.bzl` + return None + if opt[0] != "-" and opt.endswith(".swift"): + # These are the files to compile, not options. They are seen here + # because of the way we collect Swift compiler options. Ideally in + # the future we could collect Swift compiler options similar to how + # we collect C and C++ compiler options. + return None + + if opt == "-Xfrontend": + # We return early to prevent issues with the checks below + return opt + + # -vfsoverlay doesn't apply `-working_directory=`, so we need to + # prefix it ourselves + previous_vfsoverlay_opt = previous_frontend_opt or previous_opt + if previous_vfsoverlay_opt == "-vfsoverlay": + if opt[0] != "/": + return "$(CURRENT_EXECUTION_ROOT)/" + opt + return opt + if opt.startswith("-vfsoverlay"): + value = opt[11:] + if value and value[0] != "/": + return "-vfsoverlay$(CURRENT_EXECUTION_ROOT)/" + value + return opt + + return opt + + +def process_args( + params_paths: List[str], + parse_args, + build_mode: str) -> List[str]: + is_bwx = build_mode == "xcode" + + # First line is "swiftc" + skip_next = 1 + + processed_opts = [] + previous_opt = None + previous_frontend_opt = None + previous_clang_opt = None + for params_path in params_paths: + opts_iter = parse_args(params_path) + + next_opt = None + while True: + if next_opt: + opt = next_opt + next_opt = None + else: + opt = next(opts_iter, None) + if opt == None: + break + + # Remove trailing newline + opt = opt[:-1] + + if skip_next: + skip_next -= 1 + continue + + # Change "compile.params" from `shell` to `multiline` format + # https://bazel.build/versions/6.1.0/rules/lib/Args#set_param_file_format.format + if opt.startswith("'") and opt.endswith("'"): + opt = opt[1:-1] + + root_opt = opt.split("=")[0] + + skip_next = _SWIFTC_SKIP_OPTS.get(root_opt, 0) + if skip_next: + skip_next -= 1 + continue + + compound_skip_next = _SWIFTC_SKIP_COMPOUND_OPTS.get(root_opt) + if compound_skip_next: + next_opt = next(opts_iter, None) + if next_opt: + # Remove trailing newline + next_opt = next_opt[:-1] + skip_next = compound_skip_next.get(next_opt, 0) + if skip_next: + # No need to decrement 1, since we need to skip the + # first opt + continue + + processed_opt = _inner_process_swiftcopts( + opt = opt, + previous_opt = previous_opt, + previous_frontend_opt = previous_frontend_opt, + previous_clang_opt = previous_clang_opt, + is_bwx = is_bwx, + ) + + if previous_opt == "-Xcc": + previous_clang_opt = opt + previous_frontend_opt = None + elif opt != "-Xcc": + previous_clang_opt = None + if previous_opt == "-Xfrontend": + previous_frontend_opt = opt + elif opt != "-Xfrontend": + previous_frontend_opt = None + + previous_opt = opt + + opt = processed_opt + if not opt: + continue + + # Use Xcode set `DEVELOPER_DIR` + opt = opt.replace( + "__BAZEL_XCODE_DEVELOPER_DIR__", + "$(DEVELOPER_DIR)", + ) + + # Use Xcode set `SDKROOT` + opt = opt.replace("__BAZEL_XCODE_SDKROOT__", "$(SDKROOT)") + + # Quote the option if it contains spaces or build setting variables + if " " in opt or ("$(" in opt and ")" in opt): + opt = f"'{opt}'" + + processed_opts.append(opt) + + return processed_opts + + +def _main(output_path: str, build_mode: str, params_paths: List[str]) -> None: + processed_opts = process_args(params_paths, _parse_args, build_mode) + + with open(output_path, encoding = "utf-8", mode = "w") as fp: + result = "\n".join(processed_opts) + fp.write(f'{result}\n') + + +def _parse_args(params_path: str) -> Iterator[str]: + return open(params_path, encoding = "utf-8") + + +if __name__ == "__main__": + if len(sys.argv) < 4: + print( + f""" +Usage: {sys.argv[0]} output_path build_mode [params_file, ...]""", + file = sys.stderr, + ) + exit(1) + + _main(sys.argv[1], sys.argv[2], sys.argv[3:]) diff --git a/tools/params_processors/swift_compiler_params_processor_tests.py b/tools/params_processors/swift_compiler_params_processor_tests.py new file mode 100644 index 0000000000..8cf6deb30b --- /dev/null +++ b/tools/params_processors/swift_compiler_params_processor_tests.py @@ -0,0 +1,696 @@ +"""Tests for swift_compiler_params_processor.""" + +import unittest + +from tools.params_processors import swift_compiler_params_processor + +class swift_compiler_params_processor_test(unittest.TestCase): + + def test_paths_bwb(self): + def _parse_args(args): + return iter([f"{arg}\n" for arg in args]) + + self.assertEqual( + swift_compiler_params_processor.process_args( + [[ + "swiftc", + + # -fmodule-map-file + "-Xcc", + "-fmodule-map-file=/absolute/path", + "-Xcc", + "-fmodule-map-file=relative/path", + "-Xcc", + "-fmodule-map-file=.", + + # -iquote + "-iquote/absolute/path", + "-iquote", + "/absolute/path", + "-iquote", + "/absolute/path", + "-iquoterelative/path", + "-iquote", + "relative/path", + "-iquote.", + "-iquote", + ".", + + "-Xcc", + "-iquote/absolute/path", + "-Xcc", + "-iquote", + "-Xcc", + "/absolute/path", + "-Xcc", + "-iquote", + "-Xcc", + "/absolute/path", + "-Xcc", + "-iquoterelative/path", + "-Xcc", + "-iquote", + "-Xcc", + "relative/path", + "-Xcc", + "-iquote.", + "-Xcc", + "-iquote", + "-Xcc", + ".", + + # -I + "-I/absolute/path", + "-I", + "/absolute/path", + "-Irelative/path", + "-I", + "relative/path", + "-I.", + "-I", + ".", + + "-Xcc", + "-I/absolute/path", + "-Xcc", + "-I", + "-Xcc", + "/absolute/path", + "-Xcc", + "-Irelative/path", + "-Xcc", + "-I", + "-Xcc", + "relative/path", + "-Xcc", + "-I.", + "-Xcc", + "-I", + "-Xcc", + ".", + + # -isystem + "-isystem/absolute/path", + "-isystem", + "/absolute/path", + "-isystemrelative/path", + "-isystem", + "relative/path", + "-isystem.", + "-isystem", + ".", + + "-Xcc", + "-isystem/absolute/path", + "-Xcc", + "-isystem", + "-Xcc", + "/absolute/path", + "-Xcc", + "-isystemrelative/path", + "-Xcc", + "-isystem", + "-Xcc", + "relative/path", + "-Xcc", + "-isystem.", + "-Xcc", + "-isystem", + "-Xcc", + ".", + ]], + parse_args = _parse_args, + build_mode = "bazel", + ), + [ + # -fmodule-map-file + "-Xcc", + "-fmodule-map-file=/absolute/path", + "-Xcc", + "-fmodule-map-file=relative/path", + "-Xcc", + "-fmodule-map-file=.", + + # -iquote + "-iquote/absolute/path", + "-iquote", + "/absolute/path", + "-iquote", + "/absolute/path", + "-iquoterelative/path", + "-iquote", + "relative/path", + "-iquote.", + "-iquote", + ".", + + "-Xcc", + "-iquote/absolute/path", + "-Xcc", + "-iquote", + "-Xcc", + "/absolute/path", + "-Xcc", + "-iquote", + "-Xcc", + "/absolute/path", + "-Xcc", + "-iquoterelative/path", + "-Xcc", + "-iquote", + "-Xcc", + "relative/path", + "-Xcc", + "-iquote.", + "-Xcc", + "-iquote", + "-Xcc", + ".", + + # -I + "-I/absolute/path", + "-I", + "/absolute/path", + "-Irelative/path", + "-I", + "relative/path", + "-I.", + "-I", + ".", + + "-Xcc", + "-I/absolute/path", + "-Xcc", + "-I", + "-Xcc", + "/absolute/path", + "-Xcc", + "-Irelative/path", + "-Xcc", + "-I", + "-Xcc", + "relative/path", + "-Xcc", + "-I.", + "-Xcc", + "-I", + "-Xcc", + ".", + + # -isystem + "-isystem/absolute/path", + "-isystem", + "/absolute/path", + "-isystemrelative/path", + "-isystem", + "relative/path", + "-isystem.", + "-isystem", + ".", + + "-Xcc", + "-isystem/absolute/path", + "-Xcc", + "-isystem", + "-Xcc", + "/absolute/path", + "-Xcc", + "-isystemrelative/path", + "-Xcc", + "-isystem", + "-Xcc", + "relative/path", + "-Xcc", + "-isystem.", + "-Xcc", + "-isystem", + "-Xcc", + ".", + ], + ) + + def test_paths_bwx(self): + self.maxDiff = None + + def _parse_args(args): + return iter([f"{arg}\n" for arg in args]) + + self.assertEqual( + swift_compiler_params_processor.process_args( + [[ + "swiftc", + + # -fmodule-map-file + "-Xcc", + "-fmodule-map-file=/absolute/path", + "-Xcc", + "-fmodule-map-file=relative/path", + "-Xcc", + "-fmodule-map-file=.", + + # -iquote + "-iquote/absolute/path", + "-iquote", + "/absolute/path", + "-iquote", + "/absolute/path", + "-iquoterelative/path", + "-iquote", + "relative/path", + "-iquote.", + "-iquote", + ".", + + "-Xcc", + "-iquote/absolute/path", + "-Xcc", + "-iquote", + "-Xcc", + "/absolute/path", + "-Xcc", + "-iquote", + "-Xcc", + "/absolute/path", + "-Xcc", + "-iquoterelative/path", + "-Xcc", + "-iquote", + "-Xcc", + "relative/path", + "-Xcc", + "-iquote.", + "-Xcc", + "-iquote", + "-Xcc", + ".", + + # -I + "-I/absolute/path", + "-I", + "/absolute/path", + "-Irelative/path", + "-I", + "relative/path", + "-I.", + "-I", + ".", + + "-Xcc", + "-I/absolute/path", + "-Xcc", + "-I", + "-Xcc", + "/absolute/path", + "-Xcc", + "-Irelative/path", + "-Xcc", + "-I", + "-Xcc", + "relative/path", + "-Xcc", + "-I.", + "-Xcc", + "-I", + "-Xcc", + ".", + + # -isystem + "-isystem/absolute/path", + "-isystem", + "/absolute/path", + "-isystemrelative/path", + "-isystem", + "relative/path", + "-isystem.", + "-isystem", + ".", + + "-Xcc", + "-isystem/absolute/path", + "-Xcc", + "-isystem", + "-Xcc", + "/absolute/path", + "-Xcc", + "-isystemrelative/path", + "-Xcc", + "-isystem", + "-Xcc", + "relative/path", + "-Xcc", + "-isystem.", + "-Xcc", + "-isystem", + "-Xcc", + ".", + ]], + parse_args = _parse_args, + build_mode = "xcode", + ), + [ + # -fmodule-map-file + "-Xcc", + "-fmodule-map-file=/absolute/path", + "-Xcc", + "'-fmodule-map-file=$(PROJECT_DIR)/relative/path'", + "-Xcc", + "'-fmodule-map-file=$(PROJECT_DIR)'", + + # -iquote + "-iquote/absolute/path", + "-iquote", + "/absolute/path", + "-iquote", + "/absolute/path", + "'-iquote$(PROJECT_DIR)/relative/path'", + "-iquote", + "'$(PROJECT_DIR)/relative/path'", + "'-iquote$(PROJECT_DIR)'", + "-iquote", + "'$(PROJECT_DIR)'", + + "-Xcc", + "-iquote/absolute/path", + "-Xcc", + "-iquote", + "-Xcc", + "/absolute/path", + "-Xcc", + "-iquote", + "-Xcc", + "/absolute/path", + "-Xcc", + "'-iquote$(PROJECT_DIR)/relative/path'", + "-Xcc", + "-iquote", + "-Xcc", + "'$(PROJECT_DIR)/relative/path'", + "-Xcc", + "'-iquote$(PROJECT_DIR)'", + "-Xcc", + "-iquote", + "-Xcc", + "'$(PROJECT_DIR)'", + + # -I + "-I/absolute/path", + "-I", + "/absolute/path", + "'-I$(PROJECT_DIR)/relative/path'", + "-I", + "'$(PROJECT_DIR)/relative/path'", + "'-I$(PROJECT_DIR)'", + "-I", + "'$(PROJECT_DIR)'", + + "-Xcc", + "-I/absolute/path", + "-Xcc", + "-I", + "-Xcc", + "/absolute/path", + "-Xcc", + "'-I$(PROJECT_DIR)/relative/path'", + "-Xcc", + "-I", + "-Xcc", + "'$(PROJECT_DIR)/relative/path'", + "-Xcc", + "'-I$(PROJECT_DIR)'", + "-Xcc", + "-I", + "-Xcc", + "'$(PROJECT_DIR)'", + + # -isystem + "-isystem/absolute/path", + "-isystem", + "/absolute/path", + "'-isystem$(PROJECT_DIR)/relative/path'", + "-isystem", + "'$(PROJECT_DIR)/relative/path'", + "'-isystem$(PROJECT_DIR)'", + "-isystem", + "'$(PROJECT_DIR)'", + + "-Xcc", + "-isystem/absolute/path", + "-Xcc", + "-isystem", + "-Xcc", + "/absolute/path", + "-Xcc", + "'-isystem$(PROJECT_DIR)/relative/path'", + "-Xcc", + "-isystem", + "-Xcc", + "'$(PROJECT_DIR)/relative/path'", + "-Xcc", + "'-isystem$(PROJECT_DIR)'", + "-Xcc", + "-isystem", + "-Xcc", + "'$(PROJECT_DIR)'", + ], + ) + + def test_replacements(self): + def _parse_args(args): + return iter([f"{arg}\n" for arg in args]) + + self.assertEqual( + swift_compiler_params_processor.process_args( + [[ + "swiftc", + "-F__BAZEL_XCODE_DEVELOPER_DIR__/Hi", + "-I__BAZEL_XCODE_SDKROOT__/Yo", + "-I__BAZEL_XCODE_SOMETHING_/path", + ]], + parse_args = _parse_args, + build_mode = "bazel", + ), + [ + "'-F$(DEVELOPER_DIR)/Hi'", + "'-I$(SDKROOT)/Yo'", + "-I__BAZEL_XCODE_SOMETHING_/path", + ], + ) + + def test_skips_bwb(self): + def _parse_args(args): + return iter([f"{arg}\n" for arg in args]) + + self.assertEqual( + swift_compiler_params_processor.process_args( + [[ + "swiftc", + "-output-file-map", + "path", + "-passthrough", + "-debug-prefix-map", + "__BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR", + "-file-prefix-map", + "__BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR", + "-emit-module-path", + "path", + "-passthrough", + "-Xfrontend", + "-color-diagnostics", + "-Xfrontend", + "-import-underlying-module", + "-emit-object", + "-enable-batch-mode", + "-passthrough", + "-gline-tables-only", + "-sdk", + "something", + "-module-name", + "name", + "-passthrough", + "-I__BAZEL_XCODE_SOMETHING_/path", + "-num-threads", + "6", + "-passthrough", + "-Ibazel-out/...", + "-parse-as-library", + "-passthrough", + "-parse-as-library", + "-keep-me=something.swift", + "reject-me.swift", + "-target", + "ios", + "-Xcc", + "-weird", + "-Xcc", + "-a=bazel-out/hi", + "-Xwrapped-swift", + "-passthrough", + ]], + parse_args = _parse_args, + build_mode = "bazel", + ), + [ + "-passthrough", + "-passthrough", + "-Xfrontend", + "-import-underlying-module", + "-passthrough", + "-passthrough", + "-I__BAZEL_XCODE_SOMETHING_/path", + "-passthrough", + "-Ibazel-out/...", + "-passthrough", + "-keep-me=something.swift", + "-Xcc", + "-weird", + "-Xcc", + "-a=bazel-out/hi", + "-passthrough", + ], + ) + + def test_skips_bwx(self): + def _parse_args(args): + return iter([f"{arg}\n" for arg in args]) + + self.assertEqual( + swift_compiler_params_processor.process_args( + [[ + "swiftc", + "-output-file-map", + "path", + "-passthrough", + "-debug-prefix-map", + "__BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR", + "-file-prefix-map", + "__BAZEL_XCODE_DEVELOPER_DIR__=DEVELOPER_DIR", + "-emit-module-path", + "path", + "-passthrough", + "-Xfrontend", + "-color-diagnostics", + "-Xfrontend", + "-import-underlying-module", + "-emit-object", + "-enable-batch-mode", + "-passthrough", + "-gline-tables-only", + "-sdk", + "something", + "-module-name", + "name", + "-passthrough", + "-I__BAZEL_XCODE_SOMETHING_/path", + "-num-threads", + "6", + "-passthrough", + "-Ibazel-out/...", + "-parse-as-library", + "-passthrough", + "-parse-as-library", + "-keep-me=something.swift", + "reject-me.swift", + "-target", + "ios", + "-Xcc", + "-weird", + "-Xcc", + "-a=bazel-out/hi", + "-Xwrapped-swift", + "-passthrough", + ]], + parse_args = _parse_args, + build_mode = "xcode", + ), + [ + "-passthrough", + "-passthrough", + "-Xfrontend", + "-import-underlying-module", + "-passthrough", + "-passthrough", + "-I__BAZEL_XCODE_SOMETHING_/path", + "-passthrough", + "'-I$(PROJECT_DIR)/bazel-out/...'", + "-passthrough", + "-keep-me=something.swift", + "-Xcc", + "-weird", + "-Xcc", + "-a=bazel-out/hi", + "-passthrough", + ], + ) + + def test_vfsoverlay(self): + def _parse_args(args): + return iter([f"{arg}\n" for arg in args]) + + self.assertEqual( + swift_compiler_params_processor.process_args( + [[ + "swiftc", + "-vfsoverlay", + "/Some/Path.yaml", + "-vfsoverlay", + "relative/Path.yaml", + "-Xfrontend", + "-vfsoverlay", + "-Xfrontend", + "/Some/Path.yaml", + "-Xfrontend", + "-vfsoverlay", + "-Xfrontend", + "relative/Path.yaml", + "-Xfrontend", + "-vfsoverlay/Some/Path.yaml", + "-Xfrontend", + "-vfsoverlayrelative/Path.yaml", + ]], + parse_args = _parse_args, + build_mode = "bazel", + ), + [ + "-vfsoverlay", + "/Some/Path.yaml", + "-vfsoverlay", + "'$(CURRENT_EXECUTION_ROOT)/relative/Path.yaml'", + "-Xfrontend", + "-vfsoverlay", + "-Xfrontend", + "/Some/Path.yaml", + "-Xfrontend", + "-vfsoverlay", + "-Xfrontend", + "'$(CURRENT_EXECUTION_ROOT)/relative/Path.yaml'", + "-Xfrontend", + "-vfsoverlay/Some/Path.yaml", + "-Xfrontend", + "'-vfsoverlay$(CURRENT_EXECUTION_ROOT)/relative/Path.yaml'", + ], + ) + + def test_quoting(self): + def _parse_args(args): + return iter([f"{arg}\n" for arg in args]) + + self.assertEqual( + swift_compiler_params_processor.process_args( + [[ + "swiftc", + "-Inon/quoted/path", + "-vfsoverlay", + "relative/Path.yaml", + ]], + parse_args = _parse_args, + build_mode = "bazel", + ), + [ + "-Inon/quoted/path", + "-vfsoverlay", + "'$(CURRENT_EXECUTION_ROOT)/relative/Path.yaml'", + ], + ) + +if __name__ == '__main__': + unittest.main() diff --git a/xcodeproj/internal/opts.bzl b/xcodeproj/internal/opts.bzl index a45c0ec554..a32d43318b 100644 --- a/xcodeproj/internal/opts.bzl +++ b/xcodeproj/internal/opts.bzl @@ -4,60 +4,6 @@ load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") load(":files.bzl", "is_relative_path") load(":memory_efficiency.bzl", "EMPTY_LIST") -# Swift compiler flags that we don't want to propagate to Xcode. -# The values are the number of flags to skip, 1 being the flag itself, 2 being -# another flag right after it, etc. -_SWIFTC_SKIP_OPTS = { - # Xcode sets output paths - "-emit-module-path": 2, - "-emit-object": 1, - "-output-file-map": 2, - - # Xcode sets these, and no way to unset them - "-enable-bare-slash-regex": 1, - "-module-name": 2, - "-num-threads": 2, - "-parse-as-library": 1, - "-sdk": 2, - "-target": 2, - - # We want to use Xcode's normal PCM handling - "-module-cache-path": 2, - - # We want Xcode's normal debug handling - "-debug-prefix-map": 2, - "-file-prefix-map": 2, - "-gline-tables-only": 1, - - # We want to use Xcode's normal indexing handling - "-index-ignore-system-modules": 1, - "-index-store-path": 2, - - # We set Xcode build settings to control these - "-enable-batch-mode": 1, - - # We don't want to translate this for BwX - "-emit-symbol-graph-dir": 2, - - # This is rules_swift specific, and we don't want to translate it for BwX - "-Xwrapped-swift": 1, -} - -_SWIFTC_SKIP_COMPOUND_OPTS = { - "-Xfrontend": { - # We want Xcode to control coloring - "-color-diagnostics": 1, - - # We want Xcode's normal debug handling - "-no-clang-module-breadcrumbs": 1, - "-no-serialize-debugging-options": 1, - "-serialize-debugging-options": 1, - - # We don't want to translate this for BwX - "-emit-symbol-graph": 1, - }, -} - # Maps Swift compliation mode compiler flags to the corresponding Xcode values _SWIFT_COMPILATION_MODE_OPTS = { "-incremental": "singlefile", @@ -261,14 +207,21 @@ def _get_unprocessed_compiler_opts( * A `list` of Swift compiler options. """ - swiftcopts = [] + swiftcopts = EMPTY_LIST + swift_args = EMPTY_LIST for action in target.actions: if action.mnemonic == "SwiftCompile": # First two arguments are "worker" and "swiftc" swiftcopts = action.argv[2:] + swift_args = action.args break - conlyopts, conly_args, cxxopts, cxxargs = _get_unprocessed_cc_compiler_opts( + ( + conlyopts, + conly_args, + cxxopts, + cxx_args, + ) = _get_unprocessed_cc_compiler_opts( ctx = ctx, c_sources = c_sources, cxx_sources = cxx_sources, @@ -288,8 +241,9 @@ Using VFS overlays with `build_mode = "xcode"` is unsupported. conlyopts, conly_args, cxxopts, - cxxargs, + cxx_args, swiftcopts, + swift_args, ) def _process_cc_opts(opts, *, build_settings): @@ -393,126 +347,86 @@ def _process_swiftcopts( def _process_clang_opt(opt, previous_opt, previous_clang_opt): if opt == "-Xcc": - return opt - - is_clang_opt = previous_opt == "-Xcc" + return True + if previous_opt != "-Xcc": + return False if opt.startswith("-F"): path = opt[2:] - if is_clang_opt: - if path == ".": - clang_opt = "-F$(PROJECT_DIR)" - elif is_relative_path(path): - clang_opt = "-F$(PROJECT_DIR)/" + path - else: - clang_opt = opt - clang_opts.append(clang_opt) - return opt - - is_bwx = build_mode == "xcode" - if not (is_clang_opt or is_bwx): - return None - - if opt.startswith("-fmodule-map-file="): + if path == ".": + clang_opt = "-F$(PROJECT_DIR)" + elif is_relative_path(path): + clang_opt = "-F$(PROJECT_DIR)/" + path + else: + clang_opt = opt + elif opt.startswith("-fmodule-map-file="): path = opt[18:] - is_relative = is_relative_path(path) - if is_clang_opt or is_relative: - if path == ".": - bwx_opt = "-fmodule-map-file=$(PROJECT_DIR)" - elif is_relative: - bwx_opt = "-fmodule-map-file=$(PROJECT_DIR)/" + path - else: - bwx_opt = opt - if is_bwx: - opt = bwx_opt - clang_opts.append(bwx_opt) - return opt - if opt.startswith("-iquote"): + if path == ".": + clang_opt = "-fmodule-map-file=$(PROJECT_DIR)" + elif is_relative_path(path): + clang_opt = "-fmodule-map-file=$(PROJECT_DIR)/" + path + else: + clang_opt = opt + elif opt.startswith("-iquote"): path = opt[7:] if not path: - if is_clang_opt: - clang_opts.append(opt) - return opt - is_relative = is_relative_path(path) - if is_clang_opt or is_relative: - if path == ".": - bwx_opt = "-iquote$(PROJECT_DIR)" - elif is_relative: - bwx_opt = "-iquote$(PROJECT_DIR)/" + path - else: - bwx_opt = opt - if is_bwx: - opt = bwx_opt - if is_clang_opt: - clang_opts.append(bwx_opt) - return opt - if opt.startswith("-I"): + clang_opt = opt + elif path == ".": + clang_opt = "-iquote$(PROJECT_DIR)" + elif is_relative_path(path): + clang_opt = "-iquote$(PROJECT_DIR)/" + path + else: + clang_opt = opt + elif opt.startswith("-I"): path = opt[2:] if not path: - if is_clang_opt: - clang_opts.append(opt) - return opt - is_relative = is_relative_path(path) - if is_clang_opt or is_relative: - if path == ".": - bwx_opt = "-I$(PROJECT_DIR)" - elif is_relative: - bwx_opt = "-I$(PROJECT_DIR)/" + path - else: - bwx_opt = opt - if is_bwx: - opt = bwx_opt - if is_clang_opt: - clang_opts.append(bwx_opt) - return opt - if opt.startswith("-isystem"): + clang_opt = opt + elif path == ".": + clang_opt = "-I$(PROJECT_DIR)" + elif is_relative_path(path): + clang_opt = "-I$(PROJECT_DIR)/" + path + else: + clang_opt = opt + elif opt.startswith("-isystem"): path = opt[8:] if not path: - if is_clang_opt: - clang_opts.append(opt) - return opt - is_relative = is_relative_path(path) - if is_clang_opt or is_relative: - if path == ".": - bwx_opt = "-isystem$(PROJECT_DIR)" - elif is_relative: - bwx_opt = "-isystem$(PROJECT_DIR)/" + path - else: - bwx_opt = opt - if is_bwx: - opt = bwx_opt - if is_clang_opt: - clang_opts.append(bwx_opt) - return opt - if previous_clang_opt in _CLANG_SEARCH_PATHS: + clang_opt = opt + elif path == ".": + clang_opt = "-isystem$(PROJECT_DIR)" + elif is_relative_path(path): + clang_opt = "-isystem$(PROJECT_DIR)/" + path + else: + clang_opt = opt + elif previous_clang_opt in _CLANG_SEARCH_PATHS: if opt == ".": - opt = "$(PROJECT_DIR)" + clang_opt = "$(PROJECT_DIR)" elif is_relative_path(opt): - opt = "$(PROJECT_DIR)/" + opt - clang_opts.append(opt) - return opt - if is_clang_opt: + clang_opt = "$(PROJECT_DIR)/" + opt + else: + clang_opt = opt + elif previous_clang_opt == "-ivfsoverlay": # -vfsoverlay doesn't apply `-working_directory=`, so we need to # prefix it ourselves - if previous_clang_opt == "-ivfsoverlay": - if opt[0] != "/": - opt = "$(CURRENT_EXECUTION_ROOT)/" + opt - elif opt.startswith("-ivfsoverlay"): - value = opt[12:] - if not value.startswith("/"): - opt = "-ivfsoverlay$(CURRENT_EXECUTION_ROOT)/" + value - - # We do this check here, to prevent the `-O` logic below - # from incorrectly detecting this situation - clang_opts.append(opt) - return opt + if opt[0] != "/": + clang_opt = "$(CURRENT_EXECUTION_ROOT)/" + opt + else: + clang_opt = opt + elif opt.startswith("-ivfsoverlay"): + value = opt[12:] + if not value.startswith("/"): + clang_opt = "-ivfsoverlay$(CURRENT_EXECUTION_ROOT)/" + value + else: + clang_opt = opt + else: + clang_opt = opt - return None + clang_opts.append(clang_opt) + + return True - def _inner_process_swiftcopts(opt, previous_opt, previous_frontend_opt, previous_clang_opt): - clang_opt = _process_clang_opt(opt, previous_opt, previous_clang_opt) - if clang_opt: - return clang_opt + def _inner_process_swiftcopts(opt, previous_opt, previous_clang_opt): + if _process_clang_opt(opt, previous_opt, previous_clang_opt): + return if previous_opt == "-emit-objc-header-path": if not opt.startswith(package_bin_dir): @@ -521,121 +435,64 @@ def _process_swiftcopts( under {}""".format(opt, package_bin_dir)) header_name = opt[len(package_bin_dir) + 1:] build_settings["SWIFT_OBJC_INTERFACE_HEADER_NAME"] = header_name - return None + return if opt.startswith("-O"): if opt != "-Onone": build_settings["SWIFT_OPTIMIZATION_LEVEL"] = opt - return None + return if build_mode == "xcode" and opt.startswith("-vfsoverlay"): fail("""\ Using VFS overlays with `build_mode = "xcode"` is unsupported. """) if opt == "-enable-testing": build_settings["ENABLE_TESTABILITY"] = True - return None + return compilation_mode = _SWIFT_COMPILATION_MODE_OPTS.get(opt, "") if compilation_mode: build_settings["SWIFT_COMPILATION_MODE"] = compilation_mode - return None + return if opt.startswith("-swift-version="): version = opt[15:] if version != "5.0": build_settings["SWIFT_VERSION"] = version - return None + return if opt == "-emit-objc-header-path": # Handled in `previous_opt` check above - return None + return if opt.startswith("-strict-concurrency="): build_settings["SWIFT_STRICT_CONCURRENCY"] = opt[20:] - return None - if opt[0] != "-" and opt.endswith(".swift"): - # These are the files to compile, not options. They are seen here - # because of the way we collect Swift compiler options. Ideally in - # the future we could collect Swift compiler options similar to how - # we collect C and C++ compiler options. - return None - - if opt == "-Xfrontend": - # We return early to prevent issues with the checks below - return opt - - # -vfsoverlay doesn't apply `-working_directory=`, so we need to - # prefix it ourselves - previous_vfsoverlay_opt = previous_frontend_opt or previous_opt - if previous_vfsoverlay_opt == "-vfsoverlay": - if opt[0] != "/": - return "$(CURRENT_EXECUTION_ROOT)/" + opt - return opt - if opt.startswith("-vfsoverlay"): - value = opt[11:] - if value and value[0] != "/": - return "-vfsoverlay$(CURRENT_EXECUTION_ROOT)/" + value - return opt - - return opt + return - processed_opts = [] has_debug_info = False - skip_next = 0 outer_previous_opt = None - outer_previous_frontend_opt = None outer_previous_clang_opt = None - for idx, outer_opt in enumerate(opts): - if skip_next: - skip_next -= 1 - continue - root_opt = outer_opt.split("=")[0] - - skip_next = _SWIFTC_SKIP_OPTS.get(root_opt, 0) - if skip_next: - skip_next -= 1 - continue - - compound_skip_next = _SWIFTC_SKIP_COMPOUND_OPTS.get(root_opt) - if compound_skip_next: - skip_next = compound_skip_next.get(opts[idx + 1], 0) - if skip_next: - # No need to decrement 1, since we need to skip the first opt - continue - + for outer_opt in opts: if outer_opt == "-g": has_debug_info = True continue - processed_opt = _inner_process_swiftcopts( + _inner_process_swiftcopts( outer_opt, outer_previous_opt, - outer_previous_frontend_opt, outer_previous_clang_opt, ) if outer_previous_opt == "-Xcc": outer_previous_clang_opt = outer_opt - outer_previous_frontend_opt = None elif outer_opt != "-Xcc": outer_previous_clang_opt = None - if outer_previous_opt == "-Xfrontend": - outer_previous_frontend_opt = outer_opt - elif outer_opt != "-Xfrontend": - outer_previous_frontend_opt = None outer_previous_opt = outer_opt - outer_opt = processed_opt - if not outer_opt: - continue - - processed_opts.append(outer_opt) + return clang_opts, has_debug_info - return processed_opts, clang_opts, has_debug_info - -def _create_cc_compile_params( +def _create_compile_params( *, actions, name, args, opt_type, - cc_compiler_params_processor): + params_processor): if not args or not actions: return None @@ -659,7 +516,7 @@ def _create_cc_compile_params( ] params = actions.declare_file( - "{}.rules_xcodeproj.{}.compile.params".format(name, opt_type), + "{}.rules_xcodeproj.{}.compile.params".format(name, opt_type.lower()), ) params_args = actions.args() @@ -667,9 +524,9 @@ def _create_cc_compile_params( params_args.add_all(sub_params) actions.run( - executable = cc_compiler_params_processor, + executable = params_processor, arguments = [params_args], - mnemonic = "ProcessCCCompileParams", + mnemonic = "Process{}CompileParams".format(opt_type), progress_message = "Generating %{output}", inputs = sub_params, outputs = [params], @@ -677,22 +534,6 @@ def _create_cc_compile_params( return params -def _create_swift_compile_params(*, actions, name, opts): - if not opts or not actions: - return None - - args = actions.args() - args.add_all(opts) - - output = actions.declare_file( - "{}.rules_xcodeproj.swift.compile.params".format(name), - ) - actions.write( - output = output, - content = args, - ) - return output - def _process_compiler_opts( *, actions, @@ -706,6 +547,8 @@ def _process_compiler_opts( cxxopts, name, package_bin_dir, + swift_args, + swift_compiler_params_processor, swiftcopts): """Processes compiler options. @@ -725,6 +568,9 @@ def _process_compiler_opts( name: The name of the target. package_bin_dir: The package directory for the target within `ctx.bin_dir`. + swift_args: An `Args` object for Swift compiler options. + swift_compiler_params_processor: The `swift_compiler_params_processor` + executable. swiftcopts: A `list` of Swift compiler options. Returns: @@ -750,7 +596,6 @@ def _process_compiler_opts( build_settings = build_settings, ) ( - swiftcopts, clang_opts, swift_has_debug_info, ) = _process_swiftcopts( @@ -770,24 +615,26 @@ def _process_compiler_opts( else: build_settings["DEBUG_INFORMATION_FORMAT"] = "" - c_params = _create_cc_compile_params( + c_params = _create_compile_params( actions = actions, name = name, args = conly_args, - opt_type = "c", - cc_compiler_params_processor = cc_compiler_params_processor, + opt_type = "C", + params_processor = cc_compiler_params_processor, ) - cxx_params = _create_cc_compile_params( + cxx_params = _create_compile_params( actions = actions, name = name, args = cxx_args, - opt_type = "cxx", - cc_compiler_params_processor = cc_compiler_params_processor, + opt_type = "CXX", + params_processor = cc_compiler_params_processor, ) - swift_params = _create_swift_compile_params( + swift_params = _create_compile_params( actions = actions, name = name, - opts = swiftcopts, + args = swift_args, + opt_type = "Swift", + params_processor = swift_compiler_params_processor, ) c_has_fortify_source = "-D_FORTIFY_SOURCE=1" in conlyopts @@ -845,6 +692,7 @@ def _process_target_compiler_opts( cxxopts, cxx_args, swiftcopts, + swift_args, ) = _get_unprocessed_compiler_opts( ctx = ctx, build_mode = build_mode, @@ -861,6 +709,7 @@ def _process_target_compiler_opts( cxxopts = cxxopts, cxx_args = cxx_args, swiftcopts = swiftcopts, + swift_args = swift_args, build_mode = build_mode, cpp_fragment = ctx.fragments.cpp, package_bin_dir = package_bin_dir, @@ -868,6 +717,9 @@ def _process_target_compiler_opts( cc_compiler_params_processor = ( ctx.executable._cc_compiler_params_processor ), + swift_compiler_params_processor = ( + ctx.executable._swift_compiler_params_processor + ), ) # Utility diff --git a/xcodeproj/internal/xcodeproj_aspect.bzl b/xcodeproj/internal/xcodeproj_aspect.bzl index a16c19355f..487d3fc0a0 100644 --- a/xcodeproj/internal/xcodeproj_aspect.bzl +++ b/xcodeproj/internal/xcodeproj_aspect.bzl @@ -97,6 +97,13 @@ def make_xcodeproj_aspect(*, build_mode, generator_name): "@bazel_tools//tools/cpp:current_cc_toolchain", )), "_generator_name": attr.string(default = generator_name), + "_swift_compiler_params_processor": attr.label( + cfg = "exec", + default = Label( + "//tools/params_processors:swift_compiler_params_processor", + ), + executable = True, + ), "_xcode_config": attr.label( default = configuration_field( name = "xcode_config_label",