I have started a new C++ project and I'm trying to configure Bazel and Google Test.
Bazel is configured to build the core project as a DLL. The DLL builds fine, and I can also build an exe which depends on the DLL. Google's sample tests run fine as they don't reference the library, but when a test links a library header I get linker errors like this:
error LNK2019: unresolved external symbol "public: void __cdecl CallbackLogger::registerLogInfo(void (__cdecl*)(char const *,int))" (?registerLogInfo@CallbackLogger@@QEAAXP6AXPEBDH@Z@Z) referenced in function "private: virtual void __cdecl CallbackLoggerTest_RegisterLogInfoCallback_Test::TestBody(void)" (?TestBody@CallbackLoggerTest_RegisterLogInfoCallback_Test@@EEAAXXZ)
My project has this structure:
src
WORKSPACE
MODULE.bazel
core
... some other files/folders
logger
callback_logger.h
callback_logger.cpp
windows_dll_library.bzl
BUILD
test
BUILD
callback_logger_test.cpp
The test code:
/test/callback_logger_test.cpp
#include "../core/logger/callback_logger.h"
#include <gtest/gtest.h>
#include <iostream>
using namespace std;
void test_callback(const char *message, int size)
{
string str(message, size);
cout << "Message received: " + str << endl;
}
// Tests registering loginfo callback
TEST(CallbackLoggerTest, RegisterLogInfoCallback)
{
auto logger = new CallbackLogger();
logger->registerLogInfo(&test_callback);
}
And these are my BUILD files:
/core/BUILD
:
load(":windows_dll_library.bzl", "windows_dll_library")
# Define the shared library
windows_dll_library(
name = "core",
srcs = glob(["**/*.cpp"]),
hdrs = glob(["**/*.h"]),
# Define COMPILING_DLL to export symbols during compiling the DLL.
copts = ["/DCOMPILING_DLL"],
visibility = ["//test:__pkg__", "//core_runner:__pkg__"],
)
/test/BUILD
:
cc_test(
name = "test",
size = "small",
srcs = glob(["*.cpp"]),
deps = [
"@googletest//:gtest",
"@googletest//:gtest_main",
"//core:core",
],
)
I'm running the tests with this Bazel command:
bazel test --test_output=all //test:test
How do I configure Bazel to link the library?
Extra info
Here is the bazel config for the windows DLL library:
windows_dll_library.bzl
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_import", "cc_library")
def windows_dll_library(
name,
srcs = [],
deps = [],
hdrs = [],
visibility = None,
**kwargs):
"""A simple windows_dll_library rule for builing a DLL Windows."""
dll_name = name + ".dll"
import_lib_name = name + "_import_lib"
import_target_name = name + "_dll_import"
# Build the shared library
cc_binary(
name = dll_name,
srcs = srcs + hdrs,
deps = deps,
linkshared = 1,
**kwargs
)
# Get the import library for the dll
native.filegroup(
name = import_lib_name,
srcs = [":" + dll_name],
output_group = "interface_library",
)
# Because we cannot directly depend on cc_binary from other cc rules in deps attribute,
# we use cc_import as a bridge to depend on the dll.
cc_import(
name = import_target_name,
interface_library = ":" + import_lib_name,
shared_library = ":" + dll_name,
)
# Create a new cc_library to also include the headers needed for the shared library
cc_library(
name = name,
hdrs = hdrs,
visibility = visibility,
deps = deps + [
":" + import_target_name,
],
)
This is a standard file that was provided in a Bazel sample.
If I add the DLL package (//core:core) to deps
when building an exe, this is all I need to do:
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "my_exe",
srcs = ["my_exe.cpp"],
deps = [
"//core:core",
],
)
my_exe can now call the exported functions in the DLL.
But for my unit tests I want to exercise the classes within the library, not just the DLL's external interface.
How do I configure Bazel to do this? Do I need to provide another library config in BUILD for the DLL package to be available as a static library too?
The problem is caused by my tests trying to access functions which aren't exported in the dll.
The exported dll functions are prefixed with __declspec(dllexport)
, and only these functions can be called on the dll. Because I want to unit test the library's internal classes, but I don't want to export this internal functionality in the dll, I need to split the core functionality into a static library and make the dll a wrapper for this static library. The test package can link the static library directly, and the dll only needs to export the necessary functions.
This gives a project structure like this:
src
WORKSPACE
MODULE.bazel
api
api.h
api.cpp
windows_dll_library.bzl
BUILD
core
... other files/folders
logger
callback_logger.h
callback_logger.cpp
BUILD
test
BUILD
callback_logger_test.cpp
Giving 3 packages which can be built:
//core:core
. This is the static library which contains the bulk of the library code.//api:api
. This produces the DLL and only exports the functions marked __declspec(dllexport)
. Depends on //core:core
.//test:test
. This is the test package which exercises the core functionality. Depends on //core:core
.Here are the new BUILD files:
/core/BUILD
cc_library(
name = "core",
srcs = glob(["**/*.cpp"]),
hdrs = glob(["**/*.h"]),
visibility = [
"//api:__pkg__",
"//test:__pkg__",
],
)
/api/BUILD
load(":windows_dll_library.bzl", "windows_dll_library")
# Define the shared library
windows_dll_library(
name = "api",
srcs = ["api.cpp"],
hdrs = ["api.h"],
deps = ["//core:core"],
# Define COMPILING_DLL to export symbols during compiling the DLL.
# See api.h
copts = ["/DCOMPILING_DLL"],
)
test/BUILD
cc_test(
name = "test",
size = "small",
srcs = glob(["*.cpp"]),
deps = [
"@googletest//:gtest",
"@googletest//:gtest_main",
"//core:core",
],
)