Skip to main content
Glama
build_params.bzl15.7 kB
# Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under both the MIT license found in the # LICENSE-MIT file in the root directory of this source tree and the Apache # License, Version 2.0 found in the LICENSE-APACHE file in the root directory # of this source tree. # Rules for mapping requirements to options load("@prelude//cxx:cxx_toolchain_types.bzl", "LinkerType") load( "@prelude//linking:link_info.bzl", "LibOutputStyle", "LinkStrategy", ) load("@prelude//os_lookup:defs.bzl", "Os", "OsLookup") load("@prelude//utils:expect.bzl", "expect") # --crate-type= # Excludes `lib` because we want to explicitly choose the library flavour CrateType = enum( # Binary "bin", # Rust linkage "rlib", "dylib", "proc-macro", # Native linkage "cdylib", "staticlib", ) # Crate type is intended for native linkage (eg C++) def crate_type_native_linkage(crate_type: CrateType) -> bool: return crate_type.value in ("cdylib", "staticlib") # Crate type which invokes the linker def crate_type_linked(crate_type: CrateType) -> bool: return crate_type.value in ("bin", "dylib", "proc-macro", "cdylib") # Crate type which should always need codegen def crate_type_codegen(crate_type: CrateType) -> bool: return crate_type_linked(crate_type) or crate_type_native_linkage(crate_type) # -Crelocation-model= from --print relocation-models RelocModel = enum( # Common "static", "pic", "pie", # Various obscure types "dynamic-no-pic", "ropi", "rwpi", "ropi-rwpi", "default", ) # --emit= Emit = enum( "asm", "llvm-bc", "llvm-ir", "llvm-ir-noopt", "obj", "link", "dep-info", "mir", "expand", # pseudo emit alias for -Zunpretty=expanded "clippy", # Rustc actually has two different forms of metadata: # - The full flavor, which is what's outputted when passing # `--emit link,metadata` and can be used as a part of pipelined builds # - The fast flavor, which is emitted from `--emit metadata`, is faster to # build, but cannot be used in pipelined builds. "metadata-full", "metadata-fast", ) ProfileMode = enum( "llvm-time-trace", "self-profile", ) # The different quantities of Rust metadata that can be requested from # dependencies. Each one corresponds to an `Emit` variant, but not all `Emit` # variants output metadata MetadataKind = enum( "fast", "full", "link", ) # Emitting this artifact generates code def dep_metadata_of_emit(emit: Emit) -> MetadataKind: return { Emit("asm"): MetadataKind("link"), Emit("llvm-bc"): MetadataKind("link"), Emit("llvm-ir"): MetadataKind("link"), Emit("llvm-ir-noopt"): MetadataKind("link"), Emit("obj"): MetadataKind("link"), Emit("link"): MetadataKind("link"), Emit("mir"): MetadataKind("link"), Emit("metadata-fast"): MetadataKind("fast"), Emit("clippy"): MetadataKind("fast"), Emit("dep-info"): MetadataKind("full"), Emit("expand"): MetadataKind("full"), Emit("metadata-full"): MetadataKind("full"), }[emit] # Represents a way of invoking rustc to produce an artifact. These values are computed from # information such as the rule type, linkstyle, crate type, etc. BuildParams = record( crate_type = field(CrateType), reloc_model = field(RelocModel), dep_link_strategy = field(LinkStrategy), # A prefix and suffix to use for the name of the produced artifact. Note that although we store # these in this type, they are in principle computable from the remaining fields and the OS. # Keeping them here just turns out to be a little more convenient. prefix = field(str), suffix = field(str), ) RustcFlags = record( crate_type = field(CrateType), platform_to_affix = field(typing.Callable), link_strategy = field(LinkStrategy | None), ) # Filenames used for various emitted forms # `None` for a prefix or suffix means use the build_param version _EMIT_PREFIX_SUFFIX = { Emit("asm"): ("", ".s"), Emit("llvm-bc"): ("", ".bc"), Emit("llvm-ir"): ("", ".ll"), Emit("llvm-ir-noopt"): ("", ".ll"), Emit("obj"): ("", ".o"), Emit("metadata-fast"): ("lib", ".rmeta"), # even binaries get called 'libfoo.rmeta' Emit("metadata-full"): (None, None), # Hollow rlibs, so they get the same name Emit("link"): (None, None), # crate type and reloc model dependent Emit("dep-info"): ("", ".d"), Emit("mir"): (None, ".mir"), Emit("expand"): (None, ".rs"), Emit("clippy"): ("lib", ".rmeta"), # Treated like metadata-fast } # Return the filename for a particular emitted artifact type def output_filename(cratename: str, emit: Emit, buildparams: BuildParams, extra: [str, None] = None) -> str: epfx, esfx = _EMIT_PREFIX_SUFFIX[emit] prefix = epfx if epfx != None else buildparams.prefix suffix = esfx if esfx != None else buildparams.suffix return prefix + cratename + (extra or "") + suffix # Rule type - 'binary' also covers 'test' RuleType = enum("binary", "library") # Controls how we build our rust libraries, largely dependent on whether rustc # or buck is driving the final linking and whether we are linking the artifact # into other rust targets. # # Rust: In this mode, we build rust libraries as rlibs. This is the primary # approach for building rust targets when the final link step is driven by # rustc (e.g. rust_binary, rust_unittest, etc). # # Native: In this mode, we build rust libraries as staticlibs, where rustc # will bundle all of this target's rust dependencies into a single library # artifact. This approach is the most standardized way to build rust libraries # for linkage in non-rust code. # # NOTE: This approach does not scale well. It's possible to end up with # non-rust target A depending on two rust targets B and C, which can cause # duplicate symbols if B and C share common rust dependencies. # # Native Unbundled: In this mode, we revert back to building as rlibs. This # approach mitigates the duplicate symbol downside of the "Native" approach. # However, this option is not formally supported by rustc, and depends on an # implementation detail of rlibs (they're effectively .a archives and can be # linked with other native code using the CXX linker). # # See https://github.com/rust-lang/rust/issues/73632 for more details on # stabilizing this approach. LinkageLang = enum( "rust", "native", "native-unbundled", ) _BINARY = 0 _RUST_PROC_MACRO_RUSTDOC_TEST = 1 _NATIVE_LINKABLE_SHARED_OBJECT = 3 _RUST_DYLIB_SHARED = 4 _RUST_PROC_MACRO = 5 _RUST_STATIC_PIC_LIBRARY = 6 _RUST_STATIC_NON_PIC_LIBRARY = 7 _NATIVE_LINKABLE_STATIC_PIC = 8 _NATIVE_LINKABLE_STATIC_NON_PIC = 9 def _executable_prefix_suffix(linker_type: LinkerType, target_os_type: OsLookup) -> (str, str): return { LinkerType("darwin"): ("", ""), LinkerType("gnu"): ("", ".exe") if target_os_type.os == Os("windows") else ("", ""), LinkerType("wasm"): ("", ".wasm"), LinkerType("windows"): ("", ".exe"), }[linker_type] def _library_prefix_suffix(linker_type: LinkerType, target_os_type: OsLookup) -> (str, str): return { LinkerType("darwin"): ("lib", ".dylib"), LinkerType("gnu"): ("", ".dll") if target_os_type.os == Os("windows") else ("lib", ".so"), LinkerType("wasm"): ("", ".wasm"), LinkerType("windows"): ("", ".dll"), }[linker_type] _BUILD_PARAMS = { _BINARY: RustcFlags( crate_type = CrateType("bin"), platform_to_affix = _executable_prefix_suffix, # link_strategy is provided by the rust_binary attribute link_strategy = None, ), # It's complicated: this is a rustdoc test for a procedural macro crate. # We need deps built as if this were a binary, while passing crate-type # proc_macro to the rustdoc invocation. _RUST_PROC_MACRO_RUSTDOC_TEST: RustcFlags( crate_type = CrateType("proc-macro"), platform_to_affix = _executable_prefix_suffix, link_strategy = LinkStrategy("static_pic"), ), _NATIVE_LINKABLE_SHARED_OBJECT: RustcFlags( crate_type = CrateType("cdylib"), platform_to_affix = _library_prefix_suffix, # cdylibs statically link all rust code and export a single C-style dylib # for consumption by other languages link_strategy = LinkStrategy("shared"), ), _RUST_DYLIB_SHARED: RustcFlags( crate_type = CrateType("dylib"), platform_to_affix = _library_prefix_suffix, link_strategy = LinkStrategy("shared"), ), _RUST_PROC_MACRO: RustcFlags( crate_type = CrateType("proc-macro"), platform_to_affix = _library_prefix_suffix, # FIXME(JakobDegen): It's not really clear what we should do about # proc macros. The principled thing is probably to treat them sort # of like a normal library, except that they always have preferred # linkage shared? Preserve existing behavior for now link_strategy = LinkStrategy("static_pic"), ), # FIXME(JakobDegen): Add a comment explaining why `.a`s need reloc-strategy # dependent names while `.rlib`s don't. _RUST_STATIC_PIC_LIBRARY: RustcFlags( crate_type = CrateType("rlib"), platform_to_affix = lambda _l, _t: ("lib", ".rlib"), link_strategy = LinkStrategy("static_pic"), ), _RUST_STATIC_NON_PIC_LIBRARY: RustcFlags( crate_type = CrateType("rlib"), platform_to_affix = lambda _l, _t: ("lib", ".rlib"), link_strategy = LinkStrategy("static"), ), _NATIVE_LINKABLE_STATIC_PIC: RustcFlags( crate_type = CrateType("staticlib"), platform_to_affix = lambda _l, _t: ("lib", "_pic.a"), link_strategy = LinkStrategy("static_pic"), ), _NATIVE_LINKABLE_STATIC_NON_PIC: RustcFlags( crate_type = CrateType("staticlib"), platform_to_affix = lambda _l, _t: ("lib", ".a"), link_strategy = LinkStrategy("static"), ), } _INPUTS = { # Binary ("binary", False, None, "rust"): _BINARY, ("binary", True, None, "rust"): _RUST_PROC_MACRO_RUSTDOC_TEST, # Native linkable shared object ("library", False, "shared_lib", "native"): _NATIVE_LINKABLE_SHARED_OBJECT, # Native unbundled linkable shared object ("library", False, "shared_lib", "native-unbundled"): _RUST_DYLIB_SHARED, # Rust dylib shared object ("library", False, "shared_lib", "rust"): _RUST_DYLIB_SHARED, # Rust proc-macro ("library", True, "archive", "rust"): _RUST_PROC_MACRO, ("library", True, "pic_archive", "rust"): _RUST_PROC_MACRO, ("library", True, "shared_lib", "rust"): _RUST_PROC_MACRO, # Rust static_pic library ("library", False, "pic_archive", "rust"): _RUST_STATIC_PIC_LIBRARY, # Rust static (non-pic) library ("library", False, "archive", "rust"): _RUST_STATIC_NON_PIC_LIBRARY, # Native linkable static_pic ("library", False, "pic_archive", "native"): _NATIVE_LINKABLE_STATIC_PIC, # Native linkable static non-pic ("library", False, "archive", "native"): _NATIVE_LINKABLE_STATIC_NON_PIC, # Native Unbundled static_pic library ("library", False, "pic_archive", "native-unbundled"): _RUST_STATIC_PIC_LIBRARY, # Native Unbundled static (non-pic) library ("library", False, "archive", "native-unbundled"): _RUST_STATIC_NON_PIC_LIBRARY, } # Check types of _INPUTS, writing these out as types is too verbose, but let's make sure we don't have any typos. [ (RuleType(rule_type), LibOutputStyle(lib_output_style) if lib_output_style else None, LinkageLang(linkage_lang)) for (rule_type, _, lib_output_style, linkage_lang), _ in _INPUTS.items() ] def _get_reloc_model(rule: RuleType, link_strategy: LinkStrategy, target_os_type: OsLookup) -> RelocModel: if target_os_type.os == Os("windows"): return RelocModel("pic") if link_strategy == LinkStrategy("static"): return RelocModel("static") if rule == RuleType("binary"): return RelocModel("pie") return RelocModel("pic") # Compute crate type, relocation model and name mapping given what rule we're building, whether its # a proc-macro, linkage information and language. # # Binaries should pass the link strategy and not the lib output style, while libraries should do the # opposite. # # The linking information that's passed here is different from what one might expect in the C++ # rules. There's a good reason for that, so let's go over it. First, let's recap how C++ handles # this, as of December 2023 (I say "recap" but I don't think this is actually documented anywhere): # # 1. C++ libraries can be built in three different ways: Archives, pic archives, and shared # libraries. Which one of these is used for a given link strategy is determined by the preferred # linkage using `linking/link_info.bzl:get_lib_output_style`. # 2. When a C++ library is built as a shared library, the link strategy used for its dependencies # is determined by the link style attribute on the C++ library. # 3. When a C++ library is built as an archive (either kind), there's no need to know a link # strategy for the dependencies. None of the per-link-strategy providers of the dependencies # need to be accessed. # # There are two relevant ways in which Rust differs: # # 1. There are more ways of building Rust libraries than are represented by `LibOutputStyle`. The # Rust analogue is the `BuildParams` type, which implicitly holds a `LibOutputStyle` as well as # a bunch of additional information - this is why `LibOutputStyle` is relatively rarely used # directly in the Rust rules. # 2. Rust does not have the property in point three above, ie building a Rust library into an # archive does require knowing per-link-strategy properties of the dependencies. This is # fundamental in cases without native unbundled deps - with native unbundled deps it may be # fixable, but that's not super clear. def build_params( rule: RuleType, proc_macro: bool, link_strategy: LinkStrategy | None, lib_output_style: LibOutputStyle | None, lang: LinkageLang, linker_type: LinkerType, target_os_type: OsLookup) -> BuildParams: if rule == RuleType("binary"): expect(link_strategy != None) expect(lib_output_style == None) else: expect(lib_output_style != None) input = (rule.value, proc_macro, lib_output_style.value if lib_output_style else None, lang.value) expect( input in _INPUTS, "missing case for rule_type={} proc_macro={} lib_output_style={} lang={}", rule, proc_macro, lib_output_style, lang, ) flags = _BUILD_PARAMS[_INPUTS[input]] # FIXME(JakobDegen): We deal with Rust needing to know the link strategy # even for building archives by using a default link strategy specifically # for those cases. I've gone through the code and checked all the places # where the link strategy is used to determine that this won't do anything # too bad, but it would be nice to enforce that more strictly or not have # this at all. link_strategy = link_strategy or flags.link_strategy reloc_model = _get_reloc_model(rule, link_strategy, target_os_type) prefix, suffix = flags.platform_to_affix(linker_type, target_os_type) return BuildParams( crate_type = flags.crate_type, reloc_model = reloc_model, dep_link_strategy = link_strategy, prefix = prefix, suffix = suffix, )

Latest Blog Posts

MCP directory API

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

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

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