Current section
Files
Jump to
Current section
Files
c_src/CMakeLists.txt
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
project(ErlangPythonNIF C)
# CMake policies
if(POLICY CMP0028)
cmake_policy(SET CMP0028 NEW)
endif()
if(POLICY CMP0054)
cmake_policy(SET CMP0054 NEW)
endif()
if(POLICY CMP0074)
cmake_policy(SET CMP0074 NEW)
endif()
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMake")
# Add standard package manager paths for macOS (MacPorts and Homebrew)
if(APPLE)
# MacPorts
if(EXISTS "/opt/local")
list(APPEND CMAKE_PREFIX_PATH "/opt/local")
list(APPEND CMAKE_INCLUDE_PATH "/opt/local/include")
list(APPEND CMAKE_LIBRARY_PATH "/opt/local/lib")
endif()
# Homebrew on Apple Silicon
if(EXISTS "/opt/homebrew")
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
list(APPEND CMAKE_INCLUDE_PATH "/opt/homebrew/include")
list(APPEND CMAKE_LIBRARY_PATH "/opt/homebrew/lib")
endif()
# Homebrew on Intel (default location)
if(EXISTS "/usr/local/include")
list(APPEND CMAKE_PREFIX_PATH "/usr/local")
list(APPEND CMAKE_INCLUDE_PATH "/usr/local/include")
list(APPEND CMAKE_LIBRARY_PATH "/usr/local/lib")
endif()
endif()
# Output directory
set(priv_dir "${PROJECT_SOURCE_DIR}/../priv")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${priv_dir})
file(MAKE_DIRECTORY ${priv_dir})
# Build type
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
# Performance build option (for maximum optimization)
option(PERF_BUILD "Enable aggressive performance optimizations (-O3, LTO, native arch)" OFF)
# Sanitizer options for debugging race conditions and memory issues
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)
if(ENABLE_ASAN)
message(STATUS "AddressSanitizer enabled")
add_compile_options(-fsanitize=address -fno-omit-frame-pointer -g -O1)
add_link_options(-fsanitize=address)
# ASan is incompatible with TSan
if(ENABLE_TSAN)
message(FATAL_ERROR "ASan and TSan cannot be used together")
endif()
endif()
if(ENABLE_TSAN)
message(STATUS "ThreadSanitizer enabled")
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer -g -O1)
add_link_options(-fsanitize=thread)
endif()
if(ENABLE_UBSAN)
message(STATUS "UndefinedBehaviorSanitizer enabled")
add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer -g -O1)
add_link_options(-fsanitize=undefined)
endif()
if(PERF_BUILD)
message(STATUS "Performance build enabled - using aggressive optimizations")
# Override compiler flags for maximum performance
set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG")
# Enable Link-Time Optimization
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
# Find Erlang
include(FindErlang)
include_directories(${ERLANG_ERTS_INCLUDE_PATH})
# Find Python using CMake's built-in FindPython3
#
# To specify a particular Python installation, set PYTHON_CONFIG env variable:
# PYTHON_CONFIG=/opt/local/bin/python3.14-config cmake ...
#
# CMake will use its default search order otherwise.
if(DEFINED ENV{PYTHON_CONFIG})
# Extract prefix from python-config for hinting
execute_process(
COMMAND $ENV{PYTHON_CONFIG} --prefix
OUTPUT_VARIABLE Python3_ROOT_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(Python3_FIND_STRATEGY LOCATION)
endif()
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
message(STATUS "Python3 executable: ${Python3_EXECUTABLE}")
message(STATUS "Python3 version: ${Python3_VERSION}")
message(STATUS "Python3 include dirs: ${Python3_INCLUDE_DIRS}")
message(STATUS "Python3 libraries: ${Python3_LIBRARIES}")
message(STATUS "Python3 library: ${Python3_LIBRARY}")
# Detect Python features for worker pool optimization
# We use both version checks and compile tests to verify actual API availability
include(CheckCSourceCompiles)
# First check Python version - subinterpreters with OWN_GIL require Python 3.12+
if(Python3_VERSION VERSION_GREATER_EQUAL "3.12")
message(STATUS "Python ${Python3_VERSION} >= 3.12, checking subinterpreter API...")
# Save and set required variables for compile test
set(CMAKE_REQUIRED_INCLUDES ${Python3_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES Python3::Python)
# Clear any cached result to ensure fresh detection
unset(HAVE_SUBINTERPRETERS CACHE)
# Check for subinterpreter API with per-interpreter GIL (PEP 684, Python 3.12+)
# This verifies PyInterpreterConfig and PyInterpreterConfig_OWN_GIL are available
check_c_source_compiles("
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int main(void) {
PyInterpreterConfig config = {
.use_main_obmalloc = 0,
.allow_fork = 0,
.allow_exec = 0,
.allow_threads = 1,
.allow_daemon_threads = 0,
.check_multi_interp_extensions = 1,
.gil = PyInterpreterConfig_OWN_GIL,
};
(void)config;
return 0;
}
" HAVE_SUBINTERPRETERS)
if(HAVE_SUBINTERPRETERS)
message(STATUS "Subinterpreter API detected (PyInterpreterConfig_OWN_GIL available)")
# OWN_GIL mode requires Python 3.14+ due to C extension global state bugs
# in 3.12/3.13 (e.g., _decimal). See https://github.com/python/cpython/issues/106078
if(Python3_VERSION VERSION_GREATER_EQUAL "3.14")
set(HAVE_OWNGIL TRUE)
message(STATUS "Python ${Python3_VERSION} >= 3.14, OWN_GIL mode enabled")
else()
set(HAVE_OWNGIL FALSE)
message(STATUS "Python ${Python3_VERSION} < 3.14, OWN_GIL mode disabled (C extension bugs)")
endif()
else()
message(STATUS "Subinterpreter API compile test failed, using shared GIL fallback")
set(HAVE_OWNGIL FALSE)
endif()
else()
message(STATUS "Python ${Python3_VERSION} < 3.12, subinterpreter API not available")
set(HAVE_SUBINTERPRETERS FALSE)
set(HAVE_OWNGIL FALSE)
endif()
# Check for free-threaded Python (Python 3.13+ with --disable-gil / nogil build)
# Free-threaded builds have Py_GIL_DISABLED defined in sysconfig
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import sysconfig; print('yes' if sysconfig.get_config_var('Py_GIL_DISABLED') else 'no')"
OUTPUT_VARIABLE Python3_FREE_THREADED
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(Python3_FREE_THREADED STREQUAL "yes")
set(HAVE_FREE_THREADED TRUE)
message(STATUS "Free-threaded Python detected: GIL disabled at runtime")
else()
set(HAVE_FREE_THREADED FALSE)
endif()
# Create the NIF shared library
add_library(py_nif MODULE py_nif.c)
# Pass Python library path to NIF for dlopen
# Python3_LIBRARY is the full path to the Python shared library
if(Python3_LIBRARY)
target_compile_definitions(py_nif PRIVATE PYTHON_LIBRARY_PATH="${Python3_LIBRARY}")
message(STATUS "Using Python library path for dlopen: ${Python3_LIBRARY}")
elseif(Python3_LIBRARIES)
# Fallback to first library in the list
list(GET Python3_LIBRARIES 0 Python3_FIRST_LIB)
target_compile_definitions(py_nif PRIVATE PYTHON_LIBRARY_PATH="${Python3_FIRST_LIB}")
message(STATUS "Using Python library path for dlopen: ${Python3_FIRST_LIB}")
endif()
# Add Python feature compile definitions for worker pool optimization
if(HAVE_SUBINTERPRETERS)
target_compile_definitions(py_nif PRIVATE HAVE_SUBINTERPRETERS=1)
endif()
if(HAVE_OWNGIL)
target_compile_definitions(py_nif PRIVATE HAVE_OWNGIL=1)
endif()
if(HAVE_FREE_THREADED)
target_compile_definitions(py_nif PRIVATE HAVE_FREE_THREADED=1)
endif()
# Set output name
set_target_properties(py_nif PROPERTIES
PREFIX ""
OUTPUT_NAME "py_nif"
)
# Include directories
target_include_directories(py_nif PRIVATE
${ERLANG_ERTS_INCLUDE_PATH}
${Python3_INCLUDE_DIRS}
)
# Compiler flags
if(PERF_BUILD)
# Performance build: aggressive optimizations
target_compile_options(py_nif PRIVATE
-O3
-Wall
-fPIC
-march=native
-ffast-math
-funroll-loops
)
else()
# Standard build
target_compile_options(py_nif PRIVATE
-O2
-Wall
-fPIC
)
endif()
# Platform-specific settings
if(APPLE)
target_link_options(py_nif PRIVATE
-undefined dynamic_lookup
-flat_namespace
)
# Add CoreFoundation framework
target_link_libraries(py_nif PRIVATE "-framework CoreFoundation")
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR
CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
# BSD systems
target_link_options(py_nif PRIVATE
-Wl,--export-dynamic
)
# No need to link libdl on BSD - dlopen is in libc
elseif(UNIX)
# Linux
target_link_options(py_nif PRIVATE
-Wl,--export-dynamic
)
# dlopen for loading libpython with RTLD_GLOBAL
target_link_libraries(py_nif PRIVATE dl)
endif()
# Link Python
target_link_libraries(py_nif PRIVATE Python3::Python)
# Threads
find_package(Threads REQUIRED)
target_link_libraries(py_nif PRIVATE Threads::Threads)
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Output directory: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")