Skip to main content
Glama
link_info.bzl25.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. # Implementation of the Rust build rules. load( "@prelude//:artifact_tset.bzl", "ArtifactTSet", "make_artifact_tset", ) load( "@prelude//cxx:cxx.bzl", "get_auto_link_group_specs", ) load("@prelude//cxx:cxx_context.bzl", "get_cxx_toolchain_info") load( "@prelude//cxx:cxx_library_utility.bzl", "cxx_is_gnu", ) load("@prelude//cxx:cxx_toolchain_types.bzl", "PicBehavior") load( "@prelude//cxx:link_groups.bzl", "BuildLinkGroupsContext", "LinkGroupLinkInfo", # @unused Used as a type "collect_linkables", "create_link_groups", "get_filtered_labels_to_links_map", "get_filtered_links", "get_filtered_targets", "get_link_group", "get_link_group_info", "get_link_group_preferred_linkage", "get_public_link_group_nodes", ) load( "@prelude//cxx:link_groups_types.bzl", "LinkGroupInfo", # @unused Used as a type ) load( "@prelude//cxx:linker.bzl", "get_default_shared_library_name", "get_shared_library_name_for_param", ) load( "@prelude//linking:link_groups.bzl", "LinkGroupLib", # @unused Used as a type "LinkGroupLibInfo", # @unused Used as a type ) load( "@prelude//linking:link_info.bzl", "LibOutputStyle", "LinkInfo", "LinkStrategy", "MergedLinkInfo", ) load( "@prelude//linking:linkable_graph.bzl", "LinkableGraph", "create_linkable_graph", "reduce_linkable_graph", ) load( "@prelude//linking:shared_libraries.bzl", "SharedLibraryInfo", ) load( "@prelude//linking:types.bzl", "Linkage", # @unused Used as a type ) load( "@prelude//utils:type_defs.bzl", "is_dict", "is_string", ) load( ":build_params.bzl", "MetadataKind", # @unused Used as a type ) load( ":context.bzl", "CrateName", # @unused Used as a type "DepCollectionContext", # @unused Used as a type ) load(":rust_toolchain.bzl", "PanicRuntime", "RustToolchainInfo") # Link strategy for targets which do not set an explicit `link_style` attribute. # # These values are also used as the defaults for check/clippy subtargets on # libraries, and are the only way in which metadata-fast output can be built. # # Internally at Meta, these are a good choice for a default because they allow # sharing work between check builds and dev mode builds, which have shared link # strategy, and so consume their dependencies as `static_pic`. DEFAULT_STATIC_LINK_STRATEGY = LinkStrategy("static_pic") DEFAULT_STATIC_LIB_OUTPUT_STYLE = LibOutputStyle("pic_archive") RustProcMacroPlugin = plugins.kind() # This provider is used for proc macros in those places where `RustLinkInfo` would typically be used # for libraries. It represents a proc macro in the dependency graph, and contains as a field the # `target_label` of that proc macro. The actual providers will always be accessed later through # `ctx.plugins` RustProcMacroMarker = provider(fields = { "label": typing.Any, }) # Information which is keyed on link_style RustLinkStrategyInfo = record( # Path to the rlib, rmeta, dylib, etc. outputs = field(dict[MetadataKind, Artifact]), # Transitive dependencies which are relevant to the consumer. For crate types which do not # propagate their deps (specifically proc macros), this set is empty # This does not include the proc macros, which are passed separately in `RustLinkInfo` transitive_deps = field(dict[MetadataKind, dict[Artifact, CrateName]]), transitive_proc_macro_deps = field(dict[RustProcMacroMarker, ()]), # Path to PDB file with Windows debug data. pdb = field(Artifact | None), # Debug info which is referenced -- but not included -- by the linkable rlib. external_debug_info = field(ArtifactTSet), ) # Output of a Rust compilation RustLinkInfo = provider( # @unsorted-dict-items fields = { # crate - crate name "crate": CrateName, # strategies - information about each LinkStrategy as RustLinkStrategyInfo "strategies": dict[LinkStrategy, RustLinkStrategyInfo], # Rust interacts with the native link graph in a non-standard way. # # The first difference is in the re-export behavior of Rust compared to C++. The native link # providers make an assumption that if one node in the link graph references a symbol in # another node in the link graph, there is also a corresponding edge in the link graph. # Specifically, the first node must declare a direct dependency on the second, a transitive # dependency is not enough. For C++, this just means that each library depends in the link # graph on its direct deps and their exported deps. # # For Rust, the situation is different. Because of re-exports and generics causing delayed # codegen, the generated object files for a Rust library can generate symbol references to # any of the library's transitive Rust dependencies, as well as to the immediate C++ # dependencies of those libraries. So to account for that, each Rust library reports direct # dependencies on all of those libraries in the link graph. The `merged_link_infos` and # `linkable_graphs` lists are the providers from all of those libraries. # # The second difference is unique to the case where `advanced_unstable_linking` is not set # on the toolchain. Imagine we have a Rust library `:B` with its only one dependency `:A`, # another Rust library. The Rust rules give Rust -> Rust dependencies special treatment in # the non-`advanced_unstable_linking` case. As a result, the `MergedLinkInfo` provided from # `:B` is not a "superset" of the `MergedLinkInfo` provided from `:A` (concrete differences # discussed below). # # This distinction is implemented by effectively having each Rust library provide two sets # of link providers. The first is the link providers used across Rust -> Rust dependency # edges - this is what the fields below are. The second set is the one that is used by C++ # and other non-Rust dependents, and is returned from the rule like normal. The second set # is a superset of the first, that is it includes anything that the first link providers # added. # # The concrete difference is that the Rust `MergedLinkInfo` provided by `:A` is only the # result of merging the `MergedLinkInfo`s from `:A`'s deps, and does not contain anything # about `:A`. Instead, when `:B` produces the native `MergedLinkInfo`, it will add a single # static library that bundles all transitive Rust deps, including `:A` (and similarly for # the DSO case). # # With `advanced_unstable_linkin`, Rust libraries essentially behave just like C++ # libraries in the link graph, with the handling of transitive dependencies being the only # difference. "merged_link_infos": dict[ConfiguredTargetLabel, MergedLinkInfo], "linkable_graphs": list[LinkableGraph], "shared_libs": SharedLibraryInfo, # LinkGroupLibInfo intentionally omitted because the Rust -> Rust version # never needs to be different from the Rust -> native version # # Rust currently treats all native dependencies as being exported, in # the sense of C++ `exported_deps`. However, they are not only exported # from the Rust library that directly depends on them, they are also # exported through any further chains of Rust libraries. This list # tracks those dependencies # # FIXME(JakobDegen): We should not default to treating all native deps # as exported. "exported_link_deps": list[Dependency], }, ) def _adjust_link_strategy_for_rust_dependencies(toolchain_info: RustToolchainInfo, dep_link_strategy: LinkStrategy) -> LinkStrategy: if dep_link_strategy == LinkStrategy("shared") and not toolchain_info.advanced_unstable_linking: return DEFAULT_STATIC_LINK_STRATEGY else: return dep_link_strategy def strategy_info(toolchain_info: RustToolchainInfo, info: RustLinkInfo, dep_link_strategy: LinkStrategy) -> RustLinkStrategyInfo: rust_dep_link_strategy = _adjust_link_strategy_for_rust_dependencies(toolchain_info, dep_link_strategy) return info.strategies[rust_dep_link_strategy] # Any dependency of a Rust crate RustOrNativeDependency = record( # The actual dependency dep = field(Dependency), # The local name, if any (for `named_deps`) name = field(None | str | ResolvedStringWithMacros), # Any flags for the dependency (`flagged_deps`), which are passed on to rustc. flags = field(list[str]), ) RustDependency = record( info = field(RustLinkInfo), label = field(ConfiguredProvidersLabel), dep = field(Dependency), name = field(None | str | ResolvedStringWithMacros), flags = field(list[str]), proc_macro_marker = field([None, RustProcMacroMarker]), ) # Information about cxx link groups that rust depends on RustCxxLinkGroupInfo = record( # cxx link infos to link against filtered_links = field(list[LinkInfo]), # symbol files args to ensure we export the required symbols symbol_files_info = field(LinkInfo), # targets to link against filtered_targets = field(list[TargetLabel]), # information about the link groups link_group_info = field([LinkGroupInfo, None]), # shared libraries created from link groups link_group_libs = field(dict[str, [LinkGroupLib, None]]), # mapping from target labels to the corresponding link group link_info labels_to_links_map = field(dict[Label, LinkGroupLinkInfo]), # Target to link group name where it was actually linked into targets_consumed_by_link_groups = field(dict[Label, str]), # preferred linkage mode for link group libraries link_group_preferred_linkage = field(dict[Label, Linkage]), ) # Returns all first-order dependencies. def _do_resolve_deps( deps: list[Dependency], named_deps: dict[str, Dependency] | list[(ResolvedStringWithMacros, Dependency)], flagged_deps: list[(Dependency, list[str])] = []) -> list[RustOrNativeDependency]: named_deps_items = named_deps.items() if is_dict(named_deps) else named_deps return [ RustOrNativeDependency(name = name, dep = dep, flags = flags) for name, dep, flags in [(None, dep, []) for dep in deps] + [(name, dep, []) for name, dep in named_deps_items] + [(None, dep, flags) for dep, flags in flagged_deps] ] def gather_explicit_sysroot_deps(dep_ctx: DepCollectionContext) -> list[RustOrNativeDependency]: explicit_sysroot_deps = dep_ctx.explicit_sysroot_deps if not explicit_sysroot_deps: return [] out = [] if explicit_sysroot_deps.core: out.append(RustOrNativeDependency( dep = explicit_sysroot_deps.core, name = None, flags = ["noprelude", "nounused"], )) if explicit_sysroot_deps.std: out.append(RustOrNativeDependency( dep = explicit_sysroot_deps.std, name = None, # "force" is used here in order to link std internals (like alloc hooks) even if the rest # of std is otherwise unused (e.g. we are building a no_std crate that needs to link with # other std-enabled crates as a standalone dylib). flags = ["noprelude", "nounused", "force"], )) if explicit_sysroot_deps.proc_macro: out.append(RustOrNativeDependency( dep = explicit_sysroot_deps.proc_macro, name = None, flags = ["noprelude", "nounused"], )) # When advanced_unstable_linking is on, we only add the dep that matches the # panic runtime. Without advanced_unstable_linking, we just let rustc deal # with it if explicit_sysroot_deps.panic_unwind: if not dep_ctx.advanced_unstable_linking or dep_ctx.panic_runtime == PanicRuntime("unwind"): out.append(RustOrNativeDependency( dep = explicit_sysroot_deps.panic_unwind, name = None, flags = ["noprelude", "nounused"], )) if explicit_sysroot_deps.panic_abort: if not dep_ctx.advanced_unstable_linking or dep_ctx.panic_runtime == PanicRuntime("abort"): out.append(RustOrNativeDependency( dep = explicit_sysroot_deps.panic_abort, name = None, flags = ["noprelude", "nounused"], )) for d in explicit_sysroot_deps.others: out.append(RustOrNativeDependency( dep = d, name = None, flags = ["noprelude", "nounused"], )) return out def resolve_deps( ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> list[RustOrNativeDependency]: dependencies = _do_resolve_deps( deps = ctx.attrs.deps, named_deps = ctx.attrs.named_deps, flagged_deps = ctx.attrs.flagged_deps, ) if dep_ctx.include_doc_deps: dependencies.extend(_do_resolve_deps( deps = getattr(ctx.attrs, "doc_deps", []), named_deps = getattr(ctx.attrs, "doc_named_deps", {}), )) return dependencies + gather_explicit_sysroot_deps(dep_ctx) def resolve_rust_deps_inner( ctx: AnalysisContext, all_deps: list[RustOrNativeDependency]) -> list[RustDependency]: rust_deps = [] available_proc_macros = get_available_proc_macros(ctx) for dep in all_deps: proc_macro_marker = dep.dep.get(RustProcMacroMarker) if proc_macro_marker != None: # Confusingly, this is not `proc_macro_marker.label`, since that has type # `target_label`, but this wants a `label` label = available_proc_macros[proc_macro_marker.label].label info = available_proc_macros[proc_macro_marker.label][RustLinkInfo] else: label = dep.dep.label info = dep.dep.get(RustLinkInfo) if info == None: continue rust_deps.append(RustDependency( info = info, label = label, dep = dep.dep, name = dep.name, flags = dep.flags, proc_macro_marker = proc_macro_marker, )) return rust_deps def resolve_rust_deps( ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> list[RustDependency]: all_deps = resolve_deps(ctx, dep_ctx) return resolve_rust_deps_inner(ctx, all_deps) def get_available_proc_macros(ctx: AnalysisContext) -> dict[TargetLabel, Dependency]: return {x.label.raw_target(): x for x in ctx.plugins[RustProcMacroPlugin]} # Returns native link dependencies. def _native_link_dependencies( ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> list[Dependency]: """ Return all first-order native linkable dependencies of all transitive Rust libraries. This emulates v1's graph walk, where it traverses through Rust libraries looking for non-Rust native link infos (and terminating the search there). """ first_order_deps = [dep.dep for dep in resolve_deps(ctx, dep_ctx)] return [ d for d in first_order_deps if RustLinkInfo not in d and MergedLinkInfo in d ] # Returns the rust link infos for non-proc macro deps. # # This is intended to be used to access the Rust -> Rust link providers def _rust_non_proc_macro_link_infos( ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> list[RustLinkInfo]: return [d.info for d in resolve_rust_deps(ctx, dep_ctx) if d.proc_macro_marker == None] def inherited_exported_link_deps(ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> list[Dependency]: deps = {} for dep in _native_link_dependencies(ctx, dep_ctx): deps[dep.label] = dep for dep in resolve_rust_deps(ctx, dep_ctx): if dep.proc_macro_marker != None: continue for dep in dep.info.exported_link_deps: deps[dep.label] = dep return deps.values() def inherited_rust_cxx_link_group_info( ctx: AnalysisContext, dep_ctx: DepCollectionContext, link_strategy: LinkStrategy) -> RustCxxLinkGroupInfo | None: # Check minimum requirements if not cxx_is_gnu(ctx) or not ctx.attrs.auto_link_groups: return None link_graphs = inherited_linkable_graphs(ctx, dep_ctx) link_group = get_link_group(ctx) # Assume a rust executable wants to use link groups if a link group map # is present link_group_info = get_link_group_info(ctx, link_graphs, link_strategy) if link_group_info == None: return None link_groups = link_group_info.groups link_group_mappings = link_group_info.mappings link_group_preferred_linkage = get_link_group_preferred_linkage(link_groups.values()) auto_link_group_specs = get_auto_link_group_specs(ctx, link_group_info) linkable_graph = create_linkable_graph( ctx, deps = link_graphs, ) reduced_linkable_graph = reduce_linkable_graph(linkable_graph) linkable_graph_node_map = reduced_linkable_graph.nodes executable_deps = [] for g in link_graphs: if g.label in linkable_graph_node_map: executable_deps.append(g.label) else: # handle labels that are mutated by version alias executable_deps.append(g.nodes.value.label) public_link_group_nodes = get_public_link_group_nodes( linkable_graph_node_map, link_group_mappings, executable_deps, link_group, ) linked_link_groups = create_link_groups( ctx = ctx, link_groups = link_groups, link_strategy = link_strategy, linkable_graph = reduced_linkable_graph, link_group_mappings = link_group_mappings, link_group_preferred_linkage = link_group_preferred_linkage, executable_deps = executable_deps, linker_flags = [], link_group_specs = auto_link_group_specs, other_roots = [], prefer_stripped_objects = False, # Does Rust ever use stripped objects? anonymous = ctx.attrs.anonymous_link_groups, public_nodes = public_link_group_nodes, ) auto_link_groups = {} link_group_libs = {} for name, linked_link_group in linked_link_groups.libs.items(): auto_link_groups[name] = linked_link_group.artifact if linked_link_group.library != None: link_group_libs[name] = linked_link_group.library roots = set(executable_deps) pic_behavior = PicBehavior("always_enabled") if link_strategy == LinkStrategy("static_pic") else PicBehavior("supported") is_executable_link = True exec_linkables = collect_linkables( reduced_linkable_graph, is_executable_link, link_strategy, link_group_preferred_linkage, pic_behavior, roots, ) build_context = BuildLinkGroupsContext( public_nodes = public_link_group_nodes, linkable_graph = reduced_linkable_graph, link_groups = link_groups, link_group_mappings = link_group_mappings, link_group_preferred_linkage = link_group_preferred_linkage, link_strategy = link_strategy, pic_behavior = pic_behavior, link_group_libs = { name: (lib.label, lib.shared_link_infos) for name, lib in link_group_libs.items() }, prefer_stripped = False, prefer_optimized = False, ) labels_to_links = get_filtered_labels_to_links_map( link_group = link_group, linkables = exec_linkables, is_executable_link = is_executable_link, build_context = build_context, force_static_follows_dependents = True, ) return RustCxxLinkGroupInfo( filtered_links = get_filtered_links(labels_to_links.map), symbol_files_info = LinkInfo( pre_flags = linked_link_groups.symbol_ldflags, ), filtered_targets = get_filtered_targets(labels_to_links.map), link_group_info = link_group_info, link_group_libs = link_group_libs, labels_to_links_map = labels_to_links.map, targets_consumed_by_link_groups = linked_link_groups.targets_consumed_by_link_groups, link_group_preferred_linkage = link_group_preferred_linkage, ) def inherited_merged_link_infos( ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> dict[ConfiguredTargetLabel, MergedLinkInfo]: infos = {} for d in _native_link_dependencies(ctx, dep_ctx): g = d.get(MergedLinkInfo) if g: infos[d.label.configured_target()] = g for info in _rust_non_proc_macro_link_infos(ctx, dep_ctx): infos.update(info.merged_link_infos) return infos def inherited_shared_libs( ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> list[SharedLibraryInfo]: infos = [] infos.extend([d[SharedLibraryInfo] for d in _native_link_dependencies(ctx, dep_ctx)]) infos.extend([d.shared_libs for d in _rust_non_proc_macro_link_infos(ctx, dep_ctx)]) return infos def inherited_linkable_graphs(ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> list[LinkableGraph]: deps = {} for d in _native_link_dependencies(ctx, dep_ctx): g = d.get(LinkableGraph) if g: deps[g.label] = g for info in _rust_non_proc_macro_link_infos(ctx, dep_ctx): for g in info.linkable_graphs: deps[g.label] = g return deps.values() def inherited_link_group_lib_infos(ctx: AnalysisContext, dep_ctx: DepCollectionContext) -> list[LinkGroupLibInfo]: # There are no special Rust -> Rust versions of this provider deps = {} for d in resolve_deps(ctx, dep_ctx): i = d.dep.get(LinkGroupLibInfo) if i: deps[d.dep.label] = i return deps.values() def inherited_rust_external_debug_info( ctx: AnalysisContext, dep_ctx: DepCollectionContext, link_strategy: LinkStrategy) -> list[ArtifactTSet]: toolchain_info = ctx.attrs._rust_toolchain[RustToolchainInfo] return [strategy_info(toolchain_info, d.info, link_strategy).external_debug_info for d in resolve_rust_deps(ctx, dep_ctx)] def inherited_external_debug_info( ctx: AnalysisContext, dep_ctx: DepCollectionContext, dwo_output_directory: Artifact | None, dep_link_strategy: LinkStrategy) -> ArtifactTSet: inherited_debug_infos = [] inherited_link_infos = [] toolchain_info = ctx.attrs._rust_toolchain[RustToolchainInfo] for d in resolve_deps(ctx, dep_ctx): if RustLinkInfo in d.dep: inherited_debug_infos.append(strategy_info(toolchain_info, d.dep[RustLinkInfo], dep_link_strategy).external_debug_info) inherited_link_infos.extend(d.dep[RustLinkInfo].merged_link_infos.values()) elif MergedLinkInfo in d.dep: inherited_link_infos.append(d.dep[MergedLinkInfo]) inherited_debug_infos.append(make_artifact_tset( actions = ctx.actions, label = ctx.label, children = filter( None, [x._external_debug_info.get(dep_link_strategy) for x in inherited_link_infos], ), )) return make_artifact_tset( actions = ctx.actions, label = ctx.label, artifacts = filter(None, [dwo_output_directory]), children = inherited_debug_infos, ) def normalize_crate(label: str | ResolvedStringWithMacros) -> str | ResolvedStringWithMacros: return label.replace("-", "_") if is_string(label) else label def attr_simple_crate_for_filenames(ctx: AnalysisContext) -> str: """ A "good enough" identifier to use in filenames. Buck wants to have filenames of artifacts figured out before we begin building them. Normally we want a crate foo to produce artifact libfoo.rlib; but if crate_dynamic is being used, the true crate name is not known until later. In this situation we use the rule's name in place of the true crate name in filenames. # produces libordinary.rlib rust_library( name = "ordinary", crate = "ordinary", ) # produces libthrift_generated.rlib rust_library( name = "thrift-generated", crate_dynamic = ":get-namespace-from-thrift-file", ) """ return normalize_crate(ctx.attrs.crate or ctx.label.name) def attr_crate(ctx: AnalysisContext) -> CrateName: """ The true user-facing name of the crate, which may only be known at build time, not during analysis. """ dynamic = getattr(ctx.attrs, "crate_dynamic", None) if dynamic: dynamic = dynamic.get(DefaultInfo).default_outputs[0] return CrateName( simple = normalize_crate(ctx.attrs.crate or ctx.label.name), dynamic = dynamic, ) def attr_soname(ctx: AnalysisContext) -> str: """ Get the shared library name to set for the given rust library. """ linker_info = get_cxx_toolchain_info(ctx).linker_info if ctx.attrs.soname != None: return get_shared_library_name_for_param(linker_info, ctx.attrs.soname) return get_default_shared_library_name(linker_info, ctx.label)

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