diff --git a/.agents/docs/2026-06-29-manifest-environment-and-platform-design.md b/.agents/docs/2026-06-29-manifest-environment-and-platform-design.md index 5ecfd9f..bf51158 100644 --- a/.agents/docs/2026-06-29-manifest-environment-and-platform-design.md +++ b/.agents/docs/2026-06-29-manifest-environment-and-platform-design.md @@ -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 diff --git a/.github/workflows/cross-build-test.yml b/.github/workflows/cross-build-test.yml index ccc4c32..017f3ca 100644 --- a/.github/workflows/cross-build-test.yml +++ b/.github/workflows/cross-build-test.yml @@ -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@" 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}" \ @@ -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 diff --git a/docs/05-mcpp-toml.md b/docs/05-mcpp-toml.md index 396f740..9ec0257 100644 --- a/docs/05-mcpp-toml.md +++ b/docs/05-mcpp-toml.md @@ -411,8 +411,16 @@ cxxflags = ["-fno-plt"] ldflags = [] ``` -- Selection: `mcpp build --profile ` (and `mcpp test --profile `, 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 ` (builds the + code-under-test plus the test binaries under that profile). +- **Per-project default** — `[build].default-profile = ""` (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.]` can override a built-in definition wholesale. diff --git a/docs/zh/05-mcpp-toml.md b/docs/zh/05-mcpp-toml.md index bf759e3..c1b03cc 100644 --- a/docs/zh/05-mcpp-toml.md +++ b/docs/zh/05-mcpp-toml.md @@ -389,8 +389,14 @@ cxxflags = ["-fno-plt"] ldflags = [] ``` -- 选择:`mcpp build --profile `(以及 `mcpp test --profile `,会让被测代码与测试二进制 - 都在该 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 ` 同理 + (被测代码与测试二进制都在该 profile 下编译)。 +- **项目级默认** —— `[build].default-profile = ""`(别名 `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.<内置名>]` 可整体覆盖内置定义。 diff --git a/mcpp.toml b/mcpp.toml index f1a48d0..2eb3143 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -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 ` in src/libs/json.cppm. include_dirs = ["src/libs/json"] diff --git a/src/build/prepare.cppm b/src/build/prepare.cppm index afa216d..94a4f21 100644 --- a/src/build/prepare.cppm +++ b/src/build/prepare.cppm @@ -554,10 +554,19 @@ prepare_build(bool print_fingerprint, // 1. project mcpp.toml [toolchain]. 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.] from the manifest → buildConfig. + // Resolve the build profile, overlaid by any [profile.] 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; } diff --git a/src/cli.cppm b/src/cli.cppm index 6f4c0f7..f9e5d9e 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -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") diff --git a/src/cli/cmd_build.cppm b/src/cli/cmd_build.cppm index e5ff22d..0291e56 100644 --- a/src/cli/cmd_build.cppm +++ b/src/cli/cmd_build.cppm @@ -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"); diff --git a/src/manifest.cppm b/src/manifest.cppm index 032a580..2f8d501 100644 --- a/src/manifest.cppm +++ b/src/manifest.cppm @@ -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. @@ -1118,6 +1126,8 @@ std::expected 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) { diff --git a/src/toolchain/fingerprint.cppm b/src/toolchain/fingerprint.cppm index 94b77e6..ee8c1f7 100644 --- a/src/toolchain/fingerprint.cppm +++ b/src/toolchain/fingerprint.cppm @@ -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; diff --git a/tests/e2e/68_profile_passthrough.sh b/tests/e2e/68_profile_passthrough.sh index 6654b1b..23f8758 100755 --- a/tests/e2e/68_profile_passthrough.sh +++ b/tests/e2e/68_profile_passthrough.sh @@ -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 diff --git a/tests/e2e/87_build_default_profile.sh b/tests/e2e/87_build_default_profile.sh new file mode 100755 index 0000000..d12441c --- /dev/null +++ b/tests/e2e/87_build_default_profile.sh @@ -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 = ""` (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"