Building C++20 Modules with Xmake
Xmake has first-class C++20 modules support across GCC, Clang, and MSVC. It handles the BMI (Built Module Interface) cache, dependency scanning, and per-compiler flag differences automatically — you mostly just point it at the files and enable the policy.
1. Prerequisites
- C++20 language version.
- A new-ish compiler: GCC 14+, Clang 16+ (better: 17+), MSVC 19.34+ (VS 2022 17.4+).
build.c++.modulespolicy enabled.
2. Minimal executable with modules
Project layout:
myapp/
├── xmake.lua
└── src/
├── main.cpp # importer (regular .cpp)
└── math.mpp # module interface
// src/math.mpp
export module math;
export int add(int a, int b) {
return a + b;
}
// src/main.cpp
import math;
#include <iostream>
int main() {
std::cout << add(2, 3) << "\n";
return 0;
}
-- xmake.lua
add_rules("mode.debug", "mode.release")
target("myapp")
set_kind("binary")
set_languages("c++20")
set_policy("build.c++.modules", true)
add_files("src/main.cpp", "src/math.mpp")
Build + run:
xmake f -m release
xmake
xmake run myapp
That's it — xmake scans the .mpp, builds the BMI, and links.
3. File extension conventions
Xmake recognizes all common module interface extensions:
| Extension | Used by | Notes |
|---|---|---|
.mpp |
xmake convention | Recommended — cross-compiler |
.cppm |
Clang convention | Works everywhere with xmake |
.ixx |
MSVC convention | Works everywhere with xmake |
.mxx |
Alternative | Also recognized |
.cpp |
Regular source | Only a module if it starts with module; or export module |
Use whichever you prefer; xmake treats them uniformly. .mpp is the xmake default in examples.
4. Module target kinds
Binary (executable)
target("app")
set_kind("binary")
set_languages("c++20")
set_policy("build.c++.modules", true)
add_files("src/*.cpp", "src/*.mpp")
Static/shared library exposing modules
target("mylib")
set_kind("static") -- or "shared"
set_languages("c++20")
set_policy("build.c++.modules", true)
add_files("src/*.cpp", "src/*.mpp")
Consumers that add_deps("mylib") inherit the module search path automatically.
moduleonly — pure module library
When a library has no non-module sources (no .cpp, only .mpp/.cppm), use moduleonly. Xmake skips the linker and just ships the BMI + sources.
target("math_modules")
set_kind("moduleonly")
set_languages("c++20")
add_files("src/*.mpp")
This is the right choice for header-only-style module libraries. Consumers still build the BMI but don't link anything.
Headeronly with module hints
For mixed header/module libs, keep headeronly for the header side and a separate moduleonly target for the module side:
target("mylib_headers")
set_kind("headeronly")
add_headerfiles("include/(**.h)")
target("mylib_modules")
set_kind("moduleonly")
set_languages("c++20")
add_files("src/*.mpp")
5. Module dependencies between files
Xmake scans import ...; statements and orders compilation automatically. No manual dep lists needed:
// src/math.mpp
export module math;
export int add(int a, int b) { return a + b; }
// src/geometry.mpp
export module geometry;
import math; // xmake sees this, builds math first
export int perimeter(int w, int h) { return 2 * (w + h); }
target("app")
set_languages("c++20")
set_policy("build.c++.modules", true)
add_files("src/main.cpp", "src/math.mpp", "src/geometry.mpp")
6. Module partitions
// src/math/core.mpp
export module math:core;
export int add(int a, int b) { return a + b; }
// src/math/vec.mpp
export module math:vec;
export struct Vec { int x, y; };
// src/math.mpp (primary interface)
export module math;
export import :core;
export import :vec;
Just add them all to add_files — xmake handles partition ordering.
7. Standard library modules
import std; / import std.compat; — supported where the compiler supports it:
- Clang 17+: native
import std;once libc++ modules are built. - MSVC 19.35+ (VS 2022 17.5+): native
import std;out of the box. - GCC 14+: partial; check the release notes.
Enable in xmake:
target("app")
set_languages("c++23") -- std modules usually need c++23
set_policy("build.c++.modules", true)
set_policy("build.c++.modules.std", true)
add_files("src/*.cpp", "src/*.mpp")
// src/main.cpp
import std;
int main() { std::cout << "hi\n"; }
8. Per-compiler notes
Clang
- Best cross-platform support. Use Clang 17+ for production.
- Needs
libc++forimport std;. - Xmake passes
-fmodules -fbuiltin-module-mapas needed.
GCC
- GCC 14+ is the realistic baseline. GCC 11–13 have partial/buggy support.
- Dependency scanner is less mature than Clang's — if a file is mis-ordered,
xmake -rvand inspect.
MSVC
- Works since VS 2022 17.4, best on 17.6+.
.ixxis MSVC's natural extension but.mpp/.cppmalso work.- Incremental builds on MSVC are slower than Clang.
Mixed toolchains
Stick with one toolchain per build. Mixing Clang BMIs with GCC/MSVC won't work — BMI format is compiler-specific.
9. Dependency packages
Consuming a module package:
add_requires("fmt")
target("app")
set_languages("c++20")
set_policy("build.c++.modules", true)
add_files("src/main.cpp")
add_packages("fmt")
For a package that is modules-only, see xmake-private-packages:
package("foo")
set_kind("library", {moduleonly = true})
set_sourcedir(path.join(os.scriptdir(), "src"))
on_install(function (package)
import("package.tools.xmake").install(package, {})
end)
10. Inspecting the build
xmake -v # show actual compile commands (useful for BMI flags)
xmake -rv # verbose rebuild
xmake clean --all # drop BMI cache when debugging dep-order bugs
BMI files live under build/.gens/<target>/. Safe to delete — xmake will regenerate.
Common pitfalls
- Forgot
set_policy("build.c++.modules", true). Xmake then builds.mppas regular C++ and ignoresimport. Symptom: "unresolved external" orimporttreated as an error. set_languages("c++17"). Modules requirec++20(orc++23for std modules).- Compiler too old. "imports are not enabled" → upgrade. Xmake can't work around missing compiler support.
- Stale BMI after edit. Dep-scan sometimes misses an edit;
xmake -rvorxmake clean --all. - Mixing toolchains between dependent targets.
mylibbuilt with Clang,appwith GCC → BMI is unreadable. Rebuild all targets with one toolchain. moduleonlywith regular sources. Xmake refuses to link — either split the.cppinto its ownstatic/sharedtarget, or dropmoduleonlyand usestatic.import std;on a compiler that doesn't support it. Even with the policy enabled, the compiler must ship libc++/MSVC std modules. Fall back to#include <iostream>if unsupported.- Module cycles.
a.mppimportsb,b.mppimportsa. C++20 modules forbid cycles — refactor one file into a partition or restructure.
When to branch out
- Target kinds (binary/static/shared/headeronly/moduleonly) →
xmake-targets - Compiler / toolchain selection →
xmake-toolchains,xmake-cross-compilation - Distributing a module library →
xmake-private-packages - Slow module builds →
xmake-build-optimization