diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 00000000000..d3e8606e96d --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,23 @@ +name: Build + +on: [push, pull_request] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.20.0 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9293e319b42..1415074ad71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,9 +145,7 @@ jobs: - name: test shell: bash - run: | - CTEST_OUTPUT_ON_FAILURE=1 make test -C $GITHUB_WORKSPACE/build/ - $GITHUB_WORKSPACE/tools/ci/gha-script.sh + run: $GITHUB_WORKSPACE/tools/ci/gha-script.sh - name: after_success shell: bash diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b7b69f9fe59..bcbea55994e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,9 +1,14 @@ version: 2 build: - os: "ubuntu-20.04" + os: "ubuntu-22.04" tools: python: "3.10" + apt_packages: + - g++ + - cmake + - libhdf5-dev + - libpng-dev sphinx: configuration: docs/source/conf.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f4cc1b527b..ffa682ad111 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,11 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # Set module path set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) +# Conditionally find Python if building with scikit-build-core +if(SKBUILD) + find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +endif() + # Enable correct usage of CXX_EXTENSIONS if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.22) cmake_policy(SET CMP0128 NEW) @@ -36,6 +41,7 @@ option(OPENMC_USE_MCPL "Enable MCPL" option(OPENMC_USE_NCRYSTAL "Enable support for NCrystal scattering" OFF) option(OPENMC_USE_UWUW "Enable UWUW" OFF) + # Warnings for deprecated options foreach(OLD_OPT IN ITEMS "openmp" "profile" "coverage" "dagmc" "libmesh") if(DEFINED ${OLD_OPT}) @@ -327,6 +333,11 @@ if("${isSystemDir}" STREQUAL "-1") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") endif() +if(SKBUILD) + set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +endif() + #=============================================================================== # libopenmc #=============================================================================== @@ -614,3 +625,8 @@ install(FILES man/man1/openmc.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) install(FILES LICENSE DESTINATION "${CMAKE_INSTALL_DOCDIR}" RENAME copyright) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES "${CMAKE_BINARY_DIR}/include/openmc/version.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/openmc) + +if(SKBUILD) + install(TARGETS openmc RUNTIME DESTINATION openmc/bin) + install(TARGETS libopenmc LIBRARY DESTINATION openmc/bin) +endif() diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 7f222f77cb8..3e0778a9eca 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -160,28 +160,19 @@ download and install OpenMC by entering the following commands in a terminal: git clone --recurse-submodules https://github.com/openmc-dev/openmc.git cd openmc - mkdir build && cd build - cmake .. - make - sudo make install + python -m pip install . -This will build an executable named ``openmc`` and install it (by default in -/usr/local/bin). If you do not have administrator privileges, the cmake command -should specify an installation directory where you have write access, e.g. +The easiest way to install it is using `pip `_. +This `pip` command will install the `openmc` Python package and compile an executable named ``openmc`` +and install it (by default in the bin folder of the Python package directory). .. code-block:: sh - cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local .. - -The :mod:`openmc` Python package must be installed separately. The easiest way -to install it is using `pip `_. -From the root directory of the OpenMC repository, run: + python -m pip install . --global-option="build_ext" --global-option="--" --global-option="-DOPENMC_USE_MPI=ON" -.. code-block:: sh - - python -m pip install . +The compilion of the ``openmc`` can be customised by specifying CMake arguments. By default, OpenMC will be built with multithreading support. To build -distributed-memory parallel versions of OpenMC using MPI or to configure other -options, directions can be found in the :ref:`detailed installation instructions +distributed-memory parallel versions of OpenMC using MPI the above command can be run. +There are other options that can be set, more details can be found in the :ref:`detailed installation instructions `. diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 1c0b7fa5b32..b0fab8c68bc 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -175,9 +175,9 @@ feature can be used to access the installed packages. .. _install_source: ----------------------- -Installing from Source ----------------------- +-------------------------------- +Compiling from source with CMake +-------------------------------- .. _prerequisites: @@ -367,6 +367,8 @@ Note that first a build directory is created as a subdirectory of the source directory. The Makefile in the top-level directory will automatically perform an out-of-source build with default options. +.. _cmake_arguemnts: + CMakeLists.txt Options ++++++++++++++++++++++ @@ -505,15 +507,15 @@ To run the test suite, you will first need to download a pre-generated cross section library along with windowed multipole data. Please refer to our :ref:`devguide_tests` documentation for further details. ---------------------- -Installing Python API ---------------------- +----------------------------------------------- +Installing Python API and compiling from source +----------------------------------------------- If you installed OpenMC using :ref:`Conda `, no further steps are necessary in order to use OpenMC's :ref:`Python API `. However, if -you are :ref:`installing from source `, the Python API is not -installed by default when ``make install`` is run because in many situations it -doesn't make sense to install a Python package in the same location as the +you are :ref:`Compiling from Source with CMake `, the Python API +is not installed by default when ``make install`` is run because in many situations +it doesn't make sense to install a Python package in the same location as the ``openmc`` executable (for example, if you are installing the package into a `virtual environment `_). The easiest way to install the :mod:`openmc` Python package is to use pip_, which is @@ -527,7 +529,27 @@ distribution/repository, run: pip will first check that all :ref:`required third-party packages ` have been installed, and if they are not present, they will be installed by downloading the appropriate packages from the Python -Package Index (`PyPI `_). +Package Index (`PyPI `_). The pip command will also compile +an executable named ``openmc`` and install it (by default in the bin folder of +the Python package directory). + +Passing CMake arguments via pip +-------------------------------- + +If you need to pass CMake options to the build process, you can do so by +running pip install with some additional options. All the CMake arguments +covered in the :ref:`CMakeLists.txt Options` are supported. +For example, to build OpenMC with MPI support, you can run: + +.. code-block:: sh + + python -m pip install . --config-settings=cmake.args=-DOPENMC_USE_MPI=ON + +To build OpenMC with DAGMC support two CMake arguments are needed, you can run: + +.. code-block:: sh + + python -m pip install . --config-settings=cmake.args="-DOPENMC_USE_DAGMC=ON;DDAGMC_ROOT=/path/to/dagmc/installation" Installing in "Development" Mode -------------------------------- diff --git a/openmc/__init__.py b/openmc/__init__.py index 566d287068f..32320365d58 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -34,6 +34,7 @@ from openmc.polynomial import * from openmc.tracks import * from .config import * +from .openmc_exec import main # Import a few names from the model module from openmc.model import Model diff --git a/openmc/openmc_exec.py b/openmc/openmc_exec.py new file mode 100644 index 00000000000..579eeced24f --- /dev/null +++ b/openmc/openmc_exec.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# helper script that launches the openmc binary + +import os +import sys +import sysconfig +from pathlib import Path + + +def main(): + os.execv( + Path(sysconfig.get_path("platlib")) / "openmc" / "bin" / "openmc", sys.argv + ) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 39aa261c1b4..c24026300b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["setuptools", "wheel", "numpy", "cython", "scikit-build-core"] +build-backend = "scikit_build_core.build" [project] name = "openmc" @@ -75,3 +75,17 @@ openmc-track-to-vtk = "scripts.openmc_track_to_vtk:main" openmc-update-inputs = "scripts.openmc_update_inputs:main" openmc-update-mgxs = "scripts.openmc_update_mgxs:main" openmc-voxel-to-vtk = "scripts.openmc_voxel_to_vtk:main" +openmc = "openmc.openmc_exec:main" + +[tool.scikit-build] +build.verbose = true +# cmake args can be passed in here or in the command line via exports or inline with the pip install +cmake.args = ["-DCMAKE_BUILD_TYPE=RELEASE"] + +[tool.cibuildwheel] +skip = "*-win_*" +before-all = """ + yum install -y epel-release + yum install -y hdf5 hdf5-devel libpng-devel cmake eigen3-devel gcc gcc-c++ wget +""" +manylinux-x86_64-image = "manylinux_2_28" diff --git a/setup.py b/setup.py new file mode 100755 index 00000000000..f26b3138274 --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +import os +import numpy as np +from setuptools import setup, Extension + + +class OpenMCExtension(Extension): + def __init__(self, name, cmake_lists_dir=".", sources=[], **kwa): + Extension.__init__(self, name, sources=sources, **kwa) + self.cmake_lists_dir = os.path.abspath(cmake_lists_dir) + + +kwargs = { + 'ext_modules': [OpenMCExtension('libopenmc')], + 'include_dirs': [np.get_include()] +} + +setup(**kwargs) diff --git a/tools/ci/gha-install.py b/tools/ci/gha-install.py index f046e863470..da94df1bc2b 100644 --- a/tools/ci/gha-install.py +++ b/tools/ci/gha-install.py @@ -1,57 +1,52 @@ import os -import shutil import subprocess def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False, ncrystal=False): - # Create build directory and change to it - shutil.rmtree('build', ignore_errors=True) - os.mkdir('build') - os.chdir('build') # Build in debug mode by default with support for MCPL - cmake_cmd = ['cmake', '-DCMAKE_BUILD_TYPE=Debug', '-DOPENMC_USE_MCPL=on'] + pip_command = ['pip', 'install', '.[test,vtk,ci]'] + pip_suffix = ['--config-settings=cmake.args="-DCMAKE_BUILD_TYPE=ON;-DOPENMC_USE_MCPL=ON'] # Turn off OpenMP if specified if not omp: - cmake_cmd.append('-DOPENMC_USE_OPENMP=off') + pip_suffix.append('-DOPENMC_USE_OPENMP=off') # Use MPI wrappers when building in parallel if mpi: - cmake_cmd.append('-DOPENMC_USE_MPI=on') + pip_suffix.append('-DOPENMC_USE_MPI=ON') # Tell CMake to prefer parallel HDF5 if specified if phdf5: if not mpi: raise ValueError('Parallel HDF5 must be used in ' 'conjunction with MPI.') - cmake_cmd.append('-DHDF5_PREFER_PARALLEL=ON') + pip_suffix.append('-DHDF5_PREFER_PARALLEL=ON') else: - cmake_cmd.append('-DHDF5_PREFER_PARALLEL=OFF') + pip_suffix.append('-DHDF5_PREFER_PARALLEL=OFF') if dagmc: - cmake_cmd.append('-DOPENMC_USE_DAGMC=ON') - cmake_cmd.append('-DCMAKE_PREFIX_PATH=~/DAGMC') + pip_suffix.append('-DOPENMC_USE_DAGMC=ON') + pip_suffix.append('-DCMAKE_PREFIX_PATH=~/DAGMC') if libmesh: - cmake_cmd.append('-DOPENMC_USE_LIBMESH=ON') + pip_suffix.append('-DOPENMC_USE_LIBMESH=ON') libmesh_path = os.environ.get('HOME') + '/LIBMESH' - cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + libmesh_path) + pip_suffix.append('-DCMAKE_PREFIX_PATH=' + libmesh_path) if ncrystal: - cmake_cmd.append('-DOPENMC_USE_NCRYSTAL=ON') + pip_suffix.append('-DOPENMC_USE_NCRYSTAL=ON') ncrystal_cmake_path = os.environ.get('HOME') + '/ncrystal_inst/lib/cmake' - cmake_cmd.append(f'-DCMAKE_PREFIX_PATH={ncrystal_cmake_path}') + pip_suffix.append(f'-DCMAKE_PREFIX_PATH={ncrystal_cmake_path}') # Build in coverage mode for coverage testing - cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on') + pip_suffix.append('-DOPENMC_ENABLE_COVERAGE=ON"') + pip_command.append(';'.join(pip_suffix)) + pip_command.append('--verbose') # Build and install - cmake_cmd.append('..') - print(' '.join(cmake_cmd)) - subprocess.check_call(cmake_cmd) - subprocess.check_call(['make', '-j4']) - subprocess.check_call(['sudo', 'make', 'install']) + print(' '.join(pip_command)) + subprocess.check_call(pip_command) def main(): # Convert Travis matrix environment variables into arguments for install() diff --git a/tools/ci/gha-install.sh b/tools/ci/gha-install.sh index cff7dc834f5..e04c61abc33 100755 --- a/tools/ci/gha-install.sh +++ b/tools/ci/gha-install.sh @@ -45,6 +45,3 @@ fi # Build and install OpenMC executable python tools/ci/gha-install.py - -# Install Python API in editable mode -pip install -e .[test,vtk,ci]