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.
The route
Jump straight to the step you need, or tick off Done means at the end.
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
- Missing header. Usually means the list path or
-Ipath is wrong. Test the exact file withtest -r /path/to/header.hbefore changing the module map. - Works from one directory, fails from another. A list entry with a relative path can break after a script changes directory. Use absolute names or keep the list and its relative headers in a stable layout.
- Wrong language mode. Headers written in C need
-x c; headers using project macros may need matching-DNAME=valuearguments. Copy the relevant arguments from the real compile command. - Zero exit status is not a guarantee. It means this invocation found no detected problem, not that every target platform, macro combination or downstream build rule is safe.
Done means
- Header list is right. It contains the intended files and dependencies.
- Check passes. It ran clean with the project's language, include and macro arguments.
- Problem files reviewed. Anything
--display-file-listsflagged has been looked at, not ignored. - Module map checked. It was generated to a reviewed path and its contents were checked manually.
- Backup taken. Any existing module map was backed up before an intentional overwrite.