c++bazelgoogletestbazel-cpp

How to configure Bazel/Google Test to resolve `bazel test` linker error


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?


Solution

  • 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:

    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",
        ],
    )