Apply get_lib_name correctly to the C++ runtime libraries (#1508)
https://github.com/bazelbuild/rules_rust/pull/1500 added an additional `for_windows` parameter to `get_lib_name`. I missed the fact that we also pass that function to `map_each` here: https://github.com/bazelbuild/rules_rust/blob/main/rust/private/rustc.bzl#L1671
and as such, this code does not always work correctly (we don't get to pass the `for_windows` parameter, and internally at Google it ended up evaluating to `True` on Linux builds).
I tried to avoid flattening the `cc_toolchain.dynamic_runtime_lib` and `cc_toolchain.static_runtime_lib` depsets by using a lambda:
```
args.add_all(
cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
map_each = lambda x: get_lib_name(x, for_windows = toolchain.os.startswith("windows)),
format_each = "-ldylib=%s",
)
```
However it looks like such usage of lambdas is not allowed:
```
Error in add_all: to avoid unintended retention of analysis data structures,
the map_each function (declared at ...) must be declared by a top-level def statement
```
So instead of `get_lib_name` we now have `get_lib_name_default` and `get_lib_name_for_windows`.
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 97b8a17..70193d3 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -27,7 +27,8 @@
"expand_dict_value_locations",
"expand_list_element_locations",
"find_cc_toolchain",
- "get_lib_name",
+ "get_lib_name_default",
+ "get_lib_name_for_windows",
"get_preferred_artifact",
"is_exec_configuration",
"make_static_lib_symlink",
@@ -425,7 +426,7 @@
# Take the absolute value of hash() since it could be negative.
path_hash = abs(hash(lib.path))
- lib_name = get_lib_name(lib, for_windows = toolchain.os.startswith("windows"))
+ lib_name = get_lib_name_for_windows(lib) if toolchain.os.startswith("windows") else get_lib_name_default(lib)
prefix = "lib"
extension = ".a"
@@ -488,7 +489,7 @@
if _is_dylib(lib):
continue
artifact = get_preferred_artifact(lib, use_pic)
- name = get_lib_name(artifact, for_windows = toolchain.os.startswith("windows"))
+ name = get_lib_name_for_windows(artifact) if toolchain.os.startswith("windows") else get_lib_name_default(artifact)
# On Linux-like platforms, normally library base names start with
# `lib`, following the pattern `lib[name].(a|lo)` and we pass
@@ -1523,7 +1524,7 @@
"""
return crate.output.dirname
-def _portable_link_flags(lib, use_pic, ambiguous_libs, for_windows = False):
+def _portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name, for_windows):
artifact = get_preferred_artifact(lib, use_pic)
if ambiguous_libs and artifact.path in ambiguous_libs:
artifact = ambiguous_libs[artifact.path]
@@ -1561,14 +1562,14 @@
artifact.basename.startswith("libtest-") or artifact.basename.startswith("libstd-") or
artifact.basename.startswith("test-") or artifact.basename.startswith("std-")
):
- return ["-lstatic=%s" % get_lib_name(artifact, for_windows)]
+ return ["-lstatic=%s" % get_lib_name(artifact)]
return [
- "-lstatic=%s" % get_lib_name(artifact, for_windows),
+ "-lstatic=%s" % get_lib_name(artifact),
"-Clink-arg=-l%s" % (get_lib_name(artifact) if not for_windows else artifact.basename),
]
elif _is_dylib(lib):
return [
- "-ldylib=%s" % get_lib_name(artifact, for_windows),
+ "-ldylib=%s" % get_lib_name(artifact),
]
return []
@@ -1580,7 +1581,7 @@
if lib.alwayslink:
ret.extend(["-C", "link-arg=/WHOLEARCHIVE:%s" % get_preferred_artifact(lib, use_pic).path])
else:
- ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, for_windows = True))
+ ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_for_windows, for_windows = True))
return ret
def _make_link_flags_darwin(linker_input_and_use_pic_and_ambiguous_libs):
@@ -1593,7 +1594,7 @@
("link-arg=-Wl,-force_load,%s" % get_preferred_artifact(lib, use_pic).path),
])
else:
- ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs))
+ ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default, for_windows = False))
return ret
def _make_link_flags_default(linker_input_and_use_pic_and_ambiguous_libs):
@@ -1610,7 +1611,7 @@
"link-arg=-Wl,--no-whole-archive",
])
else:
- ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs))
+ ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default, for_windows = False))
return ret
def _libraries_dirnames(linker_input_and_use_pic_and_ambiguous_libs):
@@ -1639,10 +1640,13 @@
if toolchain.os == "windows":
make_link_flags = _make_link_flags_windows
+ get_lib_name = get_lib_name_for_windows
elif toolchain.os.startswith("mac") or toolchain.os.startswith("darwin"):
make_link_flags = _make_link_flags_darwin
+ get_lib_name = get_lib_name_default
else:
make_link_flags = _make_link_flags_default
+ get_lib_name = get_lib_name_default
# TODO(hlopko): Remove depset flattening by using lambdas once we are on >=Bazel 5.0
args_and_pic_and_ambiguous_libs = [(arg, use_pic, ambiguous_libs) for arg in dep_info.transitive_noncrates.to_list()]
diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl
index e342b3b..583dc6e 100644
--- a/rust/private/utils.bzl
+++ b/rust/private/utils.bzl
@@ -97,12 +97,11 @@
path_parts = path.split("/")
return [part for part in path_parts if part != "."]
-def get_lib_name(lib, for_windows = False):
- """Returns the name of a library artifact, eg. libabc.a -> abc
+def get_lib_name_default(lib):
+ """Returns the name of a library artifact.
Args:
lib (File): A library file
- for_windows: Whether we're building on Windows.
Returns:
str: The name of the library
@@ -126,11 +125,49 @@
# The library name is now everything minus the extension.
libname = ".".join(comps[:-1])
- if libname.startswith("lib") and not for_windows:
+ if libname.startswith("lib"):
return libname[3:]
else:
return libname
+# TODO: Could we remove this function in favor of a "windows" parameter in the
+# above function? It looks like currently lambdas cannot accept local parameters
+# so the following doesn't work:
+# args.add_all(
+# cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
+# map_each = lambda x: get_lib_name(x, for_windows = toolchain.os.startswith("windows)),
+# format_each = "-ldylib=%s",
+# )
+def get_lib_name_for_windows(lib):
+ """Returns the name of a library artifact for Windows builds.
+
+ Args:
+ lib (File): A library file
+
+ Returns:
+ str: The name of the library
+ """
+ # On macos and windows, dynamic/static libraries always end with the
+ # extension and potential versions will be before the extension, and should
+ # be part of the library name.
+ # On linux, the version usually comes after the extension.
+ # So regardless of the platform we want to find the extension and make
+ # everything left to it the library name.
+
+ # Search for the extension - starting from the right - by removing any
+ # trailing digit.
+ comps = lib.basename.split(".")
+ for comp in reversed(comps):
+ if comp.isdigit():
+ comps.pop()
+ else:
+ break
+
+ # The library name is now everything minus the extension.
+ libname = ".".join(comps[:-1])
+
+ return libname
+
def abs(value):
"""Returns the absolute value of a number.
diff --git a/test/unit/versioned_libs/versioned_libs_unit_test.bzl b/test/unit/versioned_libs/versioned_libs_unit_test.bzl
index 0a30525..1ede046 100644
--- a/test/unit/versioned_libs/versioned_libs_unit_test.bzl
+++ b/test/unit/versioned_libs/versioned_libs_unit_test.bzl
@@ -3,32 +3,32 @@
load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
# buildifier: disable=bzl-visibility
-load("//rust/private:utils.bzl", "get_lib_name")
+load("//rust/private:utils.bzl", "get_lib_name_default", "get_lib_name_for_windows")
def _produced_expected_lib_name_test_impl(ctx):
env = unittest.begin(ctx)
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.dylib")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.so")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.a")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "python.dll")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "python.lib")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.dylib")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.so")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.a")))
+ asserts.equals(env, "python", get_lib_name_for_windows(struct(basename = "python.dll")))
+ asserts.equals(env, "python", get_lib_name_for_windows(struct(basename = "python.lib")))
- asserts.equals(env, "python3", get_lib_name(struct(basename = "libpython3.dylib")))
- asserts.equals(env, "python3.8", get_lib_name(struct(basename = "libpython3.8.dylib")))
- asserts.equals(env, "python3", get_lib_name(struct(basename = "libpython3.a")))
- asserts.equals(env, "python3.8", get_lib_name(struct(basename = "libpython3.8.a")))
+ asserts.equals(env, "python3", get_lib_name_default(struct(basename = "libpython3.dylib")))
+ asserts.equals(env, "python3.8", get_lib_name_default(struct(basename = "libpython3.8.dylib")))
+ asserts.equals(env, "python3", get_lib_name_default(struct(basename = "libpython3.a")))
+ asserts.equals(env, "python3.8", get_lib_name_default(struct(basename = "libpython3.8.a")))
- asserts.equals(env, "python38", get_lib_name(struct(basename = "python38.dll")))
- asserts.equals(env, "python38m", get_lib_name(struct(basename = "python38m.dll")))
+ asserts.equals(env, "python38", get_lib_name_for_windows(struct(basename = "python38.dll")))
+ asserts.equals(env, "python38m", get_lib_name_for_windows(struct(basename = "python38m.dll")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.so.3")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.so.3.8")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.so.3.8.0")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.a.3")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.a.3.8")))
- asserts.equals(env, "python", get_lib_name(struct(basename = "libpython.a.3.8.0")))
- asserts.equals(env, "python-3.8.0", get_lib_name(struct(basename = "libpython-3.8.0.so.3.8.0")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.so.3")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.so.3.8")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.so.3.8.0")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.a.3")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.a.3.8")))
+ asserts.equals(env, "python", get_lib_name_default(struct(basename = "libpython.a.3.8.0")))
+ asserts.equals(env, "python-3.8.0", get_lib_name_default(struct(basename = "libpython-3.8.0.so.3.8.0")))
return unittest.end(env)