44
55.DEFAULT_GOAL := help
66
7+ # --------------------------------------------------------------------------
8+ # Python interpreter detection
9+ # --------------------------------------------------------------------------
10+ # VENV_BIN: path to local virtualenv bin directory
711VENV_BIN := $(PWD ) /venv/bin
8- # Use dynamic (recursive) assignment so a newly created venv is picked up
9- # Prefer explicit versions first (newest to oldest), then python3 on PATH
10- PY_FIND := $(shell for v in 3.13 3.12 3.11 3.10; do command -v python$$v >/dev/null 2>&1 && { echo python$$v; exit 0; }; done; command -v python3 2>/dev/null || command -v python 2>/dev/null)
11- PYTHON ?= $(if $(wildcard $(VENV_BIN ) /python) ,$(VENV_BIN ) /python,$(PY_FIND ) )
12- PIP = $(PYTHON ) -m pip
13- PYTEST = $(PYTHON ) -m pytest
14- RUFF = $(PYTHON ) -m ruff
15- PRECOMMIT = $(PYTHON ) -m pre_commit
12+
13+ # PY_BEST: scan for newest supported Python (used when creating new venvs)
14+ # Supports 3.9-3.13 to match CI matrix
15+ PY_BEST := $(shell for v in 3.13 3.12 3.11 3.10 3.9; do command -v python$$v >/dev/null 2>&1 && { echo python$$v; exit 0; }; done; command -v python3 2>/dev/null || command -v python 2>/dev/null)
16+
17+ # PY_PATH: active python3/python on PATH (respects CI setup-python and activated venvs)
18+ PY_PATH := $(shell command -v python3 2>/dev/null || command -v python 2>/dev/null)
19+
20+ # PYTHON: interpreter used for all commands
21+ # 1. Use local venv if present
22+ # 2. Otherwise use active python on PATH (important for CI)
23+ # 3. Fall back to best available version
24+ PYTHON ?= $(if $(wildcard $(VENV_BIN ) /python) ,$(VENV_BIN ) /python,$(if $(PY_PATH ) ,$(PY_PATH ) ,$(PY_BEST ) ) )
25+
26+ # Derived tool commands (always use -m to ensure correct environment)
27+ PIP := $(PYTHON ) -m pip
28+ PYTEST := $(PYTHON ) -m pytest
29+ RUFF := $(PYTHON ) -m ruff
30+ PRECOMMIT := $(PYTHON ) -m pre_commit
1631
1732# Detect Apple Command Line Tools compilers (prefer system toolchain on macOS)
1833APPLE_CLANG := $(shell xcrun --find clang 2>/dev/null)
3550 @echo " make clean - Clean build artifacts"
3651 @echo " make hooks - Run pre-commit on all files"
3752 @echo " make info - Show tool versions"
38- @echo " make check-python - Check if venv Python matches system Python "
39- @echo " make rebuild - Clean and rebuild with system Python (respects CMAKE_ARGS)"
53+ @echo " make check-python - Check if venv Python matches best available "
54+ @echo " make rebuild - Clean and rebuild (respects CMAKE_ARGS)"
4055
4156# Allow callers to pass CMAKE_ARGS and MACOSX_DEPLOYMENT_TARGET consistently
4257ENV_MACOS := $(if $(MACOSX_DEPLOYMENT_TARGET ) ,MACOSX_DEPLOYMENT_TARGET=$(MACOSX_DEPLOYMENT_TARGET ) ,MACOSX_DEPLOYMENT_TARGET=$(DEFAULT_MACOSX ) )
@@ -48,12 +63,12 @@ DEV_ENV := $(ENV_MACOS) $(ENV_CC) $(ENV_CXX) $(ENV_CMAKE)
4863dev :
4964 @echo " 🚀 Setting up development environment..."
5065 @if [ ! -x " $( VENV_BIN) /python" ]; then \
51- if [ -z " $( PY_FIND ) " ]; then \
66+ if [ -z " $( PY_BEST ) " ]; then \
5267 echo " ❌ Error: No Python interpreter found (python3 or python)" ; \
5368 exit 1; \
5469 fi ; \
55- echo " 🐍 Creating virtual environment with $( PY_FIND ) ..." ; \
56- $(PY_FIND ) -m venv venv || { echo " ❌ Failed to create venv" ; exit 1; }; \
70+ echo " 🐍 Creating virtual environment with $( PY_BEST ) ..." ; \
71+ $(PY_BEST ) -m venv venv || { echo " ❌ Failed to create venv" ; exit 1; }; \
5772 if [ ! -x " $( VENV_BIN) /python" ]; then \
5873 echo " ❌ Error: venv creation failed - $( VENV_BIN) /python not found" ; \
5974 exit 1; \
6984
7085venv :
7186 @echo " 🐍 Creating virtual environment in ./venv ..."
72- @if [ -z " $( PY_FIND ) " ]; then \
87+ @if [ -z " $( PY_BEST ) " ]; then \
7388 echo " ❌ Error: No Python interpreter found (python3 or python)" ; \
7489 exit 1; \
7590 fi
76- @$(PY_FIND ) -m venv venv || { echo " ❌ Failed to create venv" ; exit 1; }
91+ @$(PY_BEST ) -m venv venv || { echo " ❌ Failed to create venv" ; exit 1; }
7792 @if [ ! -x " $( VENV_BIN) /python" ]; then \
7893 echo " ❌ Error: venv creation failed - $( VENV_BIN) /python not found" ; \
7994 exit 1; \
@@ -88,7 +103,6 @@ install:
88103 @echo " 📦 Installing package (editable)"
89104 @$(DEV_ENV ) $(PIP ) install -e .
90105
91-
92106check :
93107 @PYTHON=$(PYTHON ) bash dev/run-checks.sh
94108 @$(MAKE ) lint
@@ -134,7 +148,7 @@ clean:
134148
135149info :
136150 @echo " Python (active): $$ ($( PYTHON) --version)"
137- @echo " Python (system ): $$ ($( PY_FIND ) --version 2>/dev/null || echo 'missing')"
151+ @echo " Python (best ): $$ ($( PY_BEST ) --version 2>/dev/null || echo 'missing')"
138152 @$(MAKE ) check-python
139153 @echo " Ruff: $$ ($( RUFF) --version 2>/dev/null || echo 'missing')"
140154 @echo " Pyright: $$ ($( PYTHON) -m pyright --version 2>/dev/null | head -1 || echo 'missing')"
@@ -145,10 +159,10 @@ info:
145159check-python :
146160 @if [ -x " $( VENV_BIN) /python" ]; then \
147161 VENV_VER=$$($(VENV_BIN ) /python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}') " 2>/dev/null || echo " unknown" ); \
148- SYS_VER =$$($(PY_FIND ) -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}') " 2>/dev/null || echo " unknown" ); \
149- if [ -n " $$ VENV_VER" ] && [ -n " $$ SYS_VER " ] && [ " $$ VENV_VER" != " $$ SYS_VER " ]; then \
150- echo " ⚠️ WARNING: venv Python ($$ VENV_VER) != system Python ($$ SYS_VER )" ; \
151- echo " Run 'make clean-venv && make dev' to recreate venv with system Python " ; \
162+ BEST_VER =$$($(PY_BEST ) -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}') " 2>/dev/null || echo " unknown" ); \
163+ if [ -n " $$ VENV_VER" ] && [ -n " $$ BEST_VER " ] && [ " $$ VENV_VER" != " $$ BEST_VER " ]; then \
164+ echo " ⚠️ WARNING: venv Python ($$ VENV_VER) != best available Python ($$ BEST_VER )" ; \
165+ echo " Run 'make clean-venv && make dev' to recreate venv if desired " ; \
152166 fi ; \
153167 fi
154168
@@ -163,7 +177,6 @@ cpp-test:
163177 if command -v ninja > /dev/null 2>&1 ; then GEN_ARGS=" -G Ninja" ; fi ; \
164178 cmake -S . -B " $$ BUILD_DIR" -DNETGRAPH_CORE_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release $$ GEN_ARGS; \
165179 cmake --build " $$ BUILD_DIR" --config Release -j; \
166- # Determine parallelism
167180 if command -v sysctl > /dev/null 2>&1 ; then \
168181 NPROC=$$(sysctl -n hw.ncpu 2>/dev/null || echo 2 ) ; \
169182 elif command -v nproc > /dev/null 2>&1 ; then \
@@ -175,7 +188,7 @@ cpp-test:
175188
176189cov :
177190 @echo " 📦 Reinstalling with C++ coverage instrumentation..."
178- @$(PIP ) install -U scikit-build-core " pybind11>=3 "
191+ @$(PIP ) install -U scikit-build-core " pybind11>=2.11 "
179192 @PIP_NO_BUILD_ISOLATION=1 CMAKE_ARGS=" -DNETGRAPH_CORE_COVERAGE=ON" $(PIP ) install -e .' [dev]'
180193 @echo " 🧪 Running Python tests with coverage..."
181194 @mkdir -p build/coverage
187200 if command -v ninja > /dev/null 2>&1 ; then GEN_ARGS=" -G Ninja" ; fi ; \
188201 cmake -S . -B " $$ BUILD_DIR" -DNETGRAPH_CORE_BUILD_TESTS=ON -DNETGRAPH_CORE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug $$ GEN_ARGS; \
189202 cmake --build " $$ BUILD_DIR" --config Debug -j; \
190- ctest --test-dir " $$ BUILD_DIR" --output-on-failure || true
203+ ctest --test-dir " $$ BUILD_DIR" --output-on-failure || echo " ⚠️ Some C++ tests failed (continuing for coverage) "
191204 @echo " 📈 Generating C++ coverage XML (gcovr)..."
192205 @$(PYTHON ) -m gcovr --root . \
193206 --object-directory build \
201214 @echo " "
202215 @echo " ✅ Coverage ready in build/coverage/: coverage-python.xml, coverage-cpp.xml, coverage-combined.html"
203216
204- .PHONY : sanitize-test
205217sanitize-test :
206218 @echo " 🧪 Building and running C++ tests with sanitizers..."
207219 @BUILD_DIR=" build/cpp-sanitize" ; \
@@ -210,10 +222,10 @@ sanitize-test:
210222 if command -v ninja > /dev/null 2>&1 ; then GEN_ARGS=" -G Ninja" ; fi ; \
211223 cmake -S . -B " $$ BUILD_DIR" -DNETGRAPH_CORE_BUILD_TESTS=ON -DNETGRAPH_CORE_SANITIZE=ON -DCMAKE_BUILD_TYPE=Debug $$ GEN_ARGS; \
212224 cmake --build " $$ BUILD_DIR" --config Debug -j; \
213- ASAN_OPTIONS=detect_leaks=1 ctest --test-dir " $$ BUILD_DIR" --output-on-failure || true
225+ ASAN_OPTIONS=detect_leaks=1 ctest --test-dir " $$ BUILD_DIR" --output-on-failure || echo " ⚠️ Some sanitizer tests failed "
214226
215227# Clean + reinstall in dev mode (respects CMAKE_ARGS and MACOSX_DEPLOYMENT_TARGET)
216- # Always uses system Python to avoid venv version mismatches
228+ # Uses active PYTHON (venv or PATH) to avoid environment mismatches
217229rebuild : clean
218- @echo " 🔨 Rebuilding with system Python : $( PY_FIND ) "
219- @$(DEV_ENV ) $(PY_FIND ) -m pip install -e .' [dev]'
230+ @echo " 🔨 Rebuilding with: $( PYTHON ) "
231+ @$(DEV_ENV ) $(PIP ) install -e .' [dev]'
0 commit comments