From ff5de30d2ac590656205abe9e4f94e4185ede163 Mon Sep 17 00:00:00 2001
From: David Li
Date: Tue, 11 Aug 2026 16:44:35 +0900
Subject: [PATCH 1/2] feat: check exported symbols on macOS
---
adbc_drivers_dev/make_checks.py | 51 ++++++++++---
tests/test_make_checks.py | 130 +++++++++++++++++++-------------
2 files changed, 117 insertions(+), 64 deletions(-)
diff --git a/adbc_drivers_dev/make_checks.py b/adbc_drivers_dev/make_checks.py
index 0cad29b..fd6ab58 100644
--- a/adbc_drivers_dev/make_checks.py
+++ b/adbc_drivers_dev/make_checks.py
@@ -33,6 +33,14 @@ def _read_linux_symbols(binary: Path) -> list[str]:
)
+def _read_macos_symbols(binary: Path) -> list[str]:
+ return (
+ subprocess.check_output(["nm", "-gU", str(binary)], text=True)
+ .strip()
+ .splitlines()
+ )
+
+
def _read_linux_symbols_in_docker(
make_env: MakeEnv, make_config: MakeConfig, binary: Path
) -> list[str]:
@@ -67,18 +75,28 @@ def _read_linux_symbols_in_docker(
)
-def check_linux_symbols(
- symbols: list[str], binary: Path, manylinux: str, driver: str
-) -> None:
- bad_symbols = []
- exported_symbols = set()
+def _extract_linux_symbols(symbols: list[str]) -> list[str]:
+ exported_symbols = []
for symbol in symbols:
if " T " not in symbol:
continue
_, _, name = symbol.partition(" T ")
- exported_symbols.add(name)
- if not name.startswith("Adbc"):
- bad_symbols.append(name)
+ exported_symbols.append(name)
+ return exported_symbols
+
+
+def _extract_macos_symbols(symbols: list[str]) -> list[str]:
+ exported_symbols = []
+ for symbol in symbols:
+ if " T " not in symbol:
+ continue
+ _, _, name = symbol.partition(" T ")
+ exported_symbols.append(name.removeprefix("_"))
+ return exported_symbols
+
+
+def check_symbols(symbols: list[str], binary: Path, driver: str) -> None:
+ bad_symbols = [symbol for symbol in symbols if not symbol.startswith("Adbc")]
if bad_symbols:
raise RuntimeError(
f"{', '.join(bad_symbols[:3])}... ({len(bad_symbols)} symbols total) should not be exported from {binary}"
@@ -87,11 +105,13 @@ def check_linux_symbols(
driver_init = f"AdbcDriver{driver.lower().capitalize()}Init"
missing_symbols = set()
for required_symbol in (driver_init, "AdbcDriverInit"):
- if required_symbol not in exported_symbols:
+ if required_symbol not in symbols:
missing_symbols.add(required_symbol)
if missing_symbols:
raise RuntimeError(f"{', '.join(missing_symbols)} should be exported from {binary}")
+
+def check_manylinux_symbols(symbols: list[str], manylinux: str) -> None:
limits = {
"manylinux2014": ("2.17", "3.4.19"),
"manylinux_2_28": ("2.28", "3.4.32"),
@@ -125,10 +145,11 @@ def _check_linux(make_env: MakeEnv, make_config: MakeConfig, binary: Path) -> No
raise RuntimeError(
"Cannot run Linux compatibility checks on non-Linux host without Docker"
)
- check_linux_symbols(symbols, binary, make_config.manylinux, make_config.driver)
+ check_symbols(_extract_linux_symbols(symbols), binary, make_config.driver)
+ check_manylinux_symbols(symbols, make_config.manylinux)
-def _check_macos(binary: Path) -> None:
+def _check_macos_deployment_target(binary: Path) -> None:
output = subprocess.check_output(["otool", "-l", str(binary)], text=True)
minos = None
for line in output.splitlines():
@@ -148,9 +169,15 @@ def _check_macos(binary: Path) -> None:
)
+def _check_macos(make_config: MakeConfig, binary: Path) -> None:
+ symbols = _read_macos_symbols(binary)
+ check_symbols(_extract_macos_symbols(symbols), binary, make_config.driver)
+ _check_macos_deployment_target(binary)
+
+
def check(make_env: MakeEnv, make_config: MakeConfig, binary: Path) -> None:
if make_env.target_platform == "linux":
_check_linux(make_env, make_config, binary)
elif make_env.target_platform == "macos":
- _check_macos(binary)
+ _check_macos(make_config, binary)
# TODO: implement Windows checks
diff --git a/tests/test_make_checks.py b/tests/test_make_checks.py
index 73ab268..106afcc 100644
--- a/tests/test_make_checks.py
+++ b/tests/test_make_checks.py
@@ -13,97 +13,123 @@
# limitations under the License.
from pathlib import Path
+from unittest.mock import Mock
import pytest
from adbc_drivers_dev import make_checks
-def test_check_linux_symbols_accepts_adbc_exports() -> None:
- make_checks.check_linux_symbols(
+def test_check_symbols_accepts_adbc_exports() -> None:
+ make_checks.check_symbols(
[
- "000000 T AdbcDatabaseNew",
- "000000 T AdbcConnectionInit",
- "000000 T AdbcDriverInit",
- "000000 T AdbcDriverMultiwordnameInit",
- " U external_symbol",
- "000000 B _cgo_runtime",
+ "AdbcDatabaseNew",
+ "AdbcConnectionInit",
+ "AdbcDriverMultiwordnameInit",
+ "AdbcDriverInit",
],
Path("driver.so"),
- "manylinux2014",
"multiwordname",
)
-def test_check_linux_symbols_rejects_non_adbc_exports() -> None:
+def test_check_symbols_rejects_non_adbc_exports() -> None:
with pytest.raises(RuntimeError, match="bad_symbol"):
- make_checks.check_linux_symbols(
+ make_checks.check_symbols(
[
- "000000 T AdbcDatabaseNew",
- "000000 T AdbcDriverInit",
- "000000 T AdbcDriverDriverInit",
- "000000 T bad_symbol",
+ "AdbcDatabaseNew",
+ "AdbcDriverDriverInit",
+ "AdbcDriverInit",
+ "bad_symbol",
],
Path("driver.so"),
- "manylinux2014",
"driver",
)
-def test_check_linux_symbols_requires_driver_init() -> None:
+def test_check_symbols_requires_driver_init() -> None:
with pytest.raises(RuntimeError, match="AdbcDriverMultiwordnameInit"):
- make_checks.check_linux_symbols(
- ["000000 T AdbcDriverInit", "000000 T AdbcDriverMultiWordNameInit"],
+ make_checks.check_symbols(
+ ["AdbcDriverMultiWordNameInit"],
Path("libadbc_driver_multiwordname.so"),
- "manylinux2014",
"multiwordname",
)
-def test_check_linux_symbols_requires_generic_driver_init() -> None:
- with pytest.raises(RuntimeError, match="AdbcDriverInit"):
- make_checks.check_linux_symbols(
- ["000000 T AdbcDriverMultiwordnameInit"],
- Path("libadbc_driver_multiwordname.so"),
- "manylinux2014",
- "multiwordname",
+def test_extract_linux_symbols() -> None:
+ assert make_checks._extract_linux_symbols(
+ [
+ "000000 T AdbcDriverInit",
+ "000000 T AdbcDriverDriverInit",
+ " U external_symbol",
+ "000000 B _cgo_runtime",
+ ]
+ ) == ["AdbcDriverDriverInit"]
+
+
+def test_extract_macos_symbols() -> None:
+ assert make_checks._extract_macos_symbols(
+ [
+ "000000 T _AdbcDriverMultiwordnameInit",
+ "000000 U _external_symbol",
+ "000000 D __cgo_runtime",
+ ]
+ ) == ["AdbcDriverMultiwordnameInit"]
+
+
+def test_check_macos_accepts_adbc_exports(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(
+ make_checks,
+ "_read_macos_symbols",
+ lambda _binary: ["000000 T _AdbcDriverMultiwordnameInit"],
+ )
+ monkeypatch.setattr(
+ make_checks, "_check_macos_deployment_target", lambda _binary: None
+ )
+
+ make_checks._check_macos(
+ Mock(driver="multiwordname"), Path("libadbc_driver_multiwordname.dylib")
+ )
+
+
+def test_check_macos_rejects_non_adbc_exports(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.setattr(
+ make_checks,
+ "_read_macos_symbols",
+ lambda _binary: [
+ "000000 T _AdbcDriverMultiwordnameInit",
+ "000000 T _bad_symbol",
+ ],
+ )
+ monkeypatch.setattr(
+ make_checks, "_check_macos_deployment_target", lambda _binary: None
+ )
+
+ with pytest.raises(RuntimeError, match="bad_symbol"):
+ make_checks._check_macos(
+ Mock(driver="multiwordname"),
+ Path("libadbc_driver_multiwordname.dylib"),
)
-def test_check_linux_symbols_enforces_manylinux_limits() -> None:
+def test_check_manylinux_symbols_enforces_limits() -> None:
with pytest.raises(RuntimeError, match="GLIBC_2.18"):
- make_checks.check_linux_symbols(
- [
- "000000 T AdbcDriverInit",
- "000000 T AdbcDriverDriverInit",
- " U function@GLIBC_2.18",
- ],
- Path("driver.so"),
- "manylinux2014",
- "driver",
- )
+ make_checks.check_manylinux_symbols([" U function@GLIBC_2.18"], "manylinux2014")
- make_checks.check_linux_symbols(
+ make_checks.check_manylinux_symbols(
[
- "000000 T AdbcDriverInit",
- "000000 T AdbcDriverDriverInit",
" U function@GLIBC_2.28",
" U function@GLIBCXX_3.4.32",
],
- Path("driver.so"),
"manylinux_2_28",
- "driver",
)
-def test_check_linux_symbols_rejects_unknown_policy() -> None:
+def test_check_manylinux_symbols_rejects_unknown_policy() -> None:
with pytest.raises(ValueError, match="Unsupported manylinux policy"):
- make_checks.check_linux_symbols(
- ["000000 T AdbcDriverInit", "000000 T AdbcDriverDriverInit"],
- Path("driver.so"),
- "unknown",
- "driver",
- )
+ make_checks.check_manylinux_symbols([], "unknown")
def test_check_macos_rejects_new_deployment_target(
@@ -115,7 +141,7 @@ def test_check_macos_rejects_new_deployment_target(
lambda *args, **kwargs: " minos 12.0\n",
)
with pytest.raises(RuntimeError, match="macOS 12.0"):
- make_checks._check_macos(Path("driver.dylib"))
+ make_checks._check_macos_deployment_target(Path("driver.dylib"))
def test_check_macos_accepts_supported_deployment_target(
@@ -126,4 +152,4 @@ def test_check_macos_accepts_supported_deployment_target(
"check_output",
lambda *args, **kwargs: " minos 11.0\n",
)
- make_checks._check_macos(Path("driver.dylib"))
+ make_checks._check_macos_deployment_target(Path("driver.dylib"))
From 7e51fab816544302052e271c66573ee9d971e0f1 Mon Sep 17 00:00:00 2001
From: David Li
Date: Tue, 11 Aug 2026 17:22:52 +0900
Subject: [PATCH 2/2] tweak
---
tests/test_make_checks.py | 38 --------------------------------------
1 file changed, 38 deletions(-)
diff --git a/tests/test_make_checks.py b/tests/test_make_checks.py
index 106afcc..2d25a08 100644
--- a/tests/test_make_checks.py
+++ b/tests/test_make_checks.py
@@ -13,7 +13,6 @@
# limitations under the License.
from pathlib import Path
-from unittest.mock import Mock
import pytest
@@ -77,43 +76,6 @@ def test_extract_macos_symbols() -> None:
) == ["AdbcDriverMultiwordnameInit"]
-def test_check_macos_accepts_adbc_exports(monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.setattr(
- make_checks,
- "_read_macos_symbols",
- lambda _binary: ["000000 T _AdbcDriverMultiwordnameInit"],
- )
- monkeypatch.setattr(
- make_checks, "_check_macos_deployment_target", lambda _binary: None
- )
-
- make_checks._check_macos(
- Mock(driver="multiwordname"), Path("libadbc_driver_multiwordname.dylib")
- )
-
-
-def test_check_macos_rejects_non_adbc_exports(
- monkeypatch: pytest.MonkeyPatch,
-) -> None:
- monkeypatch.setattr(
- make_checks,
- "_read_macos_symbols",
- lambda _binary: [
- "000000 T _AdbcDriverMultiwordnameInit",
- "000000 T _bad_symbol",
- ],
- )
- monkeypatch.setattr(
- make_checks, "_check_macos_deployment_target", lambda _binary: None
- )
-
- with pytest.raises(RuntimeError, match="bad_symbol"):
- make_checks._check_macos(
- Mock(driver="multiwordname"),
- Path("libadbc_driver_multiwordname.dylib"),
- )
-
-
def test_check_manylinux_symbols_enforces_limits() -> None:
with pytest.raises(RuntimeError, match="GLIBC_2.18"):
make_checks.check_manylinux_symbols([" U function@GLIBC_2.18"], "manylinux2014")