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: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Preserve upstream document bytes and original whitespace on every platform.
upstream/** -text -whitespace
88 changes: 88 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
mcpp:
name: mcpp (${{ matrix.os }}, ${{ matrix.toolchain }}${{ matrix.args && format(', {0}', matrix.args) || '' }})
strategy:
fail-fast: false
matrix:
# Each C library with each C++ standard library mcpp builds against.
include:
- { os: ubuntu-24.04, toolchain: 'gcc@16.1.0' } # glibc, libstdc++
- { os: ubuntu-24.04, toolchain: 'gcc@16.1.0', args: '--target x86_64-linux-musl' } # musl, libstdc++
- { os: ubuntu-24.04, toolchain: 'llvm@23.1.3' } # glibc, libc++
- { os: ubuntu-24.04, toolchain: 'llvm@23.1.3', args: '--target x86_64-openkal --platform openkal-platform@0.24.0' } # openkal-musl, libc++
- { os: macos-15, toolchain: 'llvm@23.1.3' } # libSystem, libc++
- { os: windows-2025, toolchain: 'llvm@23.1.3' } # the Windows CRT
- { os: windows-2025, toolchain: 'msvc@system' } # the Windows CRT, the MSVC STL
- { os: windows-2025, toolchain: 'llvm@23.1.3', args: '--target x86_64-windows-gnu' } # mingw-w64
runs-on: ${{ matrix.os }}
timeout-minutes: 60
defaults:
run:
shell: bash
env:
XLINGS_NON_INTERACTIVE: 1
ARGS: ${{ matrix.args }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Mwrap identity and bilingual README contract
run: python3 tools/check_mwrap_release.py

- name: Install Xlings (Linux/macOS)
if: runner.os != 'Windows'
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/refs/heads/main/tools/other/quick_install.sh | bash
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install Xlings (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Install project tools (.xlings.json)
run: |
for attempt in 1 2 3; do
xlings update > /dev/null 2>&1 || true
if xlings install; then break; fi
[ "$attempt" = 3 ] && exit 1
sleep 30
done
mcpp --version

- name: Select the toolchain
run: |
name="${{ matrix.toolchain }}"
[ "$name" = msvc@system ] || mcpp toolchain install "${name%@*}" "${name#*@}"
mcpp toolchain default "$name"

- name: Build the modules
run: mcpp build $ARGS

- name: Tests (the streams, errno, the stdio constants, limits, the function-like macros, every mapping against its macro, header mixing)
run: mcpp test $ARGS

- name: Mapping audit (the macros as clang reads them on this host)
if: startsWith(matrix.toolchain, 'llvm') && !matrix.args
run: python3 tools/generate.py --check
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Release
on:
push:
tags: ["mwrap-*"]
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
actions: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Check tag, upstream version and bilingual entry pages
run: python3 tools/check_mwrap_release.py --tag "$GITHUB_REF_NAME"
- name: Require successful source CI at this exact commit
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
result=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ci.yml --commit "$GITHUB_SHA" --limit 1 --json status,conclusion --jq '.[0] | .status == "completed" and .conclusion == "success"')
test "$result" = true || { echo 'No successful source CI at the tag commit'; exit 1; }
- name: Build immutable source asset and publish
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#mwrap-}"
repo="${GITHUB_REPOSITORY#*/}"
asset="${repo}-${version}.tar.gz"
git archive --format=tar.gz --prefix="${repo}-${version}/" "$GITHUB_SHA" > "$RUNNER_TEMP/$asset"
(cd "$RUNNER_TEMP" && sha256sum "$asset" > "$asset.sha256")
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo 'Release already exists; refusing to change immutable assets'; exit 1
fi
extra=()
if [[ "$version" == *-rc* ]]; then extra+=(--prerelease); fi
gh release create "$GITHUB_REF_NAME" "$RUNNER_TEMP/$asset" "$RUNNER_TEMP/$asset.sha256" \
--repo "$GITHUB_REPOSITORY" --verify-tag --title "$repo $version" \
--notes "Module wrapper for upstream $version. New mwrap package identities become installable after mcpp-index publication; see the bilingual README for the tested support scope." "${extra[@]}"
28 changes: 28 additions & 0 deletions .github/workflows/templates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Actual project templates
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: templates-${{ github.ref }}
cancel-in-progress: true
env:
MCPP_VERSION: "2026.11.5.1"
jobs:
templates:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install the pinned mcpp
shell: bash
run: |
archive="mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz"
curl -L -fsS -o "$archive" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$archive"
tar -xzf "$archive"
echo "$PWD/mcpp-${MCPP_VERSION}-linux-x86_64/bin" >> "$GITHUB_PATH"
- name: Actual scaffold, strict build and run of the default template
run: python3 tools/test_template.py --toolchain gcc@16.1.0
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
target/
.mcpp/
mcpp.lock
compile_commands.json
__pycache__/
.xlings/
5 changes: 5 additions & 0 deletions .xlings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": {
"xim:mcpp": "2026.11.5.1"
}
}
9 changes: 9 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
libc-m: C++ module wrapper
Copyright (c) 2026 mcpp-community contributors

The wrapper contributions are licensed under the Apache License, Version 2.0.
The upstream is the ISO C standard (ISO/IEC 9899:2018), whose library summary
names the macros this package maps; no text of the standard is included. The
implementation is the target's C library, under its own license, and the
function-like macros of include/mwrap/libc/macros.hpp are that library's own
definitions, reached through its headers.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ target leaves out (`FP_FAST_FMA` where `fma` is not fast) is not mapped there.
POSIX names that ISO C does not have (`ENOENT`, `EACCES` …) are not mapped:
`std::errc` gives them portably. `NULL` is `nullptr`. The function-like macros
that no function can replace stay macros in `<mwrap/libc/macros.hpp>`, which
includes the five headers that define them; in a unit that includes it, the
object-like macros of those headers (`SIZE_MAX` …) are active and are used as
macros ([docs/usage.md](docs/usage.md)).
includes the five headers that define them and then undefines every
object-like macro of ISO C, so the mapped names work in that unit on every C
library ([docs/usage.md](docs/usage.md)).

## Build and testing

Expand Down
72 changes: 72 additions & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# libc

> C 标准库(ISO C17)以 C++23 模块提供,其宏成为 C++ 名字。

[English](README.md) · **简体中文** · [上游](https://www.iso.org/standard/74528.html) · [发布](https://github.com/mcpplibs/libc-m/releases) · [mcpp](https://github.com/mcpp-community/mcpp) · [索引](https://github.com/mcpplibs/mcpp-index)

[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue.svg)](https://en.cppreference.com/w/cpp/23)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![mcpp-community](https://img.shields.io/badge/mcpp--community-mwrap-blue)](https://github.com/mcpp-community)
[![CI](https://github.com/mcpplibs/libc-m/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mcpplibs/libc-m/actions/workflows/ci.yml)

`import std.compat` 给 C++ 程序提供 C 库的函数与类型,但不提供宏:命名模块不能导出宏,而 `stdout`、`errno`、`EOF`、
`BUFSIZ`、`INT_MAX` 在每个 C 库中都是宏。本包把 ISO C17 的对象宏以原名映射:常量为 `inline constexpr`,其余(`stdout`、
`errno`、`MB_CUR_MAX` 等)为返回宏表达式的函数。本包不实现任何东西:实现是目标的 C 库;版本 17.0.0 指所依据的标准
(ISO/IEC 9899:2018,C++23 规范性引用的版本)。

## 模块

- `import mcpplibs.mwrap.libc;`:重新导出 `std.compat`,以及宏模块
- `import mcpplibs.mwrap.libc.macros;`:`stdout()`、`stderr()`、`stdin()`、`errno()`、`EOF`、`BUFSIZ`、`SEEK_SET`、`EDOM`、`ERANGE`、`EILSEQ`、`CHAR_BIT`、`INT_MAX`、`SIZE_MAX`、`DBL_EPSILON`、`PRId64`、`EXIT_FAILURE`、`LC_ALL`、`SIGINT` 等,位于 `mcpplibs::mwrap::libc::macros`
- `#include <mwrap/libc/macros.hpp>`:`assert`、`setjmp`、`va_start` … `va_end`、`offsetof`、`INT64_C` 等,即 C 库自己的定义

> 更多模块 API -> [用法与 API](docs/usage.md)

## 快速开始

```sh
mcpp new stdio --template mcpplibs.mwrap.libc@17.0.0
cd stdio
mcpp run
```

默认模板用 C stdio 打开一个文件,打不开时报告 C 库的 `errno()` 与 `strerror`,并打印几个极限值。

```toml
[dependencies]
"mcpplibs.mwrap.libc" = "17.0.0"
```

```cpp
import mcpplibs.mwrap.libc;

namespace c = mcpplibs::mwrap::libc::macros;

int main() {
std::fprintf(c::stderr(), "BUFSIZ is %d\n", int(c::BUFSIZ));
c::errno() = 0;
std::strtol("99999999999999999999", nullptr, 10);
return c::errno() == c::ERANGE ? c::EXIT_SUCCESS : c::EXIT_FAILURE;
}
```

## 映射范围

ISO C17 库摘要(附录 B)中目标所定义的每个对象宏:`<errno.h>`、`<fenv.h>`、`<float.h>`、`<inttypes.h>`、`<limits.h>`、
`<locale.h>`、`<math.h>`、`<signal.h>`、`<stdint.h>`、`<stdio.h>`、`<stdlib.h>`、`<time.h>`、`<wchar.h>` 中共 334 个名字。
目标未定义的宏(例如 `fma` 不快时的 `FP_FAST_FMA`)在该目标上不映射。ISO C 没有的 POSIX 名字(`ENOENT`、`EACCES` 等)
不映射:`std::errc` 可移植地给出它们。`NULL` 即 `nullptr`。无法用函数替代的函数式宏保留在 `<mwrap/libc/macros.hpp>`,
它包含定义这些宏的五个头文件,随后取消定义 ISO C 的全部对象宏,使映射后的名字在该翻译单元中于每种 C 库上都可用
([docs/usage.md](docs/usage.md))。

## 构建与测试

CI 在 mcpp 所面向的每种 C 库与每种 C++ 标准库组合上构建并测试:glibc 加 libstdc++ 与 libc++、musl、openkal-musl(经
okrun 运行)、macOS libSystem、Windows CRT 加 libc++ 与 MSVC STL、mingw-w64。测试经 `stdout()` 与 `stderr()` 写出,读取 C 库
设置的 `errno()`,在临时文件上使用 stdio 常量,调用函数式宏,在该目标上比较每个映射与其宏,并混用头文件与模块。映射审计
([tools/generate.py](tools/generate.py) `--check`)由 [tools/iso_c.toml](tools/iso_c.toml) 重新生成映射,并在每个 LLVM
宿主上检查被归为常量的宏在该宿主上确为常量。另一个作业把默认模板当作已安装的包生成项目并运行。

## 许可

包装部分为 [Apache-2.0](LICENSE),作者 mcpp-community([NOTICE](NOTICE))。各目标的 C 库仍遵循其自身的许可。
78 changes: 78 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Usage and API

`mcpplibs.mwrap.libc` 17.0.0 maps the object-like macros of ISO C17 for C++
modules. This page lists what the modules hold, how each macro is mapped, and
how the package relates to `std.compat` and to the C headers.

## Modules

| Module | Holds |
|---|---|
| `mcpplibs.mwrap.libc` | `export import std.compat;` (the functions and types of the C library, in `std` and in the global namespace) and `export import mcpplibs.mwrap.libc.macros;` |
| `mcpplibs.mwrap.libc.macros` | in `mcpplibs::mwrap::libc::macros`, every object-like macro of ISO C17 Annex B that the target defines (tools/iso_c.toml lists the 334 names by header) |

## How a macro is mapped

- **A constant expression** is an `inline constexpr` of the same name and
value: `EOF`, `BUFSIZ`, `SEEK_SET`, `EDOM`, `ERANGE`, `EILSEQ`, `CHAR_BIT`,
`INT_MAX`, `SIZE_MAX`, the `<float.h>` limits, `PRId64` and the other format
strings (a `const char*`), `EXIT_SUCCESS`, `LC_ALL`, `SIGINT`, `CLOCKS_PER_SEC` ….
- **Anything else** is a function of the same name, `constexpr` and returning
the macro's expression (`decltype(auto)`):
- `errno()` is the C library's error number, an `int&`: `errno() = 0` clears it;
- `stdin()`, `stdout()`, `stderr()` are the streams (`std::FILE*`);
- `MB_CUR_MAX()` follows the locale; `SIG_ERR()`, `SIG_IGN()` and
`FE_DFL_ENV()` are pointers that are not constants where glibc spells them;
- `INFINITY()`, `NAN()`, `HUGE_VAL()`, `HUGE_VALF()`, `HUGE_VALL()`,
`math_errhandling()`, `FLT_ROUNDS()` and `FLT_EVAL_METHOD()` are constants
with some C libraries and not with others (the Windows CRT, macOS), so they
take the function form everywhere (tools/surface.toml). Where the target
allows, the call is a constant expression; `std::numeric_limits` gives
`infinity()` and `quiet_NaN()` as constants on every target.
- **A macro the target does not define** is not mapped there: `FP_FAST_FMA`,
`FP_FAST_FMAF` and `FP_FAST_FMAL` exist where `fma` is fast, and a `FE_*`
exception or rounding mode exists where the hardware has it. Test with
`requires { mcpplibs::mwrap::libc::macros::FP_FAST_FMA(); }`.

tests/macros.cpp compares every mapping with its macro on each target: values
equal, and `errno()` and the streams the same objects.

## What is not mapped

| Macros | Reason |
|---|---|
| `ENOENT`, `EACCES`, `ENOEXEC`, `EPIPE` and the other POSIX error numbers | not ISO C; their values differ between C libraries. `std::errc` names them portably: `std::make_error_code(std::errc::no_such_file_or_directory)` |
| `NULL` | C++ has `nullptr` |
| `<complex.h>`, `<tgmath.h>` | in C++ these headers are C++'s (`std::complex`, the overloads of `<cmath>`) |
| `<iso646.h>`, `<stdalign.h>`, `<stdbool.h>`, `<stdnoreturn.h>` | their macros spell C++ keywords and attributes |
| `<stdatomic.h>`, `<threads.h>` | C++ has `std::atomic` and `std::thread` |
| `fpclassify`, `isnan`, `signbit`, `isgreater` … | functions of `<cmath>` in C++ (`std::isnan`) |
| `__STDC_*` | predefined by the compiler |
| Annex K (`L_tmpnam_s`, `RSIZE_MAX` …) | optional; the C libraries targeted here do not provide it |

## The function-like macros

`assert`, `setjmp`, `va_start`, `va_arg`, `va_copy`, `va_end`, `offsetof` and
`INT8_C` … `UINTMAX_C` take a type or a member name, act at the call site, or
must be macros by the standard. `<mwrap/libc/macros.hpp>` includes the five
headers that define them (`<assert.h>`, `<setjmp.h>`, `<stdarg.h>`,
`<stddef.h>`, `<stdint.h>`), so they are the C library's own. Include it before
the import. Those headers also define object-like macros, and which ones
differs between C libraries: the Windows CRT's `<stddef.h>` defines `errno`,
and macOS's headers bring `MB_CUR_MAX` and `NAN`. A macro expands even inside a
qualified name, so the header undefines every object-like macro of
tools/iso_c.toml after the includes: in the unit, `errno()` and `SIZE_MAX` are
the mapped names on every target. A C header included later in the same unit
is already included and defines nothing again.

A unit that includes a C header itself, beside the module, has that header's
macros active; it uses them as macros, or reads their values under other names
and `#undef`s them before naming the mapped ones (tests/header_mix_test.cpp).

## The implementation

The package has no sources and no dependency: the C library is the target's,
as mcpp selects it (glibc, musl, openkal-musl, the Windows CRT, macOS
libSystem, mingw-w64). The version names the standard: 17.0.0 is ISO/IEC
9899:2018, which C++23 references normatively. A release for C23, when C++
references it, will be 23.0.0.
Loading
Loading