Compare commits

...

4 Commits

Author SHA1 Message Date
0759ff6d9c docs(version): first gridfire release version (v1.0.0)
This version is the version which is scientifically published and which includes the lower friction install system with pip
2026-06-12 16:38:07 -04:00
2fca1674f8 fix(fourdst): pinned fourdst to v0.10.6 for ABI compatibility
this version pins pybind11 to v3.0.0
2026-06-12 16:36:47 -04:00
5502b0ac80 build(omp): added explicit omp prefix override
this is useful when building omp compatible wheels on mac which bundle libomp with them
2026-06-12 14:49:19 -04:00
61cd7359d4 build(python): gridfire uses fourdst wheel in python mode 2026-06-12 14:30:59 -04:00
18 changed files with 513 additions and 339 deletions

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = GridFire
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = v0.7.6rc4.2
PROJECT_NUMBER = v1.0.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewers a

View File

@@ -61,7 +61,6 @@ version_sufficient = required_min == '' ? true : compiler_version.version_compar
# --- failure analysis and reporting ----------------------------------------
if toolchain_functional and not version_sufficient
# Works in practice; don't break a functioning setup over a number.
warning(toolchain_desc + ' is below the minimum GridFire tests against ('
+ required_min + '), but all C++23 capability probes passed. '
+ 'Proceeding; if you hit compiler errors deep in the build, '
@@ -69,7 +68,6 @@ if toolchain_functional and not version_sufficient
endif
if not toolchain_functional
# 1) Name the failure precisely.
failure_detail = ''
if not have_print_hdr
failure_detail += '\n * C++ standard library header <print> not found.'
@@ -83,14 +81,11 @@ if not toolchain_functional
if not version_sufficient
failure_detail += '\n * ' + toolchain_desc + ' is below the required minimum (' + required_min + ').'
elif compiler_id == 'clang' and not is_apple_clang
# New-enough clang but probes failed: almost always the C++ stdlib
# underneath it, not clang itself.
failure_detail += ('\n * clang itself is new enough; on Linux clang uses the system '
+ 'libstdc++, so the GNU C++ runtime is likely too old. Install GCC >= '
+ gridfire_gcc_min + ' (clang will pick up its libstdc++), or use -Dcpp_args=-stdlib=libc++ with libc++ >= 17 installed.')
endif
# 2) Search for a suitable already-installed alternate.
candidate_names = []
if compiler_id == 'gcc'
candidate_names += ['g++-16', 'g++-15', 'g++-14', 'clang++-21', 'clang++-20', 'clang++-19', 'clang++-18', 'clang++-17']
@@ -110,7 +105,6 @@ if not toolchain_functional
p = find_program(cand, required: false)
if p.found()
cand_ver = p.version()
# Decide the applicable minimum from the candidate's family.
cand_min = cand.contains('clang') ? gridfire_clang_min : gridfire_gcc_min
if cand_ver != 'unknown' and cand_ver.version_compare('>=' + cand_min)
candidates_report += '\n [OK] ' + p.full_path() + ' (version ' + cand_ver + ')'
@@ -123,7 +117,6 @@ if not toolchain_functional
endif
endforeach
# 3) OS-specific install guidance.
os_help = ''
if host_machine.system() == 'darwin'
os_help = '''
@@ -160,7 +153,6 @@ How to get a suitable compiler on Linux:''' + distro_hint + '''
os_help = '\nInstall GCC >= ' + gridfire_gcc_min + ' or LLVM clang >= ' + gridfire_clang_min + ' for your platform.'
endif
# 4) Assemble the verdict.
if suitable_cxx != ''
action = ('\nA suitable compiler IS already installed. Meson cannot switch compilers '
+ 'after configuration starts, so re-run setup pointing at it:\n\n'
@@ -181,18 +173,9 @@ How to get a suitable compiler on Linux:''' + distro_hint + '''
+ action)
endif
# --- everything below unchanged from the original check ---------------------
# For Eigen
add_project_arguments('-Wno-deprecated-declarations', language: 'cpp')
if get_option('build_python')
message('enabling hidden visibility for C++ symbols when building Python extension. This reduces the size of the resulting shared library.')
add_project_arguments('-fvisibility=hidden', language: 'cpp')
else
message('enabling default visibility for C++ symbols')
add_project_arguments('-fvisibility=default', language: 'cpp')
endif
add_project_arguments('-fvisibility=default', language: 'cpp')
if get_option('openmp_support')
gridfire_args += ['-DGF_USE_OPENMP']

View File

@@ -1,82 +1,74 @@
fourdst_build_lib_all = true
if not get_option('plugin_support')
fourdst_build_lib_all=false
message('Disabling fourdst plugin support as per user request.')
endif
fourdst_default_options = [
'build_tests=' + get_option('build_tests').to_string(),
'build_python=' + get_option('build_python').to_string(),
'build_lib_all=' + fourdst_build_lib_all.to_string(),
'build_lib_comp=true',
'build_lib_config=true',
'build_lib_log=true',
'build_lib_const=true',
'pkg_config=' + get_option('pkg_config').to_string(),
]
if get_option('build_python')
fourdst_default_options += ['default_library=static']
endif
fourdst_inc_probe = run_command(py_installation, '-c',
'import fourdst; print("\\n".join(fourdst.get_include_dirs()))',
check: false)
if fourdst_inc_probe.returncode() != 0
error('Could not interrogate the fourdst wheel:\n' + fourdst_inc_probe.stderr()
+ '\nIs fourdst installed in the build environment?')
endif
fourdst_inc_dirs = fourdst_inc_probe.stdout().strip().split('\n')
fourdst_sp = subproject('fourdst', default_options: fourdst_default_options)
fourdst_lib_probe = run_command(py_installation, '-c',
'import fourdst; print("\\n".join(fourdst.get_lib_dirs()))',
check: false)
if fourdst_lib_probe.returncode() != 0
error('Could not interrogate the fourdst wheel:\n' + fourdst_lib_probe.stderr())
endif
fourdst_lib_dirs = fourdst_lib_probe.stdout().strip().split('\n')
fourdst_inc_args = []
foreach d : fourdst_inc_dirs
fourdst_inc_args += ['-I' + d]
endforeach
composition_dep = fourdst_sp.get_variable('composition_dep')
log_dep = fourdst_sp.get_variable('log_dep')
const_dep = fourdst_sp.get_variable('const_dep')
config_dep = fourdst_sp.get_variable('config_dep')
if get_option('plugin_support')
warning('Including plugin library from fourdst. Note this will bring in minizip-ng and openssl, which can cause build issues with cross compilation due to their complexity.')
plugin_dep = fourdst_sp.get_variable('plugin_dep')
endif
cpp = meson.get_compiler('cpp')
comp_lib = cpp.find_library('composition', dirs: fourdst_lib_dirs)
log_lib = cpp.find_library('logging', dirs: fourdst_lib_dirs)
const_lib = cpp.find_library('const', dirs: fourdst_lib_dirs)
refl_lib = cpp.find_library('reflect_cpp', dirs: fourdst_lib_dirs)
libcomposition = fourdst_sp.get_variable('libcomposition')
libconst = fourdst_sp.get_variable('libconst')
liblogging = fourdst_sp.get_variable('liblogging')
composition_dep = declare_dependency(compile_args: fourdst_inc_args, dependencies: [comp_lib, refl_lib])
log_dep = declare_dependency(compile_args: fourdst_inc_args, dependencies: log_lib)
const_dep = declare_dependency(compile_args: fourdst_inc_args, dependencies: const_lib)
config_dep = declare_dependency(compile_args: fourdst_inc_args) # header-only libconfig
else
fourdst_build_lib_all = true
if not get_option('plugin_support')
fourdst_build_lib_all=false
message('Disabling fourdst plugin support as per user request.')
endif
if get_option('plugin_support')
warning('Including plugin library from fourdst. Note this will bring in minizip-ng and openssl, which can cause build issues with cross compilation due to their complexity.')
libplugin = fourdst_sp.get_variable('libplugin')
endif
if get_option('build_python')
sp_root = meson.project_source_root() / 'subprojects'
fourdst_header_trees = [
['config',
sp_root / 'libconfig' / 'src' / 'config' / 'include' / 'fourdst' / 'config',
gridfire_includedir / 'fourdst'],
['composition',
sp_root / 'libcomposition' / 'src' / 'composition' / 'include' / 'fourdst' / 'composition',
gridfire_includedir / 'fourdst'],
['atomic',
sp_root / 'libcomposition' / 'src' / 'composition' / 'include' / 'fourdst' / 'atomic',
gridfire_includedir / 'fourdst'],
['constants',
sp_root / 'libconstants' / 'src' / 'constants' / 'include' / 'fourdst' / 'constants',
gridfire_includedir / 'fourdst'],
['logging',
sp_root / 'liblogging' / 'src' / 'logging' / 'include' / 'fourdst' / 'logging',
gridfire_includedir / 'fourdst'],
['toml++',
sp_root / 'libconfig' / 'build-config' / 'tomlpp' / 'vendor' / 'include' / 'toml++',
gridfire_fourdst_vendor_includedir],
['quill',
sp_root / 'quill' / 'include' / 'quill',
gridfire_fourdst_vendor_includedir],
['CLI',
sp_root / 'CLI11-2.6.1' / 'include' / 'CLI',
gridfire_fourdst_vendor_includedir],
fourdst_default_options = [
'build_tests=' + get_option('build_tests').to_string(),
'build_python=' + get_option('build_python').to_string(),
'build_lib_all=' + fourdst_build_lib_all.to_string(),
'build_lib_comp=true',
'build_lib_config=true',
'build_lib_log=true',
'build_lib_const=true',
'pkg_config=' + get_option('pkg_config').to_string(),
]
foreach t : fourdst_header_trees
custom_target(
'wheel_headers_' + t[0].underscorify(),
command: copytree_cmd + [t[1], '@OUTPUT@'],
output: t[0],
install: true,
install_dir: t[2],
)
endforeach
if get_option('build_python')
fourdst_default_options += ['default_library=static']
endif
fourdst_sp = subproject('fourdst', default_options: fourdst_default_options)
composition_dep = fourdst_sp.get_variable('composition_dep')
log_dep = fourdst_sp.get_variable('log_dep')
const_dep = fourdst_sp.get_variable('const_dep')
config_dep = fourdst_sp.get_variable('config_dep')
if get_option('plugin_support')
warning('Including plugin library from fourdst. Note this will bring in minizip-ng and openssl, which can cause build issues with cross compilation due to their complexity.')
plugin_dep = fourdst_sp.get_variable('plugin_dep')
endif
libcomposition = fourdst_sp.get_variable('libcomposition')
libconst = fourdst_sp.get_variable('libconst')
liblogging = fourdst_sp.get_variable('liblogging')
if get_option('plugin_support')
warning('Including plugin library from fourdst. Note this will bring in minizip-ng and openssl, which can cause build issues with cross compilation due to their complexity.')
libplugin = fourdst_sp.get_variable('libplugin')
endif
endif

View File

@@ -36,28 +36,16 @@ if get_option('build_python')
]
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_rpath: gridfire_ext_rpath,
install_dir: py_installation.get_install_dir() + '/gridfire'
)
else
py_mod = py_installation.extension_module(
'_gridfire',
sources: py_sources,
dependencies : gridfire_py_deps,
install : true,
install_rpath: gridfire_ext_rpath,
subdir: 'gridfire',
)
endif
py_mod = py_installation.extension_module(
'_gridfire',
sources: py_sources,
dependencies: gridfire_py_deps,
install: true,
link_args: gridfire_ext_rpath_args,
build_rpath: gridfire_ext_rpath,
install_rpath: gridfire_ext_rpath,
subdir: 'gridfire',
)
py_installation.install_sources(
files(

View File

@@ -18,7 +18,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# *********************************************************************** #
project('GridFire', ['c', 'cpp'], version: 'v0.7.6rc4.2', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0')
project('GridFire', ['c', 'cpp'], version: 'v1.0.0', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0')
gridfire_args = []

View File

@@ -13,3 +13,4 @@ option('openmp_support', type: 'boolean', value: false, description: 'Enable Ope
option('use_mimalloc', type: 'boolean', value: true, description: 'Use mimalloc as the memory allocator for GridFire. Generally this is ~10% faster than the system allocator.')
option('build_benchmarks', type: 'boolean', value: false, description: 'build the benchmark suite')
option('asan', type: 'boolean', value: false, description: 'Enable AddressSanitizer (ASan) support for detecting memory errors')
option('libomp_prefix', type: 'string', value: '', description: 'Explicit prefix of an LLVM OpenMP runtime to use instead of auto-detection (macOS wheel builds). Empty = dependency(\'openmp\') auto-detection.')

View File

@@ -1,108 +0,0 @@
#!/bin/bash
# pip_install_mac_patch.sh - Workaround for meson-python duplicate RPATH bug on macOS
set -e
# Color codes for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
# Returns 0 if the Mach-O binary at $1 has duplicate LC_RPATH entries.
has_duplicate_rpaths() {
local binary="$1"
local rpaths dup
rpaths=$(otool -l "$binary" | awk '/cmd LC_RPATH/{getline; getline; print $2}')
dup=$(printf '%s\n' "$rpaths" | sort | uniq -d)
[ -n "$dup" ]
}
echo -e "${YELLOW}"
echo "========================================================================="
echo " INSTALLATION + DUPLICATE-RPATH SAFETY NET (macOS)"
echo "========================================================================="
echo -e "${NC}"
echo ""
echo "This script installs gridfire with pip and then checks the installed"
echo "extension modules for duplicate LC_RPATH entries (a meson-python bug"
echo "exposed by macOS 26.1, see:"
echo " https://github.com/mesonbuild/meson-python/issues/813 )."
echo ""
echo "With the current self-contained wheel layout the bug should not"
echo "trigger; binaries are only patched if duplicates are actually found."
echo ""
echo -e "${YELLOW}Continue? [y/N]${NC} "
read -r response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
echo -e "${RED}Installation cancelled.${NC}"
exit 1
fi
echo ""
echo -e "${GREEN}Step 1: Finding current Python environment...${NC}"
PYTHON_BIN=$(which python3)
if [ -z "$PYTHON_BIN" ]; then
echo -e "${RED}Error: python3 not found in PATH${NC}"
exit 1
fi
echo "Using Python: $PYTHON_BIN"
PYTHON_VERSION=$($PYTHON_BIN --version)
echo "Python version: $PYTHON_VERSION"
SITE_PACKAGES=$($PYTHON_BIN -c "import site; print(site.getsitepackages()[0])")
echo "Site packages: $SITE_PACKAGES"
echo ""
echo -e "${GREEN}Step 2: Installing gridfire with pip...${NC}"
$PYTHON_BIN -m pip install . -v --no-build-isolation
if [ $? -ne 0 ]; then
echo -e "${RED}Error: pip install failed${NC}"
exit 1
fi
echo ""
FIX_SCRIPT="build-python/fix_rpaths.py"
check_and_fix() {
local label="$1" so_file="$2"
if [ -z "$so_file" ]; then
echo -e "${YELLOW}Skipping ${label}: extension module not found (package may not be installed).${NC}"
return 0
fi
echo "Found ${label} extension module: $so_file"
if has_duplicate_rpaths "$so_file"; then
echo -e "${YELLOW}Duplicate LC_RPATH entries detected in ${label}; applying fix...${NC}"
if [ ! -f "$FIX_SCRIPT" ]; then
echo -e "${RED}Error: $FIX_SCRIPT not found${NC}"
echo "Please run this script from the project root directory."
exit 1
fi
$PYTHON_BIN "$FIX_SCRIPT" "$so_file"
else
echo -e "${GREEN}No duplicate LC_RPATH entries in ${label}; no patch needed.${NC}"
fi
echo ""
}
echo -e "${GREEN}Step 3: Checking installed extension modules...${NC}"
GRIDFIRE_SO=$(find "$SITE_PACKAGES/gridfire" -name "_gridfire.cpython-*-darwin.so" 2>/dev/null | head -n 1)
check_and_fix "gridfire" "$GRIDFIRE_SO"
FOURDST_SO=$(find "$SITE_PACKAGES/fourdst" -name "_phys.cpython-*-darwin.so" 2>/dev/null | head -n 1)
check_and_fix "fourdst" "$FOURDST_SO"
echo -e "${GREEN}=========================================================================${NC}"
echo -e "${GREEN} Installation Complete!${NC}"
echo -e "${GREEN}=========================================================================${NC}"
echo ""
echo "Test the installation with:"
echo " $PYTHON_BIN -c 'import gridfire; print(gridfire.__version__)'"
echo ""

View File

@@ -1,14 +1,10 @@
[build-system]
requires = [
"meson-python>=0.19.0",
"meson>=1.9.1",
"pybind11>=2.10"
]
requires = ["meson-python>=0.19.0", "meson>=1.9.1", "pybind11==3.0.0", "fourdst==0.10.5"]
build-backend = "mesonpy"
[project]
name = "gridfire"
version = "v0.7.6rc4.2"
dynamic = ["version"]
description = "Python interface to the GridFire nuclear network code"
readme = "README.md"
license = { file = "LICENSE.txt" }
@@ -21,6 +17,8 @@ maintainers = [
{name = "Emily M. Boudreaux", email = "emily@boudreauxmail.com"}
]
dependencies = ["fourdst==0.10.6"]
[tool.meson-python.args]
setup = [
'-Ddefault_library=static',

View File

@@ -130,7 +130,7 @@ struct MultiscalePartitioningEngineViewScratchPad final : AbstractScratchPad {
* @brief Check whether the scratchpad has been initialized.
* @return true if initialized with a valid SUNContext, false otherwise.
*/
[[nodiscard]] bool is_initialized() const override { return has_initialized; }
[[nodiscard]] bool is_initialized() const override;
/**
* @brief Initialize the scratchpad by creating a SUNDIALS context.
@@ -150,16 +150,7 @@ struct MultiscalePartitioningEngineViewScratchPad final : AbstractScratchPad {
* SUNContext ctx = scratch.sun_ctx;
* @endcode
*/
void initialize() {
if (has_initialized) return;
const int flag = SUNContext_Create(SUN_COMM_NULL, &sun_ctx);
if (flag != 0) {
throw std::runtime_error("Failed to create SUNContext in MultiscalePartitioningEngineViewScratchPad.");
}
has_initialized = true;
}
void initialize();
/**
* @brief Destructor that properly releases SUNDIALS resources.
@@ -167,13 +158,7 @@ struct MultiscalePartitioningEngineViewScratchPad final : AbstractScratchPad {
* Clears all QSE solvers before freeing the SUNContext to ensure
* proper cleanup order and avoid dangling references.
*/
~MultiscalePartitioningEngineViewScratchPad() override {
qse_solvers.clear();
if (sun_ctx != nullptr) {
SUNContext_Free(&sun_ctx);
sun_ctx = nullptr;
}
}
~MultiscalePartitioningEngineViewScratchPad() override;
/**
* @brief Create a partial copy of this scratchpad.
@@ -196,20 +181,7 @@ struct MultiscalePartitioningEngineViewScratchPad final : AbstractScratchPad {
*
* @endcode
*/
[[nodiscard]] std::unique_ptr<AbstractScratchPad> clone() const override {
auto clone_pad = std::make_unique<MultiscalePartitioningEngineViewScratchPad>();
clone_pad->qse_groups = this->qse_groups;
clone_pad->dynamic_species = this->dynamic_species;
clone_pad->algebraic_species = this->algebraic_species;
clone_pad->composition_cache = this->composition_cache;
clone_pad->initialize();
clone_pad->qse_solvers.reserve(this->qse_solvers.size());
for (const auto& solver : qse_solvers) {
clone_pad->qse_solvers.push_back(solver->clone(clone_pad->sun_ctx)); // Must rebind context to new SUNContext
}
return clone_pad;
}
[[nodiscard]] std::unique_ptr<AbstractScratchPad> clone() const override;
};
} // namespace gridfire::engine::scratch

View File

@@ -0,0 +1,52 @@
#include "gridfire/engine/views/engine_multiscale.h"
#include "gridfire/engine/scratchpads/scratchpad_abstract.h"
#include "gridfire/engine/scratchpads/types.h"
#include "gridfire/engine/scratchpads/engine_multiscale_scratchpad.h"
#include "fourdst/atomic/atomicSpecies.h"
#include <vector>
#include <memory>
#include <unordered_map>
#include "sundials/sundials_context.h"
namespace gridfire::engine::scratch {
bool MultiscalePartitioningEngineViewScratchPad::is_initialized() const{ return has_initialized; }
void MultiscalePartitioningEngineViewScratchPad::initialize() {
if (has_initialized) return;
const int flag = SUNContext_Create(SUN_COMM_NULL, &sun_ctx);
if (flag != 0) {
throw std::runtime_error("Failed to create SUNContext in MultiscalePartitioningEngineViewScratchPad.");
}
has_initialized = true;
}
MultiscalePartitioningEngineViewScratchPad::~MultiscalePartitioningEngineViewScratchPad() {
qse_solvers.clear();
if (sun_ctx != nullptr) {
SUNContext_Free(&sun_ctx);
sun_ctx = nullptr;
}
}
std::unique_ptr<AbstractScratchPad> MultiscalePartitioningEngineViewScratchPad::clone() const {
auto clone_pad = std::make_unique<MultiscalePartitioningEngineViewScratchPad>();
clone_pad->qse_groups = this->qse_groups;
clone_pad->dynamic_species = this->dynamic_species;
clone_pad->algebraic_species = this->algebraic_species;
clone_pad->composition_cache = this->composition_cache;
clone_pad->initialize();
clone_pad->qse_solvers.reserve(this->qse_solvers.size());
for (const auto& solver : qse_solvers) {
clone_pad->qse_solvers.push_back(solver->clone(clone_pad->sun_ctx)); // Must rebind context to new SUNContext
}
return clone_pad;
}
}

View File

@@ -12,6 +12,7 @@ gridfire_sources = files(
'lib/engine/procedures/construction.cpp',
'lib/engine/diagnostics/dynamic_engine_diagnostics.cpp',
'lib/engine/types/jacobian.cpp',
'lib/engine/scratchpads/engine_multiscale_scratchpad.cpp',
'lib/reaction/reaction.cpp',
'lib/reaction/reaclib.cpp',
'lib/reaction/weak/weak.cpp',
@@ -56,7 +57,16 @@ if get_option('plugin_support')
endif
if get_option('openmp_support')
openmp_dep = dependency('openmp', required: true)
libomp_prefix = get_option('libomp_prefix')
if libomp_prefix != ''
omp_lib = cpp.find_library('omp', dirs: [libomp_prefix + '/lib'])
openmp_dep = declare_dependency(
compile_args: ['-Xpreprocessor', '-fopenmp', '-I' + libomp_prefix + '/include'],
dependencies: omp_lib,
)
else
openmp_dep = dependency('openmp', required: true)
endif
gridfire_build_dependencies += [openmp_dep]
endif
@@ -67,39 +77,97 @@ gridfire_link_args = cpp.get_supported_link_arguments(
)
if get_option('build_python')
gridfire_link_whole += [libcomposition, libconst, liblogging]
if get_option('plugin_support')
gridfire_link_whole += [libplugin]
error('plugin_support is not available when build_python=true: '
+ 'the fourdst wheel does not ship libplugin, and bundling it '
+ 'would break cross-package type compatibility.')
endif
# ---- rpaths ---------------------------------------------------------
# Wheel layout (both packages share one site-packages):
# <site-packages>/gridfire/_gridfire.<tag>.so
# <site-packages>/gridfire/lib/libgridfire.{so,dylib}
# <site-packages>/fourdst/lib/lib{composition,logging,const}.*
# <site-packages>/fourdst/lib/vendor/libreflect_cpp.*
#
# libgridfire (in gridfire/lib/) needs: itself-dir, ../../fourdst/lib{,/vendor}
# _gridfire (in gridfire/) needs: ./lib, ../fourdst/lib{,/vendor}
#
# Platform split:
# * macOS / Mach-O: LC_RPATH cannot hold colon-separated lists,
# meson carries a user-supplied build_rpath as a single string, and
# install_rpath is only applied by `meson install` (which
# meson-python never runs). So on darwin we emit one raw
# `-Wl,-rpath,<path>` link arg per path, and deliberately do NOT
# set build_rpath/install_rpath (a real `meson install` would then
# add install_rpath on top of the link args, producing duplicate
# LC_RPATH entries, which macOS 26+ dyld treats as a hard load
# error).
# * Linux / ELF: colon-joined DT_RUNPATH is the native format, and
# install_rpath is baked into the binary at link time, so the
# kwargs work for both meson-python wheels and `meson install`.
#
# NOTE: compose paths with '+', never the '/' join operator —
# 'x' / '/y' silently discards 'x' in Meson.
#
# gridfire_ext_rpath_args / gridfire_ext_rpath are consumed later by
# build-python/meson.build for the _gridfire extension module (this
# subdir is processed before build-python in the root meson.build).
if host_machine.system() == 'darwin'
gridfire_lib_rpath_args = [
'-Wl,-rpath,@loader_path',
'-Wl,-rpath,@loader_path/../../fourdst/lib',
'-Wl,-rpath,@loader_path/../../fourdst/lib/vendor',
]
gridfire_lib_rpath = ''
gridfire_ext_rpath_args = [
'-Wl,-rpath,@loader_path/lib',
'-Wl,-rpath,@loader_path/../fourdst/lib',
'-Wl,-rpath,@loader_path/../fourdst/lib/vendor',
]
gridfire_ext_rpath = ''
else
gridfire_lib_rpath_args = []
gridfire_lib_rpath = '$ORIGIN:' + '$ORIGIN/../../fourdst/lib:' + '$ORIGIN/../../fourdst/lib/vendor'
gridfire_ext_rpath_args = []
gridfire_ext_rpath = '$ORIGIN/lib:' + '$ORIGIN/../fourdst/lib:' + '$ORIGIN/../fourdst/lib/vendor'
endif
libgridfire = shared_library('gridfire',
gridfire_sources,
include_directories: include_directories('include'),
dependencies: gridfire_build_dependencies,
cpp_args: gridfire_args,
link_whole: gridfire_link_whole,
link_args: gridfire_link_args,
install: true,
install_dir: gridfire_libdir)
gridfire_sources,
include_directories: include_directories('include'),
dependencies: gridfire_build_dependencies,
cpp_args: gridfire_args,
link_whole: gridfire_link_whole,
link_args: gridfire_link_args + gridfire_lib_rpath_args,
install: true,
install_dir: gridfire_libdir,
build_rpath: gridfire_lib_rpath,
install_rpath: gridfire_lib_rpath,
)
else
libgridfire = library('gridfire',
gridfire_sources,
include_directories: include_directories('include'),
dependencies: gridfire_build_dependencies,
link_whole: gridfire_link_whole,
link_args: gridfire_link_args,
cpp_args: gridfire_args,
install : true)
gridfire_sources,
include_directories: include_directories('include'),
dependencies: gridfire_build_dependencies,
link_whole: gridfire_link_whole,
link_args: gridfire_link_args,
cpp_args: gridfire_args,
install : true
)
endif
if get_option('build_python')
gridfire_iface_deps = []
foreach d : gridfire_build_dependencies
gridfire_iface_deps += d.partial_dependency(compile_args: true, includes: true)
endforeach
gridfire_iface_dep = declare_dependency(
dependencies: gridfire_build_dependencies,
).partial_dependency(compile_args: true, includes: true)
gridfire_dep = declare_dependency(
include_directories: include_directories('include'),
link_with: libgridfire,
dependencies: gridfire_iface_deps,
dependencies: [gridfire_iface_dep],
compile_args: gridfire_args,
)
else
@@ -114,8 +182,8 @@ endif
meson.override_dependency('gridfire', gridfire_dep)
install_subdir('include/gridfire',
install_dir: gridfire_includedir,
exclude_files: ['utils/config.h.in'],
install_dir: gridfire_includedir,
exclude_files: ['utils/config.h.in'],
)
@@ -126,4 +194,3 @@ if get_option('build_c_api')
message('Configuring C API...')
subdir('extern')
endif

View File

@@ -1,3 +1,13 @@
try:
import fourdst as fst
except ImportError as e:
raise ImportError(
"gridfire requires the fourdst package (its C++ types and shared "
"libraries come from there). pip install fourdst."
) from e
from ._gridfire import *
from ._gridfire import *
import sys
@@ -70,4 +80,49 @@ def gf_credits():
"Emily M. Boudreaux - Lead Developer",
"Aaron Dotter - Co-Developer",
"4D-STAR Collaboration - Contributors"
]
]
import os
from pathlib import Path
from typing import List
_PACKAGE_DIR = Path(__file__).resolve().parent
def gf_get_include_dirs():
return [
os.fspath(_PACKAGE_DIR / "include"),
os.fspath(_PACKAGE_DIR / "include" / "gridfire" / "vendor"),
]
def gf_get_lib_dirs():
return [
os.fspath(_PACKAGE_DIR / "lib"),
]
def gf_get_rpath_flags() -> List[str]:
return ["-Wl,-rpath," + os.fspath(_PACKAGE_DIR / "lib")]
def gf_get_lib_flags() -> List[str]:
flags = ["-L" + d for d in gf_get_lib_dirs()]
flags += ["-lgridfire"]
return flags
def gf_get_include_flags() -> List[str]:
return ["-I" + d for d in gf_get_include_dirs()]
def gf_get_extra_flags() -> List[str]:
return ['--std=c++23', '-fPIC']
def gf_compiler_flags(just_gridfire=False):
flags = []
if not just_gridfire:
flags.extend(fourdst_flags = fst.get_compiler_flags())
flags.extend(gf_get_rpath_flags())
flags.extend(gf_get_lib_flags())
flags.extend(gf_get_include_flags())
flags.extend(gf_get_extra_flags())
return flags
def gf_get_compiler_flags_formatted(just_gridfire=False) -> int:
flags = gf_compiler_flags(just_gridfire)
print(" ".join(flags))
return 0

View File

@@ -17,14 +17,6 @@ namespace py = pybind11;
void register_solver_bindings(const py::module &m) {
auto py_cvode_timestep_context = py::class_<gridfire::solver::PointSolverTimestepContext>(m, "PointSolverTimestepContext");
py_cvode_timestep_context.def_readonly("t", &gridfire::solver::PointSolverTimestepContext::t);
py_cvode_timestep_context.def_property_readonly(
"state",
[](const gridfire::solver::PointSolverTimestepContext& self) -> std::vector<double> {
const sunrealtype* nvec_data = N_VGetArrayPointer(self.state);
const sunindextype length = N_VGetLength(self.state);
return {nvec_data, nvec_data + length};
}
);
py_cvode_timestep_context.def_readonly("dt", &gridfire::solver::PointSolverTimestepContext::dt);
py_cvode_timestep_context.def_readonly("last_step_time", &gridfire::solver::PointSolverTimestepContext::last_step_time);
py_cvode_timestep_context.def_readonly("T9", &gridfire::solver::PointSolverTimestepContext::T9);
@@ -57,6 +49,20 @@ void register_solver_bindings(const py::module &m) {
return self.getPhysicalComposition();
}
);
py_cvode_timestep_context.def_property_readonly(
"rawState",
[](const gridfire::solver::PointSolverTimestepContext& self) -> std::vector<double> {
const std::span<const double> s = self.rawState();
return std::vector<double>(s.begin(), s.end());
}
);
py_cvode_timestep_context.def("abundance",
py::overload_cast<size_t>(&gridfire::solver::PointSolverTimestepContext::abundance, py::const_),
py::arg("species_index"));
py_cvode_timestep_context.def("abundance",
py::overload_cast<const fourdst::atomic::Species&>(&gridfire::solver::PointSolverTimestepContext::abundance, py::const_),
py::arg("species"));
py_cvode_timestep_context.def_property_readonly("accumulatedSpecificEnergy", &gridfire::solver::PointSolverTimestepContext::accumulatedSpecificEnergy);

View File

@@ -1,4 +1,4 @@
[wrap-git]
url = https://github.com/4D-STAR/fourdst
revision = v0.10.2
revision = v0.10.6
depth = 1

View File

@@ -4,5 +4,9 @@
#gtest_nomain_dep = dependency('gtest', main: false, required : true)
# Subdirectories for unit and integration tests
subdir('graphnet_sandbox')
subdir('extern')
if get_option('build_tests') and not get_option('build_python')
subdir('graphnet_sandbox')
subdir('extern')
else
message('Tests disabled by build options! To enable them build with build_tests = true and build_python=false')
endif

View File

@@ -1,12 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <git-repo-url>"
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <git-repo-url> [fourdst-wheels-dir]"
echo " fourdst-wheels-dir: optional local directory of fourdst wheels to"
echo " install from instead of PyPI (for bootstrapping a new fourdst+gridfire pair)"
exit 1
fi
REPO_URL="$1"
LOCAL_FOURDST_WHEELS="${2:-}"
WORK_DIR="$(pwd)"
WHEEL_DIR="${WORK_DIR}/wheels_linux_aarch64"
@@ -17,21 +20,58 @@ TMPDIR="$(mktemp -d)"
echo "➤ Cloning ${REPO_URL}${TMPDIR}/project"
git clone "${REPO_URL}" "${TMPDIR}/project"
DOCKER_MOUNTS=(-v "${WHEEL_DIR}":/io/wheels -v "${TMPDIR}/project":/io/project)
if [[ -n "${LOCAL_FOURDST_WHEELS}" ]]; then
DOCKER_MOUNTS+=(-v "${LOCAL_FOURDST_WHEELS}":/io/fourdst-wheels)
fi
for IMAGE in \
tboudreaux/manylinux_2_28_aarch64_boost_1_88_0:latest
do
docker run --rm \
-v "${WHEEL_DIR}":/io/wheels \
-v "${TMPDIR}/project":/io/project \
"${DOCKER_MOUNTS[@]}" \
"${IMAGE}" \
/bin/bash -eux -c '
cd /io/project
FOURDST_PIN="$(grep -oE "fourdst==[0-9][0-9a-zA-Z.]*" pyproject.toml | head -n1 || true)"
if [ -d /io/fourdst-wheels ]; then
export PIP_FIND_LINKS=/io/fourdst-wheels
fi
for PY in /opt/python/*/bin/python; do
"$PY" -m pip install --upgrade pip setuptools wheel meson meson-python
CC=clang CXX=clang++ "$PY" -m pip wheel . --config-settings=setup-args=-Dunity=on -w /io/wheels -vv
auditwheel repair /io/wheels/*.whl -w /io/wheels
BUILD_WHEEL_DIR="$(mktemp -d)"
CC=clang CXX=clang++ "$PY" -m pip wheel . \
--no-deps --config-settings=setup-args=-Dunity=on \
-w "$BUILD_WHEEL_DIR" -vv
CURRENT_WHEEL="$(find "$BUILD_WHEEL_DIR" -name "*.whl" | head -n1)"
if [ -n "$FOURDST_PIN" ]; then
"$PY" -m pip install --force-reinstall "$FOURDST_PIN"
FOURDST_LIB_PATH="$("$PY" -c "import fourdst, os; print(os.pathsep.join(fourdst.get_lib_dirs()))")"
LD_LIBRARY_PATH="$FOURDST_LIB_PATH" auditwheel repair \
--exclude "libcomposition.so*" \
--exclude "liblogging.so*" \
--exclude "libconst.so*" \
--exclude "libreflect_cpp.so*" \
-w /io/wheels "$CURRENT_WHEEL"
REPAIRED="$(ls -t /io/wheels/*.whl | head -n1)"
if unzip -l "$REPAIRED" | grep -E "libcomposition|liblogging|libconst[^a-z]|libreflect_cpp"; then
echo "ERROR: repaired wheel contains vendored fourdst libraries"
exit 1
fi
else
auditwheel repair -w /io/wheels "$CURRENT_WHEEL"
fi
rm -rf "$BUILD_WHEEL_DIR"
done
echo "Linux wheels ready in /io/wheels"
echo "Linux wheels ready in /io/wheels"
'
done
done

View File

@@ -1,12 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <git-repo-url>"
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <git-repo-url> [fourdst-wheels-dir]"
echo " fourdst-wheels-dir: optional local directory of fourdst wheels to"
echo " install from instead of PyPI (for bootstrapping a new fourdst+gridfire pair)"
exit 1
fi
REPO_URL="$1"
LOCAL_FOURDST_WHEELS="${2:-}"
WORK_DIR="$(pwd)"
WHEEL_DIR="${WORK_DIR}/wheels_linux_x86_64"
@@ -17,21 +20,58 @@ TMPDIR="$(mktemp -d)"
echo "➤ Cloning ${REPO_URL}${TMPDIR}/project"
git clone "${REPO_URL}" "${TMPDIR}/project"
DOCKER_MOUNTS=(-v "${WHEEL_DIR}":/io/wheels -v "${TMPDIR}/project":/io/project)
if [[ -n "${LOCAL_FOURDST_WHEELS}" ]]; then
DOCKER_MOUNTS+=(-v "${LOCAL_FOURDST_WHEELS}":/io/fourdst-wheels)
fi
for IMAGE in \
tboudreaux/manylinux_2_28_x86_64_boost_1_88_0:latest
do
docker run --rm \
-v "${WHEEL_DIR}":/io/wheels \
-v "${TMPDIR}/project":/io/project \
"${DOCKER_MOUNTS[@]}" \
"${IMAGE}" \
/bin/bash -eux -c '
cd /io/project
FOURDST_PIN="$(grep -oE "fourdst==[0-9][0-9a-zA-Z.]*" pyproject.toml | head -n1 || true)"
if [ -d /io/fourdst-wheels ]; then
export PIP_FIND_LINKS=/io/fourdst-wheels
fi
for PY in /opt/python/*/bin/python; do
"$PY" -m pip install --upgrade pip setuptools wheel meson meson-python
CC=clang CXX=clang++ "$PY" -m pip wheel . --config-settings=setup-args=-Dunity=on -w /io/wheels -vv
auditwheel repair /io/wheels/*.whl -w /io/wheels
BUILD_WHEEL_DIR="$(mktemp -d)"
CC=clang CXX=clang++ "$PY" -m pip wheel . \
--no-deps --config-settings=setup-args=-Dunity=on \
-w "$BUILD_WHEEL_DIR" -vv
CURRENT_WHEEL="$(find "$BUILD_WHEEL_DIR" -name "*.whl" | head -n1)"
if [ -n "$FOURDST_PIN" ]; then
"$PY" -m pip install --force-reinstall "$FOURDST_PIN"
FOURDST_LIB_PATH="$("$PY" -c "import fourdst, os; print(os.pathsep.join(fourdst.get_lib_dirs()))")"
LD_LIBRARY_PATH="$FOURDST_LIB_PATH" auditwheel repair \
--exclude "libcomposition.so*" \
--exclude "liblogging.so*" \
--exclude "libconst.so*" \
--exclude "libreflect_cpp.so*" \
-w /io/wheels "$CURRENT_WHEEL"
REPAIRED="$(ls -t /io/wheels/*.whl | head -n1)"
if unzip -l "$REPAIRED" | grep -E "libcomposition|liblogging|libconst[^a-z]|libreflect_cpp"; then
echo "ERROR: repaired wheel contains vendored fourdst libraries"
exit 1
fi
else
auditwheel repair -w /io/wheels "$CURRENT_WHEEL"
fi
rm -rf "$BUILD_WHEEL_DIR"
done
echo "Linux wheels ready in /io/wheels"
echo "Linux wheels ready in /io/wheels"
'
done

View File

@@ -1,74 +1,158 @@
#!/usr/bin/env bash
set -euo pipefail
# 1. Validation
if [[ $(uname -m) != "arm64" ]]; then
echo "Error: This script is intended to run on an Apple Silicon (arm64) Mac."
exit 1
fi
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <git-repo-url>"
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <git-repo-url> [fourdst-wheels-dir]"
echo " fourdst-wheels-dir: optional local directory of fourdst wheels to"
echo " install from instead of PyPI (for bootstrapping a new fourdst+gridfire pair)"
exit 1
fi
# 2. Setup Directories
for TOOL in pyenv cmake git; do
if ! command -v "$TOOL" &> /dev/null; then
echo "Error: ${TOOL} not found."
exit 1
fi
done
REPO_URL="$1"
LOCAL_FOURDST_WHEELS="${2:-}"
WORK_DIR="$(pwd)"
WHEEL_DIR="${WORK_DIR}/wheels_macos_aarch64_tmp"
FINAL_WHEEL_DIR="${WORK_DIR}/wheels_macos_aarch64"
RPATH_SCRIPT="${WORK_DIR}/../../build-python/fix_rpaths.py" # Assumes script is in this location relative to execution
echo "➤ Creating wheel output directories"
mkdir -p "${WHEEL_DIR}"
mkdir -p "${FINAL_WHEEL_DIR}"
export MACOSX_DEPLOYMENT_TARGET=15.0
LLVM_VER="17.0.6"
LIBOMP_PREFIX="${WORK_DIR}/libomp-${MACOSX_DEPLOYMENT_TARGET}-${LLVM_VER}"
if [[ ! -f "${LIBOMP_PREFIX}/lib/libomp.dylib" ]]; then
echo "➤ Building libomp ${LLVM_VER} for macOS ${MACOSX_DEPLOYMENT_TARGET}${LIBOMP_PREFIX}"
LIBOMP_SRC_DIR="$(mktemp -d)"
pushd "${LIBOMP_SRC_DIR}" > /dev/null
curl -fLO "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/openmp-${LLVM_VER}.src.tar.xz"
curl -fLO "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VER}/cmake-${LLVM_VER}.src.tar.xz"
tar xf "openmp-${LLVM_VER}.src.tar.xz"
tar xf "cmake-${LLVM_VER}.src.tar.xz"
mv "cmake-${LLVM_VER}.src" cmake
cmake -S "openmp-${LLVM_VER}.src" -B libomp-build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET}" \
-DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_INSTALL_PREFIX="${LIBOMP_PREFIX}" \
-DLIBOMP_INSTALL_ALIASES=OFF
cmake --build libomp-build -j "$(sysctl -n hw.ncpu)"
cmake --install libomp-build
popd > /dev/null
rm -rf "${LIBOMP_SRC_DIR}"
fi
LIBOMP_MINOS="$(otool -l "${LIBOMP_PREFIX}/lib/libomp.dylib" | awk '/minos/ {print $2; exit}')"
if [[ "${LIBOMP_MINOS}" != "${MACOSX_DEPLOYMENT_TARGET}"* ]]; then
echo "Error: cached libomp at ${LIBOMP_PREFIX} targets macOS ${LIBOMP_MINOS}," \
"expected ${MACOSX_DEPLOYMENT_TARGET}. Delete the directory and re-run."
exit 1
fi
echo "➤ Using libomp ${LLVM_VER} (min macOS ${LIBOMP_MINOS}) from ${LIBOMP_PREFIX}"
export CPPFLAGS="-I${LIBOMP_PREFIX}/include ${CPPFLAGS:-}"
export LDFLAGS="-L${LIBOMP_PREFIX}/lib ${LDFLAGS:-}"
export LIBRARY_PATH="${LIBOMP_PREFIX}/lib${LIBRARY_PATH:+:${LIBRARY_PATH}}"
TMPDIR="$(mktemp -d)"
echo "➤ Cloning ${REPO_URL}${TMPDIR}/project"
git clone --depth 1 "${REPO_URL}" "${TMPDIR}/project"
cd "${TMPDIR}/project"
# 3. Build Configuration
export MACOSX_DEPLOYMENT_TARGET=15.0
PYTHON_VERSIONS=("3.9.23" "3.10.18" "3.11.13" "3.12.11" "3.13.5" "3.13.5t" "3.14.0rc1" "3.14.0rc1t" 'pypy3.10-7.3.19' "pypy3.11-7.3.20")
if ! command -v pyenv &> /dev/null; then
echo "Error: pyenv not found. Please install it to manage Python versions."
exit 1
FOURDST_PIN="$(grep -oE 'fourdst==[0-9][0-9a-zA-Z.]*' pyproject.toml | head -n1 || true)"
if [[ -n "${FOURDST_PIN}" ]]; then
echo "➤ Project depends on ${FOURDST_PIN}; wheel repair will exclude fourdst libraries"
fi
PYTHON_VERSIONS=("3.9.23" "3.10.18" "3.11.13" "3.12.11" "3.13.5" "3.14.0rc1" "3.14.0rc1t")
eval "$(pyenv init -)"
# 4. Build Loop
for PY_VERSION in "${PYTHON_VERSIONS[@]}"; do
(
set -e
pyenv shell "${PY_VERSION}"
PY="$(pyenv which python)"
echo "----------------------------------------------------------------"
echo "➤ Building for $($PY --version) on macOS arm64"
echo "----------------------------------------------------------------"
# Install build deps explicitly so we can skip build isolation
"$PY" -m pip install --upgrade pip setuptools wheel meson-python delocate
"$PY" -m pip install meson==1.9.1
if [[ -n "${FOURDST_PIN}" ]]; then
if [[ -n "${LOCAL_FOURDST_WHEELS}" ]]; then
"$PY" -m pip install --force-reinstall \
--find-links "${LOCAL_FOURDST_WHEELS}" "${FOURDST_PIN}"
else
"$PY" -m pip install --force-reinstall "${FOURDST_PIN}"
fi
fi
echo "➤ Building wheel with ccache enabled"
echo "➤ Found meson version $(meson --version)"
# for every single build, saving significant I/O and network time.
CC="ccache clang" CXX="ccache clang++" "$PY" -m pip wheel . --no-build-isolation -w "${WHEEL_DIR}" -v
# We expect exactly one new wheel in the tmp dir per iteration
CC="ccache clang" CXX="ccache clang++" \
"$PY" -m pip wheel . --no-deps --no-build-isolation \
--config-settings=setup-args="-Dlibomp_prefix=${LIBOMP_PREFIX}" \
-w "${WHEEL_DIR}" -v
CURRENT_WHEEL=$(find "${WHEEL_DIR}" -name "*.whl" | head -n 1)
echo "➤ Repairing wheel with delocate"
# Delocate moves the repaired wheel to FINAL_WHEEL_DIR
delocate-wheel -w "${FINAL_WHEEL_DIR}" "$CURRENT_WHEEL"
DELOCATE_DYLD_PATH="${LIBOMP_PREFIX}/lib"
if [[ -n "${FOURDST_PIN}" ]]; then
FOURDST_LIB_PATH="$("$PY" -c 'import fourdst, os; print(os.pathsep.join(fourdst.get_lib_dirs()))')"
DELOCATE_DYLD_PATH="${FOURDST_LIB_PATH}:${DELOCATE_DYLD_PATH}"
DYLD_LIBRARY_PATH="${DELOCATE_DYLD_PATH}" \
delocate-wheel --require-archs arm64 \
-e composition -e logging -e const -e reflect_cpp \
-w "${FINAL_WHEEL_DIR}" -v "$CURRENT_WHEEL"
else
DYLD_LIBRARY_PATH="${DELOCATE_DYLD_PATH}" \
delocate-wheel --require-archs arm64 \
-w "${FINAL_WHEEL_DIR}" -v "$CURRENT_WHEEL"
fi
REPAIRED_WHEEL="${FINAL_WHEEL_DIR}/$(basename "$CURRENT_WHEEL")"
if [[ -n "${FOURDST_PIN}" ]]; then
if unzip -l "${REPAIRED_WHEEL}" | grep -E 'libcomposition|liblogging|libconst|libreflect_cpp'; then
echo "ERROR: repaired wheel contains vendored fourdst libraries"
exit 1
fi
fi
if unzip -l "${REPAIRED_WHEEL}" | grep -q 'libomp.dylib'; then
CHECK_DIR="$(mktemp -d)"
unzip -q "${REPAIRED_WHEEL}" '*/libomp.dylib' -d "${CHECK_DIR}" || true
BUNDLED_OMP="$(find "${CHECK_DIR}" -name 'libomp.dylib' | head -n1)"
BUNDLED_MINOS="$(otool -l "${BUNDLED_OMP}" | awk '/minos/ {print $2; exit}')"
rm -rf "${CHECK_DIR}"
if [[ "${BUNDLED_MINOS}" != "${MACOSX_DEPLOYMENT_TARGET}"* ]]; then
echo "ERROR: bundled libomp targets macOS ${BUNDLED_MINOS}, expected ${MACOSX_DEPLOYMENT_TARGET}"
exit 1
fi
echo "➤ Bundled libomp targets macOS ${BUNDLED_MINOS}"
fi
# Clean up the intermediate wheel from this iteration so it doesn't confuse the next
rm "$CURRENT_WHEEL"
)
done
@@ -77,4 +161,4 @@ done
rm -rf "${TMPDIR}"
rm -rf "${WHEEL_DIR}"
echo "✅ All builds complete. Artifacts in ${FINAL_WHEEL_DIR}"
echo "✅ All builds complete. Artifacts in ${FINAL_WHEEL_DIR}"