Build and Check a Clang Module Map with modularize-20

Ubuntu's modularize-20 checks a batch of C or C++ headers for modularisation problems and can draft a starting module.modulemap for you. The command does not rewrite your headers. Allow 15 to 30 minutes for a small header set, plus time to fix any diagnostics it finds.

This guide uses clang-tools-20 version 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139 and the installed executable reports LLVM 20.1.8. You need readable headers, a shell, and compiler-style arguments that let Clang parse them. The checks are normally unprivileged; use sudo only to read files that your account genuinely cannot access.

1. Make a header list

Create a plain text file with one header per line. Blank lines and lines beginning with # are ignored. A header can be followed by a colon and space-separated dependency names on the same line. Relative names are resolved relative to the list file unless you supply --prefix.

mkdir -p /tmp/modularize-demo/include
cat > /tmp/modularize-demo/include/widget.h <<'EOF'
#ifndef WIDGET_H
#define WIDGET_H
int widget_value;
#endif
EOF
cat > /tmp/modularize-demo/headers.list <<'EOF'
include/widget.h
EOF

The commands above create disposable test material. For a real project, keep the list beside the source tree or in a build directory that is easy to review.

2. Check that headers compile on their own

Run the list with the front-end arguments after it. The -x c++ argument tells Clang how to interpret the header; the tool otherwise assumes C++ for .h files. Pass -I for directories needed while checking includes.

modularize-20 /tmp/modularize-demo/headers.list \
  -x c++ -I /tmp/modularize-demo/include

A clean run is quiet and returns status zero. Check the status immediately if you are testing a script:

printf '%s\n' "$?"
0

Checkpoint: If the command prints a compiler diagnostic, fix the include path, language mode, macro definitions, or header itself before moving on. Do not hide a real parse error with --no-coverage-check; that option controls a different check.

3. Separate good and problem files

For a larger list, add --display-file-lists. The tool prints headers with no detected errors, headers with possible errors, and a combined list. In the combined list, a leading # marks a problem file.

modularize-20 --display-file-lists \
  /tmp/modularize-demo/headers.list \
  -x c++ -I /tmp/modularize-demo/include

This is a useful triage pass, not a proof that a header is safe in every build configuration. Header behaviour can depend on macros, target options and include order. Preserve the reported list, then repeat the check with the same front-end arguments used by your project.

4. Generate a module map

Use --module-map-path when you want the tool to act as a module-map assistant. It skips the normal modularisation checks and writes a generated file from the header list.

modularize-20 \
  --module-map-path=/tmp/modularize-demo/module.modulemap \
  --root-module=demo \
  /tmp/modularize-demo/headers.list \
  -x c++ -I /tmp/modularize-demo/include

sed -n '1,120p' /tmp/modularize-demo/module.modulemap

For the example, expect a module named demo containing a submodule for the listed header and an export * line. The generated file is a starting point: inspect its paths, module names, visibility and dependencies before putting it into a build.

Warning: --module-map-path overwrites the named output. Choose a new path while experimenting, or make a backup first:

cp --preserve=all /path/to/module.modulemap /path/to/module.modulemap.bak
modularize-20 --module-map-path=/path/to/module.modulemap /path/to/headers.list \
  -x c++

If the generated result is wrong, restore the backup with mv. Do not delete the backup until the replacement has been reviewed.

5. Check coverage when a module map already exists

If the header list contains a module map, the normal run performs a coverage check using the include paths supplied with -I. To run only that part, use --coverage-check-only:

modularize-20 --coverage-check-only \
  /path/to/headers.list -I /path/to/include -x c++

Use --no-coverage-check only when you deliberately want to skip coverage. These switches do not repair a module map and do not change compiler include paths.

6. Diagnose the common traps

Done means