Skip to content

Commit c06fd43

Browse files
author
peng.li24
committed
build: replace tests/Makefile with CMakeLists.txt
tests/CMakeLists.txt: - Auto-discovers numpy umath .so path via execute_process - Finds pybind11 via python get_cmake_dir(), Eigen3, OpenMP - Compiles with -O3 -march=native -ffp-contract=off - Links numpy umath .so: target_link_directories + -l:filename.so (items starting with -l in target_link_libraries are passed verbatim AND placed after object files, satisfying GNU ld --as-needed ordering) - RPATH set to numpy/core so loader finds the .so at runtime - 'pytest' custom target and ctest integration with PYTHONPATH tests/Makefile: deleted
1 parent 69a6689 commit c06fd43

2 files changed

Lines changed: 146 additions & 28 deletions

File tree

tests/CMakeLists.txt

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# tests/CMakeLists.txt — build + test the numpycpp Python extension.
2+
#
3+
# Build (out-of-source, from project root):
4+
# cmake -S tests -B tests/build
5+
# cmake --build tests/build
6+
# cmake --build tests/build --target pytest
7+
#
8+
# Precision strategy (no dlopen / no /proc tricks):
9+
# float32 → npy_*f scalar from numpy's _multiarray_umath.so → ≤1 ULP
10+
# float64 → __svml_*8 SVML from numpy's _multiarray_umath.so → 0 ULP
11+
# Both symbol sets are resolved at link-time via -l:<umath.so>.
12+
13+
cmake_minimum_required(VERSION 3.18)
14+
project(numpycpp_tests LANGUAGES CXX)
15+
16+
set(CMAKE_CXX_STANDARD 17)
17+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
18+
set(CMAKE_CXX_EXTENSIONS OFF)
19+
20+
# ---------------------------------------------------------------------------
21+
# Dependencies
22+
# ---------------------------------------------------------------------------
23+
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
24+
25+
# Locate pybind11 — prefer the pip-installed copy, fall back to system paths
26+
execute_process(
27+
COMMAND "${Python3_EXECUTABLE}" -c
28+
"import pybind11; print(pybind11.get_cmake_dir())"
29+
OUTPUT_VARIABLE _PYBIND11_CMAKE_DIR
30+
OUTPUT_STRIP_TRAILING_WHITESPACE
31+
ERROR_QUIET
32+
)
33+
find_package(pybind11 REQUIRED CONFIG HINTS "${_PYBIND11_CMAKE_DIR}")
34+
35+
find_package(Eigen3 REQUIRED NO_MODULE)
36+
find_package(OpenMP)
37+
38+
# ---------------------------------------------------------------------------
39+
# Locate numpy's _multiarray_umath.so
40+
# This .so bundles Intel SVML (__svml_exp8 / __svml_sin8 / …) and exports
41+
# numpy's scalar polynomial helpers (npy_expf / npy_sinf / …).
42+
# We link against it directly so core.h's extern "C" declarations resolve at
43+
# link time — no dlopen, no runtime symbol lookup.
44+
# ---------------------------------------------------------------------------
45+
execute_process(
46+
COMMAND "${Python3_EXECUTABLE}" -c
47+
"import os, numpy.core; print(os.path.dirname(numpy.core.__file__))"
48+
OUTPUT_VARIABLE NUMPY_CORE_DIR
49+
OUTPUT_STRIP_TRAILING_WHITESPACE
50+
COMMAND_ERROR_IS_FATAL ANY
51+
)
52+
execute_process(
53+
COMMAND "${Python3_EXECUTABLE}" -c
54+
"import os, glob, numpy.core
55+
d = os.path.dirname(numpy.core.__file__)
56+
print(os.path.basename(glob.glob(os.path.join(d, '_multiarray_umath*.so'))[0]))"
57+
OUTPUT_VARIABLE NUMPY_UMATH_FILENAME
58+
OUTPUT_STRIP_TRAILING_WHITESPACE
59+
COMMAND_ERROR_IS_FATAL ANY
60+
)
61+
message(STATUS "NumPy core : ${NUMPY_CORE_DIR}")
62+
message(STATUS "NumPy umath : ${NUMPY_UMATH_FILENAME}")
63+
64+
# ---------------------------------------------------------------------------
65+
# Python extension module: numpycpp
66+
# ---------------------------------------------------------------------------
67+
pybind11_add_module(numpycpp MODULE
68+
"${CMAKE_CURRENT_SOURCE_DIR}/module.cpp"
69+
)
70+
71+
target_include_directories(numpycpp PRIVATE
72+
"${CMAKE_CURRENT_SOURCE_DIR}/.." # numpy/core.h, numpy/einsum.h, …
73+
"${CMAKE_CURRENT_SOURCE_DIR}/../pycpp" # core_py.h, linalg_py.h, …
74+
)
75+
76+
target_compile_options(numpycpp PRIVATE
77+
-O3
78+
-march=native # enables __AVX512F__ on this machine
79+
-fno-math-errno
80+
-fno-trapping-math
81+
-ffp-contract=off # no FMA contraction (keeps results reproducible)
82+
-DNUMPYCPP_USE_NUMPY_MATH # activates npy_* / SVML specialisations in core.h
83+
)
84+
85+
if(Eigen3_FOUND)
86+
target_link_libraries(numpycpp PRIVATE Eigen3::Eigen)
87+
endif()
88+
89+
if(OpenMP_CXX_FOUND)
90+
target_link_libraries(numpycpp PRIVATE OpenMP::OpenMP_CXX)
91+
endif()
92+
93+
# Link against numpy's umath .so to resolve __svml_* and npy_* symbols.
94+
#
95+
# Naming constraints:
96+
# • The file is _multiarray_umath*.so — no "lib" prefix.
97+
# • Passing the absolute path makes CMake generate "-l_multiarray_umath…"
98+
# (strips dir + .so) which ld cannot find.
99+
# • LINK_FLAGS / target_link_options put flags BEFORE object files;
100+
# with GNU ld --as-needed (Ubuntu default) the library is then skipped
101+
# because no symbols are referenced yet → runtime undefined symbols.
102+
#
103+
# Solution: items starting with "-l" inside target_link_libraries() are:
104+
# (a) passed verbatim (no CMake name mangling), and
105+
# (b) placed AFTER object files in the linker command (the link-libraries
106+
# slot, not the link-flags slot) — satisfying --as-needed ordering.
107+
# target_link_directories() adds -L<dir> (safe before objects).
108+
target_link_directories(numpycpp PRIVATE "${NUMPY_CORE_DIR}")
109+
target_link_libraries(numpycpp PRIVATE "-l:${NUMPY_UMATH_FILENAME}")
110+
111+
# Embed the numpy core directory as RPATH so the loader finds the .so at runtime.
112+
set_target_properties(numpycpp PROPERTIES
113+
BUILD_RPATH "${NUMPY_CORE_DIR}"
114+
INSTALL_RPATH "${NUMPY_CORE_DIR}"
115+
)
116+
117+
# ---------------------------------------------------------------------------
118+
# Test targets
119+
# ---------------------------------------------------------------------------
120+
# PYTHONPATH must include the build dir so `import numpycpp` resolves.
121+
set(PYTEST_ENV "PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}:$ENV{PYTHONPATH}")
122+
123+
# `cmake --build . --target pytest` — verbose, shows per-test output
124+
add_custom_target(pytest
125+
COMMAND ${CMAKE_COMMAND} -E env "${PYTEST_ENV}"
126+
"${Python3_EXECUTABLE}" -m pytest
127+
"${CMAKE_CURRENT_SOURCE_DIR}/test_all.py"
128+
-v --tb=short --no-header
129+
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.."
130+
DEPENDS numpycpp
131+
COMMENT "pytest (PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR})"
132+
VERBATIM
133+
)
134+
135+
# CTest integration: `ctest` or `cmake --build . --target test`
136+
enable_testing()
137+
add_test(
138+
NAME pytest
139+
COMMAND "${Python3_EXECUTABLE}" -m pytest
140+
"${CMAKE_CURRENT_SOURCE_DIR}/test_all.py"
141+
-q --tb=short --no-header
142+
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.."
143+
)
144+
set_tests_properties(pytest PROPERTIES
145+
ENVIRONMENT "${PYTEST_ENV}"
146+
)

tests/Makefile

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)