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 @@ -285,8 +285,13 @@ missing declared outputs as failure.
`insert()` keeps an existing unconditional entry (no silent override). Test:
`tests/e2e/86_target_cfg_dependencies.sh`. **Still TODO:** `lazy = true` (fetch only
when a gated path requests it) + content-hash identity.
- **Phase 2 — L-1 environment.** Surface `[xlings]` (+ `[xlings.workspace]`/`.deps`/
`.subos`/`.envs`, 1:1 with `.xlings.json`) → extend the project
- **✅ Phase 2 — L-1 environment (mcpp 0.0.77).** `[xlings]` (+ `[xlings.workspace]`/
`deps`/`subos`/`[xlings.envs]`) parsed into `Manifest::xlings` (`manifest.cppm`) and
materialized **1:1** into `<proj>/.mcpp/.xlings.json` via an extended
`seed_xlings_json` (new `xlings::ProjectEnv`) called from `ensure_project_index_dir`
(now triggered by custom `[indices]` **or** a non-empty `[xlings]`). Test:
`tests/e2e/88_xlings_environment.sh`. The keys map verbatim onto what xlings already
consumes (no translation layer). *Superseded the rest of this bullet:* extend the project
`.xlings.json` writer (`config.cppm:699-705`) to emit `deps`/`workspace`/`envs`/`subos`;
fold `[toolchain]` into `workspace`; wire `[build-dependencies]`.
- **Phase 3 — mcpp-index workspace** (companion doc) — first real consumer of Phase 1/1b.
Expand Down
23 changes: 23 additions & 0 deletions docs/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,29 @@ The vocabulary is fixed by mcpp (which owns the target/triple system):
`linux | macos | windows`; unknown values produce a warning, and an error under
`--strict`.

### 2.12 `[xlings]` — Build Environment

```toml
[xlings]
deps = ["make@4.4", "cmake@3.28", "python@3.13"] # host build-tools to provision
subos = "dev" # a named per-project sandbox

[xlings.workspace] # pin tool versions (general form of [toolchain])
clang = "20.1.7"

[xlings.envs] # env vars applied to the tool environment
OPENBLAS_NUM_THREADS = "1"
```

Declares the project's **build environment**, provisioned through xlings (which mcpp
is built on). The subsection names mirror xlings' own `.xlings.json` schema **1:1**, so
mcpp materializes them verbatim into `<project>/.mcpp/.xlings.json` (no translation
layer): `deps` (host build-tools), `[xlings.workspace]` (tool→version pins),
`subos` (a named sandbox), `[xlings.envs]` (env vars). Use it to declare host tools a
build needs (`make`/`cmake`/`protoc`/…), pin tool versions per project, or set
build-time env vars — without hand-editing `.xlings.json`. `[toolchain]` (§2.7) remains
the ergonomic shorthand for the compiler; `[xlings.workspace]` is the general form.

## Appendix A. Schema Ownership Principle (admission criteria for new fields)

> **Closed syntax, open vocabulary**: whoever owns the parsing semantics defines the keys; whoever owns the domain knowledge defines the values.
Expand Down
21 changes: 21 additions & 0 deletions docs/zh/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,27 @@ platforms = ["linux", "macos", "windows"]
(它拥有 target/triple 体系):`linux | macos | windows`;未知值 warning,
`--strict` 下报错。

### 2.12 `[xlings]` — 构建环境

```toml
[xlings]
deps = ["make@4.4", "cmake@3.28", "python@3.13"] # 要供给的 host 构建工具
subos = "dev" # 命名的项目级沙箱

[xlings.workspace] # 固定工具版本([toolchain] 的通用形式)
clang = "20.1.7"

[xlings.envs] # 应用到工具环境的环境变量
OPENBLAS_NUM_THREADS = "1"
```

声明项目的**构建环境**,经 xlings(mcpp 的底座)供给。子段名与 xlings 自身的
`.xlings.json` schema **1:1** 对齐,因此 mcpp 把它们**原样**物化进
`<项目>/.mcpp/.xlings.json`(无翻译层):`deps`(host 构建工具)、`[xlings.workspace]`
(工具→版本固定)、`subos`(命名沙箱)、`[xlings.envs]`(环境变量)。用它声明构建所需的
host 工具(`make`/`cmake`/`protoc`…)、按项目固定工具版本、或设构建期环境变量——无需手改
`.xlings.json`。`[toolchain]`(§2.7)仍是编译器的便捷简写;`[xlings.workspace]` 是其通用形式。

## 附录 A. Schema 所有权原则(新字段准入标准)

> **语法封闭,词汇开放**:谁拥有解析语义谁定义键;谁拥有领域知识谁定义值。
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 = "0.0.76"
version = "0.0.77"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
16 changes: 11 additions & 5 deletions src/build/prepare.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -898,13 +898,19 @@ prepare_build(bool print_fingerprint,
}
}

// Set up project-level .mcpp/ directory for custom indices.
// This creates .mcpp/.xlings.json with custom non-builtin index
// entries so xlings can clone them into the project-scoped data dir.
if (!m->indices.empty()) {
// Set up project-level .mcpp/ directory for custom indices and/or the
// [xlings] build environment (L-1). This creates .mcpp/.xlings.json with
// custom non-builtin index entries (so xlings can clone them) plus the
// [xlings] deps/workspace/subos/envs materialized verbatim.
if (!m->indices.empty() || !m->xlings.empty()) {
auto cfg2 = get_cfg();
if (cfg2) {
mcpp::config::ensure_project_index_dir(**cfg2, *root, m->indices);
mcpp::xlings::ProjectEnv penv;
penv.deps = m->xlings.deps;
penv.subos = m->xlings.subos;
for (auto const& [k, v] : m->xlings.workspace) penv.workspace.emplace_back(k, v);
for (auto const& [k, v] : m->xlings.envs) penv.envs.emplace_back(k, v);
mcpp::config::ensure_project_index_dir(**cfg2, *root, m->indices, penv);

// On first build, the project index data root may be empty because
// ensure_project_index_dir only writes .xlings.json but does not
Expand Down
10 changes: 6 additions & 4 deletions src/config.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ void reset_registry(const GlobalConfig& cfg) {
bool ensure_project_index_dir(
const GlobalConfig& cfg,
const std::filesystem::path& projectDir,
const std::map<std::string, mcpp::pm::IndexSpec>& indices);
const std::map<std::string, mcpp::pm::IndexSpec>& indices,
const mcpp::xlings::ProjectEnv& penv = {});

struct ConfigError { std::string message; };

Expand Down Expand Up @@ -661,7 +662,8 @@ void print_env(const GlobalConfig& cfg) {
bool ensure_project_index_dir(
const GlobalConfig& cfg,
const std::filesystem::path& projectDir,
const std::map<std::string, mcpp::pm::IndexSpec>& indices)
const std::map<std::string, mcpp::pm::IndexSpec>& indices,
const mcpp::xlings::ProjectEnv& penv)
{
// Collect custom non-builtin indices that need xlings project-scope data.
// Local path indices are also seeded so xlings can create its own
Expand Down Expand Up @@ -694,15 +696,15 @@ bool ensure_project_index_dir(
}
}

if (customRepos.empty()) return false; // nothing to do
if (customRepos.empty() && penv.empty()) return false; // nothing to do

auto dotMcpp = projectDir / ".mcpp";
std::filesystem::create_directories(dotMcpp, ec);

// Seed .xlings.json with the custom index entries.
mcpp::xlings::Env env;
env.home = dotMcpp;
mcpp::xlings::seed_xlings_json(env, customRepos);
mcpp::xlings::seed_xlings_json(env, customRepos, "auto", penv);

auto exposeLocalIndex = [&](const std::string& name,
const std::filesystem::path& source,
Expand Down
29 changes: 29 additions & 0 deletions src/manifest.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,24 @@ struct RuntimeConfig {
std::map<std::string, std::string> providerOverrides;
};

// `[xlings]` — the project's build ENVIRONMENT (L-1). The subsection names mirror
// xlings' own `.xlings.json` schema 1:1, so mcpp materializes them verbatim into
// `<proj>/.mcpp/.xlings.json` (no translation layer): `deps` (host build-tools
// installed by xlings), `[xlings.workspace]` (tool→version pins, the general form
// of `[toolchain]`), `subos` (a named per-project sandbox), `[xlings.envs]`
// (env vars applied by xvm shims). See
// .agents/docs/2026-06-29-manifest-environment-and-platform-design.md (L-1).
struct XlingsConfig {
std::vector<std::string> deps; // → .xlings.json "deps"
std::map<std::string, std::string> workspace; // → "workspace" (tool → version)
std::string subos; // → "subos" (named project sandbox)
std::map<std::string, std::string> envs; // → "envs" (env var → value)

bool empty() const {
return deps.empty() && workspace.empty() && subos.empty() && envs.empty();
}
};

// `[target.<triple>]` — per-target overrides.
// Picked up when caller passes --target <triple> to build/run/test.
struct TargetEntry {
Expand Down Expand Up @@ -283,6 +301,7 @@ struct Manifest {
Toolchain toolchain; // optional; empty == fallback
BuildConfig buildConfig;
RuntimeConfig runtimeConfig;
XlingsConfig xlings; // [xlings] build environment (L-1)
std::vector<ConditionalConfig> conditionalConfigs; // [target.'cfg(...)'.build], deferred
std::map<std::string, Profile> profiles; // [profile.<name>]
// [features] — feature name → implied features ("default" = default set).
Expand Down Expand Up @@ -1128,6 +1147,16 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
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

// [xlings] — build environment (L-1). Subsections mirror .xlings.json 1:1.
if (auto v = doc->get_string_array("xlings.deps")) m.xlings.deps = *v;
if (auto v = doc->get_string("xlings.subos")) m.xlings.subos = *v;
if (auto* wt = doc->get_table("xlings.workspace"))
for (auto& [k, val] : *wt)
if (val.is_string()) m.xlings.workspace[k] = val.as_string();
if (auto* et = doc->get_table("xlings.envs"))
for (auto& [k, val] : *et)
if (val.is_string()) m.xlings.envs[k] = val.as_string();
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.76";
inline constexpr std::string_view MCPP_VERSION = "0.0.77";

struct FingerprintInputs {
Toolchain toolchain;
Expand Down
40 changes: 38 additions & 2 deletions src/xlings.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,23 @@ int install_direct(const Env& env, std::string_view target, bool quiet = false);
// slow/unreachable gitcode. An explicit `mcpp self config --mirror CN|GLOBAL`
// still writes that fixed value (config priority). Mirror selection is xlings'
// responsibility; mcpp just declines to override it by default.
// The project's build-environment payload (L-1), materialized 1:1 into the
// `.xlings.json` keys xlings already understands. Plain types (no manifest
// dependency); the caller fills it from a manifest's [xlings] section.
struct ProjectEnv {
std::vector<std::string> deps; // → "deps"
std::vector<std::pair<std::string,std::string>> workspace; // → "workspace"
std::string subos; // → "subos"
std::vector<std::pair<std::string,std::string>> envs; // → "envs"
bool empty() const {
return deps.empty() && workspace.empty() && subos.empty() && envs.empty();
}
};

void seed_xlings_json(const Env& env,
std::span<const std::pair<std::string,std::string>> repos,
std::string_view mirror = "auto");
std::string_view mirror = "auto",
const ProjectEnv& penv = {});

// Persist the xlings mirror selection in .xlings.json via xlings itself.
int config_show(const Env& env);
Expand Down Expand Up @@ -1069,7 +1083,8 @@ int install_direct(const Env& env, std::string_view target, bool quiet) {

void seed_xlings_json(const Env& env,
std::span<const std::pair<std::string,std::string>> repos,
std::string_view mirror)
std::string_view mirror,
const ProjectEnv& penv)
{
auto path = env.home / ".xlings.json";
std::string json = "{\n";
Expand All @@ -1081,6 +1096,27 @@ void seed_xlings_json(const Env& env,
i + 1 == repos.size() ? "" : ",");
}
json += " ],\n";
// [xlings] build environment (L-1): materialize deps/workspace/subos/envs
// verbatim into the keys xlings reads. Each is emitted only when non-empty.
auto emit_obj = [&](std::string_view key,
std::span<const std::pair<std::string,std::string>> kv) {
json += std::format(" \"{}\": {{\n", key);
for (std::size_t i = 0; i < kv.size(); ++i)
json += std::format(" \"{}\": \"{}\"{}\n",
json_escape(kv[i].first), json_escape(kv[i].second),
i + 1 == kv.size() ? "" : ",");
json += " },\n";
};
if (!penv.deps.empty()) {
json += " \"deps\": [";
for (std::size_t i = 0; i < penv.deps.size(); ++i)
json += std::format("{}\"{}\"", i ? ", " : "", json_escape(penv.deps[i]));
json += "],\n";
}
if (!penv.workspace.empty()) emit_obj("workspace", penv.workspace);
if (!penv.subos.empty())
json += std::format(" \"subos\": \"{}\",\n", json_escape(penv.subos));
if (!penv.envs.empty()) emit_obj("envs", penv.envs);
json += " \"lang\": \"en\",\n";
json += std::format(" \"mirror\": \"{}\"\n", json_escape(mirror));
json += "}\n";
Expand Down
52 changes: 52 additions & 0 deletions tests/e2e/88_xlings_environment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# 88_xlings_environment.sh — L-1 build environment: a project's `[xlings]` section
# is materialized verbatim into <proj>/.mcpp/.xlings.json (the keys xlings already
# reads: deps / workspace / subos / envs), so a project can declare its host
# build-tools, per-tool env vars, pinned tool versions, and a named sandbox.
# See .agents/docs/2026-06-29-manifest-environment-and-platform-design.md (L-1).
#
# requires: gcc
set -e

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

mkdir -p app/src
cat > app/mcpp.toml <<'EOF'
[package]
name = "app"
version = "0.1.0"

[xlings]
# Host build-tools the project wants available (xlings "deps").
deps = ["make@4.4", "cmake@3.28"]
# A named per-project sandbox.
subos = "dev"

[xlings.workspace]
# Pin a tool version (the general form of [toolchain]).
ninja = "1.12.1"

[xlings.envs]
# Env vars applied by xvm shims.
APP_BUILD_ENV = "1"
EOF
echo 'int main() { return 0; }' > app/src/main.cpp

cd app
"$MCPP" build > b.log 2>&1 || { cat b.log; echo "FAIL: build errored"; exit 1; }

# The project .xlings.json must carry the [xlings] section materialized 1:1.
J=.mcpp/.xlings.json
[ -f "$J" ] || { echo "FAIL: $J was not written"; ls -la .mcpp 2>/dev/null; exit 1; }
echo "--- $J ---"; cat "$J"
grep -q '"deps"' "$J" || { echo "FAIL: deps not materialized"; exit 1; }
grep -q 'make@4.4' "$J" || { echo "FAIL: deps entry missing"; exit 1; }
grep -q '"subos": "dev"' "$J" || { echo "FAIL: subos not materialized"; exit 1; }
grep -q '"workspace"' "$J" || { echo "FAIL: workspace not materialized"; exit 1; }
grep -q '"ninja": "1.12.1"' "$J" || { echo "FAIL: workspace pin missing"; exit 1; }
grep -q '"envs"' "$J" || { echo "FAIL: envs not materialized"; exit 1; }
grep -q '"APP_BUILD_ENV": "1"' "$J" || { echo "FAIL: env var missing"; exit 1; }

echo "OK"
Loading