How do configure Meson for a project with multiple subdirectories? - c++

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

Related

How do I override cmake_prefix_path option per-dependency with meson?

I have a meson.build like this:
project('foobar', 'cpp',
version : '0.1',
default_options : ['warning_level=3',
'cpp_std=c++17',
'cmake_prefix_path=/foo/bar/')
cv2 = dependency('OpenCV', version : '>=4.0.0', default_options : [ 'cmake_prefix_path=/usr/local/' ])
exe = executable(foobar, foobar.cpp,
dependencies : [ cv2 ])
I set a global prefix to find cmake dependencies, but I need a way to override this path for single dependencies, as I have multiple versions installed in different locations.
How do I tell meson to look in a specific prefix?

Releasing a meson package, how to specify libraries the depending code should link with?

I have a project A that uses meson as the build system and another project B that depends on A.
A runs, compiles and passes all tests. I installed A by doing meson install, which put all the headers and shared library objects where I needed them to be.
After installing A I want to compile B so I added:
A = dependency('A', include_type : 'system')
exe = executable(
'B',
src,
dependencies: [
A
],
cpp_args : '-DSHADER_PATH="' + meson.current_source_dir() + '/"',)
To the meson.build of B. meson does find A as a package and starts compiling B but fails to link. A defines a plethora of small utilities, each one as its own independent .so binary, all of which need to be linked against. Looking at the commands executed when compiling B, the directory where A's .so libraries are is added to the path using -L, but none of the libraries in that directory are listed for linking. So linking fials because the symbols in those binaries are not found (obviously they are not linked).
What do I need to specify in A to let it know a given library needs to be linked by default when the project is used as a dependency?
For example this is what one of the utilities of A looks like:
renderer_so_relative_path = \
'' + renderer_lib.full_path().replace(meson.build_root() + '/', '')
peripheral_so_relative_path = \
'' + peripheral_lib.full_path().replace(meson.build_root() + '/', '')
loader_sources = [
'ModuleStorage.cpp',
'CLI.cpp'
]
install_subdir('.', install_dir : 'include/ModuleStorage/')
loader_lib = library(
'ne_loader',
sources : loader_sources,
cpp_args : [
'-DNE_RENDERER_PATH="' + renderer_so_relative_path + '"',
'-DNE_PERIPHERAL_PATH="' + peripheral_so_relative_path + '"'
],
link_with : [],
include_directories : [],
dependencies : [core_dep, image_dep, argparse_dep, glfw],
install: true)
module_storage_dep = declare_dependency(link_with:loader_lib, include_directories: ['..'])
subdir('Imgui')
Maybe this helps:
Add to your A project:
# C compiler
ccompiler = meson.get_compiler('c')
# Where is the lib:
a_lib_dir = '/install/dir'
# Find the lib:
a_lib = ccompiler.find_library('a_lib_name', dirs: a_lib_dir)
# Create dependency
a_lib_dep = declare_dependency(
dependencies: a_lib,
include_directories: include_directories(inc_dir)
)
And link the dependency in B:
# Dependencies
b_deps = []
b_deps += dependency('alib', fallback:['alib', 'a_lib_dep'])
# Your B object
b_lib = static_library( 'blib', src,
dependencies: b_deps,
etc...)
# Your B object as someone's dependency (if needed):
b_as_dep = declare_dependency(
link_with: b_lib,
include_directories: inc_dirs,
dependencies: b_deps)

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

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

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