Skip to content
Draft
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
51 changes: 39 additions & 12 deletions adbc_drivers_dev/make_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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}"
Expand All @@ -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"),
Expand Down Expand Up @@ -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():
Expand All @@ -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
94 changes: 41 additions & 53 deletions tests/test_make_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,91 +19,79 @@
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_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(
Expand All @@ -115,7 +103,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(
Expand All @@ -126,4 +114,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"))
Loading