Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building a simple library with bazel, fixing include path

Tags:

bazel

I have a very simple directory structure:

.
├── libs
│   └── foo
│       ├── BUILD
│       ├── include
│       │   └── foo
│       │       └── func.h
│       └── src
│           └── func.cxx
└── WORKSPACE

With func.h:

#pragma once

int square(int );

And func.cxx:

#include <foo/func.h>

int square(int i) { return i * i; }

And BUILD:

cc_library(
    name = "foo",
    srcs = ["src/func.cxx"],
    hdrs = ["include/foo/func.h"],
    visibility = ["//visibility:public"],
)

This fails to build:

$ bazel build //libs/foo
INFO: Analysed target //libs/foo:foo (0 packages loaded).
INFO: Found 1 target...
ERROR: /home/brevzin/sandbox/bazel/libs/foo/BUILD:1:1: C++ compilation of rule '//libs/foo:foo' failed (Exit 1)
libs/foo/src/func.cxx:1:22: fatal error: foo/func.h: No such file or directory
 #include <foo/func.h>
                      ^
compilation terminated.
Target //libs/foo:foo failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 0.299s, Critical Path: 0.02s
FAILED: Build did NOT complete successfully

How do I set the include path properly? I tried using include_prefix (whether include or include/foo) but that didn't change the behavior.

like image 367
Barry Avatar asked Dec 20 '17 22:12

Barry


2 Answers

Hmm, the tricky part about including headers from another places is that you have to specify the header file from its relative location according to the workspace (where the WORKSPACE file resides).

Moreover, you should not use the angle-bracket including style #include <a/b.h> unless you are including the system header files.

The related specifications for #include can be found here: https://docs.bazel.build/versions/master/bazel-and-cpp.html#include-paths

TL;DR The only change you need to make to your func.cxx file is that, change the first line to #include "libs/foo/include/foo/func.h".

And then, when you run bazel build //... (build all targets in this workspace, similar to make makes all) from the root of the workspace, you will encounter no error.


However, this is not the only way that can solve your problem.

Another way to resolve this issue that does not involve changing the source code include statement would be specifying the include path in the attribute of the rule cc_library.

Which means, you can make changes to your BUILD in path libs/foo to make it look like this:

cc_library(
    name = "foo",
    srcs = ["src/func.cxx"],
    hdrs = ["include/foo/func.h"],
    copts = ["-Ilibs/foo/include"], # This is the key part
    visibility = ["//visibility:public"],
)

After this change, the compiler will be able to figure out where to find the header file(s), and you don't have to change your source code, yay.

Related information can be found here: https://docs.bazel.build/versions/master/cpp-use-cases.html#adding-include-paths


Nevertheless, there is another hacky way to solve your problem, however, it involves in more changes to your code.

It makes uses of the rule cc_inc_library.

The rule cc_inc_library will strips prefix attribute passed to this rule from the relative path of the header files specified in the hdrs attribute.

The example on the website is a little confusing, your code and directory structure will yield a much better demonstration purpose.

In your case, you have to modify your BUILD file under libs/foo to something that looks like this:

cc_library(
    name = "foo",
    srcs = ["src/func.cxx"],
    deps = [":lib"],
    copts = ["-Ilibs/foo/include"],
    visibility = ["//visibility:public"],
)

cc_inc_library(
    name = "lib",
    hdrs = ["include/foo/func.h"],
    prefix = "include/foo",
)

In your case, the header file func.h which has a relative path from the package libs/foo as include/foo/func.h, which is specified in the hdrs attribute.
Since it has a relative path to the root of the workspace as libs/foo/include/foo/func.h, and the prefix attribute in the cc_inc_library is specified as include/foo: the value include/foo will be stripped form lib/foo/include/foo/func.h, making it libs/foo/func.h.

So, now you can include this header file in your func.cxx as #include "libs/foo/func.h".
And now, bazel will not report error saying that it was not able to find the header file.

You can find the information about this rule at: https://docs.bazel.build/versions/master/be/c-cpp.html#cc_inc_library. However, as stated above, the explanation is confusing at best, possibly because the documentation for it is out of date.

I was puzzled by the office explanation on bazel.build for quite some time until I read the source code for this rule at: https://github.com/bazelbuild/bazel/blob/f20ae6b20816df6a393c6e8352befba9b158fdf4/src/main/java/com/google/devtools/build/lib/rules/cpp/CcIncLibrary.java#L36-L50

The comment for the actual code that implements the function does a much, much better job at explaining what this rule actually does.

cc_inc_library rule has been deprecated since Bazel release 0.12.

Use cc_library approach instead.

See: https://blog.bazel.build/2018/04/11/bazel-0.12.html

like image 80
Xiao Liang Avatar answered Nov 05 '22 18:11

Xiao Liang


What you really want here is strip_include_prefix:

cc_library(
    name = "foo",
    srcs = ["src/func.cxx"],
    hdrs = ["include/foo/func.h"],
    # Here!
    strip_include_prefix = "include",
    visibility = ["//visibility:public"],
)

This will make the headers accessible via:

#include "foo/func.h"

This attribute has been available since at least Bazel 0.17.

like image 32
kirbyfan64sos Avatar answered Nov 05 '22 18:11

kirbyfan64sos