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

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,39 @@
> Each `## [<version>]` section is that release's notes. Entries are written in English
> from 2026.9.28.3 on; earlier entries remain as written.

## [2026.10.31.2] - 2026-10-31

### Added

- **`mcpp test <name> --print-launch`** (feature `test-launch`, docs/50 §9):
the one test the pattern names, built as `mcpp test` builds it and described
as `mcpp test` would start it -- its runner, the environment the run adds, the
arguments after `--` -- in the `mcpp.launch` envelope `mcpp run
--print-launch` prints, with `test` naming it. A debugger starts what it
describes (an IDE debugging a test or one of its cases, design 2026-10-30
part 2 §7.4). A pattern several tests contain resolves to the test named
exactly; one naming several, `--list`, `--no-run`, `--archive`,
`--message-format json`, several legs and several members are refused.

### Fixed

- **The build's contract states the target side** (`${mcpp.contract}`,
docs/30). Its words held the configuration's own flags only: a foreign build
reading it for an ok image (`<arch>-openkal`) had neither the C library's and
the C++ runtime's headers nor `__OPENKAL__`, and could not compile
`<stdio.h>`. They now end with what every unit receives from beneath it: the
platform's layers' include directories and flags, the engine's predefines
(`__MCPP_TARGET_<OS>__`, `__OPENKAL__`) and the realised C environment. e2e 994
compiles a C and a C++ unit for the target with the contract alone.
- **A package's Windows launcher builds whichever openkal-platform release the
project selects** (SPEC-014 §4.3). The launcher (`bin/<name>.exe`) is mcpp's
program, and its `mcpplibs.os` pins one openkal release; it was built over the
project's platform, so a platform whose version group names a newer openkal
(openkal-platform 0.19.0) gave its graph two openkal releases and `mcpp pack`
stopped. It is now built over the platform mcpp's openkal rows pin, the one
its `mcpplibs.os` was released against. e2e 995 packs a project that selects
openkal-platform 0.19.0 with its Windows launcher.

## [2026.10.31.1] - 2026-10-31

### Added
Expand Down
8 changes: 8 additions & 0 deletions docs/08-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,14 @@ One NDJSON record per test, for a CI job or an editor. The schema and its
version are [50 — Machine-Readable Output](50-machine-output.md); what belongs
here is only that the flag exists and that the human format is the default.

```bash
mcpp test unit/math --print-launch -- --gtest_filter=Math.Adds
```

Builds the one test the pattern names and prints how `mcpp test` would start it
(the `mcpp.launch` envelope, mcpp 2026.10.31.2+), for a debugger to start: the
runner, the environment the run adds and the arguments after `--` are in it.

## Current limitations

- A test is one `.cpp` producing one program. mcpp does not discover cases
Expand Down
4 changes: 3 additions & 1 deletion docs/10-pack-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,9 @@ own arguments, looking for the runner first in `libexec/<runner>/<os>-<arch>/`
and then on `PATH`. The Windows launcher is the engine's program built for
`x86_64-windows-gnu`: it finds the package from its own path, picks the image
of the machine's architecture, and starts the runner the same way; no `.cmd` is
written. A machine of an architecture the package does not hold gets exit
written. Where the build's platform has a hosted arrangement for that row, the
launcher is built over the platform mcpp's openkal rows pin rather than the
project's, since the `mcpplibs.os` release it uses pins that platform's openkal. A machine of an architecture the package does not hold gets exit
status 126 and a sentence naming both. Files `[runtime] deploy` places lie
beside each leg's image.

Expand Down
13 changes: 13 additions & 0 deletions docs/50-machine-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ envelope says when either has changed since.
|---|---|---|---|
| `target-sets` | 2026.10.31.1 (meaning from 2026.10.30.1) | `--target openkal` and the other sets expand to their legs (SPEC-015 §1) | an IDE's target picker |
| `pkg-format-list` | 2026.10.31.1 (meaning from 2026.10.30.1) | `--pkg-format tar,okb` writes each container of one tree (SPEC-014 §5.4) | an IDE's pack form |
| `test-launch` | 2026.10.31.2 | `mcpp test <name> --print-launch` prints the `mcpp.launch` of one test (below) | an IDE debugging a test |

### How a tool runs mcpp for a project

Expand Down Expand Up @@ -1001,6 +1002,18 @@ starting it. `data` is `program`, `argv`, `cwd`, `env` and `via`, as
`launch` above. `--print-launch` takes no `--format`: on `mcpp run`, `--pkg-format`
names a distributable.

```
mcpp test <name> --print-launch [-- <arguments>]
```

The same for one test (feature `test-launch`, 2026.10.31.2): the test `<name>` names is
built as `mcpp test` builds it and described as `mcpp test` would start it --
its runner, the environment the run adds, and the arguments after `--` -- with
`test` in `data` naming it. A name that several tests contain is resolved to
the one named exactly; a pattern that names several is refused, as are
`--list`, `--no-run`, `--archive`, `--message-format json`, several legs and
several members. A debugger starts what the envelope describes.

## 10. Denying effects — `--deny` *(mcpp 2026.10.26.1+)*

```
Expand Down
7 changes: 7 additions & 0 deletions docs/zh/08-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ mcpp test --message-format json
[50 —— 机器可读输出](50-machine-output.md);本章只交代这个旗标存在,以及人类可读
格式是默认值。

```bash
mcpp test unit/math --print-launch -- --gtest_filter=Math.Adds
```

构建该模式指定的一个测试,并打印 `mcpp test` 启动它的方式(`mcpp.launch` 信封,mcpp 2026.10.31.2+),
供调试器启动:其中包括运行器、运行所加的环境与 `--` 之后的参数。

## 当前边界

- 一个测试是一个 `.cpp` 产出一个程序。mcpp 不发现文件内部的用例,因此框架的逐用例
Expand Down
3 changes: 2 additions & 1 deletion docs/zh/10-pack-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,8 @@ target/dist/hello-0.1.0-openkal/
包的根,按 `uname -m`(`x86_64`、`amd64` → `x86_64`;`aarch64`、`arm64` → `aarch64`)选择架构目录,Git Bash、MSYS 与
Cygwin 的 shell 交给 `bin/<name>.exe`,以该目录中的映像与自身的参数启动运行器,先在 `libexec/<runner>/<os>-<arch>/`
中、再在 `PATH` 上查找运行器。Windows 启动器是为 `x86_64-windows-gnu` 构建的引擎的程序:从自身路径找到包,选本机
架构的映像,以同样的方式启动运行器;不生成 `.cmd`。包中没有本机架构时以状态 126 退出,写出两者。
架构的映像,以同样的方式启动运行器;不生成 `.cmd`。构建的平台为该行提供宿主安排时,启动器在 mcpp 的 openkal 行固定的
平台上构建,而不是项目选择的平台上,因为它使用的 `mcpplibs.os` 版本固定了该平台的 openkal。包中没有本机架构时以状态 126 退出,写出两者。
`[runtime] deploy` 放置的文件位于每条腿的映像旁边。

`--mode self-contained` 时运行器随包携带:对 `--systems` 的每个系统(`linux`、`macos`、`windows` 取腿的全部架构;
Expand Down
10 changes: 10 additions & 0 deletions docs/zh/50-machine-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ effects 采用具名的方式而不是一个 `destructive: true|false`,因为
|---|---|---|---|
| `target-sets` | 2026.10.31.1(语义自 2026.10.30.1) | `--target openkal` 等集合展开为其腿(SPEC-015 §1) | IDE 的 target 选择 |
| `pkg-format-list` | 2026.10.31.1(语义自 2026.10.30.1) | `--pkg-format tar,okb` 写出一棵树的各个容器(SPEC-014 §5.4) | IDE 的打包界面 |
| `test-launch` | 2026.10.31.2 | `mcpp test <name> --print-launch` 打印一个测试的 `mcpp.launch`(见下) | 调试测试的 IDE |

### 工具为项目运行 mcpp 的方式

Expand Down Expand Up @@ -889,6 +890,15 @@ mcpp run [target] --print-launch
`argv`、`cwd`、`env` 与 `via`,同上文的 `launch`。`--print-launch` 不接受
`--format`:在 `mcpp run` 上,`--pkg-format` 表示可分发物的格式。

```
mcpp test <name> --print-launch [-- <参数>]
```

对一个测试作同样的描述(特性 `test-launch`,2026.10.31.2):`<name>` 指定的测试按 `mcpp test`
的方式构建,并按 `mcpp test` 启动它的方式描述——它的运行器、运行所加的环境、`--` 之后的参数——`data`
另以 `test` 给出测试名。多个测试名包含该模式时取名字完全相同的那个;仍指向多个测试时拒绝,`--list`、
`--no-run`、`--archive`、`--message-format json`、多条腿与多个成员同样拒绝。调试器启动信封描述的程序。

## 10. 拒绝副作用 —— `--deny` *(mcpp 2026.10.26.1+)*

```
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mcpp"
version = "2026.10.31.1"
version = "2026.10.31.2"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
2 changes: 1 addition & 1 deletion modules/versioning/src/version.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ import std;

export namespace mcpp {

inline constexpr std::string_view MCPP_VERSION = "2026.10.31.1";
inline constexpr std::string_view MCPP_VERSION = "2026.10.31.2";

} // namespace mcpp
23 changes: 23 additions & 0 deletions src/build/compile_commands.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -871,13 +871,36 @@ std::expected<void, std::string> write_build_contract(const BuildPlan& plan, con
t["args"] = std::move(args);
return t;
};
// The target side every unit receives from beneath it (the platform's
// layers' headers and flags, the engine's predefines, the realised C
// environment), after the configuration's own words, as on a unit's line.
auto target_side = [&](nlohmann::json& t, const std::vector<std::string>& words, bool assembly) {
auto& args = t["args"];
for (auto const& d : plan.contractTargetSide.includeDirs) {
args.push_back("-isystem");
args.push_back(d.generic_string());
}
for (auto const& d : plan.contractTargetSide.includeDirsAfter) {
args.push_back("-idirafter");
args.push_back(d.generic_string());
}
for (auto const& w : words) {
// An assembler unit takes the C words' -D, -U and -I only, as a unit's line does.
if (assembly && !(w.starts_with("-D") || w.starts_with("-U") || w.starts_with("-I"))) continue;
args.push_back(w);
}
};
nlohmann::json doc;
doc["schema"] = 1;
doc["target"] = plan.toolchain.targetTriple;
doc["dialect"] = std::string(mcpp::toolchain::dialect_for(plan.toolchain).id);
doc["compile"]["c"] = tool(flags.ccBinary, flags.cc, true);
target_side(doc["compile"]["c"], plan.contractTargetSide.cflags, false);
doc["compile"]["cxx"] = tool(flags.cxxBinary, flags.cxx, true);
target_side(doc["compile"]["cxx"], plan.contractTargetSide.cxxflags, false);
doc["compile"]["asm"] = tool(flags.ccBinary, flags.as, true);
target_side(doc["compile"]["asm"], plan.contractTargetSide.cflags, true);
for (auto const& w : plan.contractTargetSide.asmflags) doc["compile"]["asm"]["args"].push_back(w);
auto link = tool(flags.ldDriver.empty() ? flags.cxxBinary : flags.ldDriver, flags.ld, false);
link["archiver"] = flags.arBinary.generic_string();
doc["link"] = std::move(link);
Expand Down
35 changes: 34 additions & 1 deletion src/build/execute.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -4265,6 +4265,7 @@ static int test_run_member(TestBuild& tb, const TestOptions& testOpts,
std::vector<std::string> argv;
std::vector<std::pair<std::string, std::string>> env;
long long buildMs = 0; // this test's own edges in .ninja_log
std::filesystem::path program; // the test's own artifact
};
std::vector<Runnable> runnable;

Expand Down Expand Up @@ -4551,7 +4552,39 @@ static int test_run_member(TestBuild& tb, const TestOptions& testOpts,
}

runnable.push_back({lu.targetName, std::move(argv), std::move(childEnv),
buildMsOfTest});
buildMsOfTest, exe});
}
// `mcpp test <name> --print-launch` (design 2026-10-30 part 2 §7.4): the one test the pattern
// names, built, described as `mcpp run --print-launch` describes a program -- the `mcpp.launch`
// envelope, composed here where the run composes it -- and not started. A debugger starts it.
if (print_launch_flag()) {
const Runnable* chosen = nullptr;
for (auto const& r : runnable)
if (r.name == testOpts.filter) chosen = &r;
if (!chosen && runnable.size() == 1) chosen = &runnable.front();
if (!chosen) {
mcpp::ui::error(runnable.empty()
? std::format("--print-launch: no test that built is named '{}'", testOpts.filter)
: std::format("--print-launch describes one test, and '{}' names {}: name one", testOpts.filter, runnable.size()));
return 2;
}
if (!invocationNotRunReason.empty()) {
mcpp::ui::error(invocationNotRunReason);
return 2;
}
ctx->buildLock.reset();
mcpp::build::result::Launch launch{chosen->argv, chosen->env,
std::filesystem::current_path().generic_string(),
!tb.runUnderPrefix.empty() ? "run_under"
: !runnerTmpl.empty() ? "runner" : ""};
mcpp::wire::Envelope env{.kind = "mcpp.launch",
.effects = {mcpp::wire::Effect::ReadProject,
mcpp::wire::Effect::WriteProject}};
env.data = mcpp::build::result::to_json(launch);
env.data["program"] = chosen->program.generic_string();
env.data["test"] = chosen->name;
mcpp::wire::emit(env);
return 0;
}
// Pass 2: run them. Concurrently unless there is exactly one — see
// `runJobs` for why the single-test case is deliberately different.
Expand Down
5 changes: 5 additions & 0 deletions src/build/plan.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,11 @@ struct BuildPlan {
// `openkal-llvm` family name, which had to be invented to carry a fact
// about the dependency graph on an object that describes a compiler.
mcpp::targetside::TargetSide targetSide;
// What every unit of this build receives from beneath it -- the target
// side's include directories and flags, the engine's predefines, the
// realised C environment -- as the build's contract states it for a
// foreign build compiling for the target (`write_build_contract`).
mcpp::modgraph::UsageRequirements contractTargetSide;
// THE C++ ISLAND OF THIS BUILD, when it has one (design 2026-10-26 §6.3):
// a C++ runtime other than the build's own reaches the link -- declared by
// packages' private `mcpp:c++-abi` requirements, named on the link line,
Expand Down
1 change: 1 addition & 0 deletions src/build/prepare/plan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ static std::expected<void, std::string> step13_make_plan(PrepareState& state, Bu
// that function does depends on it: the flag assembly that does reads the
// plan, and every reader of `compute_flags` runs after this line.
ctx.plan.targetSide = state.resolvedTargetSide;
ctx.plan.contractTargetSide = state.contractTargetSide;

// C5 / D5a-b (design 2026-09-26 §3.5): the standard-library units this
// configuration's build compiles, when it imports `std`. Recovered here,
Expand Down
3 changes: 3 additions & 0 deletions src/build/prepare/state.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,9 @@ struct PrepareState {
std::optional<std::size_t> cxxLayerProviderIndex;
bool targetSideResolved = false;
mcpp::modgraph::UsageRequirements targetSideUsage;
// The same, with the engine's predefines and the realised C environment:
// what the build's contract states (BuildPlan::contractTargetSide).
mcpp::modgraph::UsageRequirements contractTargetSide;

// ── P9/P11: the module scan, its validation, and the fingerprint they
// feed; read again by P13 (the plan, resolution.json) ───────────────────
Expand Down
20 changes: 20 additions & 0 deletions src/build/prepare/target_side.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,26 @@ step9_target_side_include_broadcast(PrepareState& state, TargetSideGather& gathe
}
}

// THE CONTRACT'S TARGET SIDE: everything above that every unit receives,
// in one set, for `${mcpp.contract}` (a foreign build compiling for the
// target reads it there; docs/30). The C environment is the default
// one: `c-environment = "platform"` is a package's own exception.
state.contractTargetSide = state.targetSideUsage;
if (state.tc) {
std::string contractOs;
if (auto ttOs = mcpp::toolchain::triple::parse(state.tc->targetTriple))
contractOs = ttOs->os;
const auto defines = mcpp::toolchain::predefines::define_tokens(
contractOs, state.tc->kernelAbiIsOpenkal);
for (auto* words : {&state.contractTargetSide.cflags, &state.contractTargetSide.cxxflags}) {
state.appendUniqueFlags(*words, defines);
state.appendUniqueFlags(*words, state.tc->cEnvTokens);
state.appendUniqueFlags(*words, state.tc->cEnvBuiltinsTokens);
}
state.appendUniqueFlags(state.contractTargetSide.asmflags, state.tc->cEnvTokens);
state.appendUniqueFlags(state.contractTargetSide.asmflags, state.tc->cEnvBuiltinsTokens);
}

if (state.tc && (state.tc->kernelAbiIsOpenkal || !state.tc->cEnvTokens.empty()
|| !state.tc->cEnvBuiltinsTokens.empty())) {
for (auto& p : state.packages) {
Expand Down
6 changes: 6 additions & 0 deletions src/cli.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,8 @@ int run(int argc, char** argv) {
.help("List (filtered) tests without building or running them"))
.option(cl::Option("no-runner")
.help("Run test binaries directly, ignoring any [target.<triple>].runner (a host that runs them natively)"))
.option(cl::Option("print-launch")
.help("Build the one test PATTERN names, then print how it would be started (the mcpp.launch envelope, as `mcpp run --print-launch` prints) instead of running it; arguments after `--` are in its argv"))
.option(cl::Option("no-run")
.help("Build the tests for --target and stop; a build that succeeds is the result (for a target this host cannot execute)"))
.option(cl::Option("archive").takes_value().value_name("FILE")
Expand Down Expand Up @@ -1799,6 +1801,10 @@ int run(int argc, char** argv) {
{"run --print-launch", {Effect::InitMcppHome, Effect::ReadProject,
Effect::WriteProject, Effect::Network,
Effect::WriteGlobalCache, Effect::ExecBuildScript}},
// ...and of a test, the one it names.
{"test --print-launch", {Effect::InitMcppHome, Effect::ReadProject,
Effect::WriteProject, Effect::Network,
Effect::WriteGlobalCache, Effect::ExecBuildScript}},
// The index is refreshed when older than the search TTL.
{"search", {Effect::InitMcppHome, Effect::Network}},
{"index list", {Effect::InitMcppHome, Effect::ReadProject}},
Expand Down
Loading
Loading