Building Aegean

This chapter demonstrates building Aegean. It uses command lines and CMake for GCC and Clang. The details will aid in adapting other build systems.

Building a program with modules requires creating the usual object files plus the Built Module Interface (BMI) .

Built Module Interface

As the name implies, the Built Module Interface is a precompiled and optimized binary version of the Module Interface Unit (MIU). It contains:

  • a representation of the exported declarations,
  • the dependencies among the imported modules,
  • type information for the exported classes, struts, and functions,
  • compiler version. (A Clang 18 BMI won’t work as a Clang 19 or GCC BMI.)

The BMI provides information for compiling code that uses a module but is not linked with the application. A BMI must be built before any code that uses it, including other members of the module unit.

Building with Command Lines

These commands build Aegean the previous chapter using GCC and Clang. Unfortunately, the two compilers use different compiler options to control building modules, making the situation a little bit difficult.

The tables in the next section show for GCC and Clang the new and existing command line options required to build modules.

Building With GCC

The GCC build begins by creating the BMI from the MIU. The output is a **GCC Compiled Module (GCM) ** and the standard .o object file. The generated BMI has the extension .gcm by default and resides in the gcm.cache subdirectory.

Figure 5.1. GCC build and link options
Option .cppm (BMI) .cpp using module
-fmodules-ts building a module building a module
-x c++ build BMI not used
-c do not link flags not to link
-r do not create executable do not create executable

The .cppm column is for builds of MIUs to generate a BMI and its object file. The .cpp column applies to main.cpp, application files using modules, and module implementation files.

GCC Command Lines

Here’s how to do the GCC build.

 1 # build the module unit interface - produces a .gcm in gcm.cache
 2 g++ -std=c++23 -fmodules-ts -c -x c++ Aegean.cppm -o bin/Aegean.o
 3 
 4 # build the module unit implementation and link with the MUI
 5 g++ -std=c++23 -fmodules-ts -c aegean.cpp -o bin/aegean.o -c
 6 g++ -std=c++23 -fmodules-ts -r bin/Aegean.o bin/aegean.o -o bin/Aegean.All.o
 7 
 8 # build main and link with the module
 9 g++ -std=c++23 -fmodules-ts -c main.cpp -o bin/main.o
10 g++ -std=c++23 -fmodules-ts  bin/main.o bin/Aegean.All.o -o bin/main

The first step (line 1) creates the BMI (.gcm) in the gcm_cache directory and the binary output in the bin directory using the .cppm file. The implementation file is built (line 5), and the two object files are linked (line 6) into a single object file (Aegean.All), making the module self-contained.

The final steps are to build the main() (line 9) and link it (line 10) to the module binary, which creates the executable.

After everything is complete, the output files are in these directories.

1 ├── bin
2 │   ├── Aegean.o
3 │   ├── aegean.o
4 │   ├── main
5 │   └── main.o
6 ├──gcm.cache
7    └── Aegean.gcm

Building With Clang

The Clang build also begins by creating the BMI from the MUI. The output is a .pcm file, a PreCompiled Module. It then needs to build an object file. The location of the BMI is specified during the build. The .cppm extension is the norm expected by Clang but may be overriden with additional options.

Figure 5.2. Clang build and link options
Option .cppm (BMI) .cpp using module
-precompile build the BMI not used
-c do not link do not link
-r do not create executable do not create executable
-fmodule-file=<name>=bin/<name>.pcm* location and file for module

The .cppm column applies to builds of MIUs to generate the BMI and its object file (lines 2 and 3).

The .cpp column applies to the main.cpp (lines 10 and 11), application files using modules, and module implementation files (lines 6 and 7).

Clang Command Lines

 1 # build the module unit interface - produces a .pcm in bin
 2 clang++ -std=c++23 -fmodules --precompile Aegean.cppm -o bin/Aegean.pcm
 3 clang++ -std=c++23 -c Aegean.cppm -o bin/Aegean.o
 4 
 5 # build the module unit implementation and link with the MIU
 6 clang++ -std=c++23 -c -fmodule-file=Aegean=bin/Aegean.pcm aegean.cpp -o bin/aegean.o
 7 clang++ -std=c++23  -r bin/Aegean.o bin/aegean.o -o bin/Aegean.All.o
 8 
 9 # build main and link with the module
10 clang++ -std=c++23 -c -fmodule-file=Aegean=bin/Aegean.pcm main.cpp -o bin/main.o
11 clang++ -std=c++23  bin/Aegean.All.o bin/main.o -o bin/main

The BMI is built from the .cppm file (line 2) to create a .pcm.

In Clang’s terms, this is a two-phase build process that supports parallel compilation. The BMI and the object file are built in separate steps. The BMI is available for translation units that depend on it, without needing to wait for the object file. A one-phase version of the build process is demonstrated later.

Next, the MIU object file is built (line 3). The implementation file is built (line 7) and linked (line 9) with the MIU object file. Finally, main is built (line 13) and everything is linked (line 15).

The build output is placed in the output directories specified, in this case, bin, and the BMI, again bin.

The artifacts created by the build are the object files, the pcm, and the executable, main. Clang doesn’t use a specific cache directory like GCC so all files are in the bin directory.

1  bin
2     ├── Aegean.o
3     ├── Aegean.pcm
4     ├── aegean.o
5     ├── main
6     └── main.o

Command Line Summary

I haven’t worked with command-line builds for years, possibly decades, so making these work was an adventure. After achieving success with these builds, I didn’t stop my research, continuing to read and watch talks. This continued study led to command-line improvements that you’ll see in later chapters. This approach is part of my “it works” attitude in the book. Once something works, you can improve it as you learn more. I sure do.

Building with CMake

Fortunately, CMake is the same for both compilers and works both within CLion and from the command line. At the bottom of each subdirectory’s CMake file, there is a command line for building the project with CMake.

Scanning for Modules

One reason modules took a few years to be fully available with CMake is the need to scan files to see if they are part of or use a module unit, which requires coordination between compilers and CMake. Fortunately, CMake had experience with Fortran, which implemented modules a number of years ago.

Clang uses the clang-tools to scan files and build a database, while GCC does this with the g++ compiler.

Parent Directory CMakelist.txt

The parent CMakelist.txt is typical except for adding the Clang line (line 4) at the beginning. Modules require specifying C++20 or later and activating the CMAKE_CXX_SCAN_FOR_MODULES (line 12) command to invoke the module scan. The other CMAKE_CXX statements assure that the correct standard is used and no extensions are included.

The real work is done in the subdirectory files.

 1 cmake_minimum_required(VERSION 3.28.3)
 2 
 3 # must appear before project()
 4 set(CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS "/usr/bin/clang-scan-deps-18")
 5 
 6 project(pragmatic_modules LANGUAGES CXX)
 7 
 8 set(CMAKE_CXX_STANDARD 23)
 9 set(CMAKE_CXX_STANDARD_REQUIRED ON)
10 set(CMAKE_CXX_SCAN_FOR_MODULES ON)
11 
12 add_subdirectory(Aegean)
13 #... more subdirectories

Subdirectory CMakelist.txt

Here is the subdirectory CMakelist.txt.

 1 cmake_minimum_required(VERSION 3.30.5)
 2 project(Aegean LANGUAGES CXX)
 3 set(module_name aegean)
 4 
 5 add_library(${module_name})
 6 target_compile_features(${module_name} PUBLIC cxx_std_23)
 7 target_sources(${module_name}
 8                PUBLIC
 9                FILE_SET cxx_modules TYPE CXX_MODULES
10                FILES ${PROJECT_NAME}.cppm
11                PRIVATE ${module_name}.cpp
12 )
13 
14 add_executable(${PROJECT_NAME} main.cpp)
15 target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
16 target_link_libraries(${PROJECT_NAME} ${module_name})
17 
18 # command line build
19 # rm -rf build && cmake -B build -G Ninja  && cmake --build build --verbose && build/Aegean

The project() statement (line 1) as always names the project. I add set(module_name aegean) (line 2) as a convenience to use ${module_name}, along with ${PROJECT_NAME} to easily change the names needed later in the file.

Kitware, the creators of CMake, reused add_library() (line 9) instead of creating a new statement for modules. This makes it easier to remember. It takes the module’s name.

The target_sources() (line 11) specifies which files are part of the module. First, a FILE_SET (line 13) states an MUI is being built using CXX_MODULES, an addition specifically for C++ modules. The FILES (line 14) indicates which file to use. The PRIVATE (line 15) brings the implementation unit file into the build.

The statement target_compile_features() (lines 6 and 10) specifies the C++ standard used. This is required when building on the command line but not in CLion.

Building the main file and any other non-module files is the same as using any library. You can either list the files in another target_sources() or include them in the add_executable() statement (line 5). The standard target_link_libraries() (line 7) associates the module with the target.

The last line (line 19) is used to build from the command line instead of CLion. It creates a build directory for the CMake files, populates it, builds it with verbose output to see what happens, and runs the executable. I added this so I don’t have to remember or type it each time it’s needed.

I won’t repeat the command lines or the CMake files for all the following examples unless the project in a chapter requires significant changes. Later chapters with a clowder of files and subdirectories will need expanded build details. (Are files that won’t build a glare of files?)