Skip to main content
Glama
linkable_graph.bzl14.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. load("@prelude//cxx:cxx_toolchain_types.bzl", "PicBehavior") load("@prelude//cxx:headers.bzl", "CPrecompiledHeaderInfo") load("@prelude//cxx:platform.bzl", "cxx_by_platform") # TODO(mattpayne): Add this back once the type is supported by dependency mgmt # load("@prelude//cxx:shared_library_interface.bzl", "SharedInterfaceInfo") load("@prelude//linking:types.bzl", "Linkage") load("@prelude//python:python.bzl", "PythonLibraryInfo") load("@prelude//utils:expect.bzl", "expect") load( "@prelude//utils:graph_utils.bzl", "depth_first_traversal_by", ) load( "@prelude//utils:utils.bzl", "flatten", ) load( ":link_info.bzl", "LibOutputStyle", "LinkInfo", # @unused Used as a type "LinkInfos", "LinkStrategy", "LinkerFlags", "MergedLinkInfo", "get_lib_output_style", "get_output_styles_for_linkage", _get_link_info = "get_link_info", ) load( ":shared_libraries.bzl", "SharedLibraries", ) # A provider with information used to link a rule into a shared library. # Potential omnibus roots must provide this so that omnibus can link them # here, in the context of the top-level packaging rule. LinkableRootInfo = provider( # @unsorted-dict-items fields = { "label": provider_field(Label), "link_infos": provider_field(typing.Any, default = None), # LinkInfos "name": provider_field(typing.Any, default = None), # [str, None] "deps": provider_field(typing.Any, default = None), # ["label"] }, ) ############################################################################### # Linkable Graph collects information on a node in the target graph that # contains linkable output. This graph information may then be provided to any # consumers of this target. ############################################################################### _DisallowConstruction = record() _TargetSourceType = Artifact | str | tuple LinkableNode = record( # Attribute labels on the target. labels = field(list[str], []), # Preferred linkage for this target. preferred_linkage = field(Linkage, Linkage("any")), default_link_strategy = field(LinkStrategy), # Linkable deps of this target. deps = field(list[Label], []), # Exported linkable deps of this target. # # We distinguish between deps and exported deps so that when creating shared # libraries in a large graph we only need to link each library against its # deps and their (transitive) exported deps. This helps keep link lines smaller # and produces more efficient libs (for example, DT_NEEDED stays a manageable size). exported_deps = field(list[Label], []), # List of both deps and exported deps. We traverse linkable graph lots of times # and preallocating this list saves RAM during analysis all_deps = field(list[Label], []), # Link infos for all supported lib output styles supported by this node. This should have a value # for every output_style supported by the preferred linkage. link_infos = field(dict[LibOutputStyle, LinkInfos], {}), # Contains the linker flags for this node. # Note: The values in link_infos will already be adding in the exported_linker_flags # TODO(cjhopman): We should probably make all use of linker_flags explicit, but that may need to wait # for all link strategies to operate on the LinkableGraph. linker_flags = field(LinkerFlags), # Shared libraries provided by this target. Used if this target is # excluded. shared_libs = field(SharedLibraries, SharedLibraries(libraries = [])), # The soname this node would use in default link strategies. May be used by non-default # link strategies as a lib's soname. default_soname = field(str | None), # Records Android's can_be_asset value for the node. This indicates whether the node can be bundled # as an asset in android apks. can_be_asset = field(bool), # Collected target sources from the target. srcs = field(list[_TargetSourceType]), # Whether the node should appear in the android mergemap (which provides information about the original # soname->final merged lib mapping) include_in_android_mergemap = field(bool), # Don't follow dependents on this node even if has preferred linkage static ignore_force_static_follows_dependents = field(bool), # Shared interface provider for this node. # TODO(mattpayne): This type is incompatible with Autodeps. # Once the pyautotargets service is rolled out, we can change it back. # It should be SharedInterfaceInfo | None shared_interface_info = field(typing.Any), # Should this library only be used for build time linkage stub = field(bool), # Only allow constructing within this file. _private = _DisallowConstruction, ) LinkableGraphNode = record( # Target/label of this node label = field(Label), # If this node has linkable output, it's linkable data linkable = field([LinkableNode, None]), # All potential root notes for an omnibus link (e.g. C++ libraries, # C++ Python extensions). roots = field(dict[Label, LinkableRootInfo]), # Exclusions this node adds to the Omnibus graph excluded = field(dict[Label, None]), # Only allow constructing within this file. _private = _DisallowConstruction, ) LinkableGraphTSet = transitive_set() # The LinkableGraph for a target holds all the transitive nodes, roots, and exclusions # from all of its dependencies. # # TODO(cjhopman): Rather than flattening this at each node, we should build up an actual # graph structure. LinkableGraph = provider(fields = { # Target identifier of the graph. "label": provider_field(typing.Any, default = None), # Label "nodes": provider_field(typing.Any, default = None), # "LinkableGraphTSet" }) # Used to tag a rule as providing a shared native library that may be loaded # dynamically, at runtime (e.g. via `dlopen`). DlopenableLibraryInfo = provider(fields = {}) def _get_required_outputs_for_linkage(linkage: Linkage) -> list[LibOutputStyle]: if linkage == Linkage("shared"): return [LibOutputStyle("shared_lib")] return get_output_styles_for_linkage(linkage) def _get_target_sources(ctx: AnalysisContext) -> list[_TargetSourceType]: srcs = [] if hasattr(ctx.attrs, "srcs"): srcs.extend(ctx.attrs.srcs) if hasattr(ctx.attrs, "platform_srcs"): srcs.extend(flatten(cxx_by_platform(ctx, ctx.attrs.platform_srcs))) return srcs def create_linkable_node( ctx: AnalysisContext, default_soname: str | None, preferred_linkage: Linkage = Linkage("any"), default_link_strategy: LinkStrategy = LinkStrategy("shared"), deps: list[Dependency | LinkableGraph] = [], exported_deps: list[Dependency | LinkableGraph] = [], link_infos: dict[LibOutputStyle, LinkInfos] = {}, shared_libs: SharedLibraries = SharedLibraries(libraries = []), can_be_asset: bool = True, include_in_android_mergemap: bool = True, linker_flags: [LinkerFlags, None] = None, ignore_force_static_follows_dependents: bool = False, # TODO(mattpayne): This type is incompatible with Autodeps. # Once the pyautotargets service is rolled out, we can change it back. # It should be SharedInterfaceInfo | None shared_interface_info: typing.Any = None, stub: bool = False) -> LinkableNode: for output_style in _get_required_outputs_for_linkage(preferred_linkage): expect( output_style in link_infos, "must have {} link info".format(output_style), ) if not linker_flags: linker_flags = LinkerFlags() deps = linkable_deps(deps) exported_deps = linkable_deps(exported_deps) return LinkableNode( labels = ctx.attrs.labels, preferred_linkage = preferred_linkage, default_link_strategy = default_link_strategy, deps = deps, exported_deps = exported_deps, all_deps = deps + exported_deps, link_infos = link_infos, shared_libs = shared_libs, can_be_asset = can_be_asset, srcs = _get_target_sources(ctx), include_in_android_mergemap = include_in_android_mergemap, default_soname = default_soname, linker_flags = linker_flags, ignore_force_static_follows_dependents = ignore_force_static_follows_dependents, shared_interface_info = shared_interface_info, stub = stub, _private = _DisallowConstruction(), ) def create_linkable_graph_node( ctx: AnalysisContext, linkable_node: [LinkableNode, None] = None, roots: dict[Label, LinkableRootInfo] = {}, excluded: dict[Label, None] = {}, label: Label | None = None) -> LinkableGraphNode: if not label: label = ctx.label return LinkableGraphNode( label = label, linkable = linkable_node, roots = roots, excluded = excluded, _private = _DisallowConstruction(), ) def create_linkable_graph( ctx: AnalysisContext, node: [LinkableGraphNode, None] = None, # This list of deps must include all deps referenced by the LinkableGraphNode. deps: list[[LinkableGraph, Dependency]] = []) -> LinkableGraph: graph_deps = [] for d in deps: if isinstance(d, LinkableGraph): graph_deps.append(d) else: graph = d.get(LinkableGraph) if graph: graph_deps.append(graph) if node and node.linkable: deps_labels = set([x.label for x in graph_deps]) for l in [node.linkable.deps, node.linkable.exported_deps]: # buildifier: disable=confusing-name for d in l: if not d in deps_labels: fail("LinkableNode had {} in its deps, but that label is missing from the node's linkable graph children (`{}`)".format(d, ", ".join(deps_labels))) children = [x.nodes for x in graph_deps] kwargs = { "children": children, } if node: kwargs["value"] = node label = node.label else: label = ctx.label return LinkableGraph( label = label, nodes = ctx.actions.tset(LinkableGraphTSet, **kwargs), ) ReducedLinkableGraph = record( # Label to information map for whole graph. # Does not have entry for executable nodes = field(dict[Label, LinkableNode]), # Order of linkable in the graph as it would go into linker argsfile # when building executable in static link strategy link_order = field(dict[Label, int]), ) def reduce_linkable_graph(graph: LinkableGraph) -> ReducedLinkableGraph: linkable_nodes = {} link_order = {} # Link groups machinery may be used by something that does not # store all information in dependency graph. E.g. python native dlopen # So gathering link ordering starting from executable label may not collect all # dependencies correctly. To account for that we add remaining pieces to # final result. There is no particular reasoning behing putting remaining linkables first, # but it is just more convenient to implement. # So to make it work properly we start with `1` instead of `0` and return default `0` for linkables # that we did not put into linkable graph nodes. link_order_idx = 1 for node in filter(None, graph.nodes.traverse()): if node.linkable: linkable_nodes[node.label] = node.linkable link_order[node.label] = link_order_idx link_order_idx += 1 return ReducedLinkableGraph( nodes = linkable_nodes, link_order = link_order, ) def get_linkable_graph_node_map_func(graph: LinkableGraph): def get_linkable_graph_node_map() -> dict[Label, LinkableNode]: nodes = graph.nodes.traverse() linkable_nodes = {} for node in filter(None, nodes): if node.linkable: linkable_nodes[node.label] = node.linkable return linkable_nodes return get_linkable_graph_node_map def linkable_deps(deps: list[Dependency | LinkableGraph]) -> list[Label]: labels = [] for dep in deps: if isinstance(dep, LinkableGraph): labels.append(dep.label) else: dep_info = linkable_graph(dep) if dep_info != None: labels.append(dep_info.label) return labels def linkable_graph(dep: Dependency) -> [LinkableGraph, None]: """ Helper to extract `LinkableGraph` from a dependency which also provides `MergedLinkInfo`. """ # We only care about "linkable" deps. if PythonLibraryInfo in dep or MergedLinkInfo not in dep or dep.label.sub_target == ["headers"]: return None if CPrecompiledHeaderInfo in dep: # `cxx_precompiled_header()` does not contribute to the link, only to compile return None return dep[LinkableGraph] def get_link_info( node: LinkableNode, output_style: LibOutputStyle, prefer_stripped: bool = False, prefer_optimized: bool = False) -> LinkInfo: info = _get_link_info( node.link_infos[output_style], prefer_stripped = prefer_stripped, prefer_optimized = prefer_optimized, ) return info def get_deps_for_link( node: LinkableNode, strategy: LinkStrategy, pic_behavior: PicBehavior, overridden_preferred_linkage: Linkage | None = None) -> list[Label]: """ Return deps to follow when linking against this node with the given link style. """ # If we're linking statically, include non-exported deps. output_style = get_lib_output_style(strategy, overridden_preferred_linkage if overridden_preferred_linkage else node.preferred_linkage, pic_behavior) if output_style != LibOutputStyle("shared_lib"): return node.all_deps else: return node.exported_deps def get_transitive_deps( link_infos: dict[Label, LinkableNode], roots: list[Label]) -> list[Label]: """ Return all transitive deps from following the given nodes. """ def find_transitive_deps(node: Label): return link_infos[node].all_deps return depth_first_traversal_by(link_infos, roots, find_transitive_deps)

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