#
# This file is part of the GROMACS molecular simulation package.
#
-# Copyright (c) 2019, by the GROMACS development team, led by
+# Copyright (c) 2019,2020,2021, by the GROMACS development team, led by
# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
# and including many others, as listed in the AUTHORS file in the
# top-level source directory and at http://www.gromacs.org.
# To help us fund GROMACS development, we humbly ask that you cite
# the research papers on the package. Check out http://www.gromacs.org.
-# This CMakeLists.txt allows source distributions of the gmxapi Python package
-# to rely on scikit-build for support of various Python packaging systems. The
-# simplest use case is to allow the `setup.py` file to invoke skbuild to
-# configure and run CMake. CMake could be invoked directly by the user or a
-# parent package, but the Python distribution would not be packaged automatically.
-# Reference https://redmine.gromacs.org/issues/2896 for additional discussion.
-cmake_minimum_required(VERSION 3.9.6)
+# This CMakeLists.txt is not intended to be used directly, but either through
+# setup.py or as an inclusion of the full GROMACS project.
+# See https://manual.gromacs.org/current/gmxapi/userguide/install.html for more.
+cmake_minimum_required(VERSION 3.16.3)
# This needs to be set before project() in order to pick up toolchain files
#list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake)
-# OS X deployment target should be >=10.9 for modern C++ compatibility.
+# OS X deployment target should be >=10.14 for modern C++ compatibility.
# Reference https://scikit-build.readthedocs.io/en/latest/generators.html#macosx
# and https://github.com/MacPython/wiki/wiki/Spinning-wheels
-set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9 CACHE STRING
- "OS X deployment target below 10.9 does not use modern standard library"
- FORCE)
+set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14 CACHE STRING
+ "OS X deployment target below 10.14 does not use modern standard library")
set(CMAKE_OSX_ARCHITECTURES x86_64 CACHE STRING
"OS X should build Python package for 64-bit architecture"
FORCE)
-project(gmxapi VERSION 0.1.0)
+# Note that this is the gmxapi._gmxapi Python bindings package version,
+# not the C++ API version. It is not essential that it match the pure Python
+# package version, but is likely to do so.
+project(gmxapi)
# Check if Python package is being built directly or via add_subdirectory
set(GMXAPI_MASTER_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(GMXAPI_MASTER_PROJECT ON)
+ if (NOT Python3_FIND_STRATEGY)
+ # If the user provides a hint for the Python installation with Python3_ROOT_DIR,
+ # prevent FindPython3 from overriding the choice with a newer Python version
+ # when CMP0094 is set to OLD.
+ set(Python3_FIND_STRATEGY LOCATION)
+ endif ()
+ if(NOT Python3_FIND_VIRTUALENV)
+ # We advocate using Python venvs to manage package availability, so by default
+ # we want to preferentially discover user-space software.
+ set(Python3_FIND_VIRTUALENV FIRST)
+ endif()
endif()
-set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Only interpret if() arguments as variables or keywords when unquoted.
cmake_policy(SET CMP0074 NEW)
endif()
+find_package(Python3 3.7 COMPONENTS Interpreter Development)
+find_package(pybind11 2.6 CONFIG)
+# If we are not running through setup.py, we may need to look for the pybind11 headers.
+if (NOT pybind11_FOUND)
+ execute_process(
+ COMMAND
+ "${Python3_EXECUTABLE}" -c
+ "import pybind11; print(pybind11.get_cmake_dir())"
+ OUTPUT_VARIABLE _tmp_dir
+ OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ECHO STDOUT)
+ list(APPEND CMAKE_PREFIX_PATH "${_tmp_dir}")
+ find_package(pybind11 2.6 CONFIG)
+endif ()
+if (NOT pybind11_FOUND)
+ message(FATAL_ERROR "Python package build dependencies not found with interpreter ${Python3_EXECUTABLE}. "
+ "See https://manual.gromacs.org/current/gmxapi/userguide/install.html")
+endif ()
+
if(GMXAPI_MASTER_PROJECT)
- find_package(gmxapi 0.0.8 REQUIRED
+ find_package(gmxapi 0.2 REQUIRED
HINTS "$ENV{GROMACS_DIR}"
)
+ if (gmxapi_VERSION VERSION_LESS 0.2.1)
+ message(WARNING "Your GROMACS installation does not support custom MD plugins. "
+ "If you need this feature, please install GROMACS 2021.3 or higher.")
+ endif ()
+else()
+ # Building as part of the GROMACS master project. GROMACS CMake logic should
+ # not be processing this unless Python3 was appropriately detected.
+ if (NOT Python3_FOUND)
+ message(FATAL_ERROR "Error in CMake script. Please report GROMACS bug.")
+ endif ()
+
+ get_target_property(gmxapi_VERSION gmxapi VERSION)
endif()
+
if(gmxapi_FOUND)
set(_suffix "")
# GROMACS master branch and development branches may have divergent
if(gmxapi_EXPERIMENTAL)
set(_suffix " (unofficial)")
endif()
- message(STATUS "Found gmxapi version ${gmxapi_VERSION}${_suffix}")
endif()
+message(STATUS "Configuring Python package for gmxapi version ${gmxapi_VERSION}${_suffix}")
+
# The Gromacs::gmxapi target could be imported from an existing installation or
# provided as an alias target within the GROMACS build tree.
if (NOT TARGET Gromacs::gmxapi)
message(FATAL_ERROR "Cannot build Python package without GROMACS gmxapi support.")
endif ()
-# TODO: Provide user hints for mpi4py installation.
+# TODO(#3279): Provide user hints for mpi4py installation.
# Note that neither the Python package nor the Gromacs::gmxapi CMake target are
# built with MPI in any case, but they _should_ be built with a C++ compiler
# that is compatible with the available MPI compiler wrappers, and technically
# For convenience, it is fine if libgmxapi and _gmxapi are built with the mpi
# compiler wrapper.
-option(GMXAPI_USE_BUNDLED_PYBIND
- "Use pybind11 headers bundled with this repository. If OFF, CMake does `find_package(pybind11)`."
- ON)
-if(GMXAPI_USE_BUNDLED_PYBIND)
- add_subdirectory(external/pybind)
-else()
- # Reference https://redmine.gromacs.org/issues/2896
- find_package(pybind11 2.2 REQUIRED)
-endif()
-
-set(GMXAPI_PYTHON_EXTENSION_SOURCES
- gmxapi/module.cpp
- gmxapi/export_context.cpp
- gmxapi/export_exceptions.cpp
- gmxapi/export_system.cpp
- gmxapi/export_tprfile.cpp
- gmxapi/pycontext.cpp
- gmxapi/pysystem.cpp
- )
-
pybind11_add_module(_gmxapi
- ${GMXAPI_PYTHON_EXTENSION_SOURCES}
+ gmxapi/module.cpp
+ gmxapi/export_context.cpp
+ gmxapi/export_exceptions.cpp
+ gmxapi/export_system.cpp
+ gmxapi/export_tprfile.cpp
+ gmxapi/pycontext.cpp
+ gmxapi/pysystem.cpp
)
+if (gmxapi_VERSION VERSION_GREATER_EQUAL 0.2.1)
+ target_sources(_gmxapi PRIVATE gmxapi/launch_021.cpp)
+else()
+ message(WARNING "Found an old gmxapi library version. Please consider updating your GROMACS installation.")
+ target_sources(_gmxapi PRIVATE gmxapi/launch_020.cpp)
+endif()
+
target_include_directories(_gmxapi PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/gmxapi
${CMAKE_CURRENT_BINARY_DIR}/gmxapi
# RPATH management: make sure build artifacts can find GROMACS library.
set_target_properties(_gmxapi PROPERTIES SKIP_BUILD_RPATH FALSE)
-# Python sources (*.py) will be packaged by scikit-build and setuptools.
-# Note that library targets are built in CMAKE_LIBRARY_OUTPUT_DIRECTORY if not otherwise specified.
-# This may be an unexpected location, whether inherited from the GROMACS build tree
-# or the SKBUILD framework. Note, also, that when scikit-build is invoked with setup.py,
-# the CMake build takes place in a subdirectory of ./_skbuild/.
-set(GMXAPI_PYTHON_STAGING_DIR ${CMAKE_CURRENT_BINARY_DIR}/gmxapi_staging)
-set_target_properties(_gmxapi PROPERTIES
- LIBRARY_OUTPUT_DIRECTORY ${GMXAPI_PYTHON_STAGING_DIR}/gmxapi)
-
-# scikit-build sets SKBUILD when running Python packaging tools through setup.py
-# (e.g. with pip)
-if(SKBUILD)
- # The Python module is being built for a GROMACS installation.
+if(GMXAPI_MASTER_PROJECT)
+ # TODO: This requirement is probably overly restrictive.
+ find_package(GROMACS 2021 REQUIRED
+ NAMES gromacs gromacs_mpi
+ HINTS "$ENV{GROMACS_DIR}"
+ )
+endif()
+
+# Get details of GROMACS installation needed by the Python package at run time.
+
+# Get the MPI capability.
+get_target_property(_gmx_mpi Gromacs::gmxapi MPI)
+if (${_gmx_mpi} STREQUAL "library")
+ set(_gmx_mpi_type "\"library\"")
+elseif(${_gmx_mpi} STREQUAL "tmpi")
+ set(_gmx_mpi_type "\"tmpi\"")
+elseif(${_gmx_mpi} STREQUAL "none")
+ set(_gmx_mpi_type "null")
+else()
+ message(FATAL_ERROR "Unrecognized gmxapi MPI value: ${_gmx_mpi}")
+endif ()
+unset(_gmx_mpi)
+# Get the path of the command line entry point and binary install directory.
+if (NOT TARGET Gromacs::gmx)
+ message(FATAL_ERROR "GROMACS command line tool not found.")
+endif ()
+get_target_property(_gmx_executable_imported Gromacs::gmx IMPORTED)
+if (_gmx_executable_imported)
+ get_target_property(_gmx_executable Gromacs::gmx LOCATION)
+ get_filename_component(_gmx_bindir ${_gmx_executable} DIRECTORY)
+ message(STATUS "Imported ${_gmx_bindir} executable.")
+ unset(_gmx_executable_imported)
+else()
+ get_target_property(_gmx_bindir Gromacs::gmx RUNTIME_OUTPUT_DIRECTORY)
+ get_target_property(_gmx_executable Gromacs::gmx OUTPUT_NAME)
+ set(_gmx_executable "${_gmx_bindir}/${_gmx_executable}")
+ message(STATUS "Using ${_gmx_executable} from build tree.")
+endif ()
+if (NOT _gmx_bindir OR NOT _gmx_executable)
+ message(FATAL_ERROR "Could not get path for gmx wrapper binary.")
+endif ()
+configure_file(gmxapi/gmxconfig.json.in gmxapi/gmxconfig.json)
+unset(_gmx_executable)
+unset(_gmx_bindir)
+unset(_gmx_mpi_type)
+
+if (GMXAPI_MASTER_PROJECT)
set_target_properties(_gmxapi PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
set_target_properties(_gmxapi PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE)
target_link_libraries(_gmxapi PRIVATE Gromacs::gmxapi)
- # By default, scikit-build expects the library to be installed into a directory
- # named for the Python package as in setup.py.
- install(TARGETS _gmxapi LIBRARY DESTINATION gmxapi)
+ # The Python setup.py sets CMAKE_LIBRARY_OUTPUT_DIRECTORY and will be looking for generated files there.
+ file(COPY ${CMAKE_CURRENT_BINARY_DIR}/gmxapi/gmxconfig.json
+ DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
else()
- # The Python module is being built against GROMACS in its build tree.
- # Note: we do not have plans to install the staged package when SKBUILD != TRUE
- set_target_properties(_gmxapi PROPERTIES BUILD_WITH_INSTALL_RPATH FALSE)
- target_link_libraries(_gmxapi PRIVATE Gromacs::gmxapi)
-
- # TODO: Determine packaging and installation cases and implementation.
- # Reference https://redmine.gromacs.org/issues/2896 for additional discussion.
- # Currently, CMake should be run by scikit-build through setup.py for proper Python packaging.
- # We don't want to install by default in the outer scope of the GROMACS
- # CMake procedure because we could end up trying to install to a system directory
- # the user did not intend, or a user might install a Python-installation-specific
- # package into an overly generic GROMACS path.
+ # The rest of the logic in this conditional is to support the GMX_PYTHON_PACKAGE option
+ # for testing the gmxapi Python packages within a full GROMACS project build_command and.
+ # for building full GROMACS project documentation.
+ set(GMXAPI_PYTHON_STAGING_DIR ${CMAKE_CURRENT_BINARY_DIR}/gmxapi_staging)
# Instead, we should probably build a source package and alert the user of its location.
# We can use CMake to call the Python packaging tools to create an 'sdist'
# source distribution archive to be installed in the GROMACS installation
# destination. We can use the build directory as the working directory for
# easier clean-up, as well.
- # TODO: (ref issue #2896) Build and install 'sdist' with GROMACS.
+ # TODO: (ref Issue #2896) Build and install 'sdist' with GROMACS.
+ # The Python module is being built against GROMACS in its build tree, so we will not install.
+ set_target_properties(_gmxapi PROPERTIES BUILD_WITH_INSTALL_RPATH FALSE)
+ target_link_libraries(_gmxapi PRIVATE Gromacs::gmxapi)
# However, we can still produce an importable package for documentation builds and
# basic testing in ${CMAKE_CURRENT_BINARY_DIR}/gmxapi_staging
- if(CMAKE_VERSION VERSION_LESS 3.12)
- file(GLOB_RECURSE _py_sources
- ${CMAKE_CURRENT_SOURCE_DIR}/gmxapi/*.py)
- else()
- # CONFIGURE_DEPENDS appears in CMake 3.12 and can help to more robustly detect
- # the need to update anything depending on the staged package.
- file(GLOB_RECURSE _py_sources
- CONFIGURE_DEPENDS
- ${CMAKE_CURRENT_SOURCE_DIR}/gmxapi/*.py)
- endif()
+ set_target_properties(_gmxapi PROPERTIES
+ LIBRARY_OUTPUT_DIRECTORY ${GMXAPI_PYTHON_STAGING_DIR}/gmxapi)
+ file(GLOB_RECURSE _py_sources
+ CONFIGURE_DEPENDS
+ ${CMAKE_CURRENT_SOURCE_DIR}/gmxapi/*.py)
foreach(_package_file IN LISTS _py_sources)
get_filename_component(_absolute_dir ${_package_file} DIRECTORY)
file(RELATIVE_PATH _relative_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_absolute_dir})
- file(COPY ${_package_file} DESTINATION gmxapi_staging/${_relative_dir})
+ file(COPY ${_package_file} DESTINATION ${GMXAPI_PYTHON_STAGING_DIR}/${_relative_dir})
endforeach()
file(COPY setup.py CMakeLists.txt DESTINATION ${GMXAPI_PYTHON_STAGING_DIR})
- # Set CMake variable pybind11_DIR to ${CMAKE_CURRENT_SOURCE_DIR}/external/pybind/tools
- # if re-invoking CMake (including via Python setuptools) for the files in gmxapi_staging.
+ file(COPY ${CMAKE_CURRENT_BINARY_DIR}/gmxapi/gmxconfig.json DESTINATION ${GMXAPI_PYTHON_STAGING_DIR}/gmxapi)
# Unit test and build docs using PYTHONPATH=$CMAKE_CURRENT_BINARY_DIR/gmxapi_staging
set_target_properties(_gmxapi PROPERTIES staging_dir ${GMXAPI_PYTHON_STAGING_DIR})
# to the `check` target. Normal usage is to first install the Python package,
# then run `pytest` on the `tests` directory. Refer to gmxapi package documentation.
if(NOT GMXAPI_MASTER_PROJECT)
- add_subdirectory(test)
+ if (BUILD_TESTING)
+ add_subdirectory(test)
+ endif()
endif()