|
| 1 | +-- compat.mysql-connector-cpp -- MySQL Connector/C++ X DevAPI and JDBC. |
| 2 | +package = { |
| 3 | + spec = "1", |
| 4 | + namespace = "compat", |
| 5 | + name = "mysql-connector-cpp", |
| 6 | + description = "MySQL Connector/C++ X DevAPI and JDBC (static, source-built)", |
| 7 | + licenses = {"GPL-2.0-only", "Universal-FOSS-exception-1.0"}, |
| 8 | + repo = "https://github.com/mysql/mysql-connector-cpp", |
| 9 | + type = "package", |
| 10 | + |
| 11 | + xpm = { |
| 12 | + linux = { |
| 13 | + deps = { |
| 14 | + "compat:libmysqlclient@8.4.6", |
| 15 | + "compat:openssl@3.5.1", |
| 16 | + "xim:cmake@latest", |
| 17 | + "xim:make@latest", |
| 18 | + }, |
| 19 | + ["26.7.0"] = { |
| 20 | + url = "https://github.com/mysql/mysql-connector-cpp/archive/refs/tags/26.7.0.tar.gz", |
| 21 | + sha256 = "b2299862eefc33fd71c0aac68328305671805fc955e6bd2578ef205c10f98550", |
| 22 | + }, |
| 23 | + }, |
| 24 | + macosx = { |
| 25 | + deps = { |
| 26 | + "compat:libmysqlclient@8.4.6", |
| 27 | + "compat:openssl@3.5.1", |
| 28 | + "xim:cmake@latest", |
| 29 | + }, |
| 30 | + ["26.7.0"] = { |
| 31 | + url = "https://github.com/mysql/mysql-connector-cpp/archive/refs/tags/26.7.0.tar.gz", |
| 32 | + sha256 = "b2299862eefc33fd71c0aac68328305671805fc955e6bd2578ef205c10f98550", |
| 33 | + }, |
| 34 | + }, |
| 35 | + }, |
| 36 | + |
| 37 | + mcpp = { |
| 38 | + language = "c++23", |
| 39 | + import_std = false, |
| 40 | + sources = { "mcpp_mysql_connector_cpp_anchor.c" }, |
| 41 | + include_dirs = { "include" }, |
| 42 | + targets = { ["mysql_connector_cpp"] = { kind = "lib" } }, |
| 43 | + deps = { |
| 44 | + ["compat.libmysqlclient"] = "8.4.6", |
| 45 | + ["compat.openssl"] = "3.5.1", |
| 46 | + }, |
| 47 | + |
| 48 | + linux = { |
| 49 | + ldflags = { |
| 50 | + "-Llib", "-l:libmysqlcppconnx-static.a", |
| 51 | + "-l:libmysqlcppconn-static.a", "-lresolv", |
| 52 | + }, |
| 53 | + }, |
| 54 | + macosx = { |
| 55 | + ldflags = { |
| 56 | + "-Llib", "-lmysqlcppconnx-static", |
| 57 | + "-lmysqlcppconn-static", "-lresolv", |
| 58 | + }, |
| 59 | + }, |
| 60 | + }, |
| 61 | +} |
| 62 | + |
| 63 | +import("xim.libxpkg.pkginfo") |
| 64 | +import("xim.libxpkg.log") |
| 65 | +-- Hook diagnostics: xlings swallows install-hook stdout and log.error, so a |
| 66 | +-- failure used to surface only as an empty E_INTERNAL with no build log. This |
| 67 | +-- hook writes a step trace + the failing reason to |
| 68 | +-- $HOME/.mcpp/registry/data/mcpp_mysql_connector_cpp_build.log, which |
| 69 | +-- validate.yml's `find ... -name 'mcpp_*_build.log'` dump tail-prints. It is a |
| 70 | +-- no-op outside the xlings runtime (the `path` global is absent there), so the |
| 71 | +-- descriptor-executing lint helpers stay green. |
| 72 | +local hook_logf |
| 73 | +local function hook_log(msg) |
| 74 | + if type(path) ~= "table" then return end |
| 75 | + if not hook_logf then |
| 76 | + local home = os.getenv and os.getenv("HOME") or "/tmp" |
| 77 | + hook_logf = home .. "/.mcpp/registry/data/mcpp_mysql_connector_cpp_build.log" |
| 78 | + end |
| 79 | + local f = io.open(hook_logf, "a") |
| 80 | + if f then |
| 81 | + f:write(os.date("%H:%M:%S") .. " " .. tostring(msg) .. "\n") |
| 82 | + f:close() |
| 83 | + end |
| 84 | +end |
| 85 | +hook_log("descriptor loaded") |
| 86 | + |
| 87 | +local function sh_quote(value) |
| 88 | + return "'" .. tostring(value):gsub("'", "'\\''") .. "'" |
| 89 | +end |
| 90 | + |
| 91 | +local function resolve_tool(dep, name, fallback) |
| 92 | + local pkg = pkginfo.build_dep(dep) |
| 93 | + if pkg then |
| 94 | + local candidates = {} |
| 95 | + if pkg.bin then candidates[#candidates + 1] = path.join(pkg.bin, name) end |
| 96 | + if pkg.path then |
| 97 | + -- macOS .app bundles (xim cmake ships CMake.app/Contents/bin). |
| 98 | + candidates[#candidates + 1] = |
| 99 | + path.join(pkg.path, name .. ".app", "Contents", "bin", name) |
| 100 | + end |
| 101 | + for _, cand in ipairs(candidates) do |
| 102 | + if os.isfile(cand) then return cand end |
| 103 | + end |
| 104 | + end |
| 105 | + return fallback |
| 106 | +end |
| 107 | + |
| 108 | +local function tail_lines(file, count) |
| 109 | + local ok, content = pcall(io.readfile, file) |
| 110 | + if not ok or not content then return "<log unavailable>" end |
| 111 | + local lines = {} |
| 112 | + for line in (tostring(content) .. "\n"):gmatch("(.-)\n") do |
| 113 | + lines[#lines + 1] = line |
| 114 | + end |
| 115 | + return table.concat(lines, "\n", math.max(1, #lines - count + 1), #lines) |
| 116 | +end |
| 117 | + |
| 118 | +local function run(step, logf, command) |
| 119 | + -- xlings 的 os.exec 通过返回值报告非零退出;pcall 只负责捕获 Lua 错误。 |
| 120 | + local invoked, result, reason, code = |
| 121 | + pcall(os.exec, string.format("bash -c %s", sh_quote(command))) |
| 122 | + if invoked and result then return true end |
| 123 | + local err = invoked and (tostring(reason) .. " " .. tostring(code)) |
| 124 | + or tostring(result) |
| 125 | + local msg = "compat.mysql-connector-cpp: " .. step .. " failed (" |
| 126 | + .. tostring(err) .. ")\n--- last 40 lines of " .. logf .. " ---\n" |
| 127 | + .. tail_lines(logf, 40) |
| 128 | + log.error("%s", msg) |
| 129 | + hook_log(msg) |
| 130 | + return false |
| 131 | +end |
| 132 | + |
| 133 | +local function find_source_root() |
| 134 | + local ifile = pkginfo.install_file() |
| 135 | + local roots = {} |
| 136 | + if ifile then roots[#roots + 1] = tostring(ifile):replace(".tar.gz", "") end |
| 137 | + roots[#roots + 1] = "mysql-connector-cpp-" .. pkginfo.version() |
| 138 | + for _, root in ipairs(roots) do |
| 139 | + if os.isfile(path.join(root, "CMakeLists.txt")) then return root end |
| 140 | + end |
| 141 | +end |
| 142 | + |
| 143 | +local function find_mysql_source_root(prefix) |
| 144 | + local direct = path.join(prefix, "libmysqlclient-8.4.6") |
| 145 | + if os.isfile(path.join(direct, "mcpp.toml")) then return direct end |
| 146 | + for _, manifest in ipairs(os.files(path.join(prefix, "**", "mcpp.toml")) or {}) do |
| 147 | + local root = path.directory(manifest) |
| 148 | + if os.isfile(path.join(root, "include", "mysql.h")) then return root end |
| 149 | + end |
| 150 | +end |
| 151 | + |
| 152 | +-- `pkginfo.install_dir` scans only the member-local xpkgs roots; a dependency |
| 153 | +-- that was installed into the shared registry cache (e.g. compat.openssl built |
| 154 | +-- by the sibling libmysqlclient member and reused here without a member-local |
| 155 | +-- copy) is invisible to it and comes back nil. Fall back to the known xpkgs |
| 156 | +-- roots before giving up. |
| 157 | +local function find_dep_install_dir(dep_name, dep_version) |
| 158 | + local dir = pkginfo.install_dir(dep_name, dep_version) |
| 159 | + if dir then return dir end |
| 160 | + local ns, bare = dep_name:match("^([^:]+):(.+)$") |
| 161 | + if not ns then return nil end |
| 162 | + local store = ns .. "-x-" .. bare |
| 163 | + local pfx = pkginfo.install_dir() |
| 164 | + local roots = {} |
| 165 | + if pfx then roots[#roots + 1] = path.directory(path.directory(pfx)) end |
| 166 | + local home = os.getenv and os.getenv("MCPP_HOME") or "" |
| 167 | + if home == "" then home = ((os.getenv and os.getenv("HOME")) or "") .. "/.mcpp" end |
| 168 | + roots[#roots + 1] = path.join(home, "registry/data/xpkgs") |
| 169 | + for _, root in ipairs(roots) do |
| 170 | + local cand = path.join(root, store, dep_version) |
| 171 | + if os.isdir(cand) then return cand end |
| 172 | + end |
| 173 | + return nil |
| 174 | +end |
| 175 | + |
| 176 | +local function patch_internal_compression_helper(srcroot) |
| 177 | + local source = path.join(srcroot, "common", "session.cc") |
| 178 | + local content = io.readfile(source) |
| 179 | + local replacement = |
| 180 | + "\nstatic TCPIP_options::compression_algorithm_t get_compression_algorithm(std::string alg)" |
| 181 | + if content:find(replacement, 1, true) then return true end |
| 182 | + |
| 183 | + local signature = |
| 184 | + "\nTCPIP_options::compression_algorithm_t get_compression_algorithm%(std::string alg%)" |
| 185 | + local patched, count = content:gsub(signature, replacement) |
| 186 | + if count ~= 1 then |
| 187 | + log.error("compat.mysql-connector-cpp: expected one compression helper, found " |
| 188 | + .. tostring(count)) |
| 189 | + return false |
| 190 | + end |
| 191 | + io.writefile(source, patched) |
| 192 | + return true |
| 193 | +end |
| 194 | + |
| 195 | +function install() |
| 196 | + hook_log("install() called; install_file=" .. tostring(pkginfo.install_file()) |
| 197 | + .. " install_dir=" .. tostring(pkginfo.install_dir()) |
| 198 | + .. " deps=" .. tostring(pkginfo.deps_list())) |
| 199 | + local ok, result = pcall(function() |
| 200 | + local srcroot = find_source_root() |
| 201 | + local mysql = find_dep_install_dir("compat:libmysqlclient", "8.4.6") |
| 202 | + local openssl = find_dep_install_dir("compat:openssl", "3.5.1") |
| 203 | + hook_log("srcroot=" .. tostring(srcroot) .. " mysql=" .. tostring(mysql) |
| 204 | + .. " openssl=" .. tostring(openssl)) |
| 205 | + if not srcroot or not mysql or not openssl then |
| 206 | + local why = "source/dependency prefix not found (srcroot=" |
| 207 | + .. tostring(srcroot) .. " mysql=" .. tostring(mysql) |
| 208 | + .. " openssl=" .. tostring(openssl) .. ")" |
| 209 | + log.error("compat.mysql-connector-cpp: %s", why) |
| 210 | + hook_log(why) |
| 211 | + return false |
| 212 | + end |
| 213 | + |
| 214 | + local mysql_src = find_mysql_source_root(mysql) |
| 215 | + hook_log("mysql_src=" .. tostring(mysql_src)) |
| 216 | + if not mysql_src then |
| 217 | + log.error("compat.mysql-connector-cpp: libmysqlclient Form A source root not found") |
| 218 | + return false |
| 219 | + end |
| 220 | + |
| 221 | + local prefix = pkginfo.install_dir() |
| 222 | + local builddir = path.join(srcroot, "mcpp-build") |
| 223 | + local logf = path.join(prefix, "mcpp_mysql_connector_cpp_build.log") |
| 224 | + hook_log("prefix=" .. tostring(prefix) .. " builddir=" .. tostring(builddir) |
| 225 | + .. " logf=" .. tostring(logf)) |
| 226 | + os.tryrm(prefix) |
| 227 | + os.mkdir(prefix) |
| 228 | + os.tryrm(builddir) |
| 229 | + |
| 230 | + -- 此 helper 只在 session.cc 内使用;内部链接避免与 MySQL 8.4 |
| 231 | + -- libmysqlclient 中同签名的实现发生静态链接符号冲突。 |
| 232 | + if not patch_internal_compression_helper(srcroot) then return false end |
| 233 | + |
| 234 | + -- Connector/C++ 的配置阶段只要求能找到一个 mysqlclient archive;真正的 |
| 235 | + -- libmysqlclient.a 会在最终消费时由 mcpp 的依赖图链接。这里的探测库不安装。 |
| 236 | + local mysql_probe = path.join(builddir, "mysqlclient-probe") |
| 237 | + os.mkdir(path.join(mysql_probe, "lib")) |
| 238 | + os.mkdir(path.join(mysql_probe, "include")) |
| 239 | + local probe_src = path.join(mysql_probe, "probe.c") |
| 240 | + local probe_obj = path.join(mysql_probe, "probe.o") |
| 241 | + local probe_lib = path.join(mysql_probe, "lib", "libmysqlclient.a") |
| 242 | + io.writefile(probe_src, "int mcpp_mysqlclient_probe(void) { return 0; }\n") |
| 243 | + local probe_command = string.format( |
| 244 | + "cp -R %s/. %s/ && cp %s/*.h %s/ && " |
| 245 | + .. "cc -c %s -o %s && ar rcs %s %s", |
| 246 | + sh_quote(path.join(mysql_src, "include")), |
| 247 | + sh_quote(path.join(mysql_probe, "include")), |
| 248 | + sh_quote(path.join(mysql_src, "generated")), |
| 249 | + sh_quote(path.join(mysql_probe, "include")), |
| 250 | + sh_quote(probe_src), sh_quote(probe_obj), sh_quote(probe_lib), sh_quote(probe_obj)) |
| 251 | + if not run("mysqlclient probe archive", logf, |
| 252 | + probe_command .. " >" .. sh_quote(logf) .. " 2>&1") then |
| 253 | + return false |
| 254 | + end |
| 255 | + |
| 256 | + local cmake = resolve_tool("xim:cmake", "cmake", "cmake") |
| 257 | + local make = resolve_tool("xim:make", "make", "make") |
| 258 | + local jobs = (os.default_njob and os.default_njob()) or 4 |
| 259 | + local clean_env = "env -u CPPFLAGS -u CFLAGS -u CXXFLAGS -u LDFLAGS " |
| 260 | + local compiler = "" |
| 261 | + if os.host() == "macosx" then |
| 262 | + -- Connector 在 project() 前启动 bootstrap CMake;必须通过环境变量 |
| 263 | + -- 将最低系统版本同步给 bootstrap 及其后续的内置依赖构建。 |
| 264 | + clean_env = clean_env .. "MACOSX_DEPLOYMENT_TARGET=14.0 " |
| 265 | + -- Sanitize PATH: the connector's CMake merge step invokes Apple's |
| 266 | + -- libtool, and a GNU libtool earlier on PATH (conda/homebrew) breaks |
| 267 | + -- the archive merge. cmake/make come from xim by absolute path and |
| 268 | + -- cc/c++ are pinned below, so system-only PATH is safe here. |
| 269 | + local system_path = "/usr/bin:/bin:/usr/sbin:/sbin:" .. (os.getenv("PATH") or "") |
| 270 | + clean_env = clean_env .. "PATH=" .. sh_quote(system_path) .. " " |
| 271 | + compiler = "-DCMAKE_C_COMPILER=/usr/bin/cc -DCMAKE_CXX_COMPILER=/usr/bin/c++ " |
| 272 | + .. "-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 " |
| 273 | + end |
| 274 | + |
| 275 | + local configure = string.format( |
| 276 | + "%s%s -S %s -B %s -G \"Unix Makefiles\" " |
| 277 | + .. "-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%s " |
| 278 | + .. "-DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_MAKE_PROGRAM=%s " |
| 279 | + .. "-DBUILD_STATIC=ON -DWITH_JDBC=ON -DWITH_TESTS=OFF " |
| 280 | + .. "-DWITH_DOC=OFF -DWITH_HEADER_CHECKS=OFF " |
| 281 | + .. "-DOPENSSL_USE_STATIC_LIBS=TRUE -DWITH_SSL=%s " |
| 282 | + .. "-DMYSQL_INCLUDE_DIR=%s/include -DMYSQL_LIB_DIR=%s/lib " |
| 283 | + .. "-DMYSQLCLIENT_STATIC_LINKING=ON -DMYSQLCLIENT_STATIC_BINDING=ON %s", |
| 284 | + clean_env, sh_quote(cmake), sh_quote(srcroot), sh_quote(builddir), |
| 285 | + sh_quote(prefix), sh_quote(make), sh_quote(openssl), |
| 286 | + sh_quote(mysql_probe), sh_quote(mysql_probe), compiler) |
| 287 | + if not run("CMake configure", logf, configure .. " >" .. sh_quote(logf) .. " 2>&1") then |
| 288 | + return false |
| 289 | + end |
| 290 | + |
| 291 | + local build = string.format( |
| 292 | + "%s%s --build %s --target connector -- -j%d >>%s 2>&1", |
| 293 | + clean_env, sh_quote(cmake), sh_quote(builddir), jobs, sh_quote(logf)) |
| 294 | + if not run("X DevAPI build", logf, build) then return false end |
| 295 | + |
| 296 | + local jdbc_build = string.format( |
| 297 | + "%s%s --build %s --target connector-jdbc -- -j%d >>%s 2>&1", |
| 298 | + clean_env, sh_quote(cmake), sh_quote(builddir), jobs, sh_quote(logf)) |
| 299 | + if not run("JDBC build", logf, jdbc_build) then return false end |
| 300 | + |
| 301 | + local install_cmd = string.format( |
| 302 | + "%s%s --install %s --component XDevAPIDev >>%s 2>&1", |
| 303 | + clean_env, sh_quote(cmake), sh_quote(builddir), sh_quote(logf)) |
| 304 | + if not run("X DevAPI install", logf, install_cmd) then return false end |
| 305 | + |
| 306 | + local jdbc_install = string.format( |
| 307 | + "%s%s --install %s --component JDBCDev >>%s 2>&1", |
| 308 | + clean_env, sh_quote(cmake), sh_quote(builddir), sh_quote(logf)) |
| 309 | + if not run("JDBC install", logf, jdbc_install) then return false end |
| 310 | + |
| 311 | + if not os.isfile(path.join(prefix, "include", "mysqlx", "xdevapi.h")) |
| 312 | + or not os.isfile(path.join(prefix, "include", "mysql", "jdbc.h")) |
| 313 | + or not os.isfile(path.join(prefix, "lib", "libmysqlcppconnx-static.a")) |
| 314 | + or not os.isfile(path.join(prefix, "lib", "libmysqlcppconn-static.a")) then |
| 315 | + log.error("compat.mysql-connector-cpp: incomplete X DevAPI/JDBC install") |
| 316 | + return false |
| 317 | + end |
| 318 | + |
| 319 | + io.writefile(path.join(prefix, "mcpp_mysql_connector_cpp_anchor.c"), |
| 320 | + "int mcpp_compat_mysql_connector_cpp_anchor(void) { return 0; }\n") |
| 321 | + end) |
| 322 | + if not ok then |
| 323 | + hook_log("UNCAUGHT Lua error: " .. tostring(result)) |
| 324 | + return false |
| 325 | + end |
| 326 | + hook_log("install() result=" .. tostring(result)) |
| 327 | + return result |
| 328 | +end |
0 commit comments