C++ project with Bazel and GTest - c++

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

Related

How do configure Meson for a project with multiple subdirectories?

everyone.
I have a C++ project that uses MongoDB and wxWidgets as its dependencies, and the project is structured into multiple subdirectories.
I have recently started using the Meson build system but have no idea how to configure the meson.build file for this project structure.
Here is how my project is structured:
Project/
meson.build
BuildDir/
Source/
A/
meson.build
A1.hpp
A1.cpp
A2.hpp
A2.cpp
...
B/
meson.build
B1.hpp
B1.cpp
B2.hpp
B2.cpp
...
C/
meson.build
C1.hpp
C1.cpp
C2.hpp
C2.cpp
...
Thank you.
in your project A,B and C are sub project,
so a possible solution is on every sub project have a structure like that:
project(
'A',
'cpp',
version : '1.0.0',
default_options : ['warning_level=3']
)
project_description = ' ....'
sourceRoot = meson.source_root()
project_headers = [ # add your project headers ]
public_headers = [# add public header ]
build_args = [#add build args.... ]
# ======
# Target
# ======
#shared or static lib or executable....
project_target = shared_library( ...... )
# =======
# Project
# =======
# Make this library usable as a Meson subproject.
project_dep = declare_dependency(
include_directories: public_headers
)
set_variable(meson.project_name() + '_dep', project_dep)
# Make this library usable from the system's
# package manager.
#install_headers(project_headers, subdir : meson.project_name())
pkg_mod = import('pkgconfig')
pkg_mod.generate(
name : meson.project_name(),
filebase : meson.project_name(),
description : project_description,
subdirs : meson.project_name(),
)
an the same for B,C sub project,
than in the main meson must specify sub projects folders and add the dependency
...
project(
'MyProjj',
'cpp',
version : '1.1.0',
default_options : ['buildtype=plain','warning_level=3'],
subproject_dir : 'Source',
meson_version: '>= <the version you want to use>'
)
.......
dependency('A', fallback : ['<A path>', 'A_dep']),
dependency('B', fallback : ['<B path>', 'B_dep']),
dependency('C', fallback : ['<C path>', 'C_dep']),
so referring to your example:
Project/
meson.build
BuildDir/
Source/
A/
...
"A path" is A,
and the first parameter of dependency(...) is the names of the dependency too look up

How to package a header-only C++ library for iOS with Bazel?

I'm working on a header-only C++ library with several dependencies (xtensor, fmtlib, Boost, Accelerate.framework) using Bazel as the build system. There is a currently a single class whose interface I'd like to expose to an iOS app (to be) written in Objective-C++.
I have tried using something along the lines of the following configuration:
BUILD:
package(
default_visibility = ["//visibility:public"],
)
ios_static_framework(
name = "MyFramework",
hdrs = ["objc_lib.h"],
deps = [":objc_lib"],
families = ["iphone"],
minimum_os_version = "12.0",
)
objc_library(
name = "objc_lib",
hdrs = ["objc_lib.h"],
non_arc_srcs = ["objc_lib.mm"],
deps = [":cc_lib"],
sdk_frameworks = ["Accelerate"],
)
cc_library(
name = "cc_lib",
hdrs = ["cc_lib.hpp"],
deps = [
# several header-only targets
":dep1",
":dep2",
],
)
...
cc_lib.hpp:
#include "dep1.hpp"
#include "dep2.hpp"
class MyClass {
// ...
};
objc_lib.h:
#include "cc_lib.hpp"
objc_lib.mm:
#include "objc_lib.h"
Running bazel build //... produces a framework archive with the following structure that doesn't have any of the headers of objc_lib's transitive dependencies.
MyFramework.framework
├── Headers
│   ├── MyFramework.h
│   └── objc_lib.h
├── Modules
│   └── module.modulemap
└── MyFramework
Is there a "canonical" way of packaging a header-only library for iOS (as a framework or otherwise) with Bazel?

How do I create a Buckaroo package from a header-only library?

Given a header-only library like this:
└── foo
   ├── bar.hpp
   └── foo.hpp
How can I package this using Buckaroo?
The Buckaroo Wiki Page mentions the process:
First, run buckaroo init to generate a .buckconfig and buckaroo_macros.bzl.
Then you will need to edit two files:
BUCK - it describes the build
buckaroo.toml - it describes your external dependencies
BUCK:
The following BUCK file packages your headers so that every file can be included via #include <foo/*.hpp>:
cxx_library(
name = 'foo',
header_namespace = '',
exported_headers = glob(['foo/*.hpp']),
visibility = ['PUBLIC'],
)
This is equivalent with:
cxx_library(
name = 'foo',
header_namespace = '',
exported_headers = {
'foo/foo.hpp': 'foo/foo.hpp',
'foo/bar.hpp': 'foo/bar.hpp',
},
visibility = ['PUBLIC'],
)
This map describes how the path defined in #include <a/b/c.h> maps to the actual files in the file-system. As the include-path is identical to your file-system layout, keys and values are identical.
buckaroo.toml
To make the installation of your package convenient for consumers, it is recommended to explicitly list the public packages that should be exported by default in the targets section in buckaroo.toml
targets = [ "//:foo" ]
If you have external dependencies you may want to install them via
buckaroo add URL#VERSION
and connect the dependencies in your BUCK file:
load('//:buckaroo_macros.bzl', 'buckaroo_deps')
cxx_library(
name = 'foo',
// ...
deps = buckaroo_deps(),
visibility = ['PUBLIC'],
)

Bazel & automatically generated cpp / hpp files

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

C++ header-only library with waf

Good day,
before fully migrating to waf (1.7.5), I have tried to create a simple project of this structure:
wafproject
├── application
│ ├── main.cpp
│ └── wscript
├── library1
│ ├── foo1.hpp
│ ├── foo2.hpp
│ └── wscript
└── wscript
This is the root wscript:
def options(opt) :
opt.load('compiler_cxx')
def configure(cnf) :
cnf.load('compiler_cxx')
def build(bld) :
bld.recurse('library1')
bld.recurse('application')
This is the application wscript:
def build(bld) :
bld( features = 'cxx cxxprogram'
, target = 'application'
, source = 'main.cpp'
, use = ['library1']
)
This is the library1 wscript
def build(bld) :
bld( name = 'library1'
, inludes = '../../'
, export_inludes = '../../'
)
(Note: I have tried using target instead of name for library1, and I have also
tried enabling cxx cxxshlib features for library1.)
This is the main.cpp:
#include <wafproject/library1/foo1.hpp>
#include <wafproject/library1/foo2.hpp>
int main()
{
}
And this is the error I get:
Setting top to : /home/<path>/wafproject
Setting out to : /home/<path>/wafproject/build
Checking for 'g++' (c++ compiler) : /usr/bin/g++
'configure' finished successfully (0.038s)
Waf: Entering directory `/home/<path>/wafproject/build'
[1/3] cxxshlib: -> build/library1/liblibrary1.so
[2/3] cxx: application/main.cpp -> build/application/main.cpp.1.o
../application/main.cpp:1:40: fatal error: wafproject/library1/foo1.hpp: Directory or file does not exist.
compilation terminated.
Waf: Leaving directory `/home/<path>/wafproject/build'
Build failed
-> task in 'application' failed (exit status 1):
{task 139729350901264: cxx main.cpp -> main.cpp.1.o}
['/usr/bin/g++', '../application/main.cpp', '-c', '-o', 'application/main.cpp.1.o']
I do not want to change the way I include the headers, but for that I apparentely need to change the way my project is set up.
I would be glad for any input, thanks.
EDIT: Solved, it was just a typo (inludes instead of includes and export_inludes instead of export_includes).
Since this is the first thing on google for 'header only library waf', I thought I should post the generic solution.
bld(name = 'libname', export_includes = 'PATH/TO/lib/')
Which works for me.