Simple way to build a C++ modules (TS) project

c++ build2

Let's build a C++ project with one C++ module foo.mxx

// foo.mmx
export module foo;
export void test() { 
  printf("hello, modules! \n"); 
}

and main file main.cxx:

// main.cxx
import foo;
int main() {
  test();
}

clang++

We can quickly find that following commands do the job:

clang++ --std=c++17 -fmodules-ts --precompile foo.mxx -o foo.pcm
clang++ --std=c++17 -fmodules-ts -c foo.pcm -o foo.o
clang++ --std=c++17 -fmodules-ts -fprebuilt-module-path=. foo.o bar.cxx
./a.out    # hello, modules!

(These commands should work with clang 6 and 7 at least.)

This would work fine for a project with a single module. But in general case we are required to build modules in the order of their dependency tree.

build2

I found that there's a new building system — build2, which support C++ modules out of the box and automatically analyse dependencies of them in the project.

Usual method to use build2 is to execute many commands that create a bunch of files and directories. This seems a little too complicated, at least for small projects.

However, we can use build2 in so called "simple project" configuration (as far as I know this is undocumented). In this case we create only one file — buildfile. Just like with make.

buildfile for our project would look like follows:

config.cxx = clang++
config.cxx.poptions = [null]
config.cxx.coptions = [null]
config.cxx.loptions = [null]
config.cxx.libs = [null]

cxx.std = experimental

using cxx

cxx{*}: extension = cxx
mxx{*}: extension = mxx

exe{test}: {cxx mxx}{*}

Here we can specify options for compiler (cxx.coptions) and linker (cxx.loptions). To enable modules support we select experimental C++ standard to use. And at the end we direct the builder to compile our program from mxx and cxx files. (mxx for modules, cxx for main file.)

Now it is sufficient to execute b command from within the project's directory to build it:

$ b
$ ./test     # hello, modules!

build2 automatically analyses the dependency tree of modules and build all the source files in correct order. (Also it does partial recompilation of project, recompiling modified modules only.)

Using /tmp for compilation

To (may be) speed up compilation but mainly to have the project's directory clean of many intermediate files that build2 creates (*.pcm, *.ii, *.d), we can compile in temporary folder and then copy executable file back, if compilation was successful:

#!/bin/bash
PROJECT=test
TMP_DIR="/tmp/$PROJECT"
cp buildfile $TMP_DIR/
for fn in *.mww; do
  name="$(basename "$fn")"
  cp "$fn" "$TMP_DIR/$name"
done
for fn in *.cww; do
  name="$(basename "$fn")"
  cp "$fn" "$TMP_DIR/$name"
done
pushd . > /dev/null
cd $TMP_DIR
b -q 
BUILD_OK="$?" 
popd > /dev/null
if [ $BUILD_OK -eq 0 ];then
  cp "$TMP_DIR/$PROJECT" $PROJECT
fi

I also like to have automatic rebuild on file changes:

inotifywait -qq $0 *.cww *.mww buildfile
. $0  # re-execute itself

Disadvantage here is that we copy all the files every time, so it is ok for really small projects. For bigger ones one should try to use build2 in normal configuration.

Good luck with the new C++ features!

 

shitpoet@gmail.com