Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,43 @@ expressible, all backed by existing xlings behavior.

---

## L0.5 — Default build profile: follow the mainstream (dev), `--release` opt-in

(Issue #179.) mcpp's old default was `release`; the prevailing convention across
modern build tools is the **opposite** — a bare build is **debug/dev**, release is
opt-in:

| Tool | bare-command default | release via |
|---|---|---|
| Cargo | **dev/debug** (-O0 + debuginfo) | `cargo build --release` |
| Meson | **debug** | `--buildtype=release` |
| CMake | empty `CMAKE_BUILD_TYPE` (≈debug) | `-DCMAKE_BUILD_TYPE=Release` |
| Zig | **Debug** | `-Doptimize=ReleaseFast/Safe/Small` |
| Bazel | **fastbuild** (no opt) | `-c opt` |
| MSBuild/VS | **Debug** | Release configuration |
| **xmake** | **release** | `xmake f -m debug` |

Debug-default dominates (Cargo/Meson/CMake/Zig/Bazel/MSBuild); only the **xmake**
lineage defaults to release — exactly where mcpp's old default came from (mcpp builds
on xlings/xmake). But mcpp's *surface* is Cargo-flavored, so users expect Cargo
semantics. **Decision: flip the global default to `dev` (-O0 -g); release is opt-in
via `--release` / `--profile release`.**

- **Precedence**: `--profile NAME` / `--release` / `--dev` flag > `[build].default-profile`
(project default) > global `dev`. (`prepare.cppm`; `--dev`/`--release` are shorthands
in `cli.cppm`/`cmd_build.cppm`.)
- **`[build].default-profile`** (alias: `profile`) — a project's own default; its role
*inverts* under the flip: it now means **"opt into release"** for projects that ship/
run optimized by default. **mcpp's own `mcpp.toml` sets `default-profile = "release"`**
so the self-host CI build and `release.yml` stay `-O2` with **zero workflow changes** —
the knob *is* the migration mechanism (no `--release` threaded through release pipelines,
and mcpp's own CI doesn't slow to `-O0`).
- **Distribution footgun**: a project that defaults to dev passes `--release` to produce
a distributable (a pack-time release guard is a possible follow-up).
- Tests: `tests/e2e/87_build_default_profile.sh`, `68_profile_passthrough.sh` (updated).

---

## L0 — Dependency categories: keep three axes, wire `build`

Keep Cargo/Conan's **normal / dev / build** trichotomy (mcpp already declares all
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/cross-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ jobs:
- name: Bootstrap mcpp via xlings
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '0.4.30'
# 0.4.61 (current). The earlier 0.4.61 "download 404 for mcpp@<pin>" was
# NOT a version bug — the xlings-res/mcpp GitHub release assets were
# uploaded in a broken state (records present, blobs missing → 404 on
# GET); re-uploaded clean. The stale-INDEX half is handled by the
# marker-clear below.
XLINGS_VERSION: '0.4.61'
run: |
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
curl -fsSL -o "/tmp/${tarball}" \
Expand All @@ -96,8 +101,10 @@ jobs:
"/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install
export PATH="$HOME/.xlings/subos/default/bin:$PATH"
xlings --version
# Refresh the index so a cached ~/.xlings still sees newly published
# cross toolchains (xim:aarch64-linux-musl-gcc, static ninja, ...).
# Force a real index re-sync even on a warm cache: drop the TTL refresh
# markers so `xlings update` actually pulls the latest index (sees the
# current bootstrap pin) while the toolchain payloads stay cached.
find "$HOME/.xlings" -name '.xlings-index-cache.json' -delete 2>/dev/null || true
xlings config --mirror GLOBAL 2>/dev/null || true
xlings update -y 2>/dev/null || xlings update 2>/dev/null || true
xlings install mcpp -y
Expand Down
12 changes: 10 additions & 2 deletions docs/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,16 @@ cxxflags = ["-fno-plt"]
ldflags = []
```

- Selection: `mcpp build --profile <name>` (and `mcpp test --profile <name>`, which builds the
code-under-test plus the test binaries under that profile), defaulting to `release`.
- Selection & default: a bare `mcpp build` uses the **`dev`** profile (`-O0 -g`) — the
mainstream convention (cf. Cargo/Meson/CMake/Zig/Bazel). **Release is opt-in:**
`mcpp build --release` (shorthand) or `--profile release`. `--dev` is the explicit
shorthand for dev. Same applies to `mcpp test --profile <name>` (builds the
code-under-test plus the test binaries under that profile).
- **Per-project default** — `[build].default-profile = "<name>"` (alias: `profile`) sets
the project's own default when no flag is passed. The typical use is a tool/library
that should build optimized by default: `[build] default-profile = "release"`. Precedence:
`--profile`/`--release`/`--dev` flag **>** `[build].default-profile` **>** global `dev`.
(A project that defaults to dev should pass `--release` when producing a distributable.)
- Built-in profiles: `release` (-O2) / `dev`, `debug` (-O0 -g) / `dist` (-O3 + strip;
**LTO is not enabled by default**). `[profile.<built-in name>]` can override a
built-in definition wholesale.
Expand Down
10 changes: 8 additions & 2 deletions docs/zh/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,14 @@ cxxflags = ["-fno-plt"]
ldflags = []
```

- 选择:`mcpp build --profile <name>`(以及 `mcpp test --profile <name>`,会让被测代码与测试二进制
都在该 profile 下编译),默认 `release`。
- 选择与默认:裸 `mcpp build` 走 **`dev`** 档(`-O0 -g`)——主流惯例(参照
Cargo/Meson/CMake/Zig/Bazel)。**release 为 opt-in:** `mcpp build --release`(短写)或
`--profile release`;`--dev` 是 dev 的显式短写。`mcpp test --profile <name>` 同理
(被测代码与测试二进制都在该 profile 下编译)。
- **项目级默认** —— `[build].default-profile = "<name>"`(别名 `profile`)设置该项目在不带
flag 时的默认。典型用途是"以发布优化为常态"的工具/库:`[build] default-profile = "release"`。
优先级:`--profile`/`--release`/`--dev` flag **>** `[build].default-profile` **>** 全局 `dev`。
(默认 dev 的项目在产出可分发物时应显式 `--release`。)
- 内置档案:`release`(-O2)/ `dev`、`debug`(-O0 -g)/ `dist`(-O3 + strip;
**不默认开 lto**)。`[profile.<内置名>]` 可整体覆盖内置定义。

Expand Down
7 changes: 6 additions & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
[package]
name = "mcpp"
version = "0.0.75"
version = "0.0.76"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
repo = "https://github.com/mcpp-community/mcpp"

[build]
# The global default profile is now "dev" (-O0 -g, mainstream convention);
# mcpp itself is a shipped tool, so opt its plain `mcpp build` (and release.yml's
# self-host build) back into the optimized profile. Without this the released
# binary would be -O0. A `--profile`/`--dev`/`--release` flag still overrides.
default-profile = "release"
# nlohmann/json.hpp lives in src/libs/json/; expose it to the global
# module fragment `#include <json.hpp>` in src/libs/json.cppm.
include_dirs = ["src/libs/json"]
Expand Down
15 changes: 12 additions & 3 deletions src/build/prepare.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -554,10 +554,19 @@ prepare_build(bool print_fingerprint,
// 1. project mcpp.toml [toolchain].<platform> or .default
// 2. global ~/.mcpp/config.toml [toolchain].default
// 3. hard error (no system fallback)
// Resolve the build profile: --profile (default "release") → built-in
// defaults, overlaid by any [profile.<name>] from the manifest → buildConfig.
// Resolve the build profile, overlaid by any [profile.<name>] from the
// manifest → buildConfig.
{
std::string pname = overrides.profile.empty() ? "release" : overrides.profile;
// Precedence: --profile / --release / --dev flag (overrides.profile) >
// [build].default-profile (project default) > "dev" (global default).
// The global default is "dev" (-O0 -g) to follow the dominant convention
// (Cargo/Meson/CMake/Zig/Bazel/MSBuild all default to debug); release is
// opt-in via --release / --profile release. A project that wants its
// plain `mcpp build` optimized sets [build].default-profile = "release"
// (mcpp's own mcpp.toml does this, so the released binary stays -O2).
std::string pname = !overrides.profile.empty() ? overrides.profile
: !m->buildConfig.defaultProfile.empty() ? m->buildConfig.defaultProfile
: "dev";
mcpp::manifest::Profile pr;
if (pname == "dev" || pname == "debug") { pr.optLevel = "0"; pr.debug = true; }
else if (pname == "dist") { pr.optLevel = "3"; pr.strip = true; }
Expand Down
2 changes: 2 additions & 0 deletions src/cli.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ int run(int argc, char** argv) {
.help("Build only the named workspace member"))
.option(cl::Option("profile").takes_value().value_name("NAME")
.help("Build profile: release (default) | dev | dist | <[profile.*] name>"))
.option(cl::Option("release").help("Shorthand for --profile release"))
.option(cl::Option("dev").help("Shorthand for --profile dev (-O0 -g)"))
.option(cl::Option("features").takes_value().value_name("LIST")
.help("Activate root-package features (comma-separated)"))
.option(cl::Option("cap").takes_value().value_name("LIST")
Expand Down
5 changes: 5 additions & 0 deletions src/cli/cmd_build.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ export int cmd_build(const mcpplibs::cmdline::ParsedArgs& parsed) {
mcpp::build::BuildOverrides ov;
if (auto t = parsed.value("target")) ov.target_triple = *t;
if (auto p = parsed.value("package")) ov.package_filter = *p;
// Profile selection precedence: --profile NAME > --release / --dev > the
// project default ([build].default-profile) > "release", resolved in
// prepare_build. --release/--dev are shorthands only.
if (auto pr = parsed.value("profile")) ov.profile = *pr;
else if (parsed.is_flag_set("release")) ov.profile = "release";
else if (parsed.is_flag_set("dev")) ov.profile = "dev";
if (auto fs = parsed.value("features")) ov.features = *fs;
if (auto cp = parsed.value("cap")) ov.capabilities = *cp;
ov.strict = parsed.is_flag_set("strict");
Expand Down
10 changes: 10 additions & 0 deletions src/manifest.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ struct BuildConfig {
bool debug = false; // -g
bool lto = false; // -flto
bool strip = false; // link -s
// `[build].default-profile` (alias: `profile`) — the project's DEFAULT
// profile when no --profile/--dev/--release is passed. The global convention
// default stays "release"; this lets a project opt its plain `mcpp build`
// into e.g. "dev" without typing --profile. Precedence: --profile/--dev/
// --release flag > [build].default-profile > "release". NOTE (distribution
// footgun): a project that defaults to dev should pass `--profile release`
// when producing a distributable (a pack-time release guard is a follow-up).
std::string defaultProfile;
};

// `[runtime]` — requirements needed when launching built binaries.
Expand Down Expand Up @@ -1118,6 +1126,8 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
if (auto v = doc->get_string_array("build.cxxflags")) m.buildConfig.cxxflags = *v;
if (auto v = doc->get_string_array("build.ldflags")) m.buildConfig.ldflags = *v;
if (auto v = doc->get_string("build.c_standard")) m.buildConfig.cStandard = *v;
if (auto v = doc->get_string("build.default-profile")) m.buildConfig.defaultProfile = *v;
else if (auto v = doc->get_string("build.profile")) m.buildConfig.defaultProfile = *v; // accepted alias
if (auto v = doc->get_string("build.macos_deployment_target"))
m.buildConfig.macosDeploymentTarget = *v;
for (auto const& flag : m.buildConfig.cxxflags) {
Expand Down
2 changes: 1 addition & 1 deletion src/toolchain/fingerprint.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import mcpp.toolchain.detect;

export namespace mcpp::toolchain {

inline constexpr std::string_view MCPP_VERSION = "0.0.75";
inline constexpr std::string_view MCPP_VERSION = "0.0.76";

struct FingerprintInputs {
Toolchain toolchain;
Expand Down
12 changes: 6 additions & 6 deletions tests/e2e/68_profile_passthrough.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ strip = true
cxxflags = ["-fno-plt"]
EOF

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

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

# dist from [profile.dist]: -O3 -flto + passthrough cxxflag, stripped binary.
rm -rf target
Expand Down
49 changes: 49 additions & 0 deletions tests/e2e/87_build_default_profile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# 87_build_default_profile.sh — profile selection follows the mainstream convention:
# the GLOBAL default is "dev" (-O0 -g, like Cargo/Meson/CMake/Zig/Bazel/MSBuild);
# "release" is opt-in via --release / --profile release. A project can set its own
# default with `[build] default-profile = "<name>"` (e.g. opt back into release).
# Precedence: --profile/--release/--dev flag > [build].default-profile > "dev".
# See .agents/docs/2026-06-29-manifest-environment-and-platform-design.md.
set -e

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

# --- (1) A plain project: no [build].default-profile → GLOBAL default = dev. ---
mkdir -p plain/src
cat > plain/mcpp.toml <<'EOF'
[package]
name = "plain"
version = "0.1.0"
EOF
echo 'int main() { return 0; }' > plain/src/main.cpp
( cd plain
"$MCPP" build > b.log 2>&1 || { cat b.log; echo "FAIL: build errored"; exit 1; }
grep -q '\-O0' compile_commands.json || { echo "FAIL: global default is not dev (-O0)"; cat compile_commands.json; exit 1; }
grep -q '\-g\b' compile_commands.json || { echo "FAIL: global default dev lacks -g"; exit 1; }
# --release opts into the optimized profile.
"$MCPP" build --release > b2.log 2>&1 || { cat b2.log; echo "FAIL: --release errored"; exit 1; }
grep -q '\-O2' compile_commands.json || { echo "FAIL: --release did not yield -O2"; cat compile_commands.json; exit 1; }
)

# --- (2) A project that opts into release via [build].default-profile. ---
mkdir -p opt/src
cat > opt/mcpp.toml <<'EOF'
[package]
name = "opt"
version = "0.1.0"
[build]
default-profile = "release"
EOF
echo 'int main() { return 0; }' > opt/src/main.cpp
( cd opt
"$MCPP" build > b.log 2>&1 || { cat b.log; echo "FAIL: build errored"; exit 1; }
grep -q '\-O2' compile_commands.json || { echo "FAIL: [build].default-profile=release did not yield -O2"; cat compile_commands.json; exit 1; }
# --dev overrides the project's release default back to -O0.
"$MCPP" build --dev > b2.log 2>&1 || { cat b2.log; echo "FAIL: --dev errored"; exit 1; }
grep -q '\-O0' compile_commands.json || { echo "FAIL: --dev did not override project default to -O0"; cat compile_commands.json; exit 1; }
)

echo "OK"
Loading