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..2d25a08 100644 --- a/tests/test_make_checks.py +++ b/tests/test_make_checks.py @@ -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( @@ -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( @@ -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"))