diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 877ec68338e..024d3132e87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,7 @@ jobs: run: | echo "OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml" >> $GITHUB_ENV echo "OPENMC_ENDF_DATA=$HOME/endf-b-vii.1" >> $GITHUB_ENV + echo "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV # get the sha of the last branch commit # for push and workflow_dispatch events, use the current reference head BRANCH_SHA=HEAD diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 3578144b254..4c97633bbbd 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,6 +4,10 @@ build: os: "ubuntu-24.04" tools: python: "3.12" + apt_packages: + - g++ + - libhdf5-dev + - libpng-dev jobs: post_checkout: - git fetch --unshallow || true diff --git a/CMakeLists.txt b/CMakeLists.txt index 461abe508ab..815a60afcab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16 FATAL_ERROR) +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) project(openmc C CXX) # Set module path @@ -299,23 +299,48 @@ include(GNUInstallDirs) # installed one in CMAKE_INSTALL_PREFIX. Ref: # https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling -# use, i.e. don't skip the full RPATH for the build tree -set(CMAKE_SKIP_BUILD_RPATH FALSE) -# when building, don't use the install RPATH already -# (but later on when installing) -set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +if(SKBUILD) + # By default, scikit-build will install everything to ${SKBUILD_PLATLIB_DIR}/openmc + # Install include and bin directories to ${SKBUILD_PLATLIB_DIR}/openmc/SKBUILD_SUBDIR + set(SKBUILD_SUBDIR core/) + set(CMAKE_INSTALL_BINDIR ${SKBUILD_SUBDIR}${CMAKE_INSTALL_BINDIR}) + set(CMAKE_INSTALL_INCLUDEDIR ${SKBUILD_SUBDIR}${CMAKE_INSTALL_INCLUDEDIR}) + set(CMAKE_INSTALL_DATADIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_DATADIR}) + set(CMAKE_INSTALL_DOCDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_DOCDIR}) + set(CMAKE_INSTALL_INFODIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_INFODIR}) + set(CMAKE_INSTALL_MANDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_MANDIR}) + set(CMAKE_INSTALL_LOCALEDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_LOCALEDIR}) + set(CMAKE_INSTALL_LOCALSTATEDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_LOCALSTATEDIR}) + set(CMAKE_INSTALL_RUNSTATEDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_RUNSTATEDIR}) + + # Set RPATH + if(APPLE) + set(CMAKE_MACOSX_RPATH ON) + set(OPENMC_LIBRARY_RPATH "@loader_path") + set(OPENMC_BINARY_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}") + elseif(UNIX) + set(OPENMC_LIBRARY_RPATH "$ORIGIN") + set(OPENMC_BINARY_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") + endif() +else() + # use, i.e. don't skip the full RPATH for the build tree + set(CMAKE_SKIP_BUILD_RPATH FALSE) -# add the automatically determined parts of the RPATH -# which point to directories outside the build tree to the install RPATH -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + # when building, don't use the install RPATH already + # (but later on when installing) + set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) -# the RPATH to be used when installing, but only if it's not a system directory -list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" isSystemDir) -if("${isSystemDir}" STREQUAL "-1") - set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") + # the RPATH to be used when installing, but only if it's not a system directory + list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" isSystemDir) + if("${isSystemDir}" STREQUAL "-1") + set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") + endif() endif() +# add the automatically determined parts of the RPATH +# which point to directories outside the build tree to the install RPATH +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) #=============================================================================== # libopenmc #=============================================================================== @@ -458,7 +483,7 @@ endif() target_include_directories(libopenmc PUBLIC - $ + $ $ ${HDF5_INCLUDE_DIRS} ) @@ -466,6 +491,14 @@ target_include_directories(libopenmc # Set compile flags target_compile_options(libopenmc PRIVATE ${cxxflags}) +if(OPENMC_LIBRARY_RPATH) + set_target_properties( + libopenmc + PROPERTIES + INSTALL_RPATH "${OPENMC_LIBRARY_RPATH}" + ) +endif() + # Add include directory for configured version file target_include_directories(libopenmc PUBLIC $) @@ -557,15 +590,14 @@ target_compile_features(openmc PUBLIC cxx_std_17) target_compile_features(libopenmc PUBLIC cxx_std_17) set_target_properties(openmc libopenmc PROPERTIES CXX_EXTENSIONS OFF) -#=============================================================================== -# Python package -#=============================================================================== - -add_custom_command(TARGET libopenmc POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - $ - ${CMAKE_CURRENT_SOURCE_DIR}/openmc/lib/$ - COMMENT "Copying libopenmc to Python module directory") +# Set rpath for openmc executable +if(OPENMC_BINARY_RPATH) + set_target_properties( + openmc + PROPERTIES + INSTALL_RPATH "${OPENMC_BINARY_RPATH}" + ) +endif() #=============================================================================== # Install executable, scripts, manpage, license @@ -574,17 +606,35 @@ add_custom_command(TARGET libopenmc POST_BUILD configure_file(cmake/OpenMCConfig.cmake.in "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMCConfig.cmake" @ONLY) configure_file(cmake/OpenMCConfigVersion.cmake.in "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMCConfigVersion.cmake" @ONLY) -set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) +if(SKBUILD) + set(INSTALL_CONFIGDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) +else() + set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) +endif() + install(TARGETS openmc libopenmc EXPORT openmc-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${SKBUILD_SUBDIR}${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${SKBUILD_SUBDIR}${CMAKE_INSTALL_LIBDIR} ) + +set(INSTALL_TARGETDIR ${SKBUILD_SUBDIR}${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) install(EXPORT openmc-targets FILE OpenMCTargets.cmake NAMESPACE OpenMC:: - DESTINATION ${INSTALL_CONFIGDIR}) + DESTINATION ${INSTALL_TARGETDIR} +) + +# Collect scripts +file(GLOB SCRIPTS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*") + +# Install scripts to bin directory +if(SKBUILD) + install(PROGRAMS ${SCRIPTS} DESTINATION ${SKBUILD_SCRIPTS_DIR}) +else() + install(PROGRAMS ${SCRIPTS} DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() install(FILES "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMCConfig.cmake" diff --git a/Dockerfile b/Dockerfile index 1ae615a50f8..ffa081080cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -195,31 +195,19 @@ ENV LIBMESH_INSTALL_DIR=$HOME/LIBMESH # clone and install openmc RUN mkdir -p ${HOME}/OpenMC && cd ${HOME}/OpenMC \ && git clone --shallow-submodules --recurse-submodules --single-branch -b ${openmc_branch} ${OPENMC_REPO} \ - && mkdir build && cd build ; \ - if [ ${build_dagmc} = "on" ] && [ ${build_libmesh} = "on" ]; then \ - cmake ../openmc \ - -DCMAKE_CXX_COMPILER=mpicxx \ - -DOPENMC_USE_MPI=on \ - -DHDF5_PREFER_PARALLEL=on \ - -DOPENMC_USE_DAGMC=on \ - -DOPENMC_USE_LIBMESH=on \ - -DCMAKE_PREFIX_PATH="${DAGMC_INSTALL_DIR};${LIBMESH_INSTALL_DIR}" ; \ - fi ; \ - if [ ${build_dagmc} = "on" ] && [ ${build_libmesh} = "off" ]; then \ - cmake ../openmc \ - -DCMAKE_CXX_COMPILER=mpicxx \ - -DOPENMC_USE_MPI=on \ - -DHDF5_PREFER_PARALLEL=on \ - -DOPENMC_USE_DAGMC=ON \ - -DCMAKE_PREFIX_PATH=${DAGMC_INSTALL_DIR} ; \ + && cd openmc ; \ + export SKBUILD_CMAKE_ARGS="-DCMAKE_CXX_COMPILER=mpicxx; \ + -DOPENMC_USE_MPI=on; \ + -DHDF5_PREFER_PARALLEL=on" \ + if [ ${build_dagmc} = "on" ]; then \ + SKBUILD_CMAKE_ARGS="${SKBUILD_CMAKE_ARGS}; \ + -DOPENMC_USE_DAGMC=on; \ + -DCMAKE_PREFIX_PATH=${DAGMC_INSTALL_DIR}" ; \ fi ; \ - if [ ${build_dagmc} = "off" ] && [ ${build_libmesh} = "on" ]; then \ - cmake ../openmc \ - -DCMAKE_CXX_COMPILER=mpicxx \ - -DOPENMC_USE_MPI=on \ - -DHDF5_PREFER_PARALLEL=on \ - -DOPENMC_USE_LIBMESH=on \ - -DCMAKE_PREFIX_PATH=${LIBMESH_INSTALL_DIR} ; \ + if [ ${build_libmesh} = "on" ]; then \ + SKBUILD_CMAKE_ARGS="${SKBUILD_CMAKE_ARGS}; \ + -DOPENMC_USE_LIBMESH=on; \ + -DCMAKE_PREFIX_PATH=${LIBMESH_INSTALL_DIR}" ; \ fi ; \ if [ ${build_dagmc} = "off" ] && [ ${build_libmesh} = "off" ]; then \ cmake ../openmc \ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index cdc7e2abcf0..00000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,47 +0,0 @@ -include CMakeLists.txt -include LICENSE -include CODE_OF_CONDUCT.md -include CODEOWNERS -include CONTRIBUTING.md -include Dockerfile -include schemas.xml -include pyproject.toml -include pytest.ini -include docs/source/_templates/layout.html -global-include *.cmake -global-include *.cmake.in -global-include *.rst -recursive-include docs *.css -recursive-include docs *.dia -recursive-include docs *.png -recursive-include docs *.py -recursive-include docs *.svg -recursive-include docs *.tex -recursive-include docs *.txt -recursive-include docs Makefile -recursive-include examples *.cpp -recursive-include examples *.py -recursive-include examples *.xml -recursive-include include *.h -recursive-include include *.h.in -recursive-include include *.hh -recursive-include man *.1 -recursive-include src *.cc -recursive-include src *.cpp -recursive-include src *.rnc -recursive-include src *.rng -recursive-include tests *.dat -recursive-include tests *.h5 -recursive-include tests *.h5m -recursive-include tests *.py -recursive-include tests *.xml -recursive-include vendor CMakeLists.txt -recursive-include vendor *.cc -recursive-include vendor *.cpp -recursive-include vendor *.h -recursive-include vendor *.hh -recursive-include vendor *.hpp -recursive-include vendor *.pc.in -recursive-include vendor *.natvis -prune docs/build -prune docs/source/pythonapi/generated/ diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index 5cab4790abc..994cde0e293 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -4,6 +4,9 @@ find_package(fmt REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../fmt) find_package(pugixml REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../pugixml) find_package(xtl REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtl) find_package(xtensor REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtensor) + +find_package(HDF5 REQUIRED COMPONENTS C HL) + if(@OPENMC_USE_DAGMC@) find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@) endif() @@ -17,7 +20,48 @@ endif() find_package(PNG) -if(NOT TARGET OpenMC::libopenmc) +if(@SKBUILD@) + # Find the Python interpreter and ensure it's available. + find_package(Python COMPONENTS Interpreter REQUIRED) + + # Function to run Python commands and validate their execution. + function(run_python_command output_var command) + execute_process( + COMMAND ${Python_EXECUTABLE} -c "${command}" + OUTPUT_VARIABLE ${output_var} + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE result + ) + # Check if the command was successful + if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to run Python command: ${command}") + else() + # Add the output variable to the parent scope + set(${output_var} "${${output_var}}" PARENT_SCOPE) + endif() + endfunction() + + # Extract MOAB include paths, library paths, and extra libraries + run_python_command(OpenMC_INCLUDE_DIRS "import openmc; print(' '.join(openmc.include_path))") + run_python_command(OpenMC_LIBRARY_DIRS "import openmc; print(' '.join(openmc.lib_path))") + run_python_command(OpenMC_EXTRA_LIBRARIES "import openmc; print(' '.join(openmc.extra_lib))") + + # Check if the wheel was repaired using auditwheel or delocate + if(OpenMC_EXTRA_LIBRARIES) + message(FATAL_ERROR + "This build of OpenMC is not supported. " + "It appears that the wheel was repaired using tools like auditwheel or delocate, " + "that modifies the shared libraries, which may cause problems.\n" + "OpenMC_EXTRA_LIBRARIES is not empty: ${OpenMC_EXTRA_LIBRARIES}.\n" + "To resolve this, please build OpenMC from scratch. " + "For more information, visit: https://docs.openmc.org/" + ) + endif() + + # Add OpenMC targets + file(TO_CMAKE_PATH "${OpenMC_LIBRARY_DIRS}/cmake/OpenMC/OpenMCTargets.cmake" OpenMC_TARGETS_FILE) + include(${OpenMC_TARGETS_FILE}) +elseif(NOT TARGET OpenMC::libopenmc) include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") endif() diff --git a/docs/source/devguide/workflow.rst b/docs/source/devguide/workflow.rst index d9d36c161c2..8b12d9f2327 100644 --- a/docs/source/devguide/workflow.rst +++ b/docs/source/devguide/workflow.rst @@ -116,15 +116,14 @@ pip_. From the root directory of the OpenMC repository, run: .. code-block:: sh - python -m pip install -e .[test] - -This installs the OpenMC Python package in `"editable" mode -`_ so that 1) -it can be imported from a Python interpreter and 2) any changes made are -immediately reflected in the installed version (that is, you don't need to keep -reinstalling it). While the same effect can be achieved using the -:envvar:`PYTHONPATH` environment variable, this is generally discouraged as it -can interfere with virtual environments. + python -m pip install --no-build-isolation \ + -Ceditable.rebuild=true \ + -Ccmake.build-type=Debug \ + -Cbuild-dir=build \ + -ve ".[test]" + +This feature allows for rebuilding on initial import, providing +flexibility for iterative development or testing changes to the codebase. .. _git: https://git-scm.com/ .. _GitHub: https://github.com/ diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 323cd7fd48d..f56c7b727f6 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -149,38 +149,66 @@ source below. Building Source on Linux or macOS --------------------------------- -All OpenMC source code is hosted on `GitHub -`_. If you have `git -`_, a modern C++ compiler, `CMake `_, -and `HDF5 `_ installed, you can -download and install OpenMC by entering the following commands in a terminal: +All OpenMC source code is hosted on GitHub (`OpenMC GitHub `_). +Depending on your needs, you can either: -.. code-block:: sh +- Build only the OpenMC executable (using CMake). +- Build both the OpenMC executable and Python package (using pip). + +1. Building the OpenMC Executable Only (CMake) +============================================== + +If you only need the OpenMC executable without Python bindings, you can build it using +the following steps. You will need `git `_, a modern C++ compiler, +`CMake `_, and `HDF5 `_ installed: + +.. code-block:: bash 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 .. + make + make install -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: +2. Building the OpenMC Executable with Python Support (pip) +=========================================================== + +If you also want to install the OpenMC Python package,you can use +`pip `_ to build both the executable and +the Python package at the same time. This requires a Python environment +and pip installed. From the root directory of the OpenMC repository, run: .. code-block:: sh python -m pip install . +This will build the ``openmc`` executable and install it along with the Python bindings. +There is no need to manually run ``cmake`` or ``make``, as the pip command handles +both the C++ and Python build processes. + +Custom Build Options +~~~~~~~~~~~~~~~~~~~~ + +If you need to customize the build options (e.g., enabling MPI, DAGMC, or LibMesh), +you can pass CMake arguments using the ``SKBUILD_CMAKE_ARGS`` environment variable +before running pip. For example: + +.. code-block:: bash + + export SKBUILD_CMAKE_ARGS="-DOPENMC_USE_MPI=on;-DOPENMC_USE_DAGMC=on" + python -m pip install ".[test,depletion-mpi]" + +This allows you to configure your build just like you would with CMake. + 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 0aa561ee3d7..035256e75a8 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: @@ -366,6 +366,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 ++++++++++++++++++++++ @@ -501,29 +503,72 @@ 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 -``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 -included by default in Python 3.4+. From the root directory of the OpenMC -distribution/repository, run: +necessary to use OpenMC's :ref:`Python API `. However, if you are +:ref:`installing from source `, the Python API is not installed +by default when building OpenMC because, in many cases, it doesn't make sense to +install a Python package in the same location as the OpenMC executable (for example, +if you are using a virtual environment `virtualenv `_). -.. code-block:: sh +To install OpenMC and its Python API, the recommended method is to use pip, +which includes both the Python package and the OpenMC executable. +From the root directory of the OpenMC repository, run: + +.. code-block:: bash + + python -m pip install ".[test,depletion-mpi]" + +This command installs both the OpenMC executable and the Python API together. +There's no need to manually run ``cmake`` or ``make install`` as pip automatically +handles the build process, including any CMake configuration. + +Custom Build Options +==================== + +If you need to customize the build (for example, to enable MPI, DAGMC, or LibMesh), +you can pass the necessary CMake arguments through the ``SKBUILD_CMAKE_ARGS`` +environment variable before running pip install: + +.. code-block:: bash + + export SKBUILD_CMAKE_ARGS="-DOPENMC_USE_MPI=on;-DOPENMC_USE_DAGMC=on" + python -m pip install ".[test,depletion-mpi]" + +Alternatively, pip provides additional ways to configure the build using +``--config-settings`` or ``-C``: - python -m pip install . +.. code-block:: bash + + python -m pip install . --config-settings=cmake.args="-DOPENMC_USE_MPI=ON;-DOPENMC_USE_MCPL=ON" 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 bb972b4e6ad..9cca2dd8c14 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -1,3 +1,5 @@ +import os +import glob import importlib.metadata from openmc.arithmetic import * from openmc.bounding_box import * @@ -43,3 +45,49 @@ __version__ = importlib.metadata.version("openmc") + +try: + OPENMC_CORE_BASE_PATH = os.path.join(__path__[0], "core") +except NameError: + OPENMC_CORE_BASE_PATH = None + + +def get_paths(subdir, pattern="*", recursive=False): + """ + Helper function to return paths that match a given pattern within a subdirectory. + + Args: + subdir (str): The subdirectory within the 'core' directory. + pattern (str): The pattern to match files or directories. + recursive (bool): Whether to search recursively in subdirectories. + + Returns: + list: A list of matched paths. + """ + search_pattern = os.path.join(OPENMC_CORE_BASE_PATH, subdir, "**", pattern) if recursive else os.path.join(OPENMC_CORE_BASE_PATH, subdir, pattern) + return glob.glob(search_pattern, recursive=recursive) + +def get_include_path(): + """Return includes and include path for OpenMC headers.""" + include = get_paths("include", "*", recursive=True) + include_path = get_paths("include", "", recursive=False) + return include, include_path + +def get_core_libraries(): + """Return libraries and library paths for OpenMC.""" + lib = [lib_file for lib in ["lib", "lib64"] for lib_file in get_paths(lib, "libopenmc*", recursive=True)] + lib_path = [lib_file for lib in ["lib", "lib64"] for lib_file in get_paths(lib, "", recursive=False)] + return lib, lib_path + +def get_extra_libraries(): + """Return the extra libraries installed by auditwheel or delocate.""" + libs_path = os.path.join(__path__[0], ".dylibs") if sys.platform == "darwin" else os.path.normpath(os.path.join(__path__[0], "..", "openmc.libs")) + return (glob.glob(os.path.join(libs_path, "*")), libs_path) if os.path.exists(libs_path) else ([], []) + +# Setup variables +include, include_path = get_include_path() +lib, lib_path = get_core_libraries() +extra_lib, extra_lib_path = get_extra_libraries() + +# Export variables for easy access +__all__ = ["include", "include_path", "lib", "lib_path", "extra_lib", "extra_lib_path"] \ No newline at end of file diff --git a/openmc/_cli_openmc.py b/openmc/_cli_openmc.py new file mode 100644 index 00000000000..20c9249b852 --- /dev/null +++ b/openmc/_cli_openmc.py @@ -0,0 +1,53 @@ +def get_ncrystal_libdir(): + """ + Returns NCrystal library directory or None. + """ + import shutil + cmd = shutil.which('ncrystal-config') + if cmd: + import subprocess + res = subprocess.run([cmd,'--show','shlibdir'],capture_output=True) + if res.returncode == 0: + return res.stdout.decode('utf8').strip() + +def runtime_env(): + """ + + Perform any dynamic environment modifications needed for openmc to load + shared libraries etc. + """ + + #Currently this only concerns NCrystal, which needs the directory with the + #NCrystal library to be added to the relevant lib path variable: + import platform + libpathvar = { 'Linux' : 'LD_LIBRARY_PATH', + 'Darwin' : 'DYLD_LIBRARY_PATH' }.get(platform.system()) + if not libpathvar: + return + + #Currently we only need this for NCrystal (in principle it is not needed + #when NCrystal is from Conda or a system package, but it doesn't actually + #hurt in that case): + nc_libdir = get_ncrystal_libdir() + if not nc_libdir: + return + + import os + env = os.environ.copy() + env[libpathvar] = '%s:%s'%(env.get(libpathvar),nc_libdir) + return env + +def main(): + """ + Entry point wrapper for the binary openmc executable, for when OpenMC is + installed from a Python wheel. + """ + import subprocess + import pathlib + import sys + f = pathlib.Path(__file__).parent.joinpath('core','bin','openmc') + a = sys.argv[:] + a[0] = f + env = runtime_env() + rv = subprocess.run( a, env = env ) + raise SystemExit(rv.returncode) diff --git a/openmc/lib/__init__.py b/openmc/lib/__init__.py index 15642b42be3..73b64801ba0 100644 --- a/openmc/lib/__init__.py +++ b/openmc/lib/__init__.py @@ -13,20 +13,21 @@ """ from ctypes import CDLL, c_bool, c_int -import importlib.resources import os -import sys - - -# Determine shared-library suffix -if sys.platform == 'darwin': - _suffix = 'dylib' -else: - _suffix = 'so' if os.environ.get('READTHEDOCS', None) != 'True': + # Load libNCrystal first if it exists + try: + import subprocess + ncrystal_path = subprocess.check_output(["ncrystal-config", "--show", "libpath"], text=True).strip() + if os.path.isfile(ncrystal_path): + CDLL(ncrystal_path, mode=os.RTLD_GLOBAL) + except Exception: + pass # NCrystal is not installed + # Open shared library - _filename = importlib.resources.files(__name__) / f'libopenmc.{_suffix}' + import openmc + _filename = openmc.lib[0] _dll = CDLL(str(_filename)) # TODO: Remove str() when Python 3.12+ else: # For documentation builds, we don't actually have the shared library diff --git a/pyproject.toml b/pyproject.toml index 6e8ed798e78..7294c8bd400 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools", "setuptools-scm", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["scikit-build-core", "setuptools-scm"] +build-backend = "scikit_build_core.build" [project] name = "openmc" @@ -58,13 +58,12 @@ Documentation = "https://docs.openmc.org" Repository = "https://github.com/openmc-dev/openmc" Issues = "https://github.com/openmc-dev/openmc/issues" -[tool.setuptools.packages.find] -include = ['openmc*'] -exclude = ['tests*'] - -[tool.setuptools.package-data] -"openmc.data.effective_dose" = ["**/*.txt"] -"openmc.data" = ["*.txt", "*.DAT", "*.json", "*.h5"] -"openmc.lib" = ["libopenmc.dylib", "libopenmc.so"] +# Scikit-Build Configuration +[tool.scikit-build] +build.verbose = true +logging.level = "INFO" +wheel.install-dir = "openmc" +wheel.packages = ["openmc"] +sdist.include = ["*.h5"] [tool.setuptools_scm] diff --git a/tools/ci/gha-install.py b/tools/ci/gha-install.py index 1cc792f8d78..33eac8e9589 100644 --- a/tools/ci/gha-install.py +++ b/tools/ci/gha-install.py @@ -4,47 +4,55 @@ def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=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'] + # List to store the CMake arguments + cmake_args = ['-DCMAKE_BUILD_TYPE=Debug', '-DOPENMC_USE_MCPL=on'] # Turn off OpenMP if specified if not omp: - cmake_cmd.append('-DOPENMC_USE_OPENMP=off') + cmake_args.append('-DOPENMC_USE_OPENMP=off') # Use MPI wrappers when building in parallel if mpi: - cmake_cmd.append('-DOPENMC_USE_MPI=on') + cmake_args.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') + raise ValueError('Parallel HDF5 must be used in conjunction with MPI.') + cmake_args.append('-DHDF5_PREFER_PARALLEL=ON') else: - cmake_cmd.append('-DHDF5_PREFER_PARALLEL=OFF') + cmake_args.append('-DHDF5_PREFER_PARALLEL=OFF') if dagmc: - cmake_cmd.append('-DOPENMC_USE_DAGMC=ON') - cmake_cmd.append('-DOPENMC_USE_UWUW=ON') + cmake_args.append('-DOPENMC_USE_DAGMC=ON') + cmake_args.append('-DOPENMC_USE_UWUW=ON') dagmc_path = os.environ.get('HOME') + '/DAGMC' - cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + dagmc_path) + cmake_args.append('-DCMAKE_PREFIX_PATH=' + dagmc_path) if libmesh: - cmake_cmd.append('-DOPENMC_USE_LIBMESH=ON') + cmake_args.append('-DOPENMC_USE_LIBMESH=ON') libmesh_path = os.environ.get('HOME') + '/LIBMESH' - cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + libmesh_path) + cmake_args.append('-DCMAKE_PREFIX_PATH=' + libmesh_path) # Build in coverage mode for coverage testing - cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on') + cmake_args.append('-DOPENMC_ENABLE_COVERAGE=on') - # Build and install - cmake_cmd.append('..') + # Set environment variable for SKBUILD + os.environ['SKBUILD_cmake_args'] = ';'.join(cmake_args) + + # Run pip to build and install + pip_suffix = '--config-settings=cmake.args="' + ';'.join(cmake_args) + '"' + + subprocess.check_call(['pip', '-v', 'install', '.[test,vtk,ci]', pip_suffix]) + + # Using standard CMake method + # Create build directory and change to it + shutil.rmtree('build', ignore_errors=True) + os.mkdir('build') + os.chdir('build') + + # Add CMake arguments for standard method + cmake_cmd = ['cmake', '..'] + cmake_args print(' '.join(cmake_cmd)) subprocess.check_call(cmake_cmd) subprocess.check_call(['make', '-j4']) diff --git a/tools/ci/gha-install.sh b/tools/ci/gha-install.sh index d8a3a7600e4..3ab12f825d3 100755 --- a/tools/ci/gha-install.sh +++ b/tools/ci/gha-install.sh @@ -45,5 +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] diff --git a/tools/ci/gha-script.sh b/tools/ci/gha-script.sh index c7c634ffa33..263ff24f5ac 100755 --- a/tools/ci/gha-script.sh +++ b/tools/ci/gha-script.sh @@ -14,5 +14,11 @@ if [[ $EVENT == 'y' ]]; then args="${args} --event " fi +# Rename openmc to openmc-test +mv openmc openmc-test + # Run regression and unit tests pytest --cov=openmc -v $args tests + +# Rename to openmc back +mv openmc-test openmc