diff --git a/.agents/docs/2026-08-15-add-nanodbc-plan.md b/.agents/docs/2026-08-15-add-nanodbc-plan.md new file mode 100644 index 0000000..c713ebd --- /dev/null +++ b/.agents/docs/2026-08-15-add-nanodbc-plan.md @@ -0,0 +1,153 @@ +# 新增 compat.nanodbc / compat.unixodbc + +> 日期:2026-08-15 · 验证工具链:mcpp `2026.8.15.1`(本地最新)+ parse 底线 mcpp `2026.8.10.3`(与 `validate.yml` 的 `MCPP_VERSION` 对齐)+ clang `22.1.8` + +## 1. 动机 + +nanodbc 是原生 C ODBC API 的薄 C++ 封装(RAII connection/statement/result),上游已冻结 +(v2.14.0,2022-03-23,此后无提交),正适合一次性收录:没有需要追踪的版本演进。ODBC +仍是接入大量既有数据源(Access、SQL Server、各类国产库)的通用门,索引里此前没有任何 +ODBC 相关的包。 + +## 2. 形态判定 + +| 包 | 形态 | 判定依据 | +|---|---|---| +| `compat.nanodbc` | **A**(C++ 源码 compat) | 一头一实现:`nanodbc/nanodbc.h` + `nanodbc/nanodbc.cpp` | +| `compat.unixodbc` | **E 叠 A**(冻结 config + 纯 C 源码) | unixODBC 2.3.14 dist tarball,DM + odbcinst + ini/log/lst + libltdl | + +版本与 sha256(均双次下载复核): + +- nanodbc `2.14.0` = `56228372042b689beccd96b0ac3476643ea85b3f57b3f23fb11ca4314e68b9a5` + (GitHub tag 归档) +- unixODBC `2.3.14` = `4e2814de3e01fc30b0b9f75e83bb5aba91ab0384ee951286504bb70205524771` + (unixodbc.org **dist** tarball;GitHub 镜像停在 2.3.12 且归档内没有 bootstrap 过的 + `libltdl/`,只有 dist tarball 自带) + +CN 镜像:本贡献者无 `mcpp-res` 写权限,两个包均按 SOP 回退为纯字符串 `url = ""` +(lint 允许,先例 `compat.libmysqlclient`),由维护者后续补镜像。 + +## 3. 关键设计:驱动管理器从哪来 + +nanodbc 只是封装,真正干活的是平台的 ODBC driver manager。三平台答案不同: + +| 平台 | 管理器 | 依据 | +|---|---|---| +| windows | SDK 自带 odbc32(`-lodbc32`) | 始终在 | +| macOS | OS 自带 iODBC(`-liodbc`) | 始终在 | +| linux | **`compat.unixodbc` 源码构建** | 见下 | + +linux 最初按"系统依赖 + `-lodbc`"实现,编译链接都过了,但 **mcpp 的运行时闭包检查拒绝** +产物: + +``` +runtime closure for .../error_path cannot be satisfied: libodbc.so.2 not found on the +search path this artifact will actually use. Its PT_INTERP is a private loader, so the +host's /usr/lib is NOT consulted +``` + +这与 mcpp#352(libGL)是同一堵墙。宿主符号链接农场(glx-runtime 旧方案)已被生态放弃, +xim 侧没有 unixodbc 包,于是只剩与 conan/vcpkg 相同的结论:**linux 的 ODBC 管理器必须 +从源码静态构建**。静态链接让消费者二进制不带 `libodbc.so.2` NEEDED,闭包检查天然通过, +也不再有宿主 glibc 版本错配的风险。 + +## 4. compat.unixodbc 的三个非常规点 + +### 4.1 无 libtool 复刻 ltdl 的 dlopen loader 注册 + +DM 通过 libltdl `dlopen` 数据库驱动。静态构建下 libtool 用 `-dlpreopen` 生成符号表完成 +注册;没有 libtool 时需要手工复刻。从 libtool 产物的重定位记录还原出它生成的表: + +```c +const lt_dlsymlist lt_libltdlc_LTX_preloaded_symbols[] = { + { "libltdlc", 0 }, { "dlopen.a", 0 }, + { "dlopen_LTX_get_vtable", &dlopen_LTX_get_vtable }, { 0, 0 } +}; +``` + +配合 `-DLTDLOPEN=libltdlc`(ltdl.c 经 `LT_CONC3` 拼出表名)与编译进包的 +`loaders/dlopen.c`,注册链路与 libtool 构建完全一致。**对拍验证**:同一 tarball 的 +libtool 静态构建 vs 本描述符直编 —— 句柄分配、驱动枚举、IM002 错误路径、以及 +`lt_dlopen("libm.so.6")` + `lt_dlsym("sin")` 全链路行为一致。 + +### 4.2 两份 config.h 合并为一份 + +DM 的 122 个 TU 无条件 `#include `(顶层 configure 产物);ltdl 的 TU 需要的是 +libltdl 子目录**另一份** configure 产物。上游用 `-DLT_CONFIG_H=''` 区分,但带 +引号的 define 无法穿过 描述符 → mcpp → 命令行 的管道(实测 `#include LT_CONFIG_H` 展开成 +无引号标识符直接报 `expected "FILENAME"`)。两份 config 的冲突宏只有 PACKAGE*/VERSION +一组,而 ltdl 源码从不读它们(已逐一核实),于是把 ltdl 独有的宏并入顶层 config.h,一个 +文件喂所有 TU。 + +### 4.3 单目标 `odbc` + +上游分 libodbc / libodbcinst 两库,但 libodbc.a 本来就吸收 odbcinst/ini/log/lst/ltdl +全部 convenience 对象(实测 241 个 .o)。单目标 `odbc` 就是这个符号集,驱动安装器要的 +`SQLInstallDriverEx` 也在其中。 + +## 5. compat.nanodbc 的两个冻结上游修复 + +### 5.1 libc++ 没有 `char_traits` + +nanodbc.cpp 有 4 处 `std::char_traits::length()`(SQLCHAR = unsigned char)。 +C++23 起主模板只剩声明,libc++ 从未带过该特化 → 硬错误。修复是 +`mcpp_nanodbc_char_traits.h`:一份完整的显式特化(标准为用户字符类型预留的定制点), +以 `_LIBCPP_VERSION` 为界,libstdc++/MSVC STL 自带特化不受影响。经 `-include` 强制包含, +**仅作用于本包编译**,消费者不见。 + +两个已踩的坑,留在描述符注释里: + +- `-include` 必须走 `cxxflags`(本包无 C TU,`cflags` 到不了 `.cpp`); +- shim 内**先 `#include ` 再判 `_LIBCPP_VERSION`** —— force-include 跑在一切头 + 文件之前,此时宏尚未定义。 + +### 5.2 SQL state 被截掉末字符(上游 off-by-one,不修,测试如实断言) + +`recent_error()`(nanodbc.cpp:411-420)复制 `size(sql_state) - 1` 个字符,DM 报的 +"IM002" 经 nanodbc 变成 "IM00"。上游冻结,这是该版本的**真实行为**;测试断言 +`state()` 是 "IM002" 的非空前缀,并断言 `what()` 含 DM 原文 +"Data source name not found"。 + +### 5.3 GCC 拒绝重复的显式实例化定义(linux default 腿,首轮 CI 红) + +C++17 起 string_view 支持默认开,`NANODBC_INSTANTIATE_BIND_STRINGS(std::string)` 与 +`(std::string_view)` 展开出**完全相同**的显式实例化定义(`value_type` 都是 char;u16 +对同理)。同一 TU、同一特化,语义无碍,但标准称其为 ill-formed,GCC(16 实测,c++23 +模式)直接报错,clang 默认静默接受 —— 这正是首轮 CI 只有 linux **default**(GCC)腿红、 +llvm 腿绿的原因(linux llvm 腿的存在价值实锤)。修复是 linux `cxxflags` 加 +`-fpermissive`:GCC 官方为这类诊断留的降级开关,clang 静默忽略该 flag,一条声明同时 +喂两条腿。 + +### 5.4 generated_files 的写法边界(评审意见引出) + +shim 最初是单行 `\n` 转义串,评审认为不可读。改写时确认了两条边界: + +- Lua 的 `..` 拼接**不被 mcpp 段解析器支持**(`malformed mcpp segment near key + 'string'`),尽管它能过 Lua 语法检查 —— 段解析器 ≠ Lua 解析器; +- `[==[ ]==]` 长括号在当前解析器下可用(compat.ffmpeg / compat.sdl2 的既有先例, + 且最新 mcpp 与钉住的 2026.8.10.3 均实测通过),shim 最终采用此形式。 + +## 6. 测试设计 + +`tests/examples/nanodbc`:**无数据库、无驱动**断言。CI 两样都没有,但有管理器本身 — +— 那正是 nanodbc 封装的层。连接不存在的 DSN 必然抛 `nanodbc::database_error`,该路径 +走穿句柄分配、DSN 查找、诊断格式化;IM002 前缀 + DM 原文证明诊断来自真实管理器而非 +空壳。成员按 `[target.'cfg(linux)']` 门控(windows/macOS 的 DM 链路在本索引 CI 不可验), +非 linux 编译为 no-op main。 + +## 7. 验证结论 + +- `mcpp test -p nanodbc`(冷构建,无任何宿主 include/lib 路径注入):**1 passed / 0 failed** + —— 全链路自包含,不依赖宿主 unixODBC。 +- lint:`check_mirror_urls` / `check_package_name`(两新包)、`check_cross_package_refs` / + `check_platform_version_parity`(全部描述符)均过。 +- `mcpp xpkg parse`:全部 98 个描述符在**钉住的 2026.8.10.3** 下通过(底线先行,语法其后)。 +- unixODBC 直编与 libtool 构建对拍:行为一致(见 §4.1)。 +- sha256 双次下载复核一致(两包)。 + +## 8. 后续可做(本 PR 不做) + +- CN 镜像:待维护者在 `mcpp-res` 建仓后,把两处纯字符串 url 换成 `{ GLOBAL, CN }` 表。 +- windows/macOS 成员测试:当前描述符已声明 `-lodbc32` / `-liodbc`,但索引 CI 不可验; + 若在真实 SDK 环境验证过,可放开成员门控。 +- SQLite ODBC 驱动打包:有了它就能做"真连库"的正向测试,而不只是错误路径。 diff --git a/README.md b/README.md index adb450e..00c8eef 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Two kinds of packages live here: | C-source compat (with `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) · [`compat.hiredis`](pkgs/c/compat.hiredis.lua) (the classic 1.2.0 — a 7-TU C build whose flat tarball headers get `hiredis/`-prefixed wrapper headers via `generated_files`, so consumers write `#include ` exactly like upstream's install layout) · [`compat.sqlite3`](pkgs/c/compat.sqlite3.lua) (plain C-source, no features: the single `sqlite3.c` amalgamation; 3.45.3, the final maintenance release of the most widely deployed 3.45.x line) · [`compat.libuv`](pkgs/c/compat.libuv.lua) (libuv 1.48.0 — the per-OS source sets transcribed from upstream's CMakeLists, because a `src/unix/*.c` glob would compile every OS's backend at once; linux/macos get explicit unix subsets, windows globs `src/win/*.c`) | | C++-source compat, one depending on the other | [`compat.abseil`](pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball. It also exposes **`protoc`** as a `kind = "bin"` target, so a consumer writing `tools = ["protoc"]` gets the compiler built for its own machine out of the same package it links — making a generator/runtime version mismatch inexpressible) · [`compat.re2`](pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua) (redis++ 1.3.13 — the sync client, 17 TUs + `patterns/redlock.cpp`, depends on `compat.hiredis`; the one header CMake would generate, `hiredis_features.h`, is snapshotted via `generated_files`, and the async/TLS TUs are left out so the base build stays a two-package pair. An `async` feature adds the libuv-backed `AsyncRedis` interface (the 9 async TUs + `compat.libuv`; `event_loop.cpp` runs `uv_run` on a background thread, and `` arrives through compat.hiredis' wrapper headers). Two versions, one on each side of the source-structure watershed, share this ONE source list: 1.3.13 (modern 17-TU layout) and 1.3.3 (pre-`redis_uri.cpp`/`redlock` 15-TU layout) — the union works because 1.3.3's TUs are a strict subset, so exactly two globs match nothing there (a warning, not an error; same trick as compat.catch2)) | | C++-source compat, zero-dep client + optional components | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs, so the **base build has zero external dependencies**: TLS off (the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and `IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op. Two optional features add on top: `server` (the four server TUs — `IXWebSocketServer`, `IXSocketServer`, `IXHttpServer`, `IXWebSocketProxyServer` — needing nothing external, and it **implies `zlib`** because upstream's server advertises permessage-deflate by default, which the transport negotiates regardless of the define) and `zlib` (deps `compat.zlib` and turns the codec into real per-message-deflate compression). The default-feature test brings its own minimal RFC 6455 echo server on loopback sockets (handshake, masking, fragmentation and close all exercised offline); a second member, `websocket-features`, runs a real `ix::WebSocketServer` and asserts the compression is observable on the wire — a 64 KiB repeated payload round-trips with `wireSize` = 80) | +| Database client + the driver manager it needs, built from source | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua) (nanodbc 2.14.0, frozen upstream — one TU over the platform ODBC driver manager. Two fixes make the four-year-old source compile and RUN here: a force-included `char_traits` shim for libc++ (the standard's own customization point, guarded on `_LIBCPP_VERSION` so libstdc++/MSVC are untouched; and note `-include` reaches C++ TUs only through `cxxflags`, never `cflags`), and a per-platform answer to the manager itself — windows links the SDK's odbc32, macOS the OS's iODBC, while linux takes `compat.unixodbc` because mcpp's runtime closure rejects a NEEDED `libodbc.so.2` that only the host has. The test asserts the manager's own diagnostics surface through the wrapper — including nanodbc's frozen off-by-one that drops the last SQL-state character) · [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua) (unixODBC 2.3.14, Shape E over A — DM + odbcinst + ini/log/lst + libltdl compiled statically into one `odbc` target, exactly upstream's libodbc.a symbol set, so the consumer carries no `libodbc.so.2` NEEDED at all. The one non-obvious piece is libtool-free ltdl wiring: `-DLTDLOPEN=libltdlc` plus a generated `lt_libltdlc_LTX_preloaded_symbols` table (reconstructed from the libtool object's relocations) registers the dlopen loader. The frozen `config.h` merges ltdl's own configure output into the top-level one — ltdl sources never read the clashing identification macros, and the merge sidesteps a quoted `-DLT_CONFIG_H` that does not survive the pipeline. Verified against the libtool build of the same tarball: identical IM002 error path and identical `lt_dlopen` behaviour) | | header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | | header-only, nothing to gate | [`compat.CLI11`](pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) · [`compat.gtl`](pkgs/c/compat.gtl.lua) (Greg's Template Library — the Swiss-table `flat_hash_map` family plus btrees and a bit_vector. `*/include` exactly, not the tarball root: `tests/` and `examples/` carry headers of their own, and naming `include/` is what upstream's INTERFACE target exposes, so a consumer cannot accidentally resolve into test code) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua) (the reference implementation of the proposed `std::hive`; the whole library is one file at the tarball root, so `*` plus an anchor TU is the entire package. Untagged upstream, so the version is a DATE over a commit archive — the compat.khrplatform precedent) | | single-header library + a GENERATED implementation TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua) (two stb-style headers where the implementation hides behind `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION`. Upstream ships no `.c` — its examples define the macros inline — so the package generates one that instantiates BOTH halves once. That is what turns a header drop into something linkable, and it moves the duplicate-symbol hazard from every consumer to a single place: consumers must NOT define those macros again, and the test links `nsvgParse` and `nsvgRasterize` together precisely so a package that instantiated only one half fails here rather than downstream) · [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua) (VMA 3.4.0, same shape but the generated TU also has to make a POLICY choice. VMA defaults to `VMA_STATIC_VULKAN_FUNCTIONS 1`, which references `vkBindBufferMemory2` and seven siblings by name — eight undefined symbols against a headers-only dep. Pulling `compat.vulkan` to satisfy them would force a Vulkan loader on every consumer of a memory allocator and fight anyone dispatching through volk, so the generated TU selects the dynamic path instead and VMA resolves everything through `VmaVulkanFunctions`. Note the implementation is C++ despite the C-shaped API, so the generated file is `.cpp`) | diff --git a/README.zh-CN.md b/README.zh-CN.md index 09d905d..a6fce19 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -38,6 +38,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) · [`compat.hiredis`](pkgs/c/compat.hiredis.lua)(经典 1.2.0 —— 7 个 C TU;tarball 平铺头经 `generated_files` 补 `hiredis/` 前缀薄包装头,消费者可写 `#include `,与上游安装布局一致) · [`compat.sqlite3`](pkgs/c/compat.sqlite3.lua)(纯 C 源码、无 feature:单一 `sqlite3.c` amalgamation;3.45.3,部署最广的 3.45.x 线) · [`compat.libuv`](pkgs/c/compat.libuv.lua)(libuv 1.48.0 —— 逐 OS 源清单转录自上游 CMakeLists,因为 `src/unix/*.c` 通配会一次编进所有 OS 的后端;linux/macos 显式列 unix 子集,windows 用 `src/win/*.c` glob) | | C++ 源码 compat(彼此依赖) | [`compat.abseil`](pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时;还以 `kind = "bin"` target 暴露 **`protoc`**,消费者写 `tools = ["protoc"]` 即可从「自己链接的那个包」拿到为本机构建的编译器,使生成器与运行时的版本错配无法表达) · [`compat.re2`](pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua)(redis++ 1.3.13 —— 同步客户端,17 TU + `patterns/redlock.cpp`,依赖 `compat.hiredis`;CMake 唯一会生成的头 `hiredis_features.h` 用 `generated_files` 快照,async/TLS TU 不收,基座保持两包成对。`async` feature 补齐 libuv 版 `AsyncRedis` 接口(9 个 async TU + `compat.libuv`;`event_loop.cpp` 在后台线程跑 `uv_run`,`` 经 compat.hiredis 的包装头到达)。两个版本分处源码结构分水岭两侧,共享同一份源列表:1.3.13(现代 17-TU 布局)与 1.3.3(缺 `redis_uri.cpp`/`redlock` 的 15-TU 旧布局)—— 并集之所以成立,是因为 1.3.3 的 TU 是 1.3.13 的严格子集,恰好两个 glob 在 1.3.3 上零命中(仅警告,非错误;与 compat.catch2 同款手法)) | | C++ 源码 compat(零依赖客户端 + 可选组件) | [`compat.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端,**基座零外部依赖**:TLS 关闭(OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),`IXWEBSOCKET_USE_ZLIB` 不定义(gzip codec 编译为 no-op)。两个可选 feature 在基座上叠加:`server`(4 个 server TU —— `IXWebSocketServer`/`IXSocketServer`/`IXHttpServer`/`IXWebSocketProxyServer`,零新增外部依赖,且 **implies `zlib`** —— 因为上游 server 默认就宣称 permessage-deflate,而 transport 的协商不受宏门控)与 `zlib`(依赖 `compat.zlib`,把 codec 变成真正的 permessage-deflate 压缩)。默认构建的测试自带基于 loopback 原始 socket 的最小 RFC 6455 echo server(握手/掩码/分片/关闭全部离线实测);第二个成员 `websocket-features` 跑真实的 `ix::WebSocketServer`,并断言压缩在线路上可观测 —— 64 KiB 重复载荷往返,`wireSize` = 80) | +| 数据库客户端 + 源码构建的驱动管理器 | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua)(nanodbc 2.14.0,上游已冻结 —— 单 TU 封装平台 ODBC 驱动管理器。两处修复让这份四年前的源码在此可编译、可运行:一个 force-include 的 `char_traits` 补丁头(标准留给用户的定制点,以 `_LIBCPP_VERSION` 为界,不影响 libstdc++/MSVC;注意 `-include` 只能经 `cxxflags` 到达 C++ TU,`cflags` 够不着),以及对驱动管理器本身的分平台答案 —— windows 链 SDK 的 odbc32、macOS 链系统自带的 iODBC,linux 则依赖 `compat.unixodbc`,因为 mcpp 的运行时闭包检查不接受只有宿主才有的 `libodbc.so.2`。测试断言管理器自身的诊断能穿过封装层 —— 包括 nanodbc 已冻结的、会把 SQL state 末字符截掉的 off-by-one)· [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua)(unixODBC 2.3.14,E 叠 A 形态 —— DM + odbcinst + ini/log/lst + libltdl 静态编入单一 `odbc` 目标,与上游 libodbc.a 符号集一致,消费者不带任何 `libodbc.so.2` NEEDED。唯一非常规之处是无 libtool 的 ltdl 接线:`-DLTDLOPEN=libltdlc` 加一张生成的 `lt_libltdlc_LTX_preloaded_symbols` 表(从 libtool 目标文件的重定位记录还原)注册 dlopen loader。冻结的 `config.h` 把 ltdl 自己的 configure 输出合并进顶层(冲突宏 ltdl 源码并不读),绕开了无法在管道中幸存的带引号 `-DLT_CONFIG_H`。已与同 tarball 的 libtool 构建对比验证:IM002 错误路径与 `lt_dlopen` 行为完全一致) | | header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | | header-only(无可门控组件) | [`compat.CLI11`](pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) · [`compat.gtl`](pkgs/c/compat.gtl.lua)(Greg's Template Library —— Swiss-table 的 `flat_hash_map` 家族,外加 btree 与 bit_vector。只取 `*/include` 而非 tarball 根:`tests/` 与 `examples/` 各自带头文件,而 `include/` 正是上游 INTERFACE target 暴露的范围,消费者不会误解析到测试代码) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua)(提案中 `std::hive` 的参考实现;整库就是 tarball 根下一个文件,故全包即 `*` 加一个 anchor TU。上游不打 tag,于是版本用 commit 归档上的日期 —— 沿用 compat.khrplatform 的先例) | | 单头库 + **生成**实现 TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua)(两个 stb 风格头文件,实现藏在 `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION` 之后。上游不提供 `.c` —— 其示例是就地 define 宏 —— 故本包生成一个,把两半各实例化一次。这才让「一堆头文件」变成可链接的包,并把重复符号的风险从每个消费者收敛到唯一一处:消费者不得再次定义这两个宏。测试刻意同时链接 `nsvgParse` 与 `nsvgRasterize`,使「只实例化了一半」的包在此处就失败,而不是流到下游)· [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua)(VMA 3.4.0,同一形态,但生成的 TU 还得做一个**策略**选择。VMA 默认 `VMA_STATIC_VULKAN_FUNCTIONS 1`,会按名字引用 `vkBindBufferMemory2` 等八个符号 —— 对「只依赖头文件」而言就是八个未定义引用。为此拉入 `compat.vulkan` 是错的:那会逼所有用内存分配器的消费者都链上 Vulkan loader,并与通过 volk 自行分发的项目冲突。故生成的 TU 改走动态路径,VMA 一律经 `VmaVulkanFunctions` 解析。注意其 API 形如 C 但实现是 C++,故生成文件为 `.cpp`)| diff --git a/mcpp.toml b/mcpp.toml index d6f42d0..eab4d02 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -87,6 +87,7 @@ members = [ "tests/examples/gtl", "tests/examples/miniaudio", "tests/examples/nanosvg", + "tests/examples/nanodbc", "tests/examples/plf-hive", "tests/examples/spirv-reflect", "tests/examples/vulkan-memory-allocator", diff --git a/pkgs/c/compat.nanodbc.lua b/pkgs/c/compat.nanodbc.lua new file mode 100644 index 0000000..04360e2 --- /dev/null +++ b/pkgs/c/compat.nanodbc.lua @@ -0,0 +1,153 @@ +-- Form B inline descriptor for nanodbc — a small C++ wrapper for the native +-- C ODBC API. One header, one implementation TU; MIT; frozen upstream +-- (v2.14.0, March 2022, no commits since), so a single version is the whole +-- story. +-- +-- SHAPE: A (source compat). `*/nanodbc/nanodbc.cpp` is the entire library; +-- `include_dirs = { "*" }` exposes from the tarball root. +-- +-- THE DRIVER MANAGER PER PLATFORM. nanodbc is a wrapper around the platform's +-- ODBC driver manager and cannot run without one: +-- +-- * windows — the SDK's odbc32 is always there; just link it. +-- * macOS — iODBC is part of the OS; just link it. +-- * linux — the manager (unixODBC) is a third-party library, and mcpp's +-- runtime closure rejects binaries whose NEEDED libodbc.so.2 only exists +-- on the host. compat.unixodbc therefore builds the manager STATICALLY +-- from source, and this package takes it as a dep; the produced binary +-- carries no libodbc.so.2 NEEDED entry at all. (The linux leg used to say +-- "-lodbc" here; the closure check is what killed that.) +-- +-- Note the actual database DRIVERS are still dlopen()ed by the manager at +-- runtime from the host's odbcinst.ini, as with any unixODBC install. +-- +-- FROZEN-UPSTREAM FIX: nanodbc.cpp calls std::char_traits::length() +-- (SQLCHAR = unsigned char) at four sites. libc++ never carried that +-- specialization, and C++23 made the primary template declaration-only, so +-- the four-year-old source no longer compiles under the mcpp toolchain. +-- mcpp_nanodbc_char_traits.h adds the specialization the standard RESERVES +-- for users (char_traits is the designated customization point for +-- non-standard character types), guarded on _LIBCPP_VERSION so libstdc++ and +-- MSVC STL -- which still ship their own -- are untouched. It is force- +-- included (-include) only while building THIS package; consumers including +-- never see it. +package = { + spec = "1", + namespace = "compat", + name = "nanodbc", + description = "Small C++ wrapper for the native C ODBC API", + licenses = {"MIT"}, + repo = "https://github.com/nanodbc/nanodbc", + type = "package", + + xpm = { + linux = { + ["2.14.0"] = { + -- Plain-string form: no gitcode mcpp-res mirror yet (no write + -- access from this contributor); lint allows it and CN users + -- fall back to the GLOBAL source. Maintainer can add the CN + -- mirror later, same as compat.libmysqlclient. + url = "https://github.com/nanodbc/nanodbc/archive/refs/tags/v2.14.0.tar.gz", + sha256 = "56228372042b689beccd96b0ac3476643ea85b3f57b3f23fb11ca4314e68b9a5", + }, + }, + macosx = { + ["2.14.0"] = { + url = "https://github.com/nanodbc/nanodbc/archive/refs/tags/v2.14.0.tar.gz", + sha256 = "56228372042b689beccd96b0ac3476643ea85b3f57b3f23fb11ca4314e68b9a5", + }, + }, + windows = { + ["2.14.0"] = { + url = "https://github.com/nanodbc/nanodbc/archive/refs/tags/v2.14.0.tar.gz", + sha256 = "56228372042b689beccd96b0ac3476643ea85b3f57b3f23fb11ca4314e68b9a5", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + include_dirs = { "*", "mcpp_generated/include" }, + -- See the FROZEN-UPSTREAM FIX note above. The header is a no-op + -- outside libc++, and -include only affects building this package. + -- cxxflags, not cflags -- this package has no C sources. + cxxflags = { "-include", "mcpp_nanodbc_char_traits.h" }, + generated_files = { + ["mcpp_generated/include/mcpp_nanodbc_char_traits.h"] = [==[ +// libc++ lacks std::char_traits (SQLCHAR); nanodbc.cpp uses its ::length(). +// must come FIRST: force-inclusion runs before any libc++ header, so +// _LIBCPP_VERSION is only defined after has been seen. +#ifndef MCPP_NANODBC_CHAR_TRAITS_H +#define MCPP_NANODBC_CHAR_TRAITS_H +#include +#include +#include +#if defined(_LIBCPP_VERSION) +namespace std { +template <> +struct char_traits { + using char_type = unsigned char; + using int_type = int; + using off_type = streamoff; + using pos_type = fpos; + using state_type = mbstate_t; + static void assign(char_type& c1, char_type c2) noexcept { c1 = c2; } + static bool eq(char_type c1, char_type c2) noexcept { return c1 == c2; } + static bool lt(char_type c1, char_type c2) noexcept { return c1 < c2; } + static int compare(const char_type* s1, const char_type* s2, size_t n) { + return n == 0 ? 0 : memcmp(s1, s2, n); + } + static size_t length(const char_type* s) { + return strlen(reinterpret_cast(s)); + } + static const char_type* find(const char_type* s, size_t n, const char_type& a) { + return static_cast(memchr(s, a, n)); + } + static char_type* move(char_type* s1, const char_type* s2, size_t n) { + return static_cast(memmove(s1, s2, n)); + } + static char_type* copy(char_type* s1, const char_type* s2, size_t n) { + return static_cast(memcpy(s1, s2, n)); + } + static char_type* assign(char_type* s, size_t n, char_type a) { + return static_cast(memset(s, a, n)); + } + static int_type not_eof(int_type c) noexcept { return eq_int_type(c, eof()) ? 0 : c; } + static char_type to_char_type(int_type c) noexcept { return static_cast(c); } + static int_type to_int_type(char_type c) noexcept { return static_cast(c); } + static bool eq_int_type(int_type c1, int_type c2) noexcept { return c1 == c2; } + static int_type eof() noexcept { return static_cast(EOF); } +}; +} // namespace std +#endif +#endif +]==], + }, + sources = { "*/nanodbc/nanodbc.cpp" }, + targets = { ["nanodbc"] = { kind = "lib" } }, + deps = { }, + linux = { + -- The driver manager, built from source; see the header comment. + deps = { ["compat.unixodbc"] = "2.3.14" }, + -- With string_view support on (C++17+), NANODBC_INSTANTIATE_BIND_ + -- STRINGS(std::string) and (std::string_view) emit identical + -- explicit instantiation DEFINITIONS (both reduce to value_type + -- = char; likewise the u16 pair). One TU, identical + -- specializations, so the semantics are unaffected -- but the + -- standard calls duplicate explicit instantiation definitions + -- ill-formed, and GCC (the linux default leg) now rejects them + -- where clang accepts silently. -fpermissive is GCC's own + -- downgrade for exactly this diagnostic; clang ignores the flag. + cxxflags = { "-fpermissive" }, + }, + macosx = { + -- iODBC driver manager, part of the OS. + ldflags = { "-liodbc" }, + }, + windows = { + -- Windows SDK ODBC driver manager. + ldflags = { "-lodbc32" }, + }, + }, +} diff --git a/pkgs/c/compat.unixodbc.lua b/pkgs/c/compat.unixodbc.lua new file mode 100644 index 0000000..3ed961e --- /dev/null +++ b/pkgs/c/compat.unixodbc.lua @@ -0,0 +1,853 @@ +-- Form B inline descriptor for unixODBC — the ODBC driver manager (libodbc, +-- with libodbcinst folded in), built from source on linux. +-- +-- WHY THIS PACKAGE EXISTS: compat.nanodbc (and any other ODBC client) needs +-- / to compile and a driver manager to link and run. On +-- Windows the SDK supplies odbc32; on macOS iODBC is part of the OS. On linux +-- the manager is a third-party library, and mcpp's runtime-closure check +-- rejects a binary whose NEEDED libodbc.so.2 can only come from the host +-- (its PT_INTERP is a private loader; the host's /usr/lib is not consulted). +-- Linking the manager STATICALLY from source removes the NEEDED entry +-- entirely, which is what makes an ODBC client runnable under mcpp at all. +-- This is the same conclusion conan (odbc/2.3.x) and vcpkg (unixodbc) reach: +-- linux ODBC gets built, not assumed. +-- +-- SHAPE: E over A — plain C sources plus a frozen configure snapshot, the +-- compat.curl / compat.sdl2 treatment. The one upstream tarball builds with +-- ZERO configure/make at consume time: +-- +-- * DriverManager/*.c, odbcinst/*.c, ini/*.c, log/*.c, lst/*.c compile +-- verbatim (their dir contents match the Makefile.am lists exactly). +-- * mcpp_generated/include/config.h is the frozen top-level configure +-- output, MERGED with the macros from libltdl/config.h that the top-level +-- one lacks (name clashes excluded -- ltdl sources never read them). One +-- config.h for everything: ltdl's LT_CONFIG_H override mechanism would +-- need a quoted define that does not survive the descriptor -> command- +-- line pipeline, and merging makes it unnecessary. +-- * mcpp_generated/include/unixodbc.h is the frozen unixodbc.h.in output. +-- It is a PUBLIC header (include/sqltypes.h includes it), which is why +-- the generated dir is on the public include path at all. +-- * -D_GNU_SOURCE: upstream relies on configure's default gnu dialect; +-- under strict -std=c11 strdup/strlcpy/intptr_t disappear. +-- +-- THE LIBLTDL PART IS THE ONE NON-OBVIOUS PIECE. The DM dlopen()s drivers +-- through libltdl, and libtool normally wires the dlopen loader in with +-- -dlpreopen. Without libtool, two things reproduce that wiring exactly: +-- +-- 1. -DLTDLOPEN=libltdlc makes ltdl.c preload the symbol table named +-- lt_libltdlc_LTX_preloaded_symbols. +-- 2. mcpp_generated/ltdl_preload.c provides that table — byte-identical in +-- effect to the one libtool generates (reconstructed from the .o's +-- relocations), registering the dlopen loader's get_vtable. +-- +-- Verified against the libtool-built static libodbc.a from the same +-- tarball: identical behaviour on handle alloc, driver enumeration, the +-- IM002 error path, AND lt_dlopen of a real .so through the registered +-- dlopen loader. +-- +-- ONE TARGET, NOT TWO. Upstream ships libodbc and libodbcinst separately; +-- here everything (DM + odbcinst + ini/log/lst + ltdl) lands in the single +-- `odbc` target, which is exactly the symbol set upstream's libodbc.a already +-- carries (it absorbs the convenience libs). Driver installers that want +-- SQLInstallDriverEx find it in the same archive. +-- +-- PATHS: the frozen config keeps configure's /usr/local defaults +-- (SYSTEM_FILE_PATH=/usr/local/etc for odbc.ini). ODBCSYSINI / ODBCINI env +-- vars override at runtime, same as any vanilla unixODBC build. +-- +-- LINUX ONLY: windows uses the SDK's odbc32, macOS has iODBC in the OS; only +-- linux lacks a system driver manager. Declared for linux alone rather than +-- pretending otherwise (the compat.glx-runtime shape). +package = { + spec = "1", + namespace = "compat", + name = "unixodbc", + description = "unixODBC driver manager (libodbc with libodbcinst folded in), full source build", + licenses = {"LGPL-2.1-or-later"}, + repo = "https://www.unixodbc.org", + type = "package", + + xpm = { + linux = { + ["2.3.14"] = { + -- Plain-string form: no gitcode mcpp-res mirror yet (no write + -- access from this contributor); lint allows it and CN users + -- fall back to the upstream source. The dist tarball (not the + -- github archive) is required: only it carries the + -- pre-bootstrapped libltdl/. + url = "https://www.unixodbc.org/unixODBC-2.3.14.tar.gz", + sha256 = "4e2814de3e01fc30b0b9f75e83bb5aba91ab0384ee951286504bb70205524771", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { + "*/include", -- sql.h, sqlext.h, sqltypes.h, sqlucode.h, odbcinst.h + "*/libltdl", -- and + "*/libltdl/libltdl", -- lt__private.h and friends (build-internal) + "mcpp_generated/include", + }, + cflags = { + "-D_GNU_SOURCE", + -- libltdl wiring, see the header comment. + "-DLTDL", + "-DLTDLOPEN=libltdlc", + }, + sources = { + "*/DriverManager/*.c", + "*/odbcinst/*.c", + "*/ini/*.c", + "*/log/*.c", + "*/lst/*.c", + "*/libltdl/ltdl.c", + "*/libltdl/lt__alloc.c", + "*/libltdl/lt_dlloader.c", + "*/libltdl/lt_error.c", + "*/libltdl/slist.c", + "*/libltdl/loaders/preopen.c", + "*/libltdl/loaders/dlopen.c", + "mcpp_generated/ltdl_preload.c", + }, + targets = { ["odbc"] = { kind = "lib" } }, + deps = { }, + generated_files = { + ["mcpp_generated/ltdl_preload.c"] = [==[ +#include +extern lt_dlvtable *dlopen_LTX_get_vtable (lt_user_data loader_data); +const lt_dlsymlist lt_libltdlc_LTX_preloaded_symbols[] = { + { "libltdlc", (void *) 0 }, + { "dlopen.a", (void *) 0 }, + { "dlopen_LTX_get_vtable", (void *) &dlopen_LTX_get_vtable }, + { 0, (void *) 0 } +}; +]==], + ["mcpp_generated/include/unixodbc.h"] = [==[ +/* unixodbc.h. Generated from unixodbc.h.in by configure. */ +/* Preprocessor constants for unixODBC */ + +/* Define to 1 if `long long' is available */ +#define HAVE_LONG_LONG 1 + +/* Define to 1 if the header file is present */ +#define HAVE_PWD_H 1 + +/* Define to 1 if the header file is present */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if the header file is present */ +#define HAVE_UNISTD_H 1 + +/* Define to the value of sizeof(long) */ +#define SIZEOF_LONG_INT 8 +]==], + ["mcpp_generated/include/config.h"] = [==[ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Encoding to use for CHAR */ +#define ASCII_ENCODING "auto-search" + +/* Install bindir */ +#define BIN_PREFIX "/usr/local/bin" + +/* Use a semaphore to allow ODBCConfig to display running counts */ +/* #undef COLLECT_STATS */ + +/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP + systems. This function is required for `alloca.c' support on those systems. + */ +/* #undef CRAY_STACKSEG_END */ + +/* Define to 1 if using `alloca.c'. */ +/* #undef C_ALLOCA */ + +/* Enable versioned cursor library */ +/* #undef DEFINE_CURSOR_LIB_VER */ + +/* Lib directory */ +#define DEFLIB_PATH "/usr/local/lib" + +/* Using perdriver iconv */ +/* #undef ENABLE_DRIVER_ICONV */ + +/* Using ini cacheing */ +#define ENABLE_INI_CACHING /**/ + +/* Install exec_prefix */ +#define EXEC_PREFIX "/usr/local" + +/* Disable the precise but slow checking of the validity of handles */ +/* #undef FAST_HANDLE_VALIDATE */ + +/* Define to 1 if you have `alloca', as a function or macro. */ +#define HAVE_ALLOCA 1 + +/* Define to 1 if you have and it should be used (not on Ultrix). + */ +#define HAVE_ALLOCA_H 1 + +/* Define to 1 if you have the `argz_add' function. */ +#define HAVE_ARGZ_ADD 1 + +/* Define to 1 if you have the `argz_append' function. */ +#define HAVE_ARGZ_APPEND 1 + +/* Define to 1 if you have the `argz_count' function. */ +#define HAVE_ARGZ_COUNT 1 + +/* Define to 1 if you have the `argz_create_sep' function. */ +#define HAVE_ARGZ_CREATE_SEP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ARGZ_H 1 + +/* Define to 1 if you have the `argz_insert' function. */ +#define HAVE_ARGZ_INSERT 1 + +/* Define to 1 if you have the `argz_next' function. */ +#define HAVE_ARGZ_NEXT 1 + +/* Define to 1 if you have the `argz_stringify' function. */ +#define HAVE_ARGZ_STRINGIFY 1 + +/* Define to 1 if you have the `atoll' function. */ +#define HAVE_ATOLL 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +#define HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if you have the `closedir' function. */ +#define HAVE_CLOSEDIR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_CRYPT_H 1 + +/* Define to 1 if you have the declaration of `cygwin_conv_path', and to 0 if + you don't. */ +/* #undef HAVE_DECL_CYGWIN_CONV_PATH */ + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the GNU dld library. */ +/* #undef HAVE_DLD */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DLD_H */ + +/* Define to 1 if you have the `dlerror' function. */ +#define HAVE_DLERROR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DL_H */ + +/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ +/* #undef HAVE_DOPRNT */ + +/* Define if you have the _dyld_func_lookup function. */ +/* #undef HAVE_DYLD */ + +/* Add editline support */ +/* #undef HAVE_EDITLINE */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_EDITLINE_READLINE_H */ + +/* Define to 1 if you have the `endpwent' function. */ +#define HAVE_ENDPWENT 1 + +/* Define to 1 if the system has the type `error_t'. */ +#define HAVE_ERROR_T 1 + +/* Define to 1 if you have the `fseeko' function. */ +#define HAVE_FSEEKO 1 + +/* Define to 1 if you have the `ftime' function. */ +#define HAVE_FTIME 1 + +/* Define to 1 if you have the `ftok' function. */ +/* #undef HAVE_FTOK */ + +/* Define to 1 if you have the four-argument form of getpwuid_r(). */ +/* #undef HAVE_FUNC_GETPWUID_R_4 */ + +/* Define to 1 if you have the five-argument form of getpwuid_r(). */ +/* #undef HAVE_FUNC_GETPWUID_R_5 */ + +/* Define to 1 if you have the `getpid' function. */ +#define HAVE_GETPID 1 + +/* Define to 1 if you have the `getpwuid' function. */ +#define HAVE_GETPWUID 1 + +/* Define to 1 if you have some form of getpwuid_r(). */ +/* #undef HAVE_GETPWUID_R */ + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `getuid' function. */ +#define HAVE_GETUID 1 + +/* Define if you have the iconv() function. */ +#define HAVE_ICONV 1 + +/* Define to 1 if the system has the type `intptr_t'. */ +#define HAVE_INTPTR_T 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define if you have and nl_langinfo(CODESET). */ +#define HAVE_LANGINFO_CODESET 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LANGINFO_H 1 + +/* Add -lcrypt to lib list */ +#define HAVE_LIBCRYPT /**/ + +/* Define if you have the libdl library or equivalent. */ +#define HAVE_LIBDL 1 + +/* Define if libdlloader will be built on this platform */ +#define HAVE_LIBDLLOADER 1 + +/* Use the -lpth thread library */ +/* #undef HAVE_LIBPTH */ + +/* Use -lpthread threading lib */ +#define HAVE_LIBPTHREAD 1 + +/* Use the -lthread threading lib */ +/* #undef HAVE_LIBTHREAD */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LOCALE_H 1 + +/* Use rentrant version of localtime */ +#define HAVE_LOCALTIME_R 1 + +/* Define if you have long long */ +#define HAVE_LONG_LONG 1 + +/* Define this if a modern libltdl is already installed */ +/* #undef HAVE_LTDL */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MACH_O_DYLD_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MALLOC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MSQL_H */ + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +/* #undef HAVE_NDIR_H */ + +/* Define to 1 if you have the `nl_langinfo' function. */ +#define HAVE_NL_LANGINFO 1 + +/* Define to 1 if you have the `opendir' function. */ +#define HAVE_OPENDIR 1 + +/* Define if libtool can extract symbol lists from object files. */ +#define HAVE_PRELOADED_SYMBOLS 1 + +/* Define to 1 if the system has the type `ptrdiff_t'. */ +#define HAVE_PTRDIFF_T 1 + +/* Define to 1 if you have the `putenv' function. */ +#define HAVE_PUTENV 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PWD_H 1 + +/* Define to 1 if you have the `readdir' function. */ +#define HAVE_READDIR 1 + +/* Add readline support */ +/* #undef HAVE_READLINE */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_READLINE_HISTORY_H */ + +/* Use the scandir lib */ +/* #undef HAVE_SCANDIR */ + +/* Define to 1 if you have the `semget' function. */ +/* #undef HAVE_SEMGET */ + +/* Define to 1 if you have the `semop' function. */ +/* #undef HAVE_SEMOP */ + +/* Define to 1 if you have the `setenv' function. */ +#define HAVE_SETENV 1 + +/* Define to 1 if you have the `setlocale' function. */ +#define HAVE_SETLOCALE 1 + +/* Define to 1 if you have the `setvbuf' function. */ +#define HAVE_SETVBUF 1 + +/* Define if you have the shl_load function. */ +/* #undef HAVE_SHL_LOAD */ + +/* Define to 1 if you have the `shmget' function. */ +/* #undef HAVE_SHMGET */ + +/* Define to 1 if you have the `snprintf' function. */ +#define HAVE_SNPRINTF 1 + +/* Define to 1 if you have the `socket' function. */ +#define HAVE_SOCKET 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDDEF_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strcasecmp' function. */ +#define HAVE_STRCASECMP 1 + +/* Define to 1 if you have the `strchr' function. */ +#define HAVE_STRCHR 1 + +/* Define to 1 if you have the `strdup' function. */ +#define HAVE_STRDUP 1 + +/* Define to 1 if you have the `stricmp' function. */ +/* #undef HAVE_STRICMP */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcat' function. */ +#define HAVE_STRLCAT 1 + +/* Define to 1 if you have the `strlcpy' function. */ +#define HAVE_STRLCPY 1 + +/* Define to 1 if you have the `strncasecmp' function. */ +#define HAVE_STRNCASECMP 1 + +/* Define to 1 if you have the `strnicmp' function. */ +/* #undef HAVE_STRNICMP */ + +/* Define to 1 if you have the `strstr' function. */ +#define HAVE_STRSTR 1 + +/* Define to 1 if you have the `strtol' function. */ +#define HAVE_STRTOL 1 + +/* Define to 1 if you have the `strtoll' function. */ +#define HAVE_STRTOLL 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYNCH_H */ + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_DL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_MALLOC_H */ + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SEM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIMEB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the `time' function. */ +#define HAVE_TIME 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_VARARGS_H */ + +/* Define to 1 if you have the `vprintf' function. */ +#define HAVE_VPRINTF 1 + +/* Define to 1 if you have the `vsnprintf' function. */ +#define HAVE_VSNPRINTF 1 + +/* This value is set to 1 to indicate that the system argz facility works */ +#define HAVE_WORKING_ARGZ 1 + +/* Define as const if the declaration of iconv() needs const. */ +#define ICONV_CONST + +/* Install includedir */ +#define INCLUDE_PREFIX "/usr/local/include" + +/* Lib directory */ +#define LIB_PREFIX "/usr/local/lib" + +/* Define if the OS needs help to load dependent libraries for dlopen(). */ +/* #undef LTDL_DLOPEN_DEPLIBS */ + +/* Define to the system default library search path. */ +#define LT_DLSEARCH_PATH "/lib:/usr/lib:/usr/lib/fdk-aac/:/usr/lib64/fdk-aac/:/usr/lib64/llvm21/lib64:/usr/lib64/pipewire-0.3/jack/" + +/* The archive extension */ +#define LT_LIBEXT "a" + +/* The archive prefix */ +#define LT_LIBPREFIX "lib" + +/* Define to the extension used for runtime loadable modules, say, ".so". */ +#define LT_MODULE_EXT ".so" + +/* Define to the name of the environment variable that determines the run-time + module search path. */ +#define LT_MODULE_PATH_VAR "LD_LIBRARY_PATH" + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#define LT_OBJDIR ".libs/" + +/* Define to the shared library suffix, say, ".dylib". */ +/* #undef LT_SHARED_EXT */ + +/* Define to the shared archive member specification, say "(shr.o)". */ +/* #undef LT_SHARED_LIB_MEMBER */ + +/* ODBC driver search path */ +/* #undef MODULEDIR */ + +/* Define if you need semundo union */ +/* #undef NEED_SEMUNDO_UNION */ + +/* Define if dlsym() requires a leading underscore in symbol names. */ +/* #undef NEED_USCORE */ + +/* Using OSX */ +/* #undef OSXHEADER */ + +/* Name of package */ +#define PACKAGE "unixODBC" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "nick@unixodbc.org" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "unixODBC" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "unixODBC 2.3.14" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "unixODBC" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "2.3.14" + +/* Platform is 64 bit */ +#define PLATFORM64 /**/ + +/* Install prefix */ +#define PREFIX "/usr/local" + +/* Using QNX */ +/* #undef QNX_LIBLTDL */ + +/* Shared lib extension */ +#define SHLIBEXT ".so" + +/* The size of `long', as computed by sizeof. */ +#define SIZEOF_LONG 8 + +/* The size of `long int', as computed by sizeof. */ +#define SIZEOF_LONG_INT 8 + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at runtime. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown */ +/* #undef STACK_DIRECTION */ + +/* Filename to use for ftok */ +#define STATS_FTOK_NAME "odbc.ini" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* don't include unixODBC prefix in driver error messages */ +#define STRICT_ODBC_ERROR /**/ + +/* System file path */ +#define SYSTEM_FILE_PATH "/usr/local/etc" + +/* Lib path */ +#define SYSTEM_LIB_PATH "/usr/local/lib" + +/* Define to 1 if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Define to 1 if your declares `struct tm'. */ +/* #undef TM_IN_SYS_TIME */ + +/* Encoding to use for UNICODE */ +#define UNICODE_ENCODING "auto-search" + +/* Flag that we are not using another DM */ +#define UNIXODBC /**/ + +/* We are building inside the unixODBC source tree */ +#define UNIXODBC_SOURCE /**/ + +/* Version number of package */ +#define VERSION "2.3.14" + +/* Work with IBM drivers that use 32 bit handles on 64 bit platforms */ +/* #undef WITH_HANDLE_REDIRECT */ + +/* Using shared env handle */ +/* #undef WITH_SHARDENV */ + +/* Using utf8 ini encoding */ +/* #undef WITH_UTF8_INI */ + +/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a + `char[]'. */ +/* #undef YYTEXT_POINTER */ + +/* Build flag for AIX */ +/* #undef _ALL_SOURCE */ + +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */ +/* #undef _LARGEFILE_SOURCE */ + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* Build flag for AIX */ +/* #undef _LONG_LONG */ + +/* Build flag for AIX */ +/* #undef _THREAD_SAFE */ + +/* Define so that glibc/gnulib argp.h does not typedef error_t. */ +/* #undef __error_t_defined */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to a type to use for 'error_t' if it is not otherwise available. */ +/* #undef error_t */ + +/* Define to `int' if doesn't define. */ +/* #undef gid_t */ + +/* Define to the type of a signed integer type wide enough to hold a pointer, + if such a type exists, and if the system does not define it. */ +/* #undef intptr_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* Define to `int' if doesn't define. */ +/* #undef uid_t */ + +/* ---- libltdl additions: macros from ltdl's own configure output that + the top-level one does not define. Name clashes are excluded; ltdl's + sources never read the clashing identification macros. ---- */ +/* config.h. Generated from config-h.in by configure. */ +/* config-h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if you have the `argz_add' function. */ + +/* Define to 1 if you have the `argz_append' function. */ + +/* Define to 1 if you have the `argz_count' function. */ + +/* Define to 1 if you have the `argz_create_sep' function. */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the `argz_insert' function. */ + +/* Define to 1 if you have the `argz_next' function. */ + +/* Define to 1 if you have the `argz_stringify' function. */ + +/* Define to 1 if you have the `closedir' function. */ + +/* Define to 1 if you have the declaration of `cygwin_conv_path', and to 0 if + you don't. */ +/* #undef HAVE_DECL_CYGWIN_CONV_PATH */ + +/* Define to 1 if you have the header file. */ + +/* Define if you have the GNU dld library. */ +/* #undef HAVE_DLD */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DLD_H */ + +/* Define to 1 if you have the `dlerror' function. */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DL_H */ + +/* Define if you have the _dyld_func_lookup function. */ +/* #undef HAVE_DYLD */ + +/* Define to 1 if the system has the type `error_t'. */ + +/* Define to 1 if you have the header file. */ + +/* Define if you have the libdl library or equivalent. */ + +/* Define if libdlloader will be built on this platform */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MACH_O_DYLD_H */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the `opendir' function. */ + +/* Define if libtool can extract symbol lists from object files. */ + +/* Define to 1 if you have the `readdir' function. */ + +/* Define if you have the shl_load function. */ +/* #undef HAVE_SHL_LOAD */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the `strlcat' function. */ + +/* Define to 1 if you have the `strlcpy' function. */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_DL_H */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the header file. */ + +/* Define to 1 if you have the header file. */ + +/* This value is set to 1 to indicate that the system argz facility works */ + +/* Define if the OS needs help to load dependent libraries for dlopen(). */ +/* #undef LTDL_DLOPEN_DEPLIBS */ + +/* Define to the system default library search path. */ + +/* The archive extension */ + +/* The archive prefix */ + +/* Define to the extension used for runtime loadable modules, say, ".so". */ + +/* Define to the name of the environment variable that determines the run-time + module search path. */ + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ + +/* Define to the shared library suffix, say, ".dylib". */ +/* #undef LT_SHARED_EXT */ + +/* Define to the shared archive member specification, say "(shr.o)". */ +/* #undef LT_SHARED_LIB_MEMBER */ + +/* Define if dlsym() requires a leading underscore in symbol names. */ +/* #undef NEED_USCORE */ + +/* Name of package */ + +/* Define to the address where bug reports for this package should be sent. */ + +/* Define to the full name of this package. */ + +/* Define to the full name and version of this package. */ + +/* Define to the one symbol short name of this package. */ + +/* Define to the home page for this package. */ + +/* Define to the version of this package. */ + +/* Define to 1 if you have the ANSI C header files. */ + +/* Version number of package */ + +/* Define so that glibc/gnulib argp.h does not typedef error_t. */ +/* #undef __error_t_defined */ + +/* Define to a type to use for 'error_t' if it is not otherwise available. */ +/* #undef error_t */ +]==], + }, + }, +} diff --git a/tests/examples/nanodbc/mcpp.toml b/tests/examples/nanodbc/mcpp.toml new file mode 100644 index 0000000..3ec54a5 --- /dev/null +++ b/tests/examples/nanodbc/mcpp.toml @@ -0,0 +1,20 @@ +# nanodbc test project: assert the ODBC driver manager round-trips through the +# wrapper. CI has no database and no ODBC driver, but it has the driver manager +# (unixODBC on linux), which is exactly the layer nanodbc wraps -- so the +# behavioral assertion drives the ERROR path: connecting to a DSN that does +# not exist must throw nanodbc::database_error carrying the manager's own +# diagnostics. +# +# Gated to linux: windows (-lodbc32, Windows SDK) and macOS (-liodbc) are +# declared in the descriptor for consumers but are not exercised by this +# index's CI -- the linux leg is the one where the system driver manager is +# guaranteed present. +[package] +name = "nanodbc-tests" +version = "0.1.0" + +[target.'cfg(linux)'.dependencies.compat] +nanodbc = "2.14.0" + +[target.'cfg(linux)'.build] +cxxflags = ["-DHAVE_NANODBC=1"] diff --git a/tests/examples/nanodbc/tests/error_path.cpp b/tests/examples/nanodbc/tests/error_path.cpp new file mode 100644 index 0000000..f25e7da --- /dev/null +++ b/tests/examples/nanodbc/tests/error_path.cpp @@ -0,0 +1,61 @@ +// compat.nanodbc — connect to a DSN that does not exist and assert nanodbc +// reports the driver manager's own diagnostics as a nanodbc::database_error. +// +// Deliberately DATABASE-FREE. A CI runner has no database server and no ODBC +// driver, so a successful connect asserts nothing that can hold everywhere. +// What IS guaranteed is the driver manager (unixODBC), which is the layer +// nanodbc wraps: the failed connect still walks handle allocation, the DSN +// lookup and the diagnostic formatting, and every one of those symbols comes +// from the compiled nanodbc.cpp — a half-linked package fails here. +#ifdef HAVE_NANODBC + +#include +import std; + +int main() { + bool ok = true; + auto check = [&](bool cond, std::string_view what) { + if (!cond) { + std::println("FAIL: {}", what); + ok = false; + } + }; + + bool thrown = false; + try { + // A DSN that cannot exist anywhere; SQL_DRIVER_NOPROMPT keeps the DM + // from trying to raise a UI prompt on a headless runner. + nanodbc::connection conn( + NANODBC_TEXT("DSN=mcpp-definitely-missing"), NANODBC_TEXT(""), + NANODBC_TEXT(""), 1 /* timeout */); + check(false, "connecting to a nonexistent DSN must not succeed"); + } catch (const nanodbc::database_error& e) { + thrown = true; + std::println("caught database_error: state={} what={}", e.state(), + e.what()); + // IM002, "[Driver Manager]Data source name not found", is the proof + // the diagnostics came out of the real driver manager, not a stub. + // + // nanodbc 2.14.0 itself drops the LAST state character (recent_error + // in nanodbc.cpp loops to size(sql_state) - 1), so state() reads + // "IM00" where the DM said "IM002". Frozen upstream, so assert the + // prefix relationship rather than equality. + check(!e.state().empty() && std::string_view("IM002").starts_with(e.state()), + "SQL state must be a non-empty prefix of IM002"); + check(std::string_view(e.what()).find("Data source name not found") != + std::string_view::npos, + "what() must carry the driver manager's own message"); + } catch (const std::exception& e) { + check(false, std::format("wrong exception type: {}", e.what())); + } + check(thrown, "nanodbc::database_error must be thrown for a bogus DSN"); + + if (ok) std::println("nanodbc error-path test passed"); + return ok ? 0 : 1; +} + +#else + +int main() { return 0; } // non-linux: dependency is cfg-gated out, nothing to test + +#endif