diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d75a64d662c..848b5e6aeb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,7 +163,6 @@ jobs: - name: test shell: bash run: | - CTEST_OUTPUT_ON_FAILURE=1 make test -C $GITHUB_WORKSPACE/build/ $GITHUB_WORKSPACE/tools/ci/gha-script.sh - name: Setup tmate debug session diff --git a/.gitignore b/.gitignore index 3b2a24a4dbc..dc30c60ce93 100644 --- a/.gitignore +++ b/.gitignore @@ -43,9 +43,6 @@ results_test.dat # Test .pytest_cache/ -# HDF5 files -*.h5 - # Build files src/CMakeCache.txt src/CMakeFiles/ 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 87b8789d101..94e6b425c8a 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 @@ -80,6 +80,9 @@ foreach(OLD_BLD in ITEMS "debug" "optimize") endif() endforeach() +# Include the GenerateScript.cmake file +include(cmake/GenerateScript.cmake) + #=============================================================================== # Set a default build configuration if not explicitly specified #=============================================================================== @@ -310,23 +313,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 #=============================================================================== @@ -339,6 +367,7 @@ list(APPEND libopenmc_SOURCES src/chain.cpp src/cmfd_solver.cpp src/collision_track.cpp + src/config.cpp src/cross_sections.cpp src/dagmc.cpp src/distribution.cpp @@ -473,7 +502,7 @@ endif() target_include_directories(libopenmc PUBLIC - $ + $ $ ${HDF5_INCLUDE_DIRS} ) @@ -481,6 +510,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 $) @@ -543,6 +580,9 @@ endif() #=============================================================================== # Log build info that this executable can report later #=============================================================================== +target_compile_definitions(libopenmc PRIVATE "CMAKE_INSTALL_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"") +target_compile_definitions(libopenmc PRIVATE "CMAKE_INSTALL_INCLUDEDIR=\"${CMAKE_INSTALL_INCLUDEDIR}\"") +target_compile_definitions(libopenmc PRIVATE "CMAKE_INSTALL_LIBDIR=\"${CMAKE_INSTALL_LIBDIR}\"") target_compile_definitions(libopenmc PRIVATE BUILD_TYPE=${CMAKE_BUILD_TYPE}) target_compile_definitions(libopenmc PRIVATE COMPILER_ID=${CMAKE_CXX_COMPILER_ID}) target_compile_definitions(libopenmc PRIVATE COMPILER_VERSION=${CMAKE_CXX_COMPILER_VERSION}) @@ -552,6 +592,9 @@ endif() if (OPENMC_ENABLE_COVERAGE) target_compile_definitions(libopenmc PRIVATE COVERAGEBUILD) endif() +if(SKBUILD) + target_compile_definitions(libopenmc PRIVATE SKBUILD) +endif() #=============================================================================== # openmc executable @@ -567,15 +610,16 @@ 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 -#=============================================================================== +generate_and_install_python_script(openmc) -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 @@ -584,17 +628,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_SUBDIR}${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 a163a281029..459aae57cce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -196,40 +196,21 @@ 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}" ; \ + && 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} = "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} ; \ + 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} = "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} ; \ - fi ; \ - if [ ${build_dagmc} = "off" ] && [ ${build_libmesh} = "off" ]; then \ - cmake ../openmc \ - -DCMAKE_CXX_COMPILER=mpicxx \ - -DOPENMC_USE_MPI=on \ - -DHDF5_PREFER_PARALLEL=on ; \ - fi ; \ - make 2>/dev/null -j${compile_cores} install \ - && cd ../openmc && pip install .[test,depletion-mpi] \ + pip -v install .[test,depletion-mpi] \ && python -c "import openmc" FROM build AS release 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/GenerateScript.cmake b/cmake/GenerateScript.cmake new file mode 100644 index 00000000000..dfef241bae3 --- /dev/null +++ b/cmake/GenerateScript.cmake @@ -0,0 +1,33 @@ +# Function to generate and install a Python script +# This function creates a Python script with a specified name, writes content to it, +# and installs it to a specified location. +# +# Arguments: +# SCRIPT_NAME - The name of the script to be generated (e.g., 'my_script'). + +function(generate_and_install_python_script SCRIPT_NAME) + if(SKBUILD) + # Strip any leading/trailing whitespace from the script name + string(STRIP "${SCRIPT_NAME}" CLEAN_SCRIPT_NAME) + + # Define the output path for the generated script using the cleaned name + set(GENERATED_SCRIPT "${CMAKE_BINARY_DIR}/${CLEAN_SCRIPT_NAME}") + + # Generate the script content directly + file(WRITE "${GENERATED_SCRIPT}" "#!/usr/bin/env python3\n\n") + file(APPEND "${GENERATED_SCRIPT}" "import os\n") + file(APPEND "${GENERATED_SCRIPT}" "import sys\n") + file(APPEND "${GENERATED_SCRIPT}" "import sysconfig\n\n") + file(APPEND "${GENERATED_SCRIPT}" "if __name__ == '__main__':\n") + file(APPEND "${GENERATED_SCRIPT}" " os.execv(\n") + file(APPEND "${GENERATED_SCRIPT}" " os.path.join(sysconfig.get_path('platlib'), '${SKBUILD_PROJECT_NAME}', '${CMAKE_INSTALL_BINDIR}', '${CLEAN_SCRIPT_NAME}'),\n") + file(APPEND "${GENERATED_SCRIPT}" " sys.argv,\n") + file(APPEND "${GENERATED_SCRIPT}" " )\n") + + # Install the generated script + install( + PROGRAMS "${GENERATED_SCRIPT}" + DESTINATION "${SKBUILD_SCRIPTS_DIR}" + ) + endif() +endfunction() diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index 837a39c7833..9881ccaa3f4 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -1,12 +1,5 @@ -get_filename_component(OpenMC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) +find_package(HDF5 REQUIRED COMPONENTS C HL) -# Compute the install prefix from this file's location -get_filename_component(_OPENMC_PREFIX "${OpenMC_CMAKE_DIR}/../../.." ABSOLUTE) - -find_package(fmt CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) -find_package(pugixml CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) -find_package(xtl CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) -find_package(xtensor CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) if(@OPENMC_USE_DAGMC@) find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@) endif() @@ -20,7 +13,60 @@ 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) + + # TODO: Need to implement ExternalProject_Add for OpenMC + find_package(fmt REQUIRED HINTS ${Python_SITELIB}/openmc) + find_package(pugixml REQUIRED HINTS ${Python_SITELIB}/openmc) + find_package(xtl REQUIRED HINTS ${Python_SITELIB}/openmc) + find_package(xtensor REQUIRED HINTS ${Python_SITELIB}/openmc) + + # 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}) +else() + # Compute the install prefix from this file's location + get_filename_component(_OPENMC_PREFIX "${OpenMC_CMAKE_DIR}/../../.." ABSOLUTE) + find_package(fmt CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) + find_package(pugixml CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) + find_package(xtl CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) + find_package(xtensor CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") endif() diff --git a/docs/source/devguide/workflow.rst b/docs/source/devguide/workflow.rst index c49326a2090..8fea63ea033 100644 --- a/docs/source/devguide/workflow.rst +++ b/docs/source/devguide/workflow.rst @@ -63,9 +63,9 @@ features and bug fixes. The general steps for contributing are as follows: .. code-block:: sh - git clone --recurse-submodules git@github.com:yourusername/openmc.git - cd openmc - git checkout -b newbranch develop + git clone --recurse-submodules git@github.com:yourusername/openmc.git + cd openmc + git checkout -b newbranch develop 3. Run ``tools/dev/install-commit-hooks.sh`` to install a post-commit hook that runs clang-format on C++ files to apply :ref:`automatic code formatting @@ -140,15 +140,14 @@ pip_. From the root directory of the OpenMC repository, run: .. code-block:: sh - python -m pip install -e .[test] + python -m pip install --no-build-isolation \ + -Ceditable.rebuild=true \ + -Ccmake.build-type=Debug \ + -Cbuild-dir=build \ + -ve ".[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. +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 0f887940ea5..19102b4c889 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -135,38 +135,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 64d48017993..41fa35ae42c 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -161,9 +161,9 @@ feature can be used to access the installed packages. .. _install_source: ----------------------- -Installing from Source ----------------------- +-------------------------------- +Compiling from source with CMake +-------------------------------- .. _prerequisites: @@ -352,6 +352,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 ++++++++++++++++++++++ @@ -488,29 +490,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_DAGMC=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/include/openmc/config.h b/include/openmc/config.h new file mode 100644 index 00000000000..94fdf076aca --- /dev/null +++ b/include/openmc/config.h @@ -0,0 +1,7 @@ +#pragma once + +namespace openmc { + +int openmc_config_main(int argc, char** argv); + +} // namespace openmc diff --git a/openmc/__init__.py b/openmc/__init__.py index bb972b4e6ad..ddc454da73f 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -35,6 +35,7 @@ from openmc.polynomial import * from openmc.tracks import * from .config import * +from .paths import * # Import a few names from the model module from openmc.model import Model diff --git a/openmc/lib/__init__.py b/openmc/lib/__init__.py index d2a794eb158..3f45cc40398 100644 --- a/openmc/lib/__init__.py +++ b/openmc/lib/__init__.py @@ -13,20 +13,12 @@ """ 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': # 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/openmc/paths.py b/openmc/paths.py new file mode 100644 index 00000000000..8671350a45e --- /dev/null +++ b/openmc/paths.py @@ -0,0 +1,73 @@ +import os +import sys +import glob +import warnings +from . import __path__ + +OPENMC_CORE_BASE_PATH = os.path.join(__path__[0], "core") + +if not os.path.exists(OPENMC_CORE_BASE_PATH): + import sysconfig + OPENMC_CORE_BASE_PATH = os.path.join(sysconfig.get_path("platlib"), "openmc", "core") + if not os.path.exists(OPENMC_CORE_BASE_PATH): + raise ImportError("OpenMC is not installed. Please run 'pip install openmc'.") + if os.environ.get("OPENMC_DEV_MODE", "0") != "1": + warnings.warn( + "It seems OpenMC is being run from its source directory. " + "This setup is not recommended as it may lead to unexpected behavior, " + "such as conflicts between source and installed versions. " + "Please run your script from outside the OpenMC source tree.", + RuntimeWarning + ) + +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 ([], []) + +def get_cmake_path(): + """Return the path to the OpenMC CMake configuration files.""" + cmake_path_lib = os.path.join(OPENMC_CORE_BASE_PATH, 'lib', 'cmake', 'OpenMC') + cmake_path_lib64 = os.path.join(OPENMC_CORE_BASE_PATH, 'lib64', 'cmake', 'OpenMC') + if os.path.exists(cmake_path_lib): + return cmake_path_lib + elif os.path.exists(cmake_path_lib64): + return cmake_path_lib64 + else: + return "" + +# Setup variables +include, include_path = get_include_path() +lib, lib_path = get_core_libraries() +extra_lib, extra_lib_path = get_extra_libraries() +cmake_path = get_cmake_path() + +# Export variables for easy access +__all__ = ["include", "include_path", "lib", "lib_path", "extra_lib", "extra_lib_path", "cmake_path"] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 2d67e834012..2cdf8943b09 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" @@ -65,13 +65,11 @@ 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-dir="build" +wheel.install-dir = "openmc" +wheel.packages = ["openmc"] +sdist.include = ["*.h5"] [tool.setuptools_scm] diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 00000000000..e07dbd3dbb1 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,151 @@ +#include "openmc/config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace openmc { + +std::string list_files(const std::string& path) +{ + std::string result = ""; + DIR* dir; + struct dirent* ent; + if ((dir = opendir(path.c_str())) != NULL) { + while ((ent = readdir(dir)) != NULL) { + if (std::string(ent->d_name) != "." && std::string(ent->d_name) != "..") { + result += ent->d_name; + result += " "; + } + } + closedir(dir); + } + return result; +} + +void print_config_usage() +{ + std::cout << "Usage: openmc config [options]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << " -p, --prefix Print the installation prefix" + << std::endl; + std::cout << " -i, --include-dir Print the include directory" + << std::endl; + std::cout << " -l, --lib-dir Print the library directory" + << std::endl; + std::cout << " -c, --cmake-dir Print the CMake configuration directory" + << std::endl; + std::cout + << " -L, --libs Print the all libraries in the library directory" + << std::endl; + std::cout << " -e, --extra-lib-dir Print the extra library directory" + << std::endl; + std::cout << " -E, --extra-libs Print the extra libraries" << std::endl; +} + +std::string exec(const std::string& cmd) +{ + std::array buffer; + std::string result; + std::unique_ptr pipe( + popen(cmd.c_str(), "r"), pclose); + if (!pipe) { + throw std::runtime_error("popen() failed!"); + } + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + // Remove trailing newline character if present + if (!result.empty() && result.back() == '\n') { + result.pop_back(); + } + return result; +} + +int openmc_config_main(int argc, char** argv) +{ + std::vector args(argv + 1, argv + argc); + + if (args.empty()) { + print_config_usage(); + return 1; + } + + // Default values + std::string install_prefix_val; + std::string include_dir_val; + std::string lib_dir_val; + std::string cmake_dir_val; + std::string libs_val; + std::string extra_libs_val; + std::string extra_lib_dir_val; + +#ifdef SKBUILD + try { + install_prefix_val = exec("python3 -c \"import sys; print(sys.prefix)\""); + include_dir_val = exec("python3 -c \"import openmc.paths; " + "print(''.join(openmc.paths.include_path))\""); + lib_dir_val = exec("python3 -c \"import openmc.paths; " + "print(''.join(openmc.paths.lib_path))\""); + cmake_dir_val = exec( + "python3 -c \"import openmc.paths; print(openmc.paths.cmake_path)\" "); + libs_val = exec( + "python3 -c \"import openmc.paths; print(' '.join(openmc.paths.lib))\""); + extra_libs_val = exec("python3 -c \"import openmc.paths; print(' " + "'.join(openmc.paths.extra_lib))\""); + extra_lib_dir_val = exec( + "python3 -c \"import openmc.paths; print(openmc.paths.extra_lib_path)\""); + } catch (const std::runtime_error& e) { + std::cerr << "Fatal error: Could not get configuration from openmc.paths " + "via Python: " + << e.what() << std::endl; + return 1; + } +#else + // Hardcoded values for CMake-only build + install_prefix_val = CMAKE_INSTALL_PREFIX; + include_dir_val = + std::string(CMAKE_INSTALL_PREFIX) + "/" + CMAKE_INSTALL_INCLUDEDIR; + lib_dir_val = std::string(CMAKE_INSTALL_PREFIX) + "/" + CMAKE_INSTALL_LIBDIR; + cmake_dir_val = std::string(CMAKE_INSTALL_PREFIX) + "/" + + CMAKE_INSTALL_LIBDIR + "/cmake/OpenMC"; + libs_val = list_files(lib_dir_val); + extra_libs_val = ""; + extra_lib_dir_val = ""; +#endif + + for (const auto& arg : args) { + if (arg == "--prefix" || arg == "-p") { + std::cout << install_prefix_val << std::endl; + return 0; + } else if (arg == "--include-dir" || arg == "-i") { + std::cout << include_dir_val << std::endl; + return 0; + } else if (arg == "--lib-dir" || arg == "-l") { + std::cout << lib_dir_val << std::endl; + return 0; + } else if (arg == "--cmake-dir" || arg == "-c") { + std::cout << cmake_dir_val << std::endl; + return 0; + } else if (arg == "--libs" || arg == "-L") { + std::cout << libs_val << std::endl; + return 0; + } else if (arg == "--extra-lib-dir" || arg == "-e") { + std::cout << extra_lib_dir_val << std::endl; + return 0; + } else if (arg == "--extra-libs" || arg == "-E") { + std::cout << extra_libs_val << std::endl; + return 0; + } + } + + print_config_usage(); + return 1; +} + +} // namespace openmc diff --git a/src/main.cpp b/src/main.cpp index 88251ac7232..2025993cd33 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,11 @@ #ifdef OPENMC_MPI #include #endif + +#include + #include "openmc/capi.h" +#include "openmc/config.h" #include "openmc/constants.h" #include "openmc/error.h" #include "openmc/message_passing.h" @@ -14,6 +18,11 @@ int main(int argc, char* argv[]) using namespace openmc; int err; + // Check for config subcommand + if (argc > 1 && std::string(argv[1]) == "config") { + return openmc_config_main(argc - 1, argv + 1); + } + // Initialize run -- when run with MPI, pass communicator #ifdef OPENMC_MPI MPI_Comm world {MPI_COMM_WORLD}; diff --git a/src/output.cpp b/src/output.cpp index 0a14e8843d8..44b72d6999a 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -269,7 +269,10 @@ void print_usage() { if (mpi::master) { fmt::print( - "Usage: openmc [options] [path]\n\n" + "Usage: openmc [options] [path]\n\n" + "Commands:\n" + " config Display installation configuration " + "information\n\n" "Options:\n" " -c, --volume Run in stochastic volume calculation mode\n" " -g, --geometry-debug Run with geometry debugging on\n" diff --git a/tests/regression_tests/cpp_driver/test.py b/tests/regression_tests/cpp_driver/test.py index b80e82ee0e1..23f8da740dd 100644 --- a/tests/regression_tests/cpp_driver/test.py +++ b/tests/regression_tests/cpp_driver/test.py @@ -16,16 +16,18 @@ def cpp_driver(request): """Compile the external source""" - # Get build directory and write CMakeLists.txt file - openmc_dir = Path(str(request.config.rootdir)) / 'build' + # Get cmake_dir from openmc config command + result = subprocess.run(['openmc', 'config', '--cmake-dir'], capture_output=True, text=True, check=True) + cmake_dir = result.stdout.strip() + with open('CMakeLists.txt', 'w') as f: - f.write(textwrap.dedent(""" + f.write(textwrap.dedent(f""" cmake_minimum_required(VERSION 3.10 FATAL_ERROR) project(openmc_cpp_driver CXX) add_executable(cpp_driver driver.cpp) - find_package(OpenMC REQUIRED HINTS {}) + find_package(OpenMC REQUIRED HINTS {cmake_dir}) target_link_libraries(cpp_driver OpenMC::libopenmc) - """.format(openmc_dir))) + """)) # Create temporary build directory and change to there local_builddir = Path('build') diff --git a/tests/regression_tests/source_dlopen/test.py b/tests/regression_tests/source_dlopen/test.py index 0581d6deec4..8d1e20e48b7 100644 --- a/tests/regression_tests/source_dlopen/test.py +++ b/tests/regression_tests/source_dlopen/test.py @@ -14,16 +14,18 @@ def compile_source(request): """Compile the external source""" - # Get build directory and write CMakeLists.txt file - openmc_dir = Path(str(request.config.rootdir)) / 'build' + # Get cmake_dir from openmc config command + result = subprocess.run(['openmc', 'config', '--cmake-dir'], capture_output=True, text=True, check=True) + cmake_dir = result.stdout.strip() + with open('CMakeLists.txt', 'w') as f: - f.write(textwrap.dedent(""" + f.write(textwrap.dedent(f""" cmake_minimum_required(VERSION 3.10 FATAL_ERROR) project(openmc_sources CXX) add_library(source SHARED source_sampling.cpp) - find_package(OpenMC REQUIRED HINTS {}) + find_package(OpenMC REQUIRED HINTS {cmake_dir}) target_link_libraries(source OpenMC::libopenmc) - """.format(openmc_dir))) + """)) # Create temporary build directory and change to there local_builddir = Path('build') diff --git a/tests/unit_tests/test_paths.py b/tests/unit_tests/test_paths.py new file mode 100644 index 00000000000..7c2fe7c3541 --- /dev/null +++ b/tests/unit_tests/test_paths.py @@ -0,0 +1,97 @@ +import sys +import pytest +import importlib +from unittest import mock +from pathlib import Path +from openmc import paths + + +def test_openmc_core_base_path_nameerror(monkeypatch): + """Test fallback logic when __path__ is not defined.""" + monkeypatch.setitem(sys.modules["openmc.paths"].__dict__, '__path__', None) + + with mock.patch("os.path.exists", return_value=False), \ + mock.patch("sysconfig.get_path", return_value="/mock/path"): + with pytest.raises(ImportError, match="OpenMC is not installed"): + importlib.reload(paths) + +def test_openmc_core_base_path_importerror(monkeypatch): + """Test ImportError raised when OpenMC is not installed and no core path is found.""" + monkeypatch.setitem(sys.modules['openmc.paths'].__dict__, '__path__', None) + with mock.patch("os.path.exists", return_value=False), \ + mock.patch("sysconfig.get_path", return_value="/mock/path"): + with pytest.raises(ImportError, match="OpenMC is not installed. Please run 'pip install openmc'."): + importlib.reload(paths) + +def test_openmc_core_base_path_warning(monkeypatch): + """Test warning when running OpenMC from source directory without dev mode enabled.""" + + monkeypatch.setitem(sys.modules["openmc.paths"].__dict__, '__path__', ["/mock/source/openmc"]) + monkeypatch.delenv("OPENMC_DEV_MODE", raising=False) + + with mock.patch("os.path.exists", side_effect=[False, True, True, True]), \ + mock.patch("sysconfig.get_path", return_value="/mock/source"), \ + pytest.warns(RuntimeWarning, match="It seems OpenMC is being run from its source directory"): + importlib.reload(paths) + +def test_get_paths_non_recursive(): + """Test get_paths with non-recursive file search.""" + result = paths.get_paths("include", "*", recursive=False) + assert isinstance(result, list) + +def test_get_paths_recursive(): + """Test get_paths with recursive file search.""" + result = paths.get_paths("include", "*", recursive=True) + assert isinstance(result, list) + +def test_get_include_path(): + """Test retrieval of include files and their path.""" + include, include_path = paths.get_include_path() + assert isinstance(include, list) + assert isinstance(include_path, list) + +def test_get_core_libraries(): + """Test retrieval of core library files and their path.""" + lib, lib_path = paths.get_core_libraries() + assert isinstance(lib, list) + assert isinstance(lib_path, list) + +@pytest.mark.parametrize("platform_value, expected_dir_name, expected_ext", [ + ("darwin", ".dylibs", "dylib"), + ("linux", "openmc.libs", "so"), +]) +def test_get_extra_libraries_cross_platform(tmp_path, platform_value, expected_dir_name, expected_ext): + """Simulate different platforms to test get_extra_libraries logic completely.""" + lib_dir = tmp_path / expected_dir_name + lib_dir.mkdir() + dummy_file = lib_dir / f"libdummy.{expected_ext}" + dummy_file.write_text("mock") + + with mock.patch.object(paths, "__path__", [str(tmp_path)]): + with mock.patch("sys.platform", new=platform_value): + extra_libs, extra_lib_path = paths.get_extra_libraries() + + assert isinstance(extra_libs, list) + if extra_lib_path: + assert isinstance(extra_lib_path, str) + assert dummy_file in map(Path, extra_libs) + assert extra_lib_path == str(lib_dir) + else: + assert extra_libs == [] + +def test_get_extra_libraries_missing_dir(): + """Test get_extra_libraries when expected directories are missing.""" + with mock.patch.object(paths, "__path__", ["/nonexistent/fakepath"]): + extra_libs, extra_lib_path = paths.get_extra_libraries() + assert extra_libs == [] + assert extra_lib_path == [] + +def test_get_extra_libraries_no_match(tmp_path): + """Ensure get_extra_libraries returns empty if no known lib dirs exist.""" + isolated_path = tmp_path / "nothing_here" + isolated_path.mkdir() + + with mock.patch.object(paths, "__path__", [str(isolated_path)]): + extra_libs, extra_lib_path = paths.get_extra_libraries() + assert extra_libs == [] + assert extra_lib_path == [] diff --git a/tools/ci/gha-install.py b/tools/ci/gha-install.py index 1cc792f8d78..752801d9fb3 100644 --- a/tools/ci/gha-install.py +++ b/tools/ci/gha-install.py @@ -4,51 +4,44 @@ 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'] # 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('..') - print(' '.join(cmake_cmd)) - subprocess.check_call(cmake_cmd) - subprocess.check_call(['make', '-j4']) - subprocess.check_call(['sudo', 'make', 'install']) + # Set environment variable for SKBUILD + os.environ['SKBUILD_CMAKE_ARGS'] = ';'.join(cmake_args) + + # Run pip to build and install + subprocess.check_call(['pip', '-v', 'install', '-e', '.[test,vtk,ci]']) 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 74c3947f183..747adf8a45b 100755 --- a/tools/ci/gha-install.sh +++ b/tools/ci/gha-install.sh @@ -42,8 +42,6 @@ if [[ $MPI == 'y' ]]; then pip install --no-binary=h5py h5py fi -# Build and install OpenMC executable +# Build and install OpenMC 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 b40238ffb56..b838b487803 100755 --- a/tools/ci/gha-script.sh +++ b/tools/ci/gha-script.sh @@ -14,6 +14,9 @@ if [[ $EVENT == 'y' ]]; then args="${args} --event " fi +# Skip source directory warning +export OPENMC_DEV_MODE=1 + # Run unit tests and then regression tests pytest -v $args \ tests/test_matplotlib_import.py \