Skip to main content
Glama
linker.bzl11.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", "LinkerInfo", "LinkerType") load("@prelude//utils:arglike.bzl", "ArgLike") load("@prelude//utils:expect.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, # The format to use for the versioned shared library extension if not set in the toolchain. default_shared_library_versioned_extension_format = str, # How to format arguments to the linker to set a shared lib name. shared_library_name_linker_flags_format = list[str], # Flags to pass to the linker to make it generate a shared library. shared_library_flags = list[str], ) # 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 = list[str], # Flags to pass to the linker to make it generate a shared library. shared_library_flags = list[ArgLike], ) LINKERS = { LinkerType("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"], ), LinkerType("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"], ), LinkerType("wasm"): Linker( default_shared_library_extension = "wasm", default_shared_library_versioned_extension_format = "{}.wasm", shared_library_name_linker_flags_format = [], # lld supports this, at least. # See https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md#llvm-implementation shared_library_flags = ["-shared"], ), LinkerType("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"], ), } PDB_SUB_TARGET = "pdb" def _sanitize(s: str) -> str: return s.replace("/", "_") # NOTE(agallagher): Does this belong in the native/shared_libraries.bzl? def get_shared_library_name( linker_info: LinkerInfo, short_name: str, apply_default_prefix: bool, version: [str, None] = None): """ Generate a platform-specific shared library name based for the given rule. """ if version == None: full_name = linker_info.shared_library_name_format.format(short_name) else: full_name = linker_info.shared_library_versioned_name_format.format(short_name, version) if apply_default_prefix: full_name = linker_info.shared_library_name_default_prefix + full_name return full_name def _parse_ext_macro(name: str) -> [(str, [str, 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, name: str): """ 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, apply_default_prefix = False, version = version, ) return name # NOTE(agallagher): Does this belong in the native/shared_libraries.bzl? def get_default_shared_library_name(linker_info: LinkerInfo, 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 compatibility, but maybe we should? short_name = "{}_{}".format(_sanitize(label.package), _sanitize(label.name)) return get_shared_library_name(linker_info, short_name, apply_default_prefix = True) def get_shared_library_name_linker_flags(linker_type: LinkerType, soname: str, flag_overrides: [SharedLibraryFlagOverrides, None] = None) -> list[str]: """ 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: LinkerType, flag_overrides: [SharedLibraryFlagOverrides, None] = None) -> list[ArgLike]: """ 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: LinkerType, inputs: list[Artifact]) -> list[typing.Any]: """ Return linker args used to always link all the given inputs. """ args = [] if linker_type == LinkerType("gnu"): args.append("-Wl,--whole-archive") args.extend(inputs) args.append("-Wl,--no-whole-archive") elif linker_type == LinkerType("darwin"): for inp in inputs: args.append("-Xlinker") args.append("-force_load") args.append("-Xlinker") args.append(inp) elif linker_type == LinkerType("windows"): for inp in inputs: args.append(inp) args.append("/WHOLEARCHIVE:" + inp.basename) else: fail("Linker type {} not supported".format(linker_type)) return args def get_objects_as_library_args(linker_type: LinkerType, objects: list[Artifact]) -> list[typing.Any]: """ Return linker args used to link the given objects as a library. """ args = [] if linker_type == LinkerType("gnu"): args.append("-Wl,--start-lib") args.extend(objects) args.append("-Wl,--end-lib") elif linker_type == LinkerType("darwin") or linker_type == LinkerType("windows"): args.extend(objects) else: fail("Linker type {} not supported".format(linker_type)) return args def get_ignore_undefined_symbols_flags(linker_type: LinkerType) -> list[str]: """ Return linker args used to suppress undefined symbol errors. """ args = [] if linker_type == LinkerType("gnu"): args.append("-Wl,--allow-shlib-undefined") args.append("-Wl,--unresolved-symbols=ignore-all") elif linker_type == LinkerType("darwin"): args.append("-Wl,-undefined,dynamic_lookup") else: fail("Linker type {} not supported".format(linker_type)) return args def get_no_as_needed_shared_libs_flags(linker_type: LinkerType) -> list[str]: """ 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 == LinkerType("gnu"): args.append("-Wl,--no-as-needed") elif linker_type == LinkerType("darwin"): pass else: fail("Linker type {} not supported".format(linker_type)) return args def get_output_flags(linker_type: LinkerType, output: Artifact) -> list[ArgLike]: if linker_type == LinkerType("windows"): return ["/Brepro", cmd_args(output.as_output(), format = "/OUT:{}")] else: return ["-o", output.as_output()] def get_import_library( ctx: AnalysisContext, linker_type: LinkerType, output_short_path: str) -> (Artifact | None, list[ArgLike]): if linker_type == LinkerType("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, [] def get_deffile_flags( ctx: AnalysisContext, linker_type: LinkerType) -> list[ArgLike]: if linker_type == LinkerType("windows") and ctx.attrs.deffile != None: return [ cmd_args(ctx.attrs.deffile, format = "/DEF:{}"), ] else: return [] def get_rpath_origin( linker_type: LinkerType) -> str: """ Return the macro that runtime loaders resolve to the main executable at runtime. """ if linker_type == LinkerType("gnu"): return "$ORIGIN" if linker_type == LinkerType("darwin"): return "@loader_path" fail("Linker type {} not supported".format(linker_type)) def is_pdb_generated( linker_type: LinkerType, linker_flags: list[[str, ResolvedStringWithMacros]]) -> bool: if linker_type != LinkerType("windows"): return False for flag in reversed(linker_flags): flag = str(flag).upper() if flag.startswith('"/DEBUG') or flag.startswith('"-DEBUG'): # The last one should be not /DEBUG:NONE return not flag.endswith('DEBUG:NONE"') return False def get_pdb_providers( pdb: Artifact, binary: Artifact): return [DefaultInfo(default_output = pdb, other_outputs = [binary])] DUMPBIN_SUB_TARGET = "dumpbin" def get_dumpbin_providers( ctx: AnalysisContext, binary: Artifact, dumpbin_toolchain_path: Artifact) -> list[Provider]: dumpbin_headers_out = ctx.actions.declare_output(binary.short_path + ".dumpbin_headers") ctx.actions.run( cmd_args( cmd_args(dumpbin_toolchain_path, format = "{}/dumpbin.exe"), # We could use /ALL to display all information in one action, but that's too # expensive in practice "/HEADERS", binary, cmd_args(dumpbin_headers_out.as_output(), format = "/OUT:{}"), ), category = "dumpbin_headers", identifier = binary.short_path, ) return [DefaultInfo(sub_targets = { "headers": [DefaultInfo( default_output = dumpbin_headers_out, )], })]

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