Vendor dependencies
Let's see how I like this workflow.
This commit is contained in:
parent
34d1830413
commit
9c435dc440
7500 changed files with 1665121 additions and 99 deletions
106
vendor/cxx/tools/buck/prelude/cxx/archive.bzl
vendored
Normal file
106
vendor/cxx/tools/buck/prelude/cxx/archive.bzl
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# 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//linking:link_info.bzl", "Archive")
|
||||
load("@prelude//utils:utils.bzl", "value_or")
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
|
||||
def _archive_flags(
|
||||
archiver_type: str.type,
|
||||
linker_type: str.type,
|
||||
use_archiver_flags: bool.type,
|
||||
thin: bool.type) -> [str.type]:
|
||||
if not use_archiver_flags:
|
||||
return []
|
||||
|
||||
if archiver_type == "windows":
|
||||
if thin:
|
||||
fail("'windows' archiver doesn't support thin archives")
|
||||
return ["/Brepro", "/d2threads1"]
|
||||
elif archiver_type == "windows_clang":
|
||||
return ["/llvmlibthin"] if thin else []
|
||||
flags = ""
|
||||
|
||||
# Operate in quick append mode, so that objects with identical basenames
|
||||
# won't overwrite one another.
|
||||
flags += "q"
|
||||
|
||||
# Suppress warning about creating a new archive.
|
||||
flags += "c"
|
||||
|
||||
# Run ranlib to generate symbol index for faster linking.
|
||||
flags += "s"
|
||||
|
||||
# Generate thin archives.
|
||||
if thin:
|
||||
flags += "T"
|
||||
|
||||
# GNU archivers support generating deterministic archives.
|
||||
if linker_type == "gnu":
|
||||
flags += "D"
|
||||
|
||||
return [flags]
|
||||
|
||||
# Create a static library from a list of object files.
|
||||
def _archive(ctx: "context", name: str.type, args: "cmd_args", thin: bool.type, prefer_local: bool.type) -> "artifact":
|
||||
archive_output = ctx.actions.declare_output(name)
|
||||
toolchain = get_cxx_toolchain_info(ctx)
|
||||
command = cmd_args(toolchain.linker_info.archiver)
|
||||
archiver_type = toolchain.linker_info.archiver_type
|
||||
command.add(_archive_flags(
|
||||
archiver_type,
|
||||
toolchain.linker_info.type,
|
||||
toolchain.linker_info.use_archiver_flags,
|
||||
thin,
|
||||
))
|
||||
if archiver_type == "windows" or archiver_type == "windows_clang":
|
||||
command.add([cmd_args(archive_output.as_output(), format = "/OUT:{}")])
|
||||
else:
|
||||
command.add([archive_output.as_output()])
|
||||
|
||||
if toolchain.linker_info.archiver_supports_argfiles:
|
||||
shell_quoted_args = cmd_args(args, quote = "shell")
|
||||
argfile, _ = ctx.actions.write(name + ".argsfile", shell_quoted_args, allow_args = True)
|
||||
command.hidden([shell_quoted_args])
|
||||
command.add(cmd_args(["@", argfile], delimiter = ""))
|
||||
else:
|
||||
command.add(args)
|
||||
|
||||
category = "archive"
|
||||
if thin:
|
||||
category = "archive_thin"
|
||||
ctx.actions.run(command, category = category, identifier = name, prefer_local = prefer_local)
|
||||
return archive_output
|
||||
|
||||
def _archive_locally(ctx: "context", linker_info: "LinkerInfo") -> bool.type:
|
||||
archive_locally = linker_info.archive_objects_locally
|
||||
if hasattr(ctx.attrs, "_archive_objects_locally_override"):
|
||||
return value_or(ctx.attrs._archive_objects_locally_override, archive_locally)
|
||||
return archive_locally
|
||||
|
||||
# Creates a static library given a list of object files.
|
||||
def make_archive(
|
||||
ctx: "context",
|
||||
name: str.type,
|
||||
objects: ["artifact"],
|
||||
args: ["cmd_args", None] = None) -> Archive.type:
|
||||
if len(objects) == 0:
|
||||
fail("no objects to archive")
|
||||
|
||||
if args == None:
|
||||
args = cmd_args(objects)
|
||||
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
thin = linker_info.archive_contents == "thin"
|
||||
archive = _archive(ctx, name, args, thin = thin, prefer_local = _archive_locally(ctx, linker_info))
|
||||
|
||||
# TODO(T110378125): use argsfiles for GNU archiver for long lists of objects.
|
||||
# TODO(T110378123): for BSD archiver, split long args over multiple invocations.
|
||||
# TODO(T110378100): We need to scrub the static library (timestamps, permissions, etc) as those are
|
||||
# sources of non-determinisim. See `ObjectFileScrubbers.createDateUidGidScrubber()` in Buck v1.
|
||||
|
||||
return Archive(artifact = archive, external_objects = objects if thin else [])
|
||||
63
vendor/cxx/tools/buck/prelude/cxx/attr_selection.bzl
vendored
Normal file
63
vendor/cxx/tools/buck/prelude/cxx/attr_selection.bzl
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# 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.
|
||||
|
||||
def cxx_by_language_ext(x: {"": ""}, ext: str.type) -> [""]:
|
||||
# lang_preprocessor_flags is indexed by c/cxx
|
||||
# lang_compiler_flags is indexed by c_cpp_output/cxx_cpp_output
|
||||
# so write a function that can do either
|
||||
#
|
||||
# === Buck v1 Compatibility ===
|
||||
#
|
||||
# `lang_compiler_flags` keys are coerced to CxxSource.Type,
|
||||
# so the allowable values are the lowercase versions of the enum values.
|
||||
#
|
||||
# The keys themselves should be the _output_ type of the language. For example,
|
||||
# for Obj-C, that would be OBJC_CPP_OUTPUT.
|
||||
#
|
||||
# The actual lookup for `lang_compiler_flags` happens in
|
||||
# CxxSourceRuleFactory::getRuleCompileFlags().
|
||||
#
|
||||
# `lang_preprocessor_flags` keys are also coerced to CxxSource.Type.
|
||||
# The keys are the _input_ type of the language. For example, for Obj-C,
|
||||
# that would be OBJC.
|
||||
if ext == ".c":
|
||||
key_pp = "c"
|
||||
|
||||
# TODO(gabrielrc): v1 docs have other keys
|
||||
# https://buck.build/rule/cxx_library.html#lang_compiler_flags
|
||||
# And you can see them in java code, but somehow it works with
|
||||
# this one, which is seem across the repo. Find out what's happening.
|
||||
key_compiler = "c_cpp_output"
|
||||
elif ext in (".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp"):
|
||||
key_pp = "cxx"
|
||||
key_compiler = "cxx_cpp_output"
|
||||
elif ext == ".m":
|
||||
key_pp = "objc"
|
||||
key_compiler = "objc_cpp_output"
|
||||
elif ext == ".mm":
|
||||
key_pp = "objcxx"
|
||||
key_compiler = "objcxx_cpp_output"
|
||||
elif ext in (".s", ".S"):
|
||||
key_pp = "assembler_with_cpp"
|
||||
key_compiler = "assembler"
|
||||
elif ext == ".cu":
|
||||
key_pp = "cuda"
|
||||
key_compiler = "cuda_cpp_output"
|
||||
elif ext == ".hip":
|
||||
key_pp = "hip"
|
||||
key_compiler = "hip_cpp_output"
|
||||
elif ext in (".asm", ".asmpp"):
|
||||
key_pp = "asm_with_cpp"
|
||||
key_compiler = "asm"
|
||||
else:
|
||||
fail("Unexpected file extension: " + ext)
|
||||
res = []
|
||||
if key_pp in x:
|
||||
res += x[key_pp]
|
||||
if key_compiler in x:
|
||||
res += x[key_compiler]
|
||||
return res
|
||||
65
vendor/cxx/tools/buck/prelude/cxx/comp_db.bzl
vendored
Normal file
65
vendor/cxx/tools/buck/prelude/cxx/comp_db.bzl
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# 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(
|
||||
":compile.bzl",
|
||||
"CxxSrcCompileCommand", # @unused Used as a type
|
||||
)
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
|
||||
# Provider that exposes the compilation database information
|
||||
CxxCompilationDbInfo = provider(fields = {
|
||||
"info": "A map of the file (an \"artifact.type\") to its corresponding \"CxxSrcCompileCommand\"",
|
||||
"platform": "platform for this compilation database",
|
||||
"toolchain": "toolchain for this compilation database",
|
||||
})
|
||||
|
||||
def make_compilation_db_info(src_compile_cmds: [CxxSrcCompileCommand.type], toolchainInfo: "CxxToolchainInfo", platformInfo: "CxxPlatformInfo") -> CxxCompilationDbInfo.type:
|
||||
info = {}
|
||||
for src_compile_cmd in src_compile_cmds:
|
||||
info.update({src_compile_cmd.src: src_compile_cmd})
|
||||
|
||||
return CxxCompilationDbInfo(info = info, toolchain = toolchainInfo, platform = platformInfo)
|
||||
|
||||
def create_compilation_database(
|
||||
ctx: "context",
|
||||
src_compile_cmds: [CxxSrcCompileCommand.type]) -> DefaultInfo.type:
|
||||
mk_comp_db = get_cxx_toolchain_info(ctx).mk_comp_db[RunInfo]
|
||||
|
||||
# Generate the per-source compilation DB entries.
|
||||
entries = {}
|
||||
other_outputs = []
|
||||
|
||||
for src_compile_cmd in src_compile_cmds:
|
||||
cdb_path = paths.join("__comp_db__", src_compile_cmd.src.short_path + ".comp_db.json")
|
||||
if cdb_path not in entries:
|
||||
entry = ctx.actions.declare_output(cdb_path)
|
||||
cmd = cmd_args(mk_comp_db)
|
||||
cmd.add("gen")
|
||||
cmd.add(cmd_args(entry.as_output(), format = "--output={}"))
|
||||
cmd.add(src_compile_cmd.src.basename)
|
||||
cmd.add(cmd_args(src_compile_cmd.src).parent())
|
||||
cmd.add("--")
|
||||
cmd.add(src_compile_cmd.cxx_compile_cmd.base_compile_cmd)
|
||||
cmd.add(src_compile_cmd.cxx_compile_cmd.argsfile.cmd_form)
|
||||
cmd.add(src_compile_cmd.args)
|
||||
ctx.actions.run(cmd, category = "cxx_compilation_database", identifier = src_compile_cmd.src.short_path)
|
||||
|
||||
# Add all inputs the command uses to runtime files.
|
||||
other_outputs.append(cmd)
|
||||
entries[cdb_path] = entry
|
||||
|
||||
# Merge all entries into the actual compilation DB.
|
||||
db = ctx.actions.declare_output("compile_commands.json")
|
||||
cmd = cmd_args(mk_comp_db)
|
||||
cmd.add("merge")
|
||||
cmd.add(cmd_args(db.as_output(), format = "--output={}"))
|
||||
cmd.add(entries.values())
|
||||
ctx.actions.run(cmd, category = "cxx_compilation_database_merge")
|
||||
|
||||
return DefaultInfo(default_outputs = [db], other_outputs = other_outputs)
|
||||
448
vendor/cxx/tools/buck/prelude/cxx/compile.bzl
vendored
Normal file
448
vendor/cxx/tools/buck/prelude/cxx/compile.bzl
vendored
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
# 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//linking:lto.bzl", "LtoMode")
|
||||
load(
|
||||
"@prelude//utils:utils.bzl",
|
||||
"flatten",
|
||||
)
|
||||
load(":attr_selection.bzl", "cxx_by_language_ext")
|
||||
load(
|
||||
":compiler.bzl",
|
||||
"get_flags_for_colorful_output",
|
||||
"get_flags_for_reproducible_build",
|
||||
"get_headers_dep_files_flags_factory",
|
||||
"get_output_flags",
|
||||
"get_pic_flags",
|
||||
)
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
load(":debug.bzl", "SplitDebugMode")
|
||||
load(
|
||||
":headers.bzl",
|
||||
"CPrecompiledHeaderInfo",
|
||||
)
|
||||
load(":platform.bzl", "cxx_by_platform")
|
||||
load(
|
||||
":preprocessor.bzl",
|
||||
"CPreprocessor", # @unused Used as a type
|
||||
"CPreprocessorInfo", # @unused Used as a type
|
||||
"cxx_attr_preprocessor_flags",
|
||||
"cxx_merge_cpreprocessors",
|
||||
)
|
||||
|
||||
# Supported Cxx file extensions
|
||||
CxxExtension = enum(
|
||||
".cpp",
|
||||
".cc",
|
||||
".cxx",
|
||||
".c++",
|
||||
".c",
|
||||
".s",
|
||||
".S",
|
||||
".m",
|
||||
".mm",
|
||||
".cu",
|
||||
".hip",
|
||||
".asm",
|
||||
".asmpp",
|
||||
".h",
|
||||
".hpp",
|
||||
)
|
||||
|
||||
# Information on argsfiles created for Cxx compilation.
|
||||
_CxxCompileArgsfile = record(
|
||||
# The generated argsfile
|
||||
file = field("artifact"),
|
||||
# This argfile as a command form that would use the argfile
|
||||
cmd_form = field("cmd_args"),
|
||||
# The args that was written to the argfile
|
||||
argfile_args = field("cmd_args"),
|
||||
# The args in their prisitine form without shell quoting
|
||||
args = field("cmd_args"),
|
||||
# Hidden args necessary for the argsfile to reference
|
||||
hidden_args = field([["artifacts", "cmd_args"]]),
|
||||
)
|
||||
|
||||
_HeadersDepFiles = record(
|
||||
# An executable to wrap the actual command with for post-processing of dep
|
||||
# files into the format that Buck2 recognizes (i.e. one artifact per line).
|
||||
processor = field("cmd_args"),
|
||||
# The tag that was added to headers.
|
||||
tag = field("artifact_tag"),
|
||||
# A function that produces new cmd_args to append to the compile command to
|
||||
# get it to emit the dep file. This will receive the output dep file as an
|
||||
# input.
|
||||
mk_flags = field("function"),
|
||||
)
|
||||
|
||||
# Information about how to compile a source file of particular extension.
|
||||
_CxxCompileCommand = record(
|
||||
# The compiler and any args which are independent of the rule.
|
||||
base_compile_cmd = field("cmd_args"),
|
||||
# The argsfile of arguments from the rule and it's dependencies.
|
||||
argsfile = field(_CxxCompileArgsfile.type),
|
||||
headers_dep_files = field([_HeadersDepFiles.type, None]),
|
||||
compiler_type = field(str.type),
|
||||
)
|
||||
|
||||
# Information about how to compile a source file.
|
||||
CxxSrcCompileCommand = record(
|
||||
# Source file to compile.
|
||||
src = field("artifact"),
|
||||
# If we have multiple source entries with same files but different flags,
|
||||
# specify an index so we can differentiate them. Otherwise, use None.
|
||||
index = field(["int", None], None),
|
||||
# The CxxCompileCommand to use to compile this file.
|
||||
cxx_compile_cmd = field(_CxxCompileCommand.type),
|
||||
# Arguments specific to the source file.
|
||||
args = field(["_arg"]),
|
||||
)
|
||||
|
||||
# Output of creating compile commands for Cxx source files.
|
||||
CxxCompileCommandOutput = record(
|
||||
# List of compile commands for each source file
|
||||
src_compile_cmds = field([CxxSrcCompileCommand.type]),
|
||||
# Argsfiles to generate in order to compile these source files
|
||||
argsfiles_info = field(DefaultInfo.type),
|
||||
# Each argsfile by the file extension for which it is used
|
||||
argsfile_by_ext = field({str.type: "artifact"}),
|
||||
)
|
||||
|
||||
# Output of creating compile commands for Cxx source files.
|
||||
CxxCompileCommandOutputForCompDb = record(
|
||||
# Output of creating compile commands for Cxx source files.
|
||||
source_commands = field(CxxCompileCommandOutput.type),
|
||||
# this field is only to be used in CDB generation
|
||||
comp_db_commands = field(CxxCompileCommandOutput.type),
|
||||
)
|
||||
|
||||
# An input to cxx compilation, consisting of a file to compile and optional
|
||||
# file specific flags to compile with.
|
||||
CxxSrcWithFlags = record(
|
||||
file = field("artifact"),
|
||||
flags = field(["resolved_macro"], []),
|
||||
# If we have multiple source entries with same files but different flags,
|
||||
# specify an index so we can differentiate them. Otherwise, use None.
|
||||
index = field(["int", None], None),
|
||||
)
|
||||
|
||||
CxxCompileOutput = record(
|
||||
# The compiled `.o` file.
|
||||
object = field("artifact"),
|
||||
object_has_external_debug_info = field(bool.type, False),
|
||||
# Externally referenced debug info, which doesn't get linked with the
|
||||
# object (e.g. the above `.o` when using `-gsplit-dwarf=single` or the
|
||||
# the `.dwo` when using `-gsplit-dwarf=split`).
|
||||
external_debug_info = field(["artifact", None], None),
|
||||
)
|
||||
|
||||
def create_compile_cmds(
|
||||
ctx: "context",
|
||||
impl_params: "CxxRuleConstructorParams",
|
||||
own_preprocessors: [CPreprocessor.type],
|
||||
inherited_preprocessor_infos: [CPreprocessorInfo.type]) -> CxxCompileCommandOutputForCompDb.type:
|
||||
"""
|
||||
Forms the CxxSrcCompileCommand to use for each source file based on it's extension
|
||||
and optional source file flags. Returns CxxCompileCommandOutput containing an array
|
||||
of the generated compile commands and argsfile output.
|
||||
"""
|
||||
|
||||
srcs_with_flags = []
|
||||
for src in impl_params.srcs:
|
||||
srcs_with_flags.append(src)
|
||||
header_only = False
|
||||
if len(srcs_with_flags) == 0 and len(impl_params.additional.srcs) == 0:
|
||||
all_headers = flatten([x.headers for x in own_preprocessors])
|
||||
if len(all_headers) == 0:
|
||||
all_raw_headers = flatten([x.raw_headers for x in own_preprocessors])
|
||||
if len(all_raw_headers) != 0:
|
||||
header_only = True
|
||||
for header in all_raw_headers:
|
||||
if header.extension in [".h", ".hpp"]:
|
||||
srcs_with_flags.append(CxxSrcWithFlags(file = header))
|
||||
else:
|
||||
return CxxCompileCommandOutputForCompDb(
|
||||
source_commands = CxxCompileCommandOutput(src_compile_cmds = [], argsfiles_info = DefaultInfo(), argsfile_by_ext = {}),
|
||||
comp_db_commands = CxxCompileCommandOutput(src_compile_cmds = [], argsfiles_info = DefaultInfo(), argsfile_by_ext = {}),
|
||||
)
|
||||
else:
|
||||
header_only = True
|
||||
for header in all_headers:
|
||||
if header.artifact.extension in [".h", ".hpp"]:
|
||||
srcs_with_flags.append(CxxSrcWithFlags(file = header.artifact))
|
||||
|
||||
# TODO(T110378129): Buck v1 validates *all* headers used by a compilation
|
||||
# at compile time, but that doing that here/eagerly might be expensive (but
|
||||
# we should figure out something).
|
||||
_validate_target_headers(ctx, own_preprocessors)
|
||||
|
||||
# Combine all preprocessor info and prepare it for compilations.
|
||||
pre = cxx_merge_cpreprocessors(
|
||||
ctx,
|
||||
filter(None, own_preprocessors + impl_params.extra_preprocessors),
|
||||
inherited_preprocessor_infos,
|
||||
)
|
||||
|
||||
headers_tag = ctx.actions.artifact_tag()
|
||||
|
||||
src_compile_cmds = []
|
||||
cxx_compile_cmd_by_ext = {}
|
||||
argsfile_by_ext = {}
|
||||
|
||||
for src in srcs_with_flags:
|
||||
ext = CxxExtension(src.file.extension)
|
||||
|
||||
# Deduplicate shared arguments to save memory. If we compile multiple files
|
||||
# of the same extension they will have some of the same flags. Save on
|
||||
# allocations by caching and reusing these objects.
|
||||
if not ext in cxx_compile_cmd_by_ext:
|
||||
toolchain = get_cxx_toolchain_info(ctx)
|
||||
compiler_info = _get_compiler_info(toolchain, ext)
|
||||
base_compile_cmd = _get_compile_base(compiler_info)
|
||||
|
||||
headers_dep_files = None
|
||||
if _supports_dep_files(ext) and toolchain.use_dep_files:
|
||||
mk_dep_files_flags = get_headers_dep_files_flags_factory(compiler_info.compiler_type)
|
||||
if mk_dep_files_flags:
|
||||
headers_dep_files = _HeadersDepFiles(
|
||||
processor = cmd_args(compiler_info.dep_files_processor),
|
||||
mk_flags = mk_dep_files_flags,
|
||||
tag = headers_tag,
|
||||
)
|
||||
|
||||
argsfile_by_ext[ext.value] = _mk_argsfile(ctx, compiler_info, pre, ext, headers_tag)
|
||||
cxx_compile_cmd_by_ext[ext] = _CxxCompileCommand(
|
||||
base_compile_cmd = base_compile_cmd,
|
||||
argsfile = argsfile_by_ext[ext.value],
|
||||
headers_dep_files = headers_dep_files,
|
||||
compiler_type = compiler_info.compiler_type,
|
||||
)
|
||||
|
||||
cxx_compile_cmd = cxx_compile_cmd_by_ext[ext]
|
||||
|
||||
src_args = []
|
||||
src_args.extend(src.flags)
|
||||
src_args.extend(["-c", src.file])
|
||||
|
||||
src_compile_command = CxxSrcCompileCommand(src = src.file, cxx_compile_cmd = cxx_compile_cmd, args = src_args, index = src.index)
|
||||
src_compile_cmds.append(src_compile_command)
|
||||
|
||||
# Create an output file of all the argsfiles generated for compiling these source files.
|
||||
argsfiles = []
|
||||
argsfile_names = cmd_args()
|
||||
other_outputs = []
|
||||
argsfile_artifacts_by_ext = {}
|
||||
for ext, argsfile in argsfile_by_ext.items():
|
||||
argsfiles.append(argsfile.file)
|
||||
argsfile_names.add(cmd_args(argsfile.file).ignore_artifacts())
|
||||
other_outputs.extend(argsfile.hidden_args)
|
||||
argsfile_artifacts_by_ext[ext] = argsfile.file
|
||||
|
||||
for argsfile in impl_params.additional.argsfiles:
|
||||
argsfiles.append(argsfile.file)
|
||||
argsfile_names.add(cmd_args(argsfile.file).ignore_artifacts())
|
||||
other_outputs.extend(argsfile.hidden_args)
|
||||
|
||||
argsfiles_summary = ctx.actions.write("argsfiles", argsfile_names)
|
||||
|
||||
# Create a provider that will output all the argsfiles necessary and generate those argsfiles.
|
||||
argsfiles = DefaultInfo(default_outputs = [argsfiles_summary] + argsfiles, other_outputs = other_outputs)
|
||||
|
||||
if header_only:
|
||||
return CxxCompileCommandOutputForCompDb(
|
||||
source_commands = CxxCompileCommandOutput(src_compile_cmds = [], argsfiles_info = DefaultInfo(), argsfile_by_ext = {}),
|
||||
comp_db_commands = CxxCompileCommandOutput(src_compile_cmds = src_compile_cmds, argsfiles_info = argsfiles, argsfile_by_ext = argsfile_artifacts_by_ext),
|
||||
)
|
||||
else:
|
||||
return CxxCompileCommandOutputForCompDb(
|
||||
source_commands = CxxCompileCommandOutput(src_compile_cmds = src_compile_cmds, argsfiles_info = argsfiles, argsfile_by_ext = argsfile_artifacts_by_ext),
|
||||
comp_db_commands = CxxCompileCommandOutput(src_compile_cmds = src_compile_cmds, argsfiles_info = argsfiles, argsfile_by_ext = argsfile_artifacts_by_ext),
|
||||
)
|
||||
|
||||
def compile_cxx(
|
||||
ctx: "context",
|
||||
src_compile_cmds: [CxxSrcCompileCommand.type],
|
||||
pic: bool.type = False) -> [CxxCompileOutput.type]:
|
||||
"""
|
||||
For a given list of src_compile_cmds, generate output artifacts.
|
||||
"""
|
||||
toolchain = get_cxx_toolchain_info(ctx)
|
||||
linker_info = toolchain.linker_info
|
||||
|
||||
objects = []
|
||||
for src_compile_cmd in src_compile_cmds:
|
||||
identifier = src_compile_cmd.src.short_path
|
||||
if src_compile_cmd.index != None:
|
||||
# Add a unique postfix if we have duplicate source files with different flags
|
||||
identifier = identifier + "_" + str(src_compile_cmd.index)
|
||||
|
||||
filename_base = identifier + (".pic" if pic else "")
|
||||
object = ctx.actions.declare_output(
|
||||
paths.join("__objects__", "{}.{}".format(filename_base, linker_info.object_file_extension)),
|
||||
)
|
||||
|
||||
cmd = cmd_args(src_compile_cmd.cxx_compile_cmd.base_compile_cmd)
|
||||
|
||||
compiler_type = src_compile_cmd.cxx_compile_cmd.compiler_type
|
||||
cmd.add(get_output_flags(compiler_type, object))
|
||||
|
||||
args = cmd_args()
|
||||
|
||||
if pic:
|
||||
args.add(get_pic_flags(compiler_type))
|
||||
|
||||
args.add(src_compile_cmd.cxx_compile_cmd.argsfile.cmd_form)
|
||||
args.add(src_compile_cmd.args)
|
||||
|
||||
cmd.add(args)
|
||||
|
||||
action_dep_files = {}
|
||||
|
||||
headers_dep_files = src_compile_cmd.cxx_compile_cmd.headers_dep_files
|
||||
if headers_dep_files:
|
||||
intermediary_dep_file = ctx.actions.declare_output(
|
||||
paths.join("__dep_files_intermediaries__", filename_base),
|
||||
).as_output()
|
||||
dep_file = ctx.actions.declare_output(
|
||||
paths.join("__dep_files__", filename_base),
|
||||
).as_output()
|
||||
|
||||
dep_file_flags = headers_dep_files.mk_flags(intermediary_dep_file)
|
||||
cmd.add(dep_file_flags)
|
||||
|
||||
# API: First argument is the dep file source path, second is the
|
||||
# dep file destination path, other arguments are the actual compile
|
||||
# command.
|
||||
cmd = cmd_args([
|
||||
headers_dep_files.processor,
|
||||
intermediary_dep_file,
|
||||
headers_dep_files.tag.tag_artifacts(dep_file),
|
||||
cmd,
|
||||
])
|
||||
|
||||
action_dep_files["headers"] = headers_dep_files.tag
|
||||
|
||||
if pic:
|
||||
identifier += " (pic)"
|
||||
ctx.actions.run(cmd, category = "cxx_compile", identifier = identifier, dep_files = action_dep_files)
|
||||
|
||||
# If we're building with split debugging, where the debug info is in the
|
||||
# original object, then add the object as external debug info, *unless*
|
||||
# we're doing LTO, which generates debug info at link time (*except* for
|
||||
# fat LTO, which still generates native code and, therefore, debug info).
|
||||
object_has_external_debug_info = (
|
||||
toolchain.split_debug_mode == SplitDebugMode("single") and
|
||||
linker_info.lto_mode in (LtoMode("none"), LtoMode("fat"))
|
||||
)
|
||||
|
||||
objects.append(CxxCompileOutput(
|
||||
object = object,
|
||||
object_has_external_debug_info = object_has_external_debug_info,
|
||||
))
|
||||
|
||||
return objects
|
||||
|
||||
def _validate_target_headers(ctx: "context", preprocessor: [CPreprocessor.type]):
|
||||
path_to_artifact = {}
|
||||
all_headers = flatten([x.headers for x in preprocessor])
|
||||
for header in all_headers:
|
||||
header_path = paths.join(header.namespace, header.name)
|
||||
artifact = path_to_artifact.get(header_path)
|
||||
if artifact != None:
|
||||
if artifact != header.artifact:
|
||||
fail("Conflicting headers {} and {} map to {} in target {}".format(artifact, header.artifact, header_path, ctx.label))
|
||||
else:
|
||||
path_to_artifact[header_path] = header.artifact
|
||||
|
||||
def _get_compiler_info(toolchain: "CxxToolchainInfo", ext: CxxExtension.type) -> "_compiler_info":
|
||||
if ext.value in (".cpp", ".cc", ".mm", ".cxx", ".c++", ".h", ".hpp"):
|
||||
return toolchain.cxx_compiler_info
|
||||
elif ext.value in (".c", ".m"):
|
||||
return toolchain.c_compiler_info
|
||||
elif ext.value in (".s", ".S"):
|
||||
return toolchain.as_compiler_info
|
||||
elif ext.value == ".cu":
|
||||
return toolchain.cuda_compiler_info
|
||||
elif ext.value == ".hip":
|
||||
return toolchain.hip_compiler_info
|
||||
elif ext.value in (".asm", ".asmpp"):
|
||||
return toolchain.asm_compiler_info
|
||||
else:
|
||||
# This should be unreachable as long as we handle all enum values
|
||||
fail("Unknown C++ extension: " + ext.value)
|
||||
|
||||
def _get_compile_base(compiler_info: "_compiler_info") -> "cmd_args":
|
||||
"""
|
||||
Given a compiler info returned by _get_compiler_info, form the base compile args.
|
||||
"""
|
||||
|
||||
cmd = cmd_args(compiler_info.compiler)
|
||||
|
||||
return cmd
|
||||
|
||||
def _supports_dep_files(ext: CxxExtension.type) -> bool.type:
|
||||
# Raw assembly doesn't make sense to capture dep files for.
|
||||
if ext.value in (".s", ".S", ".asm"):
|
||||
return False
|
||||
elif ext.value == ".hip":
|
||||
# TODO (T118797886): HipCompilerInfo doesn't have dep files processor.
|
||||
# Should it?
|
||||
return False
|
||||
return True
|
||||
|
||||
def _add_compiler_info_flags(compiler_info: "_compiler_info", ext: CxxExtension.type, cmd: "cmd_args"):
|
||||
cmd.add(compiler_info.preprocessor_flags or [])
|
||||
cmd.add(compiler_info.compiler_flags or [])
|
||||
cmd.add(get_flags_for_reproducible_build(compiler_info.compiler_type))
|
||||
|
||||
if ext.value not in (".asm", ".asmpp"):
|
||||
# Clang's asm compiler doesn't support colorful output, so we skip this there.
|
||||
cmd.add(get_flags_for_colorful_output(compiler_info.compiler_type))
|
||||
|
||||
def _mk_argsfile(ctx: "context", compiler_info: "_compiler_info", preprocessor: CPreprocessorInfo.type, ext: CxxExtension.type, headers_tag: "artifact_tag") -> _CxxCompileArgsfile.type:
|
||||
"""
|
||||
Generate and return an {ext}.argsfile artifact and command args that utilize the argsfile.
|
||||
"""
|
||||
args = cmd_args()
|
||||
|
||||
_add_compiler_info_flags(compiler_info, ext, args)
|
||||
|
||||
args.add(headers_tag.tag_artifacts(preprocessor.set.project_as_args("args")))
|
||||
|
||||
# Different preprocessors will contain whether to use modules,
|
||||
# and the modulemap to use, so we need to get the final outcome.
|
||||
if preprocessor.set.reduce("uses_modules"):
|
||||
args.add(headers_tag.tag_artifacts(preprocessor.set.project_as_args("modular_args")))
|
||||
|
||||
args.add(cxx_attr_preprocessor_flags(ctx, ext.value))
|
||||
args.add(_attr_compiler_flags(ctx, ext.value))
|
||||
args.add(headers_tag.tag_artifacts(preprocessor.set.project_as_args("include_dirs")))
|
||||
|
||||
# Workaround as that's not precompiled, but working just as prefix header.
|
||||
# Another thing is that it's clang specific, should be generalized.
|
||||
if ctx.attrs.precompiled_header != None:
|
||||
args.add(["-include", headers_tag.tag_artifacts(ctx.attrs.precompiled_header[CPrecompiledHeaderInfo].header)])
|
||||
if ctx.attrs.prefix_header != None:
|
||||
args.add(["-include", headers_tag.tag_artifacts(ctx.attrs.prefix_header)])
|
||||
|
||||
shell_quoted_args = cmd_args(args, quote = "shell")
|
||||
argfile, _ = ctx.actions.write(ext.value + ".argsfile", shell_quoted_args, allow_args = True)
|
||||
|
||||
hidden_args = [args]
|
||||
|
||||
cmd_form = cmd_args(argfile, format = "@{}").hidden(hidden_args)
|
||||
|
||||
return _CxxCompileArgsfile(file = argfile, cmd_form = cmd_form, argfile_args = shell_quoted_args, args = args, hidden_args = hidden_args)
|
||||
|
||||
def _attr_compiler_flags(ctx: "context", ext: str.type) -> [""]:
|
||||
return (
|
||||
ctx.attrs.compiler_flags +
|
||||
cxx_by_language_ext(ctx.attrs.lang_compiler_flags, ext) +
|
||||
flatten(cxx_by_platform(ctx, ctx.attrs.platform_compiler_flags)) +
|
||||
flatten(cxx_by_platform(ctx, cxx_by_language_ext(ctx.attrs.lang_platform_compiler_flags, ext)))
|
||||
)
|
||||
68
vendor/cxx/tools/buck/prelude/cxx/compiler.bzl
vendored
Normal file
68
vendor/cxx/tools/buck/prelude/cxx/compiler.bzl
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# 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.
|
||||
|
||||
# TODO(T110378132): Added here for compat with v1, but this might make more
|
||||
# sense on the toolchain definition.
|
||||
def get_flags_for_reproducible_build(compiler_type: str.type) -> [str.type]:
|
||||
"""
|
||||
Return flags needed to make compilations reproducible (e.g. avoiding
|
||||
embedding the working directory into debug info.
|
||||
"""
|
||||
|
||||
flags = []
|
||||
|
||||
if compiler_type in ["clang_cl", "windows"]:
|
||||
flags.append("/Brepro")
|
||||
|
||||
if compiler_type in ["clang", "clang_windows", "clang_cl"]:
|
||||
flags.extend(["-Xclang", "-fdebug-compilation-dir", "-Xclang", "."])
|
||||
|
||||
if compiler_type == "clang_windows":
|
||||
flags.append("-mno-incremental-linker-compatible")
|
||||
|
||||
return flags
|
||||
|
||||
def get_flags_for_colorful_output(compiler_type: str.type) -> [str.type]:
|
||||
"""
|
||||
Return flags for enabling colorful diagnostic output.
|
||||
"""
|
||||
flags = []
|
||||
if compiler_type in ["clang", "clang_windows", "clang_cl"]:
|
||||
# https://clang.llvm.org/docs/UsersManual.html
|
||||
flags.append("-fcolor-diagnostics")
|
||||
elif compiler_type == "gcc":
|
||||
# https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Message-Formatting-Options.html
|
||||
flags.append("-fdiagnostics-color=always")
|
||||
|
||||
return flags
|
||||
|
||||
def cc_dep_files(output: "_arglike") -> cmd_args.type:
|
||||
return cmd_args(["-MD", "-MF", output])
|
||||
|
||||
def windows_cc_dep_files(_output: "_arglike") -> cmd_args.type:
|
||||
return cmd_args(["/showIncludes"])
|
||||
|
||||
def get_headers_dep_files_flags_factory(compiler_type: str.type) -> ["function", None]:
|
||||
if compiler_type in ["clang", "gcc", "clang_windows"]:
|
||||
return cc_dep_files
|
||||
|
||||
if compiler_type in ["windows", "clang_cl"]:
|
||||
return windows_cc_dep_files
|
||||
|
||||
return None
|
||||
|
||||
def get_pic_flags(compiler_type: str.type) -> [str.type]:
|
||||
if compiler_type in ["clang", "gcc"]:
|
||||
return ["-fPIC"]
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_output_flags(compiler_type: str.type, output: "artifact") -> [""]:
|
||||
if compiler_type in ["windows", "clang_cl", "windows_ml64"]:
|
||||
return [cmd_args(output.as_output(), format = "/Fo{}")]
|
||||
else:
|
||||
return ["-o", output.as_output()]
|
||||
608
vendor/cxx/tools/buck/prelude/cxx/cxx.bzl
vendored
Normal file
608
vendor/cxx/tools/buck/prelude/cxx/cxx.bzl
vendored
Normal file
|
|
@ -0,0 +1,608 @@
|
|||
# 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//cxx:link_groups.bzl",
|
||||
"LinkGroupInfo", # @unused Used as a type
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_groups.bzl",
|
||||
"merge_link_group_lib_info",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"Archive",
|
||||
"ArchiveLinkable",
|
||||
"LinkArgs",
|
||||
"LinkInfo",
|
||||
"LinkInfos",
|
||||
"LinkStyle",
|
||||
"Linkage",
|
||||
"LinkedObject",
|
||||
"SharedLibLinkable",
|
||||
"create_merged_link_info",
|
||||
"get_actual_link_style",
|
||||
"get_link_args",
|
||||
"get_link_styles_for_linkage",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkable_graph.bzl",
|
||||
"AnnotatedLinkableRoot",
|
||||
"LinkableGraph",
|
||||
"create_linkable_graph",
|
||||
"create_linkable_graph_node",
|
||||
"create_linkable_node",
|
||||
)
|
||||
load("@prelude//linking:shared_libraries.bzl", "SharedLibraryInfo", "create_shared_libraries", "merge_shared_libraries")
|
||||
load(
|
||||
"@prelude//tests:re_utils.bzl",
|
||||
"get_re_executor_from_props",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:utils.bzl",
|
||||
"expect",
|
||||
"filter_and_map_idx",
|
||||
"flatten",
|
||||
"value_or",
|
||||
)
|
||||
load("@prelude//test/inject_test_run_info.bzl", "inject_test_run_info")
|
||||
load(
|
||||
":compile.bzl",
|
||||
"CxxSrcWithFlags", # @unused Used as a type
|
||||
)
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
load(":cxx_executable.bzl", "cxx_executable")
|
||||
load(":cxx_library.bzl", "cxx_library_parameterized")
|
||||
load(
|
||||
":cxx_library_utility.bzl",
|
||||
"cxx_attr_deps",
|
||||
"cxx_attr_exported_deps",
|
||||
"cxx_attr_exported_linker_flags",
|
||||
"cxx_attr_exported_post_linker_flags",
|
||||
"cxx_attr_preferred_linkage",
|
||||
"cxx_inherited_link_info",
|
||||
"cxx_mk_shlib_intf",
|
||||
"cxx_platform_supported",
|
||||
"cxx_use_shlib_intfs",
|
||||
)
|
||||
load(
|
||||
":cxx_types.bzl",
|
||||
"CxxRuleConstructorParams",
|
||||
"CxxRuleProviderParams",
|
||||
"CxxRuleSubTargetParams",
|
||||
)
|
||||
load(
|
||||
":groups.bzl",
|
||||
"Group", # @unused Used as a type
|
||||
"MATCH_ALL_LABEL",
|
||||
"NO_MATCH_LABEL",
|
||||
)
|
||||
load(
|
||||
":headers.bzl",
|
||||
"CPrecompiledHeaderInfo",
|
||||
"cxx_get_regular_cxx_headers_layout",
|
||||
)
|
||||
load(
|
||||
":link.bzl",
|
||||
_cxx_link_into_shared_library = "cxx_link_into_shared_library",
|
||||
)
|
||||
load(
|
||||
":link_groups.bzl",
|
||||
"LinkGroupLibSpec",
|
||||
"get_link_group_info",
|
||||
)
|
||||
load(
|
||||
":linker.bzl",
|
||||
"get_link_whole_args",
|
||||
"get_shared_library_name",
|
||||
)
|
||||
load(
|
||||
":omnibus.bzl",
|
||||
"create_linkable_root",
|
||||
"is_known_omnibus_root",
|
||||
)
|
||||
load(":platform.bzl", "cxx_by_platform")
|
||||
load(
|
||||
":preprocessor.bzl",
|
||||
"CPreprocessor",
|
||||
"cxx_attr_exported_preprocessor_flags",
|
||||
"cxx_exported_preprocessor_info",
|
||||
"cxx_inherited_preprocessor_infos",
|
||||
"cxx_merge_cpreprocessors",
|
||||
)
|
||||
|
||||
cxx_link_into_shared_library = _cxx_link_into_shared_library
|
||||
|
||||
#####################################################################
|
||||
# Attributes
|
||||
|
||||
# The source files
|
||||
def get_srcs_with_flags(ctx: "context") -> [CxxSrcWithFlags.type]:
|
||||
all_srcs = ctx.attrs.srcs + flatten(cxx_by_platform(ctx, ctx.attrs.platform_srcs))
|
||||
|
||||
# src -> flags_hash -> flags
|
||||
flags_sets_by_src = {}
|
||||
for x in all_srcs:
|
||||
if type(x) == type(()):
|
||||
artifact = x[0]
|
||||
flags = x[1]
|
||||
else:
|
||||
artifact = x
|
||||
flags = []
|
||||
|
||||
flags_hash = hash(str(flags))
|
||||
flag_sets = flags_sets_by_src.setdefault(artifact, {})
|
||||
flag_sets[flags_hash] = flags
|
||||
|
||||
# Go through collected (source, flags) pair and set the index field if there are duplicate source files
|
||||
cxx_src_with_flags_records = []
|
||||
for (artifact, flag_sets) in flags_sets_by_src.items():
|
||||
needs_indices = len(flag_sets) > 1
|
||||
for i, flags in enumerate(flag_sets.values()):
|
||||
index = i if needs_indices else None
|
||||
cxx_src_with_flags_records.append(CxxSrcWithFlags(file = artifact, flags = flags, index = index))
|
||||
|
||||
return cxx_src_with_flags_records
|
||||
|
||||
#####################################################################
|
||||
# Operations
|
||||
|
||||
def _get_shared_link_style_sub_targets_and_providers(
|
||||
link_style: LinkStyle.type,
|
||||
_ctx: "context",
|
||||
_executable: "artifact",
|
||||
_external_debug_info: ["_arglike"],
|
||||
dwp: ["artifact", None]) -> ({str.type: ["provider"]}, ["provider"]):
|
||||
if link_style != LinkStyle("shared") or dwp == None:
|
||||
return ({}, [])
|
||||
return ({"dwp": [DefaultInfo(default_outputs = [dwp])]}, [])
|
||||
|
||||
def cxx_library_impl(ctx: "context") -> ["provider"]:
|
||||
if ctx.attrs.can_be_asset and ctx.attrs.used_by_wrap_script:
|
||||
fail("Cannot use `can_be_asset` and `used_by_wrap_script` in the same rule")
|
||||
|
||||
if ctx.attrs._is_building_android_binary:
|
||||
sub_target_params, provider_params = _get_params_for_android_binary_cxx_library()
|
||||
else:
|
||||
sub_target_params = CxxRuleSubTargetParams()
|
||||
provider_params = CxxRuleProviderParams()
|
||||
|
||||
params = CxxRuleConstructorParams(
|
||||
rule_type = "cxx_library",
|
||||
headers_layout = cxx_get_regular_cxx_headers_layout(ctx),
|
||||
srcs = get_srcs_with_flags(ctx),
|
||||
link_style_sub_targets_and_providers_factory = _get_shared_link_style_sub_targets_and_providers,
|
||||
is_omnibus_root = is_known_omnibus_root(ctx),
|
||||
force_emit_omnibus_shared_root = ctx.attrs.force_emit_omnibus_shared_root,
|
||||
generate_sub_targets = sub_target_params,
|
||||
generate_providers = provider_params,
|
||||
)
|
||||
output = cxx_library_parameterized(ctx, params)
|
||||
return output.providers
|
||||
|
||||
def _only_shared_mappings(group: Group.type) -> bool.type:
|
||||
"""
|
||||
Return whether this group only has explicit "shared" linkage mappings,
|
||||
which indicates a group that re-uses pre-linked libs.
|
||||
"""
|
||||
for mapping in group.mappings:
|
||||
if mapping.preferred_linkage != Linkage("shared"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_cxx_auto_link_group_specs(ctx: "context", link_group_info: [LinkGroupInfo.type, None]) -> [[LinkGroupLibSpec.type], None]:
|
||||
if link_group_info == None or not ctx.attrs.auto_link_groups:
|
||||
return None
|
||||
specs = []
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
for group in link_group_info.groups:
|
||||
if group.name in (MATCH_ALL_LABEL, NO_MATCH_LABEL):
|
||||
continue
|
||||
|
||||
# TODO(agallagher): We should probably add proper handling for "provided"
|
||||
# system handling to avoid needing this special case.
|
||||
if _only_shared_mappings(group):
|
||||
continue
|
||||
specs.append(
|
||||
LinkGroupLibSpec(
|
||||
name = get_shared_library_name(linker_info, group.name),
|
||||
is_shared_lib = True,
|
||||
group = group,
|
||||
),
|
||||
)
|
||||
return specs
|
||||
|
||||
def cxx_binary_impl(ctx: "context") -> ["provider"]:
|
||||
link_group_info = get_link_group_info(ctx, filter_and_map_idx(LinkableGraph, cxx_attr_deps(ctx)))
|
||||
params = CxxRuleConstructorParams(
|
||||
rule_type = "cxx_binary",
|
||||
headers_layout = cxx_get_regular_cxx_headers_layout(ctx),
|
||||
srcs = get_srcs_with_flags(ctx),
|
||||
link_group_info = link_group_info,
|
||||
auto_link_group_specs = get_cxx_auto_link_group_specs(ctx, link_group_info),
|
||||
)
|
||||
output, comp_db_info, xcode_data_info = cxx_executable(ctx, params)
|
||||
|
||||
return [
|
||||
DefaultInfo(
|
||||
default_outputs = [output.binary],
|
||||
other_outputs = output.runtime_files,
|
||||
sub_targets = output.sub_targets,
|
||||
),
|
||||
RunInfo(args = cmd_args(output.binary).hidden(output.runtime_files)),
|
||||
comp_db_info,
|
||||
xcode_data_info,
|
||||
]
|
||||
|
||||
def _prebuilt_item(
|
||||
ctx: "context",
|
||||
item: ["", None],
|
||||
platform_items: [[(str.type, "_a")], None]) -> ["_a", None]:
|
||||
"""
|
||||
Parse the given item that can be specified by regular and platform-specific
|
||||
parameters.
|
||||
"""
|
||||
|
||||
if item != None:
|
||||
return item
|
||||
|
||||
if platform_items != None:
|
||||
items = cxx_by_platform(ctx, platform_items)
|
||||
if len(items) == 0:
|
||||
return None
|
||||
if len(items) != 1:
|
||||
fail("expected single platform match: name={}//{}:{}, platform_items={}, items={}".format(ctx.label.cell, ctx.label.package, ctx.label.name, str(platform_items), str(items)))
|
||||
return items[0]
|
||||
|
||||
return None
|
||||
|
||||
def _prebuilt_linkage(ctx: "context") -> Linkage.type:
|
||||
"""
|
||||
Construct the preferred linkage to use for the given prebuilt library.
|
||||
"""
|
||||
if ctx.attrs.header_only:
|
||||
return Linkage("any")
|
||||
if ctx.attrs.force_static:
|
||||
return Linkage("static")
|
||||
preferred_linkage = cxx_attr_preferred_linkage(ctx)
|
||||
if preferred_linkage != Linkage("any"):
|
||||
return preferred_linkage
|
||||
if ctx.attrs.provided:
|
||||
return Linkage("shared")
|
||||
return Linkage("any")
|
||||
|
||||
def prebuilt_cxx_library_impl(ctx: "context") -> ["provider"]:
|
||||
# Versioned params should be intercepted and converted away via the stub.
|
||||
expect(not ctx.attrs.versioned_exported_lang_platform_preprocessor_flags)
|
||||
expect(not ctx.attrs.versioned_exported_lang_preprocessor_flags)
|
||||
expect(not ctx.attrs.versioned_exported_platform_preprocessor_flags)
|
||||
expect(not ctx.attrs.versioned_exported_preprocessor_flags)
|
||||
expect(not ctx.attrs.versioned_header_dirs)
|
||||
expect(not ctx.attrs.versioned_shared_lib)
|
||||
expect(not ctx.attrs.versioned_static_lib)
|
||||
expect(not ctx.attrs.versioned_static_pic_lib)
|
||||
|
||||
if not cxx_platform_supported(ctx):
|
||||
return [DefaultInfo(default_outputs = [])]
|
||||
|
||||
providers = []
|
||||
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
linker_type = linker_info.type
|
||||
|
||||
# Parse library parameters.
|
||||
static_lib = _prebuilt_item(
|
||||
ctx,
|
||||
ctx.attrs.static_lib,
|
||||
ctx.attrs.platform_static_lib,
|
||||
)
|
||||
static_pic_lib = _prebuilt_item(
|
||||
ctx,
|
||||
ctx.attrs.static_pic_lib,
|
||||
ctx.attrs.platform_static_pic_lib,
|
||||
)
|
||||
shared_lib = _prebuilt_item(
|
||||
ctx,
|
||||
ctx.attrs.shared_lib,
|
||||
ctx.attrs.platform_shared_lib,
|
||||
)
|
||||
header_dirs = _prebuilt_item(
|
||||
ctx,
|
||||
ctx.attrs.header_dirs,
|
||||
ctx.attrs.platform_header_dirs,
|
||||
)
|
||||
soname = value_or(ctx.attrs.soname, get_shared_library_name(linker_info, ctx.label.name))
|
||||
preferred_linkage = _prebuilt_linkage(ctx)
|
||||
|
||||
# Use ctx.attrs.deps instead of cxx_attr_deps, since prebuilt rules don't have platform_deps.
|
||||
first_order_deps = ctx.attrs.deps
|
||||
exported_first_order_deps = cxx_attr_exported_deps(ctx)
|
||||
|
||||
# Exported preprocessor info.
|
||||
inherited_pp_infos = cxx_inherited_preprocessor_infos(exported_first_order_deps)
|
||||
generic_exported_pre = cxx_exported_preprocessor_info(ctx, cxx_get_regular_cxx_headers_layout(ctx), [])
|
||||
args = cxx_attr_exported_preprocessor_flags(ctx)
|
||||
if header_dirs != None:
|
||||
for x in header_dirs:
|
||||
args += ["-isystem", x]
|
||||
specific_exportd_pre = CPreprocessor(args = args)
|
||||
providers.append(cxx_merge_cpreprocessors(
|
||||
ctx,
|
||||
[generic_exported_pre, specific_exportd_pre],
|
||||
inherited_pp_infos,
|
||||
))
|
||||
|
||||
inherited_link = cxx_inherited_link_info(ctx, first_order_deps)
|
||||
inherited_exported_link = cxx_inherited_link_info(ctx, exported_first_order_deps)
|
||||
exported_linker_flags = cxx_attr_exported_linker_flags(ctx)
|
||||
|
||||
# Gather link infos, outputs, and shared libs for effective link style.
|
||||
outputs = {}
|
||||
libraries = {}
|
||||
solibs = {}
|
||||
sub_targets = {}
|
||||
for link_style in get_link_styles_for_linkage(preferred_linkage):
|
||||
args = []
|
||||
outs = []
|
||||
|
||||
# Add exported linker flags first.
|
||||
args.extend(cxx_attr_exported_linker_flags(ctx))
|
||||
post_link_flags = cxx_attr_exported_post_linker_flags(ctx)
|
||||
linkable = None
|
||||
|
||||
# If we have sources to compile, generate the necessary libraries and
|
||||
# add them to the exported link info.
|
||||
if not ctx.attrs.header_only:
|
||||
def archive_linkable(lib):
|
||||
return ArchiveLinkable(
|
||||
archive = Archive(artifact = lib),
|
||||
linker_type = linker_type,
|
||||
link_whole = ctx.attrs.link_whole,
|
||||
)
|
||||
|
||||
if link_style == LinkStyle("static"):
|
||||
if static_lib:
|
||||
outs.append(static_lib)
|
||||
linkable = archive_linkable(static_lib)
|
||||
elif link_style == LinkStyle("static_pic"):
|
||||
lib = static_pic_lib or static_lib
|
||||
if lib:
|
||||
outs.append(lib)
|
||||
linkable = archive_linkable(lib)
|
||||
else: # shared
|
||||
# If no shared library was provided, link one from the static libraries.
|
||||
if shared_lib != None:
|
||||
shared_lib = LinkedObject(output = shared_lib)
|
||||
else:
|
||||
lib = static_pic_lib or static_lib
|
||||
if lib:
|
||||
shlink_args = []
|
||||
|
||||
# TODO(T110378143): Support post link flags properly.
|
||||
shlink_args.extend(exported_linker_flags)
|
||||
shlink_args.extend(get_link_whole_args(linker_type, [lib]))
|
||||
shared_lib = cxx_link_into_shared_library(
|
||||
ctx,
|
||||
soname,
|
||||
[
|
||||
LinkArgs(flags = shlink_args),
|
||||
# TODO(T110378118): As per v1, we always link against "shared"
|
||||
# dependencies when building a shaerd library.
|
||||
get_link_args(inherited_exported_link, LinkStyle("shared")),
|
||||
],
|
||||
)
|
||||
|
||||
if shared_lib:
|
||||
outs.append(shared_lib.output)
|
||||
|
||||
# Some prebuilt shared libs don't set a SONAME (e.g.
|
||||
# IntelComposerXE), so we can't link them via just the shared
|
||||
# lib (otherwise, we'll may embed buid-time paths in `DT_NEEDED`
|
||||
# tags).
|
||||
if ctx.attrs.link_without_soname:
|
||||
if ctx.attrs.supports_shared_library_interface:
|
||||
fail("cannot use `link_without_soname` with shlib interfaces")
|
||||
linkable = SharedLibLinkable(
|
||||
lib = shared_lib.output,
|
||||
link_without_soname = True,
|
||||
)
|
||||
else:
|
||||
shared_lib_for_linking = shared_lib.output
|
||||
|
||||
# Generate a shared library interface if the rule supports it.
|
||||
if ctx.attrs.supports_shared_library_interface and cxx_use_shlib_intfs(ctx):
|
||||
shared_lib_for_linking = cxx_mk_shlib_intf(ctx, ctx.attrs.name, shared_lib.output)
|
||||
linkable = SharedLibLinkable(lib = shared_lib_for_linking)
|
||||
|
||||
# Provided means something external to the build will provide
|
||||
# the libraries, so we don't need to propagate anything.
|
||||
if not ctx.attrs.provided:
|
||||
solibs[soname] = shared_lib
|
||||
|
||||
# Provide a sub-target that always provides the shared lib
|
||||
# using the soname.
|
||||
if soname and shared_lib.output.basename != soname:
|
||||
soname_lib = ctx.actions.copy_file(soname, shared_lib.output)
|
||||
else:
|
||||
soname_lib = shared_lib.output
|
||||
sub_targets["soname-lib"] = [DefaultInfo(default_outputs = [soname_lib])]
|
||||
|
||||
# TODO(cjhopman): is it okay that we sometimes don't have a linkable?
|
||||
outputs[link_style] = outs
|
||||
libraries[link_style] = LinkInfos(
|
||||
default = LinkInfo(
|
||||
name = ctx.attrs.name,
|
||||
pre_flags = args,
|
||||
post_flags = post_link_flags,
|
||||
linkables = [linkable] if linkable else [],
|
||||
),
|
||||
)
|
||||
|
||||
sub_targets[link_style.value.replace("_", "-")] = [DefaultInfo(
|
||||
default_outputs = outputs[link_style],
|
||||
)]
|
||||
|
||||
# Create the default ouput for the library rule given it's link style and preferred linkage
|
||||
link_style = get_cxx_toolchain_info(ctx).linker_info.link_style
|
||||
actual_link_style = get_actual_link_style(link_style, preferred_linkage)
|
||||
output = outputs[actual_link_style]
|
||||
providers.append(DefaultInfo(
|
||||
default_outputs = output,
|
||||
sub_targets = sub_targets,
|
||||
))
|
||||
|
||||
# Propagate link info provider.
|
||||
providers.append(create_merged_link_info(
|
||||
ctx,
|
||||
# Add link info for each link style,
|
||||
libraries,
|
||||
preferred_linkage = preferred_linkage,
|
||||
# Export link info from non-exported deps (when necessary).
|
||||
deps = [inherited_link],
|
||||
# Export link info from out (exported) deps.
|
||||
exported_deps = [inherited_exported_link],
|
||||
))
|
||||
|
||||
# Propagate shared libraries up the tree.
|
||||
providers.append(merge_shared_libraries(
|
||||
ctx.actions,
|
||||
create_shared_libraries(ctx, solibs),
|
||||
filter(None, [x.get(SharedLibraryInfo) for x in exported_first_order_deps]),
|
||||
))
|
||||
|
||||
# Create, augment and provide the linkable graph.
|
||||
deps_linkable_graph = create_linkable_graph(
|
||||
ctx,
|
||||
deps = exported_first_order_deps,
|
||||
)
|
||||
|
||||
# Omnibus root provider.
|
||||
known_omnibus_root = is_known_omnibus_root(ctx)
|
||||
linkable_root = None
|
||||
if LinkStyle("static_pic") in libraries and (static_pic_lib or static_lib) and not ctx.attrs.header_only:
|
||||
# TODO(cjhopman): This doesn't support thin archives
|
||||
linkable_root = create_linkable_root(
|
||||
ctx,
|
||||
name = soname,
|
||||
link_infos = LinkInfos(default = LinkInfo(
|
||||
name = soname,
|
||||
pre_flags = cxx_attr_exported_linker_flags(ctx),
|
||||
linkables = [ArchiveLinkable(
|
||||
archive = Archive(
|
||||
artifact = static_pic_lib or static_lib,
|
||||
),
|
||||
linker_type = linker_type,
|
||||
link_whole = True,
|
||||
)],
|
||||
post_flags = cxx_attr_exported_post_linker_flags(ctx),
|
||||
)),
|
||||
deps = exported_first_order_deps,
|
||||
graph = deps_linkable_graph,
|
||||
create_shared_root = known_omnibus_root,
|
||||
)
|
||||
providers.append(linkable_root)
|
||||
|
||||
roots = {}
|
||||
|
||||
if linkable_root != None and known_omnibus_root:
|
||||
roots[ctx.label] = AnnotatedLinkableRoot(root = linkable_root)
|
||||
|
||||
linkable_graph = create_linkable_graph(
|
||||
ctx,
|
||||
node = create_linkable_graph_node(
|
||||
ctx,
|
||||
linkable_node = create_linkable_node(
|
||||
ctx = ctx,
|
||||
preferred_linkage = preferred_linkage,
|
||||
exported_deps = exported_first_order_deps,
|
||||
# If we don't have link input for this link style, we pass in `None` so
|
||||
# that omnibus knows to avoid it.
|
||||
link_infos = libraries,
|
||||
shared_libs = solibs,
|
||||
),
|
||||
roots = roots,
|
||||
excluded = {ctx.label: None} if not value_or(ctx.attrs.supports_merged_linking, True) else {},
|
||||
),
|
||||
children = [deps_linkable_graph],
|
||||
)
|
||||
|
||||
providers.append(linkable_graph)
|
||||
|
||||
providers.append(
|
||||
merge_link_group_lib_info(
|
||||
deps = first_order_deps + exported_first_order_deps,
|
||||
),
|
||||
)
|
||||
|
||||
return providers
|
||||
|
||||
def cxx_precompiled_header_impl(ctx: "context") -> ["provider"]:
|
||||
inherited_pp_infos = cxx_inherited_preprocessor_infos(ctx.attrs.deps)
|
||||
inherited_link = cxx_inherited_link_info(ctx, ctx.attrs.deps)
|
||||
return [
|
||||
DefaultInfo(default_outputs = [ctx.attrs.src]),
|
||||
cxx_merge_cpreprocessors(ctx, [], inherited_pp_infos),
|
||||
inherited_link,
|
||||
CPrecompiledHeaderInfo(header = ctx.attrs.src),
|
||||
]
|
||||
|
||||
def cxx_test_impl(ctx: "context") -> ["provider"]:
|
||||
link_group_info = get_link_group_info(ctx, filter_and_map_idx(LinkableGraph, cxx_attr_deps(ctx)))
|
||||
|
||||
# TODO(T110378115): have the runinfo contain the correct test running args
|
||||
params = CxxRuleConstructorParams(
|
||||
rule_type = "cxx_test",
|
||||
headers_layout = cxx_get_regular_cxx_headers_layout(ctx),
|
||||
srcs = get_srcs_with_flags(ctx),
|
||||
link_group_info = link_group_info,
|
||||
auto_link_group_specs = get_cxx_auto_link_group_specs(ctx, link_group_info),
|
||||
)
|
||||
output, comp_db_info, xcode_data_info = cxx_executable(ctx, params, is_cxx_test = True)
|
||||
|
||||
command = [cmd_args(output.binary).hidden(output.runtime_files)] + ctx.attrs.args
|
||||
|
||||
# Setup a RE executor based on the `remote_execution` param.
|
||||
re_executor = get_re_executor_from_props(ctx.attrs.remote_execution)
|
||||
|
||||
return inject_test_run_info(
|
||||
ctx,
|
||||
ExternalRunnerTestInfo(
|
||||
type = "gtest",
|
||||
command = command,
|
||||
env = ctx.attrs.env,
|
||||
labels = ctx.attrs.labels,
|
||||
contacts = ctx.attrs.contacts,
|
||||
default_executor = re_executor,
|
||||
# We implicitly make this test via the project root, instead of
|
||||
# the cell root (e.g. fbcode root).
|
||||
run_from_project_root = any([
|
||||
"buck2_run_from_project_root" in (ctx.attrs.labels or []),
|
||||
re_executor != None,
|
||||
]),
|
||||
use_project_relative_paths = re_executor != None,
|
||||
),
|
||||
) + [
|
||||
DefaultInfo(default_outputs = [output.binary], other_outputs = output.runtime_files, sub_targets = output.sub_targets),
|
||||
comp_db_info,
|
||||
xcode_data_info,
|
||||
]
|
||||
|
||||
def _get_params_for_android_binary_cxx_library() -> (CxxRuleSubTargetParams.type, CxxRuleProviderParams.type):
|
||||
sub_target_params = CxxRuleSubTargetParams(
|
||||
argsfiles = False,
|
||||
compilation_database = False,
|
||||
headers = False,
|
||||
link_group_map = False,
|
||||
xcode_data = False,
|
||||
)
|
||||
provider_params = CxxRuleProviderParams(
|
||||
compilation_database = False,
|
||||
omnibus_root = False,
|
||||
preprocessor_for_tests = False,
|
||||
)
|
||||
|
||||
return sub_target_params, provider_params
|
||||
87
vendor/cxx/tools/buck/prelude/cxx/cxx_bolt.bzl
vendored
Normal file
87
vendor/cxx/tools/buck/prelude/cxx/cxx_bolt.bzl
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# 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.
|
||||
|
||||
# BOLT (Binary Optimization Layout Tool) is a post link profile guided optimizer used for
|
||||
# performance-critical services in fbcode: https://www.internalfb.com/intern/wiki/HHVM-BOLT/
|
||||
|
||||
load("@prelude//:local_only.bzl", "link_cxx_binary_locally")
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
|
||||
def cxx_use_bolt(ctx: "context") -> bool.type:
|
||||
cxx_toolchain_info = get_cxx_toolchain_info(ctx)
|
||||
return cxx_toolchain_info.bolt_enabled and ctx.attrs.bolt_profile != None
|
||||
|
||||
def bolt_gdb_index(ctx: "context", bolt_output: "artifact", identifier: [str.type, None]) -> "artifact":
|
||||
# Run gdb-indexer
|
||||
# gdb-indexer <input_binary> -o <output_binary>
|
||||
gdb_index_output_name = bolt_output.short_path.removesuffix("-pre_gdb_index") + "-gdb_index"
|
||||
gdb_index_output = ctx.actions.declare_output(gdb_index_output_name)
|
||||
gdb_index_args = cmd_args(
|
||||
ctx.attrs.bolt_gdb_index,
|
||||
bolt_output,
|
||||
"-o",
|
||||
gdb_index_output.as_output(),
|
||||
)
|
||||
ctx.actions.run(
|
||||
gdb_index_args,
|
||||
category = "gdb_index",
|
||||
identifier = identifier,
|
||||
local_only = link_cxx_binary_locally(ctx),
|
||||
)
|
||||
|
||||
# Run objcopy
|
||||
# objcopy -R .gdb_index --add-section=.gdb_index=<gdb_index_binary> <input_binary> <output_binary>
|
||||
objcopy_output_name = gdb_index_output_name.removesuffix("-gdb_index")
|
||||
objcopy_output = ctx.actions.declare_output(objcopy_output_name)
|
||||
objcopy_args = cmd_args(
|
||||
get_cxx_toolchain_info(ctx).binary_utilities_info.objcopy,
|
||||
"-R",
|
||||
".gdb_index",
|
||||
cmd_args(gdb_index_output, format = "--add-section=.gdb_index={}"),
|
||||
bolt_output,
|
||||
objcopy_output.as_output(),
|
||||
)
|
||||
ctx.actions.run(
|
||||
objcopy_args,
|
||||
category = "objcopy",
|
||||
identifier = identifier,
|
||||
local_only = link_cxx_binary_locally(ctx),
|
||||
)
|
||||
|
||||
return objcopy_output
|
||||
|
||||
def bolt(ctx: "context", prebolt_output: "artifact", identifier: [str.type, None]) -> "artifact":
|
||||
output_name = prebolt_output.short_path.removesuffix("-wrapper") + ("-pre_gdb_index" if (ctx.attrs.bolt_gdb_index != None) else "")
|
||||
postbolt_output = ctx.actions.declare_output(output_name)
|
||||
bolt_msdk = get_cxx_toolchain_info(ctx).binary_utilities_info.bolt_msdk
|
||||
|
||||
if not bolt_msdk or not cxx_use_bolt(ctx):
|
||||
fail("Cannot use bolt if bolt_msdk is not available or bolt profile is not available")
|
||||
args = cmd_args()
|
||||
|
||||
# bolt command format:
|
||||
# {llvm_bolt} {input_bin} -o $OUT -data={fdata} {args}
|
||||
args.add(
|
||||
cmd_args(bolt_msdk, format = "{}/bin/llvm-bolt"),
|
||||
prebolt_output,
|
||||
"-o",
|
||||
postbolt_output.as_output(),
|
||||
cmd_args(ctx.attrs.bolt_profile, format = "-data={}"),
|
||||
ctx.attrs.bolt_flags,
|
||||
)
|
||||
|
||||
ctx.actions.run(
|
||||
args,
|
||||
category = "bolt",
|
||||
identifier = identifier,
|
||||
local_only = get_cxx_toolchain_info(ctx).linker_info.link_binaries_locally,
|
||||
)
|
||||
|
||||
if ctx.attrs.bolt_gdb_index != None:
|
||||
return bolt_gdb_index(ctx, postbolt_output, identifier)
|
||||
|
||||
return postbolt_output
|
||||
34
vendor/cxx/tools/buck/prelude/cxx/cxx_context.bzl
vendored
Normal file
34
vendor/cxx/tools/buck/prelude/cxx/cxx_context.bzl
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# 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//apple:apple_toolchain_types.bzl", "AppleToolchainInfo")
|
||||
load("@prelude//cxx:cxx_toolchain_types.bzl", "CxxPlatformInfo", "CxxToolchainInfo")
|
||||
|
||||
# The functions below allow the Cxx rules to find toolchain providers
|
||||
# from different rule contexts. For example, the Cxx functions are
|
||||
# re-used by non-`cxx_` rules (e.g., the Apple rules) but the toolchain
|
||||
# setup on such rules might/would be different.
|
||||
#
|
||||
# The functions should be used throughout the Cxx rules to get
|
||||
# the required providers instead of going via the `_cxx_toolchain`
|
||||
# field of the `ctx`.
|
||||
#
|
||||
# In an ideal world, we would have been injecting all these from
|
||||
# the top level but as part of the transition to support
|
||||
# `apple_toolchain`, we want to make progress now.
|
||||
|
||||
def get_cxx_platform_info(ctx: "context") -> "CxxPlatformInfo":
|
||||
apple_toolchain = getattr(ctx.attrs, "_apple_toolchain", None)
|
||||
if apple_toolchain:
|
||||
return apple_toolchain[AppleToolchainInfo].cxx_platform_info
|
||||
return ctx.attrs._cxx_toolchain[CxxPlatformInfo]
|
||||
|
||||
def get_cxx_toolchain_info(ctx: "context") -> "CxxToolchainInfo":
|
||||
apple_toolchain = getattr(ctx.attrs, "_apple_toolchain", None)
|
||||
if apple_toolchain:
|
||||
return apple_toolchain[AppleToolchainInfo].cxx_toolchain_info
|
||||
return ctx.attrs._cxx_toolchain[CxxToolchainInfo]
|
||||
530
vendor/cxx/tools/buck/prelude/cxx/cxx_executable.bzl
vendored
Normal file
530
vendor/cxx/tools/buck/prelude/cxx/cxx_executable.bzl
vendored
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
# 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//:local_only.bzl", "link_cxx_binary_locally")
|
||||
load(
|
||||
"@prelude//:resources.bzl",
|
||||
"create_resource_db",
|
||||
"gather_resources",
|
||||
)
|
||||
load(
|
||||
"@prelude//apple:apple_frameworks.bzl",
|
||||
"build_link_args_with_deduped_framework_flags",
|
||||
"create_frameworks_linkable",
|
||||
"get_frameworks_link_info_by_deduping_link_infos",
|
||||
)
|
||||
load(
|
||||
"@prelude//cxx:cxx_bolt.bzl",
|
||||
"cxx_use_bolt",
|
||||
)
|
||||
load(
|
||||
"@prelude//ide_integrations:xcode.bzl",
|
||||
"XCODE_DATA_SUB_TARGET",
|
||||
"generate_xcode_data",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_groups.bzl",
|
||||
"LinkGroupLib",
|
||||
"gather_link_group_libs",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"LinkArgs",
|
||||
"LinkInfo",
|
||||
"LinkInfos",
|
||||
"LinkStyle",
|
||||
"LinkedObject", # @unused Used as a type
|
||||
"ObjectsLinkable",
|
||||
"SharedLibLinkable",
|
||||
"merge_link_infos",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkable_graph.bzl",
|
||||
"create_linkable_graph",
|
||||
"get_linkable_graph_node_map_func",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkables.bzl",
|
||||
"linkables",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:shared_libraries.bzl",
|
||||
"merge_shared_libraries",
|
||||
"traverse_shared_library_info",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:utils.bzl",
|
||||
"expect",
|
||||
"flatten_dict",
|
||||
)
|
||||
load(
|
||||
":comp_db.bzl",
|
||||
"CxxCompilationDbInfo", # @unused Used as a type
|
||||
"create_compilation_database",
|
||||
"make_compilation_db_info",
|
||||
)
|
||||
load(
|
||||
":compile.bzl",
|
||||
"compile_cxx",
|
||||
"create_compile_cmds",
|
||||
)
|
||||
load(":cxx_context.bzl", "get_cxx_platform_info", "get_cxx_toolchain_info")
|
||||
load(
|
||||
":cxx_library_utility.bzl",
|
||||
"ARGSFILES_SUBTARGET",
|
||||
"cxx_attr_deps",
|
||||
"cxx_attr_link_style",
|
||||
"cxx_attr_linker_flags",
|
||||
"cxx_attr_resources",
|
||||
"cxx_is_gnu",
|
||||
)
|
||||
load(
|
||||
":cxx_link_utility.bzl",
|
||||
"executable_shared_lib_arguments",
|
||||
)
|
||||
load(
|
||||
":cxx_types.bzl",
|
||||
"CxxRuleConstructorParams", # @unused Used as a type
|
||||
)
|
||||
load(
|
||||
":link.bzl",
|
||||
"cxx_link",
|
||||
)
|
||||
load(
|
||||
":link_groups.bzl",
|
||||
"LINK_GROUP_MAP_DATABASE_SUB_TARGET",
|
||||
"create_link_group",
|
||||
"get_filtered_labels_to_links_map",
|
||||
"get_filtered_links",
|
||||
"get_filtered_targets",
|
||||
"get_link_group",
|
||||
"get_link_group_map_json",
|
||||
"get_link_group_preferred_linkage",
|
||||
)
|
||||
load(
|
||||
":preprocessor.bzl",
|
||||
"cxx_inherited_preprocessor_infos",
|
||||
"cxx_private_preprocessor_info",
|
||||
)
|
||||
|
||||
_CxxExecutableOutput = record(
|
||||
binary = "artifact",
|
||||
# Files that will likely need to be included as .hidden() arguments
|
||||
# when executing the executable (ex. RunInfo())
|
||||
runtime_files = ["_arglike"],
|
||||
sub_targets = {str.type: [DefaultInfo.type]},
|
||||
# The LinkArgs used to create the final executable in 'binary'.
|
||||
link_args = [LinkArgs.type],
|
||||
# External components needed to debug the executable.
|
||||
external_debug_info = ["_arglike"],
|
||||
shared_libs = {str.type: LinkedObject.type},
|
||||
# All link group links that were generated in the executable.
|
||||
auto_link_groups = field({str.type: LinkedObject.type}, {}),
|
||||
)
|
||||
|
||||
# returns a tuple of the runnable binary as an artifact, a list of its runtime files as artifacts and a sub targets map, and the CxxCompilationDbInfo provider
|
||||
def cxx_executable(ctx: "context", impl_params: CxxRuleConstructorParams.type, is_cxx_test: bool.type = False) -> (_CxxExecutableOutput.type, CxxCompilationDbInfo.type, "XcodeDataInfo"):
|
||||
# Gather preprocessor inputs.
|
||||
preprocessor_deps = cxx_attr_deps(ctx) + filter(None, [ctx.attrs.precompiled_header])
|
||||
(own_preprocessor_info, test_preprocessor_infos) = cxx_private_preprocessor_info(
|
||||
ctx,
|
||||
impl_params.headers_layout,
|
||||
raw_headers = ctx.attrs.raw_headers,
|
||||
extra_preprocessors = impl_params.extra_preprocessors,
|
||||
non_exported_deps = preprocessor_deps,
|
||||
is_test = is_cxx_test,
|
||||
)
|
||||
inherited_preprocessor_infos = cxx_inherited_preprocessor_infos(preprocessor_deps) + impl_params.extra_preprocessors_info
|
||||
|
||||
# The link style to use.
|
||||
link_style = cxx_attr_link_style(ctx)
|
||||
|
||||
sub_targets = {}
|
||||
|
||||
# Compile objects.
|
||||
compile_cmd_output = create_compile_cmds(
|
||||
ctx,
|
||||
impl_params,
|
||||
[own_preprocessor_info] + test_preprocessor_infos,
|
||||
inherited_preprocessor_infos,
|
||||
)
|
||||
cxx_outs = compile_cxx(ctx, compile_cmd_output.source_commands.src_compile_cmds, pic = link_style != LinkStyle("static"))
|
||||
sub_targets[ARGSFILES_SUBTARGET] = [compile_cmd_output.source_commands.argsfiles_info]
|
||||
|
||||
# Compilation DB.
|
||||
comp_db = create_compilation_database(ctx, compile_cmd_output.comp_db_commands.src_compile_cmds)
|
||||
sub_targets["compilation-database"] = [comp_db]
|
||||
comp_db_info = make_compilation_db_info(compile_cmd_output.comp_db_commands.src_compile_cmds, get_cxx_toolchain_info(ctx), get_cxx_platform_info(ctx))
|
||||
|
||||
# Link deps
|
||||
link_deps = linkables(cxx_attr_deps(ctx)) + impl_params.extra_link_deps
|
||||
|
||||
# Link Groups
|
||||
link_group = get_link_group(ctx)
|
||||
link_group_info = impl_params.link_group_info
|
||||
|
||||
if link_group_info:
|
||||
link_groups = link_group_info.groups
|
||||
link_group_mappings = link_group_info.mappings
|
||||
link_group_deps = [mapping.root.node.linkable_graph for group in link_group_info.groups for mapping in group.mappings if mapping.root != None]
|
||||
else:
|
||||
link_groups = []
|
||||
link_group_mappings = {}
|
||||
link_group_deps = []
|
||||
link_group_preferred_linkage = get_link_group_preferred_linkage(link_groups)
|
||||
|
||||
# Create the linkable graph with the binary's deps and any link group deps.
|
||||
linkable_graph = create_linkable_graph(
|
||||
ctx,
|
||||
children = [d.linkable_graph for d in link_deps] + link_group_deps,
|
||||
)
|
||||
|
||||
# Gather link inputs.
|
||||
own_link_flags = cxx_attr_linker_flags(ctx) + impl_params.extra_link_flags + impl_params.extra_exported_link_flags
|
||||
own_binary_link_flags = ctx.attrs.binary_linker_flags + own_link_flags
|
||||
inherited_link = merge_link_infos(ctx, [d.merged_link_info for d in link_deps])
|
||||
frameworks_linkable = create_frameworks_linkable(ctx)
|
||||
|
||||
# Link group libs.
|
||||
link_group_libs = {}
|
||||
auto_link_groups = {}
|
||||
labels_to_links_map = {}
|
||||
|
||||
if not link_group_mappings:
|
||||
dep_links = build_link_args_with_deduped_framework_flags(
|
||||
ctx,
|
||||
inherited_link,
|
||||
frameworks_linkable,
|
||||
link_style,
|
||||
prefer_stripped = ctx.attrs.prefer_stripped_objects,
|
||||
)
|
||||
else:
|
||||
linkable_graph_node_map = get_linkable_graph_node_map_func(linkable_graph)()
|
||||
|
||||
# If we're using auto-link-groups, where we generate the link group links
|
||||
# in the prelude, the link group map will give us the link group libs.
|
||||
# Otherwise, pull them from the `LinkGroupLibInfo` provider from out deps.
|
||||
if impl_params.auto_link_group_specs != None:
|
||||
for link_group_spec in impl_params.auto_link_group_specs:
|
||||
# NOTE(agallagher): It might make sense to move this down to be
|
||||
# done when we generated the links for the executable, so we can
|
||||
# handle the case when a link group can depend on the executable.
|
||||
link_group_lib = create_link_group(
|
||||
ctx = ctx,
|
||||
spec = link_group_spec,
|
||||
executable_deps = [
|
||||
dep.linkable_graph.nodes.value.label
|
||||
for dep in link_deps
|
||||
],
|
||||
root_link_group = link_group,
|
||||
linkable_graph_node_map = linkable_graph_node_map,
|
||||
linker_flags = own_link_flags,
|
||||
link_group_mappings = link_group_mappings,
|
||||
link_group_preferred_linkage = {},
|
||||
#link_style = LinkStyle("static_pic"),
|
||||
# TODO(agallagher): We should generate link groups via post-order
|
||||
# traversal to get inter-group deps correct.
|
||||
#link_group_libs = {},
|
||||
prefer_stripped_objects = ctx.attrs.prefer_stripped_objects,
|
||||
category_suffix = "link_group",
|
||||
)
|
||||
auto_link_groups[link_group_spec.group.name] = link_group_lib
|
||||
if link_group_spec.is_shared_lib:
|
||||
link_group_libs[link_group_spec.group.name] = LinkGroupLib(
|
||||
shared_libs = {link_group_spec.name: link_group_lib},
|
||||
shared_link_infos = LinkInfos(
|
||||
default = LinkInfo(
|
||||
linkables = [
|
||||
SharedLibLinkable(lib = link_group_lib.output),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else:
|
||||
link_group_libs = gather_link_group_libs(
|
||||
children = [d.link_group_lib_info for d in link_deps],
|
||||
)
|
||||
|
||||
# TODO(T110378098): Similar to shared libraries, we need to identify all the possible
|
||||
# scenarios for which we need to propagate up link info and simplify this logic. For now
|
||||
# base which links to use based on whether link groups are defined.
|
||||
labels_to_links_map = get_filtered_labels_to_links_map(
|
||||
linkable_graph_node_map,
|
||||
link_group,
|
||||
link_group_mappings,
|
||||
link_group_preferred_linkage,
|
||||
link_group_libs = link_group_libs,
|
||||
link_style = link_style,
|
||||
deps = [d.linkable_graph.nodes.value.label for d in link_deps],
|
||||
is_executable_link = True,
|
||||
prefer_stripped = ctx.attrs.prefer_stripped_objects,
|
||||
)
|
||||
|
||||
if is_cxx_test and link_group != None:
|
||||
# if a cpp_unittest is part of the link group, we need to traverse through all deps
|
||||
# from the root again to ensure we link in gtest deps
|
||||
labels_to_links_map = labels_to_links_map | get_filtered_labels_to_links_map(
|
||||
linkable_graph_node_map,
|
||||
None,
|
||||
link_group_mappings,
|
||||
link_group_preferred_linkage,
|
||||
link_style,
|
||||
deps = [d.linkable_graph.nodes.value.label for d in link_deps],
|
||||
is_executable_link = True,
|
||||
prefer_stripped = ctx.attrs.prefer_stripped_objects,
|
||||
)
|
||||
|
||||
filtered_links = get_filtered_links(labels_to_links_map)
|
||||
filtered_targets = get_filtered_targets(labels_to_links_map)
|
||||
|
||||
# Unfortunately, link_groups does not use MergedLinkInfo to represent the args
|
||||
# for the resolved nodes in the graph.
|
||||
# Thus, we have no choice but to traverse all the nodes to dedupe the framework linker args.
|
||||
frameworks_link_info = get_frameworks_link_info_by_deduping_link_infos(ctx, filtered_links, frameworks_linkable)
|
||||
if frameworks_link_info:
|
||||
filtered_links.append(frameworks_link_info)
|
||||
|
||||
dep_links = LinkArgs(infos = filtered_links)
|
||||
sub_targets[LINK_GROUP_MAP_DATABASE_SUB_TARGET] = [get_link_group_map_json(ctx, filtered_targets)]
|
||||
|
||||
# Set up shared libraries symlink tree only when needed
|
||||
shared_libs = {}
|
||||
|
||||
# Only setup a shared library symlink tree when shared linkage or link_groups is used
|
||||
gnu_use_link_groups = cxx_is_gnu(ctx) and link_group_mappings
|
||||
if link_style == LinkStyle("shared") or gnu_use_link_groups:
|
||||
shlib_info = merge_shared_libraries(
|
||||
ctx.actions,
|
||||
deps = [d.shared_library_info for d in link_deps],
|
||||
)
|
||||
|
||||
def is_link_group_shlib(label: "label"):
|
||||
# If this maps to a link group which we have a `LinkGroupLibInfo` for,
|
||||
# then we'll handlet his below.
|
||||
# buildifier: disable=uninitialized
|
||||
if label in link_group_mappings and link_group_mappings[label] in link_group_libs:
|
||||
return False
|
||||
|
||||
# if using link_groups, only materialize the link_group shlibs
|
||||
return label in labels_to_links_map and labels_to_links_map[label].link_style == LinkStyle("shared") # buildifier: disable=uninitialized
|
||||
|
||||
for name, shared_lib in traverse_shared_library_info(shlib_info).items():
|
||||
label = shared_lib.label
|
||||
if not gnu_use_link_groups or is_link_group_shlib(label):
|
||||
shared_libs[name] = shared_lib.lib
|
||||
|
||||
if gnu_use_link_groups:
|
||||
# All explicit link group libs (i.e. libraries that set `link_group`).
|
||||
link_group_names = {n: None for n in link_group_mappings.values()}
|
||||
for name, link_group_lib in link_group_libs.items():
|
||||
# Is it possible to find a link group lib in our graph without it
|
||||
# having a mapping setup?
|
||||
expect(name in link_group_names)
|
||||
shared_libs.update(link_group_lib.shared_libs)
|
||||
|
||||
toolchain_info = get_cxx_toolchain_info(ctx)
|
||||
linker_info = toolchain_info.linker_info
|
||||
links = [
|
||||
LinkArgs(infos = [
|
||||
LinkInfo(
|
||||
pre_flags = own_binary_link_flags,
|
||||
linkables = [ObjectsLinkable(
|
||||
objects = [out.object for out in cxx_outs],
|
||||
linker_type = linker_info.type,
|
||||
link_whole = True,
|
||||
)],
|
||||
external_debug_info = (
|
||||
[out.object for out in cxx_outs if out.object_has_external_debug_info] +
|
||||
[out.external_debug_info for out in cxx_outs if out.external_debug_info != None]
|
||||
),
|
||||
),
|
||||
]),
|
||||
dep_links,
|
||||
] + impl_params.extra_link_args
|
||||
|
||||
binary, runtime_files, shared_libs_symlink_tree, extra_args = _link_into_executable(
|
||||
ctx,
|
||||
links,
|
||||
# If shlib lib tree generation is enabled, pass in the shared libs (which
|
||||
# will trigger the necessary link tree and link args).
|
||||
shared_libs if impl_params.exe_shared_libs_link_tree else {},
|
||||
linker_info.link_weight,
|
||||
linker_info.binary_extension,
|
||||
prefer_local = False if impl_params.force_full_hybrid_if_capable else link_cxx_binary_locally(ctx),
|
||||
enable_distributed_thinlto = ctx.attrs.enable_distributed_thinlto,
|
||||
strip = impl_params.strip_executable,
|
||||
strip_args_factory = impl_params.strip_args_factory,
|
||||
link_postprocessor = impl_params.link_postprocessor,
|
||||
force_full_hybrid_if_capable = impl_params.force_full_hybrid_if_capable,
|
||||
)
|
||||
|
||||
# Define the xcode data sub target
|
||||
xcode_data_default_info, xcode_data_info = generate_xcode_data(
|
||||
ctx,
|
||||
rule_type = impl_params.rule_type,
|
||||
output = binary.output,
|
||||
populate_rule_specific_attributes_func = impl_params.cxx_populate_xcode_attributes_func,
|
||||
srcs = impl_params.srcs + impl_params.additional.srcs,
|
||||
argsfiles_by_ext = compile_cmd_output.source_commands.argsfile_by_ext,
|
||||
product_name = get_cxx_excutable_product_name(ctx),
|
||||
)
|
||||
sub_targets[XCODE_DATA_SUB_TARGET] = xcode_data_default_info
|
||||
|
||||
# Info about dynamic-linked libraries for fbpkg integration:
|
||||
# - the symlink dir that's part of RPATH
|
||||
# - sub-sub-targets that reference shared library dependencies and their respective dwp
|
||||
# - [shared-libraries] - a json map that references the above rules.
|
||||
if shared_libs_symlink_tree:
|
||||
sub_targets["rpath-tree"] = [DefaultInfo(default_outputs = [shared_libs_symlink_tree])]
|
||||
sub_targets["shared-libraries"] = [DefaultInfo(
|
||||
default_outputs = [ctx.actions.write_json(
|
||||
binary.output.basename + ".shared-libraries.json",
|
||||
{
|
||||
"libraries": ["{}:{}[shared-libraries][{}]".format(ctx.label.path, ctx.label.name, name) for name in shared_libs.keys()],
|
||||
"librariesdwp": ["{}:{}[shared-libraries][{}][dwp]".format(ctx.label.path, ctx.label.name, name) for name, lib in shared_libs.items() if lib.dwp],
|
||||
"rpathtree": ["{}:{}[rpath-tree]".format(ctx.label.path, ctx.label.name)] if shared_libs_symlink_tree else [],
|
||||
},
|
||||
)],
|
||||
sub_targets = {
|
||||
name: [DefaultInfo(
|
||||
default_outputs = [lib.output],
|
||||
sub_targets = {"dwp": [DefaultInfo(default_outputs = [lib.dwp])]} if lib.dwp else {},
|
||||
)]
|
||||
for name, lib in shared_libs.items()
|
||||
},
|
||||
)]
|
||||
|
||||
# TODO(T110378140): We can't really enable this yet, as Python binaries
|
||||
# consuming C++ binaries as resources don't know how to handle the
|
||||
# extraneous debug paths and will crash. We probably need to add a special
|
||||
# exported resources provider and make sure we handle the workflows.
|
||||
# Add any referenced debug paths to runtime files.
|
||||
#runtime_files.extend(binary.external_debug_info)
|
||||
|
||||
# If we have some resources, write it to the resources JSON file and add
|
||||
# it and all resources to "runtime_files" so that we make to materialize
|
||||
# them with the final binary.
|
||||
resources = flatten_dict(gather_resources(
|
||||
label = ctx.label,
|
||||
resources = cxx_attr_resources(ctx),
|
||||
deps = cxx_attr_deps(ctx),
|
||||
).values())
|
||||
if resources:
|
||||
runtime_files.append(create_resource_db(
|
||||
ctx = ctx,
|
||||
name = binary.output.basename + ".resources.json",
|
||||
binary = binary.output,
|
||||
resources = resources,
|
||||
))
|
||||
for resource, other in resources.values():
|
||||
runtime_files.append(resource)
|
||||
runtime_files.extend(other)
|
||||
|
||||
if binary.dwp:
|
||||
# A `dwp` sub-target which generates the `.dwp` file for this binary.
|
||||
sub_targets["dwp"] = [DefaultInfo(default_outputs = [binary.dwp])]
|
||||
|
||||
# If bolt is not ran, binary.prebolt_output will be the same as binary.output. Only
|
||||
# expose binary.prebolt_output if cxx_use_bolt(ctx) is True to avoid confusion
|
||||
if cxx_use_bolt(ctx):
|
||||
sub_targets["prebolt"] = [DefaultInfo(default_outputs = [binary.prebolt_output])]
|
||||
|
||||
(linker_map, binary_for_linker_map) = _linker_map(
|
||||
ctx,
|
||||
binary,
|
||||
[LinkArgs(flags = extra_args)] + links,
|
||||
prefer_local = link_cxx_binary_locally(ctx, toolchain_info),
|
||||
link_weight = linker_info.link_weight,
|
||||
)
|
||||
sub_targets["linker-map"] = [DefaultInfo(default_outputs = [linker_map], other_outputs = [binary_for_linker_map])]
|
||||
|
||||
sub_targets["linker.argsfile"] = [DefaultInfo(
|
||||
default_outputs = [binary.linker_argsfile],
|
||||
)]
|
||||
|
||||
if linker_info.supports_distributed_thinlto and ctx.attrs.enable_distributed_thinlto:
|
||||
sub_targets["index.argsfile"] = [DefaultInfo(
|
||||
default_outputs = [binary.index_argsfile],
|
||||
)]
|
||||
|
||||
return _CxxExecutableOutput(
|
||||
binary = binary.output,
|
||||
runtime_files = runtime_files,
|
||||
sub_targets = sub_targets,
|
||||
link_args = links,
|
||||
external_debug_info = binary.external_debug_info,
|
||||
shared_libs = shared_libs,
|
||||
auto_link_groups = auto_link_groups,
|
||||
), comp_db_info, xcode_data_info
|
||||
|
||||
# Returns a tuple of:
|
||||
# - the resulting executable
|
||||
# - list of files/directories that should be present for executable to be run successfully
|
||||
# - optional shared libs symlink tree symlinked_dir action
|
||||
# - extra linking args (for the shared_libs)
|
||||
def _link_into_executable(
|
||||
ctx: "context",
|
||||
links: [LinkArgs.type],
|
||||
shared_libs: {str.type: LinkedObject.type},
|
||||
link_weight: int.type,
|
||||
binary_extension: str.type,
|
||||
prefer_local: bool.type = False,
|
||||
enable_distributed_thinlto = False,
|
||||
strip: bool.type = False,
|
||||
strip_args_factory = None,
|
||||
link_postprocessor: ["cmd_args", None] = None,
|
||||
force_full_hybrid_if_capable: bool.type = False) -> (LinkedObject.type, ["_arglike"], ["artifact", None], [""]):
|
||||
output = ctx.actions.declare_output("{}{}".format(get_cxx_excutable_product_name(ctx), "." + binary_extension if binary_extension else ""))
|
||||
extra_args, runtime_files, shared_libs_symlink_tree = executable_shared_lib_arguments(
|
||||
ctx.actions,
|
||||
get_cxx_toolchain_info(ctx),
|
||||
output,
|
||||
shared_libs,
|
||||
)
|
||||
exe = cxx_link(
|
||||
ctx,
|
||||
[LinkArgs(flags = extra_args)] + links,
|
||||
output,
|
||||
prefer_local = prefer_local,
|
||||
link_weight = link_weight,
|
||||
enable_distributed_thinlto = enable_distributed_thinlto,
|
||||
category_suffix = "executable",
|
||||
strip = strip,
|
||||
strip_args_factory = strip_args_factory,
|
||||
executable_link = True,
|
||||
link_postprocessor = link_postprocessor,
|
||||
force_full_hybrid_if_capable = force_full_hybrid_if_capable,
|
||||
)
|
||||
return (exe, runtime_files, shared_libs_symlink_tree, extra_args)
|
||||
|
||||
def _linker_map(
|
||||
ctx: "context",
|
||||
binary: LinkedObject.type,
|
||||
links: [LinkArgs.type],
|
||||
prefer_local: bool.type,
|
||||
link_weight: int.type) -> ("artifact", "artifact"):
|
||||
identifier = binary.output.short_path + ".linker-map-binary"
|
||||
binary_for_linker_map = ctx.actions.declare_output(identifier)
|
||||
linker_map = ctx.actions.declare_output(binary.output.short_path + ".linker-map")
|
||||
cxx_link(
|
||||
ctx,
|
||||
links,
|
||||
binary_for_linker_map,
|
||||
category_suffix = "linker_map",
|
||||
linker_map = linker_map,
|
||||
prefer_local = prefer_local,
|
||||
link_weight = link_weight,
|
||||
identifier = identifier,
|
||||
generate_dwp = False,
|
||||
)
|
||||
return (
|
||||
linker_map,
|
||||
binary_for_linker_map,
|
||||
)
|
||||
|
||||
def get_cxx_excutable_product_name(ctx: "context") -> str.type:
|
||||
return ctx.label.name + ("-wrapper" if cxx_use_bolt(ctx) else "")
|
||||
1093
vendor/cxx/tools/buck/prelude/cxx/cxx_library.bzl
vendored
Normal file
1093
vendor/cxx/tools/buck/prelude/cxx/cxx_library.bzl
vendored
Normal file
File diff suppressed because it is too large
Load diff
165
vendor/cxx/tools/buck/prelude/cxx/cxx_library_utility.bzl
vendored
Normal file
165
vendor/cxx/tools/buck/prelude/cxx/cxx_library_utility.bzl
vendored
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# 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//linking:link_info.bzl",
|
||||
"LinkStyle",
|
||||
"Linkage",
|
||||
"MergedLinkInfo",
|
||||
"merge_link_infos",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:utils.bzl",
|
||||
"expect",
|
||||
"flatten",
|
||||
"from_named_set",
|
||||
"value_or",
|
||||
)
|
||||
load(":cxx_context.bzl", "get_cxx_platform_info", "get_cxx_toolchain_info")
|
||||
load(
|
||||
":headers.bzl",
|
||||
"cxx_attr_header_namespace",
|
||||
)
|
||||
load(
|
||||
":linker.bzl",
|
||||
"get_shared_library_name",
|
||||
)
|
||||
load(":platform.bzl", "cxx_by_platform")
|
||||
|
||||
ARGSFILES_SUBTARGET = "argsfiles"
|
||||
|
||||
# The dependencies
|
||||
def cxx_attr_deps(ctx: "context") -> ["dependency"]:
|
||||
return (
|
||||
ctx.attrs.deps +
|
||||
flatten(cxx_by_platform(ctx, ctx.attrs.platform_deps)) +
|
||||
(getattr(ctx.attrs, "deps_query", []) or [])
|
||||
)
|
||||
|
||||
def cxx_attr_exported_deps(ctx: "context") -> ["dependency"]:
|
||||
return ctx.attrs.exported_deps + flatten(cxx_by_platform(ctx, ctx.attrs.exported_platform_deps))
|
||||
|
||||
def cxx_attr_exported_linker_flags(ctx: "context") -> [""]:
|
||||
return (
|
||||
ctx.attrs.exported_linker_flags +
|
||||
flatten(cxx_by_platform(ctx, ctx.attrs.exported_platform_linker_flags))
|
||||
)
|
||||
|
||||
def cxx_attr_exported_post_linker_flags(ctx: "context") -> [""]:
|
||||
return (
|
||||
ctx.attrs.exported_post_linker_flags +
|
||||
flatten(cxx_by_platform(ctx, ctx.attrs.exported_post_platform_linker_flags))
|
||||
)
|
||||
|
||||
def cxx_inherited_link_info(ctx, first_order_deps: ["dependency"]) -> MergedLinkInfo.type:
|
||||
# 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 merge_link_infos(ctx, filter(None, [x.get(MergedLinkInfo) for x in first_order_deps]))
|
||||
|
||||
# Linker flags
|
||||
def cxx_attr_linker_flags(ctx: "context") -> [""]:
|
||||
return (
|
||||
ctx.attrs.linker_flags +
|
||||
flatten(cxx_by_platform(ctx, ctx.attrs.platform_linker_flags))
|
||||
)
|
||||
|
||||
def cxx_attr_link_style(ctx: "context") -> LinkStyle.type:
|
||||
if ctx.attrs.link_style != None:
|
||||
return LinkStyle(ctx.attrs.link_style)
|
||||
if ctx.attrs.defaults != None:
|
||||
# v1 equivalent code is in CxxConstructorArg::getDefaultFlavors and ParserWithConfigurableAttributes::applyDefaultFlavors
|
||||
# Only values in the map are used by v1 as flavors, copy this behavior and return the first value which is compatible with link style.
|
||||
v1_flavors = ctx.attrs.defaults.values()
|
||||
for s in [LinkStyle("static"), LinkStyle("static_pic"), LinkStyle("shared")]:
|
||||
if s.value in v1_flavors:
|
||||
return s
|
||||
return get_cxx_toolchain_info(ctx).linker_info.link_style
|
||||
|
||||
def cxx_attr_preferred_linkage(ctx: "context") -> Linkage.type:
|
||||
preferred_linkage = ctx.attrs.preferred_linkage
|
||||
|
||||
# force_static is deprecated, but it has precedence over preferred_linkage
|
||||
if getattr(ctx.attrs, "force_static", False):
|
||||
preferred_linkage = "static"
|
||||
|
||||
return Linkage(preferred_linkage)
|
||||
|
||||
def cxx_attr_resources(ctx: "context") -> {str.type: ("artifact", ["_arglike"])}:
|
||||
"""
|
||||
Return the resources provided by this rule, as a map of resource name to
|
||||
a tuple of the resource artifact and any "other" outputs exposed by it.
|
||||
"""
|
||||
|
||||
resources = {}
|
||||
namespace = cxx_attr_header_namespace(ctx)
|
||||
|
||||
# Use getattr, as apple rules don't have a `resources` parameter.
|
||||
for name, resource in from_named_set(getattr(ctx.attrs, "resources", {})).items():
|
||||
if type(resource) == "artifact":
|
||||
other = []
|
||||
else:
|
||||
info = resource[DefaultInfo]
|
||||
expect(
|
||||
len(info.default_outputs) == 1,
|
||||
"expected exactly one default output from {} ({})"
|
||||
.format(resource, info.default_outputs),
|
||||
)
|
||||
[resource] = info.default_outputs
|
||||
other = info.other_outputs
|
||||
resources[paths.join(namespace, name)] = (resource, other)
|
||||
|
||||
return resources
|
||||
|
||||
def cxx_mk_shlib_intf(
|
||||
ctx: "context",
|
||||
name: str.type,
|
||||
shared_lib: "artifact") -> "artifact":
|
||||
"""
|
||||
Convert the given shared library into an interface used for linking.
|
||||
"""
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
args = cmd_args(linker_info.mk_shlib_intf[RunInfo])
|
||||
args.add(shared_lib)
|
||||
output = ctx.actions.declare_output(
|
||||
get_shared_library_name(linker_info, name + "-interface"),
|
||||
)
|
||||
args.add(output.as_output())
|
||||
ctx.actions.run(args, category = "generate_shared_library_interface")
|
||||
return output
|
||||
|
||||
def cxx_is_gnu(ctx: "context") -> bool.type:
|
||||
return get_cxx_toolchain_info(ctx).linker_info.type == "gnu"
|
||||
|
||||
def cxx_use_link_groups(ctx: "context") -> bool.type:
|
||||
# Link groups is enabled by default in darwin
|
||||
return cxx_is_gnu(ctx) and value_or(ctx.attrs.use_link_groups, False)
|
||||
|
||||
def cxx_use_shlib_intfs(ctx: "context") -> bool.type:
|
||||
"""
|
||||
Return whether we should use shared library interfaces for linking.
|
||||
"""
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
|
||||
# TODO(T110378128): Apple currently uses the same configuration as fbcode
|
||||
# platforms, so only explicitly enable for linux until this is fixed.
|
||||
return linker_info.shlib_interfaces != "disabled" and linker_info.type == "gnu"
|
||||
|
||||
def cxx_platform_supported(ctx: "context") -> bool.type:
|
||||
"""
|
||||
Return whether this rule's `supported_platforms_regex` matches the current
|
||||
platform name.
|
||||
"""
|
||||
|
||||
if ctx.attrs.supported_platforms_regex == None:
|
||||
return True
|
||||
|
||||
return regex_match(
|
||||
ctx.attrs.supported_platforms_regex,
|
||||
get_cxx_platform_info(ctx).name,
|
||||
)
|
||||
201
vendor/cxx/tools/buck/prelude/cxx/cxx_link_utility.bzl
vendored
Normal file
201
vendor/cxx/tools/buck/prelude/cxx/cxx_link_utility.bzl
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
# 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//cxx:cxx_toolchain_types.bzl", "CxxToolchainInfo")
|
||||
load("@prelude//cxx:debug.bzl", "SplitDebugMode")
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"LinkArgs",
|
||||
"LinkInfo",
|
||||
"unpack_link_args",
|
||||
"unpack_link_args_filelist",
|
||||
)
|
||||
load("@prelude//linking:lto.bzl", "LtoMode")
|
||||
load("@prelude//utils:utils.bzl", "expect")
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
|
||||
def linker_map_args(ctx, linker_map) -> LinkArgs.type:
|
||||
darwin_flags = [
|
||||
"-Xlinker",
|
||||
"-map",
|
||||
"-Xlinker",
|
||||
linker_map,
|
||||
]
|
||||
gnu_flags = [
|
||||
"-Xlinker",
|
||||
"-Map",
|
||||
"-Xlinker",
|
||||
linker_map,
|
||||
"-Xlinker",
|
||||
# A linker map is useful even when the output executable can't be correctly created
|
||||
# (e.g. due to relocation overflows). Turn errors into warnings so the
|
||||
# path/to:binary[linker-map] sub-target succesfully runs and produces the linker map file.
|
||||
"-noinhibit-exec",
|
||||
# If linking hits relocation overflows these will produce a huge amount of almost identical logs.
|
||||
"-Xlinker",
|
||||
"--error-limit=1",
|
||||
]
|
||||
return LinkArgs(flags = darwin_flags if get_cxx_toolchain_info(ctx).linker_info.type == "darwin" else gnu_flags)
|
||||
|
||||
def map_link_args_for_dwo(ctx: "context", links: ["LinkArgs"], dwo_dir_name: [str.type, None]) -> (["LinkArgs"], ["artifact", None]):
|
||||
"""
|
||||
Takes LinkArgs, and if they enable the DWO output dir hack, returns updated
|
||||
args and a DWO dir as output. If they don't, just returns the args as-is.
|
||||
"""
|
||||
|
||||
# TODO(T110378131): Once we have first-class support for ThinLTO and
|
||||
# split-dwarf, we can move way from this hack and have the rules add this
|
||||
# parameter appropriately. But, for now, to maintain compatibility for how
|
||||
# the macros setup ThinLTO+split-dwarf, use a macro hack to intercept when
|
||||
# we're setting an explicitly tracked dwo dir and pull into the explicit
|
||||
# tracking we do at the `LinkedObject` level.
|
||||
#
|
||||
# Can't mutate a variable, so put it in a list and mutate the innards
|
||||
dwo_dir = [None]
|
||||
|
||||
def adjust_flag(flag: "_arglike") -> "_arglike":
|
||||
if "HACK-OUTPUT-DWO-DIR" in repr(flag):
|
||||
expect(dwo_dir_name != None)
|
||||
expect(dwo_dir[0] == None)
|
||||
dwo_dir[0] = ctx.actions.declare_output(dwo_dir_name)
|
||||
return cmd_args(dwo_dir[0].as_output(), format = "dwo_dir={}")
|
||||
else:
|
||||
return flag
|
||||
|
||||
def adjust_link_info(link_info: LinkInfo.type) -> LinkInfo.type:
|
||||
return LinkInfo(
|
||||
name = link_info.name,
|
||||
linkables = link_info.linkables,
|
||||
pre_flags = [adjust_flag(x) for x in link_info.pre_flags],
|
||||
post_flags = [adjust_flag(x) for x in link_info.post_flags],
|
||||
use_link_groups = link_info.use_link_groups,
|
||||
)
|
||||
|
||||
links = [
|
||||
LinkArgs(
|
||||
tset = link.tset,
|
||||
flags = [adjust_flag(flag) for flag in link.flags] if link.flags != None else None,
|
||||
infos = [adjust_link_info(info) for info in link.infos] if link.infos != None else None,
|
||||
)
|
||||
for link in links
|
||||
]
|
||||
return (links, dwo_dir[0])
|
||||
|
||||
def make_link_args(
|
||||
ctx: "context",
|
||||
links: ["LinkArgs"],
|
||||
suffix = None,
|
||||
dwo_dir_name: [str.type, None] = None,
|
||||
is_shared: [bool.type, None] = None,
|
||||
link_ordering: ["LinkOrdering", None] = None) -> ("_arglike", ["_hidden"], ["artifact", None]):
|
||||
"""
|
||||
Merges LinkArgs. Returns the args, files that must be present for those
|
||||
args to work when passed to a linker, and optionally an artifact where DWO
|
||||
outputs will be written to.
|
||||
"""
|
||||
suffix = "" if suffix == None else "-" + suffix
|
||||
args = cmd_args()
|
||||
|
||||
filelists = filter(None, [unpack_link_args_filelist(link) for link in links])
|
||||
|
||||
cxx_toolchain_info = get_cxx_toolchain_info(ctx)
|
||||
linker_type = cxx_toolchain_info.linker_info.type
|
||||
if filelists:
|
||||
if linker_type == "gnu":
|
||||
fail("filelist populated for gnu linker")
|
||||
elif linker_type == "darwin":
|
||||
path = ctx.actions.write("filelist%s.txt" % suffix, filelists)
|
||||
args.add(["-Xlinker", "-filelist", "-Xlinker", path])
|
||||
else:
|
||||
fail("Linker type {} not supported".format(linker_type))
|
||||
|
||||
# On Apple platforms, DWARF data is contained in the object files
|
||||
# and executables contains paths to the object files (N_OSO stab).
|
||||
#
|
||||
# By default, ld64 will use absolute file paths in N_OSO entries
|
||||
# which machine-dependent executables. Such executables would not
|
||||
# be debuggable on any host apart from the host which performed
|
||||
# the linking. Instead, we want produce machine-independent
|
||||
# hermetic executables, so we need to relativize those paths.
|
||||
#
|
||||
# This is accomplished by passing the `oso-prefix` flag to ld64,
|
||||
# which will strip the provided prefix from the N_OSO paths.
|
||||
#
|
||||
# The flag accepts a special value, `.`, which means it will
|
||||
# use the current workding directory. This will make all paths
|
||||
# relative to the parent of `buck-out`.
|
||||
#
|
||||
# Because all actions in Buck2 are run from the project root
|
||||
# and `buck-out` is always inside the project root, we can
|
||||
# safely pass `.` as the `-oso_prefix` without having to
|
||||
# write a wrapper script to compute it dynamically.
|
||||
if linker_type == "darwin":
|
||||
args.add(["-Wl,-oso_prefix,."])
|
||||
|
||||
# Not all C/C++ codebases use split-DWARF. Apple uses dSYM files, instead.
|
||||
#
|
||||
# If we aren't going to use .dwo/.dwp files, avoid the codepath.
|
||||
# Historically we've seen that going down this path bloats
|
||||
# the memory usage of FBiOS by 12% (which amounts to Gigabytes.)
|
||||
#
|
||||
# Context: D36669131
|
||||
dwo_dir = None
|
||||
if cxx_toolchain_info.linker_info.lto_mode != LtoMode("none") and cxx_toolchain_info.split_debug_mode != SplitDebugMode("none"):
|
||||
links, dwo_dir = map_link_args_for_dwo(ctx, links, dwo_dir_name)
|
||||
|
||||
for link in links:
|
||||
args.add(unpack_link_args(link, is_shared, link_ordering = link_ordering))
|
||||
|
||||
return (args, [args] + filelists, dwo_dir)
|
||||
|
||||
def shared_libs_symlink_tree_name(output: "artifact") -> str.type:
|
||||
return "__{}__shared_libs_symlink_tree".format(output.short_path)
|
||||
|
||||
# Returns a tuple of:
|
||||
# - list of extra arguments,
|
||||
# - list of files/directories that should be present for executable to be run successfully
|
||||
# - optional shared libs symlink tree symlinked_dir action
|
||||
def executable_shared_lib_arguments(
|
||||
actions: "actions",
|
||||
cxx_toolchain: CxxToolchainInfo.type,
|
||||
output: "artifact",
|
||||
shared_libs: {str.type: "LinkedObject"}) -> ([""], ["_arglike"], ["artifact", None]):
|
||||
extra_args = []
|
||||
runtime_files = []
|
||||
shared_libs_symlink_tree = None
|
||||
|
||||
# Add external debug paths to runtime files, so that they're
|
||||
# materialized when the binary is built.
|
||||
for shlib in shared_libs.values():
|
||||
runtime_files.extend(shlib.external_debug_info)
|
||||
|
||||
if len(shared_libs) > 0:
|
||||
shared_libs_symlink_tree = actions.symlinked_dir(
|
||||
shared_libs_symlink_tree_name(output),
|
||||
{name: shlib.output for name, shlib in shared_libs.items()},
|
||||
)
|
||||
runtime_files.append(shared_libs_symlink_tree)
|
||||
linker_type = cxx_toolchain.linker_info.type
|
||||
if linker_type == "gnu":
|
||||
rpath_reference = "$ORIGIN"
|
||||
elif linker_type == "darwin":
|
||||
rpath_reference = "@loader_path"
|
||||
else:
|
||||
fail("Linker type {} not supported".format(linker_type))
|
||||
|
||||
# We ignore_artifacts() here since we don't want the symlink tree to actually be there for the link.
|
||||
rpath_arg = cmd_args(shared_libs_symlink_tree, format = "-Wl,-rpath,{}/{{}}".format(rpath_reference)).relative_to(output, parent = 1).ignore_artifacts()
|
||||
extra_args.append(rpath_arg)
|
||||
|
||||
return (extra_args, runtime_files, shared_libs_symlink_tree)
|
||||
|
||||
# The command line for linking with C++
|
||||
def cxx_link_cmd(ctx: "context") -> "cmd_args":
|
||||
toolchain = get_cxx_toolchain_info(ctx)
|
||||
command = cmd_args(toolchain.linker_info.linker)
|
||||
command.add(toolchain.linker_info.linker_flags or [])
|
||||
return command
|
||||
170
vendor/cxx/tools/buck/prelude/cxx/cxx_toolchain.bzl
vendored
Normal file
170
vendor/cxx/tools/buck/prelude/cxx/cxx_toolchain.bzl
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
# 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//cxx:cxx_toolchain_types.bzl", "AsCompilerInfo", "AsmCompilerInfo", "BinaryUtilitiesInfo", "CCompilerInfo", "CudaCompilerInfo", "CxxCompilerInfo", "HipCompilerInfo", "LinkerInfo", "StripFlagsInfo", "cxx_toolchain_infos")
|
||||
load("@prelude//cxx:debug.bzl", "SplitDebugMode")
|
||||
load("@prelude//cxx:headers.bzl", "HeaderMode", "HeadersAsRawHeadersMode")
|
||||
load("@prelude//cxx:linker.bzl", "LINKERS")
|
||||
load("@prelude//linking:link_info.bzl", "LinkStyle")
|
||||
load("@prelude//linking:lto.bzl", "LtoMode")
|
||||
load("@prelude//utils:utils.bzl", "value_or")
|
||||
|
||||
def cxx_toolchain_impl(ctx):
|
||||
c_info = CCompilerInfo(
|
||||
compiler = ctx.attrs.c_compiler[RunInfo],
|
||||
compiler_type = ctx.attrs.c_compiler_type or ctx.attrs.compiler_type,
|
||||
compiler_flags = cmd_args(ctx.attrs.c_compiler_flags),
|
||||
preprocessor_flags = cmd_args(ctx.attrs.c_preprocessor_flags),
|
||||
dep_files_processor = ctx.attrs._dep_files_processor[RunInfo],
|
||||
)
|
||||
cxx_info = CxxCompilerInfo(
|
||||
compiler = ctx.attrs.cxx_compiler[RunInfo],
|
||||
compiler_type = ctx.attrs.cxx_compiler_type or ctx.attrs.compiler_type,
|
||||
compiler_flags = cmd_args(ctx.attrs.cxx_compiler_flags),
|
||||
preprocessor_flags = cmd_args(ctx.attrs.cxx_preprocessor_flags),
|
||||
dep_files_processor = ctx.attrs._dep_files_processor[RunInfo],
|
||||
)
|
||||
asm_info = AsmCompilerInfo(
|
||||
compiler = ctx.attrs.asm_compiler[RunInfo],
|
||||
compiler_type = ctx.attrs.asm_compiler_type or ctx.attrs.compiler_type,
|
||||
compiler_flags = cmd_args(ctx.attrs.asm_compiler_flags),
|
||||
preprocessor_flags = cmd_args(ctx.attrs.asm_preprocessor_flags),
|
||||
dep_files_processor = ctx.attrs._dep_files_processor[RunInfo],
|
||||
) if ctx.attrs.asm_compiler else None
|
||||
as_info = AsCompilerInfo(
|
||||
compiler = ctx.attrs.assembler[RunInfo],
|
||||
compiler_type = ctx.attrs.assembler_type or ctx.attrs.compiler_type,
|
||||
compiler_flags = cmd_args(ctx.attrs.assembler_flags),
|
||||
preprocessor_flags = cmd_args(ctx.attrs.assembler_preprocessor_flags),
|
||||
dep_files_processor = ctx.attrs._dep_files_processor[RunInfo],
|
||||
) if ctx.attrs.assembler else None
|
||||
cuda_info = CudaCompilerInfo(
|
||||
compiler = ctx.attrs.cuda_compiler[RunInfo],
|
||||
compiler_type = ctx.attrs.cuda_compiler_type or ctx.attrs.compiler_type,
|
||||
compiler_flags = cmd_args(ctx.attrs.cuda_compiler_flags),
|
||||
preprocessor_flags = cmd_args(ctx.attrs.cuda_preprocessor_flags),
|
||||
) if ctx.attrs.cuda_compiler else None
|
||||
hip_info = HipCompilerInfo(
|
||||
compiler = ctx.attrs.hip_compiler[RunInfo],
|
||||
compiler_type = ctx.attrs.hip_compiler_type or ctx.attrs.compiler_type,
|
||||
compiler_flags = cmd_args(ctx.attrs.hip_compiler_flags),
|
||||
preprocessor_flags = cmd_args(ctx.attrs.hip_preprocessor_flags),
|
||||
) if ctx.attrs.hip_compiler else None
|
||||
|
||||
linker_info = LinkerInfo(
|
||||
archiver = ctx.attrs.archiver[RunInfo],
|
||||
archiver_supports_argfiles = ctx.attrs.archiver_supports_argfiles,
|
||||
archiver_type = ctx.attrs.archiver_type,
|
||||
archive_contents = ctx.attrs.archive_contents,
|
||||
archive_objects_locally = False,
|
||||
binary_extension = value_or(ctx.attrs.binary_extension, ""),
|
||||
link_binaries_locally = not value_or(ctx.attrs.cache_links, True),
|
||||
link_libraries_locally = False,
|
||||
link_style = LinkStyle("static"),
|
||||
link_weight = 1,
|
||||
link_ordering = ctx.attrs.link_ordering,
|
||||
linker = ctx.attrs.linker[RunInfo],
|
||||
linker_flags = cmd_args(ctx.attrs.linker_flags),
|
||||
lto_mode = LtoMode("none"),
|
||||
object_file_extension = ctx.attrs.object_file_extension or "o",
|
||||
shlib_interfaces = "disabled",
|
||||
independent_shlib_interface_linker_flags = ctx.attrs.shared_library_interface_flags,
|
||||
requires_archives = value_or(ctx.attrs.requires_archives, True),
|
||||
requires_objects = value_or(ctx.attrs.requires_objects, False),
|
||||
supports_distributed_thinlto = ctx.attrs.supports_distributed_thinlto,
|
||||
shared_dep_runtime_ld_flags = ctx.attrs.shared_dep_runtime_ld_flags,
|
||||
shared_library_name_format = _get_shared_library_name_format(ctx),
|
||||
shared_library_versioned_name_format = _get_shared_library_versioned_name_format(ctx),
|
||||
static_dep_runtime_ld_flags = ctx.attrs.static_dep_runtime_ld_flags,
|
||||
static_library_extension = ctx.attrs.static_library_extension or "a",
|
||||
static_pic_dep_runtime_ld_flags = ctx.attrs.static_pic_dep_runtime_ld_flags,
|
||||
type = ctx.attrs.linker_type,
|
||||
use_archiver_flags = ctx.attrs.use_archiver_flags,
|
||||
)
|
||||
|
||||
utilities_info = BinaryUtilitiesInfo(
|
||||
nm = ctx.attrs.nm[RunInfo],
|
||||
objcopy = ctx.attrs.objcopy_for_shared_library_interface[RunInfo],
|
||||
ranlib = ctx.attrs.ranlib[RunInfo] if ctx.attrs.ranlib else None,
|
||||
strip = ctx.attrs.strip[RunInfo],
|
||||
dwp = None,
|
||||
bolt_msdk = None,
|
||||
)
|
||||
|
||||
strip_flags_info = StripFlagsInfo(
|
||||
strip_debug_flags = ctx.attrs.strip_debug_flags,
|
||||
strip_non_global_flags = ctx.attrs.strip_non_global_flags,
|
||||
strip_all_flags = ctx.attrs.strip_all_flags,
|
||||
)
|
||||
|
||||
platform_name = ctx.attrs.platform_name or ctx.attrs.name
|
||||
return [
|
||||
DefaultInfo(),
|
||||
] + cxx_toolchain_infos(
|
||||
platform_name = platform_name,
|
||||
linker_info = linker_info,
|
||||
binary_utilities_info = utilities_info,
|
||||
bolt_enabled = value_or(ctx.attrs.bolt_enabled, False),
|
||||
c_compiler_info = c_info,
|
||||
cxx_compiler_info = cxx_info,
|
||||
asm_compiler_info = asm_info,
|
||||
as_compiler_info = as_info,
|
||||
cuda_compiler_info = cuda_info,
|
||||
hip_compiler_info = hip_info,
|
||||
header_mode = _get_header_mode(ctx),
|
||||
headers_as_raw_headers_mode = HeadersAsRawHeadersMode(ctx.attrs.headers_as_raw_headers_mode) if ctx.attrs.headers_as_raw_headers_mode != None else None,
|
||||
conflicting_header_basename_allowlist = ctx.attrs.conflicting_header_basename_exemptions,
|
||||
mk_hmap = ctx.attrs._mk_hmap[RunInfo],
|
||||
mk_comp_db = ctx.attrs._mk_comp_db,
|
||||
split_debug_mode = SplitDebugMode(ctx.attrs.split_debug_mode),
|
||||
strip_flags_info = strip_flags_info,
|
||||
# TODO(T138705365): Turn on dep files by default
|
||||
use_dep_files = value_or(ctx.attrs.use_dep_files, _get_default_use_dep_files(platform_name)),
|
||||
)
|
||||
|
||||
_APPLE_PLATFORM_NAME_PREFIXES = [
|
||||
"iphonesimulator",
|
||||
"iphoneos",
|
||||
"maccatalyst",
|
||||
"macosx",
|
||||
"watchos",
|
||||
"watchsimulator",
|
||||
"appletvos",
|
||||
"appletvsimulator",
|
||||
]
|
||||
|
||||
def _get_default_use_dep_files(platform_name: str.type) -> bool.type:
|
||||
# All Apple platforms use Clang which supports the standard dep files format
|
||||
for apple_platform_name_prefix in _APPLE_PLATFORM_NAME_PREFIXES:
|
||||
if apple_platform_name_prefix in platform_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_header_mode(ctx: "context") -> HeaderMode.type:
|
||||
if ctx.attrs.use_header_map:
|
||||
if ctx.attrs.private_headers_symlinks_enabled or ctx.attrs.public_headers_symlinks_enabled:
|
||||
return HeaderMode("symlink_tree_with_header_map")
|
||||
else:
|
||||
return HeaderMode("header_map_only")
|
||||
else:
|
||||
return HeaderMode("symlink_tree_only")
|
||||
|
||||
def _get_shared_library_name_format(ctx: "context") -> str.type:
|
||||
linker_type = ctx.attrs.linker_type
|
||||
extension = ctx.attrs.shared_library_extension
|
||||
if extension == "":
|
||||
extension = LINKERS[linker_type].default_shared_library_extension
|
||||
prefix = "" if extension == "dll" else "lib"
|
||||
return prefix + "{}." + extension
|
||||
|
||||
def _get_shared_library_versioned_name_format(ctx: "context") -> str.type:
|
||||
linker_type = ctx.attrs.linker_type
|
||||
extension_format = ctx.attrs.shared_library_versioned_extension_format.replace("%s", "{}")
|
||||
if extension_format == "":
|
||||
extension_format = LINKERS[linker_type].default_shared_library_versioned_extension_format
|
||||
prefix = "" if extension_format == "dll" else "lib"
|
||||
return prefix + "{}." + extension_format
|
||||
258
vendor/cxx/tools/buck/prelude/cxx/cxx_toolchain_types.bzl
vendored
Normal file
258
vendor/cxx/tools/buck/prelude/cxx/cxx_toolchain_types.bzl
vendored
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
# 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//cxx:debug.bzl", "SplitDebugMode")
|
||||
|
||||
LinkerType = ["gnu", "darwin", "windows"]
|
||||
|
||||
# TODO(T110378149): Consider whether it makes sense to move these things to
|
||||
# configurations/constraints rather than part of the toolchain.
|
||||
LinkerInfo = provider(fields = [
|
||||
"archiver",
|
||||
"archiver_supports_argfiles",
|
||||
"archiver_type",
|
||||
"archive_contents",
|
||||
"archive_objects_locally",
|
||||
# "archiver_platform",
|
||||
# "" on Unix, "exe" on Windows
|
||||
"binary_extension", # str.type
|
||||
# Whether to run native links locally. We support this for fbcode platforms
|
||||
# to avoid issues with C++ static links (see comment in
|
||||
# `platform/cxx_toolchains.bzl` for details).
|
||||
"link_binaries_locally",
|
||||
# Whether to run native shared library links locally. For certain use cases
|
||||
# (e.g., large Apple frameworks), it's more efficient to link locally due
|
||||
# GiBs of object files (which can also lead to RE errors/timesouts etc).
|
||||
"link_libraries_locally",
|
||||
"link_style", # LinkStyle.type
|
||||
"link_weight", # int.type
|
||||
"link_ordering", # LinkOrdering.type
|
||||
"linker",
|
||||
"linker_flags",
|
||||
"lto_mode",
|
||||
"mk_shlib_intf",
|
||||
# "o" on Unix, "obj" on Windows
|
||||
"object_file_extension", # str.type
|
||||
"shlib_interfaces",
|
||||
"shared_dep_runtime_ld_flags",
|
||||
# "lib{}.so" on Linux, "lib{}.dylib" on Mac, "{}.dll" on Windows
|
||||
"shared_library_name_format", # str.type
|
||||
"shared_library_versioned_name_format", # str.type
|
||||
"static_dep_runtime_ld_flags",
|
||||
# "a" on Unix, "lib" on Windows
|
||||
"static_library_extension", # str.type
|
||||
"static_pic_dep_runtime_ld_flags",
|
||||
"requires_archives",
|
||||
"requires_objects",
|
||||
"supports_distributed_thinlto",
|
||||
"independent_shlib_interface_linker_flags",
|
||||
"type", # of "LinkerType" type
|
||||
"use_archiver_flags",
|
||||
"force_full_hybrid_if_capable",
|
||||
])
|
||||
|
||||
BinaryUtilitiesInfo = provider(fields = [
|
||||
"bolt_msdk",
|
||||
"dwp",
|
||||
"nm",
|
||||
"objcopy",
|
||||
"ranlib",
|
||||
"strip",
|
||||
])
|
||||
|
||||
StripFlagsInfo = provider(fields = [
|
||||
"strip_debug_flags", # [["str"], None]
|
||||
"strip_non_global_flags", # [["str"], None]
|
||||
"strip_all_flags", # [["str"], None]
|
||||
])
|
||||
|
||||
# TODO(T110378147): There's a bunch of info encoded in random places in buck
|
||||
# derived from information in these toolchains but hardcoded (for example,
|
||||
# which file extensions are preprocessable/compilable). We should figure out
|
||||
# how to move most of that into these toolchain infos.
|
||||
# TODO(T110378146): The inclusion of compiler and preprocessor in here is really
|
||||
# just a legacy thing that was never cleaned up. Historically, buck supported a
|
||||
# mode where compilation was done in two, explicitly separate phases
|
||||
# (preprocess and then compile). We don't support that today, and including
|
||||
# both of these mostly just ends up with added complexity and with us
|
||||
# duplicating flags in command lines.
|
||||
|
||||
# In cxx_library, we support a bunch of different types of files (ex. cuda),
|
||||
# the toolchain for these follow this common pattern.
|
||||
_compiler_fields = [
|
||||
"compiler",
|
||||
"compiler_type",
|
||||
"compiler_flags",
|
||||
"preprocessor",
|
||||
"preprocessor_type",
|
||||
"preprocessor_flags",
|
||||
"dep_files_processor",
|
||||
]
|
||||
|
||||
HipCompilerInfo = provider(fields = _compiler_fields)
|
||||
CudaCompilerInfo = provider(fields = _compiler_fields)
|
||||
CCompilerInfo = provider(fields = _compiler_fields)
|
||||
CxxCompilerInfo = provider(fields = _compiler_fields)
|
||||
AsmCompilerInfo = provider(fields = _compiler_fields)
|
||||
AsCompilerInfo = provider(fields = _compiler_fields)
|
||||
|
||||
DistLtoToolsInfo = provider(
|
||||
fields = ["planner", "opt", "prepare", "copy"],
|
||||
)
|
||||
|
||||
# TODO(T110378094): We should consider if we can change this from a hardcoded
|
||||
# list of compiler_info to something more general. We could maybe do a list of
|
||||
# compiler_info where each one also declares what extensions it supports.
|
||||
# TODO(T110378145): Could we split up this Info so that each of the compilers
|
||||
# could be provided by different dependencies? That would allow a target to
|
||||
# only depend on the compilers it actually needs.
|
||||
CxxToolchainInfo = provider(fields = [
|
||||
"conflicting_header_basename_allowlist",
|
||||
"use_distributed_thinlto",
|
||||
"header_mode",
|
||||
"headers_as_raw_headers_mode",
|
||||
"linker_info",
|
||||
"binary_utilities_info",
|
||||
"c_compiler_info",
|
||||
"cxx_compiler_info",
|
||||
"asm_compiler_info",
|
||||
"as_compiler_info",
|
||||
"hip_compiler_info",
|
||||
"cuda_compiler_info",
|
||||
"mk_comp_db",
|
||||
"mk_hmap",
|
||||
"dist_lto_tools_info",
|
||||
"use_dep_files",
|
||||
"strip_flags_info",
|
||||
"split_debug_mode",
|
||||
"bolt_enabled",
|
||||
])
|
||||
|
||||
# Stores "platform"/flavor name used to resolve *platform_* arguments
|
||||
CxxPlatformInfo = provider(fields = [
|
||||
"name",
|
||||
# List of aliases used to resolve platform_deps
|
||||
"deps_aliases",
|
||||
])
|
||||
|
||||
def _validate_linker_info(info: LinkerInfo.type):
|
||||
if info.requires_archives and info.requires_objects:
|
||||
fail("only one of `requires_archives` and `requires_objects` can be enabled")
|
||||
|
||||
if info.supports_distributed_thinlto and not info.requires_objects:
|
||||
fail("distributed thinlto requires enabling `requires_objects`")
|
||||
|
||||
def cxx_toolchain_infos(
|
||||
platform_name,
|
||||
c_compiler_info,
|
||||
cxx_compiler_info,
|
||||
linker_info,
|
||||
binary_utilities_info,
|
||||
header_mode,
|
||||
headers_as_raw_headers_mode = None,
|
||||
conflicting_header_basename_allowlist = [],
|
||||
asm_compiler_info = None,
|
||||
as_compiler_info = None,
|
||||
hip_compiler_info = None,
|
||||
cuda_compiler_info = None,
|
||||
mk_comp_db = None,
|
||||
mk_hmap = None,
|
||||
use_distributed_thinlto = False,
|
||||
use_dep_files = False,
|
||||
strip_flags_info = None,
|
||||
dist_lto_tools_info: [DistLtoToolsInfo.type, None] = None,
|
||||
split_debug_mode = SplitDebugMode("none"),
|
||||
bolt_enabled = False,
|
||||
platform_deps_aliases = []):
|
||||
"""
|
||||
Creates the collection of cxx-toolchain Infos for a cxx toolchain.
|
||||
|
||||
c and c++ compiler infos are required, as is a linker info. The rest are
|
||||
optional, and it will be an error if any cxx_library or other rules have srcs
|
||||
of those other types.
|
||||
"""
|
||||
|
||||
# TODO(T110378099): verify types of the inner info objects.
|
||||
_validate_linker_info(linker_info)
|
||||
|
||||
toolchain_info = CxxToolchainInfo(
|
||||
conflicting_header_basename_allowlist = conflicting_header_basename_allowlist,
|
||||
header_mode = header_mode,
|
||||
headers_as_raw_headers_mode = headers_as_raw_headers_mode,
|
||||
linker_info = linker_info,
|
||||
binary_utilities_info = binary_utilities_info,
|
||||
c_compiler_info = c_compiler_info,
|
||||
cxx_compiler_info = cxx_compiler_info,
|
||||
asm_compiler_info = asm_compiler_info,
|
||||
as_compiler_info = as_compiler_info,
|
||||
hip_compiler_info = hip_compiler_info,
|
||||
cuda_compiler_info = cuda_compiler_info,
|
||||
mk_comp_db = mk_comp_db,
|
||||
mk_hmap = mk_hmap,
|
||||
dist_lto_tools_info = dist_lto_tools_info,
|
||||
use_distributed_thinlto = use_distributed_thinlto,
|
||||
use_dep_files = use_dep_files,
|
||||
strip_flags_info = strip_flags_info,
|
||||
split_debug_mode = split_debug_mode,
|
||||
bolt_enabled = bolt_enabled,
|
||||
)
|
||||
|
||||
# Provide placeholder mappings, used primarily by cxx_genrule.
|
||||
# We don't support these buck1 placeholders since we can't take an argument.
|
||||
# $(ldflags-pic-filter <pattern>)
|
||||
# $(ldflags-shared-filter <pattern>)
|
||||
# $(ldflags-static-filter <pattern>)
|
||||
unkeyed_variables = {
|
||||
"ar": linker_info.archiver,
|
||||
"cc": c_compiler_info.compiler,
|
||||
"cflags": _shell_quote(c_compiler_info.compiler_flags),
|
||||
"cppflags": _shell_quote(c_compiler_info.preprocessor_flags),
|
||||
"cxx": cxx_compiler_info.compiler,
|
||||
"cxxflags": _shell_quote(cxx_compiler_info.compiler_flags),
|
||||
"cxxppflags": _shell_quote(cxx_compiler_info.preprocessor_flags),
|
||||
"ld": linker_info.linker,
|
||||
# NOTE(agallagher): The arg-less variants of the ldflags macro are
|
||||
# identical, and are just separate to match v1's behavior (ideally,
|
||||
# we just have a single `ldflags` macro for this case).
|
||||
"ldflags-shared": _shell_quote(linker_info.linker_flags),
|
||||
"ldflags-static": _shell_quote(linker_info.linker_flags),
|
||||
"ldflags-static-pic": _shell_quote(linker_info.linker_flags),
|
||||
# TODO(T110378148): $(platform-name) is almost unusued. Should we remove it?
|
||||
"platform-name": platform_name,
|
||||
}
|
||||
|
||||
if as_compiler_info != None:
|
||||
unkeyed_variables["as"] = as_compiler_info.compiler
|
||||
unkeyed_variables["asflags"] = _shell_quote(as_compiler_info.compiler_flags)
|
||||
unkeyed_variables["asppflags"] = _shell_quote(as_compiler_info.preprocessor_flags)
|
||||
|
||||
if cuda_compiler_info != None:
|
||||
unkeyed_variables["cuda"] = cuda_compiler_info.compiler
|
||||
unkeyed_variables["cudaflags"] = _shell_quote(cuda_compiler_info.compiler_flags)
|
||||
placeholders_info = TemplatePlaceholderInfo(unkeyed_variables = unkeyed_variables)
|
||||
return [toolchain_info, placeholders_info, CxxPlatformInfo(name = platform_name, deps_aliases = platform_deps_aliases)]
|
||||
|
||||
def _shell_quote(xs):
|
||||
return cmd_args(xs, quote = "shell")
|
||||
|
||||
# export these things under a single "cxx" struct
|
||||
cxx = struct(
|
||||
LinkerType = LinkerType,
|
||||
LinkerInfo = LinkerInfo,
|
||||
BinaryUtilitiesInfo = BinaryUtilitiesInfo,
|
||||
HipCompilerInfo = HipCompilerInfo,
|
||||
CudaCompilerInfo = CudaCompilerInfo,
|
||||
CCompilerInfo = CCompilerInfo,
|
||||
CxxCompilerInfo = CxxCompilerInfo,
|
||||
AsmCompilerInfo = AsmCompilerInfo,
|
||||
AsCompilerInfo = AsCompilerInfo,
|
||||
CxxToolchainInfo = CxxToolchainInfo,
|
||||
CxxPlatformInfo = CxxPlatformInfo,
|
||||
StripFlagsInfo = StripFlagsInfo,
|
||||
DistLtoToolsInfo = DistLtoToolsInfo,
|
||||
cxx_toolchain_infos = cxx_toolchain_infos,
|
||||
)
|
||||
160
vendor/cxx/tools/buck/prelude/cxx/cxx_types.bzl
vendored
Normal file
160
vendor/cxx/tools/buck/prelude/cxx/cxx_types.bzl
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# 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//linking:link_info.bzl",
|
||||
"LinkArgs",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkables.bzl",
|
||||
"LinkableProviders",
|
||||
)
|
||||
load(
|
||||
":compile.bzl",
|
||||
"CxxSrcWithFlags", # @unused Used as a type
|
||||
)
|
||||
load(
|
||||
":headers.bzl",
|
||||
"CxxHeadersLayout",
|
||||
)
|
||||
load(
|
||||
":link_groups.bzl",
|
||||
"LinkGroupInfo", # @unused Used as a type
|
||||
"LinkGroupLibSpec", # @unused Used as a type
|
||||
)
|
||||
load(
|
||||
":linker.bzl",
|
||||
"SharedLibraryFlagOverrides",
|
||||
)
|
||||
load(
|
||||
":preprocessor.bzl",
|
||||
"CPreprocessor",
|
||||
"CPreprocessorInfo",
|
||||
)
|
||||
load(
|
||||
":xcode.bzl",
|
||||
"cxx_populate_xcode_attributes",
|
||||
)
|
||||
|
||||
# Parameters to control which sub targets to define when processing Cxx rules.
|
||||
# By default, generates all subtargets.
|
||||
CxxRuleSubTargetParams = record(
|
||||
argsfiles = field(bool.type, True),
|
||||
compilation_database = field(bool.type, True),
|
||||
headers = field(bool.type, True),
|
||||
link_group_map = field(bool.type, True),
|
||||
link_style_outputs = field(bool.type, True),
|
||||
xcode_data = field(bool.type, True),
|
||||
)
|
||||
|
||||
# Parameters to control which providers to define when processing Cxx rules.
|
||||
# By default, generates all providers.
|
||||
CxxRuleProviderParams = record(
|
||||
compilation_database = field(bool.type, True),
|
||||
default = field(bool.type, True),
|
||||
java_packaging_info = field(bool.type, True),
|
||||
android_packageable_info = field(bool.type, True),
|
||||
linkable_graph = field(bool.type, True),
|
||||
link_style_outputs = field(bool.type, True),
|
||||
merged_native_link_info = field(bool.type, True),
|
||||
omnibus_root = field(bool.type, True),
|
||||
preprocessors = field(bool.type, True),
|
||||
resources = field(bool.type, True),
|
||||
shared_libraries = field(bool.type, True),
|
||||
template_placeholders = field(bool.type, True),
|
||||
preprocessor_for_tests = field(bool.type, True),
|
||||
)
|
||||
|
||||
# Params to handle an argsfile for non-Clang sources which may present in Apple rules.
|
||||
CxxAdditionalArgsfileParams = record(
|
||||
file = field("artifact"),
|
||||
# Hidden args necessary for the argsfile to reference
|
||||
hidden_args = field([["artifacts", "cmd_args"]]),
|
||||
# An extension of a file for which this argsfile is generated.
|
||||
extension = field(str.type),
|
||||
)
|
||||
|
||||
# Parameters to handle non-Clang sources, e.g Swift on Apple's platforms.
|
||||
CxxRuleAdditionalParams = record(
|
||||
srcs = field([CxxSrcWithFlags.type], []),
|
||||
argsfiles = field([CxxAdditionalArgsfileParams.type], []),
|
||||
external_debug_info = field(["_arglike"], []),
|
||||
)
|
||||
|
||||
# Parameters that allows to configure/extend generic implementation of C++ rules.
|
||||
# Apple-specific rules (such as `apple_binary` and `apple_library`) and regular C++
|
||||
# rules (such as `cxx_binary` and `cxx_library`) have too much in common, though
|
||||
# some aspects of behavior (like layout of headers affecting inclusion of those
|
||||
# or additional linking flags to support usage of platform frameworks) of are
|
||||
# different and need to be specified. The following record holds the data which
|
||||
# is needed to specialize user-facing rule from generic implementation.
|
||||
CxxRuleConstructorParams = record(
|
||||
# We need to build an empty shared library for rust_python_extensions so that they can link against the rust shared object
|
||||
build_empty_so = field(bool.type, False),
|
||||
# Name of the top level rule utilizing the cxx rule.
|
||||
rule_type = str.type,
|
||||
# If the rule is a test.
|
||||
is_test = field(bool.type, False),
|
||||
# Header layout to use importing headers.
|
||||
headers_layout = CxxHeadersLayout.type,
|
||||
# Additional information used to preprocess every unit of translation in the rule
|
||||
extra_preprocessors = field([CPreprocessor.type], []),
|
||||
extra_preprocessors_info = field([CPreprocessorInfo.type], []),
|
||||
# Additional preprocessor info to export to other rules
|
||||
extra_exported_preprocessors = field([CPreprocessor.type], []),
|
||||
# Additional information used to link every object produced by the rule,
|
||||
# flags are _both_ exported and used to link the target itself.
|
||||
extra_exported_link_flags = field([""], []),
|
||||
# Additional flags used _only_ when linking the target itself.
|
||||
# These flags are _not_ propagated up the dep tree.
|
||||
extra_link_flags = field([""], []),
|
||||
# Additional artifacts to be linked together with the cxx compilation output
|
||||
extra_link_input = field(["artifact"], []),
|
||||
# Additional args to be used to link the target
|
||||
extra_link_args = field([LinkArgs.type], []),
|
||||
# The source files to compile as part of this rule. This list can be generated
|
||||
# from ctx.attrs with the `get_srcs_with_flags` function.
|
||||
srcs = field([CxxSrcWithFlags.type]),
|
||||
additional = field(CxxRuleAdditionalParams.type, CxxRuleAdditionalParams()),
|
||||
# A function which enables the caller to inject subtargets into the link_style provider
|
||||
# as well as create custom providers based on the link styles.
|
||||
link_style_sub_targets_and_providers_factory = field("function", lambda _link_style, _context, _executable, _external_debug_info: ({}, [])),
|
||||
# Optinal postprocessor used to post postprocess the artifacts
|
||||
link_postprocessor = field(["cmd_args", None], None),
|
||||
# Linker flags that tell the linker to create shared libraries, overriding the default shared library flags.
|
||||
# e.g. when building Apple tests, we want to link with `-bundle` instead of `-shared` to allow
|
||||
# linking against the bundle loader.
|
||||
shared_library_flags = field([SharedLibraryFlagOverrides.type, None], None),
|
||||
# If passed to cxx_library_parameterized, this field will be used to determine
|
||||
# a shared subtarget's default output should be stripped.
|
||||
#
|
||||
# If passed to cxx_executable, this field will be used to determine
|
||||
# a shared subtarget's default output should be stripped.
|
||||
strip_executable = field(bool.type, False),
|
||||
strip_args_factory = field("function", lambda _: cmd_args()),
|
||||
# Wether to embed the library name as the SONAME.
|
||||
use_soname = field(bool.type, True),
|
||||
# Use link group's linking logic regardless whether a link group map's present.
|
||||
force_link_group_linking = field(bool.type, False),
|
||||
# Function to use for setting Xcode attributes for the Xcode data sub target.
|
||||
cxx_populate_xcode_attributes_func = field("function", cxx_populate_xcode_attributes),
|
||||
# Define which sub targets to generate.
|
||||
generate_sub_targets = field(CxxRuleSubTargetParams.type, CxxRuleSubTargetParams()),
|
||||
# Define which providers to generate.
|
||||
generate_providers = field(CxxRuleProviderParams.type, CxxRuleProviderParams()),
|
||||
# Force this library to be a Python Omnibus root.
|
||||
is_omnibus_root = field(bool.type, False),
|
||||
# Emit an Omnibus shared root for this node even if it's not an Omnibus
|
||||
# root. This only makes sense to use in tests.
|
||||
force_emit_omnibus_shared_root = field(bool.type, False),
|
||||
force_full_hybrid_if_capable = field(bool.type, False),
|
||||
# Whether shared libs for executables should generate a shared lib link tree.
|
||||
exe_shared_libs_link_tree = field(bool.type, True),
|
||||
extra_link_deps = field([LinkableProviders.type], []),
|
||||
auto_link_group_specs = field([[LinkGroupLibSpec.type], None], None),
|
||||
link_group_info = field([LinkGroupInfo.type, None], None),
|
||||
)
|
||||
23
vendor/cxx/tools/buck/prelude/cxx/debug.bzl
vendored
Normal file
23
vendor/cxx/tools/buck/prelude/cxx/debug.bzl
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# 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.
|
||||
|
||||
# Model the various "split" debug scenarios (e.g. `-gsplit-dwarf`).
|
||||
SplitDebugMode = enum(
|
||||
# Debug info, if present, is inline in the object file, and will be linked
|
||||
# into executables and shared libraries (e.g. traditional behavior when
|
||||
# using `-g`).
|
||||
"none",
|
||||
# Debug info. if present is included in the object file, but will *not* be
|
||||
# linked into executables and shared libraries. This style usually requires
|
||||
# an additional step, separate from the link, to combine and package debug
|
||||
# info (e.g. `dSYM`, `dwp`).
|
||||
"single",
|
||||
# FIXME(agallagher): Add support for "split", which probably just requires
|
||||
# modifying `compile_cxx` to add a `.dwo` file as a hidden output in this
|
||||
# case.
|
||||
#"split",
|
||||
)
|
||||
253
vendor/cxx/tools/buck/prelude/cxx/dist_lto/README.md
vendored
Normal file
253
vendor/cxx/tools/buck/prelude/cxx/dist_lto/README.md
vendored
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
# Distributed ThinLTO in Buck2
|
||||
Sean Gillespie, April 2022
|
||||
|
||||
This document is a technical overview into Buck2's implementation of a distributed ThinLTO.
|
||||
Like all rules in Buck2, this implementation is written entirely in Starlark, contained in
|
||||
`dist_lto.bzl` (in this same directory).
|
||||
|
||||
## Motivation
|
||||
|
||||
First, I highly recommend watching [Teresa Johnson's CppCon2017 talk about ThinLTO](https://www.youtube.com/watch?v=p9nH2vZ2mNo),
|
||||
which covers the topics in this section in much greater detail than I can.
|
||||
|
||||
C and C++ have long enjoyed significant optimizations at the hands of compilers. However, they have also
|
||||
long suffered a fundamental limitation; a C or C++ compiler can only optimize code that it sees in a single
|
||||
translation unit. For a language like C or C++, this means in practice that only code that is included via
|
||||
the preprocessor or specified in the translation unit can be optimized as a single unit. C and C++ compilers
|
||||
are unable to inline functions that are defined in different translation units. However, a crucial advantage
|
||||
of this compilation model is that all C and C++ compiler invocations are *completely parallelizable*; despite
|
||||
sacrificing some code quality, C and C++ compilation turns into a massively parallel problem with a serial
|
||||
link step at the very end.
|
||||
|
||||
```
|
||||
flowchart LR;
|
||||
header.h;
|
||||
|
||||
header.h --> a.cpp;
|
||||
header.h -->|#include| b.cpp;
|
||||
header.h --> c.cpp;
|
||||
|
||||
a.cpp --> a.o;
|
||||
b.cpp -->|clang++ -O2| b.o;
|
||||
c.cpp --> c.o;
|
||||
|
||||
a.o --> main;
|
||||
b.o -->|ld| main;
|
||||
c.o --> main;
|
||||
```
|
||||
|
||||
([Rendered](https://fburl.com/mermaid/rzup8o32). Compilation and optimization of a, b, and c can proceed in parallel.)
|
||||
|
||||
|
||||
In cases where absolute performance is required, though, the inability to perform cross-translation-unit
|
||||
(or "cross-module", in LLVM parlance) optimizations becomes more of a problem. To solve this, a new compilation
|
||||
paradigm was designed, dubbed "Link-Time Optimization" (LTO). In this scheme, a compiler will not produce machine code
|
||||
when processing a translation unit; rather, it will output the compiler's intermediate representation (e.g. LLVM bitcode).
|
||||
Later on, when it is time for the linker to run, it will load all of the compiler IR into one giant module, run
|
||||
optimization passes on the mega-module, and produce a final binary from that.
|
||||
|
||||
This works quite well, if all that you're looking for is run-time performance. A major drawback of the LTO approach is
|
||||
that all of the parallelism gained from optimizing translation units individually is now completely lost; instead, the
|
||||
linker (using a plugin) will do a single-threaded pass of *all code* produced by compilation steps. This is extremely
|
||||
slow, memory-intensive, and unable to be run incrementally. There are targets at Meta that simply can't be LTO-compiled
|
||||
because of their size.
|
||||
|
||||
```
|
||||
flowchart LR;
|
||||
header.h;
|
||||
|
||||
header.h --> a.cpp;
|
||||
header.h -->|#include| b.cpp;
|
||||
header.h --> c.cpp;
|
||||
|
||||
a.cpp --> a.bc;
|
||||
b.cpp -->|clang++ -O2 -flto -x ir| b.bc;
|
||||
c.cpp --> c.bc;
|
||||
|
||||
a.bc --> a_b_c.bc;
|
||||
b.bc -->|linker driver| a_b_c.bc;
|
||||
c.bc --> a_b_c.bc;
|
||||
|
||||
a_b_c.bc -->|opt| a_b_c_optimized.bc
|
||||
|
||||
a_b_c_optimized.bc -->|codegen| main.o
|
||||
|
||||
main.o --> |ld| main
|
||||
```
|
||||
([Rendered](https://fburl.com/mermaid/kid35io9). `a.bc`, `b.bc`, and `c.bc` are LLVM bitcode; they are all merged
|
||||
together into a single module, `a_b_c_optimized.bc`, which is then optimized and codegen'd into a final binary.)
|
||||
|
||||
The idea of ThinLTO comes from a desire to maintain the ability to optimize modules in parallel while still
|
||||
allowing for profitable cross-module optimizations. The idea is this:
|
||||
|
||||
1. Just like regular LTO, the compiler emits bitcode instead of machine code. However, it also contains some light
|
||||
metadata such as a call graph of symbols within the module.
|
||||
2. The monolithic LTO link is split into three steps: `index`, `opt`, and `link`.
|
||||
|
||||
```
|
||||
flowchart LR;
|
||||
header.h;
|
||||
|
||||
header.h --> a.cpp;
|
||||
header.h -->|#include| b.cpp;
|
||||
header.h --> c.cpp;
|
||||
|
||||
a.cpp --> a.bc;
|
||||
b.cpp -->|clang++ -O2 -flto -x ir| b.bc;
|
||||
c.cpp --> c.bc;
|
||||
|
||||
a.bc --> index;
|
||||
b.bc --> index;
|
||||
c.bc --> index;
|
||||
|
||||
index --> a.thinlto.bc;
|
||||
index --> b.thinlto.bc;
|
||||
index --> c.thinlto.bc;
|
||||
|
||||
a.thinlto.bc --> a.o;
|
||||
b.thinlto.bc --> b.o;
|
||||
b.bc --> a.o;
|
||||
b.bc --> c.o;
|
||||
c.thinlto.bc --> c.o;
|
||||
|
||||
a.o --> main;
|
||||
b.o -->|ld| main;
|
||||
c.o --> main;
|
||||
```
|
||||
|
||||
([Rendered](https://fburl.com/mermaid/56oc99t5))
|
||||
|
||||
The `index` step looks like a link step. However, it does not produce a final binary; instead, it looks at every
|
||||
compiler IR input file that it receives and heuristically determines which other IR modules it should be optimized
|
||||
with in order to achieve profitable optimizations. These modules might include functions that the index step thinks
|
||||
probably will get inlined, or globals that are read in the target IR input file. The output of the index step is a
|
||||
series of files on disk that indicate which sibling object files should be present when optimizing a particular object
|
||||
file, for each object file in the linker command-line.
|
||||
|
||||
The `opt` step runs in parallel for every object file. Each object file will be optimized using the compiler's
|
||||
optimizer (e.g. `opt`, for LLVM). The optimizer will combine the objects that were referenced as part of the index
|
||||
step as potentially profitable to include and optimize them all together.
|
||||
|
||||
The `link` step takes the outputs of `opt` and links them together, like a normal linker.
|
||||
|
||||
In practice, ThinLTO manages to recapture the inherent parallelism of C/C++ compilation by pushing the majority of work
|
||||
to the parallel `opt` phase of execution. When LLVM performs ThinLTO by default, it will launch a thread pool and process
|
||||
independent modules in parallel. ThinLTO does not produce as performant a binary as a monolithic LTO; however, in practice,
|
||||
ThinLTO binaries [paired with AutoFDO](https://fburl.com/wiki/q480euco) perform comparably to monolithic LTO. Furthermore,
|
||||
ThinLTO's greater efficiency allows for more expensive optimization passes to be run, which can further improve code quality
|
||||
near that of a monolithic LTO.
|
||||
|
||||
This is all great, and ThinLTO has been in use at Meta for some time. However, Buck2 has the ability to take a step
|
||||
further than Buck1 could ever have - Buck2 can distribute parallel `opt` actions across many machines via Remote Execution
|
||||
to achieve drastic speedups in ThinLTO wall clock time, memory usage, and incrementality.
|
||||
|
||||
## Buck2's Implementation
|
||||
|
||||
Buck2's role in a distributed ThinLTO compilation is to construct a graph of actions that directly mirrors the graph
|
||||
that the `index` step outputs. The graph that the `index` step outputs is entirely dynamic and, as such, the build
|
||||
system is only aware of what the graph could be after the `index` step is complete. Unlike Buck1 (or even Blaze/Bazel),
|
||||
Buck2 has explicit support for this paradigm [("dynamic dependencies")](https://fburl.com/gdoc/zklwhkll). Therefore, for Buck2, the basic strategy looks like:
|
||||
|
||||
1. Invoke `clang` to act as `index`. `index` will output a file for every object file that indicates what other modules
|
||||
need to be present when running `opt` on the object file (an "imports file").
|
||||
2. Read imports files and construct a graph of dynamic `opt` actions whose dependencies mirror the contents of the imports files.
|
||||
3. Collect the outputs from the `opt` actions and invoke the linker to produce a final binary.
|
||||
|
||||
Action `2` is inherently dynamic, since it must read the contents of files produced as part of action `1`. Furthermore,
|
||||
Buck2's support of `1` is complicated by the fact that certain Buck2 rules can produce an archive of object files as
|
||||
an output (namely, the Rust compiler). As a result, Buck2's implementation of Distributed ThinLTO is highly dynamic.
|
||||
|
||||
Buck2's implementation contains four phases of actions:
|
||||
|
||||
1. `thin_lto_prepare`, which specifically handles archives containing LLVM IR and prepares them to be inputs to `thin_lto_index`,
|
||||
2. `thin_lto_index`, which invokes LLVM's ThinLTO indexer to produce a imports list for every object file to be optimized,
|
||||
3. `thin_lto_opt`, which optimizes each object file in parallel with its imports present,
|
||||
4. `thin_lto_link`, which links together the optimized code into a final binary.
|
||||
|
||||
### thin_lto_prepare
|
||||
|
||||
It is a reality of Buck2 today that some rules don't produce a statically-known list of object files. The list of object
|
||||
files is known *a priori* during C/C++ compilation, since they have a one-to-one correspondence to source files; however,
|
||||
the Rust compiler emits an archive of object files; without inspecting the archive, Buck2 has no way of knowing what
|
||||
the contents of the archive are, or even if they contain bitcode at all.
|
||||
|
||||
Future steps (particularly `thin_lto_index`) are defined to only operate on a list of object files - a limitation [inherited from LLVM](https://lists.llvm.org/pipermail/llvm-dev/2019-June/133145.html). Therefore, it is the job of `thin_lto_prepare` to turn an archive into a list of objects - namely, by extracting the archive into a directory.
|
||||
|
||||
Buck2 dispatches a `thin_lto_prepare` action for every archive. Each prepare action has two outputs:
|
||||
|
||||
1. An **output directory** (called `objects` in the code), a directory that contains the unextracted contents of the archive.
|
||||
2. A **archive manifest**, a JSON document containing a list of object files that are contained in the output directory.
|
||||
|
||||
The core logic of this action is implemented in the Python script `dist_lto_prepare.py`, contained in the `tools` directory. In addition to unpacking each archive, Buck2
|
||||
keeps track of the list of archives as a Starlark array that will be referenced by index
|
||||
in later steps.
|
||||
|
||||
### thin_lto_index
|
||||
|
||||
With all archives prepared, the next step is to invoke LLVM's ThinLTO indexer. For the purposes of Buck2, the indexer
|
||||
looks like a linker; because of this, Buck2 must construct a reasonable link line. Buck2 does this by iterating over the
|
||||
list of linkables that it has been given and constructing a link line from them. Uniquely for distributed ThinLTO, Buck2
|
||||
must wrap all objects that were derived from `thin_lto_prepare` (i.e. were extracted from archives) with `-Wl,--start-lib`
|
||||
and `-Wl,--end-lib` to ensure that they are still treated as if they were archives by the indexer.
|
||||
|
||||
Invoking the indexer is relatively straightforward in that Buck2 invokes it like it would any other linker. However,
|
||||
once the indexer returns, Buck2 must post-process its output into a format that Buck2's Starlark can understand and
|
||||
translate into a graph of dynamic `opt` actions. The first thing that Buck2 is write a "meta file" to disk, which
|
||||
communicates inputs and outputs of `thin_lto_index` to a Python script, `dist_lto_planner.py`. The meta file contains
|
||||
a list of 7-tuples, whose members are:
|
||||
|
||||
1. The path to the source bitcode file. This is used as an index into
|
||||
a dictionary that records much of the metadata coming
|
||||
from these lines.
|
||||
2. The path to an output file. `dist_lto_planner.py`is expected to place a
|
||||
ThinLTO index file at this location (suffixed `.thinlto.bc`).
|
||||
3. The path to an output plan. This script is expected to place a link
|
||||
plan here (a JSON document indicating which other object files this)
|
||||
object file depends on, among other things.
|
||||
4. If this object file came from an archive, the index of the archive in
|
||||
the Starlark archives array.
|
||||
5. If this object file came from an archive, the name of the archive.
|
||||
6. If this object file came from an archive, the path to an output plan.
|
||||
This script is expected to produce an archive link plan here (a JSON)
|
||||
document similar to the object link plan, except containing link
|
||||
information for every file in the archive from which this object
|
||||
came.
|
||||
7. If this object file came from an archive, the indexes directory of that
|
||||
archive. This script is expected to place all ThinLTO indexes derived
|
||||
from object files originating from this archive in that directory.
|
||||
|
||||
There are two indices that are derived from this meta file: the object
|
||||
index (`mapping["index"]`) and the archive index (`mapping["archive_index"]`).
|
||||
These indices are indices into Starlark arrays for all objects and archive
|
||||
linkables, respectively. `dist_lto_planner.py` script does not inspect them; rather,
|
||||
it is expected to communicate these indices back to Starlark by writing them to the
|
||||
link plan.
|
||||
|
||||
`dist_lto_planner.py` reads the index and imports file produced by LLVM and derives
|
||||
a number of artifacts:
|
||||
|
||||
1. For each object file, a `thinlto.bc` file (`bitcode_file`). This file is the same as the input bitcode file, except that LLVM has inserted a number of module imports to refer to the other modules that will be present when the object file is optimized.
|
||||
2. For each object file, an optimization plan (`plan`). The optimization plan is a JSON document indicating how to construct an `opt` action for this object file. This plan includes
|
||||
this object file's module imports, whether or not this file contains bitcode at all, a location to place the optimized object file, and a list of archives that this object file imported.
|
||||
3. For each archive, an optimization plan (`archive_plan`), which contains optimization plans for all of the object files contained within the archive.
|
||||
|
||||
This action is a dynamic action because, in the case that there are archives that needed to be preprocessed by `thin_lto_prepare`, this action must read the archive manifest.
|
||||
|
||||
### thin_lto_opt
|
||||
|
||||
After `thin_lto_index` completes, Buck2 launches `thin_lto_opt` actions for every object file and for every archive. For each object file, Buck2 reads that object file's optimization plan.
|
||||
At this phase, it is Buck2's responsibility to declare dependencies on every object file referenced by that object's compilation plan; it does so here by adding `hidden` dependencies
|
||||
on every object file and archive that the archive plan says that this object depends on.
|
||||
|
||||
`thin_lto_opt` uses a Python wrapper around LLVM because of a bug (T116695431) where LTO fatal errors don't prevent `clang` from returning an exit code of zero. The Python script wraps
|
||||
`clang` and exits with a non-zero exit code if `clang` produced an empty object file.
|
||||
|
||||
For each archive, Buck2 reads the archive's optimization plan and constructs additional `thin_lto_opt` actions for each object file contained in the archive. Buck2 creates a directory of
|
||||
symlinks (`opt_objects`) that either contains symlinks to optimized object files (if the object file contained bitcode) or the original object file (if it didn't). The purpose of this symlink directory is to allow the final link to consume object files directly
|
||||
from this directory without having to know whether they were optimized or not. Paths to these files are passed to the link step
|
||||
via the optimization manifest (`opt_manifest`).
|
||||
|
||||
### thin_lto_link
|
||||
|
||||
The final link step. Similar to `thin_lto_index`, this involves creating a link line to feed to the linker that uses the optimized artifacts that we just calculated. In cases where Buck2
|
||||
would put an archive on the link line, it instead inserts `-Wl,--start-lib`, `-Wl,--end-lib`, and references to the objects in `opt_objects`.
|
||||
638
vendor/cxx/tools/buck/prelude/cxx/dist_lto/dist_lto.bzl
vendored
Normal file
638
vendor/cxx/tools/buck/prelude/cxx/dist_lto/dist_lto.bzl
vendored
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
# 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:cxx_bolt.bzl",
|
||||
"bolt",
|
||||
"cxx_use_bolt",
|
||||
)
|
||||
load(
|
||||
"@prelude//cxx:cxx_link_utility.bzl",
|
||||
"cxx_link_cmd",
|
||||
)
|
||||
load("@prelude//cxx:cxx_toolchain_types.bzl", "CxxToolchainInfo")
|
||||
load("@prelude//cxx:debug.bzl", "SplitDebugMode")
|
||||
load(
|
||||
"@prelude//cxx:dwp.bzl",
|
||||
"run_dwp_action",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"ArchiveLinkable", # @unused Used as a type
|
||||
"FrameworksLinkable", # @unused Used as a type
|
||||
"LinkArgs",
|
||||
"LinkableType",
|
||||
"LinkedObject",
|
||||
"ObjectsLinkable", # @unused Used as a type
|
||||
"SharedLibLinkable", # @unused Used as a type
|
||||
"append_linkable_args",
|
||||
"map_to_link_infos",
|
||||
)
|
||||
|
||||
def cxx_dist_link(
|
||||
ctx: "context",
|
||||
links: ["LinkArgs"],
|
||||
# The destination for the link output.
|
||||
output: "artifact",
|
||||
linker_map: ["artifact", None] = None,
|
||||
# A category suffix that will be added to the category of the link action that is generated.
|
||||
category_suffix: [str.type, None] = None,
|
||||
# An identifier that will uniquely name this link action in the context of a category. Useful for
|
||||
# differentiating multiple link actions in the same rule.
|
||||
identifier: [str.type, None] = None,
|
||||
# This action will only happen if split_dwarf is enabled via the toolchain.
|
||||
generate_dwp: bool.type = True,
|
||||
executable_link: bool.type = True) -> LinkedObject.type:
|
||||
"""
|
||||
Perform a distributed thin-lto link into the supplied output
|
||||
|
||||
Distributed thinlto splits the link into three stages:
|
||||
1. global "indexing" step
|
||||
2. many individual compilation unit optimization steps
|
||||
3. final global link step
|
||||
|
||||
The 2nd and 3rd of those are done just by constructing compiler/linker commands (in dynamic_output
|
||||
sections) using the output of the first.
|
||||
|
||||
For the first, we need to post-process the linker index output to get it into a form
|
||||
that is easy for us to consume from within bzl.
|
||||
"""
|
||||
|
||||
def make_cat(c: str.type) -> str.type:
|
||||
""" Used to make sure categories for our actions include the provided suffix """
|
||||
if category_suffix != None:
|
||||
return c + "_" + category_suffix
|
||||
return c
|
||||
|
||||
def make_id(i: str.type) -> str.type:
|
||||
""" Used to make sure identifiers for our actions include the provided identifier """
|
||||
if identifier != None:
|
||||
return identifier + "_" + i
|
||||
return i
|
||||
|
||||
recorded_outputs = {}
|
||||
|
||||
def name_for_obj(link_name: str.type, object_artifact: "artifact") -> str.type:
|
||||
""" Creates a unique name/path we can use for a particular object file input """
|
||||
prefix = "{}/{}".format(link_name, object_artifact.short_path)
|
||||
|
||||
# it's possible (though unlikely) that we can get duplicate name/short_path, so just uniquify them
|
||||
if prefix in recorded_outputs:
|
||||
recorded_outputs[prefix] += 1
|
||||
extra = recorded_outputs[prefix]
|
||||
prefix = "{}-{}".format(prefix, extra)
|
||||
else:
|
||||
recorded_outputs[prefix] = 1
|
||||
return prefix
|
||||
|
||||
names = {}
|
||||
|
||||
def name_for_link(info: "LinkInfo") -> str.type:
|
||||
""" Creates a unique name for a LinkInfo that we are consuming """
|
||||
name = info.name or "unknown"
|
||||
if name not in names:
|
||||
names[name] = 1
|
||||
else:
|
||||
names[name] += 1
|
||||
name += "-{}".format(names[name])
|
||||
return make_id(name)
|
||||
|
||||
links = [
|
||||
LinkArgs(
|
||||
tset = link.tset,
|
||||
flags = link.flags,
|
||||
infos = link.infos,
|
||||
)
|
||||
for link in links
|
||||
]
|
||||
|
||||
link_infos = map_to_link_infos(links)
|
||||
|
||||
cxx_toolchain = ctx.attrs._cxx_toolchain[CxxToolchainInfo]
|
||||
lto_planner = cxx_toolchain.dist_lto_tools_info.planner
|
||||
lto_opt = cxx_toolchain.dist_lto_tools_info.opt
|
||||
lto_prepare = cxx_toolchain.dist_lto_tools_info.prepare
|
||||
lto_copy = cxx_toolchain.dist_lto_tools_info.copy
|
||||
|
||||
BitcodeLinkData = record(
|
||||
name = str.type,
|
||||
initial_object = "artifact",
|
||||
bc_file = "artifact",
|
||||
plan = "artifact",
|
||||
opt_object = "artifact",
|
||||
)
|
||||
|
||||
ArchiveLinkData = record(
|
||||
name = str.type,
|
||||
manifest = "artifact",
|
||||
# A file containing paths to artifacts that are known to reside in opt_objects_dir.
|
||||
opt_manifest = "artifact",
|
||||
objects_dir = "artifact",
|
||||
opt_objects_dir = "artifact",
|
||||
indexes_dir = "artifact",
|
||||
plan = "artifact",
|
||||
link_whole = bool.type,
|
||||
prepend = bool.type,
|
||||
)
|
||||
|
||||
DataType = enum(
|
||||
"bitcode",
|
||||
"archive",
|
||||
"cmd_args",
|
||||
)
|
||||
|
||||
IndexLinkData = record(
|
||||
data_type = DataType.type,
|
||||
link_data = field([BitcodeLinkData.type, ArchiveLinkData.type]),
|
||||
)
|
||||
|
||||
PrePostFlags = record(
|
||||
pre_flags = list.type,
|
||||
post_flags = list.type,
|
||||
)
|
||||
|
||||
PREPEND_ARCHIVE_NAMES = [
|
||||
# T130644072: If linked with `--whole-archive`, Clang builtins must be at the
|
||||
# front of the argument list to avoid conflicts with identically-named Rust
|
||||
# symbols from the `compiler_builtins` crate.
|
||||
#
|
||||
# Once linking of C++ binaries starts to use `rlib`s, this may no longer be
|
||||
# necessary, because our Rust `rlib`s won't need to contain copies of
|
||||
# `compiler_builtins` to begin with, unlike our Rust `.a`s which presently do
|
||||
# (T130789782).
|
||||
"clang_rt.builtins",
|
||||
]
|
||||
|
||||
# Information used to construct thinlto index link command:
|
||||
# Note: The index into index_link_data is important as it's how things will appear in
|
||||
# the plans produced by indexing. That allows us to map those indexes back to
|
||||
# the actual artifacts.
|
||||
index_link_data = []
|
||||
linkables_index = {}
|
||||
pre_post_flags = {}
|
||||
|
||||
# buildifier: disable=uninitialized
|
||||
def add_linkable(idx: int.type, linkable: [ArchiveLinkable.type, SharedLibLinkable.type, ObjectsLinkable.type, FrameworksLinkable.type]):
|
||||
if idx not in linkables_index:
|
||||
linkables_index[idx] = [linkable]
|
||||
else:
|
||||
linkables_index[idx].append(linkable)
|
||||
|
||||
# buildifier: disable=uninitialized
|
||||
def add_pre_post_flags(idx: int.type, flags: PrePostFlags.type):
|
||||
if idx not in pre_post_flags:
|
||||
pre_post_flags[idx] = [flags]
|
||||
else:
|
||||
pre_post_flags[idx].append(flags)
|
||||
|
||||
# Information used to construct the dynamic plan:
|
||||
plan_inputs = []
|
||||
plan_outputs = []
|
||||
|
||||
# Information used to construct the opt dynamic outputs:
|
||||
archive_opt_manifests = []
|
||||
|
||||
prepare_cat = make_cat("thin_lto_prepare")
|
||||
|
||||
for link in link_infos:
|
||||
link_name = name_for_link(link)
|
||||
idx = len(index_link_data)
|
||||
|
||||
add_pre_post_flags(idx, PrePostFlags(
|
||||
pre_flags = link.pre_flags,
|
||||
post_flags = link.post_flags,
|
||||
))
|
||||
|
||||
for linkable in link.linkables:
|
||||
if linkable._type == LinkableType("objects"):
|
||||
add_linkable(idx, linkable)
|
||||
for obj in linkable.objects:
|
||||
name = name_for_obj(link_name, obj)
|
||||
bc_output = ctx.actions.declare_output(name + ".thinlto.bc")
|
||||
plan_output = ctx.actions.declare_output(name + ".opt.plan")
|
||||
opt_output = ctx.actions.declare_output(name + ".opt.o")
|
||||
|
||||
data = IndexLinkData(
|
||||
data_type = DataType("bitcode"),
|
||||
link_data = BitcodeLinkData(
|
||||
name = name,
|
||||
initial_object = obj,
|
||||
bc_file = bc_output,
|
||||
plan = plan_output,
|
||||
opt_object = opt_output,
|
||||
),
|
||||
)
|
||||
index_link_data.append(data)
|
||||
plan_outputs.extend([bc_output, plan_output])
|
||||
elif linkable._type == LinkableType("archive"):
|
||||
# Our implementation of Distributed ThinLTO operates on individual objects, not archives. Since these
|
||||
# archives might still contain LTO-able bitcode, we first extract the objects within the archive into
|
||||
# another directory and write a "manifest" containing the list of objects that the archive contained.
|
||||
#
|
||||
# Later actions in the LTO compilation pipeline will read this manifest and dynamically dispatch
|
||||
# actions on the objects that the manifest reports.
|
||||
|
||||
name = name_for_obj(link_name, linkable.archive.artifact)
|
||||
archive_manifest = ctx.actions.declare_output("%s/%s/manifest.json" % (prepare_cat, name))
|
||||
archive_objects = ctx.actions.declare_output("%s/%s/objects" % (prepare_cat, name))
|
||||
archive_opt_objects = ctx.actions.declare_output("%s/%s/opt_objects" % (prepare_cat, name))
|
||||
archive_indexes = ctx.actions.declare_output("%s/%s/indexes" % (prepare_cat, name))
|
||||
archive_plan = ctx.actions.declare_output("%s/%s/plan.json" % (prepare_cat, name))
|
||||
archive_opt_manifest = ctx.actions.declare_output("%s/%s/opt_objects.manifest" % (prepare_cat, name))
|
||||
prepare_args = cmd_args([
|
||||
lto_prepare,
|
||||
"--manifest-out",
|
||||
archive_manifest.as_output(),
|
||||
"--objects-out",
|
||||
archive_objects.as_output(),
|
||||
"--ar",
|
||||
cxx_toolchain.linker_info.archiver,
|
||||
"--archive",
|
||||
linkable.archive.artifact,
|
||||
"--name",
|
||||
name,
|
||||
])
|
||||
ctx.actions.run(prepare_args, category = make_cat("thin_lto_prepare"), identifier = name)
|
||||
|
||||
data = IndexLinkData(
|
||||
data_type = DataType("archive"),
|
||||
link_data = ArchiveLinkData(
|
||||
name = name,
|
||||
manifest = archive_manifest,
|
||||
opt_manifest = archive_opt_manifest,
|
||||
objects_dir = archive_objects,
|
||||
opt_objects_dir = archive_opt_objects,
|
||||
indexes_dir = archive_indexes,
|
||||
plan = archive_plan,
|
||||
link_whole = linkable.link_whole,
|
||||
prepend = link_name in PREPEND_ARCHIVE_NAMES,
|
||||
),
|
||||
)
|
||||
index_link_data.append(data)
|
||||
archive_opt_manifests.append(archive_opt_manifest)
|
||||
plan_inputs.extend([archive_manifest, archive_objects])
|
||||
plan_outputs.extend([archive_indexes, archive_plan])
|
||||
else:
|
||||
add_linkable(idx, linkable)
|
||||
|
||||
index_argsfile_out = ctx.actions.declare_output(output.basename + ".thinlto.index.argsfile")
|
||||
final_link_index = ctx.actions.declare_output(output.basename + ".final_link_index")
|
||||
|
||||
def dynamic_plan(link_plan: "artifact", index_argsfile_out: "artifact", final_link_index: "artifact"):
|
||||
def plan(ctx, artifacts, outputs):
|
||||
# buildifier: disable=uninitialized
|
||||
def add_pre_flags(idx: int.type):
|
||||
if idx in pre_post_flags:
|
||||
for flags in pre_post_flags[idx]:
|
||||
index_args.add(flags.pre_flags)
|
||||
|
||||
# buildifier: disable=uninitialized
|
||||
def add_post_flags(idx: int.type):
|
||||
if idx in pre_post_flags:
|
||||
for flags in pre_post_flags[idx]:
|
||||
index_args.add(flags.post_flags)
|
||||
|
||||
# buildifier: disable=uninitialized
|
||||
def add_linkables_args(idx: int.type):
|
||||
if idx in linkables_index:
|
||||
object_link_arg = cmd_args()
|
||||
for linkable in linkables_index[idx]:
|
||||
append_linkable_args(object_link_arg, linkable)
|
||||
index_args.add(object_link_arg)
|
||||
|
||||
# index link command args
|
||||
prepend_index_args = cmd_args()
|
||||
index_args = cmd_args()
|
||||
|
||||
# See comments in dist_lto_planner.py for semantics on the values that are pushed into index_meta.
|
||||
index_meta = cmd_args()
|
||||
|
||||
# buildifier: disable=uninitialized
|
||||
for idx, artifact in enumerate(index_link_data):
|
||||
add_pre_flags(idx)
|
||||
add_linkables_args(idx)
|
||||
|
||||
link_data = artifact.link_data
|
||||
|
||||
if artifact.data_type == DataType("bitcode"):
|
||||
index_meta.add(link_data.initial_object, outputs[link_data.bc_file].as_output(), outputs[link_data.plan].as_output(), str(idx), "", "", "")
|
||||
|
||||
elif artifact.data_type == DataType("archive"):
|
||||
manifest = artifacts[link_data.manifest].read_json()
|
||||
|
||||
if not manifest["objects"]:
|
||||
# Despite not having any objects (and thus not needing a plan), we still need to bind the plan output.
|
||||
ctx.actions.write(outputs[link_data.plan].as_output(), "{}")
|
||||
cmd = cmd_args(["/bin/sh", "-c", "mkdir", "-p", outputs[link_data.indexes_dir].as_output()])
|
||||
ctx.actions.run(cmd, category = make_cat("thin_lto_mkdir"), identifier = link_data.name)
|
||||
continue
|
||||
|
||||
archive_args = prepend_index_args if link_data.prepend else index_args
|
||||
|
||||
archive_args.hidden(link_data.objects_dir)
|
||||
|
||||
if not link_data.link_whole:
|
||||
archive_args.add("-Wl,--start-lib")
|
||||
|
||||
for obj in manifest["objects"]:
|
||||
index_meta.add(obj, "", "", str(idx), link_data.name, outputs[link_data.plan].as_output(), outputs[link_data.indexes_dir].as_output())
|
||||
archive_args.add(obj)
|
||||
|
||||
if not link_data.link_whole:
|
||||
archive_args.add("-Wl,--end-lib")
|
||||
|
||||
archive_args.hidden(link_data.objects_dir)
|
||||
|
||||
add_post_flags(idx)
|
||||
|
||||
# add any link_infos cmd_args that come after the last bitcode or archive
|
||||
add_pre_flags(len(index_link_data))
|
||||
add_linkables_args(len(index_link_data))
|
||||
add_post_flags(len(index_link_data))
|
||||
|
||||
index_argfile, _ = ctx.actions.write(
|
||||
outputs[index_argsfile_out].as_output(),
|
||||
prepend_index_args.add(index_args),
|
||||
allow_args = True,
|
||||
)
|
||||
|
||||
index_cat = make_cat("thin_lto_index")
|
||||
index_file_out = ctx.actions.declare_output(make_id(index_cat) + "/index")
|
||||
index_out_dir = cmd_args(index_file_out.as_output()).parent()
|
||||
|
||||
index_cmd = cxx_link_cmd(ctx)
|
||||
index_cmd.add(cmd_args(index_argfile, format = "@{}"))
|
||||
|
||||
output_as_string = cmd_args(output)
|
||||
output_as_string.ignore_artifacts()
|
||||
index_cmd.add("-o", output_as_string)
|
||||
index_cmd.add(cmd_args(index_file_out.as_output(), format = "-Wl,--thinlto-index-only={}"))
|
||||
index_cmd.add("-Wl,--thinlto-emit-imports-files")
|
||||
index_cmd.add("-Wl,--thinlto-full-index")
|
||||
index_cmd.add(cmd_args(index_out_dir, format = "-Wl,--thinlto-prefix-replace=;{}/"))
|
||||
|
||||
# Terminate the index file with a newline.
|
||||
index_meta.add("")
|
||||
index_meta_file = ctx.actions.write(
|
||||
output.basename + ".thinlto.meta",
|
||||
index_meta,
|
||||
)
|
||||
|
||||
plan_cmd = cmd_args([lto_planner, "--meta", index_meta_file, "--index", index_out_dir, "--link-plan", outputs[link_plan].as_output(), "--final-link-index", outputs[final_link_index].as_output(), "--"])
|
||||
plan_cmd.add(index_cmd)
|
||||
|
||||
plan_extra_inputs = cmd_args()
|
||||
plan_extra_inputs.add(index_meta)
|
||||
plan_extra_inputs.add(index_args)
|
||||
plan_cmd.hidden(plan_extra_inputs)
|
||||
|
||||
ctx.actions.run(plan_cmd, category = index_cat, identifier = identifier, local_only = True)
|
||||
|
||||
# TODO(T117513091) - dynamic_output does not allow for an empty list of dynamic inputs. If we have no archives
|
||||
# to process, we will have no dynamic inputs, and the plan action can be non-dynamic.
|
||||
#
|
||||
# However, buck2 disallows `dynamic_output` with a empty input list. We also can't call our `plan` function
|
||||
# directly, since it uses `ctx.outputs` to bind its outputs. Instead of doing Starlark hacks to work around
|
||||
# the lack of `ctx.outputs`, we declare an empty file as a dynamic input.
|
||||
plan_inputs.append(ctx.actions.write("plan_hack.txt", ""))
|
||||
plan_outputs.extend([link_plan, index_argsfile_out, final_link_index])
|
||||
ctx.actions.dynamic_output(dynamic = plan_inputs, inputs = [], outputs = plan_outputs, f = plan)
|
||||
|
||||
link_plan_out = ctx.actions.declare_output(output.basename + ".link-plan.json")
|
||||
dynamic_plan(link_plan = link_plan_out, index_argsfile_out = index_argsfile_out, final_link_index = final_link_index)
|
||||
|
||||
def prepare_opt_flags(link_infos: ["LinkInfo"]) -> "cmd_args":
|
||||
opt_args = cmd_args()
|
||||
opt_args.add(cxx_link_cmd(ctx))
|
||||
|
||||
# buildifier: disable=uninitialized
|
||||
for link in link_infos:
|
||||
for raw_flag in link.pre_flags + link.post_flags:
|
||||
opt_args.add(raw_flag)
|
||||
return opt_args
|
||||
|
||||
opt_common_flags = prepare_opt_flags(link_infos)
|
||||
|
||||
# We declare a separate dynamic_output for every object file. It would
|
||||
# maybe be simpler to have a single dynamic_output that produced all the
|
||||
# opt actions, but an action needs to re-run whenever the analysis that
|
||||
# produced it re-runs. And so, with a single dynamic_output, we'd need to
|
||||
# re-run all actions when any of the plans changed.
|
||||
def dynamic_optimize(name: str.type, initial_object: "artifact", bc_file: "artifact", plan: "artifact", opt_object: "artifact"):
|
||||
def optimize_object(ctx, artifacts, outputs):
|
||||
plan_json = artifacts[plan].read_json()
|
||||
|
||||
# If the object was not compiled with thinlto flags, then there
|
||||
# won't be valid outputs for it from the indexing, but we still
|
||||
# need to bind the artifact.
|
||||
if not plan_json["is_bc"]:
|
||||
ctx.actions.write(outputs[opt_object], "")
|
||||
return
|
||||
|
||||
opt_cmd = cmd_args(lto_opt)
|
||||
opt_cmd.add("--out", outputs[opt_object].as_output())
|
||||
opt_cmd.add("--input", initial_object)
|
||||
opt_cmd.add("--index", bc_file)
|
||||
|
||||
# When invoking opt and llc via clang, clang will not respect IR metadata to generate
|
||||
# dwo files unless -gsplit-dwarf is explicitly passed in. In other words, even if
|
||||
# 'splitDebugFilename' set in IR 'DICompileUnit', we still need to force clang to tell
|
||||
# llc to generate dwo sections.
|
||||
#
|
||||
# Local thinlto generates .dwo files by default. For distributed thinlto, however, we
|
||||
# want to keep all dwo debug info in the object file to reduce the number of files to
|
||||
# materialize.
|
||||
if cxx_toolchain.split_debug_mode == SplitDebugMode("single"):
|
||||
opt_cmd.add("--split-dwarf=single")
|
||||
|
||||
# Create an argsfile and dump all the flags to be processed later.
|
||||
opt_argsfile = ctx.actions.declare_output(outputs[opt_object].basename + ".opt.argsfile")
|
||||
ctx.actions.write(opt_argsfile.as_output(), opt_common_flags, allow_args = True)
|
||||
opt_cmd.hidden(opt_common_flags)
|
||||
opt_cmd.add("--args", opt_argsfile)
|
||||
|
||||
opt_cmd.add("--")
|
||||
opt_cmd.add(cxx_toolchain.cxx_compiler_info.compiler)
|
||||
|
||||
imports = [index_link_data[idx].link_data.initial_object for idx in plan_json["imports"]]
|
||||
archives = [index_link_data[idx].link_data.objects_dir for idx in plan_json["archive_imports"]]
|
||||
opt_cmd.hidden(imports)
|
||||
opt_cmd.hidden(archives)
|
||||
ctx.actions.run(opt_cmd, category = make_cat("thin_lto_opt"), identifier = name)
|
||||
|
||||
ctx.actions.dynamic_output(dynamic = [plan], inputs = [], outputs = [opt_object], f = optimize_object)
|
||||
|
||||
def dynamic_optimize_archive(archive: ArchiveLinkData.type):
|
||||
def optimize_archive(ctx, artifacts, outputs):
|
||||
plan_json = artifacts[archive.plan].read_json()
|
||||
if "objects" not in plan_json or not plan_json["objects"] or all([not entry["is_bc"] for entry in plan_json["objects"]]):
|
||||
# Nothing in this directory was lto-able; let's just copy the archive.
|
||||
ctx.actions.copy_file(outputs[archive.opt_objects_dir], archive.objects_dir)
|
||||
ctx.actions.write(outputs[archive.opt_manifest], "")
|
||||
return
|
||||
|
||||
output_dir = {}
|
||||
output_manifest = cmd_args()
|
||||
for entry in plan_json["objects"]:
|
||||
base_dir = plan_json["base_dir"]
|
||||
source_path = paths.relativize(entry["path"], base_dir)
|
||||
if not entry["is_bc"]:
|
||||
opt_object = ctx.actions.declare_output("%s/%s" % (make_cat("thin_lto_opt_copy"), source_path))
|
||||
output_manifest.add(opt_object)
|
||||
copy_cmd = cmd_args([
|
||||
lto_copy,
|
||||
"--to",
|
||||
opt_object.as_output(),
|
||||
"--from",
|
||||
entry["path"],
|
||||
])
|
||||
|
||||
copy_cmd.hidden(archive.objects_dir)
|
||||
ctx.actions.run(copy_cmd, category = make_cat("thin_lto_opt_copy"), identifier = source_path)
|
||||
output_dir[source_path] = opt_object
|
||||
continue
|
||||
|
||||
opt_object = ctx.actions.declare_output("%s/%s" % (make_cat("thin_lto_opt"), source_path))
|
||||
output_manifest.add(opt_object)
|
||||
output_dir[source_path] = opt_object
|
||||
opt_cmd = cmd_args(lto_opt)
|
||||
opt_cmd.add("--out", opt_object.as_output())
|
||||
opt_cmd.add("--input", entry["path"])
|
||||
opt_cmd.add("--index", entry["bitcode_file"])
|
||||
|
||||
if cxx_toolchain.split_debug_mode == SplitDebugMode("single"):
|
||||
opt_cmd.add("--split-dwarf=single")
|
||||
|
||||
opt_argsfile = ctx.actions.declare_output(opt_object.basename + ".opt.argsfile")
|
||||
ctx.actions.write(opt_argsfile.as_output(), opt_common_flags, allow_args = True)
|
||||
opt_cmd.add("--args", opt_argsfile)
|
||||
|
||||
opt_cmd.add("--")
|
||||
opt_cmd.add(cxx_toolchain.cxx_compiler_info.compiler)
|
||||
|
||||
imports = [index_link_data[idx].link_data.initial_object for idx in entry["imports"]]
|
||||
archives = [index_link_data[idx].link_data.objects_dir for idx in entry["archive_imports"]]
|
||||
opt_cmd.hidden(imports)
|
||||
opt_cmd.hidden(archives)
|
||||
opt_cmd.hidden(archive.indexes_dir)
|
||||
opt_cmd.hidden(archive.objects_dir)
|
||||
ctx.actions.run(opt_cmd, category = make_cat("thin_lto_opt"), identifier = source_path)
|
||||
|
||||
ctx.actions.symlinked_dir(outputs[archive.opt_objects_dir], output_dir)
|
||||
ctx.actions.write(outputs[archive.opt_manifest], output_manifest, allow_args = True)
|
||||
|
||||
archive_opt_inputs = [archive.plan]
|
||||
archive_opt_outputs = [archive.opt_objects_dir, archive.opt_manifest]
|
||||
ctx.actions.dynamic_output(dynamic = archive_opt_inputs, inputs = [], outputs = archive_opt_outputs, f = optimize_archive)
|
||||
|
||||
for artifact in index_link_data:
|
||||
link_data = artifact.link_data
|
||||
if artifact.data_type == DataType("bitcode"):
|
||||
dynamic_optimize(
|
||||
name = link_data.name,
|
||||
initial_object = link_data.initial_object,
|
||||
bc_file = link_data.bc_file,
|
||||
plan = link_data.plan,
|
||||
opt_object = link_data.opt_object,
|
||||
)
|
||||
elif artifact.data_type == DataType("archive"):
|
||||
dynamic_optimize_archive(link_data)
|
||||
|
||||
linker_argsfile_out = ctx.actions.declare_output(output.basename + ".thinlto.link.argsfile")
|
||||
|
||||
def thin_lto_final_link(ctx, artifacts, outputs):
|
||||
plan = artifacts[link_plan_out].read_json()
|
||||
link_args = cmd_args()
|
||||
plan_index = {int(k): v for k, v in plan["index"].items()}
|
||||
|
||||
# non_lto_objects are the ones that weren't compiled with thinlto
|
||||
# flags. In that case, we need to link against the original object.
|
||||
non_lto_objects = {int(k): 1 for k in plan["non_lto_objects"]}
|
||||
current_index = 0
|
||||
opt_objects = []
|
||||
archives = []
|
||||
for link in link_infos:
|
||||
link_args.add(link.pre_flags)
|
||||
for linkable in link.linkables:
|
||||
if linkable._type == LinkableType("objects"):
|
||||
new_objs = []
|
||||
for obj in linkable.objects:
|
||||
if current_index in plan_index:
|
||||
new_objs.append(index_link_data[current_index].link_data.opt_object)
|
||||
opt_objects.append(index_link_data[current_index].link_data.opt_object)
|
||||
elif current_index in non_lto_objects:
|
||||
new_objs.append(obj)
|
||||
opt_objects.append(obj)
|
||||
current_index += 1
|
||||
linkable = ObjectsLinkable(
|
||||
objects = new_objs,
|
||||
linker_type = linkable.linker_type,
|
||||
link_whole = linkable.link_whole,
|
||||
)
|
||||
|
||||
# TODO(T113841827): @christylee enable link_groups for distributed_thinlto
|
||||
|
||||
elif linkable._type == LinkableType("archive"):
|
||||
current_index += 1
|
||||
link_args.add(link.post_flags)
|
||||
|
||||
link_cmd = cxx_link_cmd(ctx)
|
||||
final_link_argfile, final_link_inputs = ctx.actions.write(
|
||||
outputs[linker_argsfile_out].as_output(),
|
||||
link_args,
|
||||
allow_args = True,
|
||||
)
|
||||
|
||||
# buildifier: disable=uninitialized
|
||||
for artifact in index_link_data:
|
||||
if artifact.data_type == DataType("archive"):
|
||||
link_cmd.hidden(artifact.link_data.opt_objects_dir)
|
||||
link_cmd.add(cmd_args(final_link_argfile, format = "@{}"))
|
||||
link_cmd.add(cmd_args(final_link_index, format = "@{}"))
|
||||
link_cmd.add("-o", outputs[output].as_output())
|
||||
link_cmd_extra_inputs = cmd_args()
|
||||
link_cmd_extra_inputs.add(final_link_inputs)
|
||||
link_cmd.hidden(link_cmd_extra_inputs)
|
||||
link_cmd.hidden(link_args)
|
||||
link_cmd.hidden(opt_objects)
|
||||
link_cmd.hidden(archives)
|
||||
|
||||
ctx.actions.run(link_cmd, category = make_cat("thin_lto_link"), identifier = identifier, local_only = True)
|
||||
|
||||
final_link_inputs = [link_plan_out, final_link_index] + archive_opt_manifests
|
||||
ctx.actions.dynamic_output(
|
||||
dynamic = final_link_inputs,
|
||||
inputs = [],
|
||||
outputs = [output] + ([linker_map] if linker_map else []) + [linker_argsfile_out],
|
||||
f = thin_lto_final_link,
|
||||
)
|
||||
|
||||
final_output = output if not (executable_link and cxx_use_bolt(ctx)) else bolt(ctx, output, identifier)
|
||||
dwp_output = ctx.actions.declare_output(output.short_path.removesuffix("-wrapper") + ".dwp") if generate_dwp else None
|
||||
|
||||
if generate_dwp:
|
||||
run_dwp_action(
|
||||
ctx = ctx,
|
||||
obj = final_output,
|
||||
identifier = identifier,
|
||||
category_suffix = category_suffix,
|
||||
referenced_objects = final_link_inputs,
|
||||
dwp_output = dwp_output,
|
||||
# distributed thinlto link actions are ran locally, run llvm-dwp locally as well to
|
||||
# ensure all dwo source files are available
|
||||
local_only = True,
|
||||
allow_huge_dwp = ctx.attrs.allow_huge_dwp,
|
||||
)
|
||||
|
||||
return LinkedObject(
|
||||
output = final_output,
|
||||
prebolt_output = output,
|
||||
dwp = dwp_output,
|
||||
linker_argsfile = linker_argsfile_out,
|
||||
index_argsfile = index_argsfile_out,
|
||||
)
|
||||
29
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools.bzl
vendored
Normal file
29
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools.bzl
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# 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//cxx:cxx_toolchain_types.bzl", "DistLtoToolsInfo")
|
||||
|
||||
def _impl(ctx):
|
||||
return [
|
||||
DefaultInfo(),
|
||||
DistLtoToolsInfo(
|
||||
planner = ctx.attrs.planner[RunInfo],
|
||||
prepare = ctx.attrs.prepare[RunInfo],
|
||||
opt = ctx.attrs.opt[RunInfo],
|
||||
copy = ctx.attrs.copy[RunInfo],
|
||||
),
|
||||
]
|
||||
|
||||
dist_lto_tools = rule(
|
||||
impl = _impl,
|
||||
attrs = {
|
||||
"copy": attrs.dep(),
|
||||
"opt": attrs.dep(),
|
||||
"planner": attrs.dep(),
|
||||
"prepare": attrs.dep(),
|
||||
},
|
||||
)
|
||||
44
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/TARGETS.v2
vendored
Normal file
44
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/TARGETS.v2
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
load("@prelude//cxx/dist_lto:tools.bzl", "dist_lto_tools")
|
||||
|
||||
prelude = native
|
||||
|
||||
prelude.python_bootstrap_binary(
|
||||
name = "dist_lto_planner",
|
||||
main = "dist_lto_planner.py",
|
||||
visibility = ["PUBLIC"],
|
||||
)
|
||||
|
||||
prelude.python_bootstrap_binary(
|
||||
name = "dist_lto_opt",
|
||||
main = "dist_lto_opt.py",
|
||||
visibility = ["PUBLIC"],
|
||||
)
|
||||
|
||||
prelude.python_bootstrap_binary(
|
||||
name = "dist_lto_prepare",
|
||||
main = "dist_lto_prepare.py",
|
||||
visibility = ["PUBLIC"],
|
||||
)
|
||||
|
||||
prelude.python_bootstrap_binary(
|
||||
name = "dist_lto_copy",
|
||||
main = "dist_lto_copy.py",
|
||||
visibility = ["PUBLIC"],
|
||||
)
|
||||
|
||||
dist_lto_tools(
|
||||
name = "dist_lto_tools",
|
||||
planner = ":dist_lto_planner",
|
||||
opt = ":dist_lto_opt",
|
||||
prepare = ":dist_lto_prepare",
|
||||
copy = ":dist_lto_copy",
|
||||
visibility = ["PUBLIC"],
|
||||
)
|
||||
|
||||
prelude.python_test(
|
||||
name = "test_dist_lto_opt",
|
||||
srcs = [
|
||||
"tests/test_dist_lto_opt.py",
|
||||
"dist_lto_opt.py",
|
||||
],
|
||||
)
|
||||
0
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/__init__.py
vendored
Normal file
0
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/__init__.py
vendored
Normal file
24
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/dist_lto_copy.py
vendored
Normal file
24
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/dist_lto_copy.py
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
|
||||
def main(argv: List[str]) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--to")
|
||||
parser.add_argument("--from", dest="from_")
|
||||
args = parser.parse_args(argv[1:])
|
||||
shutil.copy(args.from_, args.to)
|
||||
return 0
|
||||
|
||||
|
||||
sys.exit(main(sys.argv))
|
||||
216
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/dist_lto_opt.py
vendored
Normal file
216
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/dist_lto_opt.py
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Python wrapper around `clang` intended for use by the parallel opt phase of
|
||||
a Distributed ThinLTO compilation. This script works around a LLVM bug where
|
||||
LLVM will return a zero exit code in the case where ThinLTO fails with a
|
||||
fatal error.
|
||||
|
||||
Instead of trusting the exit code of the compiler, this script checks the
|
||||
output file and returns 1 if the file has zero size.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
EXIT_SUCCESS, EXIT_FAILURE = 0, 1
|
||||
|
||||
# Filter opt related flags
|
||||
def _filter_flags(clang_flags: List[str]) -> List[str]: # noqa: C901
|
||||
# List of llvm flags to be ignored.
|
||||
# They either don't have an valid mapping or unused during opt.
|
||||
IGNORE_OPT_FLAGS = [
|
||||
"-Wl,-plugin-opt,-function-sections",
|
||||
"-Wl,--lto-whole-program-visibility",
|
||||
"-Wl,--no-lto-whole-program-visibility",
|
||||
]
|
||||
# Conservatively, we only translate llvms flags in our known list
|
||||
KNOWN_LLVM_SHARED_LIBRARY_FLAGS = ["-shared"]
|
||||
|
||||
# Start with default flags for opt.
|
||||
# The default values may change across compiler versions.
|
||||
# Make sure they are always synced with the current values.
|
||||
opt_flags = [
|
||||
# TODO(T139459294):
|
||||
# -O2 is the default optimization flag for the link-time optimizer
|
||||
# this setting matches current llvm implementation:
|
||||
# https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/LTO/Config.h#L57
|
||||
"-O2",
|
||||
# TODO(T139459170): Remove after clang-15. NPM is the default.
|
||||
"-fexperimental-new-pass-manager",
|
||||
"-ffunction-sections",
|
||||
"-fdata-sections",
|
||||
]
|
||||
|
||||
# Clang driver passes through lld flags with "-Wl," prefix. There are 4 type of flags with unique
|
||||
# prefixes:
|
||||
# 1. "--lto-...": these are native lld flags.
|
||||
# 2. "-plugin-opt,..." or "-plugin-opt=...": these are the aliases of the native lld flags (1).
|
||||
# 3. "-mllvm,...": these are llvm flags.
|
||||
# 4. "-plugin-opt,-..." or "-plugin-opt=-...": these are the aliases of llvm flags (3). Note that they differ from (2) and always start with "-".
|
||||
#
|
||||
# For (1) and (2), we need to convert them case by case.
|
||||
# For (3) and (4), we should be able to pass them through into the optimizer directly by prefixing "-mllvm".
|
||||
# TODO(T139448744): Cover all the flags. Check available flags using "ld.lld --help | grep -A 1 '\-\-plugin-opt='"
|
||||
PLUGIN_OPT_PREFIXES = ["-Wl,-plugin-opt,", "-Wl,-plugin-opt="]
|
||||
|
||||
def _find_plugin_opt_prefix(flag: str) -> str:
|
||||
matched_prefix = [
|
||||
prefix for prefix in PLUGIN_OPT_PREFIXES if flag.startswith(prefix)
|
||||
]
|
||||
if matched_prefix:
|
||||
return matched_prefix[0]
|
||||
return ""
|
||||
|
||||
plugin_opt_to_llvm_flag_map = {
|
||||
"sample-profile=": "-fprofile-sample-use=",
|
||||
"O": "-O",
|
||||
}
|
||||
|
||||
def _plugin_opt_to_clang_flag(flag: str) -> str:
|
||||
for k, v in plugin_opt_to_llvm_flag_map.items():
|
||||
if flag.startswith(k):
|
||||
return flag.replace(k, v)
|
||||
return None
|
||||
|
||||
for raw_flag in clang_flags:
|
||||
flag = raw_flag.replace('"', "")
|
||||
if flag in IGNORE_OPT_FLAGS:
|
||||
continue
|
||||
if _find_plugin_opt_prefix(flag):
|
||||
# Convert "-Wl,-plugin-opt,...".
|
||||
flag = flag.replace(_find_plugin_opt_prefix(flag), "", 1)
|
||||
if flag.startswith("-"):
|
||||
# If flag starts with "-", it is an llvm flag. Pass it through directly.
|
||||
opt_flags.extend(["-mllvm", flag])
|
||||
else:
|
||||
flag = _plugin_opt_to_clang_flag(flag)
|
||||
if flag is None:
|
||||
# Bail on any unknown flag.
|
||||
print(f"error: unrecognized flag {raw_flag}")
|
||||
return None
|
||||
opt_flags.append(flag)
|
||||
elif flag.startswith("-Wl,-mllvm,"):
|
||||
# Convert "-Wl,-mllvm,...". It is an llvm flag. Pass it through directly.
|
||||
flag = flag.replace("-Wl,-mllvm,", "", 1)
|
||||
opt_flags.extend(["-mllvm", flag])
|
||||
elif flag in KNOWN_LLVM_SHARED_LIBRARY_FLAGS:
|
||||
# The target is a shared library, `-fPIC` is needed in opt phase to correctly generate PIC ELF.
|
||||
opt_flags.append("-fPIC")
|
||||
|
||||
return opt_flags
|
||||
|
||||
|
||||
# Clean up clang flags by obtaining the cc1 flags and filtering out those unwanted.
|
||||
# clang_opt_flags is mutated after calling this function.
|
||||
def _cleanup_flags(clang_opt_flags: List[str]) -> List[str]:
|
||||
for i, arg in enumerate(clang_opt_flags):
|
||||
if arg.startswith("--cc="):
|
||||
# Find the clang binary path.
|
||||
clang_opt_flags[i] = arg.replace("--cc=", "")
|
||||
break
|
||||
|
||||
# Get the cc1 flag dump with '-###'
|
||||
try:
|
||||
output = (
|
||||
subprocess.check_output(
|
||||
clang_opt_flags + ["-###"], stderr=subprocess.STDOUT
|
||||
)
|
||||
.decode()
|
||||
.splitlines()
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.output.decode())
|
||||
return None
|
||||
|
||||
# Flags that may conflict with the existing bitcode attributes.
|
||||
# The value indicates if the flag is followed with a value.
|
||||
flags_to_delete = {
|
||||
"-mframe-pointer=none": False,
|
||||
"-fmath-errno": False,
|
||||
"-fno-rounding-math": False,
|
||||
"-mconstructor-aliases": False,
|
||||
"-munwind-tables": False,
|
||||
"-target-cpu": True,
|
||||
"-tune-cpu": True,
|
||||
}
|
||||
|
||||
clean_output = []
|
||||
skip_next = False
|
||||
for f in output[-1].split()[1:]:
|
||||
if skip_next:
|
||||
skip_next = False
|
||||
else:
|
||||
f = f.strip('"')
|
||||
if f in flags_to_delete:
|
||||
skip_next = flags_to_delete[f]
|
||||
else:
|
||||
clean_output.append(f)
|
||||
return clean_output
|
||||
|
||||
|
||||
def main(argv: List[str]) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--out", help="The output native object file.")
|
||||
parser.add_argument("--input", help="The input bitcode object file.")
|
||||
parser.add_argument("--index", help="The thinlto index file.")
|
||||
parser.add_argument("--split-dwarf", required=False, help="Split dwarf option.")
|
||||
parser.add_argument(
|
||||
"--args", help="The argsfile containing unfiltered and unprocessed flags."
|
||||
)
|
||||
parser.add_argument("--debug", action="store_true", help="Dump clang -cc1 flags.")
|
||||
parser.add_argument("opt_args", nargs=argparse.REMAINDER)
|
||||
args = parser.parse_args(argv[1:])
|
||||
|
||||
with open(args.args, "r") as argsfile:
|
||||
clang_opt_flags = _filter_flags(argsfile.read().splitlines())
|
||||
if clang_opt_flags is None:
|
||||
return EXIT_FAILURE
|
||||
|
||||
clang_opt_flags.extend(
|
||||
[
|
||||
"-o",
|
||||
args.out,
|
||||
"-x",
|
||||
"ir",
|
||||
"-c",
|
||||
args.input,
|
||||
f"-fthinlto-index={args.index}",
|
||||
]
|
||||
)
|
||||
if args.split_dwarf:
|
||||
clang_opt_flags.append(f"-gsplit-dwarf={args.split_dwarf}")
|
||||
|
||||
# The following args slices manipulating may be confusing. The first 3 element of opt_args are:
|
||||
# 1. a spliter "--", it's not used anywhere;
|
||||
# 2. the fbcc wrapper script path
|
||||
# 3. the "-cc" arg pointing to the compiler we use
|
||||
# EXAMPLE: ['--', 'buck-out/v2/gen/fbcode/8e3db19fe005003a/tools/build/buck/wrappers/__fbcc__/fbcc', '--cc=fbcode/third-party-buck/platform010/build/llvm-fb/12/bin/clang++', '--target=x86_64-redhat-linux-gnu', ...]
|
||||
clang_cc1_flags = _cleanup_flags(args.opt_args[2:] + clang_opt_flags)
|
||||
if clang_cc1_flags is None:
|
||||
return EXIT_FAILURE
|
||||
|
||||
fbcc_cmd = args.opt_args[1:3] + clang_cc1_flags
|
||||
if args.debug:
|
||||
# Print fbcc commandline and exit.
|
||||
print(" ".join(fbcc_cmd))
|
||||
return EXIT_SUCCESS
|
||||
|
||||
subprocess.check_call(fbcc_cmd)
|
||||
if os.stat(args.out).st_size == 0:
|
||||
print("error: opt produced empty file")
|
||||
return EXIT_FAILURE
|
||||
return EXIT_SUCCESS
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
318
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/dist_lto_planner.py
vendored
Normal file
318
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/dist_lto_planner.py
vendored
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A simple wrapper around a distributed thinlto index command to fit into buck2's
|
||||
distributed thinlto build.
|
||||
|
||||
This reads in a couple of things:
|
||||
1. The "meta" file. This is a list of tuples of (object file, index output,
|
||||
plan output). All items are line-separated (so each tuple is three lines).
|
||||
2. The index and link plan output paths
|
||||
3. The commands for the actual index command.
|
||||
|
||||
It will invoke the index command and then copy the index outputs to the
|
||||
requested locations and write a plan for each of those objects. This "plan" is
|
||||
a simple json file with the most important thing being a list of the indices
|
||||
of the imports needed for that file.
|
||||
|
||||
It will then additionally write a link plan, which is just a translation of
|
||||
the thinlto index (which lists the objects actually needed for the final link).
|
||||
|
||||
|
||||
Both opt and link plans use indices to refer to other files because it allows the bzl
|
||||
code to easily map back to other objects held in buck memory.
|
||||
"""
|
||||
|
||||
# pyre-unsafe
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
|
||||
def _get_argsfile(args) -> str:
|
||||
# go through the flags passed to linker and find the index argsfile
|
||||
argsfiles = list(
|
||||
filter(lambda arg: arg.endswith("thinlto.index.argsfile"), args.index_args)
|
||||
)
|
||||
assert (
|
||||
len(argsfiles) == 1
|
||||
), f"expect only 1 argsfile but seeing multiple ones: {argsfiles}"
|
||||
argsfile = argsfiles[0]
|
||||
if argsfile.startswith("@"):
|
||||
argsfile = argsfile[1:]
|
||||
return argsfile
|
||||
|
||||
|
||||
def _extract_lib_search_path(argsfile_path: str) -> List[str]:
|
||||
lib_search_path = []
|
||||
with open(argsfile_path) as argsfile:
|
||||
for line in argsfile:
|
||||
if line.startswith("-L"):
|
||||
lib_search_path.append(line.strip())
|
||||
return lib_search_path
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--meta")
|
||||
parser.add_argument("--index")
|
||||
parser.add_argument("--link-plan")
|
||||
parser.add_argument("--final-link-index")
|
||||
parser.add_argument("index_args", nargs=argparse.REMAINDER)
|
||||
args = parser.parse_args(argv[1:])
|
||||
|
||||
subprocess.check_call(args.index_args[1:])
|
||||
|
||||
bitcode_suffix = ".thinlto.bc"
|
||||
imports_suffix = ".imports"
|
||||
opt_objects_suffix = ".opt.o" # please note the files are not exist yet, this is to generate the index file use in final link
|
||||
|
||||
with open(args.meta) as meta:
|
||||
meta_lines = [line.strip() for line in meta.readlines()]
|
||||
|
||||
def read_imports(path, imports_path):
|
||||
with open(imports_path) as infile:
|
||||
return [line.strip() for line in infile.readlines()]
|
||||
|
||||
def index_path(path):
|
||||
return os.path.join(args.index, path)
|
||||
|
||||
# The meta file comes directly from dist_lto.bzl and consists of a list of
|
||||
# 7-tuples of information. It is easiest for us to write each tuple member
|
||||
# as a separate line in Starlark, so these 7-tuples are encoded in groups
|
||||
# of seven lines.
|
||||
#
|
||||
# The seven pieces of information are:
|
||||
# 1. The path to the source bitcode file. This is used as an index into
|
||||
# a dictionary (`mapping`) that records much of the metadata coming
|
||||
# from these lines.
|
||||
# 2. The path to an output bitcode file. This script is expected to place a
|
||||
# ThinLTO index file at this location (suffixed `.thinlto.bc`).
|
||||
# 3. The path to an output plan. This script is expected to place a link
|
||||
# plan here (a JSON document indicating which other object files this)
|
||||
# object file depends on, among other things.
|
||||
# 4. The link data's index in the Starlark array.
|
||||
# 5. If this object file came from an archive, the name of the archive. Otherwise,
|
||||
# this line is empty.
|
||||
# 6. If this object file came from an archive, the path to an output plan.
|
||||
# This script is expected to produce an archive link plan here (a JSON)
|
||||
# document similar to the object link plan, except containing link
|
||||
# information for every file in the archive from which this object
|
||||
# came. Otherwise, this line is empty.
|
||||
# 7. If this object file came from an archive, the indexes directory of that
|
||||
# archive. This script is expected to place all ThinLTO indexes derived
|
||||
# from object files originating from this archive in that directory.
|
||||
# Otherwise, this line is empty.
|
||||
#
|
||||
# There are two indices that are derived from this meta file: the object
|
||||
# index (mapping["index"]) and the archive index (mapping["archive_index"]).
|
||||
# These indices are indices into Starlark arrays for all objects and archive
|
||||
# linkables, respectively. This script does not inspect them.
|
||||
mapping = {}
|
||||
archives = {}
|
||||
for i in range(0, len(meta_lines), 7):
|
||||
path = meta_lines[i]
|
||||
output = meta_lines[i + 1]
|
||||
plan_output = meta_lines[i + 2]
|
||||
idx = int(meta_lines[i + 3])
|
||||
archive_name = meta_lines[i + 4]
|
||||
archive_plan = meta_lines[i + 5]
|
||||
archive_index_dir = meta_lines[i + 6]
|
||||
|
||||
archive_idx = idx if output == "" else None # archives do not have outputs
|
||||
mapping[path] = {
|
||||
"output": output,
|
||||
"plan_output": plan_output,
|
||||
"index": idx,
|
||||
"archive_index": archive_idx,
|
||||
"archive_name": archive_name,
|
||||
}
|
||||
if archive_idx is not None:
|
||||
archives[idx] = {
|
||||
"name": archive_name,
|
||||
"objects": [],
|
||||
"plan": archive_plan,
|
||||
"index_dir": archive_index_dir,
|
||||
}
|
||||
|
||||
non_lto_objects = {}
|
||||
for path, data in sorted(mapping.items(), key=lambda v: v[0]):
|
||||
output_loc = data["output"]
|
||||
if os.path.exists(output_loc):
|
||||
continue
|
||||
|
||||
if data["archive_index"] is not None:
|
||||
archives[data["archive_index"]]["objects"].append(path)
|
||||
continue
|
||||
|
||||
bc_file = index_path(path) + bitcode_suffix
|
||||
imports_path = index_path(path) + imports_suffix
|
||||
os.makedirs(os.path.dirname(output_loc), exist_ok=True)
|
||||
|
||||
if os.path.exists(imports_path):
|
||||
assert os.path.exists(bc_file), "missing bc file for %s" % path
|
||||
os.rename(bc_file, output_loc)
|
||||
imports = read_imports(path, imports_path)
|
||||
imports_list = []
|
||||
archives_list = []
|
||||
for path in imports:
|
||||
entry = mapping[path]
|
||||
if entry["archive_index"] is not None:
|
||||
archives_list.append(int(entry["archive_index"]))
|
||||
else:
|
||||
imports_list.append(entry["index"])
|
||||
plan = {
|
||||
"imports": imports_list,
|
||||
"archive_imports": archives_list,
|
||||
"index": data["index"],
|
||||
"bitcode_file": bc_file,
|
||||
"path": path,
|
||||
"is_bc": True,
|
||||
}
|
||||
else:
|
||||
non_lto_objects[data["index"]] = 1
|
||||
with open(output_loc, "w"):
|
||||
pass
|
||||
plan = {
|
||||
"is_bc": False,
|
||||
}
|
||||
|
||||
with open(data["plan_output"], "w") as planout:
|
||||
json.dump(plan, planout, sort_keys=True)
|
||||
|
||||
for archive in archives.values():
|
||||
# For archives, we must produce a plan that provides Starlark enough
|
||||
# information about how to launch a dynamic opt for each object file
|
||||
# in the archive.
|
||||
archive_plan = {}
|
||||
|
||||
# This is convenient to store, since it's difficult for Starlark to
|
||||
# calculate it.
|
||||
archive_plan["base_dir"] = os.path.dirname(archive["plan"])
|
||||
object_plans = []
|
||||
for obj in archive["objects"]:
|
||||
imports_path = index_path(obj) + imports_suffix
|
||||
output_path = archive["index_dir"]
|
||||
os.makedirs(output_path, exist_ok=True)
|
||||
if os.path.exists(imports_path):
|
||||
bc_file = index_path(obj) + bitcode_suffix
|
||||
os.rename(bc_file, os.path.join(output_path, os.path.basename(bc_file)))
|
||||
imports = read_imports(path, imports_path)
|
||||
|
||||
imports_list = []
|
||||
archives_list = []
|
||||
for path in imports:
|
||||
entry = mapping[path]
|
||||
if entry["archive_index"] is not None:
|
||||
archives_list.append(int(entry["archive_index"]))
|
||||
else:
|
||||
imports_list.append(entry["index"])
|
||||
object_plans.append(
|
||||
{
|
||||
"is_bc": True,
|
||||
"path": obj,
|
||||
"imports": imports_list,
|
||||
"archive_imports": archives_list,
|
||||
"bitcode_file": os.path.join(
|
||||
output_path, os.path.basename(bc_file)
|
||||
),
|
||||
}
|
||||
)
|
||||
else:
|
||||
object_plans.append(
|
||||
{
|
||||
"is_bc": False,
|
||||
"path": obj,
|
||||
}
|
||||
)
|
||||
|
||||
archive_plan["objects"] = object_plans
|
||||
with open(archive["plan"], "w") as planout:
|
||||
json.dump(archive_plan, planout, sort_keys=True)
|
||||
|
||||
# We read the `index`` and `index.full`` files produced by linker in index stage
|
||||
# and translate them to 2 outputs:
|
||||
# 1. A link plan build final_link args. (This one may be able to be removed if we refactor the workflow)
|
||||
# 2. A files list (*.final_link_index) used for final link stage which includes all the
|
||||
# files needed. it's based on index.full with some modification, like path updates
|
||||
# and redundent(added by toolchain) dependencies removing.
|
||||
index = {}
|
||||
index_files_set = set()
|
||||
# TODO(T130322878): since we call linker wrapper twice (in index and in final_link), to avoid these libs get
|
||||
# added twice we remove them from the index file for now.
|
||||
KNOWN_REMOVABLE_DEPS_SUFFIX = [
|
||||
"glibc/lib/crt1.o",
|
||||
"glibc/lib/crti.o",
|
||||
"crtbegin.o",
|
||||
"crtbeginS.o",
|
||||
".build_info.o",
|
||||
"crtend.o",
|
||||
"crtendS.o",
|
||||
"glibc/lib/crtn.o",
|
||||
]
|
||||
with open(index_path("index")) as indexfile:
|
||||
for line in indexfile:
|
||||
line = line.strip()
|
||||
index_files_set.add(line)
|
||||
path = os.path.relpath(line, start=args.index)
|
||||
index[mapping[path]["index"]] = 1
|
||||
|
||||
with open(args.link_plan, "w") as outfile:
|
||||
json.dump(
|
||||
{
|
||||
"non_lto_objects": non_lto_objects,
|
||||
"index": index,
|
||||
},
|
||||
outfile,
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
||||
|
||||
# Append all search path flags (e.g -Lfbcode/third-party-buck/platform010/build/glibc/lib) from argsfile to final_index
|
||||
# this workaround is to make dist_lto compatible with link_group. see T136415235 for more info
|
||||
argsfile = _get_argsfile(args)
|
||||
lib_search_path = _extract_lib_search_path(argsfile)
|
||||
|
||||
# build index file for final link use
|
||||
with open(index_path("index.full")) as full_index_input, open(
|
||||
args.final_link_index, "w"
|
||||
) as final_link_index_output:
|
||||
final_link_index_output.write("\n".join(lib_search_path) + "\n")
|
||||
for line in full_index_input:
|
||||
line = line.strip()
|
||||
if any(filter(line.endswith, KNOWN_REMOVABLE_DEPS_SUFFIX)):
|
||||
continue
|
||||
path = os.path.relpath(line, start=args.index)
|
||||
if line in index_files_set:
|
||||
if mapping[path]["output"]:
|
||||
# handle files that were not extracted from archives
|
||||
output = mapping[path]["output"].replace(
|
||||
bitcode_suffix, opt_objects_suffix
|
||||
)
|
||||
final_link_index_output.write(output + "\n")
|
||||
elif os.path.exists(index_path(path) + imports_suffix):
|
||||
# handle files built from source that were extracted from archives
|
||||
opt_objects_path = path.replace(
|
||||
"/objects/", "/opt_objects/objects/"
|
||||
)
|
||||
final_link_index_output.write(opt_objects_path + "\n")
|
||||
else:
|
||||
# handle pre-built archives
|
||||
final_link_index_output.write(line + "\n")
|
||||
else:
|
||||
# handle input files that did not come from linker input, e.g. linkerscirpts
|
||||
final_link_index_output.write(line + "\n")
|
||||
|
||||
|
||||
sys.exit(main(sys.argv))
|
||||
162
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/dist_lto_prepare.py
vendored
Normal file
162
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/dist_lto_prepare.py
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Prepares for an object-only ThinLTO link by extracting a given archive and
|
||||
producing a manifest of the objects contained within.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
class ArchiveKind(enum.IntEnum):
|
||||
UNKNOWN = 0
|
||||
ARCHIVE = 1
|
||||
THIN_ARCHIVE = 2
|
||||
|
||||
|
||||
def _gen_path(parent_path: str, filename: str) -> str:
|
||||
# concat file path and check the file exist before return
|
||||
obj_path = os.path.join(parent_path, filename)
|
||||
assert os.path.exists(obj_path)
|
||||
return obj_path
|
||||
|
||||
|
||||
def _gen_filename(filename: str, num_of_instance: int) -> str:
|
||||
# generate the filename based on the instance,
|
||||
# for 1st instance, it's file.o
|
||||
# for 2nd instance, it's file_1.o
|
||||
if num_of_instance > 1:
|
||||
basename, extension = os.path.splitext(filename)
|
||||
return f"{basename}_{num_of_instance-1}{extension}"
|
||||
else:
|
||||
return filename
|
||||
|
||||
|
||||
def identify_file(path: str) -> Tuple[ArchiveKind, str]:
|
||||
path = os.path.realpath(path)
|
||||
output = subprocess.check_output(["file", "-b", path]).decode()
|
||||
if "ar archive" in output:
|
||||
return (ArchiveKind.ARCHIVE, output)
|
||||
elif "thin archive" in output:
|
||||
return (ArchiveKind.THIN_ARCHIVE, output)
|
||||
else:
|
||||
with open(path, "rb") as infile:
|
||||
head = infile.read(7)
|
||||
|
||||
if head == "!<thin>".encode():
|
||||
return (ArchiveKind.THIN_ARCHIVE, output)
|
||||
|
||||
return (ArchiveKind.UNKNOWN, output)
|
||||
|
||||
|
||||
def main(argv: List[str]) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--manifest-out")
|
||||
parser.add_argument("--objects-out")
|
||||
parser.add_argument("--ar")
|
||||
parser.add_argument("--name")
|
||||
parser.add_argument("--archive")
|
||||
args = parser.parse_args(argv[1:])
|
||||
|
||||
objects_path = args.objects_out
|
||||
os.makedirs(objects_path, exist_ok=True)
|
||||
|
||||
known_objects = []
|
||||
file_type, debug_output = identify_file(args.archive)
|
||||
if file_type == ArchiveKind.ARCHIVE:
|
||||
# Unfortunately, we use llvm-ar and, while binutils ar has had --output for
|
||||
# a long time, llvm-ar does not support --output and the change in llvm-ar
|
||||
# looks like it has stalled for years (https://reviews.llvm.org/D69418)
|
||||
# So, we need to invoke ar in the directory that we want it to extract into, and so
|
||||
# need to adjust some paths.
|
||||
ar_path = os.path.relpath(args.ar, start=objects_path)
|
||||
archive_path = os.path.relpath(args.archive, start=objects_path)
|
||||
output = subprocess.check_output(
|
||||
[ar_path, "t", archive_path], cwd=objects_path
|
||||
).decode()
|
||||
member_list = [member for member in output.split("\n") if member]
|
||||
|
||||
if len(set(member_list)) != len(member_list):
|
||||
# duplication detected
|
||||
counter = {}
|
||||
|
||||
for member in member_list:
|
||||
current = counter.get(member, 0) + 1
|
||||
counter[member] = current
|
||||
if current == 1:
|
||||
# just extract the file
|
||||
output = subprocess.check_output(
|
||||
[ar_path, "xN", str(current), archive_path, member],
|
||||
cwd=objects_path,
|
||||
).decode()
|
||||
assert not output
|
||||
known_objects.append(_gen_path(objects_path, member))
|
||||
else:
|
||||
# llvm doesn't allow --output so we need this clumsiness
|
||||
tmp_filename = "tmp"
|
||||
current_file = _gen_filename(member, current)
|
||||
# rename current 'member' file to tmp
|
||||
output = subprocess.check_output(
|
||||
["mv", member, tmp_filename], cwd=objects_path
|
||||
).decode()
|
||||
assert not output
|
||||
# extract the file from archive
|
||||
output = subprocess.check_output(
|
||||
[ar_path, "xN", str(current), archive_path, member],
|
||||
cwd=objects_path,
|
||||
).decode()
|
||||
assert not output
|
||||
# rename the newly extracted file
|
||||
output = subprocess.check_output(
|
||||
["mv", member, current_file], cwd=objects_path
|
||||
).decode()
|
||||
assert not output
|
||||
# rename the tmp file back to 'member'
|
||||
output = subprocess.check_output(
|
||||
["mv", tmp_filename, member], cwd=objects_path
|
||||
).decode()
|
||||
assert not output
|
||||
known_objects.append(_gen_path(objects_path, current_file))
|
||||
else:
|
||||
# no duplicated filename
|
||||
output = subprocess.check_output(
|
||||
[ar_path, "xv", archive_path], cwd=objects_path
|
||||
).decode()
|
||||
for line in output.splitlines():
|
||||
assert line.startswith("x - ")
|
||||
obj = line[4:]
|
||||
known_objects.append(_gen_path(objects_path, obj))
|
||||
|
||||
elif file_type == ArchiveKind.THIN_ARCHIVE:
|
||||
output = subprocess.check_output([args.ar, "t", args.archive]).decode()
|
||||
for line in output.splitlines():
|
||||
assert os.path.exists(line)
|
||||
known_objects.append(line)
|
||||
elif file_type == ArchiveKind.UNKNOWN:
|
||||
raise AssertionError(
|
||||
f"unknown archive kind for file {args.archive}: {debug_output}"
|
||||
)
|
||||
|
||||
manifest = {
|
||||
"debug": debug_output,
|
||||
"objects": known_objects,
|
||||
}
|
||||
with open(os.path.join(args.manifest_out), "w") as outfile:
|
||||
json.dump(manifest, outfile, indent=2, sort_keys=True)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
sys.exit(main(sys.argv))
|
||||
276
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/tests/test_dist_lto_opt.py
vendored
Normal file
276
vendor/cxx/tools/buck/prelude/cxx/dist_lto/tools/tests/test_dist_lto_opt.py
vendored
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#!/usr/bin/env fbpython
|
||||
# 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.
|
||||
|
||||
import unittest
|
||||
|
||||
from cxx.dist_lto.tools.dist_lto_opt import _filter_flags
|
||||
|
||||
|
||||
class TestDistLtoOpt(unittest.TestCase):
|
||||
def test_filter_flags(self):
|
||||
inputs = [
|
||||
"-not-opt-flag",
|
||||
"-Wl,-plugin-opt,-llvm_flag1",
|
||||
"-Wl,-plugin-opt=-llvm_flag1_different_format",
|
||||
"-Wl,-plugin-opt,sample-profile=/fbcode/abc/autofdo.profile",
|
||||
"-Wl,--lto-whole-program-visibility",
|
||||
"-Wl,-mllvm,-llvm-flag2",
|
||||
"-Wl,-plugin-opt,O3",
|
||||
"-Wl,-plugin-opt,-function-sections",
|
||||
"-Wl,-mllvm,-hot-callsite-threshold=12000",
|
||||
"-shared",
|
||||
]
|
||||
flags = _filter_flags(inputs)
|
||||
self.assertListEqual(
|
||||
flags,
|
||||
[
|
||||
"-O2",
|
||||
"-fexperimental-new-pass-manager",
|
||||
"-ffunction-sections",
|
||||
"-fdata-sections",
|
||||
"-mllvm",
|
||||
"-llvm_flag1",
|
||||
"-mllvm",
|
||||
"-llvm_flag1_different_format",
|
||||
"-fprofile-sample-use=/fbcode/abc/autofdo.profile",
|
||||
"-mllvm",
|
||||
"-llvm-flag2",
|
||||
"-O3",
|
||||
"-mllvm",
|
||||
"-hot-callsite-threshold=12000",
|
||||
"-fPIC",
|
||||
],
|
||||
)
|
||||
|
||||
def test_filter_flags_hhvm_case_rev_0f8618f31(self):
|
||||
inputs = [
|
||||
"--target=x86_64-redhat-linux-gnu",
|
||||
"-nostdinc",
|
||||
"-resource-dir",
|
||||
"fbcode/third-party-buck/platform010/build/llvm-fb/12/lib/clang/stable",
|
||||
"-idirafter",
|
||||
"fbcode/third-party-buck/platform010/build/llvm-fb/12/lib/clang/stable/include",
|
||||
"-idirafter",
|
||||
"fbcode/third-party-buck/platform010/build/glibc/include",
|
||||
"-idirafter",
|
||||
"fbcode/third-party-buck/platform010/build/kernel-headers/include",
|
||||
"-Bfbcode/third-party-buck/platform010/build/binutils/x86_64-facebook-linux/bin",
|
||||
"--cflag=--target=x86_64-redhat-linux-gnu",
|
||||
"--ar=fbcode/third-party-buck/platform010/build/llvm-fb/12/bin/llvm-ar",
|
||||
"-Bfbcode/third-party-buck/platform010/build/glibc/lib",
|
||||
"-Bfbcode/third-party-buck/platform010/tools/gcc/lib/gcc/x86_64-redhat-linux-gnu/trunk",
|
||||
"-Lfbcode/third-party-buck/platform010/build/libgcc/lib/gcc/x86_64-facebook-linux/trunk",
|
||||
"-Wl,-nostdlib",
|
||||
"-Wl,--dynamic-linker,/usr/local/fbcode/platform010/lib/ld.so",
|
||||
"-Wl,--disable-new-dtags",
|
||||
"-Bfbcode/third-party-buck/platform010/build/binutils/x86_64-facebook-linux/bin",
|
||||
"-Bbuck-out/v2/gen/fbcode/8e3db19fe005003a/third-party-buck/platform010/build/llvm-fb/12/__lld_path__/lld_path/bin",
|
||||
"-Wl,--no-mmap-output-file",
|
||||
"-nodefaultlibs",
|
||||
"--target=x86_64-redhat-linux-gnu",
|
||||
"-Lfbcode/third-party-buck/platform010/build/glibc/lib",
|
||||
"-Lfbcode/third-party-buck/platform010/build/libgcc/lib",
|
||||
"-Wl,-z,notext",
|
||||
"-Wl,-z,relro",
|
||||
"-Wl,--gc-sections",
|
||||
"-fuse-ld=lld",
|
||||
"-Wl,--discard-section=.nv_fatbin",
|
||||
"-Wl,--discard-section=.nvFatBinSegment",
|
||||
"-Wl,--discard-section=.rela.debug_info",
|
||||
"-Wl,--discard-section=.rela.debug_ranges",
|
||||
"-Wl,--discard-section=.rela.debug_loc",
|
||||
"-Wl,--discard-section=.rela.debug_line",
|
||||
"-Wl,--discard-section=.rela.debug_aranges",
|
||||
"-Wl,--discard-section=.rela.debug_types",
|
||||
"-Wl,-O1",
|
||||
"-Wl,--build-id=sha1",
|
||||
"-Wl,-mllvm,-hot-callsite-threshold=12000",
|
||||
"-Wl,--lto-whole-program-visibility",
|
||||
"-fwhole-program-vtables",
|
||||
"-fexperimental-new-pass-manager",
|
||||
"-Wl,--no-discard-section=.nv_fatbin",
|
||||
"-Wl,--no-discard-section=.nvFatBinSegment",
|
||||
"fbcode/tools/build/move_gpu_sections_implicit_linker_script.txt",
|
||||
"-fuse-ld=lld",
|
||||
"--build-info=full",
|
||||
"--build-info-build-mode=opt-hhvm-lto",
|
||||
"--build-info-build-tool=buck2",
|
||||
"--build-info-compiler=clang",
|
||||
"--build-info-fdo-profile=",
|
||||
"--build-info-platform=platform010",
|
||||
"--build-info-rule=fbcode:scripts/fnz/minimal:minimal",
|
||||
"--build-info-rule-type=cpp_binary",
|
||||
"-flto=thin",
|
||||
"-Wl,-plugin-opt,-function-sections",
|
||||
"-Wl,-plugin-opt,-profile-guided-section-prefix=false",
|
||||
"-Wl,-plugin-opt,-generate-type-units",
|
||||
"-Wl,-plugin-opt,-enable-lto-ir-verification=false",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=mallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=rallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=xallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=sallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=dallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=sdallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=nallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=mallctl",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=mallctlnametomib",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=mallctlbymib",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=malloc_stats_print",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=malloc_usable_size",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=malloc_message",
|
||||
]
|
||||
flags = _filter_flags(inputs)
|
||||
self.assertIsNotNone(flags)
|
||||
self.assertListEqual(
|
||||
flags,
|
||||
[
|
||||
"-O2",
|
||||
"-fexperimental-new-pass-manager",
|
||||
"-ffunction-sections",
|
||||
"-fdata-sections",
|
||||
"-mllvm",
|
||||
"-hot-callsite-threshold=12000",
|
||||
"-mllvm",
|
||||
"-profile-guided-section-prefix=false",
|
||||
"-mllvm",
|
||||
"-generate-type-units",
|
||||
"-mllvm",
|
||||
"-enable-lto-ir-verification=false",
|
||||
],
|
||||
)
|
||||
|
||||
def test_filter_flags_unicorn_case_rev_0f8618f31(self):
|
||||
inputs = [
|
||||
"--ld=fbcode/third-party-buck/platform010/build/llvm-fb/12/bin/clang++",
|
||||
"--cc=buck-out/v2/gen/fbcode/8e3db19fe005003a/tools/build/buck/wrappers/__fbcc__/fbcc --cc=fbcode/third-party-buck/platform010/build/llvm-fb/12/bin/clang --target=x86_64-redhat-linux-gnu -nostdinc -resource-dir fbcode/third-party-buck/platform010/build/llvm-fb/12/lib/clang/stable -idirafter fbcode/third-party-buck/platform010/build/llvm-fb/12/lib/clang/stable/include -idirafter fbcode/third-party-buck/platform010/build/glibc/include -idirafter fbcode/third-party-buck/platform010/build/kernel-headers/include -Bfbcode/third-party-buck/platform010/build/binutils/x86_64-facebook-linux/bin",
|
||||
"--cflag=--target=x86_64-redhat-linux-gnu",
|
||||
"--ar=fbcode/third-party-buck/platform010/build/llvm-fb/12/bin/llvm-ar",
|
||||
"-Bfbcode/third-party-buck/platform010/build/glibc/lib",
|
||||
"-Bfbcode/third-party-buck/platform010/tools/gcc/lib/gcc/x86_64-redhat-linux-gnu/trunk",
|
||||
"-Lfbcode/third-party-buck/platform010/build/libgcc/lib/gcc/x86_64-facebook-linux/trunk",
|
||||
"-Wl,-nostdlib",
|
||||
"-Wl,--dynamic-linker,/usr/local/fbcode/platform010/lib/ld.so",
|
||||
"-Wl,--disable-new-dtags",
|
||||
"-Bfbcode/third-party-buck/platform010/build/binutils/x86_64-facebook-linux/bin",
|
||||
"-Bbuck-out/v2/gen/fbcode/8e3db19fe005003a/third-party-buck/platform010/build/llvm-fb/12/__lld_path__/lld_path/bin",
|
||||
"-Wl,--no-mmap-output-file",
|
||||
"-nodefaultlibs",
|
||||
"--target=x86_64-redhat-linux-gnu",
|
||||
"-Lfbcode/third-party-buck/platform010/build/glibc/lib",
|
||||
"-Lfbcode/third-party-buck/platform010/build/libgcc/lib",
|
||||
"-Wl,-z,notext",
|
||||
"-Wl,-z,relro",
|
||||
"-Wl,--gc-sections",
|
||||
"-fuse-ld=lld",
|
||||
"-Wl,--discard-section=.nv_fatbin",
|
||||
"-Wl,--discard-section=.nvFatBinSegment",
|
||||
"-Wl,--discard-section=.rela.debug_info",
|
||||
"-Wl,--discard-section=.rela.debug_ranges",
|
||||
"-Wl,--discard-section=.rela.debug_loc",
|
||||
"-Wl,--discard-section=.rela.debug_line",
|
||||
"-Wl,--discard-section=.rela.debug_aranges",
|
||||
"-Wl,--discard-section=.rela.debug_types",
|
||||
"-Wl,-O1",
|
||||
"-Wl,--build-id=sha1",
|
||||
"-fexperimental-new-pass-manager",
|
||||
"-Xlinker",
|
||||
"-znow",
|
||||
"-Xlinker",
|
||||
"--emit-relocs",
|
||||
"--build-info=full",
|
||||
"--build-info-build-mode=opt-clang-thinlto",
|
||||
"--build-info-build-tool=buck2",
|
||||
"--build-info-compiler=clang",
|
||||
"--build-info-fdo-profile=fbcode//fdo/autofdo/default_profile:autofdo",
|
||||
"--build-info-platform=platform010",
|
||||
"--build-info-rule=fbcode:unicorn:index_server",
|
||||
"--build-info-rule-type=cpp_binary",
|
||||
"-flto=thin",
|
||||
"-Wl,-plugin-opt,sample-profile=buck-out/v2/gen/fbcode/40fc99293b37c503/fdo/autofdo/default_profile/__autofdo__/out/profile",
|
||||
"-Wl,-plugin-opt,-function-sections",
|
||||
"-Wl,-plugin-opt,-profile-guided-section-prefix=false",
|
||||
"-Wl,-plugin-opt,-generate-type-units",
|
||||
"-Wl,-plugin-opt,-enable-lto-ir-verification=false",
|
||||
"-Xlinker",
|
||||
"--push-state",
|
||||
"-Xlinker",
|
||||
"--no-as-needed",
|
||||
"-Xlinker",
|
||||
"--pop-state",
|
||||
"-Wl,--undefined,Global",
|
||||
"-Wl,--undefined,Local",
|
||||
"-Wl,--undefined,MockConnection",
|
||||
"-Wl,--undefined,Global",
|
||||
"-Wl,--undefined,Local",
|
||||
"-Wl,--undefined,MockConnection",
|
||||
"-Xlinker",
|
||||
"--start-group",
|
||||
"fbcode/third-party-buck/platform010/build/IntelComposerXE/mkl/lib/intel64/libmkl_intel_lp64.a",
|
||||
"fbcode/third-party-buck/platform010/build/IntelComposerXE/mkl/lib/intel64/libmkl_core.a",
|
||||
"fbcode/third-party-buck/platform010/build/IntelComposerXE/mkl/lib/intel64/libmkl_intel_thread.a",
|
||||
"-Xlinker",
|
||||
"--end-group",
|
||||
"-lpthread",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=mallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=rallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=xallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=sallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=dallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=sdallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=nallocx",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=mallctl",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=mallctlnametomib",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=mallctlbymib",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=malloc_stats_print",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=malloc_usable_size",
|
||||
"-Xlinker",
|
||||
"--export-dynamic-symbol=malloc_message",
|
||||
]
|
||||
flags = _filter_flags(inputs)
|
||||
self.assertIsNotNone(flags)
|
||||
self.assertListEqual(
|
||||
flags,
|
||||
[
|
||||
"-O2",
|
||||
"-fexperimental-new-pass-manager",
|
||||
"-ffunction-sections",
|
||||
"-fdata-sections",
|
||||
"-fprofile-sample-use=buck-out/v2/gen/fbcode/40fc99293b37c503/fdo/autofdo/default_profile/__autofdo__/out/profile",
|
||||
"-mllvm",
|
||||
"-profile-guided-section-prefix=false",
|
||||
"-mllvm",
|
||||
"-generate-type-units",
|
||||
"-mllvm",
|
||||
"-enable-lto-ir-verification=false",
|
||||
],
|
||||
)
|
||||
78
vendor/cxx/tools/buck/prelude/cxx/dwp.bzl
vendored
Normal file
78
vendor/cxx/tools/buck/prelude/cxx/dwp.bzl
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# 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//:local_only.bzl", "link_cxx_binary_locally")
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
|
||||
def dwp_available(ctx: "context"):
|
||||
dwp = get_cxx_toolchain_info(ctx).binary_utilities_info.dwp
|
||||
return dwp != None
|
||||
|
||||
def run_dwp_action(
|
||||
ctx: "context",
|
||||
obj: "artifact",
|
||||
identifier: [str.type, None],
|
||||
category_suffix: [str.type, None],
|
||||
referenced_objects: ["_arglike", ["artifact"]],
|
||||
dwp_output: "artifact",
|
||||
local_only: bool.type,
|
||||
allow_huge_dwp: bool.type = False):
|
||||
args = cmd_args()
|
||||
dwp = get_cxx_toolchain_info(ctx).binary_utilities_info.dwp
|
||||
args.add("/bin/sh", "-c", '"$1" {}-o "$2" -e "$3" && touch "$2"'.format("--continue-on-cu-index-overflow " if allow_huge_dwp else ""), "")
|
||||
args.add(dwp, dwp_output.as_output(), obj)
|
||||
|
||||
# All object/dwo files referenced in the library/executable are implicitly
|
||||
# processed by dwp.
|
||||
args.hidden(referenced_objects)
|
||||
|
||||
category = "dwp"
|
||||
if category_suffix != None:
|
||||
category += "_" + category_suffix
|
||||
|
||||
ctx.actions.run(
|
||||
args,
|
||||
category = category,
|
||||
identifier = identifier,
|
||||
local_only = local_only,
|
||||
)
|
||||
|
||||
def dwp(
|
||||
ctx: "context",
|
||||
# Executable/library to extra dwo paths from.
|
||||
obj: "artifact",
|
||||
# An identifier that will uniquely name this link action in the context of a category. Useful for
|
||||
# differentiating multiple link actions in the same rule.
|
||||
identifier: [str.type, None],
|
||||
# A category suffix that will be added to the category of the link action that is generated.
|
||||
category_suffix: [str.type, None],
|
||||
# All `.o`/`.dwo` paths referenced in `obj`.
|
||||
# TODO(T110378122): Ideally, referenced objects are a list of artifacts,
|
||||
# but currently we don't track them properly. So, we just pass in the full
|
||||
# link line and extract all inputs from that, which is a bit of an
|
||||
# overspecification.
|
||||
referenced_objects: ["_arglike", ["artifact"]],
|
||||
# whether to enable dangerous option to allow huge dwp file. DWARF specs says dwp file
|
||||
# should be less than 4GB to ensure a valid .debug_cu_index, llvm-dwp errors out on huge
|
||||
# dwp file. allow_huge_dwp will toggle option to turn error to warning.
|
||||
allow_huge_dwp: bool.type = False) -> "artifact":
|
||||
# gdb/lldb expect to find a file named $file.dwp next to $file.
|
||||
output = ctx.actions.declare_output(obj.short_path + ".dwp")
|
||||
run_dwp_action(
|
||||
ctx,
|
||||
obj,
|
||||
identifier,
|
||||
category_suffix,
|
||||
referenced_objects,
|
||||
output,
|
||||
# dwp produces ELF files on the same size scale as the corresponding @obj.
|
||||
# The files are a concatentation of input DWARF debug info.
|
||||
# Caching dwp has the same issues as caching binaries, so use the same local_only policy.
|
||||
local_only = link_cxx_binary_locally(ctx),
|
||||
allow_huge_dwp = allow_huge_dwp,
|
||||
)
|
||||
return output
|
||||
237
vendor/cxx/tools/buck/prelude/cxx/groups.bzl
vendored
Normal file
237
vendor/cxx/tools/buck/prelude/cxx/groups.bzl
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
# 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//linking:link_info.bzl",
|
||||
"Linkage",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:build_target_pattern.bzl",
|
||||
"BuildTargetPattern",
|
||||
"label_matches_build_target_pattern",
|
||||
"parse_build_target_pattern",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:graph_utils.bzl",
|
||||
"breadth_first_traversal_by",
|
||||
)
|
||||
|
||||
# Types of group traversal
|
||||
Traversal = enum(
|
||||
# Includes the target and all of it's transitive dependencies in the group.
|
||||
"tree",
|
||||
# Includes only the target in the group.
|
||||
"node",
|
||||
)
|
||||
|
||||
# Optional type of filtering
|
||||
FilterType = enum(
|
||||
# Filters for targets with labels matching the regex pattern defined after `label:`.
|
||||
"label",
|
||||
# Filters for targets for the build target pattern defined after "pattern:".
|
||||
"pattern",
|
||||
)
|
||||
|
||||
# Label for special group mapping which makes every target associated with it to be included in all groups
|
||||
MATCH_ALL_LABEL = "MATCH_ALL"
|
||||
|
||||
# Label for special group mapping which makes every target associated with it to be linked directly
|
||||
# against the final binary
|
||||
NO_MATCH_LABEL = "NO_MATCH"
|
||||
|
||||
GroupRoot = record(
|
||||
label = "label",
|
||||
# Data provided by the group (e.g. linkable graph and shared libs).
|
||||
node = "_a",
|
||||
)
|
||||
|
||||
# Representation of a parsed group mapping
|
||||
GroupMapping = record(
|
||||
# The root to apply this mapping to.
|
||||
root = field([GroupRoot.type, None], None),
|
||||
# The type of traversal to use.
|
||||
traversal = field(Traversal.type, Traversal("tree")),
|
||||
# Optional filter type to apply to the traversal. If present,
|
||||
# either `label_regex` or `build_target_pattern` is required.
|
||||
filter_type = field([FilterType.type, None], None),
|
||||
# Optional label regex filter to apply to the traversal. If present,
|
||||
# the `filter_type` is required.
|
||||
label_regex = field(["regex", None], None),
|
||||
# Optional build target pattern to apply to the traversal. If present,
|
||||
# the `filter_type` is required.
|
||||
build_target_pattern = field([BuildTargetPattern.type, None], None),
|
||||
# Preferred linkage for this target when added to a link group.
|
||||
preferred_linkage = field([Linkage.type, None], None),
|
||||
)
|
||||
|
||||
# Representation of a parsed group
|
||||
Group = record(
|
||||
# The name for this group.
|
||||
name = str.type,
|
||||
# The mappings that are part of this group.
|
||||
mappings = [GroupMapping.type],
|
||||
)
|
||||
|
||||
GroupsMappings = record(
|
||||
groups = [Group.type],
|
||||
mappings = {"label": str.type},
|
||||
)
|
||||
|
||||
def parse_groups_definitions(map: list.type, dep_to_node: "function" = lambda d: d) -> [Group.type]:
|
||||
groups = []
|
||||
for name, mappings in map:
|
||||
parsed_mappings = []
|
||||
for entry in mappings:
|
||||
traversal = _parse_traversal_from_mapping(entry[1])
|
||||
filter_type, label_regex, build_target_pattern = _parse_filter_from_mapping(entry[2])
|
||||
root = None
|
||||
if entry[0] != None:
|
||||
root = GroupRoot(
|
||||
label = entry[0].label,
|
||||
node = dep_to_node(entry[0]),
|
||||
)
|
||||
mapping = GroupMapping(
|
||||
root = root,
|
||||
traversal = traversal,
|
||||
filter_type = filter_type,
|
||||
label_regex = label_regex,
|
||||
build_target_pattern = build_target_pattern,
|
||||
preferred_linkage = Linkage(entry[3]) if len(entry) > 3 and entry[3] else None,
|
||||
)
|
||||
parsed_mappings.append(mapping)
|
||||
|
||||
group = Group(name = name, mappings = parsed_mappings)
|
||||
groups.append(group)
|
||||
|
||||
return groups
|
||||
|
||||
def _parse_traversal_from_mapping(entry: str.type) -> Traversal.type:
|
||||
if entry == "tree":
|
||||
return Traversal("tree")
|
||||
elif entry == "node":
|
||||
return Traversal("node")
|
||||
else:
|
||||
fail("Unrecognized group traversal type: " + entry)
|
||||
|
||||
def _parse_filter_from_mapping(entry: [str.type, None]) -> [(FilterType.type, "regex", None), (FilterType.type, None, BuildTargetPattern.type), (None, None, None)]:
|
||||
filter_type = None
|
||||
label_regex = None
|
||||
build_target_pattern = None
|
||||
if entry:
|
||||
# We need the anchors "^"" and "$" because experimental_regex match anywhere in the text,
|
||||
# while we want full text match for group label text.
|
||||
if entry.startswith("label"):
|
||||
filter_type = FilterType("label")
|
||||
label_regex = experimental_regex("^{}$".format(entry[6:]))
|
||||
elif entry.startswith("tag"):
|
||||
filter_type = FilterType("label")
|
||||
label_regex = experimental_regex("^{}$".format(entry[4:]))
|
||||
elif entry.startswith("pattern"):
|
||||
filter_type = FilterType("pattern")
|
||||
build_target_pattern = parse_build_target_pattern(entry[8:])
|
||||
else:
|
||||
fail("Invalid group mapping filter: {}\nFilter must begin with `label:` or `pattern:`.".format(entry))
|
||||
return filter_type, label_regex, build_target_pattern
|
||||
|
||||
def compute_mappings(groups: [Group.type], graph_map: {"label": "_b"}) -> {"label": str.type}:
|
||||
"""
|
||||
Returns the group mappings {target label -> group name} based on the provided groups and graph.
|
||||
"""
|
||||
if not groups:
|
||||
return {}
|
||||
|
||||
target_to_group_map = {}
|
||||
node_traversed_targets = {}
|
||||
|
||||
for group in groups:
|
||||
for mapping in group.mappings:
|
||||
targets_in_mapping = _find_targets_in_mapping(graph_map, mapping)
|
||||
if not targets_in_mapping and group.name != NO_MATCH_LABEL:
|
||||
warning("Could not find any targets for mapping: `{}` in group: `{}`".format(mapping, group.name))
|
||||
continue
|
||||
for target in targets_in_mapping:
|
||||
_update_target_to_group_mapping(graph_map, target_to_group_map, node_traversed_targets, group.name, mapping, target)
|
||||
|
||||
return target_to_group_map
|
||||
|
||||
def _find_targets_in_mapping(
|
||||
graph_map: {"label": "_b"},
|
||||
mapping: GroupMapping.type) -> ["label"]:
|
||||
# If we have no filtering, we don't need to do any traversal to find targets to include.
|
||||
if mapping.filter_type == None:
|
||||
if mapping.root == None:
|
||||
fail("no filter or explicit root given: {}", mapping)
|
||||
return [mapping.root.label]
|
||||
|
||||
# Else find all dependencies that match the filter.
|
||||
matching_targets = {}
|
||||
|
||||
def matches_target(
|
||||
target, # "label"
|
||||
labels) -> bool.type: # labels: [str.type]
|
||||
if mapping.filter_type == FilterType("label"):
|
||||
# Use a for loop to avoid creating a temporary array in a BFS.
|
||||
for label in labels:
|
||||
if mapping.label_regex.match(label):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return label_matches_build_target_pattern(target, mapping.build_target_pattern)
|
||||
|
||||
def find_matching_targets(node): # "label" -> ["label"]:
|
||||
graph_node = graph_map[node]
|
||||
if matches_target(node, graph_node.labels):
|
||||
matching_targets[node] = None
|
||||
if mapping.traversal == Traversal("tree"):
|
||||
# We can stop traversing the tree at this point because we've added the
|
||||
# build target to the list of all targets that will be traversed by the
|
||||
# algorithm that applies the groups.
|
||||
return []
|
||||
return graph_node.deps + graph_node.exported_deps
|
||||
|
||||
if mapping.root == None:
|
||||
for node in graph_map:
|
||||
find_matching_targets(node)
|
||||
else:
|
||||
breadth_first_traversal_by(graph_map, [mapping.root.label], find_matching_targets)
|
||||
|
||||
return matching_targets.keys()
|
||||
|
||||
# Types removed to avoid unnecessary type checking which degrades performance.
|
||||
def _update_target_to_group_mapping(
|
||||
graph_map, # {"label": "_b"}
|
||||
target_to_group_map, #: {"label": str.type}
|
||||
node_traversed_targets, #: {"label": None}
|
||||
group, # str.type,
|
||||
mapping, # GroupMapping.type,
|
||||
target): # "label"
|
||||
def assign_target_to_group(
|
||||
target: "label",
|
||||
node_traversal: bool.type) -> bool.type:
|
||||
# If the target hasn't already been assigned to a group, assign it to the
|
||||
# first group claiming the target. Return whether the target was already assigned.
|
||||
if target not in target_to_group_map:
|
||||
target_to_group_map[target] = group
|
||||
if node_traversal:
|
||||
node_traversed_targets[target] = None
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def transitively_add_targets_to_group_mapping(node: "label") -> ["label"]:
|
||||
previously_processed = assign_target_to_group(target = node, node_traversal = False)
|
||||
|
||||
# If the node has been previously processed, and it was via tree (not node), all child nodes have been assigned
|
||||
if previously_processed and node not in node_traversed_targets:
|
||||
return []
|
||||
graph_node = graph_map[node]
|
||||
return graph_node.deps + graph_node.exported_deps
|
||||
|
||||
if mapping.traversal == Traversal("node"):
|
||||
assign_target_to_group(target = target, node_traversal = True)
|
||||
else: # tree
|
||||
breadth_first_traversal_by(graph_map, [target], transitively_add_targets_to_group_mapping)
|
||||
319
vendor/cxx/tools/buck/prelude/cxx/headers.bzl
vendored
Normal file
319
vendor/cxx/tools/buck/prelude/cxx/headers.bzl
vendored
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
# 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//utils:utils.bzl", "expect", "from_named_set", "value_or")
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
load(":platform.bzl", "cxx_by_platform")
|
||||
|
||||
# Defines the varying bits of implementation affecting on how the end user
|
||||
# should include the headers.
|
||||
# Given there are 2 headers which are defined:
|
||||
# a) one header in a list, as ["foo/bar/foobar.h"]
|
||||
# b) one header in a dict (aka named header), as {"wfh/baz.h": "path/header.h"}
|
||||
#
|
||||
# `apple`:
|
||||
# 1) header from the list should be included as NAMESPACE/PATH_BASENAME:
|
||||
# #include "namespace/foobar.h"
|
||||
# 2) header from the dict should be included as DICT_KEY (aka header name):
|
||||
# #include "wfh/baz.h"
|
||||
# 3) it should be possible to include list header from the same target via basename:
|
||||
# #include "foobar.h"
|
||||
#
|
||||
# `regular`:
|
||||
# 1) header from the list should be included as NAMESPACE/PATH:
|
||||
# #include "namespace/foo/bar/foobar.h"
|
||||
# 2) header from the dict should be included as NAMESPACE/DICT_KEY:
|
||||
# #include "namespace/wfh/baz.h"
|
||||
CxxHeadersNaming = enum("apple", "regular")
|
||||
|
||||
# Modes supporting implementing the `headers` parameter of C++ rules using raw
|
||||
# headers instead of e.g. symlink trees.
|
||||
HeadersAsRawHeadersMode = enum(
|
||||
# Require that all headers be implemented as raw headers, failing if this
|
||||
# is not possible.
|
||||
"required",
|
||||
# Attempt to implement headers via raw headers, falling to header maps or
|
||||
# symlink tress when raw headers cannot be used (e.g. rule contains a
|
||||
# generated header or remaps a header to an incompatible location in the
|
||||
# header namespace).
|
||||
"preferred",
|
||||
"disabled",
|
||||
)
|
||||
|
||||
HeaderMode = enum(
|
||||
# Creates the header map that references the headers directly in the source
|
||||
# tree.
|
||||
"header_map_only",
|
||||
# Creates the tree of symbolic links of headers.
|
||||
"symlink_tree_only",
|
||||
# Creates the tree of symbolic links of headers and creates the header map
|
||||
# that references the symbolic links to the headers.
|
||||
"symlink_tree_with_header_map",
|
||||
)
|
||||
|
||||
HeaderStyle = enum(
|
||||
"local",
|
||||
"system",
|
||||
)
|
||||
|
||||
Headers = record(
|
||||
include_path = field("cmd_args"),
|
||||
# NOTE(agallagher): Used for module hack replacement.
|
||||
symlink_tree = field(["artifact", None], None),
|
||||
)
|
||||
|
||||
CHeader = record(
|
||||
# `"artifact"` pointing to the actual header file
|
||||
artifact = "artifact",
|
||||
# Basename as it should appear in include directive
|
||||
name = str.type,
|
||||
# Prefix before the basename as it should appear in include directive
|
||||
namespace = str.type,
|
||||
# Whether or not this header is provided via dict, where the corresponding key is a new name
|
||||
named = bool.type,
|
||||
)
|
||||
|
||||
# Parameters controlling the varying aspects of headers-related behavior.
|
||||
# The contract on how headers could be used (i.e. end user inclusion rules)
|
||||
# is different for `apple_library` and `cxx_library`. Those parameters
|
||||
# allows generalizing the C++ rules implementation and are provided
|
||||
# by top-level user-facing wrappers around those generalized methods.
|
||||
CxxHeadersLayout = record(
|
||||
# Prefix part of the header path in the include statement. Header name might
|
||||
# not always be prepended by the namespace, `naming` parameter is controlling
|
||||
# that behavior. The value is ready to be used and abstracts different naming
|
||||
# for such prefix in user-facing attributes (e.g. `apple_binary.header_path_prefix`
|
||||
# vs `cxx_binary.header_namespace`) and different default values when those
|
||||
# attributes are omitted (package path for regular C++ rules vs target name for
|
||||
# Apple-specific rules).
|
||||
namespace = str.type,
|
||||
# Selects the behavior in the implementation to support the specific way of how
|
||||
# headers are allowed to be included (e.g. if header namespace is applied for
|
||||
# headers from dicts). For more information see comment for `CxxHeadersNaming`
|
||||
naming = CxxHeadersNaming.type,
|
||||
)
|
||||
|
||||
CPrecompiledHeaderInfo = provider(fields = [
|
||||
# Actual precompiled header ready to be used during compilation, "artifact"
|
||||
"header",
|
||||
])
|
||||
|
||||
def cxx_attr_header_namespace(ctx: "context") -> str.type:
|
||||
return value_or(ctx.attrs.header_namespace, ctx.label.package)
|
||||
|
||||
def cxx_attr_exported_headers(ctx: "context", headers_layout: CxxHeadersLayout.type) -> [CHeader.type]:
|
||||
headers = _get_attr_headers(ctx.attrs.exported_headers, headers_layout.namespace, headers_layout.naming)
|
||||
platform_headers = _get_attr_headers(_headers_by_platform(ctx, ctx.attrs.exported_platform_headers), headers_layout.namespace, headers_layout.naming)
|
||||
return headers + platform_headers
|
||||
|
||||
def cxx_attr_headers(ctx: "context", headers_layout: CxxHeadersLayout.type) -> [CHeader.type]:
|
||||
headers = _get_attr_headers(ctx.attrs.headers, headers_layout.namespace, headers_layout.naming)
|
||||
platform_headers = _get_attr_headers(_headers_by_platform(ctx, ctx.attrs.platform_headers), headers_layout.namespace, headers_layout.naming)
|
||||
return headers + platform_headers
|
||||
|
||||
def cxx_get_regular_cxx_headers_layout(ctx: "context") -> CxxHeadersLayout.type:
|
||||
namespace = cxx_attr_header_namespace(ctx)
|
||||
return CxxHeadersLayout(namespace = namespace, naming = CxxHeadersNaming("regular"))
|
||||
|
||||
def cxx_attr_exported_header_style(ctx: "context") -> HeaderStyle.type:
|
||||
return HeaderStyle(ctx.attrs.exported_header_style)
|
||||
|
||||
def _get_attr_headers(xs: "", namespace: str.type, naming: CxxHeadersNaming.type) -> [CHeader.type]:
|
||||
if type(xs) == type([]):
|
||||
return [CHeader(artifact = x, name = _get_list_header_name(x, naming), namespace = namespace, named = False) for x in xs]
|
||||
else:
|
||||
return [CHeader(artifact = xs[x], name = x, namespace = _get_dict_header_namespace(namespace, naming), named = True) for x in xs]
|
||||
|
||||
def _headers_by_platform(ctx: "context", xs: [(str.type, "")]) -> "":
|
||||
res = {}
|
||||
for deps in cxx_by_platform(ctx, xs):
|
||||
res.update(from_named_set(deps))
|
||||
return res
|
||||
|
||||
def as_raw_headers(
|
||||
ctx: "context",
|
||||
headers: {str.type: "artifact"},
|
||||
mode: HeadersAsRawHeadersMode.type) -> [["label_relative_path"], None]:
|
||||
"""
|
||||
Return the include directories needed to treat the given headers as raw
|
||||
headers, depending on the given `HeadersAsRawHeadersMode` mode.
|
||||
|
||||
Args:
|
||||
mode:
|
||||
disabled - always return `None`
|
||||
preferred - return `None` if conversion isn't possible
|
||||
required - fail if conversion isn't possible
|
||||
"""
|
||||
|
||||
# If we're not supporting raw header conversion, return `None`.
|
||||
if mode == HeadersAsRawHeadersMode("disabled"):
|
||||
return None
|
||||
|
||||
return _as_raw_headers(
|
||||
ctx,
|
||||
headers,
|
||||
# Don't fail if conversion isn't required.
|
||||
no_fail = mode != HeadersAsRawHeadersMode("required"),
|
||||
)
|
||||
|
||||
def prepare_headers(ctx: "context", srcs: {str.type: "artifact"}, name: str.type) -> [Headers.type, None]:
|
||||
"""
|
||||
Prepare all the headers we want to use, depending on the header_mode
|
||||
set on the target's toolchain.
|
||||
- In the case of a header map, we create a `name`.hmap file and
|
||||
return it as part of the include path.
|
||||
- In the case of a symlink tree, we create a directory of `name`
|
||||
containing the headers and return it as part of the include path.
|
||||
"""
|
||||
if len(srcs) == 0:
|
||||
return None
|
||||
|
||||
header_mode = get_cxx_toolchain_info(ctx).header_mode
|
||||
|
||||
# TODO(T110378135): There's a bug in clang where using header maps w/o
|
||||
# explicit `-I` anchors breaks module map lookups. This will be fixed
|
||||
# by https://reviews.llvm.org/D103930 so, until it lands, disable header
|
||||
# maps when we see a module map.
|
||||
if (header_mode == HeaderMode("symlink_tree_with_header_map") and
|
||||
any([paths.basename(n) == "module.modulemap" for n in srcs.keys()])):
|
||||
header_mode = HeaderMode("symlink_tree_only")
|
||||
if header_mode == HeaderMode("header_map_only"):
|
||||
hmap = _mk_hmap(ctx, name, {h: (a, "{}") for h, a in srcs.items()})
|
||||
return Headers(
|
||||
include_path = cmd_args(hmap).hidden(srcs.values()),
|
||||
)
|
||||
symlink_dir = ctx.actions.symlinked_dir(name, _normalize_header_srcs(srcs))
|
||||
if header_mode == HeaderMode("symlink_tree_only"):
|
||||
return Headers(include_path = cmd_args(symlink_dir), symlink_tree = symlink_dir)
|
||||
if header_mode == HeaderMode("symlink_tree_with_header_map"):
|
||||
hmap = _mk_hmap(ctx, name, {h: (symlink_dir, "{}/" + h) for h in srcs})
|
||||
return Headers(
|
||||
include_path = cmd_args(hmap).hidden(symlink_dir),
|
||||
symlink_tree = symlink_dir,
|
||||
)
|
||||
fail("Unsupported header mode: {}".format(header_mode))
|
||||
|
||||
def _normalize_header_srcs(srcs: dict.type) -> dict.type:
|
||||
normalized_srcs = {}
|
||||
for key, val in srcs.items():
|
||||
normalized_key = paths.normalize(key)
|
||||
stored_val = normalized_srcs.get(normalized_key, None)
|
||||
expect(
|
||||
stored_val == None or stored_val == val,
|
||||
"Got different values {} and {} for the same normalized header {}".format(
|
||||
val,
|
||||
stored_val,
|
||||
normalized_key,
|
||||
),
|
||||
)
|
||||
normalized_srcs[normalized_key] = val
|
||||
|
||||
return normalized_srcs
|
||||
|
||||
def _as_raw_headers(
|
||||
ctx: "context",
|
||||
headers: {str.type: "artifact"},
|
||||
# Return `None` instead of failing.
|
||||
no_fail: bool.type = False) -> [["label_relative_path"], None]:
|
||||
"""
|
||||
Return the include directories needed to treat the given headers as raw
|
||||
headers.
|
||||
"""
|
||||
|
||||
# Find the all the include dirs needed to treat the given headers as raw
|
||||
# headers.
|
||||
inc_dirs = {}
|
||||
for name, header in headers.items():
|
||||
inc_dir = _as_raw_header(
|
||||
ctx,
|
||||
name,
|
||||
header,
|
||||
no_fail = no_fail,
|
||||
)
|
||||
|
||||
# If the conversion wasn't possible, `inc_dir` will be `None` and we
|
||||
# should bail now.
|
||||
if inc_dir == None:
|
||||
return None
|
||||
inc_dirs[inc_dir] = None
|
||||
|
||||
return [ctx.label.path.add(p) for p in inc_dirs]
|
||||
|
||||
def _as_raw_header(
|
||||
ctx: "context",
|
||||
# The full name used to include the header.
|
||||
name: str.type,
|
||||
header: "artifact",
|
||||
# Return `None` instead of failing.
|
||||
no_fail: bool.type = False) -> [str.type, None]:
|
||||
"""
|
||||
Return path to pass to `include_directories` to treat the given header as
|
||||
a raw header.
|
||||
"""
|
||||
|
||||
# We can't handle generated headers.
|
||||
if not header.is_source:
|
||||
if no_fail:
|
||||
return None
|
||||
fail("generated headers cannot be used as raw headers ({})"
|
||||
.format(header))
|
||||
|
||||
# To include the header via its name using raw headers and include dirs,
|
||||
# it needs to be a suffix of its original path, and we'll strip the include
|
||||
# name to get the include dir used to include it.
|
||||
path = paths.join(ctx.label.package, header.short_path)
|
||||
base = paths.strip_suffix(path, name)
|
||||
if base == None:
|
||||
if no_fail:
|
||||
return None
|
||||
fail("header name must be a path suffix of the header path to be " +
|
||||
"used as a raw header ({} => {})".format(name, header))
|
||||
|
||||
# If the include dir is underneath our package, then just relativize to find
|
||||
# out package-relative path.
|
||||
if len(base) > len(ctx.label.package):
|
||||
return paths.relativize(base, ctx.label.package)
|
||||
|
||||
# Otherwise, this include dir needs to reference a parent dir.
|
||||
expect(ctx.label.package.startswith(base))
|
||||
num_parents = (
|
||||
len(ctx.label.package.split("/")) -
|
||||
(0 if not base else len(base.split("/")))
|
||||
)
|
||||
return "/".join([".."] * num_parents)
|
||||
|
||||
def _get_list_header_name(header: "artifact", naming: CxxHeadersNaming.type) -> str.type:
|
||||
if naming.value == "regular":
|
||||
return header.short_path
|
||||
elif naming.value == "apple":
|
||||
return header.basename
|
||||
else:
|
||||
fail("Unsupported header naming: {}".format(naming))
|
||||
|
||||
def _get_dict_header_namespace(namespace: str.type, naming: CxxHeadersNaming.type) -> str.type:
|
||||
if naming.value == "regular":
|
||||
return namespace
|
||||
elif naming.value == "apple":
|
||||
return ""
|
||||
else:
|
||||
fail("Unsupported header naming: {}".format(naming))
|
||||
|
||||
def _mk_hmap(ctx: "context", name: str.type, headers: {str.type: ("artifact", str.type)}) -> "artifact":
|
||||
output = ctx.actions.declare_output(name + ".hmap")
|
||||
cmd = cmd_args(get_cxx_toolchain_info(ctx).mk_hmap)
|
||||
cmd.add(["--output", output.as_output()])
|
||||
|
||||
header_args = cmd_args()
|
||||
for n, (path, fmt) in headers.items():
|
||||
header_args.add(n)
|
||||
header_args.add(cmd_args(path, format = fmt))
|
||||
|
||||
hmap_args_file = ctx.actions.write(output.basename + ".argsfile", cmd_args(header_args, quote = "shell"))
|
||||
cmd.add(["--mappings-file", hmap_args_file]).hidden(header_args)
|
||||
ctx.actions.run(cmd, category = "generate_hmap", identifier = name)
|
||||
return output
|
||||
299
vendor/cxx/tools/buck/prelude/cxx/link.bzl
vendored
Normal file
299
vendor/cxx/tools/buck/prelude/cxx/link.bzl
vendored
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
# 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//cxx:cxx_bolt.bzl",
|
||||
"bolt",
|
||||
"cxx_use_bolt",
|
||||
)
|
||||
load("@prelude//cxx:debug.bzl", "SplitDebugMode")
|
||||
load(
|
||||
"@prelude//cxx/dist_lto:dist_lto.bzl",
|
||||
"cxx_dist_link",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"LinkArgs",
|
||||
"LinkOrdering",
|
||||
"LinkedObject",
|
||||
"unpack_external_debug_info",
|
||||
"unpack_link_args",
|
||||
)
|
||||
load("@prelude//linking:link_postprocessor.bzl", "postprocess")
|
||||
load("@prelude//linking:strip.bzl", "strip_shared_library")
|
||||
load("@prelude//utils:utils.bzl", "value_or")
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
load(
|
||||
":cxx_link_utility.bzl",
|
||||
"cxx_link_cmd",
|
||||
"linker_map_args",
|
||||
"make_link_args",
|
||||
)
|
||||
load(":dwp.bzl", "dwp", "dwp_available")
|
||||
load(
|
||||
":linker.bzl",
|
||||
"SharedLibraryFlagOverrides", # @unused Used as a type
|
||||
"get_import_library",
|
||||
"get_output_flags",
|
||||
"get_shared_library_flags",
|
||||
"get_shared_library_name_linker_flags",
|
||||
)
|
||||
|
||||
# Actually perform a link into the supplied output.
|
||||
def cxx_link(
|
||||
ctx: "context",
|
||||
links: [LinkArgs.type],
|
||||
# The destination for the link output.
|
||||
output: "artifact",
|
||||
linker_map: ["artifact", None] = None,
|
||||
prefer_local: bool.type = False,
|
||||
local_only: bool.type = False,
|
||||
link_weight: int.type = 1,
|
||||
enable_distributed_thinlto: bool.type = False,
|
||||
# A category suffix that will be added to the category of the link action that is generated.
|
||||
category_suffix: [str.type, None] = None,
|
||||
# An identifier that will uniquely name this link action in the context of a category. Useful for
|
||||
# differentiating multiple link actions in the same rule.
|
||||
identifier: [str.type, None] = None,
|
||||
is_shared: bool.type = False,
|
||||
strip: bool.type = False,
|
||||
# A function/lambda which will generate the strip args using the ctx.
|
||||
strip_args_factory = None,
|
||||
generate_dwp: bool.type = True,
|
||||
executable_link = False,
|
||||
link_postprocessor: ["cmd_args", None] = None,
|
||||
force_full_hybrid_if_capable: bool.type = False,
|
||||
import_library: ["artifact", None] = None) -> LinkedObject.type:
|
||||
cxx_toolchain_info = get_cxx_toolchain_info(ctx)
|
||||
linker_info = cxx_toolchain_info.linker_info
|
||||
|
||||
should_generate_dwp = generate_dwp and dwp_available(ctx) and cxx_toolchain_info.split_debug_mode != SplitDebugMode("none")
|
||||
if linker_info.supports_distributed_thinlto and enable_distributed_thinlto:
|
||||
if not linker_info.requires_objects:
|
||||
fail("Cannot use distributed thinlto if the cxx toolchain doesn't require_objects")
|
||||
return cxx_dist_link(
|
||||
ctx,
|
||||
links,
|
||||
output,
|
||||
linker_map,
|
||||
category_suffix,
|
||||
identifier,
|
||||
should_generate_dwp,
|
||||
executable_link,
|
||||
)
|
||||
if linker_map != None:
|
||||
links += [linker_map_args(ctx, linker_map.as_output())]
|
||||
(link_args, hidden, dwo_dir) = make_link_args(
|
||||
ctx,
|
||||
links,
|
||||
suffix = identifier,
|
||||
dwo_dir_name = output.short_path + ".dwo.d",
|
||||
is_shared = is_shared,
|
||||
link_ordering = LinkOrdering(linker_info.link_ordering) if linker_info.link_ordering else None,
|
||||
)
|
||||
|
||||
external_debug_info = []
|
||||
|
||||
# If we're not stripping the output linked object, than add-in an externally
|
||||
# referenced debug info that the linked object may reference (and which may
|
||||
# need to be available for debugging).
|
||||
if not (strip or getattr(ctx.attrs, "prefer_stripped_objects", False)):
|
||||
for link in links:
|
||||
external_debug_info.extend(unpack_external_debug_info(link))
|
||||
|
||||
# When using LTO+split-dwarf, the link step will generate externally
|
||||
# referenced debug info.
|
||||
if dwo_dir != None:
|
||||
external_debug_info.append(dwo_dir)
|
||||
|
||||
if linker_info.type == "windows":
|
||||
shell_quoted_args = cmd_args(link_args)
|
||||
else:
|
||||
shell_quoted_args = cmd_args(link_args, quote = "shell")
|
||||
argfile, _ = ctx.actions.write(
|
||||
output.short_path + ".linker.argsfile",
|
||||
shell_quoted_args,
|
||||
allow_args = True,
|
||||
)
|
||||
command = cxx_link_cmd(ctx)
|
||||
command.add(get_output_flags(linker_info.type, output))
|
||||
command.add(cmd_args(argfile, format = "@{}"))
|
||||
command.hidden([hidden])
|
||||
category = "cxx_link"
|
||||
if category_suffix != None:
|
||||
category += "_" + category_suffix
|
||||
|
||||
# If the linked object files don't contain debug info, clang may not
|
||||
# generate a DWO directory, so make sure we at least `mkdir` and empty
|
||||
# one to make v2/RE happy.
|
||||
if dwo_dir != None:
|
||||
cmd = cmd_args(["/bin/sh", "-c"])
|
||||
cmd.add(cmd_args(dwo_dir.as_output(), format = 'mkdir -p {}; "$@"'))
|
||||
cmd.add('""').add(command)
|
||||
cmd.hidden(command)
|
||||
command = cmd
|
||||
|
||||
# Enable hybrid execution only when prefer local is set to preserve isolation
|
||||
if prefer_local and force_full_hybrid_if_capable:
|
||||
fail("cannot use `force_full_hybrid_if_capable` when `prefer_local` is enabled")
|
||||
|
||||
if local_only and force_full_hybrid_if_capable:
|
||||
fail("cannot use `force_full_hybrid_if_capable` when `local_only` is enabled")
|
||||
|
||||
ctx.actions.run(
|
||||
command,
|
||||
prefer_local = prefer_local,
|
||||
local_only = local_only,
|
||||
weight = link_weight,
|
||||
category = category,
|
||||
identifier = identifier,
|
||||
force_full_hybrid_if_capable = force_full_hybrid_if_capable,
|
||||
)
|
||||
if strip:
|
||||
strip_args = strip_args_factory(ctx) if strip_args_factory else cmd_args()
|
||||
output = strip_shared_library(ctx, cxx_toolchain_info, output, strip_args)
|
||||
|
||||
if link_postprocessor:
|
||||
output = postprocess(ctx, output, link_postprocessor)
|
||||
|
||||
final_output = output if not (executable_link and cxx_use_bolt(ctx)) else bolt(ctx, output, identifier)
|
||||
dwp_artifact = None
|
||||
if should_generate_dwp:
|
||||
# TODO(T110378144): Once we track split dwarf from compiles, we should
|
||||
# just pass in `binary.external_debug_info` here instead of all link
|
||||
# args.
|
||||
dwp_inputs = cmd_args()
|
||||
for link in links:
|
||||
dwp_inputs.add(unpack_link_args(link))
|
||||
dwp_inputs.add(external_debug_info)
|
||||
|
||||
dwp_artifact = dwp(
|
||||
ctx,
|
||||
final_output,
|
||||
identifier = identifier,
|
||||
category_suffix = category_suffix,
|
||||
# TODO(T110378142): Ideally, referenced objects are a list of
|
||||
# artifacts, but currently we don't track them properly. So, we
|
||||
# just pass in the full link line and extract all inputs from that,
|
||||
# which is a bit of an overspecification.
|
||||
referenced_objects = [dwp_inputs],
|
||||
allow_huge_dwp = ctx.attrs.allow_huge_dwp if hasattr(ctx.attrs, "allow_huge_dwp") else False,
|
||||
)
|
||||
|
||||
return LinkedObject(
|
||||
output = final_output,
|
||||
prebolt_output = output,
|
||||
dwp = dwp_artifact,
|
||||
external_debug_info = external_debug_info,
|
||||
linker_argsfile = argfile,
|
||||
import_library = import_library,
|
||||
)
|
||||
|
||||
def _link_libraries_locally(ctx: "context", prefer_local: bool.type) -> bool.type:
|
||||
if hasattr(ctx.attrs, "_link_libraries_locally_override"):
|
||||
return value_or(ctx.attrs._link_libraries_locally_override, prefer_local)
|
||||
return prefer_local
|
||||
|
||||
def cxx_link_shared_library(
|
||||
ctx: "context",
|
||||
# The destination for the link output.
|
||||
output: "artifact",
|
||||
# Optional soname to link into shared library.
|
||||
name: [str.type, None] = None,
|
||||
links: [LinkArgs.type] = [],
|
||||
prefer_local: [bool.type, None] = None,
|
||||
local_only: [bool.type, None] = None,
|
||||
link_weight: int.type = 1,
|
||||
enable_distributed_thinlto: bool.type = False,
|
||||
# A category suffix that will be added to the category of the link action that is generated.
|
||||
category_suffix: [str.type, None] = None,
|
||||
# An identifier that will uniquely name this link action in the context of a category. Useful for
|
||||
# differentiating multiple link actions in the same rule.
|
||||
identifier: [str.type, None] = None,
|
||||
# Overrides the default flags used to specify building shared libraries
|
||||
shared_library_flags: [SharedLibraryFlagOverrides.type, None] = None,
|
||||
strip: bool.type = False,
|
||||
strip_args_factory = None,
|
||||
link_postprocessor: ["cmd_args", None] = None,
|
||||
force_full_hybrid_if_capable: [bool.type, None] = None) -> LinkedObject.type:
|
||||
"""
|
||||
Link a shared library into the supplied output.
|
||||
"""
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
linker_type = linker_info.type
|
||||
extra_args = []
|
||||
|
||||
extra_args.extend(get_shared_library_flags(linker_type, shared_library_flags)) # e.g. "-shared"
|
||||
if name != None:
|
||||
extra_args.extend(get_shared_library_name_linker_flags(linker_type, name, shared_library_flags))
|
||||
|
||||
(import_library, import_library_args) = get_import_library(
|
||||
ctx,
|
||||
linker_type,
|
||||
output.short_path,
|
||||
)
|
||||
extra_args.extend(import_library_args)
|
||||
|
||||
prefer_local_value = value_or(prefer_local, value_or(linker_info.link_libraries_locally, False))
|
||||
|
||||
return cxx_link(
|
||||
ctx,
|
||||
[LinkArgs(flags = extra_args)] + links,
|
||||
output,
|
||||
prefer_local = _link_libraries_locally(ctx, prefer_local_value),
|
||||
local_only = value_or(local_only, False),
|
||||
link_weight = link_weight,
|
||||
enable_distributed_thinlto = enable_distributed_thinlto,
|
||||
category_suffix = category_suffix,
|
||||
identifier = identifier,
|
||||
is_shared = True,
|
||||
strip = strip,
|
||||
strip_args_factory = strip_args_factory,
|
||||
link_postprocessor = link_postprocessor,
|
||||
force_full_hybrid_if_capable = value_or(force_full_hybrid_if_capable, False),
|
||||
import_library = import_library,
|
||||
)
|
||||
|
||||
def cxx_link_into_shared_library(
|
||||
ctx: "context",
|
||||
name: str.type,
|
||||
links: [LinkArgs.type] = [],
|
||||
# Wether to embed the library name as the SONAME.
|
||||
soname: bool.type = True,
|
||||
prefer_local: [bool.type, None] = None,
|
||||
local_only: [bool.type, None] = None,
|
||||
link_weight: int.type = 1,
|
||||
enable_distributed_thinlto: bool.type = False,
|
||||
# A category suffix that will be added to the category of the link action that is generated.
|
||||
category_suffix: [str.type, None] = None,
|
||||
# An identifier that will uniquely name this link action in the context of a category. Useful for
|
||||
# differentiating multiple link actions in the same rule.
|
||||
identifier: [str.type, None] = None,
|
||||
# Overrides the default flags used to specify building shared libraries
|
||||
shared_library_flags: [SharedLibraryFlagOverrides.type, None] = None,
|
||||
strip: bool.type = False,
|
||||
strip_args_factory = None,
|
||||
link_postprocessor: ["cmd_args", None] = None,
|
||||
force_full_hybrid_if_capable: [bool.type, None] = None) -> LinkedObject.type:
|
||||
output = ctx.actions.declare_output(name)
|
||||
return cxx_link_shared_library(
|
||||
ctx,
|
||||
output,
|
||||
name = name if soname else None,
|
||||
links = links,
|
||||
prefer_local = prefer_local,
|
||||
local_only = local_only,
|
||||
link_weight = link_weight,
|
||||
enable_distributed_thinlto = enable_distributed_thinlto,
|
||||
category_suffix = category_suffix,
|
||||
identifier = identifier,
|
||||
shared_library_flags = shared_library_flags,
|
||||
strip = strip,
|
||||
strip_args_factory = strip_args_factory,
|
||||
link_postprocessor = link_postprocessor,
|
||||
force_full_hybrid_if_capable = force_full_hybrid_if_capable,
|
||||
)
|
||||
433
vendor/cxx/tools/buck/prelude/cxx/link_groups.bzl
vendored
Normal file
433
vendor/cxx/tools/buck/prelude/cxx/link_groups.bzl
vendored
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
# 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//linking:link_groups.bzl",
|
||||
"LinkGroupLib", # @unused Used as a type
|
||||
"LinkGroupLibInfo",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"LinkArgs",
|
||||
"LinkInfo", # @unused Used as a type
|
||||
"LinkStyle",
|
||||
"Linkage",
|
||||
"LinkedObject", # @unused Used as a type
|
||||
"get_actual_link_style",
|
||||
"set_linkable_link_whole",
|
||||
get_link_info_from_link_infos = "get_link_info",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkable_graph.bzl",
|
||||
"LinkableGraph", # @unused Used as a type
|
||||
"LinkableNode", # @unused Used as a type
|
||||
"create_linkable_graph",
|
||||
"get_link_info",
|
||||
"get_linkable_graph_node_map_func",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkables.bzl",
|
||||
"linkable",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:graph_utils.bzl",
|
||||
"breadth_first_traversal_by",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:set.bzl",
|
||||
"set",
|
||||
"set_record",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:utils.bzl",
|
||||
"expect",
|
||||
)
|
||||
load(
|
||||
":groups.bzl",
|
||||
"Group", # @unused Used as a type
|
||||
"MATCH_ALL_LABEL",
|
||||
"NO_MATCH_LABEL",
|
||||
"compute_mappings",
|
||||
"parse_groups_definitions",
|
||||
)
|
||||
load(
|
||||
":link.bzl",
|
||||
"cxx_link_shared_library",
|
||||
)
|
||||
|
||||
LINK_GROUP_MAP_DATABASE_SUB_TARGET = "link-group-map-database"
|
||||
LINK_GROUP_MAP_FILE_NAME = "link_group_map.json"
|
||||
|
||||
LinkGroupInfo = provider(fields = [
|
||||
"groups", # [Group.type]
|
||||
"groups_hash", # str.type
|
||||
"mappings", # {"label": str.type}
|
||||
])
|
||||
|
||||
LinkGroupLinkInfo = record(
|
||||
link_info = field(LinkInfo.type),
|
||||
link_style = field(LinkStyle.type),
|
||||
)
|
||||
|
||||
LinkGroupLibSpec = record(
|
||||
# The output name given to the linked shared object.
|
||||
name = field(str.type),
|
||||
# Used to differentiate normal native shared libs from e.g. Python native
|
||||
# extensions (which are techncially shared libs, but don't set a SONAME
|
||||
# and aren't managed by `SharedLibraryInfo`s).
|
||||
is_shared_lib = field(bool.type, True),
|
||||
# The link group to link.
|
||||
group = field(Group.type),
|
||||
)
|
||||
|
||||
def parse_link_group_definitions(mappings: list.type) -> [Group.type]:
|
||||
return parse_groups_definitions(mappings, linkable)
|
||||
|
||||
def get_link_group(ctx: "context") -> [str.type, None]:
|
||||
return ctx.attrs.link_group
|
||||
|
||||
def get_link_group_info(
|
||||
ctx: "context",
|
||||
executable_deps: [[LinkableGraph.type], None] = None) -> [LinkGroupInfo.type, None]:
|
||||
"""
|
||||
Parses the currently analyzed context for any link group definitions
|
||||
and returns a list of all link groups with their mappings.
|
||||
"""
|
||||
link_group_map = ctx.attrs.link_group_map
|
||||
|
||||
if not link_group_map:
|
||||
return None
|
||||
|
||||
# If specified as a dep that provides the `LinkGroupInfo`, use that.
|
||||
if type(link_group_map) == "dependency":
|
||||
return link_group_map[LinkGroupInfo]
|
||||
|
||||
# Otherwise build one from our graph.
|
||||
expect(executable_deps != None)
|
||||
link_groups = parse_link_group_definitions(link_group_map)
|
||||
linkable_graph = create_linkable_graph(
|
||||
ctx,
|
||||
children = executable_deps,
|
||||
)
|
||||
linkable_graph_node_map = get_linkable_graph_node_map_func(linkable_graph)()
|
||||
mappings = compute_mappings(groups = link_groups, graph_map = linkable_graph_node_map)
|
||||
return LinkGroupInfo(
|
||||
groups = link_groups,
|
||||
groups_hash = hash(str(link_groups)),
|
||||
mappings = mappings,
|
||||
)
|
||||
|
||||
def get_auto_link_group_libs(ctx: "context") -> [{str.type: LinkGroupLib.type}, None]:
|
||||
"""
|
||||
Return link group libs created by the link group map rule.
|
||||
"""
|
||||
link_group_map = ctx.attrs.link_group_map
|
||||
|
||||
if not link_group_map:
|
||||
return None
|
||||
|
||||
if type(link_group_map) == "dependency":
|
||||
info = link_group_map.get(LinkGroupLibInfo)
|
||||
if info == None:
|
||||
return None
|
||||
return info.libs
|
||||
|
||||
fail("Link group maps must be provided as a link_group_map rule dependency.")
|
||||
|
||||
def get_link_group_preferred_linkage(link_groups: [Group.type]) -> {"label": Linkage.type}:
|
||||
return {
|
||||
mapping.root.label: mapping.preferred_linkage
|
||||
for group in link_groups
|
||||
for mapping in group.mappings
|
||||
if mapping.root != None and mapping.preferred_linkage != None
|
||||
}
|
||||
|
||||
def get_filtered_labels_to_links_map(
|
||||
linkable_graph_node_map: {"label": LinkableNode.type},
|
||||
link_group: [str.type, None],
|
||||
link_group_mappings: [{"label": str.type}, None],
|
||||
link_group_preferred_linkage: {"label": Linkage.type},
|
||||
link_style: LinkStyle.type,
|
||||
deps: ["label"],
|
||||
link_group_libs: {str.type: LinkGroupLib.type} = {},
|
||||
prefer_stripped: bool.type = False,
|
||||
is_executable_link: bool.type = False) -> {"label": LinkGroupLinkInfo.type}:
|
||||
"""
|
||||
Given a linkable graph, link style and link group mappings, finds all links
|
||||
to consider for linking traversing the graph as necessary and then
|
||||
identifies which link infos and targets belong the in the provided link group.
|
||||
If no link group is provided, all unmatched link infos are returned.
|
||||
"""
|
||||
|
||||
def get_traversed_deps(node: "label") -> ["label"]:
|
||||
linkable_node = linkable_graph_node_map[node] # buildifier: disable=uninitialized
|
||||
|
||||
# Always link against exported deps
|
||||
node_linkables = list(linkable_node.exported_deps)
|
||||
|
||||
# If the preferred linkage is `static` or `any` with a link style that is
|
||||
# not shared, we need to link against the deps too.
|
||||
should_traverse = False
|
||||
if linkable_node.preferred_linkage == Linkage("static"):
|
||||
should_traverse = True
|
||||
elif linkable_node.preferred_linkage == Linkage("any"):
|
||||
should_traverse = link_style != Linkage("shared")
|
||||
|
||||
if should_traverse:
|
||||
node_linkables += linkable_node.deps
|
||||
|
||||
return node_linkables
|
||||
|
||||
# Get all potential linkable targets
|
||||
linkables = breadth_first_traversal_by(
|
||||
linkable_graph_node_map,
|
||||
deps,
|
||||
get_traversed_deps,
|
||||
)
|
||||
|
||||
# An index of target to link group names, for all link group library nodes.
|
||||
# Provides fast lookup of a link group root lib via it's label.
|
||||
link_group_roots = {
|
||||
lib.label: name
|
||||
for name, lib in link_group_libs.items()
|
||||
if lib.label != None
|
||||
}
|
||||
|
||||
linkable_map = {}
|
||||
|
||||
# Keep track of whether we've already added a link group to the link line
|
||||
# already. This avoids use adding the same link group lib multiple times,
|
||||
# for each of the possible multiple nodes that maps to it.
|
||||
link_group_added = {}
|
||||
|
||||
def add_link(target: "label", link_style: LinkStyle.type):
|
||||
linkable_map[target] = LinkGroupLinkInfo(
|
||||
link_info = get_link_info(linkable_graph_node_map[target], link_style, prefer_stripped),
|
||||
link_style = link_style,
|
||||
) # buildifier: disable=uninitialized
|
||||
|
||||
def add_link_group(target: "label", target_group: str.type):
|
||||
# If we've already added this link group to the link line, we're done.
|
||||
if target_group in link_group_added:
|
||||
return
|
||||
|
||||
# In some flows, we may not have access to the actual link group lib
|
||||
# in our dep tree (e.g. https://fburl.com/code/pddmkptb), so just bail
|
||||
# in this case.
|
||||
# NOTE(agallagher): This case seems broken, as we're not going to set
|
||||
# DT_NEEDED tag correctly, or detect missing syms at link time.
|
||||
link_group_lib = link_group_libs.get(target_group)
|
||||
if link_group_lib == None:
|
||||
return
|
||||
|
||||
expect(target_group != link_group)
|
||||
link_group_added[target_group] = None
|
||||
linkable_map[target] = LinkGroupLinkInfo(
|
||||
link_info = get_link_info_from_link_infos(link_group_lib.shared_link_infos),
|
||||
link_style = LinkStyle("shared"),
|
||||
) # buildifier: disable=uninitialized
|
||||
|
||||
for target in linkables:
|
||||
node = linkable_graph_node_map[target]
|
||||
actual_link_style = get_actual_link_style(link_style, link_group_preferred_linkage.get(target, node.preferred_linkage))
|
||||
|
||||
# Always link any shared dependencies
|
||||
if actual_link_style == LinkStyle("shared"):
|
||||
# If this target is a link group root library, we
|
||||
# 1) don't propagate shared linkage down the tree, and
|
||||
# 2) use the provided link info in lieu of what's in the grph.
|
||||
target_link_group = link_group_roots.get(target)
|
||||
if target_link_group != None and target_link_group != link_group:
|
||||
add_link_group(target, target_link_group)
|
||||
else:
|
||||
add_link(target, LinkStyle("shared"))
|
||||
|
||||
# Mark transitive deps as shared.
|
||||
for exported_dep in node.exported_deps:
|
||||
exported_node = linkable_graph_node_map[exported_dep]
|
||||
if exported_node.preferred_linkage == Linkage("any"):
|
||||
link_group_preferred_linkage[exported_dep] = Linkage("shared")
|
||||
else: # static or static_pic
|
||||
target_link_group = link_group_mappings.get(target)
|
||||
|
||||
if not target_link_group and not link_group:
|
||||
# Ungrouped linkable targets belong to the unlabeled executable
|
||||
add_link(target, actual_link_style)
|
||||
elif is_executable_link and target_link_group == NO_MATCH_LABEL:
|
||||
# Targets labeled NO_MATCH belong to the unlabeled executable
|
||||
add_link(target, actual_link_style)
|
||||
elif target_link_group == MATCH_ALL_LABEL or target_link_group == link_group:
|
||||
# If this belongs to the match all link group or the group currently being evaluated
|
||||
add_link(target, actual_link_style)
|
||||
elif target_link_group not in (None, NO_MATCH_LABEL, MATCH_ALL_LABEL):
|
||||
add_link_group(target, target_link_group)
|
||||
|
||||
return linkable_map
|
||||
|
||||
# Find all link group libraries that are first order deps or exported deps of
|
||||
# the exectuble or another link group's libs
|
||||
def get_public_link_group_nodes(
|
||||
linkable_graph_node_map: {"label": LinkableNode.type},
|
||||
link_group_mappings: [{"label": str.type}, None],
|
||||
executable_deps: ["label"],
|
||||
root_link_group: [str.type, None]) -> set_record.type:
|
||||
external_link_group_nodes = set()
|
||||
|
||||
# TODO(@christylee): do we need to traverse root link group and NO_MATCH_LABEL exported deps?
|
||||
# buildifier: disable=uninitialized
|
||||
def crosses_link_group_boundary(current_group: [str.type, None], new_group: [str.type, None]):
|
||||
# belongs to root binary
|
||||
if new_group == root_link_group:
|
||||
return False
|
||||
|
||||
if new_group == NO_MATCH_LABEL:
|
||||
# Using NO_MATCH with an explicitly defined root_link_group is undefined behavior
|
||||
expect(root_link_group == None or root_link_group == NO_MATCH_LABEL)
|
||||
return False
|
||||
|
||||
# private node in link group
|
||||
if new_group == current_group:
|
||||
return False
|
||||
return True
|
||||
|
||||
# Check the direct deps of the executable since the executable is not in linkable_graph_node_map
|
||||
for label in executable_deps:
|
||||
group = link_group_mappings.get(label)
|
||||
if crosses_link_group_boundary(root_link_group, group):
|
||||
external_link_group_nodes.add(label)
|
||||
|
||||
# get all nodes that cross function boundaries
|
||||
# TODO(@christylee): dlopen-able libs that depend on the main executable does not have a
|
||||
# linkable internal edge to the main executable. Symbols that are not referenced during the
|
||||
# executable link might be dropped unless the dlopen-able libs are linked against the main
|
||||
# executable. We need to force export those symbols to avoid undefined symbls.
|
||||
for label, node in linkable_graph_node_map.items():
|
||||
current_group = link_group_mappings.get(label)
|
||||
|
||||
for dep in node.deps + node.exported_deps:
|
||||
new_group = link_group_mappings.get(dep)
|
||||
if crosses_link_group_boundary(current_group, new_group):
|
||||
external_link_group_nodes.add(dep)
|
||||
|
||||
SPECIAL_LINK_GROUPS = [MATCH_ALL_LABEL, NO_MATCH_LABEL]
|
||||
|
||||
# buildifier: disable=uninitialized
|
||||
def get_traversed_deps(node: "label") -> ["label"]:
|
||||
exported_deps = []
|
||||
for exported_dep in linkable_graph_node_map[node].exported_deps:
|
||||
group = link_group_mappings.get(exported_dep)
|
||||
if group != root_link_group and group not in SPECIAL_LINK_GROUPS:
|
||||
exported_deps.append(exported_dep)
|
||||
return exported_deps
|
||||
|
||||
external_link_group_nodes.update(
|
||||
# get transitive exported deps
|
||||
breadth_first_traversal_by(
|
||||
linkable_graph_node_map,
|
||||
external_link_group_nodes.list(),
|
||||
get_traversed_deps,
|
||||
),
|
||||
)
|
||||
|
||||
return external_link_group_nodes
|
||||
|
||||
def get_filtered_links(
|
||||
labels_to_links_map: {"label": LinkGroupLinkInfo.type},
|
||||
public_link_group_nodes: [set_record.type, None] = None):
|
||||
if public_link_group_nodes == None:
|
||||
return [link_group_info.link_info for link_group_info in labels_to_links_map.values()]
|
||||
infos = []
|
||||
for label, link_group_info in labels_to_links_map.items():
|
||||
info = link_group_info.link_info
|
||||
if public_link_group_nodes.contains(label):
|
||||
linkables = [set_linkable_link_whole(linkable) for linkable in info.linkables]
|
||||
infos.append(
|
||||
LinkInfo(
|
||||
name = info.name,
|
||||
pre_flags = info.pre_flags,
|
||||
post_flags = info.post_flags,
|
||||
linkables = linkables,
|
||||
external_debug_info = info.external_debug_info,
|
||||
),
|
||||
)
|
||||
else:
|
||||
infos.append(info)
|
||||
return infos
|
||||
|
||||
def get_filtered_targets(labels_to_links_map: {"label": LinkGroupLinkInfo.type}):
|
||||
return [label.raw_target() for label in labels_to_links_map.keys()]
|
||||
|
||||
def get_link_group_map_json(ctx: "context", targets: ["target_label"]) -> DefaultInfo.type:
|
||||
json_map = ctx.actions.write_json(LINK_GROUP_MAP_FILE_NAME, sorted(targets))
|
||||
return DefaultInfo(default_outputs = [json_map])
|
||||
|
||||
def create_link_group(
|
||||
ctx: "context",
|
||||
spec: LinkGroupLibSpec.type,
|
||||
# The deps of the top-level executable.
|
||||
executable_deps: ["label"] = [],
|
||||
root_link_group = [str.type, None],
|
||||
linkable_graph_node_map: {"label": LinkableNode.type} = {},
|
||||
linker_flags: [""] = [],
|
||||
link_group_mappings: {"label": str.type} = {},
|
||||
link_group_preferred_linkage: {"label": Linkage.type} = {},
|
||||
link_style: LinkStyle.type = LinkStyle("static_pic"),
|
||||
link_group_libs: {str.type: LinkGroupLib.type} = {},
|
||||
prefer_stripped_objects: bool.type = False,
|
||||
prefer_local: bool.type = False,
|
||||
category_suffix: [str.type, None] = None) -> LinkedObject.type:
|
||||
"""
|
||||
Link a link group library, described by a `LinkGroupLibSpec`. This is
|
||||
intended to handle regular shared libs and e.g. Python extensions.
|
||||
"""
|
||||
|
||||
inputs = []
|
||||
|
||||
# Add extra linker flags.
|
||||
if linker_flags:
|
||||
inputs.append(LinkInfo(pre_flags = linker_flags))
|
||||
|
||||
# Get roots to begin the linkable search.
|
||||
# TODO(agallagher): We should use the groups "public" nodes as the roots.
|
||||
roots = []
|
||||
for mapping in spec.group.mappings:
|
||||
# If there's no explicit root, this means use the executable deps.
|
||||
if mapping.root == None:
|
||||
roots.extend(executable_deps)
|
||||
else:
|
||||
roots.append(mapping.root.label)
|
||||
|
||||
# Add roots...
|
||||
filtered_labels_to_links_map = get_filtered_labels_to_links_map(
|
||||
linkable_graph_node_map,
|
||||
spec.group.name,
|
||||
link_group_mappings,
|
||||
link_group_preferred_linkage,
|
||||
link_group_libs = link_group_libs,
|
||||
link_style = link_style,
|
||||
deps = roots,
|
||||
is_executable_link = False,
|
||||
prefer_stripped = prefer_stripped_objects,
|
||||
)
|
||||
public_nodes = get_public_link_group_nodes(
|
||||
linkable_graph_node_map,
|
||||
link_group_mappings,
|
||||
executable_deps,
|
||||
root_link_group,
|
||||
)
|
||||
inputs.extend(get_filtered_links(filtered_labels_to_links_map, public_nodes))
|
||||
|
||||
# link the rule
|
||||
return cxx_link_shared_library(
|
||||
ctx,
|
||||
ctx.actions.declare_output(spec.name),
|
||||
name = spec.name if spec.is_shared_lib else None,
|
||||
links = [LinkArgs(infos = inputs)],
|
||||
category_suffix = category_suffix,
|
||||
identifier = spec.name,
|
||||
prefer_local = prefer_local,
|
||||
)
|
||||
238
vendor/cxx/tools/buck/prelude/cxx/linker.bzl
vendored
Normal file
238
vendor/cxx/tools/buck/prelude/cxx/linker.bzl
vendored
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
# 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//cxx:cxx_toolchain_types.bzl", "LinkerInfo")
|
||||
load("@prelude//utils:utils.bzl", "expect")
|
||||
|
||||
# Platform-specific linker flags handling. Modeled after the `Linker` abstraction
|
||||
# in v1 (https://fburl.com/diffusion/kqd2ylcy).
|
||||
# TODO(T110378136): It might make more sense to pass these in via the toolchain.
|
||||
Linker = record(
|
||||
# The extension to use for the shared library if not set in the toolchain.
|
||||
default_shared_library_extension = str.type,
|
||||
# The format to use for the versioned shared library extension if not set in the toolchain.
|
||||
default_shared_library_versioned_extension_format = str.type,
|
||||
# How to format arguments to the linker to set a shared lib name.
|
||||
shared_library_name_linker_flags_format = [str.type],
|
||||
# Flags to pass to the linker to make it generate a shared library.
|
||||
shared_library_flags = [str.type],
|
||||
)
|
||||
|
||||
# Allows overriding the default shared library flags.
|
||||
# e.g. when building Apple tests, we want to link with `-bundle` instead of `-shared` to allow
|
||||
# linking against the bundle loader.
|
||||
SharedLibraryFlagOverrides = record(
|
||||
# How to format arguments to the linker to set a shared lib name.
|
||||
shared_library_name_linker_flags_format = [str.type],
|
||||
# Flags to pass to the linker to make it generate a shared library.
|
||||
shared_library_flags = [str.type],
|
||||
)
|
||||
|
||||
LINKERS = {
|
||||
"darwin": Linker(
|
||||
default_shared_library_extension = "dylib",
|
||||
default_shared_library_versioned_extension_format = "{}.dylib",
|
||||
shared_library_name_linker_flags_format = ["-install_name", "@rpath/{}"],
|
||||
shared_library_flags = ["-shared"],
|
||||
),
|
||||
"gnu": Linker(
|
||||
default_shared_library_extension = "so",
|
||||
default_shared_library_versioned_extension_format = "so.{}",
|
||||
shared_library_name_linker_flags_format = ["-Wl,-soname,{}"],
|
||||
shared_library_flags = ["-shared"],
|
||||
),
|
||||
"windows": Linker(
|
||||
default_shared_library_extension = "dll",
|
||||
default_shared_library_versioned_extension_format = "dll",
|
||||
# NOTE(agallagher): I *think* windows doesn't support a flag to set the
|
||||
# library name, and relies on the basename.
|
||||
shared_library_name_linker_flags_format = [],
|
||||
shared_library_flags = ["/DLL"],
|
||||
),
|
||||
}
|
||||
|
||||
def _sanitize(s: str.type) -> str.type:
|
||||
return s.replace("/", "_")
|
||||
|
||||
# NOTE(agallagher): Does this belong in the native/shared_libraries.bzl?
|
||||
def get_shared_library_name(
|
||||
linker_info: LinkerInfo.type,
|
||||
short_name: str.type,
|
||||
version: [str.type, None] = None):
|
||||
"""
|
||||
Generate a platform-specific shared library name based for the given rule.
|
||||
"""
|
||||
if version == None:
|
||||
return linker_info.shared_library_name_format.format(short_name)
|
||||
else:
|
||||
return linker_info.shared_library_versioned_name_format.format(short_name, version)
|
||||
|
||||
def _parse_ext_macro(name: str.type) -> [(str.type, [str.type, None]), None]:
|
||||
"""
|
||||
Parse the `$(ext[ <version>])` macro from a user-specific library name,
|
||||
which expands to a platform-specific suffix (e.g. `.so`, `.dylib`). If an
|
||||
optional version argument is given (e.g. `$(ext 3.4)`) it expands to a
|
||||
platform-specific versioned suffix (e.g. `.so.3.4`, `.3.4.dylib`).
|
||||
"""
|
||||
|
||||
# If there's no macro, then there's nothing to do.
|
||||
if ".$(ext" not in name:
|
||||
return None
|
||||
expect(name.endswith(")"))
|
||||
|
||||
# Otherwise, attempt to parse out the macro.
|
||||
base, rest = name.split(".$(ext")
|
||||
|
||||
# If the macro is arg-less, then return w/o a version.
|
||||
if rest == ")":
|
||||
return (base, None)
|
||||
|
||||
# Otherwise, extract the version from the arg.
|
||||
expect(rest.startswith(" "))
|
||||
return (base, rest[1:-1])
|
||||
|
||||
def get_shared_library_name_for_param(linker_info: LinkerInfo.type, name: str.type):
|
||||
"""
|
||||
Format a user-provided shared library name, supporting v1's `$(ext)` suffix.
|
||||
"""
|
||||
parsed = _parse_ext_macro(name)
|
||||
if parsed != None:
|
||||
base, version = parsed
|
||||
name = get_shared_library_name(
|
||||
linker_info,
|
||||
base.removeprefix("lib"),
|
||||
version = version,
|
||||
)
|
||||
return name
|
||||
|
||||
# NOTE(agallagher): Does this belong in the native/shared_libraries.bzl?
|
||||
def get_default_shared_library_name(linker_info: LinkerInfo.type, label: "label"):
|
||||
"""
|
||||
Generate a platform-specific shared library name based for the given rule.
|
||||
"""
|
||||
|
||||
# TODO(T110378119): v1 doesn't use the cell/repo name, so we don't here for
|
||||
# initial compatiblity, but maybe we should?
|
||||
short_name = "{}_{}".format(_sanitize(label.package), _sanitize(label.name))
|
||||
return get_shared_library_name(linker_info, short_name)
|
||||
|
||||
def get_shared_library_name_linker_flags(linker_type: str.type, soname: str.type, flag_overrides: [SharedLibraryFlagOverrides.type, None] = None) -> [str.type]:
|
||||
"""
|
||||
Arguments to pass to the linker to set the given soname.
|
||||
"""
|
||||
if flag_overrides:
|
||||
shared_library_name_linker_flags_format = flag_overrides.shared_library_name_linker_flags_format
|
||||
else:
|
||||
shared_library_name_linker_flags_format = LINKERS[linker_type].shared_library_name_linker_flags_format
|
||||
|
||||
return [
|
||||
f.format(soname)
|
||||
for f in shared_library_name_linker_flags_format
|
||||
]
|
||||
|
||||
def get_shared_library_flags(linker_type: str.type, flag_overrides: [SharedLibraryFlagOverrides.type, None] = None) -> [str.type]:
|
||||
"""
|
||||
Arguments to pass to the linker to link a shared library.
|
||||
"""
|
||||
if flag_overrides:
|
||||
return flag_overrides.shared_library_flags
|
||||
|
||||
return LINKERS[linker_type].shared_library_flags
|
||||
|
||||
def get_link_whole_args(linker_type: str.type, inputs: ["artifact"]) -> [""]:
|
||||
"""
|
||||
Return linker args used to always link all the given inputs.
|
||||
"""
|
||||
|
||||
args = []
|
||||
|
||||
if linker_type == "gnu":
|
||||
args.append("-Wl,--whole-archive")
|
||||
args.extend(inputs)
|
||||
args.append("-Wl,--no-whole-archive")
|
||||
elif linker_type == "darwin":
|
||||
for inp in inputs:
|
||||
args.append("-Xlinker")
|
||||
args.append("-force_load")
|
||||
args.append("-Xlinker")
|
||||
args.append(inp)
|
||||
elif linker_type == "windows":
|
||||
for inp in inputs:
|
||||
args.append(inp)
|
||||
args.append("/WHOLEARCHIVE:" + inp.short_path)
|
||||
else:
|
||||
fail("Linker type {} not supported".format(linker_type))
|
||||
|
||||
return args
|
||||
|
||||
def get_objects_as_library_args(linker_type: str.type, objects: ["artifact"]) -> [""]:
|
||||
"""
|
||||
Return linker args used to link the given objects as a library.
|
||||
"""
|
||||
|
||||
args = []
|
||||
|
||||
if linker_type == "gnu":
|
||||
args.append("-Wl,--start-lib")
|
||||
args.extend(objects)
|
||||
args.append("-Wl,--end-lib")
|
||||
elif linker_type == "windows":
|
||||
args.extend(objects)
|
||||
else:
|
||||
fail("Linker type {} not supported".format(linker_type))
|
||||
|
||||
return args
|
||||
|
||||
def get_ignore_undefined_symbols_flags(linker_type: str.type) -> [str.type]:
|
||||
"""
|
||||
Return linker args used to suppress undefined symbol errors.
|
||||
"""
|
||||
|
||||
args = []
|
||||
|
||||
if linker_type == "gnu":
|
||||
args.append("-Wl,--allow-shlib-undefined")
|
||||
args.append("-Wl,--unresolved-symbols=ignore-all")
|
||||
elif linker_type == "darwin":
|
||||
args.append("-Wl,-flat_namespace,-undefined,suppress")
|
||||
else:
|
||||
fail("Linker type {} not supported".format(linker_type))
|
||||
|
||||
return args
|
||||
|
||||
def get_no_as_needed_shared_libs_flags(linker_type: str.type) -> [str.type]:
|
||||
"""
|
||||
Return linker args used to prevent linkers from dropping unused shared
|
||||
library dependencies from the e.g. DT_NEEDED tags of the link.
|
||||
"""
|
||||
|
||||
args = []
|
||||
|
||||
if linker_type == "gnu":
|
||||
args.append("-Wl,--no-as-needed")
|
||||
elif linker_type == "darwin":
|
||||
pass
|
||||
else:
|
||||
fail("Linker type {} not supported".format(linker_type))
|
||||
|
||||
return args
|
||||
|
||||
def get_output_flags(linker_type: str.type, output: "artifact") -> ["_argslike"]:
|
||||
if linker_type == "windows":
|
||||
return ["/Brepro", cmd_args(output.as_output(), format = "/OUT:{}")]
|
||||
else:
|
||||
return ["-o", output.as_output()]
|
||||
|
||||
def get_import_library(
|
||||
ctx: "context",
|
||||
linker_type: str.type,
|
||||
output_short_path: str.type) -> (["artifact", None], ["_argslike"]):
|
||||
if linker_type == "windows":
|
||||
import_library = ctx.actions.declare_output(output_short_path + ".imp.lib")
|
||||
return import_library, [cmd_args(import_library.as_output(), format = "/IMPLIB:{}")]
|
||||
else:
|
||||
return None, []
|
||||
949
vendor/cxx/tools/buck/prelude/cxx/omnibus.bzl
vendored
Normal file
949
vendor/cxx/tools/buck/prelude/cxx/omnibus.bzl
vendored
Normal file
|
|
@ -0,0 +1,949 @@
|
|||
# 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//:local_only.bzl", "link_cxx_binary_locally")
|
||||
load(
|
||||
"@prelude//cxx:link.bzl",
|
||||
"cxx_link_into_shared_library",
|
||||
"cxx_link_shared_library",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"LinkArgs",
|
||||
"LinkInfo",
|
||||
"LinkInfos",
|
||||
"LinkStyle",
|
||||
"Linkage",
|
||||
"LinkedObject",
|
||||
"SharedLibLinkable",
|
||||
"get_actual_link_style",
|
||||
"link_info_to_args",
|
||||
get_link_info_from_link_infos = "get_link_info",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkable_graph.bzl",
|
||||
"AnnotatedLinkableRoot",
|
||||
"LinkableGraph", # @unused Used as a type
|
||||
"LinkableNode",
|
||||
"LinkableRootAnnotation",
|
||||
"LinkableRootInfo",
|
||||
"get_deps_for_link",
|
||||
"get_link_info",
|
||||
"linkable_deps",
|
||||
"linkable_graph",
|
||||
)
|
||||
load(
|
||||
"@prelude//utils:graph_utils.bzl",
|
||||
"breadth_first_traversal_by",
|
||||
"topo_sort",
|
||||
)
|
||||
load("@prelude//utils:utils.bzl", "expect", "flatten", "value_or")
|
||||
load(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
load(
|
||||
":linker.bzl",
|
||||
"get_default_shared_library_name",
|
||||
"get_ignore_undefined_symbols_flags",
|
||||
"get_no_as_needed_shared_libs_flags",
|
||||
"get_shared_library_name",
|
||||
)
|
||||
load(
|
||||
":symbols.bzl",
|
||||
"create_global_symbols_version_script",
|
||||
"create_undefined_symbols_argsfile",
|
||||
"extract_global_syms",
|
||||
"extract_symbol_names",
|
||||
"extract_undefined_syms",
|
||||
)
|
||||
|
||||
OmnibusEnvironment = provider(fields = [
|
||||
"dummy_omnibus",
|
||||
"exclusions",
|
||||
"roots",
|
||||
"enable_explicit_roots",
|
||||
"prefer_stripped_objects",
|
||||
"shared_root_ld_flags",
|
||||
"force_hybrid_links",
|
||||
])
|
||||
|
||||
Disposition = enum("root", "excluded", "body")
|
||||
|
||||
OmnibusGraph = record(
|
||||
nodes = field({"label": LinkableNode.type}),
|
||||
# All potential root notes for an omnibus link (e.g. C++ libraries,
|
||||
# C++ Python extensions).
|
||||
roots = field({"label": AnnotatedLinkableRoot.type}),
|
||||
# All nodes that should be excluded from libomnibus.
|
||||
excluded = field({"label": None}),
|
||||
)
|
||||
|
||||
# Bookkeeping information used to setup omnibus link rules.
|
||||
OmnibusSpec = record(
|
||||
body = field({"label": None}, {}),
|
||||
excluded = field({"label": None}, {}),
|
||||
roots = field({"label": AnnotatedLinkableRoot.type}, {}),
|
||||
exclusion_roots = field(["label"]),
|
||||
# All link infos.
|
||||
link_infos = field({"label": LinkableNode.type}, {}),
|
||||
dispositions = field({"label": Disposition.type}),
|
||||
)
|
||||
|
||||
OmnibusPrivateRootProductCause = record(
|
||||
category = field(str.type),
|
||||
# Mis-assigned label
|
||||
label = field(["label", None], default = None),
|
||||
# Its actual disposiiton
|
||||
disposition = field([Disposition.type, None], default = None),
|
||||
)
|
||||
|
||||
OmnibusRootProduct = record(
|
||||
shared_library = field(LinkedObject.type),
|
||||
undefined_syms = field("artifact"),
|
||||
global_syms = field("artifact"),
|
||||
# If set, this explains why we had to use a private root for this product.
|
||||
# If unset, this means the root was a shared root we reused.
|
||||
private = field([OmnibusPrivateRootProductCause.type, None]),
|
||||
)
|
||||
|
||||
AnnotatedOmnibusRootProduct = record(
|
||||
product = field(OmnibusRootProduct.type),
|
||||
annotation = field([LinkableRootAnnotation.type, None]),
|
||||
)
|
||||
|
||||
SharedOmnibusRoot = record(
|
||||
product = field(OmnibusRootProduct.type),
|
||||
linker_type = field(str.type),
|
||||
required_body = field(["label"]),
|
||||
required_exclusions = field(["label"]),
|
||||
prefer_stripped_objects = field(bool.type),
|
||||
)
|
||||
|
||||
# The result of the omnibus link.
|
||||
OmnibusSharedLibraries = record(
|
||||
omnibus = field([LinkedObject.type, None], None),
|
||||
libraries = field({str.type: LinkedObject.type}, {}),
|
||||
roots = field({"label": AnnotatedOmnibusRootProduct.type}, {}),
|
||||
exclusion_roots = field(["label"]),
|
||||
excluded = field(["label"]),
|
||||
dispositions = field({"label": Disposition.type}),
|
||||
)
|
||||
|
||||
def get_omnibus_graph(graph: LinkableGraph.type, roots: {"label": AnnotatedLinkableRoot.type}, excluded: {"label": None}) -> OmnibusGraph.type:
|
||||
graph_nodes = graph.nodes.traverse()
|
||||
nodes = {}
|
||||
for node in filter(None, graph_nodes):
|
||||
if node.linkable:
|
||||
nodes[node.label] = node.linkable
|
||||
|
||||
for root, annotated in node.roots.items():
|
||||
# When building ou graph, we prefer un-annotated roots. Annotations
|
||||
# tell us if a root was discovered implicitly, but if was
|
||||
# discovered explicitly (in which case it has no annotation) then
|
||||
# we would rather record that, since the annotation wasn't
|
||||
# necessary.
|
||||
if annotated.annotation:
|
||||
roots.setdefault(root, annotated)
|
||||
else:
|
||||
roots[root] = annotated
|
||||
excluded.update(node.excluded)
|
||||
|
||||
return OmnibusGraph(nodes = nodes, roots = roots, excluded = excluded)
|
||||
|
||||
def get_roots(label: "label", deps: ["dependency"]) -> {"label": AnnotatedLinkableRoot.type}:
|
||||
roots = {}
|
||||
for dep in deps:
|
||||
if LinkableRootInfo in dep:
|
||||
roots[dep.label] = AnnotatedLinkableRoot(
|
||||
root = dep[LinkableRootInfo],
|
||||
annotation = LinkableRootAnnotation(dependent = label),
|
||||
)
|
||||
return roots
|
||||
|
||||
def get_excluded(deps: ["dependency"] = []) -> {"label": None}:
|
||||
excluded_nodes = {}
|
||||
for dep in deps:
|
||||
dep_info = linkable_graph(dep)
|
||||
if dep_info != None:
|
||||
excluded_nodes[dep_info.label] = None
|
||||
return excluded_nodes
|
||||
|
||||
def create_linkable_root(
|
||||
ctx: "context",
|
||||
link_infos: LinkInfos.type,
|
||||
name: [str.type, None],
|
||||
deps: ["dependency"],
|
||||
graph: LinkableGraph.type,
|
||||
create_shared_root: bool.type) -> LinkableRootInfo.type:
|
||||
# Only include dependencies that are linkable.
|
||||
deps = linkable_deps(deps)
|
||||
|
||||
def create_shared_root_impl():
|
||||
env = ctx.attrs._omnibus_environment
|
||||
if not env:
|
||||
return (None, OmnibusPrivateRootProductCause(category = "no_omnibus_environment"))
|
||||
|
||||
env = env[OmnibusEnvironment]
|
||||
prefer_stripped_objects = env.prefer_stripped_objects
|
||||
|
||||
if not create_shared_root:
|
||||
return (None, OmnibusPrivateRootProductCause(category = "no_shared_root"))
|
||||
|
||||
omnibus_graph = get_omnibus_graph(graph, {}, {})
|
||||
|
||||
inputs = []
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
linker_type = linker_info.type
|
||||
inputs.append(LinkInfo(
|
||||
pre_flags =
|
||||
get_no_as_needed_shared_libs_flags(linker_type) +
|
||||
get_ignore_undefined_symbols_flags(linker_type),
|
||||
))
|
||||
|
||||
inputs.append(get_link_info_from_link_infos(
|
||||
link_infos,
|
||||
prefer_stripped = prefer_stripped_objects,
|
||||
))
|
||||
|
||||
inputs.append(LinkInfo(linkables = [SharedLibLinkable(lib = env.dummy_omnibus)]))
|
||||
|
||||
env_excluded = _exclusions_from_env(env, omnibus_graph)
|
||||
|
||||
required_body = []
|
||||
required_exclusions = []
|
||||
|
||||
for dep in _link_deps(omnibus_graph.nodes, deps):
|
||||
node = omnibus_graph.nodes[dep]
|
||||
|
||||
actual_link_style = get_actual_link_style(
|
||||
LinkStyle("shared"),
|
||||
node.preferred_linkage,
|
||||
)
|
||||
|
||||
if actual_link_style != LinkStyle("shared"):
|
||||
inputs.append(
|
||||
get_link_info(
|
||||
node,
|
||||
actual_link_style,
|
||||
prefer_stripped = prefer_stripped_objects,
|
||||
),
|
||||
)
|
||||
continue
|
||||
|
||||
is_excluded = dep in env_excluded or dep in omnibus_graph.excluded
|
||||
is_root = dep in omnibus_graph.roots
|
||||
|
||||
if is_excluded or (_is_shared_only(node) and not is_root):
|
||||
inputs.append(get_link_info(node, actual_link_style, prefer_stripped = prefer_stripped_objects))
|
||||
required_exclusions.append(dep)
|
||||
continue
|
||||
|
||||
if is_root:
|
||||
dep_root = omnibus_graph.roots[dep].root.shared_root
|
||||
|
||||
if dep_root == None:
|
||||
# If we know our dep is a root, but our dep didn't know
|
||||
# that and didn't produce a shared root, then there is no
|
||||
# point in producing anything a reusable root here since it
|
||||
# wo'nt actually *be* reusable due to the root mismatch.
|
||||
return (None, OmnibusPrivateRootProductCause(category = "dep_no_shared_root", label = dep))
|
||||
|
||||
inputs.append(LinkInfo(pre_flags = [
|
||||
cmd_args(dep_root.product.shared_library.output),
|
||||
]))
|
||||
continue
|
||||
|
||||
required_body.append(dep)
|
||||
|
||||
output = ctx.actions.declare_output(
|
||||
"omnibus/" + value_or(name, get_default_shared_library_name(linker_info, ctx.label)),
|
||||
)
|
||||
|
||||
shared_library = cxx_link_shared_library(
|
||||
ctx,
|
||||
output,
|
||||
name = name,
|
||||
links = [LinkArgs(flags = env.shared_root_ld_flags), LinkArgs(infos = inputs)],
|
||||
category_suffix = "omnibus_root",
|
||||
identifier = name or output.short_path,
|
||||
)
|
||||
|
||||
return (
|
||||
SharedOmnibusRoot(
|
||||
product = OmnibusRootProduct(
|
||||
shared_library = shared_library,
|
||||
global_syms = extract_global_syms(ctx, shared_library.output, prefer_local = False),
|
||||
undefined_syms = extract_undefined_syms(ctx, shared_library.output, prefer_local = False),
|
||||
private = None,
|
||||
),
|
||||
required_body = required_body,
|
||||
required_exclusions = required_exclusions,
|
||||
prefer_stripped_objects = prefer_stripped_objects,
|
||||
linker_type = linker_type,
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
(shared_root, no_shared_root_reason) = create_shared_root_impl()
|
||||
|
||||
return LinkableRootInfo(
|
||||
name = name,
|
||||
link_infos = link_infos,
|
||||
deps = deps,
|
||||
shared_root = shared_root,
|
||||
no_shared_root_reason = no_shared_root_reason,
|
||||
)
|
||||
|
||||
def _exclusions_from_env(env: OmnibusEnvironment.type, graph: OmnibusGraph.type):
|
||||
excluded = [
|
||||
label
|
||||
for label, info in graph.nodes.items()
|
||||
if _is_excluded_by_environment(label, env) and not _is_static_only(info)
|
||||
]
|
||||
|
||||
return {label: None for label in excluded}
|
||||
|
||||
def _is_excluded_by_environment(label: "label", env: OmnibusEnvironment.type) -> bool.type:
|
||||
return label.raw_target() in env.exclusions
|
||||
|
||||
def _omnibus_soname(ctx):
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
return get_shared_library_name(linker_info, "omnibus")
|
||||
|
||||
def create_dummy_omnibus(ctx: "context", extra_ldflags: [""] = []) -> "artifact":
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
output = ctx.actions.declare_output(get_shared_library_name(linker_info, "omnibus-dummy"))
|
||||
cxx_link_shared_library(
|
||||
ctx,
|
||||
output,
|
||||
name = _omnibus_soname(ctx),
|
||||
links = [LinkArgs(flags = extra_ldflags)],
|
||||
category_suffix = "dummy_omnibus",
|
||||
)
|
||||
return output
|
||||
|
||||
def _link_deps(
|
||||
link_infos: {"label": LinkableNode.type},
|
||||
deps: ["label"]) -> ["label"]:
|
||||
"""
|
||||
Return transitive deps required to link dynamically against the given deps.
|
||||
This will following through deps of statically linked inputs and exported
|
||||
deps of everything else (see https://fburl.com/diffusion/rartsbkw from v1).
|
||||
"""
|
||||
|
||||
def find_deps(node: "label"):
|
||||
return get_deps_for_link(link_infos[node], LinkStyle("shared"))
|
||||
|
||||
return breadth_first_traversal_by(link_infos, deps, find_deps)
|
||||
|
||||
def all_deps(
|
||||
link_infos: {"label": LinkableNode.type},
|
||||
roots: ["label"]) -> ["label"]:
|
||||
"""
|
||||
Return all transitive deps from following the given nodes.
|
||||
"""
|
||||
|
||||
def find_transitive_deps(node: "label"):
|
||||
return link_infos[node].deps + link_infos[node].exported_deps
|
||||
|
||||
all_deps = breadth_first_traversal_by(link_infos, roots, find_transitive_deps)
|
||||
|
||||
return all_deps
|
||||
|
||||
def _create_root(
|
||||
ctx: "context",
|
||||
spec: OmnibusSpec.type,
|
||||
annotated_root_products,
|
||||
root: LinkableRootInfo.type,
|
||||
label: "label",
|
||||
link_deps: ["label"],
|
||||
omnibus: "artifact",
|
||||
extra_ldflags: [""] = [],
|
||||
prefer_stripped_objects: bool.type = False) -> OmnibusRootProduct.type:
|
||||
"""
|
||||
Link a root omnibus node.
|
||||
"""
|
||||
|
||||
linker_info = get_cxx_toolchain_info(ctx).linker_info
|
||||
linker_type = linker_info.type
|
||||
|
||||
if spec.body:
|
||||
if root.shared_root != None:
|
||||
# NOTE: This ignores ldflags. We rely on env.shared_root_ld_flags instead.
|
||||
private = _requires_private_root(
|
||||
root.shared_root,
|
||||
linker_type,
|
||||
prefer_stripped_objects,
|
||||
spec,
|
||||
)
|
||||
if private == None:
|
||||
return root.shared_root.product
|
||||
else:
|
||||
private = root.no_shared_root_reason
|
||||
else:
|
||||
private = OmnibusPrivateRootProductCause(category = "no_body")
|
||||
|
||||
inputs = []
|
||||
|
||||
# Since we're linking against a dummy omnibus which has no symbols, we need
|
||||
# to make sure the linker won't drop it from the link or complain about
|
||||
# missing symbols.
|
||||
inputs.append(LinkInfo(
|
||||
pre_flags =
|
||||
get_no_as_needed_shared_libs_flags(linker_type) +
|
||||
get_ignore_undefined_symbols_flags(linker_type),
|
||||
))
|
||||
|
||||
# add native target link input
|
||||
inputs.append(
|
||||
get_link_info_from_link_infos(
|
||||
root.link_infos,
|
||||
prefer_stripped = prefer_stripped_objects,
|
||||
),
|
||||
)
|
||||
|
||||
# Link to Omnibus
|
||||
if spec.body:
|
||||
inputs.append(LinkInfo(linkables = [SharedLibLinkable(lib = omnibus)]))
|
||||
|
||||
# Add deps of the root to the link line.
|
||||
for dep in link_deps:
|
||||
node = spec.link_infos[dep]
|
||||
actual_link_style = get_actual_link_style(
|
||||
LinkStyle("shared"),
|
||||
node.preferred_linkage,
|
||||
)
|
||||
|
||||
# If this dep needs to be linked statically, then link it directly.
|
||||
if actual_link_style != LinkStyle("shared"):
|
||||
inputs.append(get_link_info(
|
||||
node,
|
||||
actual_link_style,
|
||||
prefer_stripped = prefer_stripped_objects,
|
||||
))
|
||||
continue
|
||||
|
||||
# If this is another root.
|
||||
if dep in spec.roots:
|
||||
other_root = annotated_root_products[dep]
|
||||
|
||||
# TODO(cjhopman): This should be passing structured linkables
|
||||
inputs.append(LinkInfo(pre_flags = [cmd_args(other_root.product.shared_library.output)]))
|
||||
continue
|
||||
|
||||
# If this node is in omnibus, just add that to the link line.
|
||||
if dep in spec.body:
|
||||
continue
|
||||
|
||||
# At this point, this should definitely be an excluded node.
|
||||
expect(dep in spec.excluded, str(dep))
|
||||
|
||||
# We should have already handled statically linked nodes above.
|
||||
expect(actual_link_style == LinkStyle("shared"))
|
||||
inputs.append(get_link_info(node, actual_link_style))
|
||||
|
||||
output = ctx.actions.declare_output(value_or(root.name, get_default_shared_library_name(
|
||||
linker_info,
|
||||
label,
|
||||
)))
|
||||
|
||||
# link the rule
|
||||
shared_library = cxx_link_shared_library(
|
||||
ctx,
|
||||
output,
|
||||
name = root.name,
|
||||
links = [LinkArgs(flags = extra_ldflags), LinkArgs(infos = inputs)],
|
||||
category_suffix = "omnibus_root",
|
||||
identifier = root.name or output.short_path,
|
||||
# We prefer local execution because there are lot of cxx_link_omnibus_root
|
||||
# running simultaneously, so while their overall load is reasonable,
|
||||
# their peak execution load is very high.
|
||||
prefer_local = True,
|
||||
)
|
||||
|
||||
return OmnibusRootProduct(
|
||||
shared_library = shared_library,
|
||||
global_syms = extract_global_syms(
|
||||
ctx,
|
||||
shared_library.output,
|
||||
# Same as above.
|
||||
prefer_local = True,
|
||||
),
|
||||
undefined_syms = extract_undefined_syms(
|
||||
ctx,
|
||||
shared_library.output,
|
||||
# Same as above.
|
||||
prefer_local = True,
|
||||
),
|
||||
private = private,
|
||||
)
|
||||
|
||||
def _requires_private_root(
|
||||
candidate: SharedOmnibusRoot.type,
|
||||
linker_type: str.type,
|
||||
prefer_stripped_objects: bool.type,
|
||||
spec: OmnibusSpec.type) -> [OmnibusPrivateRootProductCause.type, None]:
|
||||
if candidate.linker_type != linker_type:
|
||||
return OmnibusPrivateRootProductCause(category = "linker_type")
|
||||
|
||||
if candidate.prefer_stripped_objects != prefer_stripped_objects:
|
||||
return OmnibusPrivateRootProductCause(category = "prefer_stripped_objects")
|
||||
|
||||
for required_body in candidate.required_body:
|
||||
if not (required_body in spec.body and required_body not in spec.roots):
|
||||
return OmnibusPrivateRootProductCause(
|
||||
category = "required_body",
|
||||
label = required_body,
|
||||
disposition = spec.dispositions[required_body],
|
||||
)
|
||||
|
||||
for required_exclusion in candidate.required_exclusions:
|
||||
if not required_exclusion in spec.excluded:
|
||||
return OmnibusPrivateRootProductCause(
|
||||
category = "required_exclusion",
|
||||
label = required_exclusion,
|
||||
disposition = spec.dispositions[required_exclusion],
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def _extract_global_symbols_from_link_args(
|
||||
ctx: "context",
|
||||
name: str.type,
|
||||
link_args: [["artifact", "resolved_macro", "cmd_args", str.type]],
|
||||
prefer_local: bool.type = False) -> "artifact":
|
||||
"""
|
||||
Extract global symbols explicitly set in the given linker args (e.g.
|
||||
`-Wl,--export-dynamic-symbol=<sym>`).
|
||||
"""
|
||||
|
||||
# TODO(T110378137): This is ported from D24065414, but it might make sense
|
||||
# to explicitly tell Buck about the global symbols, rather than us trying to
|
||||
# extract it from linker flags (which is brittle).
|
||||
output = ctx.actions.declare_output(name)
|
||||
|
||||
# We intentionally drop the artifacts referenced in the args when generating
|
||||
# the argsfile -- we just want to parse out symbol name flags and don't need
|
||||
# to materialize artifacts to do this.
|
||||
argsfile, _ = ctx.actions.write(name + ".args", link_args, allow_args = True)
|
||||
|
||||
# TODO(T110378133): Make this work with other platforms.
|
||||
param = "--export-dynamic-symbol"
|
||||
pattern = "\\(-Wl,\\)\\?{}[,=]\\([^,]*\\)".format(param)
|
||||
|
||||
# Used sed/grep to filter the symbol name from the relevant flags.
|
||||
# TODO(T110378130): As is the case in v1, we don't properly extract flags
|
||||
# from argsfiles embedded in existing args.
|
||||
script = (
|
||||
"set -euo pipefail; " +
|
||||
'cat "$@" | (grep -- \'{0}\' || [[ $? == 1 ]]) | sed \'s|{0}|\\2|\' | LC_ALL=C sort -S 10% -u > {{}}'
|
||||
.format(pattern)
|
||||
)
|
||||
ctx.actions.run(
|
||||
[
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
cmd_args(output.as_output(), format = script),
|
||||
"",
|
||||
argsfile,
|
||||
],
|
||||
category = "omnibus_global_symbol_flags",
|
||||
prefer_local = prefer_local,
|
||||
)
|
||||
return output
|
||||
|
||||
def _create_global_symbols_version_script(
|
||||
ctx: "context",
|
||||
roots: [AnnotatedOmnibusRootProduct.type],
|
||||
excluded: ["artifact"],
|
||||
link_args: [["artifact", "resolved_macro", "cmd_args", str.type]]) -> "artifact":
|
||||
"""
|
||||
Generate a version script exporting symbols from from the given objects and
|
||||
link args.
|
||||
"""
|
||||
|
||||
# Get global symbols from roots. We set a rule to do this per-rule, as
|
||||
# using a single rule to process all roots adds overhead to the critical
|
||||
# path of incremental flows (e.g. that only update a single root).
|
||||
global_symbols_files = [
|
||||
root.product.global_syms
|
||||
for root in roots
|
||||
]
|
||||
|
||||
# TODO(T110378126): Processing all excluded libs together may get expensive.
|
||||
# We should probably split this up and operate on individual libs.
|
||||
if excluded:
|
||||
global_symbols_files.append(extract_symbol_names(
|
||||
ctx,
|
||||
"__excluded_libs__.global_syms.txt",
|
||||
excluded,
|
||||
dynamic = True,
|
||||
global_only = True,
|
||||
category = "omnibus_global_syms_excluded_libs",
|
||||
))
|
||||
|
||||
# Extract explicitly globalized symbols from linker args.
|
||||
global_symbols_files.append(_extract_global_symbols_from_link_args(
|
||||
ctx,
|
||||
"__global_symbols_from_args__.txt",
|
||||
link_args,
|
||||
))
|
||||
|
||||
all_global_symbols_files = ctx.actions.write("__global_symbols__.symbols", global_symbols_files)
|
||||
all_global_symbols_files = cmd_args(all_global_symbols_files).hidden(global_symbols_files)
|
||||
|
||||
return create_global_symbols_version_script(
|
||||
actions = ctx.actions,
|
||||
name = "__global_symbols__.vers",
|
||||
category = "omnibus_version_script",
|
||||
symbol_files = global_symbols_files,
|
||||
)
|
||||
|
||||
def _is_static_only(info: LinkableNode.type) -> bool.type:
|
||||
"""
|
||||
Return whether this can only be linked statically.
|
||||
"""
|
||||
return info.preferred_linkage == Linkage("static")
|
||||
|
||||
def _is_shared_only(info: LinkableNode.type) -> bool.type:
|
||||
"""
|
||||
Return whether this can only use shared linking
|
||||
"""
|
||||
return info.preferred_linkage == Linkage("shared")
|
||||
|
||||
def _create_omnibus(
|
||||
ctx: "context",
|
||||
spec: OmnibusSpec.type,
|
||||
annotated_root_products,
|
||||
extra_ldflags: [""] = [],
|
||||
prefer_stripped_objects: bool.type = False) -> LinkedObject.type:
|
||||
inputs = []
|
||||
|
||||
# Undefined symbols roots...
|
||||
non_body_root_undefined_syms = [
|
||||
root.product.undefined_syms
|
||||
for label, root in annotated_root_products.items()
|
||||
if label not in spec.body
|
||||
]
|
||||
if non_body_root_undefined_syms:
|
||||
argsfile = create_undefined_symbols_argsfile(
|
||||
actions = ctx.actions,
|
||||
name = "__undefined_symbols__.argsfile",
|
||||
symbol_files = non_body_root_undefined_syms,
|
||||
category = "omnibus_undefined_symbols",
|
||||
)
|
||||
inputs.append(LinkInfo(pre_flags = [
|
||||
cmd_args(argsfile, format = "@{}"),
|
||||
]))
|
||||
|
||||
# Process all body nodes.
|
||||
deps = {}
|
||||
global_symbols_link_args = []
|
||||
for label in spec.body:
|
||||
# If this body node is a root, add the it's output to the link.
|
||||
if label in spec.roots:
|
||||
root = annotated_root_products[label].product
|
||||
|
||||
# TODO(cjhopman): This should be passing structured linkables
|
||||
inputs.append(LinkInfo(pre_flags = [cmd_args(root.shared_library.output)]))
|
||||
continue
|
||||
|
||||
node = spec.link_infos[label]
|
||||
|
||||
# Otherwise add in the static input for this node.
|
||||
actual_link_style = get_actual_link_style(
|
||||
LinkStyle("static_pic"),
|
||||
node.preferred_linkage,
|
||||
)
|
||||
expect(actual_link_style == LinkStyle("static_pic"))
|
||||
body_input = get_link_info(
|
||||
node,
|
||||
actual_link_style,
|
||||
prefer_stripped = prefer_stripped_objects,
|
||||
)
|
||||
inputs.append(body_input)
|
||||
global_symbols_link_args.append(link_info_to_args(body_input))
|
||||
|
||||
# Keep track of all first order deps of the omnibus monolith.
|
||||
for dep in node.deps + node.exported_deps:
|
||||
if dep not in spec.body:
|
||||
expect(dep in spec.excluded)
|
||||
deps[dep] = None
|
||||
|
||||
# Now add deps of omnibus to the link
|
||||
for label in _link_deps(spec.link_infos, deps.keys()):
|
||||
node = spec.link_infos[label]
|
||||
actual_link_style = get_actual_link_style(
|
||||
LinkStyle("shared"),
|
||||
node.preferred_linkage,
|
||||
)
|
||||
inputs.append(get_link_info(
|
||||
node,
|
||||
actual_link_style,
|
||||
prefer_stripped = prefer_stripped_objects,
|
||||
))
|
||||
|
||||
toolchain_info = get_cxx_toolchain_info(ctx)
|
||||
linker_info = toolchain_info.linker_info
|
||||
|
||||
# Add global symbols version script.
|
||||
# FIXME(agallagher): Support global symbols for darwin.
|
||||
if linker_info.type != "darwin":
|
||||
global_sym_vers = _create_global_symbols_version_script(
|
||||
ctx,
|
||||
# Extract symols from roots...
|
||||
annotated_root_products.values(),
|
||||
# ... and the shared libs from excluded nodes.
|
||||
[
|
||||
shared_lib.output
|
||||
for label in spec.excluded
|
||||
for shared_lib in spec.link_infos[label].shared_libs.values()
|
||||
],
|
||||
# Extract explicit global symbol names from flags in all body link args.
|
||||
global_symbols_link_args,
|
||||
)
|
||||
inputs.append(LinkInfo(pre_flags = [
|
||||
"-Wl,--version-script",
|
||||
global_sym_vers,
|
||||
]))
|
||||
|
||||
soname = _omnibus_soname(ctx)
|
||||
hybrid = use_hybrid_links_for_libomnibus(ctx)
|
||||
return cxx_link_into_shared_library(
|
||||
ctx,
|
||||
soname,
|
||||
links = [LinkArgs(flags = extra_ldflags), LinkArgs(infos = inputs)],
|
||||
category_suffix = "omnibus",
|
||||
# TODO(T110378138): As with static C++ links, omnibus links are
|
||||
# currently too large for RE, so run them locally for now (e.g.
|
||||
# https://fb.prod.workplace.com/groups/buck2dev/posts/2953023738319012/).
|
||||
# NB: We explicitly pass a value here to override
|
||||
# the linker_info.link_libraries_locally that's used by `cxx_link_into_shared_library`.
|
||||
# That's because we do not want to apply the linking behavior universally,
|
||||
# just use it for omnibus.
|
||||
prefer_local = False if hybrid else link_cxx_binary_locally(ctx, toolchain_info),
|
||||
link_weight = linker_info.link_weight,
|
||||
enable_distributed_thinlto = ctx.attrs.enable_distributed_thinlto,
|
||||
identifier = soname,
|
||||
force_full_hybrid_if_capable = hybrid,
|
||||
)
|
||||
|
||||
def _build_omnibus_spec(
|
||||
ctx: "context",
|
||||
graph: OmnibusGraph.type) -> OmnibusSpec.type:
|
||||
"""
|
||||
Divide transitive deps into excluded, root, and body nodes, which we'll
|
||||
use to link the various parts of omnibus.
|
||||
"""
|
||||
|
||||
exclusion_roots = graph.excluded.keys() + _implicit_exclusion_roots(ctx, graph)
|
||||
|
||||
# Build up the set of all nodes that we have to exclude from omnibus linking
|
||||
# (any node that is excluded will exclude all it's transitive deps).
|
||||
excluded = {
|
||||
label: None
|
||||
for label in all_deps(
|
||||
graph.nodes,
|
||||
exclusion_roots,
|
||||
)
|
||||
}
|
||||
|
||||
# Finalized root nodes, after removing any excluded roots.
|
||||
roots = {
|
||||
label: root
|
||||
for label, root in graph.roots.items()
|
||||
if label not in excluded
|
||||
}
|
||||
|
||||
# Find the deps of the root nodes. These form the roots of the nodes
|
||||
# included in the omnibus link.
|
||||
first_order_root_deps = []
|
||||
for label in _link_deps(graph.nodes, flatten([r.root.deps for r in roots.values()])):
|
||||
# We only consider deps which aren't *only* statically linked.
|
||||
if _is_static_only(graph.nodes[label]):
|
||||
continue
|
||||
|
||||
# Don't include a root's dep onto another root.
|
||||
if label in roots:
|
||||
continue
|
||||
first_order_root_deps.append(label)
|
||||
|
||||
# All body nodes. These included all non-excluded body nodes and any non-
|
||||
# excluded roots which are reachable by these body nodes (since they will
|
||||
# need to be put on the link line).
|
||||
body = {
|
||||
label: None
|
||||
for label in all_deps(graph.nodes, first_order_root_deps)
|
||||
if label not in excluded
|
||||
}
|
||||
|
||||
dispositions = {}
|
||||
|
||||
for node, info in graph.nodes.items():
|
||||
if _is_static_only(info):
|
||||
continue
|
||||
|
||||
if node in roots:
|
||||
dispositions[node] = Disposition("root")
|
||||
continue
|
||||
|
||||
if node in excluded:
|
||||
dispositions[node] = Disposition("excluded")
|
||||
continue
|
||||
|
||||
if node in body:
|
||||
dispositions[node] = Disposition("body")
|
||||
continue
|
||||
|
||||
fail("Node was not assigned: {}".format(node))
|
||||
|
||||
return OmnibusSpec(
|
||||
excluded = excluded,
|
||||
roots = roots,
|
||||
body = body,
|
||||
link_infos = graph.nodes,
|
||||
exclusion_roots = exclusion_roots,
|
||||
dispositions = dispositions,
|
||||
)
|
||||
|
||||
def _implicit_exclusion_roots(ctx: "context", graph: OmnibusGraph.type) -> ["label"]:
|
||||
env = ctx.attrs._omnibus_environment
|
||||
if not env:
|
||||
return []
|
||||
env = env[OmnibusEnvironment]
|
||||
|
||||
return [
|
||||
label
|
||||
for label, info in graph.nodes.items()
|
||||
if _is_excluded_by_environment(label, env) or (_is_shared_only(info) and (label not in graph.roots))
|
||||
]
|
||||
|
||||
def _ordered_roots(
|
||||
spec: OmnibusSpec.type) -> [("label", AnnotatedLinkableRoot.type, ["label"])]:
|
||||
"""
|
||||
Return information needed to link the roots nodes in topo-sorted order.
|
||||
"""
|
||||
|
||||
# Calculate all deps each root node needs to link against.
|
||||
link_deps = {}
|
||||
for label, root in spec.roots.items():
|
||||
link_deps[label] = _link_deps(spec.link_infos, root.root.deps)
|
||||
|
||||
# Used the link deps to create the graph of root nodes.
|
||||
root_graph = {
|
||||
node: [dep for dep in deps if dep in spec.roots]
|
||||
for node, deps in link_deps.items()
|
||||
}
|
||||
|
||||
ordered_roots = []
|
||||
|
||||
# Emit the root link info as a topo-sorted list, so that we generate root link
|
||||
# rules for dependencies before their dependents.
|
||||
for label in topo_sort(root_graph):
|
||||
root = spec.roots[label]
|
||||
deps = link_deps[label]
|
||||
ordered_roots.append((label, root, deps))
|
||||
|
||||
return ordered_roots
|
||||
|
||||
def create_omnibus_libraries(
|
||||
ctx: "context",
|
||||
graph: OmnibusGraph.type,
|
||||
extra_ldflags: [""] = [],
|
||||
prefer_stripped_objects: bool.type = False) -> OmnibusSharedLibraries.type:
|
||||
spec = _build_omnibus_spec(ctx, graph)
|
||||
|
||||
# Create dummy omnibus
|
||||
dummy_omnibus = create_dummy_omnibus(ctx, extra_ldflags)
|
||||
|
||||
libraries = {}
|
||||
root_products = {}
|
||||
|
||||
# Link all root nodes against the dummy libomnibus lib.
|
||||
for label, annotated_root, link_deps in _ordered_roots(spec):
|
||||
product = _create_root(
|
||||
ctx,
|
||||
spec,
|
||||
root_products,
|
||||
annotated_root.root,
|
||||
label,
|
||||
link_deps,
|
||||
dummy_omnibus,
|
||||
extra_ldflags,
|
||||
prefer_stripped_objects,
|
||||
)
|
||||
if annotated_root.root.name != None:
|
||||
libraries[annotated_root.root.name] = product.shared_library
|
||||
root_products[label] = AnnotatedOmnibusRootProduct(
|
||||
product = product,
|
||||
annotation = annotated_root.annotation,
|
||||
)
|
||||
|
||||
# If we have body nodes, then link them into the monolithic libomnibus.so.
|
||||
omnibus = None
|
||||
if spec.body:
|
||||
omnibus = _create_omnibus(
|
||||
ctx,
|
||||
spec,
|
||||
root_products,
|
||||
extra_ldflags,
|
||||
prefer_stripped_objects,
|
||||
)
|
||||
libraries[_omnibus_soname(ctx)] = omnibus
|
||||
|
||||
# For all excluded nodes, just add their regular shared libs.
|
||||
for label in spec.excluded:
|
||||
for name, lib in spec.link_infos[label].shared_libs.items():
|
||||
libraries[name] = lib
|
||||
|
||||
return OmnibusSharedLibraries(
|
||||
omnibus = omnibus,
|
||||
libraries = libraries,
|
||||
roots = root_products,
|
||||
exclusion_roots = spec.exclusion_roots,
|
||||
excluded = spec.excluded.keys(),
|
||||
dispositions = spec.dispositions,
|
||||
)
|
||||
|
||||
def is_known_omnibus_root(ctx: "context") -> bool.type:
|
||||
env = ctx.attrs._omnibus_environment
|
||||
if not env:
|
||||
return False
|
||||
|
||||
env = env[OmnibusEnvironment]
|
||||
|
||||
if not env.enable_explicit_roots:
|
||||
return False
|
||||
|
||||
if ctx.attrs.supports_python_dlopen != None:
|
||||
return ctx.attrs.supports_python_dlopen
|
||||
|
||||
if ctx.label.raw_target() in env.roots:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def explicit_roots_enabled(ctx: "context") -> bool.type:
|
||||
env = ctx.attrs._omnibus_environment
|
||||
if not env:
|
||||
return False
|
||||
return env[OmnibusEnvironment].enable_explicit_roots
|
||||
|
||||
def use_hybrid_links_for_libomnibus(ctx: "context") -> bool.type:
|
||||
env = ctx.attrs._omnibus_environment
|
||||
if not env:
|
||||
return False
|
||||
return env[OmnibusEnvironment].force_hybrid_links
|
||||
|
||||
def omnibus_environment_attr():
|
||||
default = select({
|
||||
"DEFAULT": "fbcode//buck2/platform/omnibus:omnibus_environment",
|
||||
"fbcode//buck2/platform/omnibus:do_not_inject_omnibus_environment": None,
|
||||
})
|
||||
|
||||
# In open source, we don't want to use omnibus
|
||||
default = None # @oss-enable
|
||||
|
||||
return attrs.option(attrs.dep(), default = default)
|
||||
16
vendor/cxx/tools/buck/prelude/cxx/platform.bzl
vendored
Normal file
16
vendor/cxx/tools/buck/prelude/cxx/platform.bzl
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# 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//utils:platform_flavors_util.bzl", "by_platform")
|
||||
load(":cxx_context.bzl", "get_cxx_platform_info")
|
||||
|
||||
def cxx_by_platform(ctx: "context", xs: [(str.type, "_a")]) -> ["_a"]:
|
||||
cxx_platform_info = get_cxx_platform_info(ctx)
|
||||
platform_flavors = [cxx_platform_info.name]
|
||||
if cxx_platform_info.deps_aliases:
|
||||
platform_flavors.extend(cxx_platform_info.deps_aliases)
|
||||
return by_platform(platform_flavors, xs)
|
||||
276
vendor/cxx/tools/buck/prelude/cxx/prebuilt_cxx_library_group.bzl
vendored
Normal file
276
vendor/cxx/tools/buck/prelude/cxx/prebuilt_cxx_library_group.bzl
vendored
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
# 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//cxx:preprocessor.bzl",
|
||||
"CPreprocessor",
|
||||
"cxx_inherited_preprocessor_infos",
|
||||
"cxx_merge_cpreprocessors",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_groups.bzl",
|
||||
"merge_link_group_lib_info",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"LinkInfo",
|
||||
"LinkInfos",
|
||||
"LinkStyle",
|
||||
"Linkage",
|
||||
"LinkedObject",
|
||||
"create_merged_link_info",
|
||||
"get_actual_link_style",
|
||||
"get_link_styles_for_linkage",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkable_graph.bzl",
|
||||
"create_linkable_graph",
|
||||
"create_linkable_graph_node",
|
||||
"create_linkable_node",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:shared_libraries.bzl",
|
||||
"SharedLibraryInfo",
|
||||
"create_shared_libraries",
|
||||
"merge_shared_libraries",
|
||||
)
|
||||
load("@prelude//utils:utils.bzl", "expect", "flatten_dict")
|
||||
load(
|
||||
":cxx_library_utility.bzl",
|
||||
"cxx_inherited_link_info",
|
||||
)
|
||||
|
||||
def _linkage(ctx: "context") -> Linkage.type:
|
||||
"""
|
||||
Construct the preferred linkage to use for the given prebuilt library.
|
||||
"""
|
||||
|
||||
# If we have both shared and static libs, we support any linkage.
|
||||
if (ctx.attrs.shared_link and
|
||||
(ctx.attrs.static_link or ctx.attrs.static_pic_link)):
|
||||
return Linkage("any")
|
||||
|
||||
# Otherwise, if we have a shared library, we only support shared linkage.
|
||||
if ctx.attrs.shared_link:
|
||||
return Linkage("shared")
|
||||
|
||||
# Otherwise, if we have a static library, we only support static linkage.
|
||||
if ctx.attrs.static_link or ctx.attrs.static_pic_link:
|
||||
return Linkage("static")
|
||||
|
||||
# Otherwise, header only libs use any linkage.
|
||||
return Linkage("any")
|
||||
|
||||
def _parse_macro(
|
||||
arg: str.type) -> [(str.type, str.type, str.type), None]:
|
||||
"""
|
||||
Parse a lib reference macro (e.g. `$(lib 0)`, `$(rel-lib libfoo.so)`) into
|
||||
the format string used to format the arg, the name of the macro parsed, and
|
||||
the argument passed to the macro.
|
||||
"""
|
||||
|
||||
# TODO(T110378124): This is obviously not ideal and longer-term we should
|
||||
# probably come up with a better UI for this rule or properly support these
|
||||
# macros.
|
||||
|
||||
# If there's not macro, then there's nothing to do.
|
||||
if "$(" not in arg:
|
||||
return None
|
||||
|
||||
# Extract the macro name and it's arg out of the string. Also, create a
|
||||
# format string with the remaining parts which can be used to format to an
|
||||
# actual arg. This is pretty ugly, but we don't have too complex a case to
|
||||
# support (e.g. a single macro with a single arg).
|
||||
start, rest = arg.split("$(")
|
||||
pos = rest.find(" ")
|
||||
macro = rest[:pos]
|
||||
rest = rest[pos + 1:]
|
||||
pos = rest.find(")")
|
||||
param = rest[:pos]
|
||||
end = rest[pos + 1:]
|
||||
return start + "{}" + end, macro, param
|
||||
|
||||
def _get_static_link_args(
|
||||
libs: ["artifact"],
|
||||
args: [str.type]) -> [""]:
|
||||
"""
|
||||
Format a pair of static link string args and static libs into args to be
|
||||
passed to the link, by resolving macro references to libraries.
|
||||
"""
|
||||
|
||||
link = []
|
||||
|
||||
for arg in args:
|
||||
res = _parse_macro(arg)
|
||||
if res != None:
|
||||
# Macros in the static link line are indexes to the list of static
|
||||
# archives.
|
||||
fmt, macro, param = res
|
||||
expect(macro == "lib")
|
||||
link.append(cmd_args(libs[int(param)], format = fmt))
|
||||
else:
|
||||
link.append(arg)
|
||||
|
||||
return link
|
||||
|
||||
def _get_shared_link_args(
|
||||
shared_libs: {str.type: "artifact"},
|
||||
args: [str.type]) -> [""]:
|
||||
"""
|
||||
Format a pair of shared link string args and shared libs into args to be
|
||||
passed to the link, by resolving macro references to libraries.
|
||||
"""
|
||||
|
||||
link = []
|
||||
|
||||
for arg in args:
|
||||
res = _parse_macro(arg)
|
||||
if res != None:
|
||||
# Macros in the shared link line are named references to the map
|
||||
# of all shared libs.
|
||||
fmt, macro, lib_name = res
|
||||
expect(macro in ("lib", "rel-lib"))
|
||||
shared_lib = shared_libs[lib_name]
|
||||
if macro == "lib":
|
||||
link.append(cmd_args(shared_lib, format = fmt))
|
||||
elif macro == "rel-lib":
|
||||
# rel-lib means link-without-soname.
|
||||
link.append(cmd_args(shared_lib, format = "-L{}").parent())
|
||||
link.append("-l" + shared_lib.basename.removeprefix("lib").removesuffix(shared_lib.extension))
|
||||
else:
|
||||
link.append(arg)
|
||||
|
||||
return link
|
||||
|
||||
# The `prebuilt_cxx_library_group` rule is meant to provide fine user control for
|
||||
# how a group libraries of libraries are added to the link line and was added for
|
||||
# `fbcode//third-party-buck/platform009/build/IntelComposerXE:mkl_lp64_iomp`, which
|
||||
# includes libraries with dep cycles, and so must be linked together with flags
|
||||
# like `--start-group`/`--end-group`.
|
||||
#
|
||||
# The link arguments for the various link styles are specified by pair of string
|
||||
# arguments with macros referenceing a collection of libraries:
|
||||
#
|
||||
# - For static link styles, the string link args (e.g. specific in `static_link`)
|
||||
# contain macros of the form `$(lib <number>)`, where the number is an index
|
||||
# into the corresponding list of static libraries artifacts (e.g. specified in
|
||||
# `static_libs`). For example:
|
||||
#
|
||||
# static_link = ["-Wl,--start-group", "$(lib 0)", "$(lib 1)", "-Wl,--end-group"],
|
||||
# static_libs = ["libfoo1.a", "libfoo2.a"],
|
||||
#
|
||||
# - For shared linking, the string link args contain macros of the form
|
||||
# `$(lib <name>)` or `$(rel-lib <name>)`, where the name is key for shared
|
||||
# libraries specified in `shared_libs` or `provided_shared_libs`. The
|
||||
# `lib` macro examples to the full path of the shared library, whereas the
|
||||
# `rel-lib` macro expands to `-L<dirname> -l<name>` of the library and is
|
||||
# meant to be used in situations where shared library does not contain an
|
||||
# embedded soname. For example:
|
||||
#
|
||||
# shared_link = ["$(lib libfoo1.so)", "$(rel-lib libfoo2.so)"],
|
||||
# shared_libs = {
|
||||
# "libfoo1.so": "lib/libfoo1.so",
|
||||
# "libfoo2.so": "lib/libfoo2.so",
|
||||
# },
|
||||
#
|
||||
def prebuilt_cxx_library_group_impl(ctx: "context") -> ["provider"]:
|
||||
providers = []
|
||||
|
||||
deps = ctx.attrs.deps
|
||||
exported_deps = ctx.attrs.exported_deps
|
||||
|
||||
# Figure out preprocessor stuff
|
||||
args = []
|
||||
args.extend(ctx.attrs.exported_preprocessor_flags)
|
||||
for inc_dir in ctx.attrs.include_dirs:
|
||||
args += ["-isystem", inc_dir]
|
||||
preprocessor = CPreprocessor(args = args)
|
||||
inherited_pp_info = cxx_inherited_preprocessor_infos(exported_deps)
|
||||
providers.append(cxx_merge_cpreprocessors(ctx, [preprocessor], inherited_pp_info))
|
||||
|
||||
# Figure out all the link styles we'll be building archives/shlibs for.
|
||||
preferred_linkage = _linkage(ctx)
|
||||
|
||||
inherited_non_exported_link = cxx_inherited_link_info(ctx, deps)
|
||||
inherited_exported_link = cxx_inherited_link_info(ctx, exported_deps)
|
||||
|
||||
# Gather link infos, outputs, and shared libs for effective link style.
|
||||
outputs = {}
|
||||
libraries = {}
|
||||
solibs = {}
|
||||
for link_style in get_link_styles_for_linkage(preferred_linkage):
|
||||
outs = []
|
||||
if link_style == LinkStyle("static"):
|
||||
outs.extend(ctx.attrs.static_libs)
|
||||
args = _get_static_link_args(ctx.attrs.static_libs, ctx.attrs.static_link)
|
||||
elif link_style == LinkStyle("static_pic"):
|
||||
outs.extend(ctx.attrs.static_pic_libs)
|
||||
args = _get_static_link_args(ctx.attrs.static_pic_libs, ctx.attrs.static_pic_link)
|
||||
else: # shared
|
||||
outs.extend(ctx.attrs.shared_libs.values())
|
||||
args = _get_shared_link_args(
|
||||
flatten_dict([ctx.attrs.shared_libs, ctx.attrs.provided_shared_libs]),
|
||||
ctx.attrs.shared_link,
|
||||
)
|
||||
solibs.update({n: LinkedObject(output = lib) for n, lib in ctx.attrs.shared_libs.items()})
|
||||
outputs[link_style] = outs
|
||||
|
||||
# TODO(cjhopman): This is hiding static and shared libs in opaque
|
||||
# linker args, it should instead be constructing structured LinkInfo
|
||||
# instances
|
||||
libraries[link_style] = LinkInfos(default = LinkInfo(
|
||||
name = repr(ctx.label),
|
||||
pre_flags = args,
|
||||
))
|
||||
|
||||
# Collect per-link-style default outputs.
|
||||
default_outputs = {}
|
||||
for link_style in LinkStyle:
|
||||
actual_link_style = get_actual_link_style(link_style, preferred_linkage)
|
||||
default_outputs[link_style] = outputs[actual_link_style]
|
||||
providers.append(DefaultInfo(default_outputs = default_outputs[LinkStyle("static")]))
|
||||
|
||||
# Provider for native link.
|
||||
providers.append(create_merged_link_info(
|
||||
ctx,
|
||||
libraries,
|
||||
preferred_linkage = preferred_linkage,
|
||||
# Export link info from our (non-exported) deps (e.g. when we're linking
|
||||
# statically).
|
||||
deps = [inherited_non_exported_link],
|
||||
# Export link info from our (exported) deps.
|
||||
exported_deps = [inherited_exported_link],
|
||||
))
|
||||
|
||||
# Propagate shared libraries up the tree.
|
||||
providers.append(merge_shared_libraries(
|
||||
ctx.actions,
|
||||
create_shared_libraries(ctx, solibs),
|
||||
filter(None, [x.get(SharedLibraryInfo) for x in deps + exported_deps]),
|
||||
))
|
||||
|
||||
# Create, augment and provide the linkable graph.
|
||||
linkable_graph = create_linkable_graph(
|
||||
ctx,
|
||||
node = create_linkable_graph_node(
|
||||
ctx,
|
||||
linkable_node = create_linkable_node(
|
||||
ctx = ctx,
|
||||
deps = deps,
|
||||
exported_deps = exported_deps,
|
||||
preferred_linkage = preferred_linkage,
|
||||
link_infos = libraries,
|
||||
shared_libs = solibs,
|
||||
),
|
||||
),
|
||||
deps = deps + exported_deps,
|
||||
)
|
||||
providers.append(linkable_graph)
|
||||
|
||||
providers.append(merge_link_group_lib_info(deps = deps + exported_deps))
|
||||
|
||||
return providers
|
||||
363
vendor/cxx/tools/buck/prelude/cxx/preprocessor.bzl
vendored
Normal file
363
vendor/cxx/tools/buck/prelude/cxx/preprocessor.bzl
vendored
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
# 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//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",
|
||||
"as_raw_headers",
|
||||
"cxx_attr_exported_header_style",
|
||||
"cxx_attr_exported_headers",
|
||||
"cxx_attr_headers",
|
||||
"prepare_headers",
|
||||
)
|
||||
load(":platform.bzl", "cxx_by_platform")
|
||||
|
||||
CPreprocessor = record(
|
||||
# The arguments, [arglike things]
|
||||
args = field([""], []),
|
||||
# Header specs
|
||||
headers = field([CHeader.type], []),
|
||||
# Those should be mutually exclusive with normal headers as per documentation
|
||||
raw_headers = field(["artifact"], []),
|
||||
# Directories to be included via -I, [arglike things]
|
||||
include_dirs = field(["label_relative_path"], []),
|
||||
# Directories to be included via -isystem, [arglike things]
|
||||
system_include_dirs = field(["label_relative_path"], []),
|
||||
# Whether to compile with modules support
|
||||
uses_modules = field(bool.type, False),
|
||||
# Modular args to set when modules are in use, [arglike things]
|
||||
modular_args = field([""], []),
|
||||
modulemap_path = field("", None),
|
||||
)
|
||||
|
||||
# Methods for transitive_sets must be declared prior to their use.
|
||||
|
||||
def _cpreprocessor_args(pres: [CPreprocessor.type]):
|
||||
args = cmd_args()
|
||||
for pre in pres:
|
||||
args.add(pre.args)
|
||||
return args
|
||||
|
||||
def _cpreprocessor_modular_args(pres: [CPreprocessor.type]):
|
||||
args = cmd_args()
|
||||
for pre in pres:
|
||||
args.add(pre.modular_args)
|
||||
return args
|
||||
|
||||
def _cpreprocessor_include_dirs(pres: [CPreprocessor.type]):
|
||||
args = cmd_args()
|
||||
for pre in pres:
|
||||
for d in pre.include_dirs:
|
||||
args.add("-I")
|
||||
args.add(d)
|
||||
for d in pre.system_include_dirs:
|
||||
args.add("-isystem")
|
||||
args.add(d)
|
||||
return args
|
||||
|
||||
def _cpreprocessor_uses_modules(children: [bool.type], pres: [[CPreprocessor.type], None]):
|
||||
if pres:
|
||||
for pre in pres:
|
||||
if pre.uses_modules:
|
||||
return True
|
||||
return any(children)
|
||||
|
||||
# Set of [CPreprocessor.type]. 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,
|
||||
"include_dirs": _cpreprocessor_include_dirs,
|
||||
"modular_args": _cpreprocessor_modular_args,
|
||||
},
|
||||
reductions = {
|
||||
"uses_modules": _cpreprocessor_uses_modules,
|
||||
},
|
||||
)
|
||||
|
||||
CPreprocessorInfo = provider(fields = [
|
||||
"set", # "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(fields = [
|
||||
# [str.type] - list of targets in "tests"
|
||||
"test_names", #
|
||||
# CPreprocessor.type - the private preprocessor
|
||||
# for the target which is _only_ exposed to any
|
||||
# test targets defined in `test_names`
|
||||
"own_non_exported_preprocessor",
|
||||
])
|
||||
|
||||
# Preprocessor flags
|
||||
def cxx_attr_preprocessor_flags(ctx: "context", ext: str.type) -> [""]:
|
||||
return (
|
||||
ctx.attrs.preprocessor_flags +
|
||||
cxx_by_language_ext(ctx.attrs.lang_preprocessor_flags, ext) +
|
||||
flatten(cxx_by_platform(ctx, ctx.attrs.platform_preprocessor_flags)) +
|
||||
flatten(cxx_by_platform(ctx, cxx_by_language_ext(ctx.attrs.lang_platform_preprocessor_flags, ext)))
|
||||
)
|
||||
|
||||
def cxx_attr_exported_preprocessor_flags(ctx: "context") -> [""]:
|
||||
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: ["dependency"]) -> [CPreprocessorInfo.type]:
|
||||
# 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: "context", own: [CPreprocessor.type], xs: [CPreprocessorInfo.type]) -> "CPreprocessorInfo":
|
||||
kwargs = {"children": [x.set for x in xs]}
|
||||
if own:
|
||||
kwargs["value"] = own
|
||||
return CPreprocessorInfo(
|
||||
set = ctx.actions.tset(CPreprocessorTSet, **kwargs),
|
||||
)
|
||||
|
||||
def cxx_exported_preprocessor_info(ctx: "context", headers_layout: CxxHeadersLayout.type, extra_preprocessors: [CPreprocessor.type] = []) -> CPreprocessor.type:
|
||||
"""
|
||||
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)
|
||||
|
||||
# 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, []))
|
||||
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])
|
||||
|
||||
header_root = prepare_headers(ctx, exported_header_map, "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.
|
||||
if header_root != None:
|
||||
inc_flag = _header_style_flag(style)
|
||||
args.extend([inc_flag, header_root.include_path])
|
||||
|
||||
# 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))
|
||||
|
||||
modular_args = []
|
||||
|
||||
# Append any extra preprocessor info passed in via the constructor params
|
||||
for pre in extra_preprocessors:
|
||||
args.extend(pre.args)
|
||||
modular_args.extend(pre.modular_args)
|
||||
|
||||
return CPreprocessor(
|
||||
args = args,
|
||||
headers = exported_headers,
|
||||
raw_headers = raw_headers,
|
||||
include_dirs = include_dirs,
|
||||
system_include_dirs = system_include_dirs,
|
||||
modular_args = modular_args,
|
||||
)
|
||||
|
||||
def cxx_private_preprocessor_info(
|
||||
ctx: "context",
|
||||
headers_layout: CxxHeadersLayout.type,
|
||||
raw_headers: ["artifact"] = [],
|
||||
extra_preprocessors: [CPreprocessor.type] = [],
|
||||
non_exported_deps: ["dependency"] = [],
|
||||
is_test: bool.type = False) -> (CPreprocessor.type, [CPreprocessor.type]):
|
||||
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: "context",
|
||||
headers_layout: CxxHeadersLayout.type,
|
||||
raw_headers: ["artifact"],
|
||||
extra_preprocessors: [CPreprocessor.type]) -> CPreprocessor.type:
|
||||
"""
|
||||
This rule's preprocessor info which is only applied to the compilation of
|
||||
its source, and not propagated to dependents.
|
||||
"""
|
||||
|
||||
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),
|
||||
),
|
||||
)
|
||||
|
||||
uses_modules = False
|
||||
|
||||
# Include any headers provided via constructor params and determine whether
|
||||
# to use modules
|
||||
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)
|
||||
include_dirs.extend([ctx.label.path.add(x) for x in ctx.attrs.include_directories])
|
||||
|
||||
# Create private header tree and propagate via args.
|
||||
args = []
|
||||
header_root = prepare_headers(ctx, header_map, "buck-private-headers")
|
||||
if header_root != None:
|
||||
args.extend(["-I", header_root.include_path])
|
||||
|
||||
# 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 CPreprocessor(
|
||||
args = args,
|
||||
headers = headers,
|
||||
raw_headers = all_raw_headers,
|
||||
include_dirs = include_dirs,
|
||||
uses_modules = uses_modules,
|
||||
)
|
||||
|
||||
def _by_language_cxx(x: {"": ""}) -> [""]:
|
||||
return cxx_by_language_ext(x, ".cpp")
|
||||
|
||||
def _header_style_flag(style: HeaderStyle.type) -> str.type:
|
||||
if style == HeaderStyle("local"):
|
||||
return "-I"
|
||||
if style == HeaderStyle("system"):
|
||||
return "-isystem"
|
||||
fail("unsupported header style: {}".format(style))
|
||||
|
||||
def _attr_headers_as_raw_headers_mode(ctx: "context") -> HeadersAsRawHeadersMode.type:
|
||||
"""
|
||||
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: "") -> bool.type:
|
||||
# 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: [CHeader.type]) -> [CHeader.type]:
|
||||
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
|
||||
176
vendor/cxx/tools/buck/prelude/cxx/symbols.bzl
vendored
Normal file
176
vendor/cxx/tools/buck/prelude/cxx/symbols.bzl
vendored
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
# 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(":cxx_context.bzl", "get_cxx_toolchain_info")
|
||||
|
||||
def extract_symbol_names(
|
||||
ctx: "context",
|
||||
name: str.type,
|
||||
objects: ["artifact"],
|
||||
category: str.type,
|
||||
identifier: [str.type, None] = None,
|
||||
undefined_only: bool.type = False,
|
||||
dynamic: bool.type = False,
|
||||
prefer_local: bool.type = False,
|
||||
local_only: bool.type = False,
|
||||
global_only: bool.type = False) -> "artifact":
|
||||
"""
|
||||
Generate a file with a sorted list of symbol names extracted from the given
|
||||
native objects.
|
||||
"""
|
||||
|
||||
if not objects:
|
||||
fail("no objects provided")
|
||||
|
||||
cxx_toolchain = get_cxx_toolchain_info(ctx)
|
||||
nm = cxx_toolchain.binary_utilities_info.nm
|
||||
output = ctx.actions.declare_output(name)
|
||||
|
||||
# -A: Prepend all lines with the name of the input file to which it
|
||||
# corresponds. Added only to make parsing the output a bit easier.
|
||||
# -P: Generate portable output format
|
||||
nm_flags = "-AP"
|
||||
if global_only:
|
||||
nm_flags += "g"
|
||||
if undefined_only:
|
||||
nm_flags += "u"
|
||||
|
||||
# darwin objects don't have dynamic symbol tables.
|
||||
if dynamic and cxx_toolchain.linker_info.type != "darwin":
|
||||
nm_flags += "D"
|
||||
|
||||
script = (
|
||||
"set -euo pipefail; " +
|
||||
'"$1" {} "${{@:2}}"'.format(nm_flags) +
|
||||
# Grab only the symbol name field.
|
||||
' | cut -d" " -f2 ' +
|
||||
# Strip off ABI Version (@...) when using llvm-nm to keep compat with buck1
|
||||
" | cut -d@ -f1 " +
|
||||
# Sort and dedup symbols. Use the `C` locale and do it in-memory to
|
||||
# make it significantly faster. CAUTION: if ten of these processes
|
||||
# run in parallel, they'll have cumulative allocations larger than RAM.
|
||||
" | LC_ALL=C sort -S 10% -u > {}"
|
||||
)
|
||||
|
||||
ctx.actions.run(
|
||||
[
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
cmd_args(output.as_output(), format = script),
|
||||
"",
|
||||
nm,
|
||||
] +
|
||||
objects,
|
||||
category = category,
|
||||
identifier = identifier,
|
||||
prefer_local = prefer_local,
|
||||
local_only = local_only,
|
||||
)
|
||||
return output
|
||||
|
||||
def extract_undefined_syms(ctx: "context", output: "artifact", prefer_local: bool.type) -> "artifact":
|
||||
return extract_symbol_names(
|
||||
ctx,
|
||||
output.short_path + ".undefined_syms.txt",
|
||||
[output],
|
||||
dynamic = True,
|
||||
global_only = True,
|
||||
undefined_only = True,
|
||||
category = "omnibus_undefined_syms",
|
||||
identifier = output.basename,
|
||||
prefer_local = prefer_local,
|
||||
)
|
||||
|
||||
def extract_global_syms(ctx: "context", output: "artifact", prefer_local: bool.type) -> "artifact":
|
||||
return extract_symbol_names(
|
||||
ctx,
|
||||
output.short_path + ".global_syms.txt",
|
||||
[output],
|
||||
dynamic = True,
|
||||
global_only = True,
|
||||
category = "omnibus_global_syms",
|
||||
identifier = output.basename,
|
||||
prefer_local = prefer_local,
|
||||
)
|
||||
|
||||
def _create_symbols_file_from_script(
|
||||
actions: "actions",
|
||||
name: str.type,
|
||||
script: str.type,
|
||||
symbol_files: ["artifact"],
|
||||
category: [str.type, None] = None,
|
||||
prefer_local: bool.type = False) -> "artifact":
|
||||
"""
|
||||
Generate a version script exporting symbols from from the given objects and
|
||||
link args.
|
||||
"""
|
||||
|
||||
all_symbol_files = actions.write(name + ".symbols", symbol_files)
|
||||
all_symbol_files = cmd_args(all_symbol_files).hidden(symbol_files)
|
||||
output = actions.declare_output(name)
|
||||
cmd = [
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
script,
|
||||
"",
|
||||
all_symbol_files,
|
||||
output.as_output(),
|
||||
]
|
||||
actions.run(
|
||||
cmd,
|
||||
category = category,
|
||||
prefer_local = prefer_local,
|
||||
)
|
||||
return output
|
||||
|
||||
def create_undefined_symbols_argsfile(
|
||||
actions: "actions",
|
||||
name: str.type,
|
||||
symbol_files: ["artifact"],
|
||||
category: [str.type, None] = None,
|
||||
prefer_local: bool.type = False) -> "artifact":
|
||||
"""
|
||||
Combine files with sorted lists of symbols names into an argsfile to pass
|
||||
to the linker to mark these symbols as undefined (e.g. `-m`).
|
||||
"""
|
||||
return _create_symbols_file_from_script(
|
||||
actions = actions,
|
||||
name = name,
|
||||
script = (
|
||||
"set -euo pipefail; " +
|
||||
'xargs cat < "$1" | LC_ALL=C sort -S 10% -u -m | sed "s/^/-u/" > $2'
|
||||
),
|
||||
symbol_files = symbol_files,
|
||||
category = category,
|
||||
prefer_local = prefer_local,
|
||||
)
|
||||
|
||||
def create_global_symbols_version_script(
|
||||
actions: "actions",
|
||||
name: str.type,
|
||||
symbol_files: ["artifact"],
|
||||
category: [str.type, None] = None,
|
||||
prefer_local: bool.type = False) -> "artifact":
|
||||
"""
|
||||
Combine files with sorted lists of symbols names into an argsfile to pass
|
||||
to the linker to mark these symbols as undefined (e.g. `-m`).
|
||||
"""
|
||||
return _create_symbols_file_from_script(
|
||||
actions = actions,
|
||||
name = name,
|
||||
script = """\
|
||||
set -euo pipefail
|
||||
echo "{" > "$2"
|
||||
echo " global:" >> "$2"
|
||||
xargs cat < "$1" | LC_ALL=C sort -S 10% -u -m | awk '{print " \\""$1"\\";"}' >> "$2"
|
||||
echo " local: *;" >> "$2"
|
||||
echo "};" >> "$2"
|
||||
""",
|
||||
symbol_files = symbol_files,
|
||||
category = category,
|
||||
prefer_local = prefer_local,
|
||||
)
|
||||
21
vendor/cxx/tools/buck/prelude/cxx/tools/TARGETS.v2
vendored
Normal file
21
vendor/cxx/tools/buck/prelude/cxx/tools/TARGETS.v2
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
load(":defs.bzl", "cxx_hacks")
|
||||
|
||||
prelude = native
|
||||
|
||||
prelude.python_bootstrap_binary(
|
||||
name = "make_comp_db",
|
||||
main = "make_comp_db.py",
|
||||
visibility = ["PUBLIC"],
|
||||
)
|
||||
|
||||
prelude.python_bootstrap_binary(
|
||||
name = "makefile_to_dep_file",
|
||||
main = "makefile_to_dep_file.py",
|
||||
visibility = ["PUBLIC"],
|
||||
)
|
||||
|
||||
# Required to support the $(cxx-header-tree) macro
|
||||
cxx_hacks(
|
||||
name = "cxx_hacks",
|
||||
visibility = ["PUBLIC"],
|
||||
)
|
||||
19
vendor/cxx/tools/buck/prelude/cxx/tools/defs.bzl
vendored
Normal file
19
vendor/cxx/tools/buck/prelude/cxx/tools/defs.bzl
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# 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.
|
||||
|
||||
def _cxx_hacks_impl(_ctx):
|
||||
return [DefaultInfo(), TemplatePlaceholderInfo(
|
||||
unkeyed_variables = {
|
||||
"cxx-header-tree": "/dev/null/HACK-CXX-HEADER-TREE",
|
||||
"output-dwo-dir": "/dev/null/HACK-OUTPUT-DWO-DIR",
|
||||
},
|
||||
)]
|
||||
|
||||
cxx_hacks = rule(
|
||||
impl = _cxx_hacks_impl,
|
||||
attrs = {},
|
||||
)
|
||||
84
vendor/cxx/tools/buck/prelude/cxx/tools/make_comp_db.py
vendored
Normal file
84
vendor/cxx/tools/buck/prelude/cxx/tools/make_comp_db.py
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Utility to create compilation DBs
|
||||
|
||||
$ make_comp_db.py gen --output=entry.json foo.cpp -- g++ -c -fPIC
|
||||
$ make_comp_db.py gen --output=entry2.json foo2.cpp -- g++ -c -fPIC
|
||||
$ make_comp_db.py merge --output=comp_db.json entry.json entry2.json
|
||||
"""
|
||||
|
||||
# pyre-unsafe
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
|
||||
def gen(args):
|
||||
"""
|
||||
Generate a single compilation command in JSON form.
|
||||
"""
|
||||
|
||||
entry = {}
|
||||
entry["file"] = args.directory + "/" + args.filename
|
||||
entry["directory"] = "."
|
||||
|
||||
arguments = []
|
||||
for arg in args.arguments:
|
||||
if arg.startswith("@"):
|
||||
with open(arg[1:]) as argsfile:
|
||||
for line in argsfile:
|
||||
# The argsfile's arguments are separated by newlines; we
|
||||
# don't want those included in the argument list.
|
||||
arguments.append(" ".join(shlex.split(line)))
|
||||
else:
|
||||
arguments.append(arg)
|
||||
entry["arguments"] = arguments
|
||||
|
||||
json.dump(entry, args.output, indent=2)
|
||||
|
||||
|
||||
def merge(args):
|
||||
"""
|
||||
Merge multiple compilation DB commands into a single DB.
|
||||
"""
|
||||
|
||||
entries = []
|
||||
for entry in args.entries:
|
||||
with open(entry) as f:
|
||||
entries.append(json.load(f))
|
||||
|
||||
json.dump(entries, args.output, indent=2)
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
parser_gen = subparsers.add_parser("gen")
|
||||
parser_gen.add_argument("--output", type=argparse.FileType("w"), default=sys.stdout)
|
||||
parser_gen.add_argument("filename")
|
||||
parser_gen.add_argument("directory")
|
||||
parser_gen.add_argument("arguments", nargs="*")
|
||||
parser_gen.set_defaults(func=gen)
|
||||
|
||||
parser_merge = subparsers.add_parser("merge")
|
||||
parser_merge.add_argument(
|
||||
"--output", type=argparse.FileType("w"), default=sys.stdout
|
||||
)
|
||||
parser_merge.add_argument("entries", nargs="*")
|
||||
parser_merge.set_defaults(func=merge)
|
||||
|
||||
args = parser.parse_args(argv[1:])
|
||||
args.func(args)
|
||||
|
||||
|
||||
sys.exit(main(sys.argv))
|
||||
126
vendor/cxx/tools/buck/prelude/cxx/tools/makefile_to_dep_file.py
vendored
Normal file
126
vendor/cxx/tools/buck/prelude/cxx/tools/makefile_to_dep_file.py
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
# pyre-unsafe
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def rewrite_dep_file(src_path, dst_path):
|
||||
"""
|
||||
Convert a makefile to a depfile suitable for use by Buck2. The files we
|
||||
rewrite look like P488268797.
|
||||
"""
|
||||
here = os.getcwd().replace("\\", "/") + "/"
|
||||
|
||||
with open(src_path) as f:
|
||||
body = f.read()
|
||||
|
||||
parts = body.split(": ", 1)
|
||||
body = parts[1] if len(parts) == 2 else ""
|
||||
|
||||
# Escaped newlines are not meaningful so remove them.
|
||||
body = body.replace("\\\n", "")
|
||||
|
||||
# Now, recover targets. They are space separated, but we need to ignore
|
||||
# spaces that are escaped.
|
||||
pos = 0
|
||||
|
||||
deps = []
|
||||
current_parts = []
|
||||
|
||||
def push_slice(s):
|
||||
if s:
|
||||
current_parts.append(s)
|
||||
|
||||
def flush_current_dep():
|
||||
if current_parts:
|
||||
deps.append("".join(current_parts))
|
||||
current_parts.clear()
|
||||
|
||||
while True:
|
||||
next_pos = body.find(" ", pos)
|
||||
|
||||
# If we find the same character we started at, this means we started on
|
||||
# a piece of whitespace. We know this cannot be escaped, because if we
|
||||
# started here that means we stopped at the previous character, which
|
||||
# means it must have been whitespace as well.
|
||||
if next_pos == pos:
|
||||
flush_current_dep()
|
||||
pos += 1
|
||||
continue
|
||||
|
||||
# No more whitespace, so this means that whatever is left from our
|
||||
# current position to the end is the last dependency (assuming there is
|
||||
# anything).
|
||||
if next_pos < 0:
|
||||
push_slice(body[pos:-1])
|
||||
break
|
||||
|
||||
# Check if this was escaped by looking at the previous character. If it
|
||||
# was, then insert the part before the escape, and then push a space.
|
||||
# If it wasn't, then we've reached the end of a dependency.
|
||||
if next_pos > 0 and body[next_pos - 1] == "\\":
|
||||
push_slice(body[pos : next_pos - 1])
|
||||
push_slice(" ")
|
||||
else:
|
||||
push_slice(body[pos:next_pos])
|
||||
flush_current_dep()
|
||||
|
||||
pos = next_pos + 1
|
||||
|
||||
flush_current_dep()
|
||||
|
||||
# Now that we've parsed deps, we need to normalize them.
|
||||
|
||||
normalized_deps = []
|
||||
|
||||
for dep in deps:
|
||||
# The paths we get sometimes include "../" components, so get rid
|
||||
# of those because we want ForwardRelativePath here.
|
||||
dep = os.path.normpath(dep).replace("\\", "/")
|
||||
|
||||
if os.path.isabs(dep):
|
||||
if dep.startswith(here):
|
||||
# The dep file included a path inside the build root, but
|
||||
# expressed an absolute path. In this case, rewrite it to
|
||||
# be a relative path.
|
||||
dep = dep[len(here) :]
|
||||
else:
|
||||
# The dep file included a path to something outside the
|
||||
# build root. That's bad (actions shouldn't depend on
|
||||
# anything outside the build root), but that dependency is
|
||||
# therefore not tracked by Buck2 (which can only see things
|
||||
# in the build root), so it cannot be represented as a
|
||||
# dependency and therefore we don't include it (event if we
|
||||
# could include it, this could never cause a miss).
|
||||
continue
|
||||
|
||||
normalized_deps.append(dep)
|
||||
|
||||
with open(dst_path, "w") as f:
|
||||
for dep in normalized_deps:
|
||||
f.write(dep)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Expects the src dep file to be the first argument, dst dep file to be the
|
||||
second argument, and the command to follow.
|
||||
"""
|
||||
ret = subprocess.call(sys.argv[3:])
|
||||
if ret == 0:
|
||||
rewrite_dep_file(sys.argv[1], sys.argv[2])
|
||||
sys.exit(ret)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
88
vendor/cxx/tools/buck/prelude/cxx/user/link_group_map.bzl
vendored
Normal file
88
vendor/cxx/tools/buck/prelude/cxx/user/link_group_map.bzl
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# 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//:attributes.bzl", "Linkage", "Traversal")
|
||||
load(
|
||||
"@prelude//cxx:groups.bzl",
|
||||
"compute_mappings",
|
||||
)
|
||||
load(
|
||||
"@prelude//cxx:link_groups.bzl",
|
||||
"LinkGroupInfo",
|
||||
"parse_link_group_definitions",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_groups.bzl",
|
||||
"LinkGroupLibInfo",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:link_info.bzl",
|
||||
"MergedLinkInfo",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:linkable_graph.bzl",
|
||||
"LinkableGraph",
|
||||
"create_linkable_graph",
|
||||
"get_linkable_graph_node_map_func",
|
||||
)
|
||||
load(
|
||||
"@prelude//linking:shared_libraries.bzl",
|
||||
"SharedLibraryInfo",
|
||||
)
|
||||
load("@prelude//user:rule_spec.bzl", "RuleRegistrationSpec")
|
||||
|
||||
def _v1_attrs(optional_root: bool.type = False):
|
||||
attrs_root = attrs.dep(providers = [
|
||||
LinkGroupLibInfo,
|
||||
LinkableGraph,
|
||||
MergedLinkInfo,
|
||||
SharedLibraryInfo,
|
||||
])
|
||||
if optional_root:
|
||||
attrs_root = attrs.option(attrs_root)
|
||||
return attrs.list(
|
||||
attrs.tuple(
|
||||
attrs.string(),
|
||||
attrs.list(
|
||||
attrs.tuple(
|
||||
attrs_root,
|
||||
attrs.enum(Traversal),
|
||||
attrs.option(attrs.string()),
|
||||
attrs.option(attrs.enum(Linkage)),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
def link_group_map_attr():
|
||||
v2_attrs = attrs.dep(providers = [LinkGroupInfo])
|
||||
return attrs.option(attrs.one_of(v2_attrs, _v1_attrs(optional_root = True)), default = None)
|
||||
|
||||
def _impl(ctx: "context") -> ["provider"]:
|
||||
link_groups = parse_link_group_definitions(ctx.attrs.map)
|
||||
linkable_graph = create_linkable_graph(
|
||||
ctx,
|
||||
children = [
|
||||
mapping.root.node.linkable_graph
|
||||
for group in link_groups
|
||||
for mapping in group.mappings
|
||||
],
|
||||
)
|
||||
linkable_graph_node_map = get_linkable_graph_node_map_func(linkable_graph)()
|
||||
mappings = compute_mappings(groups = link_groups, graph_map = linkable_graph_node_map)
|
||||
return [
|
||||
DefaultInfo(),
|
||||
LinkGroupInfo(groups = link_groups, groups_hash = hash(str(link_groups)), mappings = mappings),
|
||||
]
|
||||
|
||||
registration_spec = RuleRegistrationSpec(
|
||||
name = "link_group_map",
|
||||
impl = _impl,
|
||||
attrs = {
|
||||
"map": _v1_attrs(),
|
||||
},
|
||||
)
|
||||
57
vendor/cxx/tools/buck/prelude/cxx/xcode.bzl
vendored
Normal file
57
vendor/cxx/tools/buck/prelude/cxx/xcode.bzl
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# 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//cxx:compile.bzl",
|
||||
"CxxSrcWithFlags", # @unused Used as a type
|
||||
)
|
||||
|
||||
def cxx_populate_xcode_attributes(
|
||||
ctx,
|
||||
srcs: [CxxSrcWithFlags.type],
|
||||
argsfiles_by_ext: {str.type: "artifact"},
|
||||
product_name: str.type) -> {str.type: ""}:
|
||||
converted_srcs = {}
|
||||
for src in srcs:
|
||||
file_properties = _get_artifact_owner(src.file)
|
||||
if src.flags:
|
||||
# List of resolved_macros will encode as:
|
||||
# [['\"-some-flag\"'], ['\"-another-flag\"']]
|
||||
#
|
||||
# Convert it to a string and rip-out the quotes
|
||||
# so it appears as ["-some-flag", "-another-flag"]
|
||||
file_properties["flags"] = [str(flag).replace('\"', "") for flag in src.flags]
|
||||
converted_srcs[src.file] = file_properties
|
||||
|
||||
data = {
|
||||
"argsfiles_by_ext": {
|
||||
# Enum types cannot be encoded by our JSON API.
|
||||
# Use the str representation.
|
||||
repr(ext).replace('\"', ""): artifact
|
||||
for ext, artifact in argsfiles_by_ext.items()
|
||||
},
|
||||
"headers": _get_artifacts_with_owners(ctx.attrs.headers),
|
||||
"product_name": product_name,
|
||||
"srcs": converted_srcs,
|
||||
}
|
||||
|
||||
if hasattr(ctx.attrs, "exported_headers"):
|
||||
data["exported_headers"] = _get_artifacts_with_owners(ctx.attrs.exported_headers)
|
||||
|
||||
return data
|
||||
|
||||
def _get_artifacts_with_owners(files: "") -> {"artifact": {str.type: "label"}}:
|
||||
if type(files) == "dict":
|
||||
return {artifact: _get_artifact_owner(artifact) for _, artifact in files.items()}
|
||||
else:
|
||||
return {file: _get_artifact_owner(file) for file in files}
|
||||
|
||||
def _get_artifact_owner(file: "artifact") -> {str.type: "label"}:
|
||||
if file.owner:
|
||||
return {"target": file.owner}
|
||||
else:
|
||||
return {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue