Skip to content

Commit eb3200a

Browse files
committed
feat(profile): default build profile = dev (mainstream); --release opt-in; v0.0.76
Resolves #179. mcpp's old default profile was "release" (inherited from its xmake/xlings lineage), but the dominant convention is the opposite: a bare build is debug/dev and release is opt-in (Cargo/Meson/CMake/Zig/Bazel/MSBuild). Since mcpp's surface is Cargo-flavored, flip the global default to "dev" (-O0 -g); release is opt-in via --release / --profile release. - prepare.cppm: global default profile release -> dev. Precedence: --profile/--release/--dev flag > [build].default-profile > "dev". - cli.cppm/cmd_build.cppm: add --release / --dev shorthands (--release is now the mainstream opt-in flag). - manifest.cppm: [build].default-profile (alias: profile) — a project's own default; its role inverts under the flip to "opt into release" for projects that ship/run optimized. This is the migration mechanism. - mcpp.toml: default-profile = "release" — keeps mcpp's self-host CI build and release.yml at -O2 with ZERO workflow changes (no --release threaded through release pipelines; mcpp's own CI doesn't slow to -O0). - cross-build-test.yml: bump xlings cache key v2->v3 (the failing job was a poisoned ~/.xlings cache from the bootstrap-pin-timing, not code). Tests: 87_build_default_profile.sh (global dev; --release/--dev; project default-profile=release; --dev override), 68_profile_passthrough.sh (updated for the new default). Regression: unit 27/0. Doc: design doc L0.5 (convention table + decision + migration). mcpp self-build verified 'Finished release [optimized]'.
1 parent 7186ec2 commit eb3200a

10 files changed

Lines changed: 130 additions & 13 deletions

File tree

.agents/docs/2026-06-29-manifest-environment-and-platform-design.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,43 @@ expressible, all backed by existing xlings behavior.
9292

9393
---
9494

95+
## L0.5 — Default build profile: follow the mainstream (dev), `--release` opt-in
96+
97+
(Issue #179.) mcpp's old default was `release`; the prevailing convention across
98+
modern build tools is the **opposite** — a bare build is **debug/dev**, release is
99+
opt-in:
100+
101+
| Tool | bare-command default | release via |
102+
|---|---|---|
103+
| Cargo | **dev/debug** (-O0 + debuginfo) | `cargo build --release` |
104+
| Meson | **debug** | `--buildtype=release` |
105+
| CMake | empty `CMAKE_BUILD_TYPE` (≈debug) | `-DCMAKE_BUILD_TYPE=Release` |
106+
| Zig | **Debug** | `-Doptimize=ReleaseFast/Safe/Small` |
107+
| Bazel | **fastbuild** (no opt) | `-c opt` |
108+
| MSBuild/VS | **Debug** | Release configuration |
109+
| **xmake** | **release** | `xmake f -m debug` |
110+
111+
Debug-default dominates (Cargo/Meson/CMake/Zig/Bazel/MSBuild); only the **xmake**
112+
lineage defaults to release — exactly where mcpp's old default came from (mcpp builds
113+
on xlings/xmake). But mcpp's *surface* is Cargo-flavored, so users expect Cargo
114+
semantics. **Decision: flip the global default to `dev` (-O0 -g); release is opt-in
115+
via `--release` / `--profile release`.**
116+
117+
- **Precedence**: `--profile NAME` / `--release` / `--dev` flag > `[build].default-profile`
118+
(project default) > global `dev`. (`prepare.cppm`; `--dev`/`--release` are shorthands
119+
in `cli.cppm`/`cmd_build.cppm`.)
120+
- **`[build].default-profile`** (alias: `profile`) — a project's own default; its role
121+
*inverts* under the flip: it now means **"opt into release"** for projects that ship/
122+
run optimized by default. **mcpp's own `mcpp.toml` sets `default-profile = "release"`**
123+
so the self-host CI build and `release.yml` stay `-O2` with **zero workflow changes**
124+
the knob *is* the migration mechanism (no `--release` threaded through release pipelines,
125+
and mcpp's own CI doesn't slow to `-O0`).
126+
- **Distribution footgun**: a project that defaults to dev passes `--release` to produce
127+
a distributable (a pack-time release guard is a possible follow-up).
128+
- Tests: `tests/e2e/87_build_default_profile.sh`, `68_profile_passthrough.sh` (updated).
129+
130+
---
131+
95132
## L0 — Dependency categories: keep three axes, wire `build`
96133

97134
Keep Cargo/Conan's **normal / dev / build** trichotomy (mcpp already declares all

.github/workflows/cross-build-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ jobs:
7474
uses: actions/cache@v4
7575
with:
7676
path: ~/.xlings
77-
key: xlings-${{ runner.os }}-v2-${{ hashFiles('.xlings.json') }}
77+
key: xlings-${{ runner.os }}-v3-${{ hashFiles('.xlings.json') }}
7878
restore-keys: |
79-
xlings-${{ runner.os }}-v2-
79+
xlings-${{ runner.os }}-v3-
8080
8181
- name: Install qemu-user-static
8282
run: |

mcpp.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.75"
3+
version = "0.0.76"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]
77
repo = "https://github.com/mcpp-community/mcpp"
88

99
[build]
10+
# The global default profile is now "dev" (-O0 -g, mainstream convention);
11+
# mcpp itself is a shipped tool, so opt its plain `mcpp build` (and release.yml's
12+
# self-host build) back into the optimized profile. Without this the released
13+
# binary would be -O0. A `--profile`/`--dev`/`--release` flag still overrides.
14+
default-profile = "release"
1015
# nlohmann/json.hpp lives in src/libs/json/; expose it to the global
1116
# module fragment `#include <json.hpp>` in src/libs/json.cppm.
1217
include_dirs = ["src/libs/json"]

src/build/prepare.cppm

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,10 +554,19 @@ prepare_build(bool print_fingerprint,
554554
// 1. project mcpp.toml [toolchain].<platform> or .default
555555
// 2. global ~/.mcpp/config.toml [toolchain].default
556556
// 3. hard error (no system fallback)
557-
// Resolve the build profile: --profile (default "release") → built-in
558-
// defaults, overlaid by any [profile.<name>] from the manifest → buildConfig.
557+
// Resolve the build profile, overlaid by any [profile.<name>] from the
558+
// manifest → buildConfig.
559559
{
560-
std::string pname = overrides.profile.empty() ? "release" : overrides.profile;
560+
// Precedence: --profile / --release / --dev flag (overrides.profile) >
561+
// [build].default-profile (project default) > "dev" (global default).
562+
// The global default is "dev" (-O0 -g) to follow the dominant convention
563+
// (Cargo/Meson/CMake/Zig/Bazel/MSBuild all default to debug); release is
564+
// opt-in via --release / --profile release. A project that wants its
565+
// plain `mcpp build` optimized sets [build].default-profile = "release"
566+
// (mcpp's own mcpp.toml does this, so the released binary stays -O2).
567+
std::string pname = !overrides.profile.empty() ? overrides.profile
568+
: !m->buildConfig.defaultProfile.empty() ? m->buildConfig.defaultProfile
569+
: "dev";
561570
mcpp::manifest::Profile pr;
562571
if (pname == "dev" || pname == "debug") { pr.optLevel = "0"; pr.debug = true; }
563572
else if (pname == "dist") { pr.optLevel = "3"; pr.strip = true; }

src/cli.cppm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ int run(int argc, char** argv) {
226226
.help("Build only the named workspace member"))
227227
.option(cl::Option("profile").takes_value().value_name("NAME")
228228
.help("Build profile: release (default) | dev | dist | <[profile.*] name>"))
229+
.option(cl::Option("release").help("Shorthand for --profile release"))
230+
.option(cl::Option("dev").help("Shorthand for --profile dev (-O0 -g)"))
229231
.option(cl::Option("features").takes_value().value_name("LIST")
230232
.help("Activate root-package features (comma-separated)"))
231233
.option(cl::Option("cap").takes_value().value_name("LIST")

src/cli/cmd_build.cppm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ export int cmd_build(const mcpplibs::cmdline::ParsedArgs& parsed) {
2626
mcpp::build::BuildOverrides ov;
2727
if (auto t = parsed.value("target")) ov.target_triple = *t;
2828
if (auto p = parsed.value("package")) ov.package_filter = *p;
29+
// Profile selection precedence: --profile NAME > --release / --dev > the
30+
// project default ([build].default-profile) > "release", resolved in
31+
// prepare_build. --release/--dev are shorthands only.
2932
if (auto pr = parsed.value("profile")) ov.profile = *pr;
33+
else if (parsed.is_flag_set("release")) ov.profile = "release";
34+
else if (parsed.is_flag_set("dev")) ov.profile = "dev";
3035
if (auto fs = parsed.value("features")) ov.features = *fs;
3136
if (auto cp = parsed.value("cap")) ov.capabilities = *cp;
3237
ov.strict = parsed.is_flag_set("strict");

src/manifest.cppm

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ struct BuildConfig {
148148
bool debug = false; // -g
149149
bool lto = false; // -flto
150150
bool strip = false; // link -s
151+
// `[build].default-profile` (alias: `profile`) — the project's DEFAULT
152+
// profile when no --profile/--dev/--release is passed. The global convention
153+
// default stays "release"; this lets a project opt its plain `mcpp build`
154+
// into e.g. "dev" without typing --profile. Precedence: --profile/--dev/
155+
// --release flag > [build].default-profile > "release". NOTE (distribution
156+
// footgun): a project that defaults to dev should pass `--profile release`
157+
// when producing a distributable (a pack-time release guard is a follow-up).
158+
std::string defaultProfile;
151159
};
152160

153161
// `[runtime]` — requirements needed when launching built binaries.
@@ -1118,6 +1126,8 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
11181126
if (auto v = doc->get_string_array("build.cxxflags")) m.buildConfig.cxxflags = *v;
11191127
if (auto v = doc->get_string_array("build.ldflags")) m.buildConfig.ldflags = *v;
11201128
if (auto v = doc->get_string("build.c_standard")) m.buildConfig.cStandard = *v;
1129+
if (auto v = doc->get_string("build.default-profile")) m.buildConfig.defaultProfile = *v;
1130+
else if (auto v = doc->get_string("build.profile")) m.buildConfig.defaultProfile = *v; // accepted alias
11211131
if (auto v = doc->get_string("build.macos_deployment_target"))
11221132
m.buildConfig.macosDeploymentTarget = *v;
11231133
for (auto const& flag : m.buildConfig.cxxflags) {

src/toolchain/fingerprint.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "0.0.75";
21+
inline constexpr std::string_view MCPP_VERSION = "0.0.76";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

tests/e2e/68_profile_passthrough.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ strip = true
1717
cxxflags = ["-fno-plt"]
1818
EOF
1919

20-
# Built-in release default: -O2, no -g.
21-
"$MCPP" build --verbose > rel.log 2>&1 || { cat rel.log; echo "release build failed"; exit 1; }
20+
# Global default is now "dev" (-O0 -g, mainstream convention); release is opt-in.
21+
"$MCPP" build --release --verbose > rel.log 2>&1 || { cat rel.log; echo "release build failed"; exit 1; }
2222
grep -q -- "-O2" rel.log || { echo "release missing -O2"; exit 1; }
2323

24-
# dev: -O0 -g.
24+
# Bare `mcpp build` (no flag) → the dev default: -O0 -g.
2525
rm -rf target
26-
"$MCPP" build --profile dev --verbose > dev.log 2>&1 || { cat dev.log; echo "dev build failed"; exit 1; }
27-
grep -q -- "-O0" dev.log || { echo "dev missing -O0"; exit 1; }
28-
grep -q -- "-g" dev.log || { echo "dev missing -g"; exit 1; }
26+
"$MCPP" build --verbose > dev.log 2>&1 || { cat dev.log; echo "dev build failed"; exit 1; }
27+
grep -q -- "-O0" dev.log || { echo "default (dev) missing -O0"; exit 1; }
28+
grep -q -- "-g" dev.log || { echo "default (dev) missing -g"; exit 1; }
2929

3030
# dist from [profile.dist]: -O3 -flto + passthrough cxxflag, stripped binary.
3131
rm -rf target
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
# 87_build_default_profile.sh — profile selection follows the mainstream convention:
3+
# the GLOBAL default is "dev" (-O0 -g, like Cargo/Meson/CMake/Zig/Bazel/MSBuild);
4+
# "release" is opt-in via --release / --profile release. A project can set its own
5+
# default with `[build] default-profile = "<name>"` (e.g. opt back into release).
6+
# Precedence: --profile/--release/--dev flag > [build].default-profile > "dev".
7+
# See .agents/docs/2026-06-29-manifest-environment-and-platform-design.md.
8+
set -e
9+
10+
TMP=$(mktemp -d)
11+
trap "rm -rf $TMP" EXIT
12+
cd "$TMP"
13+
14+
# --- (1) A plain project: no [build].default-profile → GLOBAL default = dev. ---
15+
mkdir -p plain/src
16+
cat > plain/mcpp.toml <<'EOF'
17+
[package]
18+
name = "plain"
19+
version = "0.1.0"
20+
EOF
21+
echo 'int main() { return 0; }' > plain/src/main.cpp
22+
( cd plain
23+
"$MCPP" build > b.log 2>&1 || { cat b.log; echo "FAIL: build errored"; exit 1; }
24+
grep -q '\-O0' compile_commands.json || { echo "FAIL: global default is not dev (-O0)"; cat compile_commands.json; exit 1; }
25+
grep -q '\-g\b' compile_commands.json || { echo "FAIL: global default dev lacks -g"; exit 1; }
26+
# --release opts into the optimized profile.
27+
"$MCPP" build --release > b2.log 2>&1 || { cat b2.log; echo "FAIL: --release errored"; exit 1; }
28+
grep -q '\-O2' compile_commands.json || { echo "FAIL: --release did not yield -O2"; cat compile_commands.json; exit 1; }
29+
)
30+
31+
# --- (2) A project that opts into release via [build].default-profile. ---
32+
mkdir -p opt/src
33+
cat > opt/mcpp.toml <<'EOF'
34+
[package]
35+
name = "opt"
36+
version = "0.1.0"
37+
[build]
38+
default-profile = "release"
39+
EOF
40+
echo 'int main() { return 0; }' > opt/src/main.cpp
41+
( cd opt
42+
"$MCPP" build > b.log 2>&1 || { cat b.log; echo "FAIL: build errored"; exit 1; }
43+
grep -q '\-O2' compile_commands.json || { echo "FAIL: [build].default-profile=release did not yield -O2"; cat compile_commands.json; exit 1; }
44+
# --dev overrides the project's release default back to -O0.
45+
"$MCPP" build --dev > b2.log 2>&1 || { cat b2.log; echo "FAIL: --dev errored"; exit 1; }
46+
grep -q '\-O0' compile_commands.json || { echo "FAIL: --dev did not override project default to -O0"; cat compile_commands.json; exit 1; }
47+
)
48+
49+
echo "OK"

0 commit comments

Comments
 (0)