Skip to main content
Glama
swift_compilation.bzl54.8 kB
# Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under both the MIT license found in the # LICENSE-MIT file in the root directory of this source tree and the Apache # License, Version 2.0 found in the LICENSE-APACHE file in the root directory # of this source tree. load( "@prelude//:artifact_tset.bzl", "ArtifactInfoTag", "ArtifactTSet", # @unused Used as a type "make_artifact_tset", "project_artifacts", ) load("@prelude//apple:apple_error_handler.bzl", "apple_build_error_handler") load("@prelude//apple:apple_toolchain_types.bzl", "AppleToolchainInfo") load("@prelude//apple:apple_utility.bzl", "get_disable_pch_validation_flags", "get_module_name") load("@prelude//apple:modulemap.bzl", "preprocessor_info_for_modulemap") load("@prelude//apple/swift:swift_types.bzl", "SWIFTMODULE_EXTENSION", "SWIFT_EXTENSION", "SwiftMacroPlugin", "SwiftVersion", "get_implicit_framework_search_path_providers") load("@prelude//cxx:argsfiles.bzl", "CompileArgsfile", "CompileArgsfiles") load("@prelude//cxx:cxx_context.bzl", "get_cxx_platform_info", "get_cxx_toolchain_info") load("@prelude//cxx:cxx_library_utility.bzl", "cxx_use_shlib_intfs_mode") load( "@prelude//cxx:cxx_sources.bzl", "CxxSrcWithFlags", # @unused Used as a type ) load("@prelude//cxx:cxx_toolchain_types.bzl", "ShlibInterfacesMode") load("@prelude//cxx:headers.bzl", "CHeader") load( "@prelude//cxx:link_groups.bzl", "get_link_group", ) load( "@prelude//cxx:preprocessor.bzl", "CPreprocessor", "CPreprocessorArgs", "CPreprocessorInfo", # @unused Used as a type "cxx_inherited_preprocessor_infos", "cxx_merge_cpreprocessors", ) load("@prelude//cxx:target_sdk_version.bzl", "get_target_triple") load( "@prelude//linking:link_info.bzl", "LinkInfo", # @unused Used as a type "SwiftmoduleLinkable", # @unused Used as a type ) load("@prelude//utils:arglike.bzl", "ArgLike") load(":apple_sdk_modules_utility.bzl", "get_compiled_sdk_clang_deps_tset", "get_compiled_sdk_swift_deps_tset", "get_uncompiled_sdk_deps", "is_sdk_modules_provided") load( ":swift_debug_info_utils.bzl", "extract_and_merge_clang_debug_infos", "extract_and_merge_swift_debug_infos", ) load( ":swift_incremental_support.bzl", "get_incremental_object_compilation_flags", "should_build_swift_incrementally", ) load(":swift_module_map.bzl", "write_swift_module_map_with_deps") load(":swift_pcm_compilation.bzl", "compile_underlying_pcm", "get_compiled_pcm_deps_tset", "get_swift_pcm_anon_targets") load( ":swift_pcm_compilation_types.bzl", "SwiftPCMUncompiledInfo", ) load(":swift_sdk_flags.bzl", "get_sdk_flags") load(":swift_sdk_pcm_compilation.bzl", "get_swift_sdk_pcm_anon_targets") load(":swift_swiftinterface_compilation.bzl", "get_swift_interface_anon_targets") load(":swift_toolchain.bzl", "get_swift_toolchain_info") load( ":swift_toolchain_types.bzl", "SwiftCompiledModuleInfo", "SwiftCompiledModuleTset", "SwiftObjectFormat", "SwiftToolchainInfo", ) SwiftDependencyInfo = provider(fields = { "debug_info_tset": provider_field(ArtifactTSet), # Includes modules through exported_deps, used for compilation "exported_swiftmodules": provider_field(SwiftCompiledModuleTset), # Macro deps cannot be mixed with apple_library deps "is_macro": provider_field(bool), }) SwiftCompilationDatabase = record( db = field(Artifact), other_outputs = field(ArgLike), ) SwiftObjectOutput = record( object_files = field(list[Artifact]), argsfiles = field(CompileArgsfiles), output_map_artifact = field(Artifact | None), swiftdeps = field(list[Artifact]), ) SwiftLibraryForDistributionOutput = record( swiftinterface = field(Artifact), private_swiftinterface = field(Artifact), swiftdoc = field(Artifact), ) SwiftCompilationOutput = record( # The object file output from compilation. object_files = field(list[Artifact]), object_format = field(SwiftObjectFormat), # The swiftmodule file output from compilation. swiftmodule = field(Artifact), # The dependency info provider that contains the swiftmodule # search paths required for compilation and linking. dependency_info = field(SwiftDependencyInfo), # Preprocessor info required for ObjC compilation of this library. pre = field(CPreprocessor), # Exported preprocessor info required for ObjC compilation of rdeps. exported_pre = field(CPreprocessor), # Exported -Swift.h header exported_swift_header = field(Artifact), # Argsfiles used to compile object files. argsfiles = field(CompileArgsfiles), # A tset of (SDK/first-party) swiftmodule artifacts required to be linked into binary. # Different outputs of Swift compilation actions might contain duplicates of SDK swiftmodule artifacts. swift_debug_info = field(ArtifactTSet), # A tset of PCM artifacts used to compile a Swift module. clang_debug_info = field(ArtifactTSet), # Info required for `[swift-compilation-database]` subtarget. compilation_database = field(SwiftCompilationDatabase), # An artifact that represent the Swift module map for this target. output_map_artifact = field(Artifact | None), # An optional artifact of the exported symbols emitted for this module. exported_symbols = field(Artifact | None), # An optional artifact with files that support consuming the generated library with later versions of the swift compiler. swift_library_for_distribution_output = field(SwiftLibraryForDistributionOutput | None), # A list of artifacts that stores the index data index_store = field(Artifact), # A list of artifacts of the swiftdeps files produced during incremental compilation. swiftdeps = field(list[Artifact]), compiled_underlying_pcm_artifact = field(Artifact | None), ) SwiftDebugInfo = record( static = list[ArtifactTSet], shared = list[ArtifactTSet], ) _IS_USER_BUILD = True # @oss-enable # @oss-disable: # To determine whether we're running on CI or not, we expect user.sandcastle_alias to be set. # @oss-disable[end= ]: # _IS_USER_BUILD = (read_root_config("user", "sandcastle_alias", None) == None) _REQUIRED_SDK_MODULES = ["Swift", "SwiftOnoneSupport", "Darwin", "_Concurrency", "_StringProcessing"] _REQUIRED_SDK_CXX_MODULES = _REQUIRED_SDK_MODULES + ["std"] def _get_target_flags(ctx) -> list[str]: if get_cxx_platform_info(ctx).name.startswith("linux"): # Linux triples are unversioned return ["-target", "{}-unknown-linux-gnu".format(get_swift_toolchain_info(ctx).architecture)] else: return ["-target", get_target_triple(ctx)] def get_swift_framework_anonymous_targets(ctx: AnalysisContext, get_providers: typing.Callable) -> Promise: # Get SDK deps from direct dependencies, # all transitive deps will be compiled recursively. direct_uncompiled_sdk_deps = get_uncompiled_sdk_deps( ctx.attrs.sdk_modules, _REQUIRED_SDK_MODULES, get_swift_toolchain_info(ctx), ) # Recursively compiling headers of direct and transitive deps as PCM modules, # prebuilt_apple_framework rule doesn't support custom compiler_flags, so we pass only target triple. pcm_targets = get_swift_pcm_anon_targets( ctx, ctx.attrs.deps, _get_target_flags(ctx), False, # C++ Interop is disabled for now. ) # Recursively compiling SDK's Clang dependencies, # prebuilt_apple_framework rule doesn't support custom compiler_flags, so we pass only target triple. sdk_pcm_targets = get_swift_sdk_pcm_anon_targets( ctx, False, direct_uncompiled_sdk_deps, _get_target_flags(ctx), ) # Recursively compile SDK and prebuilt_apple_framework's Swift dependencies. swift_interface_anon_targets = get_swift_interface_anon_targets( ctx, direct_uncompiled_sdk_deps, ) return ctx.actions.anon_targets(pcm_targets + sdk_pcm_targets + swift_interface_anon_targets).promise.map(get_providers) def get_swift_anonymous_targets(ctx: AnalysisContext, get_apple_library_providers: typing.Callable) -> Promise: swift_cxx_flags = get_swift_cxx_flags(ctx) # Get SDK deps from direct dependencies, # all transitive deps will be compiled recursively. direct_uncompiled_sdk_deps = get_uncompiled_sdk_deps( ctx.attrs.sdk_modules, _REQUIRED_SDK_CXX_MODULES if ctx.attrs.enable_cxx_interop else _REQUIRED_SDK_MODULES, get_swift_toolchain_info(ctx), ) # Recursively compiling headers of direct and transitive deps as PCM modules, # passing apple_library's cxx flags through that must be used for all downward PCM compilations. pcm_targets = get_swift_pcm_anon_targets( ctx, ctx.attrs.deps + getattr(ctx.attrs, "exported_deps", []), swift_cxx_flags, ctx.attrs.enable_cxx_interop, ) # Recursively compiling SDK's Clang dependencies, # passing apple_library's cxx flags through that must be used for all downward PCM compilations. sdk_pcm_targets = get_swift_sdk_pcm_anon_targets( ctx, ctx.attrs.enable_cxx_interop, direct_uncompiled_sdk_deps, swift_cxx_flags, ) # Recursively compiling SDK's Swift dependencies, # passing apple_library's cxx flags through that must be used for all downward PCM compilations. swift_interface_anon_targets = get_swift_interface_anon_targets( ctx, direct_uncompiled_sdk_deps, ) return ctx.actions.anon_targets(pcm_targets + sdk_pcm_targets + swift_interface_anon_targets).promise.map(get_apple_library_providers) def get_swift_cxx_flags(ctx: AnalysisContext) -> list[str]: """Iterates through `swift_compiler_flags` and returns a list of flags that might affect Clang compilation""" gather, next = ([], False) # Each target needs to propagate the compilers target triple. # This can vary depending on the deployment target of each library. gather += _get_target_flags(ctx) for f in ctx.attrs.swift_compiler_flags: if next: gather.append("-Xcc") gather.append(str(f).replace('\"', "")) next = False elif str(f) == "\"-Xcc\"": next = True elif str(f) == "\"-warnings-as-errors\"": gather.append("-warnings-as-errors") elif str(f) == "\"-no-warnings-as-errors\"": gather.append("-no-warnings-as-errors") if ctx.attrs.enable_cxx_interop: gather += ["-cxx-interoperability-mode=default"] if ctx.attrs.swift_version != None: gather += ["-swift-version", ctx.attrs.swift_version] return gather def _get_compiled_underlying_pcm( ctx: AnalysisContext, module_name: str, module_pp_info: CPreprocessor | None, deps_providers: list, swift_cxx_flags: list[str], framework_search_paths: cmd_args) -> SwiftCompiledModuleInfo | None: underlying_swift_pcm_uncompiled_info = get_swift_pcm_uncompile_info( ctx, None, module_pp_info, ) if not underlying_swift_pcm_uncompiled_info: return None return compile_underlying_pcm( ctx, module_name, underlying_swift_pcm_uncompiled_info, deps_providers, swift_cxx_flags, framework_search_paths, ) def _should_compile_with_swift_interface(ctx): if not get_swift_toolchain_info(ctx).library_interface_uses_swiftinterface: return False if ctx.attrs._swift_enable_testing: return False return ctx.attrs.swift_interface_compilation_enabled and uses_explicit_modules(ctx) def compile_swift( ctx: AnalysisContext, srcs: list[CxxSrcWithFlags], parse_as_library: bool, deps_providers: list, module_name: str, private_module_name: str, exported_headers: list[CHeader], exported_objc_modulemap_pp_info: [CPreprocessor, None], private_objc_modulemap_pp_info: [CPreprocessor, None], framework_search_paths_flags: cmd_args, extra_search_paths_flags: list[ArgLike] = [], compile_category = "swift_compile", compile_swiftmodule_category = "swiftmodule_compile", is_macro = False) -> ([SwiftCompilationOutput, None], DefaultInfo): # If this target imports XCTest we need to pass the search path to its swiftmodule. framework_search_paths = cmd_args() framework_search_paths.add(_get_xctest_swiftmodule_search_path(ctx)) # Pass the framework search paths to the driver and clang importer. This is required # for pcm compilation, which does not pass through driver search paths. framework_search_paths.add(framework_search_paths_flags) framework_search_paths.add(cmd_args(framework_search_paths_flags, prepend = "-Xcc")) # If a target exports ObjC headers and Swift explicit modules are enabled, # we need to precompile a PCM of the underlying module and supply it to the Swift compilation. swift_cxx_flags = get_swift_cxx_flags(ctx) if uses_explicit_modules(ctx): exported_compiled_underlying_pcm = _get_compiled_underlying_pcm( ctx, module_name, exported_objc_modulemap_pp_info, deps_providers, swift_cxx_flags, framework_search_paths, ) if exported_objc_modulemap_pp_info else None private_compiled_underlying_pcm = _get_compiled_underlying_pcm( ctx, private_module_name, private_objc_modulemap_pp_info, deps_providers, swift_cxx_flags, framework_search_paths, ) if private_objc_modulemap_pp_info else None else: exported_compiled_underlying_pcm = None private_compiled_underlying_pcm = None # We always track inputs for dependency file tracking, but optionally set # the tag on the action depending on the use_depsfiles config. inputs_tag = ctx.actions.artifact_tag() shared_flags = _get_shared_flags( ctx = ctx, deps_providers = deps_providers, parse_as_library = parse_as_library, private_module = private_compiled_underlying_pcm, public_module = exported_compiled_underlying_pcm, module_name = module_name, private_modulemap_pp_info = private_objc_modulemap_pp_info, public_modulemap_pp_info = exported_objc_modulemap_pp_info, extra_search_paths_flags = extra_search_paths_flags, inputs_tag = inputs_tag, is_macro = is_macro, ) shared_flags.add(framework_search_paths) swift_interface_info = _create_swift_interface(ctx, shared_flags, module_name) if not srcs: return (None, swift_interface_info) toolchain = get_swift_toolchain_info(ctx) output_header = ctx.actions.declare_output(module_name + "-Swift.h") output_swiftmodule = ctx.actions.declare_output(module_name + SWIFTMODULE_EXTENSION) swift_framework_output = None swiftinterface_output = None if _should_compile_with_evolution(ctx): swift_framework_output = SwiftLibraryForDistributionOutput( swiftinterface = ctx.actions.declare_output(module_name + ".swiftinterface"), private_swiftinterface = ctx.actions.declare_output(module_name + ".private.swiftinterface"), swiftdoc = ctx.actions.declare_output(module_name + ".swiftdoc"), #this is generated automatically once we pass -emit-module-info, so must have this name ) elif _should_compile_with_swift_interface(ctx): swiftinterface_output = ctx.actions.declare_output(get_module_name(ctx) + ".swiftinterface") output_symbols = None if cxx_use_shlib_intfs_mode(ctx, ShlibInterfacesMode("stub_from_headers")): output_symbols = ctx.actions.declare_output("__tbd__/" + module_name + ".swift_symbols.txt") # When compiling with WMO (ie, not incrementally), we compile the # swiftmodule separately. In incremental mode, we generate the swiftmodule # as part of the compile action to make use of incrementality. if not should_build_swift_incrementally(ctx): _compile_swiftmodule( ctx, toolchain, shared_flags, srcs, swiftinterface_output, output_swiftmodule, output_header, output_symbols, swift_framework_output, inputs_tag, compile_swiftmodule_category, ) object_output = _compile_object( ctx = ctx, toolchain = toolchain, shared_flags = shared_flags, srcs = srcs, output_swiftmodule = output_swiftmodule, output_header = output_header, inputs_tag = inputs_tag, category = compile_category, ) index_store = _compile_index_store(ctx, toolchain, shared_flags, srcs) # Swift libraries extend the ObjC modulemaps to include the -Swift.h header modulemap_pp_info = preprocessor_info_for_modulemap( ctx, name = "swift-extended", module_name = module_name, headers = exported_headers, swift_header = output_header, mark_headers_private = False, additional_args = None, ) exported_swift_header = CHeader( artifact = output_header, name = output_header.basename, namespace = module_name, named = False, ) exported_pp_info = CPreprocessor( headers = [exported_swift_header], modular_args = modulemap_pp_info.modular_args, args = CPreprocessorArgs(args = modulemap_pp_info.args.args), modulemap_path = modulemap_pp_info.modulemap_path, ) # We also need to include the unprefixed -Swift.h header in this libraries preprocessor info swift_header = CHeader( artifact = output_header, name = output_header.basename, namespace = "", named = False, ) pre = CPreprocessor(headers = [swift_header]) # Pass up the swiftmodule paths for this module and its exported_deps return (SwiftCompilationOutput( output_map_artifact = object_output.output_map_artifact, object_files = object_output.object_files, object_format = toolchain.object_format, swiftmodule = output_swiftmodule, compiled_underlying_pcm_artifact = exported_compiled_underlying_pcm.output_artifact if exported_compiled_underlying_pcm else None, dependency_info = get_swift_dependency_info(ctx, output_swiftmodule, swiftinterface_output, deps_providers, is_macro), pre = pre, exported_pre = exported_pp_info, exported_swift_header = exported_swift_header.artifact, argsfiles = object_output.argsfiles, swift_debug_info = extract_and_merge_swift_debug_infos(ctx, deps_providers, [output_swiftmodule]), clang_debug_info = extract_and_merge_clang_debug_infos( ctx, deps_providers, filter( None, [ exported_compiled_underlying_pcm.output_artifact if exported_compiled_underlying_pcm else None, private_compiled_underlying_pcm.output_artifact if private_compiled_underlying_pcm else None, ], ), ), compilation_database = _create_compilation_database(ctx, srcs, object_output.argsfiles.relative[SWIFT_EXTENSION]), exported_symbols = output_symbols, swift_library_for_distribution_output = swift_framework_output, index_store = index_store, swiftdeps = object_output.swiftdeps, ), swift_interface_info) # We use separate actions for swiftmodule and object file output. This # improves build parallelism at the cost of duplicated work, but by disabling # type checking in function bodies the swiftmodule compilation can be done much # faster than object file output. def _compile_swiftmodule( ctx: AnalysisContext, toolchain: SwiftToolchainInfo, shared_flags: cmd_args, srcs: list[CxxSrcWithFlags], output_swiftinterface: Artifact | None, output_swiftmodule: Artifact, output_header: Artifact, output_symbols: Artifact | None, swift_framework_output: SwiftLibraryForDistributionOutput | None, inputs_tag: ArtifactTag, category: str) -> CompileArgsfiles: if output_swiftinterface: # We compile the interface in two passes: # 1. generate the ObjC header and swiftinterface file # 2. generate the swiftmodule file from the intermediate swiftinterface file # # This is more work overall, but as the swiftinterface file only contains # the public API we should have improved cache hit reducing the amount of # subsequent swiftmodule compile actions. swiftinterface_argsfile = cmd_args(shared_flags) swiftinterface_argsfile.add([ # Required as emitting a swiftinterface without library evolution # produces a warning. "-no-warnings-as-errors", # Workaround to avoid producing swiftdoc and other auxiliary outputs. "-typecheck", "-wmo", ]) swiftinterface_cmd = cmd_args([ "-emit-objc-header", "-emit-objc-header-path", output_header.as_output(), "-emit-module-interface", "-emit-module-interface-path", output_swiftinterface.as_output(), "-remove-module-prefixes", ]) _compile_with_argsfile(ctx, "emit_swiftinterface", ".swiftinterface", swiftinterface_argsfile, srcs, swiftinterface_cmd, toolchain, num_threads = 1) argfile_cmd = cmd_args(shared_flags) argfile_cmd.add([ "-disable-cmo", "-wmo", ]) cmd = cmd_args([ "-c", "-Xfrontend", "-compile-module-from-interface", output_swiftinterface, # The new driver will fail with "error: no input files" # so use the legacy driver until we add functionality for this. "-disallow-use-new-driver", "-o", output_swiftmodule.as_output(), ]) # We don't need the Swift srcs to compile a swiftmodule # from a generated swiftinterface file. srcs = [] else: argfile_cmd = cmd_args(shared_flags) argfile_cmd.add([ "-disable-cmo", "-wmo", ]) if ctx.attrs.swift_module_skip_function_bodies: argfile_cmd.add([ "-Xfrontend", "-experimental-skip-non-inlinable-function-bodies-without-types", ]) cmd = cmd_args([ "-emit-objc-header", "-emit-objc-header-path", output_header.as_output(), "-emit-module", "-emit-module-path", output_swiftmodule.as_output(), ]) if swift_framework_output: argfile_cmd.add([ "-enable-library-evolution", ]) cmd.add([ "-emit-module-interface", "-emit-module-interface-path", swift_framework_output.swiftinterface.as_output(), "-emit-private-module-interface-path", swift_framework_output.private_swiftinterface.as_output(), # Module verification fails here as the clang modules # are not loaded correctly. "-no-verify-emitted-module-interface", ]) # There is no driver flag to specify the swiftdoc output path # TODO: use an output file map for this. cmd.add(cmd_args(hidden = swift_framework_output.swiftdoc.as_output())) output_tbd = None if output_symbols != None: # Two step process, first we need to emit the TBD output_tbd = ctx.actions.declare_output("__tbd__/" + ctx.attrs.name + "-Swift.tbd") cmd.add([ "-emit-tbd", "-emit-tbd-path", output_tbd.as_output(), ]) dep_files = {} if toolchain.use_depsfiles and not output_swiftinterface: # Dependency file output paths are only specifiable via output file maps. dep_file = ctx.actions.declare_output("__depfiles__/" + ctx.attrs.name + "-swiftmodule.d").as_output() tagged_dep_file = inputs_tag.tag_artifacts(dep_file) output_file_map = { "": { "dependencies": cmd_args(tagged_dep_file, delimiter = ""), "emit-module-dependencies": cmd_args(tagged_dep_file, delimiter = ""), }, } output_file_map_json = ctx.actions.write_json( ctx.attrs.name + "_swiftmodule_output_file_map.json", output_file_map, pretty = True, ) cmd.add([ "-emit-dependencies", "-output-file-map", cmd_args(output_file_map_json, hidden = [tagged_dep_file]), ]) dep_files["swiftmodule"] = inputs_tag ret = _compile_with_argsfile( ctx = ctx, category_prefix = category, extension = SWIFTMODULE_EXTENSION, shared_flags = argfile_cmd, srcs = srcs, additional_flags = cmd, toolchain = toolchain, num_threads = 1, dep_files = dep_files, ) if output_tbd != None: # Now we have run the TBD action we need to extract the symbols extract_cmd = cmd_args([ get_cxx_toolchain_info(ctx).linker_info.mk_shlib_intf[RunInfo], "extract", "-o", output_symbols.as_output(), "--tbd", output_tbd, ]) ctx.actions.run(extract_cmd, category = "extract_tbd_symbols", error_handler = apple_build_error_handler) return ret def _compile_object( ctx: AnalysisContext, toolchain: SwiftToolchainInfo, shared_flags: cmd_args, srcs: list[CxxSrcWithFlags], output_swiftmodule: Artifact, output_header: Artifact, inputs_tag: ArtifactTag, category: str) -> SwiftObjectOutput: dep_files = {} if should_build_swift_incrementally(ctx): incremental_compilation_output = get_incremental_object_compilation_flags(ctx, srcs, output_swiftmodule, output_header) num_threads = incremental_compilation_output.num_threads output_map_artifact = incremental_compilation_output.output_map_artifact objects = incremental_compilation_output.artifacts cmd = incremental_compilation_output.incremental_flags_cmd swiftdeps = incremental_compilation_output.swiftdeps # TODO: add .d file support for incremental compilation, at a minimum # for the swiftmodule output. else: num_threads = 1 output_map_artifact = None swiftdeps = [] output_object = ctx.actions.declare_output(get_module_name(ctx) + ".o") objects = [output_object] object_format = toolchain.object_format.value embed_bitcode = False if toolchain.object_format == SwiftObjectFormat("object-embed-bitcode"): object_format = "object" embed_bitcode = True cmd = cmd_args([ "-emit-{}".format(object_format), "-o", output_object.as_output(), "-wmo", ]) if embed_bitcode: cmd.add("--embed-bitcode") if toolchain.use_depsfiles: dep_file = ctx.actions.declare_output("__depfiles__/" + ctx.attrs.name + "-object.d").as_output() tagged_dep_file = inputs_tag.tag_artifacts(dep_file) output_file_map = { "": { "dependencies": cmd_args(tagged_dep_file, delimiter = ""), "emit-module-dependencies": cmd_args(tagged_dep_file, delimiter = ""), }, } output_file_map_json = ctx.actions.write_json( ctx.attrs.name + "_swift_output_file_map.json", output_file_map, pretty = True, ) cmd.add([ "-emit-dependencies", "-output-file-map", cmd_args(output_file_map_json, hidden = [tagged_dep_file]), ]) dep_files["object"] = inputs_tag if _should_compile_with_evolution(ctx): cmd.add(["-enable-library-evolution"]) argsfiles = _compile_with_argsfile( ctx = ctx, category_prefix = category, extension = SWIFT_EXTENSION, shared_flags = shared_flags, srcs = srcs, additional_flags = cmd, toolchain = toolchain, num_threads = num_threads, dep_files = dep_files, ) return SwiftObjectOutput( object_files = objects, argsfiles = argsfiles, output_map_artifact = output_map_artifact, swiftdeps = swiftdeps, ) def _compile_index_store( ctx: AnalysisContext, toolchain: SwiftToolchainInfo, shared_flags: cmd_args, srcs: list[CxxSrcWithFlags]) -> Artifact: module_name = get_module_name(ctx) # We need an output file map with index-unit-output-path entries to be able # to index all of the srcs in a single pass. output_file_map = {} for src in srcs: output_file_map[src.file] = { # The output here is only used for the identifier of the index unit file "index-unit-output-path": src.file, "object": "/dev/null", } output_file_map_json = ctx.actions.write_json("__indexstore__/{}_output_file_map.json".format(module_name), output_file_map) index_store_output = ctx.actions.declare_output("__indexstore__/swift_{}".format(module_name), dir = True) additional_flags = cmd_args([ "-output-file-map", output_file_map_json, "-index-ignore-system-modules", "-index-store-path", index_store_output.as_output(), "-c", "-disable-batch-mode", "-disallow-use-new-driver", "-ignore-errors", ]) _compile_with_argsfile( ctx, "swift_index_compile", module_name, shared_flags, srcs, additional_flags, toolchain, module_name, cacheable = False, ) return index_store_output def _compile_with_argsfile( ctx: AnalysisContext, category_prefix: str, extension: str, shared_flags: cmd_args, srcs: list[CxxSrcWithFlags], additional_flags: cmd_args, toolchain: SwiftToolchainInfo, identifier: str | None = None, num_threads: int = 1, cacheable: bool = True, dep_files: dict[str, ArtifactTag] = {}) -> CompileArgsfiles: cmd = cmd_args(toolchain.compiler) cmd.add(additional_flags) # Assemble argsfile with compiler flags. We don't use `with_inputs` in the # write action as this strips tagged values and breaks dependency file # input tracking. shell_quoted_args = cmd_args(shared_flags, quote = "shell") argsfile, _ = ctx.actions.write(extension + "_compile_argsfile", shell_quoted_args, allow_args = True) argsfile_cmd_form = cmd_args(argsfile, format = "@{}", delimiter = "", hidden = shared_flags) cmd.add(argsfile_cmd_form) # Assemble argsfile with Swift source files. swift_quoted_files = cmd_args([s.file for s in srcs], quote = "shell") swift_files, _ = ctx.actions.write(extension + "_files", swift_quoted_files, allow_args = True) swift_files_cmd_form = cmd_args(swift_files, format = "@{}", delimiter = "", hidden = swift_quoted_files) cmd.add(swift_files_cmd_form) build_swift_incrementally = should_build_swift_incrementally(ctx) explicit_modules_enabled = uses_explicit_modules(ctx) # If we prefer to execute locally (e.g., for perf reasons), ensure we upload to the cache, # so that CI builds populate caches used by developer machines. allow_cache_upload = True local_only = False # Swift compilation on RE without explicit modules is impractically expensive # because there's no shared module cache across different libraries. prefer_local = not explicit_modules_enabled if (not cacheable) or (build_swift_incrementally and not toolchain.supports_relative_resource_dir): # When Swift code is built incrementally, the swift-driver embeds absolute paths into # the artifacts without relative resource dir support. In this case we can only build locally. allow_cache_upload = False local_only = True prefer_local = False elif build_swift_incrementally: # Swift incremental compilation requires the swiftdep files which are only present when # compiling locally. Prefer local unless otherwise overridden. prefer_local = True # Make it easier to debug whether Swift actions get compiled with explicit modules or not category = category_prefix + ("_with_explicit_mods" if explicit_modules_enabled else "") ctx.actions.run( cmd, category = category, identifier = identifier, # When building incrementally, we need to preserve local state between invocations. no_outputs_cleanup = build_swift_incrementally, error_handler = apple_build_error_handler, weight = num_threads, allow_cache_upload = allow_cache_upload, local_only = local_only, prefer_local = prefer_local, dep_files = dep_files, allow_dep_file_cache_upload = True, # Swift compiler requires unique inodes for all input files. unique_input_inodes = True, ) argsfile = CompileArgsfile( file = argsfile, cmd_form = argsfile_cmd_form, input_args = [shared_flags], args = shell_quoted_args, args_without_file_prefix_args = shared_flags, ) # Swift correctly handles relative paths and we can utilize the relative argsfile for Xcode. return CompileArgsfiles(relative = {extension: argsfile}, xcode = {extension: argsfile}) def _get_serialize_debugging_options(ctx: AnalysisContext, uses_explicit_modules: bool): if ctx.attrs.serialize_debugging_options == False: return False if uses_explicit_modules: # If the toolchain supports explicit modules we can # enable, regardless if this is a mixed library or not return get_swift_toolchain_info(ctx).supports_explicit_module_debug_serialization return True def _get_shared_flags( ctx: AnalysisContext, deps_providers: list, parse_as_library: bool, private_module: SwiftCompiledModuleInfo | None, public_module: SwiftCompiledModuleInfo | None, module_name: str, private_modulemap_pp_info: CPreprocessor | None, public_modulemap_pp_info: CPreprocessor | None, extra_search_paths_flags: list[ArgLike], inputs_tag: ArtifactTag, is_macro: bool) -> cmd_args: toolchain = get_swift_toolchain_info(ctx) cmd = cmd_args() if not toolchain.supports_relative_resource_dir: # Setting this to empty will get the driver to make all paths absolute when # passed to the frontend. We later debug prefix these to ensure relative paths # in the debug info. cmd.add(["-working-directory="]) cmd.add(get_sdk_flags(ctx)) cmd.add(_get_target_flags(ctx)) cmd.add([ # Always use color, consistent with clang. "-color-diagnostics", # Unset the working directory in the debug information. "-file-compilation-dir", ".", "-module-name", module_name, "-Xfrontend", "-enable-cross-import-overlays", "-Xfrontend", "-disable-cxx-interop-requirement-at-import", "-Xfrontend", "-emit-clang-header-nonmodular-includes", # RE actions are run in a sandbox, which will fail to run macros as # you cannot nest sandbox actions. # https://github.com/swiftlang/swift/pull/70079 "-disable-sandbox", ]) if parse_as_library: cmd.add([ "-parse-as-library", ]) if ctx.attrs.swift_package_name != None: cmd.add([ "-package-name", ctx.attrs.swift_package_name, ]) explicit_modules_enabled = uses_explicit_modules(ctx) if explicit_modules_enabled: cmd.add([ "-Xcc", "-Xclang", "-Xcc", # We set -fmodule-file-home-is-cwd as this is used to correctly # set the working directory of modules when generating debug info. "-fmodule-file-home-is-cwd", "-Xcc", "-Xclang", "-Xcc", # This is the default for compilation, but not in sourcekitd. # Set it explicitly here so that indexing will not fail with # invalid module format errors. "-fmodule-format=obj", ]) cmd.add(get_disable_pch_validation_flags()) if toolchain.resource_dir: cmd.add([ "-resource-dir", toolchain.resource_dir, ]) if ctx.attrs.swift_version: cmd.add(["-swift-version", ctx.attrs.swift_version]) swift_version = ctx.attrs.swift_version else: # Swift compiler defaults to 5 and therefore so do we # use the version 5 for upcoming features passed to tools # like the ide-tool for swift-interface generation below # include/swift/Basic/LangOptions.h?lines=175-176 swift_version = SwiftVersion[0] # "5" if ctx.attrs.enable_cxx_interop: cmd.add(["-cxx-interoperability-mode=default"]) if _get_serialize_debugging_options(ctx, explicit_modules_enabled): cmd.add([ "-Xfrontend", "-serialize-debugging-options", "-Xfrontend", "-prefix-serialized-debugging-options", ]) else: cmd.add([ "-Xfrontend", "-no-serialize-debugging-options", ]) upcoming_features = toolchain.swift_upcoming_features[swift_version] experimental_features = toolchain.swift_experimental_features[swift_version] for feature in upcoming_features: cmd.add([ "-Xfrontend", "-enable-upcoming-feature", "-Xfrontend", feature, ]) for feature in experimental_features: cmd.add([ "-Xfrontend", "-enable-experimental-feature", "-Xfrontend", feature, ]) if ctx.attrs._swift_enable_testing: cmd.add("-enable-testing") if getattr(ctx.attrs, "application_extension", False): cmd.add("-application-extension") # Only apple_library has swift_macro_deps swift_macros = getattr(ctx.attrs, "swift_macro_deps", []) if swift_macros: for m in ctx.plugins[SwiftMacroPlugin]: macro_artifact = m[DefaultInfo].default_outputs[0] cmd.add([ "-load-plugin-executable", cmd_args(inputs_tag.tag_artifacts(macro_artifact), format = "{}#" + macro_artifact.basename), ]) pcm_deps_tset = get_compiled_pcm_deps_tset(ctx, deps_providers) # If Swift Explicit Modules are enabled, a few things must be provided to a compilation job: # 1. Direct and transitive SDK deps from `sdk_modules` attribute. # 2. Direct and transitive user-defined deps. # 3. Transitive SDK deps of user-defined deps. # (This is the case, when a user-defined dep exports a type from SDK module, # thus such SDK module should be implicitly visible to consumers of that custom dep) if uses_explicit_modules(ctx): sdk_clang_deps_tset = get_compiled_sdk_clang_deps_tset(ctx, deps_providers) sdk_swift_deps_tset = get_compiled_sdk_swift_deps_tset(ctx, deps_providers) _add_swift_module_map_args( ctx = ctx, sdk_swiftmodule_deps_tset = sdk_swift_deps_tset, pcm_deps_tset = pcm_deps_tset, sdk_deps_tset = sdk_clang_deps_tset, inputs_tag = inputs_tag, cmd = cmd, is_macro = is_macro, ) _add_clang_deps_flags(ctx, pcm_deps_tset, cmd, inputs_tag) _add_swift_deps_flags(ctx, cmd, is_macro) # Add flags for importing the ObjC part of this library _add_mixed_library_flags_to_cmd( ctx, cmd, private_module, public_module, private_modulemap_pp_info, public_modulemap_pp_info, ) # Add toolchain and target flags last to allow for overriding defaults cmd.add(toolchain.compiler_flags) cmd.add(ctx.attrs.swift_compiler_flags) cmd.add(extra_search_paths_flags) return cmd def _add_swift_module_map_args( ctx: AnalysisContext, sdk_swiftmodule_deps_tset: SwiftCompiledModuleTset, pcm_deps_tset: SwiftCompiledModuleTset, sdk_deps_tset: SwiftCompiledModuleTset, inputs_tag: ArtifactTag, cmd: cmd_args, is_macro: bool): module_name = get_module_name(ctx) sdk_swiftmodule_deps_tset = [sdk_swiftmodule_deps_tset] if sdk_swiftmodule_deps_tset else [] all_deps_tset = ctx.actions.tset( SwiftCompiledModuleTset, children = _get_swift_paths_tsets(is_macro, ctx.attrs.deps + getattr(ctx.attrs, "exported_deps", [])) + [pcm_deps_tset, sdk_deps_tset] + sdk_swiftmodule_deps_tset, ) swift_module_map_artifact = write_swift_module_map_with_deps( ctx, module_name, all_deps_tset, ) cmd.add([ "-Xfrontend", "-explicit-swift-module-map-file", "-Xfrontend", inputs_tag.tag_artifacts(swift_module_map_artifact), ]) def _add_swift_deps_flags( ctx: AnalysisContext, cmd: cmd_args, is_macro: bool): if uses_explicit_modules(ctx): cmd.add([ "-Xcc", "-fno-implicit-modules", "-Xcc", "-fno-implicit-module-maps", "-Xfrontend", "-disable-implicit-swift-modules", ]) else: depset = ctx.actions.tset(SwiftCompiledModuleTset, children = _get_swift_paths_tsets(is_macro, ctx.attrs.deps + getattr(ctx.attrs, "exported_deps", []))) cmd.add(depset.project_as_args("module_search_path")) implicit_search_path_tset = get_implicit_framework_search_path_providers( ctx, None, ctx.attrs.deps, ) cmd.add(implicit_search_path_tset.project_as_args("swift_framework_implicit_search_paths_args")) def _add_clang_deps_flags( ctx: AnalysisContext, pcm_deps_tset: SwiftCompiledModuleTset, cmd: cmd_args, inputs_tag: ArtifactTag) -> None: if uses_explicit_modules(ctx): clang_flags = pcm_deps_tset.project_as_args("clang_importer_flags") cmd.add(inputs_tag.tag_artifacts(clang_flags)) else: inherited_preprocessor_infos = cxx_inherited_preprocessor_infos(ctx.attrs.deps + getattr(ctx.attrs, "exported_deps", [])) preprocessors = cxx_merge_cpreprocessors(ctx, [], inherited_preprocessor_infos) cmd.add(cmd_args(preprocessors.set.project_as_args("args"), prepend = "-Xcc")) cmd.add(cmd_args(preprocessors.set.project_as_args("modular_args"), prepend = "-Xcc")) cmd.add(cmd_args(preprocessors.set.project_as_args("include_dirs"), prepend = "-Xcc")) def _add_mixed_library_flags_to_cmd( ctx: AnalysisContext, cmd: cmd_args, private_module: SwiftCompiledModuleInfo | None, underlying_module: SwiftCompiledModuleInfo | None, private_modulemap_pp_info: CPreprocessor | None, public_modulemap_pp_info: CPreprocessor | None) -> None: if uses_explicit_modules(ctx): if private_module: cmd.add(private_module.clang_importer_args) cmd.add(private_module.clang_module_file_args) if underlying_module: cmd.add(underlying_module.clang_importer_args) cmd.add(underlying_module.clang_module_file_args) cmd.add("-import-underlying-module") return for objc_modulemap_pp_info in filter(None, [private_modulemap_pp_info, public_modulemap_pp_info]): # TODO(T99100029): We cannot use VFS overlays to mask this import from # the debugger as they require absolute paths. Instead we will enforce # that mixed libraries do not have serialized debugging info and rely on # rdeps to serialize the correct paths. for arg in objc_modulemap_pp_info.args.args: cmd.add("-Xcc") cmd.add(arg) for arg in objc_modulemap_pp_info.modular_args: cmd.add("-Xcc") cmd.add(arg) if public_modulemap_pp_info: cmd.add("-import-underlying-module") def _get_swift_dependency_info(is_macro: bool, deps: list[Dependency]) -> list[SwiftDependencyInfo]: ret = [] for d in deps: if SwiftDependencyInfo in d: if d[SwiftDependencyInfo].is_macro != is_macro: fail("Cannot have a {} dep in a {} target".format( "macro" if d[SwiftDependencyInfo].is_macro else "non-macro", "macro" if is_macro else "non-macro", )) ret.append(d[SwiftDependencyInfo]) return ret def _get_swift_paths_tsets(is_macro: bool, deps: list[Dependency]) -> list[SwiftCompiledModuleTset]: return [d.exported_swiftmodules for d in _get_swift_dependency_info(is_macro, deps)] def get_external_debug_info_tsets(is_macro: bool, deps: list[Dependency]) -> list[ArtifactTSet]: return [d.debug_info_tset for d in _get_swift_dependency_info(is_macro, deps)] def get_swift_pcm_uncompile_info( ctx: AnalysisContext, propagated_exported_preprocessor_info: [CPreprocessorInfo, None], exported_pre: [CPreprocessor, None]) -> [SwiftPCMUncompiledInfo, None]: swift_toolchain = get_swift_toolchain_info(ctx) if is_sdk_modules_provided(swift_toolchain): propagated_pp_args_cmd = cmd_args(propagated_exported_preprocessor_info.set.project_as_args("args"), prepend = "-Xcc") if propagated_exported_preprocessor_info else None return SwiftPCMUncompiledInfo( name = get_module_name(ctx), is_transient = not ctx.attrs.modular or not exported_pre, exported_preprocessor = exported_pre, exported_deps = _exported_deps(ctx), propagated_preprocessor_args_cmd = propagated_pp_args_cmd, uncompiled_sdk_modules = ctx.attrs.sdk_modules, ) return None def create_swift_dependency_info( ctx: AnalysisContext, deps, deps_providers: list, compiled_info: [SwiftCompiledModuleInfo, None], debug_info_tset: ArtifactTSet, is_macro: bool): # We pass through the SDK swiftmodules here to match Buck 1 behaviour. This is # pretty loose, but it matches Buck 1 behavior so cannot be improved until # migration is complete. transitive_swiftmodule_deps = _get_swift_paths_tsets(is_macro, deps) + [get_compiled_sdk_swift_deps_tset(ctx, deps_providers)] if compiled_info: exported_swiftmodules = ctx.actions.tset(SwiftCompiledModuleTset, value = compiled_info, children = transitive_swiftmodule_deps) else: exported_swiftmodules = ctx.actions.tset(SwiftCompiledModuleTset, children = transitive_swiftmodule_deps) return SwiftDependencyInfo( debug_info_tset = debug_info_tset, exported_swiftmodules = exported_swiftmodules, is_macro = is_macro, ) def get_swift_dependency_info( ctx: AnalysisContext, output_module: Artifact | None, output_interface: Artifact | None, deps_providers: list, is_macro: bool) -> SwiftDependencyInfo: exported_deps = _exported_deps(ctx) if output_module: compiled_info = SwiftCompiledModuleInfo( is_framework = False, is_sdk_module = False, is_swiftmodule = True, module_name = get_module_name(ctx), output_artifact = output_module, interface_artifact = output_interface, ) else: compiled_info = None debug_info_tset = make_artifact_tset( actions = ctx.actions, artifacts = filter(None, [output_module, output_interface]), children = get_external_debug_info_tsets(is_macro, ctx.attrs.deps + getattr(ctx.attrs, "exported_deps", [])), label = ctx.label, tags = [ArtifactInfoTag("swiftmodule")], ) return create_swift_dependency_info( ctx, exported_deps, deps_providers, compiled_info, debug_info_tset, is_macro, ) def uses_explicit_modules(ctx: AnalysisContext) -> bool: swift_toolchain = get_swift_toolchain_info(ctx) return ctx.attrs.uses_explicit_modules and is_sdk_modules_provided(swift_toolchain) def get_swiftmodule_linkable(swift_compile_output: [SwiftCompilationOutput, None]) -> [SwiftmoduleLinkable, None]: return SwiftmoduleLinkable(swiftmodules = swift_compile_output.swift_debug_info) if swift_compile_output else None def extract_swiftmodule_linkables(link_infos: [list[LinkInfo], None]) -> list[SwiftmoduleLinkable]: linkables = [] for info in link_infos: for linkable in info.linkables: if isinstance(linkable, SwiftmoduleLinkable): linkables.append(linkable) return linkables def get_swiftmodule_linker_flags(ctx: AnalysisContext, swiftmodule_linkable: [SwiftmoduleLinkable, None]) -> cmd_args: if swiftmodule_linkable: tset = swiftmodule_linkable.swiftmodules artifacts = project_artifacts( actions = ctx.actions, tsets = [tset], ) return cmd_args([cmd_args(swiftmodule, format = "-Wl,-add_ast_path,{}") for swiftmodule in artifacts]) return cmd_args() def _get_xctest_swiftmodule_search_path(ctx: AnalysisContext) -> cmd_args: # With explicit modules we don't need to search at all. if uses_explicit_modules(ctx): return cmd_args() for fw in ctx.attrs.frameworks: if fw.endswith("XCTest.framework"): toolchain = ctx.attrs._apple_toolchain[AppleToolchainInfo] return cmd_args(toolchain.platform_path, format = "-I{}/Developer/usr/lib") return cmd_args() def get_swift_debug_infos( ctx: AnalysisContext, swift_dependency_info: [SwiftDependencyInfo, None], swift_output: [SwiftCompilationOutput, None]) -> SwiftDebugInfo: # When determining the debug info for shared libraries, if the shared library is a link group, we rely on the link group links to # obtain the debug info for linked libraries and only need to provide any swift debug info for this library itself. Otherwise # if linking standard shared, we need to obtain the transitive debug info. if get_link_group(ctx): swift_shared_debug_info = [] else: swift_shared_debug_info = _get_swift_shared_debug_info(swift_dependency_info) if swift_dependency_info else [] clang_debug_info = [swift_output.clang_debug_info] if swift_output else [] swift_debug_info = [swift_output.swift_debug_info] if swift_output else [] return SwiftDebugInfo( static = clang_debug_info + swift_debug_info, shared = swift_shared_debug_info + clang_debug_info + swift_debug_info, ) def _get_swift_shared_debug_info(swift_dependency_info: SwiftDependencyInfo) -> list[ArtifactTSet]: return [swift_dependency_info.debug_info_tset] if swift_dependency_info.debug_info_tset else [] def _get_project_root_file(ctx) -> Artifact: content = cmd_args(ctx.label.project_root) return ctx.actions.write("project_root_file", content, absolute = True) def _create_compilation_database( ctx: AnalysisContext, srcs: list[CxxSrcWithFlags], argfile: CompileArgsfile) -> SwiftCompilationDatabase: module_name = get_module_name(ctx) swift_toolchain = get_swift_toolchain_info(ctx) mk_comp_db = swift_toolchain.mk_swift_comp_db identifier = module_name + ".swift_comp_db.json" cdb_artifact = ctx.actions.declare_output(identifier) cmd = cmd_args(mk_comp_db) cmd.add(cmd_args(cdb_artifact.as_output(), format = "--output={}")) cmd.add(cmd_args(_get_project_root_file(ctx), format = "--project-root-file={}")) cmd.add(["--files"] + [s.file for s in srcs]) cmd.add("--") cmd.add(argfile.cmd_form) cmd.add([s.file for s in srcs]) ctx.actions.run( cmd, category = "swift_compilation_database", identifier = identifier, error_handler = apple_build_error_handler, ) return SwiftCompilationDatabase(db = cdb_artifact, other_outputs = argfile.cmd_form) def _create_swift_interface(ctx: AnalysisContext, shared_flags: cmd_args, module_name: str) -> DefaultInfo: swift_toolchain = get_swift_toolchain_info(ctx) swift_ide_test_tool = swift_toolchain.swift_ide_test_tool if not swift_ide_test_tool: return DefaultInfo() mk_swift_interface = swift_toolchain.mk_swift_interface identifier = module_name + ".swift_interface" argsfile, _ = ctx.actions.write( identifier + "_argsfile", shared_flags, allow_args = True, ) interface_artifact = ctx.actions.declare_output(identifier) mk_swift_args = cmd_args( mk_swift_interface, "--swift-ide-test-tool", swift_ide_test_tool, "--module", module_name, "--out", interface_artifact.as_output(), "--", cmd_args(cmd_args(argsfile, format = "@{}", delimiter = ""), hidden = [shared_flags]), ) ctx.actions.run( mk_swift_args, category = "mk_swift_interface", identifier = identifier, error_handler = apple_build_error_handler, ) return DefaultInfo( default_output = interface_artifact, other_outputs = [ argsfile, ], ) def _exported_deps(ctx) -> list[Dependency]: if ctx.attrs.reexport_all_header_dependencies: return getattr(ctx.attrs, "exported_deps", []) + ctx.attrs.deps else: return getattr(ctx.attrs, "exported_deps", []) def _should_compile_with_evolution(ctx) -> bool: if ctx.attrs.enable_library_evolution != None: return ctx.attrs.enable_library_evolution return ctx.attrs._enable_library_evolution

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server