blob: 01ab3141cf9910dad1d1c9072e6ea472705416de [file] [log] [blame]
# Copyright 2025 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# LINT.IfChange(forked_exports)
"""
The cc_common.create_library_to_link function.
"""
load("@bazel_skylib//lib:paths.bzl", "paths")
load("//cc/common:cc_helper_internal.bzl", "is_versioned_shared_library", "path_contains_up_level_references")
load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal")
load("//cc/private/compile:lto_compilation_context.bzl", _EMPTY_LTO = "EMPTY_LTO_COMPILATION_CONTEXT")
load("//cc/private/link:lto_backends.bzl", "create_shared_non_lto_artifacts")
_warning = """ Don't use this field. It's intended for internal use and will be changed or removed
without warning."""
# A library the user can link to. This is different from a simple linker input in that it also has
# a library identifier.
LibraryToLinkInfo = provider(
"A library the user can link against.",
fields = {
"static_library": "Artifact of static library to be linked.",
"pic_static_library": "Artifact of pic static library to be linked.",
"dynamic_library": """Artifact of dynamic library to be linked. Always used for runtime
and used for linking if `interface_library` is not passed.""",
"interface_library": "Artifact of interface library to be linked.",
"resolved_symlink_dynamic_library": """The resolved `Artifact` of the dynamic library
to be linked if `dynamic_library` is a symlink, otherwise this is None.""",
"resolved_symlink_interface_library": """The resolved `Artifact` of the interface library
to be linked if `interface_library` is a symlink, otherwise this is None.""",
"objects": "List of object files in the library.",
"pic_objects": "List of pic object files in the library.",
"alwayslink": "Whether to link the static library/objects in the --whole_archive block.",
"lto_bitcode_files": "List of LTO bitcode files in the library.",
"pic_lto_bitcode_files": "List of pic LTO bitcode files in the library.",
"_library_identifier": _warning,
"_contains_objects": """
This is essential for start-end library functionality. _contains_objects is False when calling
cc_common.create_library_to_link with empty object files. This signifies to start-end that an
archive needs to be used. On the other hand cc_common.link will set object files to exactly
what's in the archive. Start-end library functionality may correctly expand the object files.
In case they are empty, this means also the archive is empty.""" + _warning,
"_disable_whole_archive": _warning,
"_must_keep_debug": """
TODO(b/338618120): This is just needed for Go, do not expose to Starlark and try to remove it.
This was introduced to let a linker input declare that it needs debug info in the executable.
Specifically, this was introduced for linking Go into a C++ binary when using the gccgo compiler.
""" + _warning,
"_lto_compilation_context": _warning,
"_pic_lto_compilation_context": _warning,
"_shared_non_lto_backends": _warning,
"_pic_shared_non_lto_backends": _warning,
},
)
# buildifier: disable=function-docstring-args
# buildifier: disable=function-docstring-return
def make_library_to_link(
*,
static_library = None,
pic_static_library = None,
dynamic_library = None,
interface_library = None,
resolved_symlink_dynamic_library = None,
resolved_symlink_interface_library = None,
objects = [],
pic_objects = [],
alwayslink = False,
_library_identifier,
_contains_objects = False,
_disable_whole_archive = False,
_must_keep_debug = False,
_lto_compilation_context = None,
_pic_lto_compilation_context = None,
_shared_non_lto_backends = {},
_pic_shared_non_lto_backends = {}):
"""Constructs a frozen LibraryToLink."""
if not (static_library or pic_static_library or dynamic_library or interface_library):
fail("At least on library artifact must be non null")
if resolved_symlink_dynamic_library and not dynamic_library:
fail("resolved_symlink_dynamic_library without dynamic_library")
if resolved_symlink_interface_library and not interface_library:
fail("resolved_symlink_interface_library without interface_library")
return LibraryToLinkInfo(
static_library = static_library,
pic_static_library = pic_static_library,
interface_library = interface_library,
dynamic_library = dynamic_library,
resolved_symlink_dynamic_library = resolved_symlink_dynamic_library,
resolved_symlink_interface_library = resolved_symlink_interface_library,
objects = _cc_internal.freeze(objects),
pic_objects = _cc_internal.freeze(pic_objects),
alwayslink = alwayslink,
# LTO data duplication is forced by public APIs
lto_bitcode_files = _cc_internal.freeze(_lto_compilation_context.lto_bitcode_inputs.keys() if _lto_compilation_context else []),
pic_lto_bitcode_files = _cc_internal.freeze(_pic_lto_compilation_context.lto_bitcode_inputs.keys() if _pic_lto_compilation_context else []),
_library_identifier = _library_identifier,
_contains_objects = _contains_objects,
_must_keep_debug = _must_keep_debug,
_disable_whole_archive = _disable_whole_archive,
_lto_compilation_context = _lto_compilation_context,
_pic_lto_compilation_context = _pic_lto_compilation_context,
_shared_non_lto_backends = _cc_internal.freeze(_shared_non_lto_backends),
_pic_shared_non_lto_backends = _cc_internal.freeze(_pic_shared_non_lto_backends),
)
def create_library_to_link(
*,
actions,
feature_configuration = None,
cc_toolchain = None,
static_library = None,
pic_static_library = None,
dynamic_library = None,
interface_library = None,
pic_objects = None,
objects = None,
lto_compilation_context = None,
pic_lto_compilation_context = None,
alwayslink = False,
dynamic_library_symlink_path = "",
interface_library_symlink_path = "",
must_keep_debug = False):
"""Creates a `LibraryToLink` struct with information for linking a single library.
Validates the names of all the passed in `File`s.
Creates a symlink for `dynamic_library` and `interface_library` in `_solib_` directory.
Args:
actions: (Actions) `actions` object.
feature_configuration: (FeatureConfiguration) `feature_configuration` to be queried.
cc_toolchain: (CcToolchainInfo) CcToolchainInfo provider to be used.
static_library: (File|None) Static library to be linked.
pic_static_library: (File|None) PIC static library to be linked.
dynamic_library: (File|None) Dynamic library to be linked. Always used for runtime and used for
linking if `interface_library` is not passed.
interface_library: (File|None) Interface library to be linked.
pic_objects: (list[File]|None) Experimental, do not use.
objects: (list[File]|None) Experimental, do not use.
lto_compilation_context: (LtoCompilationContext) Experimental, do not use.
pic_lto_compilation_context: (LtoCompilationContext) Experimental, do not use.
alwayslink: (bool) Whether to link the static library/objects in the --whole_archive block.
dynamic_library_symlink_path: (str) Override the default path of the dynamic library link
in the solib directory. Empty string to use the default.
interface_library_symlink_path: (str) Override the default path of the interface library
link in the solib directory. Empty string to use the default.
must_keep_debug: (bool) Experimental, do not use.
Returns:
(LibraryToLink)
"""
errors = []
_fail = lambda error: errors.append(error)
_validate_ext = lambda *args, **kwargs: _validate_extension(fail = _fail, *args, **kwargs)
if dynamic_library_symlink_path:
_validate_symlink_path("dynamic_library_symlink_path", dynamic_library_symlink_path)
_validate_ext(
dynamic_library_symlink_path,
[".so", ".dylib", ".dll", ".pyd", ".wasm", ".tgt", ".vpi"],
is_versioned_shared_library,
empty_ext = True,
)
if interface_library_symlink_path:
_validate_symlink_path("interface_library_symlink_path", interface_library_symlink_path)
_validate_ext(interface_library_symlink_path, [".ifso", ".tbd", ".lib", ".dll.a"])
if static_library:
if alwayslink:
_validate_ext(static_library, [".a", ".lib", ".rlib"] + [".lo"], not_ext = [".pic.lo", ".if.lib"], empty_ext = True)
else:
_validate_ext(static_library, [".a", ".lib", ".rlib"], not_ext = [".lo.lib", ".if.lib"], empty_ext = True)
if pic_static_library:
if alwayslink:
# Ideally we'd allow only `.pic.lo` instead of `.lo`, `.pic.a` instead of `.a`, `.lo.lib` instead of `.lib`
# but in reality pic libs are often called same as no-pic.
_validate_ext(pic_static_library, [".a", ".lib", ".rlib"] + [".lo"], not_ext = [".if.lib"], empty_ext = True)
else:
_validate_ext(pic_static_library, [".a", ".lib", ".rlib"], not_ext = [".lo.lib", ".if.lib"], empty_ext = True)
resolved_symlink_dynamic_library = None
if dynamic_library:
_validate_ext(dynamic_library, [".so", ".dylib", ".dll", ".pyd", ".wasm", ".tgt", ".vpi"], is_versioned_shared_library, empty_ext = True)
if not cc_toolchain:
fail("If you pass 'dynamic_library', you must also pass a 'cc_toolchain'")
if not feature_configuration:
fail("If you pass 'dynamic_library', you must also pass a 'feature_configuration'")
if not feature_configuration.is_enabled("targets_windows"):
resolved_symlink_dynamic_library = dynamic_library
if dynamic_library_symlink_path:
if dynamic_library.short_path.startswith("_solib_"):
fail("dynamic_library must not be a symbolic link in the solib directory. Got '%s'" % dynamic_library.short_path)
dynamic_library = _cc_internal.dynamic_library_symlink2(actions, dynamic_library, cc_toolchain._solib_dir, dynamic_library_symlink_path)
else:
dynamic_library = _cc_internal.dynamic_library_symlink(actions, dynamic_library, cc_toolchain._solib_dir, True, True)
resolved_symlink_interface_library = None
if interface_library:
_validate_ext(interface_library, [".ifso", ".tbd", ".lib", ".dll.a", ".so", ".dylib"])
if not cc_toolchain:
fail("If you pass 'interface_library', you must also pass a 'cc_toolchain'")
if not feature_configuration:
fail("If you pass 'interface_library', you must also pass a 'feature_configuration'")
if not feature_configuration.is_enabled("targets_windows"):
resolved_symlink_interface_library = interface_library
if interface_library_symlink_path:
if interface_library.short_path.startswith("_solib_"):
fail("dynamic_library must not be a symbolic link in the solib directory. Got '%s'" % dynamic_library.short_path)
interface_library = _cc_internal.dynamic_library_symlink2(actions, interface_library, cc_toolchain._solib_dir, interface_library_symlink_path)
else:
interface_library = _cc_internal.dynamic_library_symlink(actions, interface_library, cc_toolchain._solib_dir, True, True)
if errors:
fail("\n".join(errors))
identifier = static_library or pic_static_library or dynamic_library or interface_library
if not identifier:
fail("Must pass at least one of the following parameters: static_library, pic_static_library, " +
"dynamic_library and interface_library.")
library_identifier = identifier.short_path.removesuffix(".pic.a").removesuffix(".nopic.a").removesuffix(".pic.lo")
library_identifier = paths.replace_extension(library_identifier, "")
objects = objects or []
pic_objects = pic_objects or []
if lto_compilation_context and static_library and objects != None:
shared_non_lto_backends = create_shared_non_lto_artifacts(
actions,
lto_compilation_context,
False,
feature_configuration,
cc_toolchain,
False,
objects,
)
else:
shared_non_lto_backends = None
lto_compilation_context = lto_compilation_context or _EMPTY_LTO
pic_lto_compilation_context = pic_lto_compilation_context or _EMPTY_LTO
return make_library_to_link(
static_library = static_library,
pic_static_library = pic_static_library,
objects = objects,
pic_objects = pic_objects,
dynamic_library = dynamic_library,
resolved_symlink_dynamic_library = resolved_symlink_dynamic_library,
interface_library = interface_library,
resolved_symlink_interface_library = resolved_symlink_interface_library,
alwayslink = alwayslink,
_library_identifier = library_identifier,
_must_keep_debug = must_keep_debug,
_lto_compilation_context = lto_compilation_context,
_pic_lto_compilation_context = pic_lto_compilation_context,
_shared_non_lto_backends = shared_non_lto_backends,
_contains_objects = bool(objects) or bool(pic_objects),
)
def _validate_symlink_path(attr, path):
if not path or paths.is_absolute(path) or path_contains_up_level_references(path):
fail("%s must be a relative file path. Got '%s" % (attr, path))
def _validate_extension(path, extensions, func = None, not_ext = [], fail = fail, empty_ext = False):
path = getattr(path, "basename", path) # Handle str|File
for ext in not_ext:
if path.endswith(ext):
fail("'%s' does not have any of the allowed extensions %s" % (path, ", ".join(extensions)))
for ext in extensions:
if path.endswith(ext):
return
if empty_ext:
_, actual_ext = paths.split_extension(path)
if actual_ext == "":
return
if func and func(struct(basename = path)):
return
fail("'%s' does not have any of the allowed extensions %s" % (path, ", ".join(extensions)))
# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/link/create_library_to_link.bzl:forked_exports)