diff --git a/.github/workflows/dependencies/gcc.sh b/.github/workflows/dependencies/gcc.sh index b658a65b1..2573d9300 100755 --- a/.github/workflows/dependencies/gcc.sh +++ b/.github/workflows/dependencies/gcc.sh @@ -22,4 +22,6 @@ sudo apt-get install -y \ wget python3 -m pip install -U pip setuptools wheel +python3 -m pip install -U cmake pytest python3 -m pip install -r requirements.txt +python3 -m pip install -r examples/requirements.txt diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index ff4bb2d66..0e8d7d625 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -52,3 +52,46 @@ jobs: { cat Backtrace.0.0; exit 1; } build/bin/impactx examples/fodo/input_fodo.in algo.particle_shape = 3 || \ { cat Backtrace.0.0; exit 1; } + + build_gcc_python: + name: GCC w/o MPI w/ Python + runs-on: ubuntu-20.04 + if: github.event.pull_request.draft == false + env: + CMAKE_GENERATOR: Ninja + CXXFLAGS: "-Werror" + OMP_NUM_THREADS: 2 + steps: + - uses: actions/checkout@v2 + + - name: install dependencies + run: | + .github/workflows/dependencies/gcc.sh + + - name: CCache Cache + uses: actions/cache@v2 + # - once stored under a key, they become immutable (even if local cache path content changes) + # - for a refresh the key has to change, e.g., hash of a tracked file in the key + with: + path: | + ~/.ccache + ~/.cache/ccache + key: ccache-openmp-pygcc-${{ hashFiles('.github/workflows/ubuntu.yml') }}-${{ hashFiles('cmake/dependencies/ABLASTR.cmake') }} + restore-keys: | + ccache-openmp-pygcc-${{ hashFiles('.github/workflows/ubuntu.yml') }}- + ccache-openmp-pygcc- + + - name: build ImpactX + run: | + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_VERBOSE_MAKEFILE=ON \ + -DImpactX_APP=OFF \ + -DImpactX_MPI=OFF \ + -DImpactX_PYTHON=ON + cmake --build build -j 2 + cmake --build build --target pip_install + + - name: run ImpactX + run: | + ctest --test-dir build --output-on-failure diff --git a/.gitignore b/.gitignore index 643e35c1a..7e8820fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ chk* ########## # Python # ########## +_tmppythonbuild/ *.pyc __pycache__ dist/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 7279b7045..b2e53aa44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,19 +44,20 @@ set_ccache() # Output Directories ########################################################## # # temporary build directories -set_default_build_dirs() +impactx_set_default_build_dirs() -# install directories -set_default_install_dirs() +# default installation directories (w/o Python) +impactx_set_default_install_dirs() # Options and Variants ######################################################## # include(CMakeDependentOption) option(ImpactX_APP "Build the ImpactX executable application" ON) -option(ImpactX_LIB "Build ImpactX as a shared library" OFF) +option(ImpactX_LIB "Build ImpactX as a library" OFF) option(ImpactX_MPI "Multi-node support (message-passing)" ON) option(ImpactX_OPENPMD "openPMD I/O (HDF5, ADIOS)" OFF) +option(ImpactX_PYTHON "Python bindings" OFF) set(ImpactX_PRECISION_VALUES SINGLE DOUBLE) set(ImpactX_PRECISION DOUBLE CACHE STRING "Floating point precision (SINGLE/DOUBLE)") @@ -77,6 +78,10 @@ mark_as_advanced(ImpactX_MPI_THREAD_MULTIPLE) option(ImpactX_ablastr_internal "Download & build ABLASTR" ON) option(ImpactX_amrex_internal "Download & build AMReX" ON) +option(ImpactX_pyamrex_internal "Download & build pyAMReX" ON) + +set(pyImpactX_VERSION_INFO "" CACHE STRING + "PEP-440 conformant version (set by distutils)") # change the default build type to Release (or RelWithDebInfo) instead of Debug set_default_build_type("Release") @@ -99,9 +104,28 @@ include(CTest) include(${ImpactX_SOURCE_DIR}/cmake/dependencies/ABLASTR.cmake) impactx_make_third_party_includes_system(WarpX::ablastr ablastr) +# Python +if(ImpactX_PYTHON) + find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) + + # default installation directories: Python + impactx_set_default_install_dirs_python() + + # pybind11 + # builds pybind11 from git (default), form local source or + # finds an existing install + include(${ImpactX_SOURCE_DIR}/cmake/dependencies/pybind11.cmake) + + # pyAMReX + include(${ImpactX_SOURCE_DIR}/cmake/dependencies/pyAMReX.cmake) +endif() + # Targets ##################################################################### # +if(ImpactX_PYTHON) + set(ImpactX_LIB ON CACHE STRING "library" FORCE) +endif() if(NOT ImpactX_APP AND NOT ImpactX_LIB) message(FATAL_ERROR "Need to build at least ImpactX app or " "library/Python bindings") @@ -122,20 +146,56 @@ if(ImpactX_APP) list(APPEND _ALL_TARGETS app) endif() -# link into a shared library +# link into a library (shared or static) if(ImpactX_LIB) - add_library(shared MODULE) - add_library(ImpactX::shared ALIAS shared) - target_link_libraries(shared PUBLIC ImpactX) - set(_BUILDINFO_SRC shared) - list(APPEND _ALL_TARGETS shared) + add_library(lib) + add_library(ImpactX::lib ALIAS lib) + target_link_libraries(lib PUBLIC ImpactX) + set(_BUILDINFO_SRC lib) + list(APPEND _ALL_TARGETS lib) - set_target_properties(ImpactX shared PROPERTIES + set_target_properties(ImpactX lib PROPERTIES POSITION_INDEPENDENT_CODE ON WINDOWS_EXPORT_ALL_SYMBOLS ON ) endif() +# build Python module (this is always a shared library) +if(ImpactX_PYTHON) + add_library(pyImpactX MODULE src/python/pyImpactX.cpp) + add_library(ImpactX::pyImpactX ALIAS pyImpactX) + target_link_libraries(pyImpactX PUBLIC lib) + set(_BUILDINFO_SRC pyImpactX) + list(APPEND _ALL_TARGETS pyImpactX) + + # set Python module properties + set_target_properties(pyImpactX PROPERTIES + # hide symbols for combining multiple pybind11 modules downstream & for + # reduced binary size + CXX_VISIBILITY_PRESET "hidden" + CUDA_VISIBILITY_PRESET "hidden" + # name of the pybind-generated python module, which is wrapped in another + # fluffy front-end modules, so we can extend it with pure Python + ARCHIVE_OUTPUT_NAME impactx_pybind + LIBRARY_OUTPUT_NAME impactx_pybind + # build output directories - mainly set to run tests from CMake & IDEs + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_PYTHON_OUTPUT_DIRECTORY}/impactx + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_PYTHON_OUTPUT_DIRECTORY}/impactx + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_PYTHON_OUTPUT_DIRECTORY}/impactx + PDB_OUTPUT_DIRECTORY ${CMAKE_PYTHON_OUTPUT_DIRECTORY}/impactx + COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_PYTHON_OUTPUT_DIRECTORY}/impactx + ) + if(EMSCRIPTEN) + set_target_properties(pyImpactX PROPERTIES + PREFIX "") + else() + pybind11_extension(pyImpactX) + endif() + if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) + pybind11_strip(pyImpactX) + endif() +endif() + # own headers target_include_directories(ImpactX PUBLIC $ @@ -168,6 +228,9 @@ endif() # link dependencies # note: only PUBLIC because ImpactX is an OBJECT collection target_link_libraries(ImpactX PUBLIC ImpactX::thirdparty::ablastr) +if(ImpactX_PYTHON) + target_link_libraries(pyImpactX PRIVATE pybind11::module pybind11::lto pybind11::windows_extras) +endif() if(ImpactX_OPENPMD) target_compile_definitions(ImpactX PUBLIC ImpactX_USE_OPENPMD) @@ -199,7 +262,7 @@ if(ImpactX_COMPUTE STREQUAL CUDA) endif() # fancy binary name for build variants -set_ImpactX_binary_name() +impactx_set_binary_name() # Defines ##################################################################### @@ -207,6 +270,11 @@ set_ImpactX_binary_name() if(ImpactX_OPENPMD) target_compile_definitions(ImpactX PUBLIC ImpactX_USE_OPENPMD) endif() +if(ImpactX_PYTHON) + # for module __version__ + target_compile_definitions(pyImpactX PRIVATE + PYIMPACTX_VERSION_INFO=${pyImpactX_VERSION_INFO}) +endif() # Warnings #################################################################### @@ -235,13 +303,17 @@ configure_file( # Installs #################################################################### # + # headers, libraries and executables set(ImpactX_INSTALL_TARGET_NAMES) if(ImpactX_APP) list(APPEND ImpactX_INSTALL_TARGET_NAMES app) endif() if(ImpactX_LIB) - list(APPEND ImpactX_INSTALL_TARGET_NAMES shared) + list(APPEND ImpactX_INSTALL_TARGET_NAMES lib) +endif() +if(ImpactX_PYTHON) + list(APPEND ImpactX_INSTALL_TARGET_NAMES pyImpactX) endif() install(TARGETS ${ImpactX_INSTALL_TARGET_NAMES} @@ -266,7 +338,7 @@ if(ImpactX_LIB) set(ABS_INSTALL_LIB_DIR ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) endif() install(CODE "file(CREATE_LINK - $ + $ ${ABS_INSTALL_LIB_DIR}/libImpactX.${mod_ext} COPY_ON_ERROR SYMBOLIC)") endif() @@ -275,26 +347,96 @@ endif() #install(EXPORT ImpactXTargets # FILE ImpactXTargets.cmake # NAMESPACE ImpactX:: -# DESTINATION ${CMAKE_INSTALL_CMAKEDIR} +# DESTINATION ${ImpactX_INSTALL_CMAKEDIR} #) #install( # FILES # ${ImpactX_BINARY_DIR}/ImpactXConfig.cmake # ${ImpactX_BINARY_DIR}/ImpactXConfigVersion.cmake -# DESTINATION ${CMAKE_INSTALL_CMAKEDIR} +# DESTINATION ${ImpactX_INSTALL_CMAKEDIR} #) +# pip helpers for the impactx package ######################################### +# +if(ImpactX_PYTHON) + set(PYINSTALLOPTIONS "" CACHE STRING + "Additional parameters to pass to `pip install`") + + # add a prefix to custom targets so we do not collide if used as a subproject + if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + set(_ImpactX_CUSTOM_TARGET_PREFIX_DEFAULT "") + else() + set(_ImpactX_CUSTOM_TARGET_PREFIX_DEFAULT "impactx_") + endif() + set(ImpactX_CUSTOM_TARGET_PREFIX "${_ImpactX_CUSTOM_TARGET_PREFIX_DEFAULT}" + CACHE STRING "Prefix for custom targets") + + # build the wheel by re-using the lib library we build + add_custom_target(${ImpactX_CUSTOM_TARGET_PREFIX}pip_wheel + ${CMAKE_COMMAND} -E rm -f "impactx*.whl" + COMMAND + ${CMAKE_COMMAND} -E env PYIMPACTX_LIBDIR=$ + ${Python_EXECUTABLE} -m pip wheel -v --no-build-isolation ${ImpactX_SOURCE_DIR} + WORKING_DIRECTORY + ${ImpactX_BINARY_DIR} + DEPENDS + pyImpactX + ) + + # this will also upgrade/downgrade dependencies, e.g., when the version of numpy changes + add_custom_target(${ImpactX_CUSTOM_TARGET_PREFIX}pip_install_requirements + ${Python_EXECUTABLE} -m pip install ${PYINSTALLOPTIONS} -r "${ImpactX_SOURCE_DIR}/requirements.txt" + WORKING_DIRECTORY + ${ImpactX_BINARY_DIR} + ) + + # if we do a superbuild, make sure we install pyAMReX via its custom install + # target + set(_EXTRA_INSTALL_DEPENDS) + if(ImpactX_pyamrex_internal OR ImpactX_pyamrex_src) + set(_EXTRA_INSTALL_DEPENDS pyamrex_pip_install) + endif() + + # We force-install because in development, it is likely that the version of + # the package does not change, but the code did change. We need --no-deps, + # because otherwise pip would also force reinstall all dependencies. + add_custom_target(${ImpactX_CUSTOM_TARGET_PREFIX}pip_install + ${CMAKE_COMMAND} -E env IMPACTX_MPI=${ImpactX_MPI} + ${Python_EXECUTABLE} -m pip install --force-reinstall --no-deps ${PYINSTALLOPTIONS} "impactx*.whl" + WORKING_DIRECTORY + ${ImpactX_BINARY_DIR} + DEPENDS + pyImpactX + ${ImpactX_CUSTOM_TARGET_PREFIX}pip_wheel ${ImpactX_CUSTOM_TARGET_PREFIX}pip_install_requirements + ${_EXTRA_INSTALL_DEPENDS} + ) +endif() + + # Tests ####################################################################### # if(BUILD_TESTING) enable_testing() # examples - add_subdirectory(examples) + if(ImpactX_APP) + add_subdirectory(examples) + endif() + + # unit tests + if(ImpactX_PYTHON) + # copy Python wrapper library to build directory + add_custom_command(TARGET pyImpactX POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${ImpactX_SOURCE_DIR}/src/python/impactx + $ + ) + endif() + add_subdirectory(tests) endif() # Status Summary for Build Options ############################################ # -ImpactX_print_summary() +impactx_print_summary() diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..8ea37b3b7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,18 @@ +include README.md LICENSE +include CODE_OF_CONDUCT.rst CONTRIBUTING.rst +include pyproject.toml +include requirements.txt + +global-include CMakeLists.txt *.cmake *.in + +recursive-include cmake * +recursive-include docs * +recursive-include etc * +recursive-include examples * +recursive-include include * +recursive-include src * +recursive-include tests * + +# see .gitignore +prune cmake-build* +prune .spack-env* diff --git a/README.md b/README.md index 6f0211f39..a1918ce2f 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,16 @@ or by adding arguments with `-D