|
| 1 | +# CMake build description for mcpp — a like-for-like counterpart to mcpp.toml |
| 2 | +# and to the xmake.lua beside it. |
| 3 | +# |
| 4 | +# WHY THIS FILE EXISTS. The build-engine benchmark in bench/ uses cmake as its |
| 5 | +# performance baseline, and until now cmake could only build the synthetic |
| 6 | +# fixture. The interesting workload is mcpp itself: 139 module interface units, |
| 7 | +# 57k lines, every one of them `import std;`. Without this file the real-project |
| 8 | +# arm had no baseline to be measured against. |
| 9 | +# |
| 10 | +# FAIRNESS CONTRACT — all five must hold or the comparison means nothing: |
| 11 | +# 1. same compiler binary — the harness passes -DCMAKE_CXX_COMPILER, and the |
| 12 | +# payload's binutils + sysroot are added below |
| 13 | +# 2. same language flags — -std=c++23, -O2 in release |
| 14 | +# 3. same source set — src/**.cppm + src/main.cpp + the pinned |
| 15 | +# mcpplibs.cmdline units |
| 16 | +# 4. same link output kind — one binary, -static-libstdc++ |
| 17 | +# 5. same standard library — `import std;`, not a header shim |
| 18 | +# |
| 19 | +# Usage (benchmark): |
| 20 | +# cmake -G Ninja -S bench/projects/mcpp -B build-cmake \ |
| 21 | +# -DCMAKE_BUILD_TYPE=Release \ |
| 22 | +# -DCMAKE_CXX_COMPILER=$HOME/.mcpp/registry/data/xpkgs/xim-x-gcc/16.1.0/bin/g++ |
| 23 | +# cmake --build build-cmake |
| 24 | + |
| 25 | +cmake_minimum_required(VERSION 3.30) |
| 26 | + |
| 27 | +# `import std;` is still behind an experimental gate whose key changes with the |
| 28 | +# CMake version — this is the CMake 4.0 key. Set BEFORE project(), because the |
| 29 | +# compiler-support probe that reads it runs during project(). |
| 30 | +set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "a9e1cf81-9932-4810-974b-6eccaf14e457") |
| 31 | + |
| 32 | +project(mcpp CXX) |
| 33 | + |
| 34 | +set(CMAKE_CXX_STANDARD 23) |
| 35 | +set(CMAKE_CXX_STANDARD_REQUIRED ON) |
| 36 | +set(CMAKE_CXX_EXTENSIONS OFF) |
| 37 | +# Every mcpp module says `import std;`. This asks CMake to build the standard |
| 38 | +# library module from the compiler's own libstdc++.modules.json, which the |
| 39 | +# hermetic gcc payload ships. |
| 40 | +set(CMAKE_CXX_MODULE_STD 1) |
| 41 | + |
| 42 | +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) |
| 43 | + set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) |
| 44 | +endif() |
| 45 | + |
| 46 | +# --------------------------------------------------------------------------- |
| 47 | +# The hermetic payload. |
| 48 | +# |
| 49 | +# mcpp always passes an explicit -B<binutils> and --sysroot; a bare g++ from the |
| 50 | +# payload otherwise falls back to PATH for `as`/`ld` and picks up whatever shim |
| 51 | +# is there — on a machine with xlings installed, a stale one. The two arms must |
| 52 | +# drive an identical process tree, so reproduce the full triple here rather than |
| 53 | +# hoping the environment matches. |
| 54 | +# |
| 55 | +# -B and --sysroot must reach BOTH compile and link: the driver spawns `as` from |
| 56 | +# it at compile time and `ld` from it at link time. Adding it on one side only |
| 57 | +# silently falls through to PATH. |
| 58 | +# --------------------------------------------------------------------------- |
| 59 | +if(DEFINED ENV{MCPP_HOME}) |
| 60 | + set(MCPP_HOME "$ENV{MCPP_HOME}") |
| 61 | +else() |
| 62 | + set(MCPP_HOME "$ENV{HOME}/.mcpp") |
| 63 | +endif() |
| 64 | +# This file lives in bench/projects/mcpp/, so the tree it builds is three up. |
| 65 | +# Resolved to an absolute path once, because a FILE_SET's base directory and a |
| 66 | +# relative glob disagree about what "here" means. |
| 67 | +get_filename_component(MCPP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../.." ABSOLUTE) |
| 68 | +if(NOT EXISTS "${MCPP_ROOT}/mcpp.toml") |
| 69 | + message(FATAL_ERROR "expected mcpp's tree at ${MCPP_ROOT} (no mcpp.toml there)") |
| 70 | +endif() |
| 71 | + |
| 72 | +set(MCPP_XPKGS "${MCPP_HOME}/registry/data/xpkgs") |
| 73 | +set(MCPP_SYSROOT "${MCPP_HOME}/registry/subos/default") |
| 74 | + |
| 75 | +file(GLOB MCPP_BINUTILS_DIRS "${MCPP_XPKGS}/xim-x-binutils/*") |
| 76 | +# CMAKE_CXX_FLAGS, not add_compile_options(): CMake generates the `std` module |
| 77 | +# target ITSELF, and directory-scope options do not reach it. Without this the |
| 78 | +# std module compiles against whatever libc headers the compiler defaults to |
| 79 | +# while every mcpp unit compiles against the sysroot, and the build dies on a |
| 80 | +# type that exists in both: |
| 81 | +# |
| 82 | +# error: conflicting type for imported declaration 'char _IO_FILE::_unused2 [20]' |
| 83 | +# .../xlings/.../glibc-2.39/include/bits/types/struct_FILE.h:98 |
| 84 | +# note: existing declaration 'char _IO_FILE::_unused2 [8]' |
| 85 | +# .../mcpp/registry/subos/default/usr/include/bits/types/struct_FILE.h:109 |
| 86 | +# |
| 87 | +# Two glibcs in one link, and the error names neither the flag nor the target |
| 88 | +# that is wrong. |
| 89 | +if(MCPP_BINUTILS_DIRS) |
| 90 | + list(SORT MCPP_BINUTILS_DIRS) |
| 91 | + list(GET MCPP_BINUTILS_DIRS -1 MCPP_BINUTILS) |
| 92 | + string(APPEND CMAKE_CXX_FLAGS " -B${MCPP_BINUTILS}/bin") |
| 93 | + string(APPEND CMAKE_EXE_LINKER_FLAGS " -B${MCPP_BINUTILS}/bin") |
| 94 | +endif() |
| 95 | +if(IS_DIRECTORY "${MCPP_SYSROOT}") |
| 96 | + string(APPEND CMAKE_CXX_FLAGS " --sysroot=${MCPP_SYSROOT}") |
| 97 | + string(APPEND CMAKE_EXE_LINKER_FLAGS " --sysroot=${MCPP_SYSROOT}") |
| 98 | +endif() |
| 99 | + |
| 100 | +# --------------------------------------------------------------------------- |
| 101 | +# Source set — mcpp.toml's inferred glob `src/**/*.{cppm,cpp}`. mcpp infers |
| 102 | +# kind=bin from src/main.cpp; CMake needs it spelled out. |
| 103 | +# |
| 104 | +# GLOB, not a hand-written list: the two arms must compile the same files even |
| 105 | +# as the tree changes, and a list that drifts silently measures two different |
| 106 | +# projects. CONFIGURE_DEPENDS re-globs on build so an added module is not missed. |
| 107 | +# --------------------------------------------------------------------------- |
| 108 | +file(GLOB_RECURSE MCPP_MODULES CONFIGURE_DEPENDS "${MCPP_ROOT}/src/*.cppm") |
| 109 | +list(LENGTH MCPP_MODULES MCPP_MODULE_COUNT) |
| 110 | +if(MCPP_MODULE_COUNT EQUAL 0) |
| 111 | + message(FATAL_ERROR "no module interface units found under src/ — refusing to " |
| 112 | + "build a project that is not mcpp") |
| 113 | +endif() |
| 114 | + |
| 115 | +# mcpp.toml pins `mcpplibs.cmdline = "0.0.1"` EXACTLY. Newer versions are often |
| 116 | +# also unpacked in the registry, so pin rather than take the newest: otherwise |
| 117 | +# the two arms are not compiling the same code. |
| 118 | +# |
| 119 | +# mcpp stages prebuilt objects for this dependency out of its global build cache |
| 120 | +# and cmake has no such cache, so cmake compiles the 3 units from source. That is |
| 121 | +# a small handicap on cmake's cold build, and it is declared in the benchmark |
| 122 | +# report rather than hidden. |
| 123 | +set(MCPP_CMDLINE_VERSION "0.0.1") |
| 124 | +set(MCPP_CMDLINE_SRC |
| 125 | + "${MCPP_XPKGS}/mcpplibs-x-cmdline/${MCPP_CMDLINE_VERSION}/cmdline-${MCPP_CMDLINE_VERSION}/src") |
| 126 | +if(IS_DIRECTORY "${MCPP_CMDLINE_SRC}") |
| 127 | + file(GLOB MCPP_CMDLINE_MODULES CONFIGURE_DEPENDS "${MCPP_CMDLINE_SRC}/*.cppm") |
| 128 | +else() |
| 129 | + message(WARNING "mcpplibs.cmdline ${MCPP_CMDLINE_VERSION} not unpacked at " |
| 130 | + "${MCPP_CMDLINE_SRC}; this build will not match mcpp's own") |
| 131 | + set(MCPP_CMDLINE_MODULES "") |
| 132 | +endif() |
| 133 | + |
| 134 | +add_executable(mcpp "${MCPP_ROOT}/src/main.cpp") |
| 135 | + |
| 136 | +# FILE_SET CXX_MODULES is the only way CMake learns these are interface units. |
| 137 | +# Listing them as ordinary sources compiles them as plain TUs and the link fails |
| 138 | +# with missing module symbols. |
| 139 | +target_sources(mcpp |
| 140 | + PRIVATE |
| 141 | + FILE_SET CXX_MODULES BASE_DIRS "${MCPP_ROOT}/src" FILES ${MCPP_MODULES} |
| 142 | +) |
| 143 | + |
| 144 | +# The dependency's units need their OWN file set: a CXX_MODULES set requires |
| 145 | +# every file to live under one of its base directories, which defaults to the |
| 146 | +# project source dir, and these live in the registry outside the tree. |
| 147 | +if(MCPP_CMDLINE_MODULES) |
| 148 | + target_sources(mcpp |
| 149 | + PRIVATE |
| 150 | + FILE_SET mcpp_cmdline_modules |
| 151 | + TYPE CXX_MODULES |
| 152 | + BASE_DIRS "${MCPP_CMDLINE_SRC}" |
| 153 | + FILES ${MCPP_CMDLINE_MODULES} |
| 154 | + ) |
| 155 | +endif() |
| 156 | + |
| 157 | +# mcpp.toml: include_dirs = ["src/libs/json"] — src/libs/json.cppm reaches for |
| 158 | +# <json.hpp> from its global module fragment. |
| 159 | +target_include_directories(mcpp PRIVATE "${MCPP_ROOT}/src/libs/json") |
| 160 | + |
| 161 | +# mcpp.toml default: static_stdlib = true, so the binary is portable. |
| 162 | +target_link_options(mcpp PRIVATE -static-libstdc++) |
| 163 | + |
| 164 | +message(STATUS "mcpp: ${MCPP_MODULE_COUNT} module interface units + src/main.cpp") |
0 commit comments