Allow dependencies on versioned shared libraries (#2066)
Fixes #2037
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index be80f40..e14014d 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -165,6 +165,8 @@
- "-@test_chdir_remote//sub:go_default_test"
- "-//tests/core/cgo:dylib_client"
- "-//tests/core/cgo:dylib_test"
+ - "-//tests/core/cgo:versioned_dylib_client"
+ - "-//tests/core/cgo:versioned_dylib_test"
- "-//tests/core/coverage:coverage_test_test"
- "-//tests/core/coverage:cross_cover_test_test"
- "-//tests/core/cross:cross_test"
@@ -323,6 +325,8 @@
- "-//tests/core/cgo:opts"
- "-//tests/core/cgo:dylib_test"
- "-//tests/core/cgo:dylib_client"
+ - "-//tests/core/cgo:versioned_dylib_test"
+ - "-//tests/core/cgo:versioned_dylib_client"
- "-//tests/core/cgo:cc_libs_test"
- "-//tests/core/cgo:pure"
- "-//tests/core/cgo:cc_srcs"
diff --git a/go/private/actions/binary.bzl b/go/private/actions/binary.bzl
index a5adc62..3e98f5a 100644
--- a/go/private/actions/binary.bzl
+++ b/go/private/actions/binary.bzl
@@ -21,7 +21,7 @@
load(
"@io_bazel_rules_go//go/private:common.bzl",
"ARCHIVE_EXTENSION",
- "SHARED_LIB_EXTENSIONS",
+ "has_shared_lib_extension",
)
def emit_binary(
@@ -61,7 +61,7 @@
cgo_dynamic_deps = [
d
for d in archive.cgo_deps.to_list()
- if any([d.basename.endswith(ext) for ext in SHARED_LIB_EXTENSIONS])
+ if has_shared_lib_extension(d.basename)
]
runfiles = go._ctx.runfiles(files = cgo_dynamic_deps).merge(archive.runfiles)
diff --git a/go/private/actions/link.bzl b/go/private/actions/link.bzl
index 27b5cfe..9350237 100644
--- a/go/private/actions/link.bzl
+++ b/go/private/actions/link.bzl
@@ -18,8 +18,8 @@
)
load(
"@io_bazel_rules_go//go/private:common.bzl",
- "SHARED_LIB_EXTENSIONS",
"as_iterable",
+ "has_shared_lib_extension",
)
load(
"@io_bazel_rules_go//go/private:mode.bzl",
@@ -115,7 +115,7 @@
cgo_dynamic_deps = [
d
for d in archive.cgo_deps.to_list()
- if any([d.basename.endswith(ext) for ext in SHARED_LIB_EXTENSIONS])
+ if has_shared_lib_extension(d.basename)
]
cgo_rpaths = []
for d in cgo_dynamic_deps:
diff --git a/go/private/common.bzl b/go/private/common.bzl
index f301822..4c5dc83 100644
--- a/go/private/common.bzl
+++ b/go/private/common.bzl
@@ -153,6 +153,48 @@
"darwin": ".dylib",
}.get(goos, ".so")
+def has_shared_lib_extension(path):
+ """
+ Matches filenames of shared libraries, with or without a version number extension.
+ """
+ return (has_simple_shared_lib_extension(path) or
+ has_versioned_shared_lib_extension(path))
+
+def has_simple_shared_lib_extension(path):
+ """
+ Matches filenames of shared libraries, without a version number extension.
+ """
+ if any([path.endswith(ext) for ext in SHARED_LIB_EXTENSIONS]):
+ return True
+ return False
+
+def has_versioned_shared_lib_extension(path):
+ """
+ Matches shared libraries with version names in the extension, i.e.
+ libmylib.so.2 or libmylib.so.2.10.
+ """
+ if not path[-1].isdigit():
+ return False
+
+ so_location = path.rfind(".so")
+ # Version extensions are only allowed for .so files
+ if so_location == -1:
+ return False
+ last_dot = so_location
+ for i in range(so_location + 3, len(path)):
+ if path[i] == ".":
+ if i - last_dot > 1:
+ last_dot = i
+ else:
+ return False
+ elif not path[i].isdigit():
+ return False
+
+ if last_dot == len(path):
+ return False
+
+ return True
+
MINIMUM_BAZEL_VERSION = "0.8.0"
def as_list(v):
diff --git a/go/private/rules/cgo.bzl b/go/private/rules/cgo.bzl
index dd6595f..7631f31 100644
--- a/go/private/rules/cgo.bzl
+++ b/go/private/rules/cgo.bzl
@@ -22,10 +22,11 @@
)
load(
"@io_bazel_rules_go//go/private:common.bzl",
- "SHARED_LIB_EXTENSIONS",
"as_iterable",
"as_list",
"as_set",
+ "has_simple_shared_lib_extension",
+ "has_versioned_shared_lib_extension",
"join_srcs",
"pkg_dir",
"split_srcs",
@@ -141,7 +142,7 @@
# us the static variant. We'll get one file for each transitive dependency,
# so the same file may appear more than once.
if (lib.basename.startswith("lib") and
- any([lib.basename.endswith(ext) for ext in SHARED_LIB_EXTENSIONS])):
+ has_simple_shared_lib_extension(lib.basename)):
# If the loader would be able to find the library using rpaths,
# use -L and -l instead of hard coding the path to the library in
# the binary. This gives users more flexibility. The linker will add
@@ -150,6 +151,13 @@
libname = lib.basename[len("lib"):lib.basename.rindex(".")]
clinkopts.extend(["-L", lib.dirname, "-l", libname])
inputs_direct.append(lib)
+ elif (lib.basename.startswith("lib") and
+ has_versioned_shared_lib_extension(lib.basename)):
+ # With a versioned shared library, we must use the full filename,
+ # otherwise the library will not be found by the linker.
+ libname = ":%s" % lib.basename
+ clinkopts.extend(["-L", lib.dirname, "-l", libname])
+ inputs_direct.append(lib)
else:
lib_opts.append(lib.path)
clinkopts.extend(cc_link_flags(d))
@@ -345,7 +353,7 @@
# us the static variant. We'll get one file for each transitive dependency,
# so the same file may appear more than once.
if (lib.basename.startswith("lib") and
- any([lib.basename.endswith(ext) for ext in SHARED_LIB_EXTENSIONS])):
+ has_simple_shared_lib_extension(lib.basename)):
# If the loader would be able to find the library using rpaths,
# use -L and -l instead of hard coding the path to the library in
# the binary. This gives users more flexibility. The linker will add
@@ -353,6 +361,12 @@
# the binary location, and we don't know where that is.
libname = lib.basename[len("lib"):lib.basename.rindex(".")]
linkopts.extend(["-L", lib.dirname, "-l", libname])
+ elif (lib.basename.startswith("lib") and
+ has_versioned_shared_lib_extension(lib.basename)):
+ # With a versioned shared library, we must use the full filename,
+ # otherwise the library will not be found by the linker.
+ libname = ":%s" % lib.basename
+ linkopts.extend(["-L", lib.dirname, "-l", libname])
else:
linkopts.append(lib.path)
linkopts.extend(cc_link_flags(d))
diff --git a/go/private/skylib/lib/new_sets.bzl b/go/private/skylib/lib/new_sets.bzl
index b6fb99d..3e203c1 100644
--- a/go/private/skylib/lib/new_sets.bzl
+++ b/go/private/skylib/lib/new_sets.bzl
@@ -20,7 +20,7 @@
values in the set can be retrieved using `sets.to_list(my_set)`.
"""
-load(":dicts.bzl", "dicts")
+load(":skylib/lib/dicts.bzl", "dicts")
def _make(elements = None):
"""Creates a new set.
diff --git a/go/private/skylib/lib/unittest.bzl b/go/private/skylib/lib/unittest.bzl
index 11af89f..9ab2857 100644
--- a/go/private/skylib/lib/unittest.bzl
+++ b/go/private/skylib/lib/unittest.bzl
@@ -19,8 +19,8 @@
assertions used to within tests.
"""
-load(":sets.bzl", "sets")
-load(":new_sets.bzl", new_sets = "sets")
+load(":skylib/lib/sets.bzl", "sets")
+load(":skylib/lib/new_sets.bzl", new_sets = "sets")
def _make(impl, attrs = None):
"""Creates a unit test rule from its implementation function.
diff --git a/tests/core/README.rst b/tests/core/README.rst
index 4c54d57..7204dff 100644
--- a/tests/core/README.rst
+++ b/tests/core/README.rst
@@ -21,6 +21,7 @@
* `race instrumentation <race/README.rst>`_
* `stdlib functionality <stdlib/README.rst>`_
* `Basic go_binary functionality <go_binary/README.rst>`_
+* `Starlark unit tests <starlark/README.rst>`_
* `coverage functionality <coverage/README.rst>`_
* `Import maps <importmap/README.rst>`_
* `Basic go_path functionality <go_path/README.rst>`_
diff --git a/tests/core/cgo/BUILD.bazel b/tests/core/cgo/BUILD.bazel
index 1b8a0e2..39275dd 100644
--- a/tests/core/cgo/BUILD.bazel
+++ b/tests/core/cgo/BUILD.bazel
@@ -68,6 +68,42 @@
)
go_test(
+ name = "versioned_dylib_test",
+ srcs = ["dylib_test.go"],
+ embed = [":versioned_dylib_client"],
+ rundir = ".",
+)
+
+go_library(
+ name = "versioned_dylib_client",
+ srcs = ["dylib_client.go"],
+ cdeps = select({
+ # This test exists just for versioned `.so`s on Linux,
+ # but we can reuse the above test's dylib so it passes on darwin,
+ # where filename suffixes are not used for library version.
+ "@io_bazel_rules_go//go/platform:darwin": [":darwin_imported_dylib"],
+ "//conditions:default": [":linux_imported_versioned_dylib"],
+ # TODO(jayconrod): Support windows, skip others.
+ }),
+ cgo = True,
+ importpath = "github.com/bazelbuild/rules_go/tests/core/cgo/dylib",
+)
+
+cc_library(
+ name = "linux_imported_versioned_dylib",
+ srcs = [":libimported.so.2"],
+ linkstatic = False,
+ tags = ["manual"],
+)
+
+cc_binary(
+ name = "libimported.so.2",
+ srcs = ["imported.c"],
+ linkshared = True,
+ tags = ["manual"],
+)
+
+go_test(
name = "cc_libs_test",
srcs = [
"cc_libs_darwin_test.go",
diff --git a/tests/core/cgo/README.rst b/tests/core/cgo/README.rst
index 15a7f31..98a2f96 100644
--- a/tests/core/cgo/README.rst
+++ b/tests/core/cgo/README.rst
@@ -14,6 +14,12 @@
(especially those provided with ``cc_import``) may only have dynamic versions,
and we should be able to link against them and find them at run-time.
+dylib_test
+----------
+
+Checks that Go binaries can link against dynamic C libraries that are only
+available as a versioned shared library, like ``libfoo.so.1``.
+
cc_libs_test
------------
diff --git a/tests/core/starlark/BUILD.bazel b/tests/core/starlark/BUILD.bazel
new file mode 100644
index 0000000..6d36a25
--- /dev/null
+++ b/tests/core/starlark/BUILD.bazel
@@ -0,0 +1,3 @@
+load(":common_tests.bzl", "common_test_suite")
+
+common_test_suite()
diff --git a/tests/core/starlark/README.rst b/tests/core/starlark/README.rst
new file mode 100644
index 0000000..e1f8df7
--- /dev/null
+++ b/tests/core/starlark/README.rst
@@ -0,0 +1,9 @@
+Starlark unit tests
+=======================
+
+common_test_suite
+---------
+
+Checks that ``has_shared_lib_extension`` from ``//go/private:common.bzl``
+correctly matches shared library filenames, which may optionally have a version
+number at the end.
diff --git a/tests/core/starlark/common_tests.bzl b/tests/core/starlark/common_tests.bzl
new file mode 100644
index 0000000..11eff53
--- /dev/null
+++ b/tests/core/starlark/common_tests.bzl
@@ -0,0 +1,38 @@
+load("@io_bazel_rules_go//go/private:common.bzl", "has_shared_lib_extension")
+load("@io_bazel_rules_go//go/private:skylib/lib/unittest.bzl", "asserts", "unittest")
+
+def _versioned_shared_libraries_test(ctx):
+ env = unittest.begin(ctx)
+
+ # See //src/test/java/com/google/devtools/build/lib/rules/cpp:CppFileTypesTest.java
+ # for the corresponding native C++ rules tests.
+ asserts.true(env, has_shared_lib_extension("somelibrary.so"))
+ asserts.true(env, has_shared_lib_extension("somelibrary.so.2"))
+ asserts.true(env, has_shared_lib_extension("somelibrary.so.20"))
+ asserts.true(env, has_shared_lib_extension("somelibrary.so.20.2"))
+ asserts.true(env, has_shared_lib_extension("a/somelibrary.so.2"))
+ asserts.true(env, has_shared_lib_extension("somelibrary✅.so.2"))
+ asserts.true(env, has_shared_lib_extension("somelibrary✅.so.2.1"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so.e"))
+ asserts.false(env, has_shared_lib_extension("xx.1"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so.2e"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so.e2"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so.20.e2"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.a.2"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.a..2"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so.2."))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so."))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so.2🚫"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so.🚫2"))
+ asserts.false(env, has_shared_lib_extension("somelibrary.so🚫.2.0"))
+
+ return unittest.end(env)
+
+versioned_shared_libraries_test = unittest.make(_versioned_shared_libraries_test)
+
+def common_test_suite():
+ """Creates the test targets and test suite for common.bzl tests."""
+ unittest.suite(
+ "common_tests",
+ versioned_shared_libraries_test,
+ )