Skip to main content
Glama
preprocessor.bzl20.6 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//:paths.bzl", "paths") load("@prelude//cxx:target_sdk_version.bzl", "get_target_sdk_version_flags") load( "@prelude//utils:utils.bzl", "flatten", "value_or", ) load(":attr_selection.bzl", "cxx_by_language_ext") load(":cxx_context.bzl", "get_cxx_toolchain_info") load( ":headers.bzl", "CHeader", # @unused Used as a type "CxxHeadersLayout", # @unused Used as a type "CxxHeadersNaming", "HeaderStyle", "HeadersAsRawHeadersMode", "RawHeadersAsHeadersMode", "as_headers", "as_raw_headers", "cxx_attr_exported_header_style", "cxx_attr_exported_headers", "cxx_attr_headers", "prepare_headers", ) load(":platform.bzl", "cxx_by_platform") SystemIncludeDirs = record( # Compiler type to infer correct include flags compiler_type = field(str), # Directories to be included via [-isystem | /external:I] [arglike things] include_dirs = field(list[CellPath]), ) CPreprocessorArgs = record( # The arguments, [arglike things] args = field(list[typing.Any], []), # File prefix args maps symlinks to source file location file_prefix_args = field(list[typing.Any], []), ) HeaderUnit = record( name = field(str), module = field(Artifact), include_dir = field(Artifact), import_include = field(str | None), clang_trace = field(Artifact | None), ) # Note: Any generic attributes are assumed to be relative. CPreprocessor = record( # Relative path args to be used for build operations. args = field(CPreprocessorArgs, CPreprocessorArgs()), # Header specs headers = field(list[CHeader], []), # Those should be mutually exclusive with normal headers as per documentation raw_headers = field(list[Artifact], []), # Directories to be included via -I, [arglike things] include_dirs = field(list[CellPath], []), # Directories to be included via -isystem, [arglike things] system_include_dirs = field([SystemIncludeDirs, None], None), # Whether to compile with modules support uses_modules = field(bool, False), # Modular args to set when modules are in use, [arglike things] modular_args = field(list[typing.Any], []), # Path to the modulemap which defines the API exposed to Swift modulemap_path = field([cmd_args, None], None), # Header units to load transitively and supporting args. header_units = field(list[HeaderUnit], []), ) # Methods for transitive_sets must be declared prior to their use. def _cpreprocessor_args(pres: list[CPreprocessor]): args = cmd_args() for pre in pres: args.add(pre.args.args) return args def _cpreprocessor_modular_args(pres: list[CPreprocessor]): args = cmd_args() for pre in pres: args.add(pre.modular_args) return args def _cpreprocessor_header_units_args(pres: list[CPreprocessor]): args = cmd_args() for pre in pres: for h in pre.header_units: args.add(cmd_args("-fmodule-file=", h.name, "=", h.module, delimiter = "")) args.add(cmd_args(h.include_dir, format = "-I{}")) args.add(cmd_args(h.include_dir, format = "-fmodule-map-file={}/module.modulemap")) if h.import_include: args.add(["-include", h.import_include]) return args def _cpreprocessor_file_prefix_args(pres: list[CPreprocessor]): args = cmd_args() for pre in pres: args.add(pre.args.file_prefix_args) return args def _cpreprocessor_include_dirs(pres: list[CPreprocessor]): args = cmd_args() for pre in pres: for d in pre.include_dirs: args.add(cmd_args(d, format = "-I{}")) if pre.system_include_dirs != None: for d in pre.system_include_dirs.include_dirs: system_include_args = format_system_include_arg(cmd_args(d), pre.system_include_dirs.compiler_type) args.add(system_include_args) return args def _cpreprocessor_uses_modules(children: list[bool], pres: [list[CPreprocessor], None]): if pres: for pre in pres: if pre.uses_modules: return True return any(children) # Set of [CPreprocessor]. Most nodes have just a single value, but we # allow > 1 for cxx compilation commands where it we do want > 1 (one for # exported pp info and one for not-exported). CPreprocessorTSet = transitive_set( args_projections = { "args": _cpreprocessor_args, "file_prefix_args": _cpreprocessor_file_prefix_args, "header_units_args": _cpreprocessor_header_units_args, "include_dirs": _cpreprocessor_include_dirs, "modular_args": _cpreprocessor_modular_args, }, reductions = { "uses_modules": _cpreprocessor_uses_modules, }, ) CPreprocessorInfo = provider(fields = { "set": provider_field(typing.Any, default = None), # "CPreprocessorTSet" }) # Defines the provider exposed by libraries to test targets, # so that tests can have access to the private headers of # the first order deps (for testing purposes). CPreprocessorForTestsInfo = provider( # @unsorted-dict-items fields = { # [str] - list of targets in "tests" "test_names": provider_field(typing.Any, default = None), # # CPreprocessor - the private preprocessor # for the target which is _only_ exposed to any # test targets defined in `test_names` "own_non_exported_preprocessor": provider_field(typing.Any, default = None), }, ) def cxx_attr_exported_preprocessor_flags(ctx: AnalysisContext) -> list[typing.Any]: return ( ctx.attrs.exported_preprocessor_flags + _by_language_cxx(ctx.attrs.exported_lang_preprocessor_flags) + flatten(cxx_by_platform(ctx, ctx.attrs.exported_platform_preprocessor_flags)) + flatten(cxx_by_platform(ctx, _by_language_cxx(ctx.attrs.exported_lang_platform_preprocessor_flags))) ) def cxx_inherited_preprocessor_infos(first_order_deps: list[Dependency]) -> list[CPreprocessorInfo]: # We filter out nones because some non-cxx rule without such providers could be a dependency, for example # cxx_binary "fbcode//one_world/cli/util/process_wrapper:process_wrapper" depends on # python_library "fbcode//third-party-buck/$platform/build/glibc:__project__" return filter(None, [x.get(CPreprocessorInfo) for x in first_order_deps]) def cxx_merge_cpreprocessors(ctx: AnalysisContext, own: list[CPreprocessor], xs: list[CPreprocessorInfo]) -> CPreprocessorInfo: kwargs = {"children": [x.set for x in xs]} if own: kwargs["value"] = own return CPreprocessorInfo( set = ctx.actions.tset(CPreprocessorTSet, **kwargs), ) def _format_include_arg(flag: str, path: cmd_args, compiler_type: str) -> list[cmd_args]: if compiler_type == "windows": return [cmd_args(path, format = flag + "{}")] else: return [cmd_args(flag), path] def format_system_include_arg(path: cmd_args, compiler_type: str) -> list[cmd_args]: if compiler_type == "windows": return [cmd_args(path, format = "/external:I{}")] else: return [cmd_args("-isystem"), path] def cxx_exported_preprocessor_info(ctx: AnalysisContext, headers_layout: CxxHeadersLayout, extra_preprocessors: list[CPreprocessor] = []) -> CPreprocessor: """ This rule's preprocessor info which is both applied to the compilation of its source and propagated to the compilation of dependent's sources. """ # Modular libraries will provide their exported headers via a symlink tree # using extra_preprocessors, so should not be put into a header map. if getattr(ctx.attrs, "modular", False): exported_headers = [] else: exported_headers = cxx_attr_exported_headers(ctx, headers_layout) # Add any headers passed in via constructor params for pre in extra_preprocessors: exported_headers += pre.headers exported_header_map = { paths.join(h.namespace, h.name): h.artifact for h in exported_headers } raw_headers = [] include_dirs = [] system_include_dirs = [] style = cxx_attr_exported_header_style(ctx) compiler_type = get_cxx_toolchain_info(ctx).cxx_compiler_info.compiler_type # If headers-as-raw-headers is enabled, convert exported headers to raw # headers, with the appropriate include directories. raw_headers_mode = _attr_headers_as_raw_headers_mode(ctx) inferred_inc_dirs = as_raw_headers(ctx, exported_header_map, raw_headers_mode) if inferred_inc_dirs != None: raw_headers.extend(exported_header_map.values()) if style == HeaderStyle("local"): include_dirs.extend(inferred_inc_dirs) else: system_include_dirs.extend(inferred_inc_dirs) exported_header_map.clear() # Add in raw headers and include dirs from attrs. raw_headers.extend(value_or(ctx.attrs.raw_headers, [])) # if raw-headers-as-headers is enabled, convert raw headers to headers if raw_headers and _attr_raw_headers_as_headers_mode(ctx) != RawHeadersAsHeadersMode("disabled"): exported_headers = as_headers(ctx, raw_headers, ctx.attrs.public_include_directories + ctx.attrs.public_system_include_directories) exported_header_map = { paths.join(h.namespace, h.name): h.artifact for h in exported_headers } raw_headers.clear() # Force system exported header style if any system include directories are set. if ctx.attrs.public_system_include_directories: style = HeaderStyle("system") else: include_dirs.extend([ctx.label.path.add(x) for x in ctx.attrs.public_include_directories]) system_include_dirs.extend([ctx.label.path.add(x) for x in ctx.attrs.public_system_include_directories]) args = _get_exported_preprocessor_args(ctx, exported_header_map, style, compiler_type, raw_headers, extra_preprocessors) modular_args = [] for pre in extra_preprocessors: modular_args.extend(pre.modular_args) header_units = [] for pre in extra_preprocessors: header_units.extend(pre.header_units) return CPreprocessor( args = CPreprocessorArgs(args = args.args, file_prefix_args = args.file_prefix_args), headers = exported_headers, raw_headers = raw_headers, include_dirs = include_dirs, system_include_dirs = SystemIncludeDirs(compiler_type = compiler_type, include_dirs = system_include_dirs), modular_args = modular_args, header_units = header_units, ) def _get_exported_preprocessor_args(ctx: AnalysisContext, headers: dict[str, Artifact], style: HeaderStyle, compiler_type: str, raw_headers: list[Artifact], extra_preprocessors: list[CPreprocessor]) -> CPreprocessorArgs: header_root = prepare_headers(ctx, headers, "buck-headers") # Process args to handle the `$(cxx-header-tree)` macro. args = [] for arg in cxx_attr_exported_preprocessor_flags(ctx): if _needs_cxx_header_tree_hack(arg): if header_root == None or header_root.symlink_tree == None: fail("No headers") arg = _cxx_header_tree_hack_replacement(header_root.symlink_tree) args.append(arg) # Propagate the exported header tree. file_prefix_args = [] if header_root != None: args.extend(_header_style_args(style, header_root.include_path, compiler_type)) if header_root.file_prefix_args != None: file_prefix_args.append(header_root.file_prefix_args) # Embed raw headers as hidden artifacts in our args. This means downstream # cases which use these args don't also need to know to add raw headers. if raw_headers: # NOTE(agallagher): It's a bit weird adding an "empty" arg, but this # appears to do the job (and not e.g. expand to `""`). args.append(cmd_args(hidden = raw_headers)) # Append any extra preprocessor info passed in via the constructor params for pre in extra_preprocessors: args.extend(pre.args.args) return CPreprocessorArgs(args = args, file_prefix_args = file_prefix_args) def cxx_private_preprocessor_info( ctx: AnalysisContext, headers_layout: CxxHeadersLayout, raw_headers: list[Artifact] = [], extra_preprocessors: list[CPreprocessor] = [], non_exported_deps: list[Dependency] = [], is_test: bool = False) -> (CPreprocessor, list[CPreprocessor]): private_preprocessor = _cxx_private_preprocessor_info(ctx, headers_layout, raw_headers, extra_preprocessors) test_preprocessors = [] if is_test: for non_exported_dep in non_exported_deps: preprocessor_for_tests = non_exported_dep.get(CPreprocessorForTestsInfo) if preprocessor_for_tests and ctx.label.name in preprocessor_for_tests.test_names: test_preprocessors.append(preprocessor_for_tests.own_non_exported_preprocessor) return (private_preprocessor, test_preprocessors) def _cxx_private_preprocessor_info( ctx: AnalysisContext, headers_layout: CxxHeadersLayout, raw_headers: list[Artifact], extra_preprocessors: list[CPreprocessor]) -> CPreprocessor: """ This rule's preprocessor info which is only applied to the compilation of its source, and not propagated to dependents. """ compiler_type = get_cxx_toolchain_info(ctx).cxx_compiler_info.compiler_type headers = cxx_attr_headers(ctx, headers_layout) # `apple_*` rules allow headers to be included via only a basename if those # are headers (private or exported) from the same target. if headers_layout.naming == CxxHeadersNaming("apple"): headers.extend( _remap_headers_to_basename( headers + cxx_attr_exported_headers(ctx, headers_layout), ), ) # Include any headers provided via constructor params and determine whether # to use modules. uses_modules = False for pp in extra_preprocessors: headers += pp.headers uses_modules = uses_modules or pp.uses_modules header_map = {paths.join(h.namespace, h.name): h.artifact for h in headers} all_raw_headers = [] include_dirs = [] # If headers-as-raw-headers is enabled, convert exported headers to raw # headers, with the appropriate include directories. raw_headers_mode = _attr_headers_as_raw_headers_mode(ctx) inferred_inc_dirs = as_raw_headers(ctx, header_map, raw_headers_mode) if inferred_inc_dirs != None: all_raw_headers.extend(header_map.values()) include_dirs.extend(inferred_inc_dirs) header_map.clear() # Add in raw headers and include dirs from attrs. all_raw_headers.extend(raw_headers) # if raw-headers-as-headers is enabled, convert raw headers to headers if all_raw_headers and _attr_raw_headers_as_headers_mode(ctx) != RawHeadersAsHeadersMode("disabled"): # private headers are also accessible via public_include_directories # same as exported_preprocessor_flags apply to the target itself. headers = as_headers(ctx, all_raw_headers, ctx.attrs.include_directories + getattr(ctx.attrs, "public_include_directories", []) + getattr(ctx.attrs, "public_system_include_directories", [])) header_map = { paths.join(h.namespace, h.name): h.artifact for h in headers } all_raw_headers.clear() else: include_dirs.extend([ctx.label.path.add(x) for x in ctx.attrs.include_directories]) args = _get_private_preprocessor_args(ctx, header_map, compiler_type, all_raw_headers) return CPreprocessor( args = CPreprocessorArgs(args = args.args, file_prefix_args = args.file_prefix_args), headers = headers, raw_headers = all_raw_headers, include_dirs = include_dirs, uses_modules = uses_modules, ) def _get_private_preprocessor_args(ctx: AnalysisContext, headers: dict[str, Artifact], compiler_type: str, all_raw_headers: list[Artifact]) -> CPreprocessorArgs: # Create private header tree and propagate via args. args = get_target_sdk_version_flags(ctx) file_prefix_args = [] header_root = prepare_headers(ctx, headers, "buck-private-headers") if header_root != None: args.extend(_format_include_arg("-I", header_root.include_path, compiler_type)) if header_root.file_prefix_args != None: file_prefix_args.append(header_root.file_prefix_args) # Embed raw headers as hidden artifacts in our args. This means downstream # cases which use these args don't also need to know to add raw headers. if all_raw_headers: # NOTE(agallagher): It's a bit weird adding an "empty" arg, but this # appears to do the job (and not e.g. expand to `""`). args.append(cmd_args(hidden = all_raw_headers)) return CPreprocessorArgs(args = args, file_prefix_args = file_prefix_args) def _by_language_cxx(x: dict[typing.Any, typing.Any]) -> list[typing.Any]: return cxx_by_language_ext(x, ".cpp") def _header_style_args(style: HeaderStyle, path: cmd_args, compiler_type: str) -> list[cmd_args]: if style == HeaderStyle("local"): return _format_include_arg("-I", path, compiler_type) if style == HeaderStyle("system"): return format_system_include_arg(path, compiler_type) fail("unsupported header style: {}".format(style)) def _attr_raw_headers_as_headers_mode(ctx: AnalysisContext) -> RawHeadersAsHeadersMode: """ Return the `RawHeadersAsHeadersMode` setting to use for this rule. """ mode = get_cxx_toolchain_info(ctx).raw_headers_as_headers_mode # If the platform hasn't set a raw headers translation mode, we don't do anything. if mode == None: return RawHeadersAsHeadersMode("disabled") # Otherwise use the rule-specific setting, if provided (not available on prebuilt_cxx_library). if getattr(ctx.attrs, "raw_headers_as_headers_mode", None) != None: return RawHeadersAsHeadersMode(ctx.attrs.raw_headers_as_headers_mode) # Fallback to platform default. return mode def _attr_headers_as_raw_headers_mode(ctx: AnalysisContext) -> HeadersAsRawHeadersMode: """ Return the `HeadersAsRawHeadersMode` setting to use for this rule. """ mode = get_cxx_toolchain_info(ctx).headers_as_raw_headers_mode # If the platform hasn't set a raw headers translation mode, we don't do anything. if mode == None: return HeadersAsRawHeadersMode("disabled") # Otherwise use the rule-specific setting, if provided (not available on prebuilt_cxx_library). if getattr(ctx.attrs, "headers_as_raw_headers_mode", None) != None: return HeadersAsRawHeadersMode(ctx.attrs.headers_as_raw_headers_mode) # Fallback to platform default. return mode def _needs_cxx_header_tree_hack(arg: typing.Any) -> bool: # The macro $(cxx-header-tree) is used in exactly once place, and its a place which isn't very # Buck v2 compatible. We replace $(cxx-header-tree) with HACK-CXX-HEADER-TREE at attribute time, # then here we substitute in the real header tree. return "HACK-CXX-HEADER-TREE" in repr(arg) def _cxx_header_tree_hack_replacement(header_tree: Artifact) -> cmd_args: # Unfortunately, we can't manipulate flags very precisely (for good reasons), so we rely on # knowing the form it takes. # The source is: -fmodule-map-file=$(cxx-header-tree)/module.modulemap return cmd_args(header_tree, format = "-fmodule-map-file={}/module.modulemap") # Remap the given headers to be includable via their basenames (for use with # "apple" style header naming). def _remap_headers_to_basename(headers: list[CHeader]) -> list[CHeader]: remapped_headers = [] for header in headers: if not header.named: remapped_headers.append(CHeader( artifact = header.artifact, name = paths.basename(header.name), namespace = "", named = False, )) return remapped_headers def get_flags_for_compiler_type(compiler_type: str) -> list[str]: # MSVC requires this flag to enable external headers if compiler_type in ["windows"]: return ["/experimental:external", "/external:W0"] else: return []

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