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
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue.svg)](https://en.cppreference.com/w/cpp/23)
[![Module](https://img.shields.io/badge/module-ok-green.svg)](https://en.cppreference.com/w/cpp/language/modules)
[![License](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](LICENSE)
[![ci](https://github.com/mcpp-community/mcpp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci.yml)

| [文档](docs/zh/) · [快速开始](docs/zh/01-getting-started.md) · [mcpp.toml 指南](docs/zh/04-mcpp-toml.md) · [示例项目](docs/zh/03-examples.md) · [工具链管理](docs/zh/20-toolchains.md) |
|:---:|
| [包索引 mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [模块化库 mcpplibs](https://github.com/mcpplibs) · [社区论坛](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) |
| [![ci](https://github.com/mcpp-community/mcpp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci.yml) |
| 支持的插件 · [mcpp-language-server(mcppls)](https://github.com/Sunrisepeak/mcpp-language-server) —— C++20/23 模块语言服务器,面向 VS Code、Zed、CLion、Neovim、AI Agent(MCP)与 CI |

<p align="center">
Expand Down
32 changes: 29 additions & 3 deletions docs/04-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,10 @@ soname = "libmydriver.so.1"
exports = "abi/mydriver.exports" # or inline: exports = ["vk_icd*"]
```

**Omitting the key publishes everything, which is what both platforms already
do** — ELF gives symbols default visibility, and PE gets an auto-generated
`.def` listing every symbol. `exports` narrows that.
Omitting `exports` leaves ELF and Mach-O's native visibility rules in effect.
On the MSVC ABI, mcpp discovers exportable external definitions unless an input
already declares exports or `auto_export = false` disables discovery. `exports`
narrows the linker's published set.

Two projects need the narrowing. A **runtime with a stable ABI** publishes a
reviewed set and nothing else, so that what is not in the set stays free to
Expand Down Expand Up @@ -300,6 +301,31 @@ A `soname` is meaningful on `kind = "lib"` too — see
[`dependency_linkage`](#dependency_linkage--static-or-shared-is-the-consumers-decision)
below, where the form a library takes becomes the consumer's decision.

#### `auto_export` — native export control on the MSVC ABI (unreleased)

The key and LLVM bitcode discovery require an unreleased source build; they are
not available in mcpp 2026.10.3.1. A target that supplies its own export control
can omit the automatic export-discovery step:

```toml
[targets.plugin]
kind = "shared"
auto_export = false
```

The boolean defaults to `true` and applies only to PE shared libraries on the
MSVC ABI, including clang and clang-cl. It applies when a library target is
built as a dependency too. It has no effect on static libraries, executables,
ELF, Mach-O or MinGW. Native `__declspec(dllexport)`, linker flags and explicit
`exports` lists remain effective when discovery is disabled.

With discovery enabled, any input's explicit export intent suppresses automatic
exports for the whole DLL. COFF directives are checked first. LLVM bitcode is
inspected with the selected LLVM compiler, including `dllexport` declarations
and linker-option metadata. Only an unannotated DLL needs candidate enumeration;
bitcode candidates come from `llvm-nm` beside that compiler. Both FullLTO and
ThinLTO inputs can be mixed with ordinary COFF objects.

#### `windows_subsystem` and `windows_entry` — a Windows GUI executable (mcpp 2026.9.12.2+)

```toml
Expand Down
14 changes: 8 additions & 6 deletions docs/12-binary-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,9 @@ exports at 65535.
mcpp generates the `.def` from the objects, which is what CMake's
`WINDOWS_EXPORT_ALL_SYMBOLS` has done since 3.4. It is a build-graph node whose
inputs are the same objects the link consumes, so the exported surface cannot
drift from what was compiled, and it reads COFF directly rather than shelling out
to `dumpbin` — that tool lives in a Visual Studio developer environment, and
mcpp's default Windows toolchain is clang.
drift from what was compiled. Ordinary COFF objects are read directly. LLVM
bitcode inspection (unreleased source builds) uses the selected LLVM compiler
and the `llvm-nm` beside it; both FullLTO and ThinLTO are accepted.

**Two limits survive that no tool can remove**, and they are the same two CMake
documents for the same mechanism:
Expand All @@ -386,10 +386,12 @@ documents for the same mechanism:

Both are answered by annotation, and **annotation wins**: an object that already
carries `/EXPORT:` directives — which is what `__declspec(dllexport)` emits —
makes mcpp stand down and generate nothing. Adding a list on top would export the
makes mcpp write an empty `EXPORTS` section. Adding a list on top would export the
same names twice (`LNK4197`) and export everything else besides, replacing a
chosen public surface with all of it. Nothing is configured for this; the objects
say it.
chosen public surface with all of it. Bitcode's `dllexport` storage class and
linker-option metadata express the same intent. Export intent is checked before
candidate enumeration. The per-target [`auto_export`](04-mcpp-toml.md#auto_export--native-export-control-on-the-msvc-abi-unreleased)
key disables discovery entirely (unreleased source builds).

Past 65535 exportable symbols mcpp refuses rather than truncating. A truncated
export table links cleanly and then fails at whichever consumer needed the symbol
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ token in front of a reader to the chapter that owns it.
| | chapter | | chapter |
|---|---|---|---|
| `[package]`, `[targets.<n>]`, `[build]`, `[lib]` | [04](04-mcpp-toml.md) | `[profile.<n>]`, `[resources]`, `[runtime]` | [04](04-mcpp-toml.md) |
| `[targets.<n>] auto_export` | [04](04-mcpp-toml.md) | `[targets.<n>] exports` | [04](04-mcpp-toml.md) |
| `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]` | [05](05-dependencies.md) | `scan_overrides`, `module_extensions` | [04](04-mcpp-toml.md) |
| `[features]`, `[feature-deps.<f>]`, `provides` / `requires` | [06](06-features-and-capabilities.md) | `[workspace]` | [07](07-workspace.md) |
| `[toolchain]`, `cxx_runtime` | [20](20-toolchains.md) | `[target.<sel>]`, `cfg(…)` | [22](22-target-side.md) |
Expand Down
28 changes: 25 additions & 3 deletions docs/zh/04-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,9 @@ soname = "libmydriver.so.1"
exports = "abi/mydriver.exports" # or inline: exports = ["vk_icd*"]
```

**省略这个键会发布一切,而这恰好是两个平台本来就在做的事**——ELF 给
符号默认可见性,PE 自动生成一份列出每个符号的 `.def`。`exports` 收窄
这个范围。
省略 `exports` 会保留 ELF 和 Mach-O 的原生可见性规则。在 MSVC ABI 上,
mcpp 会发现可导出的外部定义;任一输入已经声明导出,或设置了
`auto_export = false` 时不进行这种发现。`exports` 收窄链接器发布的集合。

两类工程需要这种收窄。**带稳定 ABI 的运行时**只发布一份经过审查的
集合,其余一概不发布,让不在集合里的东西保留自由变化的空间。**与同类
Expand Down Expand Up @@ -300,6 +300,28 @@ script 并通过 `[build] ldflags` 传入,或者自行计算并发出
[`dependency_linkage`](#dependency_linkage--静态还是动态由消费者决定),
在那里,一个库采取的形式变成消费者的决定。

#### `auto_export` —— MSVC ABI 上的原生导出控制(尚未发布)

这个键和 LLVM bitcode 导出发现需要尚未发布的源码构建,mcpp 2026.10.3.1
不提供这些能力。自行控制导出的 target 可以省略自动导出发现步骤:

```toml
[targets.plugin]
kind = "shared"
auto_export = false
```

这个布尔值默认为 `true`,只作用于 MSVC ABI 上的 PE 共享库,包括 clang 和
clang-cl;库作为依赖构建时也生效。它不影响静态库、可执行文件、ELF、Mach-O
或 MinGW。关闭发现后,原生 `__declspec(dllexport)`、链接旗标和显式 `exports`
列表仍然生效。

开启发现时,任一输入的显式导出意图都会禁止整个 DLL 的自动导出。首先检查
COFF 指令,再使用所选 LLVM 编译器检查 bitcode 中的 `dllexport` 声明和
linker-option 元数据。只有没有标注的 DLL 才需要枚举候选符号,bitcode 的
候选符号由该编译器旁的 `llvm-nm` 提供。FullLTO 和 ThinLTO 输入都可以与
普通 COFF 对象混用。

#### `windows_subsystem` 与 `windows_entry` —— Windows GUI 可执行文件(mcpp 2026.9.12.2+)

```toml
Expand Down
14 changes: 8 additions & 6 deletions docs/zh/12-binary-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,9 +343,9 @@ error while loading shared libraries: libstdc++.so.6: cannot open shared object

mcpp 从对象文件生成 `.def`——这正是 CMake 的 `WINDOWS_EXPORT_ALL_SYMBOLS`
自 3.4 起在做的事。它是构建图里的一个节点,输入就是链接所消费的那批对象,
因此导出面不会与「实际编译了什么」发生漂移;而且它直接读 COFF,不会 shell 出去
调用 `dumpbin`——那个工具存在于 Visual Studio 的开发者环境里,而 mcpp 在
Windows 上的默认工具链是 clang。
因此导出面不会与「实际编译了什么」发生漂移。普通 COFF 对象直接读取;LLVM
bitcode 检查(尚未发布的源码构建)使用所选 LLVM 编译器和它旁边的 `llvm-nm`,
支持 FullLTO 和 ThinLTO。

**有两条限制是任何工具都消不掉的**,与 CMake 为同一机制记录的正是同样两条:

Expand All @@ -355,10 +355,12 @@ Windows 上的默认工具链是 clang。
| **vtable** 被引用的类 | 整个类都要被标注,例如一个带虚函数的类的委托构造函数 |

两者都靠标注来回答,而且**标注优先**:一个已经带 `/EXPORT:` 指令的对象——那
正是 `__declspec(dllexport)` 产生的——会让 mcpp 让开,什么都不生成。在其上再
正是 `__declspec(dllexport)` 产生的——会让 mcpp 写入空的 `EXPORTS` 节。在其上再
叠加一份列表,会把同一批符号导出两次(`LNK4197`),还会把其余所有符号也一并
导出,用「全部」取代作者选定的那个公开面。这件事没有任何东西需要配置:对象
文件自己说了算。
导出,用「全部」取代作者选定的那个公开面。bitcode 的 `dllexport` 存储类别和
linker-option 元数据表达同样的意图。导出意图在枚举候选符号前检查。每个 target
可以通过 [`auto_export`](04-mcpp-toml.md#auto_export--msvc-abi-上的原生导出控制尚未发布)
完全关闭导出发现(尚未发布的源码构建)。

超过 65535 个可导出符号时,mcpp 拒绝而不是截断。一个被截断的导出表能干净地
链接完成,随后在恰好需要那个掉出去的符号的消费方那里失败。
Expand Down
1 change: 1 addition & 0 deletions docs/zh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
| | 章节 | | 章节 |
|---|---|---|---|
| `[package]`、`[targets.<n>]`、`[build]`、`[lib]` | [04](04-mcpp-toml.md) | `[profile.<n>]`、`[resources]`、`[runtime]` | [04](04-mcpp-toml.md) |
| `[targets.<n>] auto_export` | [04](04-mcpp-toml.md) | `[targets.<n>] exports` | [04](04-mcpp-toml.md) |
| `[dependencies]`、`[dev-dependencies]`、`[build-dependencies]` | [05](05-dependencies.md) | `scan_overrides`、`module_extensions` | [04](04-mcpp-toml.md) |
| `[features]`、`[feature-deps.<f>]`、`provides` / `requires` | [06](06-features-and-capabilities.md) | `[workspace]` | [07](07-workspace.md) |
| `[toolchain]`、`cxx_runtime` | [20](20-toolchains.md) | `[target.<sel>]`、`cfg(…)` | [22](22-target-side.md) |
Expand Down
8 changes: 7 additions & 1 deletion modules/manifest/src/toml.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,12 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
if (auto msg = validate_target_soname(t, std::format("targets.{}.", tname))) {
return std::unexpected(error(origin, *msg));
}
if (auto it = tt.find("auto_export"); it != tt.end()) {
if (!it->second.is_bool())
return std::unexpected(error(origin, std::format(
"targets.{}.auto_export must be a boolean", tname)));
t.autoExport = it->second.as_bool();
}
// `exports` -- a file of symbol patterns, or the patterns inline.
//
// BOTH FORMS, because the two are used at different scales and the ABI
Expand Down Expand Up @@ -1983,7 +1989,7 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
// must reach SHARED code is intentionally not a target key; point users
// at the right axis (workspace / features / profile).
static constexpr std::string_view kKnownTargetKeys[] = {
"kind", "linkage", "main", "soname", "exports",
"kind", "linkage", "main", "soname", "exports", "auto_export",
"cflags", "cxxflags", "defines", "required_features",
"windows_entry", "windows_subsystem", "windows_code_page",
};
Expand Down
3 changes: 3 additions & 0 deletions modules/manifest/src/types.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ struct Target {
// stand in for it, adds `Application` guarded by the row's actual form.
bool is_program() const { return kind == Binary || kind == Application; }
std::string soname; // ABI name for shared libraries, e.g. libfoo.so.1
// PE / MSVC ABI only: discover exports when the objects do not declare
// any. False leaves export control entirely to the native linker inputs.
bool autoExport = true;
// WHICH SYMBOLS THIS ARTIFACT PUBLISHES. Empty = every symbol, which is
// what both platforms do today (ELF default visibility; PE gets an
// auto-generated .def listing everything, mcpp.build.coff_exports).
Expand Down
7 changes: 7 additions & 0 deletions modules/manifest/src/xpkg.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,13 @@ synthesize_from_xpkg_lua(std::string_view luaContent,
t.main = cur.read_string();
} else if (sub == "soname") {
t.soname = cur.read_string();
} else if (sub == "auto_export") {
auto raw = cur.read_bareword();
if (raw != "true" && raw != "false")
return std::unexpected(ManifestError{
std::format("targets.{}.auto_export must be a boolean", tname),
m.sourcePath, 0, 0});
t.autoExport = raw == "true";
} else if (sub == "required_features") {
// #355: without this, a Form B descriptor could not
// express the cost gate that makes an optional host
Expand Down
24 changes: 16 additions & 8 deletions src/build/coff_exports.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ bool is_supported_machine(std::uint16_t machine);
// place to say something the objects already say, and the two could disagree.
bool declares_exports(std::span<const std::byte> bytes);

// Shared by the COFF and LLVM symbol readers so LTO does not change the
// filtering policy or i386 calling-convention spelling.
std::optional<std::string> export_name(std::string_view name, bool i386);

// Read one object file's exportable symbols. `bytes` is a whole `.obj`.
//
// Returns an error string for input this reader cannot honestly interpret —
Expand Down Expand Up @@ -151,6 +155,15 @@ bool is_skipped_name(std::string_view n) {

} // namespace

std::optional<std::string> export_name(std::string_view name, bool i386) {
if (name.empty() || is_skipped_name(name)) return std::nullopt;
std::string out(name);
if (i386 && out.starts_with('_') && out.find('@') == std::string::npos)
out.erase(0, 1);
if (out.empty()) return std::nullopt;
return out;
}

bool declares_exports(std::span<const std::byte> bytes) {
if (bytes.size() < kFileHeaderSize) return false;
const auto numSections = rd16(bytes, 2);
Expand Down Expand Up @@ -293,13 +306,8 @@ read_exports(std::span<const std::byte> bytes)

auto nm = name_at(rec);
if (!nm) return std::unexpected(nm.error());
if (nm->empty() || is_skipped_name(*nm)) continue;

// i386 (and `__cdecl` on it) carries a leading underscore that is part
// of the calling convention rather than of the name.
std::string name = *nm;
if (machine == kMachineI386 && name.starts_with('_') && name.find('@') == std::string::npos)
name.erase(0, 1);
auto name = export_name(*nm, machine == kMachineI386);
if (!name) continue;

const auto flags = std::size_t(sectionNum) < sectionFlags.size()
? sectionFlags[sectionNum] : 0u;
Expand All @@ -308,7 +316,7 @@ read_exports(std::span<const std::byte> bytes)
// section (a `const`) is still data.
const bool isData = type != kSymTypeFunction
&& (flags & kScnMemExecute) == 0;
out.push_back(Export{ std::move(name), isData });
out.push_back(Export{ std::move(*name), isData });
}

return out;
Expand Down
19 changes: 17 additions & 2 deletions src/build/ninja_backend.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import mcpp.diag;
import mcpp.build.advice;
import mcpp.dyndep;
import mcpp.toolchain.detect;
import mcpp.toolchain.model;
import mcpp.modgraph.glob;
import mcpp.toolchain.dialect;
import mcpp.toolchain.provider;
import mcpp.toolchain.registry;
Expand Down Expand Up @@ -2152,7 +2154,7 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements,
// POSIX-shell command is skipped entirely on Windows, the only platform this
// edge exists for.
append("rule coff_def\n");
append(" command = $mcpp coff-def --output $out --name $def_name $in\n");
append(" command = $mcpp coff-def --output $out --name $def_name $coff_tools $in\n");
append(" description = DEF $out\n\n");

// A WINDOWS PROGRAM'S RUNTIME DLLS, PLACED AFTER ITS LINK (SPEC-007 R4.3).
Expand Down Expand Up @@ -3195,8 +3197,21 @@ std::string emit_ninja_string(const BuildPlan& plan, std::string* placements,
for (auto const& o : lu.objects) defIns += " " + escape_ninja_path(o);
append(std::format("build {} : coff_def{}\n",
escape_ninja_path(lu.defFile), defIns));
append(std::format(" def_name = {}\n\n",
append(std::format(" def_name = {}\n",
lu.output.filename().string()));
if (mcpp::toolchain::is_clang(plan.toolchain)) {
const auto& compiler = plan.toolchain.binaryPath;
const auto nm = compiler.parent_path()
/ (mcpp::platform::is_windows ? "llvm-nm.exe" : "llvm-nm");
auto cxxArg = mcpp::modgraph::try_narrow(compiler);
auto nmArg = mcpp::modgraph::try_narrow(nm);
if (!cxxArg || !nmArg)
throw std::runtime_error("the selected LLVM tools have no UTF-8 spelling");
append(" coff_tools = --llvm-cxx " + ninja_command_word(*cxxArg)
+ " --llvm-nm " + ninja_command_word(*nmArg)
+ " --llvm-target " + ninja_command_word(plan.toolchain.targetTriple) + "\n");
}
append("\n");
}

if (!lu.defFile.empty()) implicit += " " + escape_ninja_path(lu.defFile);
Expand Down
Loading
Loading