Search code examples
c++bazelntl

How to link a C++ program to the NTL library using Bazel


I would like to using Bazel to build a C++ project that links to some external libraries such as NTL. However, I can't find a way to make Bazel to build any program that includes NTL. Here is an example. Consider the following file structure:

/project-root
 |__main
 |  |__BUILD
 |  |__example.cc
 | 
 WORKSPACE

where WORKSPACE is just an empty file at the root of my project. The content of example.cc is:

#include <iostream>

int main()
{
  int a = 5;

  std::cout << "a = " << a << std::endl;

  return 0;
}

The content of BUILD is:

load("@rules_cc//cc:defs.bzl", "cc_binary")

cc_binary(
    name = "example",
    srcs = ["example.cc"],
)

I build the project as follows:

bazel build //main:example

and I obtain:

INFO: Analyzed target //main:example (38 packages loaded, 260 targets configured).
INFO: Found 1 target...
Target //main:example up-to-date:
bazel-bin/main/example
INFO: Elapsed time: 5.175s, Critical Path: 0.58s
INFO: 6 processes: 4 internal, 2 darwin-sandbox.
INFO: Build completed successfully, 6 total actions

I then run:

./bazel-bin/main/example

which returns:

a = 5

Everything works as expected. However, if I include the NTL library, I am unable to build the project. Consider the following update to example.cc

#include <iostream>
#include <NTL/ZZ.h>

int main()
{
  int a = 5;
  NTL::ZZ b = NTL::ZZ(13);

  std::cout << "a = " << a << std::endl;
  std::cout << "b = " << b << std::endl;

  return 0;
}

If I wanted to compile example.cc directly, I would execute the following:

g++ main/example.cc -o main/example --std=c++11 -lntl

However, I would like to do it using Bazel (since I will have to build for different targets, I am going to test with Google Test, and some other reasons that made me want to try Bazel.)

According to this post, I would be able to do what I want by adding linkopts in my BUILD file:

load("@rules_cc//cc:defs.bzl", "cc_binary")

cc_binary(
    name = "example",
    srcs = ["example.cc"],
    linkopts = ["-lntl"],
)

However, when I try to build it, I get:

ERROR: /Users/.../bazel_example/main/BUILD:3:10: Compiling main/example.cc failed: (Aborted): wrapped_clang_pp failed: error executing command external/local_config_cc/wrapped_clang_pp '-D_FORTIFY_SOURCE=1' -fstack-protector -fcolor-diagnostics -Wall -Wthread-safety -Wself-assign -fno-omit-frame-pointer -O0 -DDEBUG '-std=c++11' ... (remaining 31 arguments skipped)

Use --sandbox_debug to see verbose messages from the sandbox
main/example.cc:3:10: fatal error: 'NTL/ZZ.h' file not found
#include <NTL/ZZ.h>
     ^~~~~~~~~~
1 error generated.
Error in child process '/usr/bin/xcrun'. 1
Target //main:example failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 3.833s, Critical Path: 0.39s
INFO: 5 processes: 5 internal.
FAILED: Build did NOT complete successfully

The same post I previously mentioned talks about an alternative by importing whatever library you need to the project. I've seen some people recommending including the path to the headers of the library directly in the project managed by Bazel.

I wonder if there is a way to successfully build the project with Bazel while just "flagging" the NTL library so I can use it in my project.

I am specifically referring to the NTL library but this would be my desired approach to any other external library available in the system.


Solution

  • I got Bazel to build my project and link to NTL by following the steps in this answer. Refer to this link for the specifics. However, the referred solution was not enough for the particular case of the NTL library. The "problem" is that NTL uses GMP by default. When I saw the build failing because Bazel "didn't recognize" GMP I was a bit concerned. One library can have many dependencies and addressing each one of them just to get one library to work could be cumbersome.

    Thankfully, the only extra required step was to apply for GMP the same configuration I applied for NTL.

    I modified my BUILD file including the dependencies I need to compile my project:

    load("@rules_cc//cc:defs.bzl", "cc_binary")
    
    cc_binary(
        name = "example",
        srcs = ["example.cc"],
        deps = ["@ntl//:ntl_", "@gmp//:gmp_"],
    )
    

    Then I created the "dependencies" directory and put two files in there. One is BUILD.ntl with the following content:

    cc_library(
        name = "ntl_",
        srcs = glob(["lib/*.dylib"] + ["lib/*.a"]),
        hdrs = glob(["include/NTL/*.h"]),
        strip_include_prefix = "include/",
        visibility = ["//visibility:public"]
    )
    

    and BUILD.gmp

    cc_library(
        name = "gmp_",
        srcs = glob(["lib/*.dylib"] + ["lib/*.a"]),
        hdrs = glob(["include/*.h"]),
        strip_include_prefix = "include/",
        visibility = ["//visibility:public"]
    )
    

    Finally, I added to the previously empty WORKSPACE file the following:

    new_local_repository(
      name = "ntl",
      path = "/usr/local/var/homebrew/linked/ntl",
      build_file = "dependencies/BUILD.ntl",
    )
    
    new_local_repository(
      name = "gmp",
      path = "/usr/local/var/homebrew/linked/gmp",
      build_file = "dependencies/BUILD.gmp",
    )
    

    Obviously, the path under new_local_repository can be platform/machine/user specific. I installed NTL and GMP using Homebrew those paths.

    I wanted a solution that could be as "seamless" as possible. Without knowing any other "cleaner" approach, I will stick to this one.

    Now I just need to cleanup the previous failed builds:

    bazel clean
    

    and build it again:

    bazel build //main:example
    

    so I obtain:

    INFO: Analyzed target //main:example (40 packages loaded, 391 targets configured).
    INFO: Found 1 target...
    Target //main:example up-to-date:
      bazel-bin/main/example
    INFO: Elapsed time: 5.128s, Critical Path: 1.54s
    INFO: 129 processes: 127 internal, 2 darwin-sandbox.
    INFO: Build completed successfully, 129 total actions
    

    When I run

    ./bazel-bin/main/example
    

    I see the expected result:

    a = 5
    b = 13