Skip to content

Commit 5dac939

Browse files
committed
Migrate to CMake-based build system
- Add c_src/CMakeLists.txt for cross-platform builds - Add c_src/CMake/FindErlang.cmake module - Add do_cmake.sh and do_build.sh scripts - Update rebar.config with CMake pre/post hooks - Update FreeBSD CI to use cmake instead of gmake - Remove Windows support (not tested) This fixes FreeBSD builds which failed due to BSD make vs GNU make incompatibility. CMake handles cross-platform builds correctly.
1 parent ae3df61 commit 5dac939

6 files changed

Lines changed: 267 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
release: "14.1"
9797
usesh: true
9898
prepare: |
99-
pkg install -y erlang python311 gmake gcc
99+
pkg install -y erlang python311 cmake
100100
run: |
101101
# Set up environment
102102
export PYTHON_CONFIG=python3.11-config
@@ -112,7 +112,7 @@ jobs:
112112
fetch https://github.com/erlang/rebar3/releases/download/3.24.0/rebar3 -o rebar3
113113
chmod +x rebar3
114114
115-
# Compile and test
115+
# Compile and test (uses CMake-based build)
116116
./rebar3 compile
117117
./rebar3 ct --readable=compact
118118
./rebar3 dialyzer

c_src/CMake/FindErlang.cmake

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Find Erlang
2+
# This module finds if Erlang is installed and determines where the
3+
# include files and libraries are. This code sets the following variables:
4+
#
5+
# ERLANG_RUNTIME = the full path to the Erlang runtime
6+
# ERLANG_COMPILE = the full path to the Erlang compiler
7+
# ERLANG_EI_PATH = the full path to the Erlang erl_interface path
8+
# ERLANG_ERTS_PATH = the full path to the Erlang erts path
9+
# ERLANG_EI_INCLUDE_PATH = /include appended to ERLANG_EI_PATH
10+
# ERLANG_EI_LIBRARY_PATH = /lib appended to ERLANG_EI_PATH
11+
# ERLANG_ERTS_INCLUDE_PATH = /include appended to ERLANG_ERTS_PATH
12+
# ERLANG_ERTS_LIBRARY_PATH = /lib appended to ERLANG_ERTS_PATH
13+
#
14+
15+
SET(ERLANG_BIN_PATH
16+
$ENV{ERLANG_HOME}/bin
17+
/opt/bin
18+
/sw/bin
19+
/usr/bin
20+
/usr/local/bin
21+
/opt/local/bin
22+
/opt/local/lib/erlang/bin
23+
)
24+
25+
FIND_PROGRAM(ERLANG_RUNTIME
26+
NAMES erl
27+
PATHS ${ERLANG_BIN_PATH}
28+
)
29+
30+
FIND_PROGRAM(ERLANG_COMPILE
31+
NAMES erlc
32+
PATHS ${ERLANG_BIN_PATH}
33+
)
34+
35+
EXECUTE_PROCESS(COMMAND
36+
erl -noshell -eval "io:format(\"~s\", [code:lib_dir()])" -s erlang halt
37+
OUTPUT_VARIABLE ERLANG_OTP_LIB_DIR)
38+
39+
EXECUTE_PROCESS(COMMAND
40+
erl -noshell -eval "io:format(\"~s\", [code:root_dir()])" -s erlang halt
41+
OUTPUT_VARIABLE ERLANG_OTP_ROOT_DIR)
42+
43+
MESSAGE(STATUS "Using OTP lib: ${ERLANG_OTP_LIB_DIR}")
44+
45+
EXECUTE_PROCESS(COMMAND
46+
erl -noshell -eval "io:format(\"~s\",[filename:basename(code:lib_dir('erl_interface'))])" -s erlang halt
47+
OUTPUT_VARIABLE ERLANG_EI_DIR)
48+
49+
EXECUTE_PROCESS(COMMAND
50+
erl -noshell -eval "io:format(\"~s\",[filename:basename(code:lib_dir('erts'))])" -s erlang halt
51+
OUTPUT_VARIABLE ERLANG_ERTS_DIR)
52+
53+
MESSAGE(STATUS "Using erl_interface version: ${ERLANG_EI_DIR}")
54+
MESSAGE(STATUS "Using erts version: ${ERLANG_ERTS_DIR}")
55+
56+
SET(ERLANG_EI_PATH ${ERLANG_OTP_LIB_DIR}/${ERLANG_EI_DIR})
57+
SET(ERLANG_EI_INCLUDE_PATH ${ERLANG_OTP_LIB_DIR}/${ERLANG_EI_DIR}/include)
58+
SET(ERLANG_EI_LIBRARY_PATH ${ERLANG_OTP_LIB_DIR}/${ERLANG_EI_DIR}/lib)
59+
60+
SET(ERLANG_ERTS_PATH ${ERLANG_OTP_ROOT_DIR}/${ERLANG_ERTS_DIR})
61+
SET(ERLANG_ERTS_INCLUDE_PATH ${ERLANG_OTP_ROOT_DIR}/${ERLANG_ERTS_DIR}/include)
62+
SET(ERLANG_ERTS_LIBRARY_PATH ${ERLANG_OTP_ROOT_DIR}/${ERLANG_ERTS_DIR}/lib)
63+
64+
MARK_AS_ADVANCED(
65+
ERLANG_RUNTIME
66+
ERLANG_COMPILE
67+
ERLANG_EI_PATH
68+
ERLANG_EI_INCLUDE_PATH
69+
ERLANG_EI_LIBRARY_PATH
70+
)

c_src/CMakeLists.txt

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
2+
3+
project(ErlangPythonNIF C)
4+
5+
# CMake policies
6+
if(POLICY CMP0028)
7+
cmake_policy(SET CMP0028 NEW)
8+
endif()
9+
if(POLICY CMP0054)
10+
cmake_policy(SET CMP0054 NEW)
11+
endif()
12+
if(POLICY CMP0074)
13+
cmake_policy(SET CMP0074 NEW)
14+
endif()
15+
16+
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMake")
17+
18+
# Add standard package manager paths for macOS (MacPorts and Homebrew)
19+
if(APPLE)
20+
# MacPorts
21+
if(EXISTS "/opt/local")
22+
list(APPEND CMAKE_PREFIX_PATH "/opt/local")
23+
list(APPEND CMAKE_INCLUDE_PATH "/opt/local/include")
24+
list(APPEND CMAKE_LIBRARY_PATH "/opt/local/lib")
25+
endif()
26+
# Homebrew on Apple Silicon
27+
if(EXISTS "/opt/homebrew")
28+
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
29+
list(APPEND CMAKE_INCLUDE_PATH "/opt/homebrew/include")
30+
list(APPEND CMAKE_LIBRARY_PATH "/opt/homebrew/lib")
31+
endif()
32+
# Homebrew on Intel (default location)
33+
if(EXISTS "/usr/local/include")
34+
list(APPEND CMAKE_PREFIX_PATH "/usr/local")
35+
list(APPEND CMAKE_INCLUDE_PATH "/usr/local/include")
36+
list(APPEND CMAKE_LIBRARY_PATH "/usr/local/lib")
37+
endif()
38+
endif()
39+
40+
# Output directory
41+
set(priv_dir "${PROJECT_SOURCE_DIR}/../priv")
42+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${priv_dir})
43+
file(MAKE_DIRECTORY ${priv_dir})
44+
45+
# Build type
46+
if(NOT CMAKE_BUILD_TYPE)
47+
set(CMAKE_BUILD_TYPE Release)
48+
endif()
49+
50+
# Find Erlang
51+
include(FindErlang)
52+
include_directories(${ERLANG_ERTS_INCLUDE_PATH})
53+
54+
# Find Python - use the PYTHON_CONFIG env variable if set
55+
if(DEFINED ENV{PYTHON_CONFIG})
56+
set(Python3_CONFIG "$ENV{PYTHON_CONFIG}")
57+
execute_process(
58+
COMMAND ${Python3_CONFIG} --prefix
59+
OUTPUT_VARIABLE Python3_PREFIX
60+
OUTPUT_STRIP_TRAILING_WHITESPACE
61+
)
62+
execute_process(
63+
COMMAND ${Python3_CONFIG} --includes
64+
OUTPUT_VARIABLE Python3_INCLUDE_FLAGS
65+
OUTPUT_STRIP_TRAILING_WHITESPACE
66+
)
67+
execute_process(
68+
COMMAND ${Python3_CONFIG} --ldflags --embed
69+
OUTPUT_VARIABLE Python3_LINK_FLAGS
70+
OUTPUT_STRIP_TRAILING_WHITESPACE
71+
RESULT_VARIABLE Python3_EMBED_RESULT
72+
)
73+
if(NOT Python3_EMBED_RESULT EQUAL 0)
74+
execute_process(
75+
COMMAND ${Python3_CONFIG} --ldflags
76+
OUTPUT_VARIABLE Python3_LINK_FLAGS
77+
OUTPUT_STRIP_TRAILING_WHITESPACE
78+
)
79+
endif()
80+
81+
# Extract include directories from flags
82+
string(REGEX MATCHALL "-I[^ ]+" Python3_INCLUDE_DIRS_LIST "${Python3_INCLUDE_FLAGS}")
83+
string(REGEX REPLACE "-I" "" Python3_INCLUDE_DIRS_LIST "${Python3_INCLUDE_DIRS_LIST}")
84+
85+
message(STATUS "Using Python config: ${Python3_CONFIG}")
86+
message(STATUS "Python includes: ${Python3_INCLUDE_FLAGS}")
87+
message(STATUS "Python link flags: ${Python3_LINK_FLAGS}")
88+
else()
89+
# Use CMake's FindPython3
90+
find_package(Python3 REQUIRED COMPONENTS Development)
91+
set(Python3_INCLUDE_DIRS_LIST ${Python3_INCLUDE_DIRS})
92+
set(Python3_LINK_FLAGS "${Python3_LIBRARIES}")
93+
endif()
94+
95+
# Create the NIF shared library
96+
add_library(py_nif MODULE py_nif.c)
97+
98+
# Set output name
99+
set_target_properties(py_nif PROPERTIES
100+
PREFIX ""
101+
OUTPUT_NAME "py_nif"
102+
)
103+
104+
# Include directories
105+
target_include_directories(py_nif PRIVATE
106+
${ERLANG_ERTS_INCLUDE_PATH}
107+
${Python3_INCLUDE_DIRS_LIST}
108+
)
109+
110+
# Compiler flags
111+
target_compile_options(py_nif PRIVATE
112+
-O2
113+
-Wall
114+
-fPIC
115+
)
116+
117+
# Platform-specific settings
118+
if(APPLE)
119+
target_link_options(py_nif PRIVATE
120+
-undefined dynamic_lookup
121+
-flat_namespace
122+
)
123+
# Add CoreFoundation framework
124+
target_link_libraries(py_nif PRIVATE "-framework CoreFoundation")
125+
elseif(UNIX)
126+
# Linux/FreeBSD/etc
127+
target_link_options(py_nif PRIVATE
128+
-Wl,--export-dynamic
129+
)
130+
# dlopen for loading libpython with RTLD_GLOBAL
131+
target_link_libraries(py_nif PRIVATE dl)
132+
endif()
133+
134+
# Link Python
135+
if(DEFINED ENV{PYTHON_CONFIG})
136+
# Parse the link flags and add them properly
137+
separate_arguments(Python3_LINK_FLAGS_LIST UNIX_COMMAND "${Python3_LINK_FLAGS}")
138+
target_link_libraries(py_nif PRIVATE ${Python3_LINK_FLAGS_LIST})
139+
else()
140+
target_link_libraries(py_nif PRIVATE Python3::Python)
141+
endif()
142+
143+
# Threads
144+
find_package(Threads REQUIRED)
145+
target_link_libraries(py_nif PRIVATE Threads::Threads)
146+
147+
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
148+
message(STATUS "Output directory: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")

do_build.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/sh -x
2+
3+
cd _build/cmake
4+
5+
if type cmake3 > /dev/null 2>&1 ; then
6+
CMAKE=cmake3
7+
else
8+
CMAKE=cmake
9+
fi
10+
11+
case "$@" in
12+
*-j*)
13+
${CMAKE} --build . -- "$@" || exit 1
14+
;;
15+
*)
16+
CORES=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null)
17+
18+
if [ "x$CORES" = "x" ]; then
19+
PAR=""
20+
else
21+
PAR="-- -j $CORES"
22+
fi
23+
${CMAKE} --build . $PAR $@ || exit 1
24+
;;
25+
esac
26+
27+
echo done.

do_cmake.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/sh -x
2+
3+
mkdir -p _build/cmake
4+
cd _build/cmake
5+
6+
if type cmake3 > /dev/null 2>&1 ; then
7+
CMAKE=cmake3
8+
else
9+
CMAKE=cmake
10+
fi
11+
12+
${CMAKE} "$@" ../../c_src || exit 1
13+
14+
echo done.

rebar.config

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
{deps, []}.
1212

1313
{pre_hooks, [
14-
{"(linux|darwin|freebsd|netbsd|openbsd)", compile, "make -C c_src"},
15-
{"win32", compile, "nmake /F c_src\\Makefile.win"}
14+
{clean, "rm -f priv/*.so"},
15+
{clean, "rm -rf _build/cmake"},
16+
{"(linux|darwin|solaris)", compile, "./do_cmake.sh"},
17+
{"(freebsd|netbsd|openbsd)", compile, "./do_cmake.sh"}
1618
]}.
1719

1820
{post_hooks, [
19-
{"(linux|darwin|freebsd|netbsd|openbsd)", clean, "make -C c_src clean"}
21+
{"(linux|darwin|solaris)", compile, "./do_build.sh"},
22+
{"(freebsd|netbsd|openbsd)", compile, "./do_build.sh"}
2023
]}.
2124

2225
{shell, [

0 commit comments

Comments
 (0)