build(cross): macOS cross compilation

macos cross compilation now works. macos binaries can be compiled on
linux with osxcross installed and built
This commit is contained in:
Emily Boudreaux
2025-12-01 13:28:25 -05:00
parent e260c7b02c
commit e0a05bbd1a
15 changed files with 264 additions and 44 deletions

2
.gitignore vendored
View File

@@ -119,3 +119,5 @@ meson-boost-test/
*.json *.json
*.xml *.xml
*_pynucastro_network.py *_pynucastro_network.py
cross/python_includes

View File

@@ -1,7 +1,8 @@
cmake = import('cmake') cmake = import('cmake')
subdir('fourdst')
subdir('python')
subdir('fourdst')
subdir('sundials') subdir('sundials')
subdir('cppad') subdir('cppad')

View File

@@ -0,0 +1,16 @@
py_installation = import('python').find_installation('python3', pure: false)
if meson.is_cross_build() and host_machine.system() == 'darwin'
py_ver = get_option('python-target-version')
message('Cross build on Darwin, using python version ' + py_ver)
py_inc_dir = include_directories('../../cross/python_includes/python-' + py_ver + '/include/python' + py_ver)
py_dep = declare_dependency(include_directories: py_inc_dir)
py_module_prefix = ''
py_module_suffic = 'so'
meson.override_dependency('python3', py_dep)
else
py_dep = py_installation.dependency()
py_module_prefix = ''
py_module_suffic = 'so'
endif

View File

@@ -6,9 +6,8 @@ cvode_cmake_options.add_cmake_defines({
'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations', 'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations',
'BUILD_SHARED_LIBS' : 'OFF', 'BUILD_SHARED_LIBS' : 'OFF',
'BUILD_STATIC_LIBS' : 'ON', 'BUILD_STATIC_LIBS' : 'ON',
'CMAKE_BUILD_WITH_INSTALL_RPATH': 'ON',
'EXAMPLES_ENABLE_C': 'OFF', 'EXAMPLES_ENABLE_C': 'OFF',
'CMAKE_POSITION_INDEPENDENT_CODE': 'ON' 'CMAKE_POSITION_INDEPENDENT_CODE': true
}) })
@@ -22,29 +21,56 @@ cvode_sp = cmake.subproject(
options: cvode_cmake_options, options: cvode_cmake_options,
) )
# For the core SUNDIALS library (SUNContext, etc.) sundials_core_tgt = cvode_sp.target('sundials_core_static')
sundials_core_dep = cvode_sp.dependency('sundials_core_static') sundials_cvode_tgt = cvode_sp.target('sundials_cvode_static')
sundials_nvecserial_tgt = cvode_sp.target('sundials_nvecserial_static')
sundials_sunmatrixdense_tgt = cvode_sp.target('sundials_sunmatrixdense_static')
sundials_sunlinsoldense_tgt = cvode_sp.target('sundials_sunlinsoldense_static')
# For the CVODE integrator library cvode_objs = [
sundials_cvode_dep = cvode_sp.dependency('sundials_cvode_static') sundials_core_tgt.extract_all_objects(recursive: true),
sundials_cvode_tgt.extract_all_objects(recursive: true),
sundials_nvecserial_tgt.extract_all_objects(recursive: true),
sundials_sunmatrixdense_tgt.extract_all_objects(recursive: true),
sundials_sunlinsoldense_tgt.extract_all_objects(recursive: true),
]
# For the serial NVector library sundials_core_includes = cvode_sp.include_directories('sundials_core_static')
sundials_nvecserial_dep = cvode_sp.dependency('sundials_nvecserial_static') sundials_cvode_includes = cvode_sp.include_directories('sundials_cvode_static')
sundials_nvecserial_includes = cvode_sp.include_directories('sundials_nvecserial_static')
sundials_sunmatrixdense_includes = cvode_sp.include_directories('sundials_sunmatrixdense_static')
sundials_sunlinsoldense_includes = cvode_sp.include_directories('sundials_sunlinsoldense_static')
# For the dense matrix library cvode_includes = [
sundials_sunmatrixdense_dep = cvode_sp.dependency('sundials_sunmatrixdense_static') sundials_core_includes,
sundials_cvode_includes,
sundials_nvecserial_includes,
sundials_sunmatrixdense_includes,
sundials_sunlinsoldense_includes
]
# For the dense linear solver library
sundials_sunlinsoldense_dep = cvode_sp.dependency('sundials_sunlinsoldense_static')
cvode_dep = declare_dependency( empty_cvode_file = configure_file(
dependencies: [ output: 'cvode_dummy_ar.cpp',
sundials_core_dep, command: ['echo'],
sundials_cvode_dep, capture: true
sundials_nvecserial_dep, )
sundials_sunmatrixdense_dep,
sundials_sunlinsoldense_dep,
],
libcvode_static = static_library(
'cvode-static',
empty_cvode_file,
objects: cvode_objs,
include_directories: cvode_includes,
pic: true,
install: false
)
cvode_dep = declare_dependency(
link_with: libcvode_static,
include_directories: cvode_includes,
) )

View File

@@ -7,9 +7,8 @@ kinsol_cmake_options.add_cmake_defines({
'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations', 'CMAKE_C_FLAGS' : '-Wno-deprecated-declarations',
'BUILD_SHARED_LIBS' : 'OFF', 'BUILD_SHARED_LIBS' : 'OFF',
'BUILD_STATIC_LIBS' : 'ON', 'BUILD_STATIC_LIBS' : 'ON',
'CMAKE_BUILD_WITH_INSTALL_RPATH': 'ON',
'EXAMPLES_ENABLE_C' : 'OFF', 'EXAMPLES_ENABLE_C' : 'OFF',
'CMAKE_POSITION_INDEPENDENT_CODE': 'ON' 'CMAKE_POSITION_INDEPENDENT_CODE': true
}) })
kinsol_cmake_options.add_cmake_defines({ kinsol_cmake_options.add_cmake_defines({
@@ -22,11 +21,31 @@ kinsol_sp = cmake.subproject(
options: kinsol_cmake_options, options: kinsol_cmake_options,
) )
sundials_kinsol_shared = kinsol_sp.dependency('sundials_kinsol_static') sundials_kinsol_static_tgt = kinsol_sp.target('sundials_kinsol_obj_static')
kinsol_includes = kinsol_sp.include_directories('sundials_kinsol_obj_static')
kinsol_dep = declare_dependency( kinsol_objs = [sundials_kinsol_static_tgt.extract_all_objects(recursive: false)]
dependencies: [
sundials_kinsol_shared, empty_kinsol_file = configure_file(
] output: 'kinsol_dummy_ar.cpp',
command: ['echo'],
capture: true
)
libkinsol_static = static_library(
'kinsol_static',
empty_kinsol_file,
objects: kinsol_objs,
include_directories: kinsol_includes,
pic: true,
install: false
) )
kinsol_dep = declare_dependency(
link_with: libkinsol_static,
include_directories: kinsol_includes
)

View File

@@ -1,7 +1,6 @@
# --- Python Extension Setup ---
py_installation = import('python').find_installation('python3', pure: false)
gridfire_py_deps = [ gridfire_py_deps = [
py_dep,
pybind11_dep, pybind11_dep,
const_dep, const_dep,
config_dep, config_dep,
@@ -9,9 +8,7 @@ gridfire_py_deps = [
gridfire_dep gridfire_dep
] ]
py_mod = py_installation.extension_module( py_sources = [
'_gridfire', # Name of the generated .so/.pyd file (without extension)
sources: [
meson.project_source_root() + '/src/python/bindings.cpp', meson.project_source_root() + '/src/python/bindings.cpp',
meson.project_source_root() + '/src/python/types/bindings.cpp', meson.project_source_root() + '/src/python/types/bindings.cpp',
meson.project_source_root() + '/src/python/partition/bindings.cpp', meson.project_source_root() + '/src/python/partition/bindings.cpp',
@@ -29,11 +26,28 @@ py_mod = py_installation.extension_module(
meson.project_source_root() + '/src/python/policy/bindings.cpp', meson.project_source_root() + '/src/python/policy/bindings.cpp',
meson.project_source_root() + '/src/python/policy/trampoline/py_policy.cpp', meson.project_source_root() + '/src/python/policy/trampoline/py_policy.cpp',
meson.project_source_root() + '/src/python/utils/bindings.cpp', meson.project_source_root() + '/src/python/utils/bindings.cpp',
], ]
if meson.is_cross_build() and host_machine.system() == 'darwin'
py_mod = shared_module(
'_gridfire',
sources: py_sources,
dependencies: gridfire_py_deps,
name_prefix: '',
name_suffix: 'so',
install: true,
install_dir: py_installation.get_install_dir() + '/gridfire'
)
else
py_mod = py_installation.extension_module(
'_gridfire', # Name of the generated .so/.pyd file (without extension)
sources: py_sources,
dependencies : gridfire_py_deps, dependencies : gridfire_py_deps,
install : true, install : true,
subdir: 'gridfire', subdir: 'gridfire',
) )
endif
py_installation.install_sources( py_installation.install_sources(

View File

@@ -4,13 +4,16 @@ cpp = 'arm64-apple-darwin25-clang++'
ar = 'arm64-apple-darwin25-ar' ar = 'arm64-apple-darwin25-ar'
strip = 'arm64-apple-darwin25-strip' strip = 'arm64-apple-darwin25-strip'
pkg-config = 'pkg-config' pkg-config = 'pkg-config'
ranlib = '/usr/bin/true'
[host-machine] [host_machine]
system = 'darwin' system = 'darwin'
cpu_family = 'aarch64' cpu_family = 'aarch64'
cpi = 'arm64' cpu = 'arm64'
endian = 'little' endian = 'little'
[built-in options] [built-in options]
c_args = ['-mmacosx-version-min=15.0'] c_args = ['-mmacosx-version-min=15.0']
cpp_args = ['-mmacos-version-min=15.0'] cpp_args = ['-mmacos-version-min=15.0']
c_link_args = ['-mmacosx-version-min=15.0']
cpp_link_args = ['-mmacos-version-min=15.0']

View File

@@ -30,6 +30,7 @@ message('Found CXX compiler: ' + meson.get_compiler('cpp').get_id())
message('C++ standard set to: ' + get_option('cpp_std')) message('C++ standard set to: ' + get_option('cpp_std'))
cppc = meson.get_compiler('cpp') cppc = meson.get_compiler('cpp')
cc = meson.get_compiler('c')
if cppc.get_id() == 'clang' if cppc.get_id() == 'clang'
@@ -72,6 +73,11 @@ if not cppc.has_header('format')
endif endif
ignore_unused_args = '-Wno-unused-command-line-argument'
add_global_arguments(ignore_unused_args, language: 'cpp')
add_global_arguments(ignore_unused_args, language: 'c')
# For Eigen # For Eigen

View File

@@ -5,3 +5,4 @@ option('build-tests', type: 'boolean', value: true, description: 'build the test
option('build-fortran', type: 'boolean', value: false, description: 'build fortran module support') option('build-fortran', type: 'boolean', value: false, description: 'build fortran module support')
option('unsafe-fortran', type: 'boolean', value: false, description: 'Allow untested fortran compilers (compilers other than gfortran)') option('unsafe-fortran', type: 'boolean', value: false, description: 'Allow untested fortran compilers (compilers other than gfortran)')
option('unity-safe', type: 'boolean', value: false, description: 'Enable safe unity builds for better compatibility across different compilers and platforms') option('unity-safe', type: 'boolean', value: false, description: 'Enable safe unity builds for better compatibility across different compilers and platforms')
option('python-target-version', type: 'string', value: '3.13', description: 'Target version for python compilation, only used for cross compilation')

View File

@@ -51,6 +51,7 @@ libgridfire = library('gridfire',
gridfire_sources, gridfire_sources,
include_directories: include_directories('include'), include_directories: include_directories('include'),
dependencies: gridfire_build_dependencies, dependencies: gridfire_build_dependencies,
objects: [cvode_objs, kinsol_objs],
install : true) install : true)
gridfire_dep = declare_dependency( gridfire_dep = declare_dependency(

View File

@@ -4,7 +4,6 @@
#include "gridfire/gridfire.h" #include "gridfire/gridfire.h"
#include "fourdst/composition/composition.h" #include "fourdst/composition/composition.h"
#include "fourdst/plugin/bundle/bundle.h"
#include "fourdst/logging/logging.h" #include "fourdst/logging/logging.h"
#include "fourdst/atomic/species.h" #include "fourdst/atomic/species.h"
#include "fourdst/composition/utils.h" #include "fourdst/composition/utils.h"

View File

@@ -1,5 +1,5 @@
executable( executable(
'graphnet_sandbox', 'graphnet_sandbox',
'main.cpp', 'main.cpp',
dependencies: [gridfire_dep, composition_dep, plugin_dep, cli11_dep], dependencies: [gridfire_dep, composition_dep, cli11_dep],
) )

View File

@@ -0,0 +1,132 @@
#!/bin/bash
# --- Configuration ---
PYTHON_VERSIONS=("3.8.10" "3.9.13" "3.10.11" "3.11.9" "3.12.3" "3.13.0" "3.14.0")
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_OUTPUT_DIR="$SCRIPT_DIR/../../cross/python_includes"
# --- OS Detection ---
OS="$(uname -s)"
echo "Detected OS: $OS"
# --- Dependency Check ---
check_dependencies() {
if [ "$OS" == "Linux" ]; then
if ! command -v 7z &> /dev/null; then
echo "Error: '7z' (p7zip-full) is required on Linux."
exit 1
fi
if ! command -v cpio &> /dev/null; then
echo "Error: 'cpio' is required."
exit 1
fi
fi
}
# --- Extraction Logic (OS Specific) ---
extract_pkg() {
local pkg_file="$1"
local extract_root="$2"
local major_ver="$3" # e.g., 3.11
echo " -> Extracting..."
if [ "$OS" == "Darwin" ]; then
pkgutil --expand "$pkg_file" "$extract_root/expanded"
local payload_path="$extract_root/expanded/Python_Framework.pkg/Payload"
if [ ! -f "$payload_path" ]; then
echo " -> Error: Could not find Payload in package."
return 1
fi
mkdir -p "$extract_root/root"
pushd "$extract_root/root" > /dev/null
cat "$payload_path" | gunzip | cpio -id "*include/python${major_ver}/*" 2>/dev/null
popd > /dev/null
else
7z x "$pkg_file" -o"$extract_root/expanded" -y > /dev/null
local payload_path="$extract_root/expanded/Python_Framework.pkg/Payload"
if [ ! -f "$payload_path" ]; then
echo " -> Error: Could not find Payload in package."
return 1
fi
mkdir -p "$extract_root/root"
pushd "$extract_root/root" > /dev/null
cat "$payload_path" | gunzip | cpio -id "*include/python${major_ver}/*" 2>/dev/null
popd > /dev/null
fi
}
check_dependencies
mkdir -p "$BASE_OUTPUT_DIR"
for FULL_VER in "${PYTHON_VERSIONS[@]}"; do
MAJOR_VER=$(echo "$FULL_VER" | cut -d. -f1,2)
TARGET_DIR="$BASE_OUTPUT_DIR/python-$MAJOR_VER"
TEMP_DIR="$BASE_OUTPUT_DIR/tmp_$FULL_VER"
PKG_NAME="python-${FULL_VER}-macos11.pkg"
if [[ "$MAJOR_VER" == "3.8" ]]; then
PKG_NAME="python-${FULL_VER}-macosx10.9.pkg"
fi
DOWNLOAD_URL="https://www.python.org/ftp/python/${FULL_VER}/$PKG_NAME"
echo "Processing Python $FULL_VER..."
if [ -d "$TARGET_DIR" ] && [ "$(ls -A $TARGET_DIR)" ]; then
echo " -> Headers already exist in $TARGET_DIR. Skipping."
continue
fi
mkdir -p "$TEMP_DIR"
echo " -> Downloading from $DOWNLOAD_URL"
curl -L -s -o "$TEMP_DIR/python.pkg" "$DOWNLOAD_URL"
if [ $? -ne 0 ]; then
echo " -> Download failed! Check version number or internet connection."
rm -rf "$TEMP_DIR"
continue
fi
# 2. Extract
extract_pkg "$TEMP_DIR/python.pkg" "$TEMP_DIR" "$MAJOR_VER"
# 3. Move Headers to Final Location
# The cpio extraction usually results in: ./Versions/X.Y/include/pythonX.Y
# We want to move that specific include folder to our target dir
FOUND_HEADERS=$(find "$TEMP_DIR/root" -type d -path "*/include/python${MAJOR_VER}" | head -n 1)
if [ -n "$FOUND_HEADERS" ]; then
echo " -> Found headers at: $FOUND_HEADERS"
# Move the content to the final destination
# We want the folder to be .../python-3.11/include/python3.11
mkdir -p "$TARGET_DIR/include"
mv "$FOUND_HEADERS" "$TARGET_DIR/include/"
# Verify pyconfig.h exists (sanity check)
if [ -f "$TARGET_DIR/include/python${MAJOR_VER}/pyconfig.h" ]; then
echo " -> Success: Headers installed to $TARGET_DIR"
else
echo " -> Warning: Header move seemed successful, but pyconfig.h is missing."
fi
else
echo " -> Error: Could not locate header files after extraction."
fi
# 4. Cleanup
rm -rf "$TEMP_DIR"
echo "---------------------------------------------------"
done
echo "Done. All headers stored in $BASE_OUTPUT_DIR"