Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/build/execute.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,9 @@ bool sources_newer_than(const std::filesystem::path& projectRoot,
}
for (auto& f : mcpp::modgraph::expand_glob(projectRoot, "src/**/*")) {
auto ext = f.extension().string();
if (ext != ".cppm" && ext != ".cpp" && ext != ".cc" &&
ext != ".cxx" && ext != ".c" && ext != ".h" && ext != ".hpp")
if (ext != ".cppm" && ext != ".ccm" && ext != ".cxxm" && ext != ".ixx" &&
ext != ".cpp" && ext != ".cc" && ext != ".cxx" && ext != ".c" &&
ext != ".h" && ext != ".hpp")
continue;
auto ft = std::filesystem::last_write_time(f, ec);
if (ec || ft > ninjaTime) return true;
Expand Down
8 changes: 4 additions & 4 deletions src/build/plan.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ std::string object_filename_for(const std::filesystem::path& src,
return src.filename().string() + std::string(objExt);
}
auto stem = src.stem().string();
// distinguish .cppm vs .cpp by extension prefix to avoid collisions
return stem + (ext == ".cppm"
// distinguish .cppm/.ccm/.cxxm/.ixx vs .cpp/.cc/.cxx by extension prefix
return stem + ((ext == ".cppm" || ext == ".ccm" || ext == ".cxxm" || ext == ".ixx")
? ".m" + std::string(objExt)
: std::string(objExt));
}
Expand Down Expand Up @@ -751,7 +751,7 @@ make_plan(const mcpp::manifest::Manifest& manifest,
auto append_package_objects = [&](LinkUnit& lu, const std::string& packageName) {
for (auto& cu : plan.compileUnits) {
if (cu.packageName != packageName) continue;
if (cu.source.extension() == ".cppm") {
if (cu.providesModule) {
lu.objects.push_back(cu.object);
}
}
Expand Down Expand Up @@ -811,7 +811,7 @@ make_plan(const mcpp::manifest::Manifest& manifest,
// For binary target, also include main.cpp's object if main is present.
for (auto& cu : plan.compileUnits) {
if (sharedDepPackages.contains(cu.packageName)) continue;
if (cu.source.extension() == ".cppm") {
if (cu.providesModule) {
lu.objects.push_back(cu.object);
}
}
Expand Down
102 changes: 102 additions & 0 deletions tests/e2e/152_module_extensions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# requires: elf gcc
# All module interface extensions (.cppm .ccm .cxxm .ixx) must compile and link
set -e

TMP=$(mktemp -d)
trap "rm -rf $TMP" EXIT

cd "$TMP"
"$MCPP" new moduleext > /dev/null
cd moduleext

# ---- module interface sources (one per extension) ----
cat > src/greet.cppm <<'EOF'
export module moduleext.greet;
import std;
export auto greet(std::string_view who) -> void {
std::println("Hello from .cppm, {}!", who);
}
EOF

cat > src/info.ccm <<'EOF'
export module moduleext.info;
import std;
export auto info(std::string_view what) -> void {
std::println("Info from .ccm: {}", what);
}
EOF

cat > src/stat.cxxm <<'EOF'
export module moduleext.stat;
import std;
export auto stat(int n) -> void {
std::println("Stat from .cxxm: {}", n);
}
EOF

cat > src/data.ixx <<'EOF'
export module moduleext.data;
import std;
export auto data(std::string_view key) -> void {
std::println("Data from .ixx: {}", key);
}
EOF

# ---- consumer ----
cat > src/main.cpp <<'EOF'
import std;
import moduleext.greet;
import moduleext.info;
import moduleext.stat;
import moduleext.data;
int main() {
greet("world");
info("build");
stat(42);
data("key");
std::println("All module extensions OK");
return 0;
}
EOF

# Default glob only picks up .cppm/.cpp/.cc/.c — explicitly add .ccm/.cxxm/.ixx
cat > mcpp.toml <<'EOF'
[package]
name = "moduleext"
version = "0.1.0"
[modules]
sources = ["src/**/*.cppm", "src/**/*.ccm", "src/**/*.cxxm", "src/**/*.ixx", "src/**/*.cpp"]
EOF

# ---- build & run ----
"$MCPP" build > build.log 2>&1 || {
cat build.log
echo "FAIL: build failed"
exit 1
}

out="$("$MCPP" run 2>&1)"
[[ "$out" == *"from .cppm"* ]] || { echo "module greet (.cppm) not invoked: $out"; exit 1; }
[[ "$out" == *"from .ccm"* ]] || { echo "module info (.ccm) not invoked: $out"; exit 1; }
[[ "$out" == *"from .cxxm"* ]] || { echo "module stat (.cxxm) not invoked: $out"; exit 1; }
[[ "$out" == *"from .ixx"* ]] || { echo "module data (.ixx) not invoked: $out"; exit 1; }
[[ "$out" == *"All module extensions OK"* ]] || { echo "final message missing: $out"; exit 1; }

# ---- verify build.ninja ----
build_ninja="$(find target -name build.ninja | head -1)"
[[ -n "$build_ninja" ]] || { echo "no build.ninja generated"; exit 1; }

# 4 BMIs
grep -q "gcm.cache/moduleext.greet.gcm" "$build_ninja" || { echo "ninja missing greet BMI"; exit 1; }
grep -q "gcm.cache/moduleext.info.gcm" "$build_ninja" || { echo "ninja missing info BMI"; exit 1; }
grep -q "gcm.cache/moduleext.stat.gcm" "$build_ninja" || { echo "ninja missing stat BMI"; exit 1; }
grep -q "gcm.cache/moduleext.data.gcm" "$build_ninja" || { echo "ninja missing data BMI"; exit 1; }

# 4 .m.o objects (one per module interface extension)
grep -q "greet.m.o" "$build_ninja" || { echo "ninja missing greet.m.o (.cppm)"; exit 1; }
grep -q "info.m.o" "$build_ninja" || { echo "ninja missing info.m.o (.ccm)"; exit 1; }
grep -q "stat.m.o" "$build_ninja" || { echo "ninja missing stat.m.o (.cxxm)"; exit 1; }
grep -q "data.m.o" "$build_ninja" || { echo "ninja missing data.m.o (.ixx)"; exit 1; }

echo "OK"
34 changes: 34 additions & 0 deletions tests/unit/test_module_extensions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <gtest/gtest.h>

import std;
import mcpp.build.plan;

using namespace mcpp::build;

TEST(ModuleExtensions, CppmGetsDotMPrefix) {
EXPECT_EQ(object_filename_for("src/foo.cppm", ".o"), "foo.m.o");
}

TEST(ModuleExtensions, CcmGetsDotMPrefix) {
EXPECT_EQ(object_filename_for("src/foo.ccm", ".o"), "foo.m.o");
}

TEST(ModuleExtensions, CxxmGetsDotMPrefix) {
EXPECT_EQ(object_filename_for("src/foo.cxxm", ".o"), "foo.m.o");
}

TEST(ModuleExtensions, IxxGetsDotMPrefix) {
EXPECT_EQ(object_filename_for("src/foo.ixx", ".o"), "foo.m.o");
}

TEST(ModuleExtensions, CppHasNoPrefix) {
EXPECT_EQ(object_filename_for("src/foo.cpp", ".o"), "foo.o");
}

TEST(ModuleExtensions, CcHasNoPrefix) {
EXPECT_EQ(object_filename_for("src/foo.cc", ".o"), "foo.o");
}

TEST(ModuleExtensions, AsmKeepsFullExt) {
EXPECT_EQ(object_filename_for("src/foo.S", ".o"), "foo.S.o");
}