Bazel & automatically generated cpp / hpp files - c++

I am starting to use Bazel as my C++ project build system.
However I am stuck with the following problem:
I am in a scenario where I automatically generate the file.hpp file.cpp (literate programming).
To reproduce my problem one can simply use this minimal generator:
-- file.sh --
#!/bin/sh
echo "int foo();" >> file.hpp
echo "#include \"myLib/file.hpp\"\n\nint foo() { return 2017; }" >> file.cpp
My project repo is: (WORKSPACE is an empty file)
├── myLib
│   ├── BUILD
│   └── file.sh
└── WORKSPACE
The BUILD file is
genrule(
name = "tangle_file",
srcs = ["file.sh"],
outs = ["file.cpp","file.hpp"],
cmd = "./$(location file.sh);cp file.cpp $(#D);cp file.hpp $(#D);"
)
cc_library(
name = "file",
srcs = ["file.cpp"],
hdrs = ["file.hpp"],
# deps = [":tangle_file"],
visibility = ["//bin:__pkg__"],
)
I have two problems:
Question (A), dealing with the genrule() part:
The fact that I must use
cmd = "./$(location file.sh);cp file.cpp $(#D);cp file.hpp $(#D);"
is quite mysterious.
My first attempt was:
cmd = "./$(location file.sh)"
However in that case I get the following error:
declared output 'myLib/file.cpp' was not created by genrule. This is probably because the genrule actually didn't create this output, or because the output was a directory and the genrule was run remotely (note that only the contents of declared file outputs are copied from genrules run remotely)
Question (B), dealing with the cc_library() part
I do not know how to make Bazel aware of that the :file target depends on the :tangle_file target.
If I uncomment:
deps = [":tangle_file"],
I get the following error:
in deps attribute of cc_library rule //myLib:file: genrule rule '//myLib:tangle_file' is misplaced here (expected cc_inc_library, cc_library, objc_library, experimental_objc_library or cc_proto_library).

Question (A)
The error that you are seeing is because the genrule cmd is not run inside of its output directory. If you hardcoded bazel-out/local-fastbuild/genfiles/myLib/file.cpp instead of file.cpp in your file.sh script, it would work. However, the recommended approach would be for your script to takes its output directory as an argument.
For example,
genrule(
name = "tangle_file",
srcs = ["file.sh"],
outs = ["file.cpp","file.hpp"],
cmd = "./$(location file.sh) $(#D)"
)
and
#!/bin/sh
echo "int foo();" >> $1/file.hpp
echo "#include \"myLib/file.hpp\"\n\nint foo() { return 2017; }" >> $1/file.cpp
Question (B)
The fact that you have
srcs = ["file.cpp"],
hdrs = ["file.hpp"],
in your cc_library is what tells Bazel that it depends on the genrule, since the genrule creates those files. If you want to make it more explicit, you could use the label syntax, which does the same thing:
srcs = ["//myLib:file.cpp"],
hdrs = ["//myLib:file.hpp"],

Related

Bazel project how to copy candidate directories during build stage

I have a cpp project with the later file struct
MyProject
WORKSPACE
-directory_a
—mylib.cpp
—interface.h
—BUILD
-directory_b
—mylib.cpp
—interface.h
—BUILD
—directoryC
—main.cpp
—BUILD
In main.cpp , the I have a simple include code as
#include directory_real/interface.h
// do something
void main(){
// say hello world
}
During building , I want to use genrule (or something like copy_directory) to rename my directory_b | directory_a into the ‘driectory_real’ based on some flags (In this case, try to move directory_b into directory_real). The build file in the root I wrote is like the code next:
load("#bazel_skylib//rules:copy_directory.bzl", "copy_directory")
copy_directory("copy_directory_into_real","directory_b","directory_real")
cc_binary(
name = "test",
srcs = [
"main.cpp",
],
visibility = ["//visibility:public"],
deps = [
"//directory_real:mylib",
],
)
However, when bazel build //directoryC:test, the error occurs
ERROR: no such package 'directory_real': BUILD file not found in any of the following directories.
Anything wrong?
How can I wrote a correct bazel file to do this

Bazel build: dependency parameterized by command line

I have a project for a plugin in C/C++ where I have different implementations and I would like to be able to select which implementation to use when building.
WORKSPACE
plugin/
BUILD
plugin.c
plugin.h
plugin_impl1/
BUILD
...
plugin_impl2/
BUILD
...
plugin_impl1/BUILD:
cc_library(
name = "plugin_impl1"
srcs = [...]
hdrs = [...]
)
plugin_impl2/BUILD:
cc_library(
name = "plugin_impl2"
srcs = [...]
hdrs = [...]
)
I want to know how to write plugin/BUILD so that I could write something like:
bazel build //plugin --which plugin_impl2
The executable name should always be the same so having multiple rules with different target names (for plugin) is not an option.

output 'external/name/x/lib/lib.so' was not created using bazel make

I was trying to follow the example provided by Building Makefile using bazel
post to build an external package in envoy. In the WORKSPACE file I added the following:
new_git_repository(
name = "name",
remote = "remote.git",
build_file = "//foo/bazel/external:x.BUILD",
)
And foo/bazel/external/x.BUILD has the following contents:
load("#rules_foreign_cc//tools/build_defs:make.bzl", "make")
filegroup(
name = "m_srcs",
srcs = glob(["code/**"]),
)
make(
name = "foo_bar",
make_commands = ["make lib"],
lib_source = ":m_srcs",
shared_libraries = ["lib.so"],
)
and I set the visibility in foo/bazel/BUILD as package(default_visibility = ["//visibility:public"])
On executing bazel build -s #name//:foo_bar, I get the error that external/name/x/lib/lib.so was not created.
I checked the bazel-bin/external/name/x/logs/GNUMake.log and make completes successfully. I see that BUILD_TMPDIR directory has created lib.so. I think it should have been copied to EXT_BUILD_DEPS/lib, but I am not sure why it was not copied. Would appreciate any tips to debug the error.
Edited make command to manually copy the lib to expected folder - make_commands = ["make libs; cp lib.so $INSTALLDIR/lib/lib.so"]

C++ project with Bazel and GTest

I want to create a Bazel C++ project with gtest for unit tests.
What is the minimal setup?
(I only have Bazel installed on my computer and I am running under Linux)
This is even easier now that googletest provides a BUILD file:
In WORKSPACE
load("#bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "gtest",
remote = "https://github.com/google/googletest",
branch = "v1.10.x",
)
In BUILD
cc_test (
name = "hello_test",
srcs = [
"hello_test.cc",
],
deps = [
"#gtest//:gtest",
"#gtest//:gtest_main" # Only if hello_test.cc has no main()
],
)
The project structure is:
.
├── bin
│   ├── BUILD
│ ├── hello.cpp
├── MyLib
│   ├── BUILD
│ ├── message.hpp
│ ├── message.cpp
│ ├── ...
├── test
│ ├── BUILD
│ ├── message_test.cpp
│ ├── ...
├── gmock.BUILD
└── WORKSPACE
Files related to Bazel+GTest
WORKSPACE
There you download gtest from github:
new_git_repository(
name = "googletest",
build_file = "gmock.BUILD",
remote = "https://github.com/google/googletest",
tag = "release-1.8.0",
)
You define a gmock BUILD file defined below:
gmock.BUILD
This BUILD file is in charge of compiling gtest/gmock:
cc_library(
name = "gtest",
srcs = [
"googletest/src/gtest-all.cc",
"googlemock/src/gmock-all.cc",
],
hdrs = glob([
"**/*.h",
"googletest/src/*.cc",
"googlemock/src/*.cc",
]),
includes = [
"googlemock",
"googletest",
"googletest/include",
"googlemock/include",
],
linkopts = ["-pthread"],
visibility = ["//visibility:public"],
)
cc_library(
name = "gtest_main",
srcs = ["googlemock/src/gmock_main.cc"],
linkopts = ["-pthread"],
visibility = ["//visibility:public"],
deps = [":gtest"],
)
test/BUILD
This build file generate the tests:
cc_test(
name = "MyTest",
srcs = glob(["**/*.cpp"]),
deps = ["//MyLib:MyLib",
"#googletest//:gtest_main"],
)
The test/message_test.cpp file is defined by:
#include "gtest/gtest.h"
#include "MyLib/message.hpp"
TEST(message_test,content)
{
EXPECT_EQ(get_message(),"Hello World!");
}
And that is all! The other files are defined as usual:
Files for the supporting example
MyLib/BUILD
Creates the libMyLib.so and libMyLib.a libraries.
cc_library(
name="MyLib",
hdrs=glob(["**/*.hpp"]),
srcs=glob(["**/*.cpp"]),
visibility = ["//visibility:public"],
)
with a basic message.hpp
#include <string>
std::string get_message();
and message.cpp
#include "MyLib/message.hpp"
std::string get_message()
{
return "Hello World!";
}
example.
bin/BUILD
Creates the hello executable.
cc_binary(
name = "hello",
srcs = ["hello.cpp"],
deps = ["//MyLib:MyLib"],
)
which is:
#include "MyLib/message.hpp"
#include <iostream>
int main()
{
std::cout << "\n" << get_message() << std::endl;
return EXIT_SUCCESS;
}
Usage:
Compiles all targets:
This will also download gtest from its github repo and compile it
bazel build ...
Checks the hello target:
You can run it with:
bazel run bin:hello
Running your tests using GTest
That was the main point of this note:
bazel test ... --test_output=errors
You should get something like:
INFO: Analysed 3 targets (0 packages loaded).
INFO: Found 2 targets and 1 test target...
INFO: Elapsed time: 0.205s, Critical Path: 0.05s
INFO: Build completed successfully, 2 total actions
//test:MyTest
PASSED in 0.0s
Executed 1 out of 1 test: 1 test passes.
Reproduce the results
For your ease I have created a github repo containing this example. I hope it works out of the box.
The current recommended practice is to use http_archive to avoid depending on the system git and take advantage of repository cache.
In WORKSPACE
# 5376968f6948923e2411081fd9372e71a59d8e77 is the commit sha for v1.12.0.
# Periodically update to the latest to "live at head"
http_archive(
name = "com_google_googletest",
sha256 = "199e68f9dff997b30d420bf23cd9a0d3f66bfee4460e2cd95084a2c45ee00f1a",
strip_prefix = "googletest-5376968f6948923e2411081fd9372e71a59d8e77",
urls = ["https://github.com/google/googletest/archive/5376968f6948923e2411081fd9372e71a59d8e77.zip"],
)
In test/BUILD
cc_test(
name = "test_greet",
srcs = ["greeting_test.cpp"],
deps = [
"//src:greeting",
"#com_google_googletest//:gtest_main",
],
)

making unit test binaries using scons with g++ and gtest

I am not able sucessfully build the the project using scons, g++ and gtest. I want to use gtest as unit test. My project looks like below:
project
| -SConstruct
| -src
| -name.hh
| -name.cc
| -main.cc
| -gtest
| -/src/gtest_name.hh
| -/src/gtest_name.cc
| -/src/gtest_main.cc
Inside SConstruct for project building, I have following code:
program_srcs = ['name.cc']
cpppath = ['./src']
libpath = ['.', 'path_to_third_party_lib']
libs = ['thirdlib']
pro_env = Environment()
env.Append(CPPPATH = cpppath)
env.Append(LIBS = libs)
env.Append(LIBPATH = libpath)
env.Library('name', program_srcs)
libpath.append('name')
env.Append(LIBPATH = libpath)
env.Program(target = 'NAME', source = [ './src/main.cc']
test_src = ['./gtest/src/gtest_name.cc']
test_env = Environment()
test_env['LIBPATH'] = ['.']
test_env.Program("unit_test", test_src, LIBS=['name'])
Inside gtest_name.cc
include"name.hh"
TEST_F(TESTNAME, testmethod) {
Name name;
ASSERT_EQ(name.get_surname, "MIKE");
}
When I tried to compile and build, it gave following errors for gtest.
g++ -o gtest/src/gtest_name.o -c gtest/src/gtest_name.cc
gtest/src/gtest_name.cc:10:29: error: name.hh: No such file or directory
When I checked for library 'name', it was already constructed. Could you please tell me what the problem is?
You have added the required include search path "src" to the variable CPPPATH, for the environment "env".
But you build the library with the environment "test_env" which doesn't have CPPPATH defined.
That's why the "-I" directive is missing in your compiler call.
Note, that SCons offers a Clone() method for environments. It copies over all current definitions (and builders for example) from one environment to create a new one...this might come in handy here.