From 2eba89b91cedbbdb72fcc00fdc063d4f0813fa8a Mon Sep 17 00:00:00 2001 From: MintoDA1 <51412913+MintoDA1@users.noreply.github.com> Date: Thu, 7 Sep 2023 09:17:55 -0400 Subject: [PATCH] Major restructuring of build system. Switched to scikit-build-core for futureproofing, which means getting rid of setup.py and relying on pyproject.toml. Working on making the Linux build more efficient --- CMakeLists.txt | 25 +- buildscripts/build_hdf5.sh | 3 - buildscripts/build_netcdf-c.sh | 4 +- buildscripts/build_netcdf-fortran.sh | 5 +- buildscripts/build_zlib.sh | 4 +- cmake/Modules/FindCython.cmake | 3 +- cmake/Modules/FindNETCDF.cmake | 15 +- cmake/Modules/FindPythonExtensions.cmake | 597 ++++++++++++++++++ cmake/Modules/UseCython.cmake | 383 +++++++++++ cmake/Modules/UsePythonExtensions.cmake | 320 ++++++++++ ...targetLinkLibrariesWithDynamicLookup.cmake | 597 ++++++++++++++++++ pyproject.toml | 65 +- setup.py | 64 -- src/globals/globals_module.f90 | 2 +- version.txt | 2 +- 15 files changed, 1967 insertions(+), 122 deletions(-) create mode 100644 cmake/Modules/FindPythonExtensions.cmake create mode 100644 cmake/Modules/UseCython.cmake create mode 100644 cmake/Modules/UsePythonExtensions.cmake create mode 100644 cmake/Modules/targetLinkLibrariesWithDynamicLookup.cmake delete mode 100644 setup.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 01aa4d8eb..4a21a2d07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.6.0...3.27.1) # Get version stored in text file FILE(READ "version.txt" VERSION) -PROJECT(swiftest VERSION ${VERSION} LANGUAGES C Fortran) +PROJECT(${SKBUILD_PROJECT_NAME} LANGUAGES C Fortran VERSION ${VERSION}) IF (CMAKE_Fortran_COMPILER_ID MATCHES "^Intel") SET(COMPILER_OPTIONS "Intel" CACHE STRING "Compiler identified as Intel") @@ -37,28 +37,7 @@ IF(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) ENDIF() # Ensure scikit-build modules -FIND_PACKAGE(Python3 COMPONENTS Interpreter Development.Module REQUIRED) -IF (NOT SKBUILD) - EXECUTE_PROCESS( - COMMAND "${Python3_EXECUTABLE}" - -c "import os, skbuild; print(os.path.dirname(skbuild.__file__))" - OUTPUT_VARIABLE SKBLD_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - FILE(TO_CMAKE_PATH ${SKBLD_DIR} SKBLD_DIR) - LIST(APPEND CMAKE_MODULE_PATH "${SKBLD_DIR}/resources/cmake") - MESSAGE(STATUS "Looking in ${SKBLD_DIR}/resources/cmake for CMake modules") -ENDIF() - -# Detect when building against a conda environment set the _using_conda variable -# for use both in this file and in the parent -GET_FILENAME_COMPONENT(_python_bin_dir ${Python3_EXECUTABLE} DIRECTORY) -IF(EXISTS "${_python_bin_dir}/../conda-meta") - MESSAGE("-- Detected conda environment, setting INSTALL_RPATH_USE_LINK_PATH") - SET(_using_conda On) -ELSE() - SET(_using_conda Off) -ENDIF() +FIND_PACKAGE(Python COMPONENTS Interpreter Development.Module REQUIRED) # Add our local modules to the module path FILE(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules" LOCAL_MODULE_PATH) diff --git a/buildscripts/build_hdf5.sh b/buildscripts/build_hdf5.sh index ae05be413..4fa633582 100755 --- a/buildscripts/build_hdf5.sh +++ b/buildscripts/build_hdf5.sh @@ -48,6 +48,3 @@ if [ $? -ne 0 ]; then printf "hdf5 could not be compiled.\n" exit 1 fi - -make distclean - diff --git a/buildscripts/build_netcdf-c.sh b/buildscripts/build_netcdf-c.sh index 7df72d91b..d11f39126 100755 --- a/buildscripts/build_netcdf-c.sh +++ b/buildscripts/build_netcdf-c.sh @@ -44,6 +44,4 @@ fi if [ $? -ne 0 ]; then printf "netcdf-c could not be compiled."\n exit 1 -fi - -make distclean \ No newline at end of file +fi \ No newline at end of file diff --git a/buildscripts/build_netcdf-fortran.sh b/buildscripts/build_netcdf-fortran.sh index 103ebbfc7..064903224 100755 --- a/buildscripts/build_netcdf-fortran.sh +++ b/buildscripts/build_netcdf-fortran.sh @@ -41,7 +41,4 @@ fi if [ $? -ne 0 ]; then printf "netcdf-fortran could not be compiled.\n" exit 1 -fi - -make distclean - +fi \ No newline at end of file diff --git a/buildscripts/build_zlib.sh b/buildscripts/build_zlib.sh index a07e9ea75..99571d8f3 100755 --- a/buildscripts/build_zlib.sh +++ b/buildscripts/build_zlib.sh @@ -38,6 +38,4 @@ fi if [ $? -ne 0 ]; then printf "zlib could not be compiled.\n" exit 1 -fi - -make distclean \ No newline at end of file +fi \ No newline at end of file diff --git a/cmake/Modules/FindCython.cmake b/cmake/Modules/FindCython.cmake index c59f0307c..c8de13112 100644 --- a/cmake/Modules/FindCython.cmake +++ b/cmake/Modules/FindCython.cmake @@ -20,7 +20,6 @@ # #============================================================================= # Copyright 2011 Kitware, Inc. -# Modified 2023 by David A. Minton (daminton@purdue.edu) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -57,7 +56,7 @@ else() endif() if(CYTHON_EXECUTABLE) - set(CYTHON_version_command "${CYTHON_EXECUTABLE} --version") # Added quotes to prevent the string from having a ; inserted in the space + set(CYTHON_version_command ${CYTHON_EXECUTABLE} --version) execute_process(COMMAND ${CYTHON_version_command} OUTPUT_VARIABLE CYTHON_version_output diff --git a/cmake/Modules/FindNETCDF.cmake b/cmake/Modules/FindNETCDF.cmake index d6a3c4868..05f91e77c 100644 --- a/cmake/Modules/FindNETCDF.cmake +++ b/cmake/Modules/FindNETCDF.cmake @@ -9,17 +9,13 @@ # - Finds the NetCDF libraries -FILE(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}/_dependencies/${CMAKE_SYSTEM_NAME}_${CMAKE_SYSTEM_PROCESSOR}_${COMPILER_OPTIONS}" NXPREFIX_CANDIDATE) -IF (EXISTS ${NXPREFIX_CANDIDATE}) - SET(NCPREFIX_DIR ${NXPREFIX_CANDIDATE} CACHE PATH "Location of provided NetCDF-C dependencies") - SET(NFPREFIX_DIR ${NXPREFIX_CANDIDATE} CACHE PATH "Location of provided NetCDF-Fortran dependencies") - SET(H5PREFIX_DIR ${NXPREFIX_CANDIDATE} CACHE PATH "Location of provided HDF5 dependencies") - SET(ZPREFIX_DIR ${NXPREFIX_CANDIDATE} CACHE PATH "Location of provided zlib dependencies") - SET(NFINCLUDE_DIR "${NFPREFIX_DIR}/include" CACHE PATH "Location of provided netcdf.mod") -ELSEIF(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") +SET(NFPREFIX_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Location of provided NetCDF-Fortran dependencies") +SET(NFINCLUDE_DIR "${NFPREFIX_DIR}/include" CACHE PATH "Location of provided netcdf.mod") +IF(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") FIND_PATH(NFBIN NAMES nf-config HINTS + NFPREFIX_DIR ENV NETCDF_FORTRAN_HOME ENV PATH PATH_SUFFIXES @@ -75,6 +71,9 @@ MESSAGE(STATUS "NetCDF-Fortran include directory: ${NETCDF_INCLUDE_DIR}") IF (BUILD_SHARED_LIBS) SET(NETCDFF "netcdff") ELSE () + SET(NCPREFIX_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Location of provided NetCDF-C dependencies") + SET(H5PREFIX_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Location of provided HDF5 dependencies") + SET(ZPREFIX_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Location of provided zlib dependencies") IF (CMAKE_SYSTEM_NAME STREQUAL "Windows") SET(NETCDFF "netcdff.lib") SET(NETCDF "netcdf.lib") diff --git a/cmake/Modules/FindPythonExtensions.cmake b/cmake/Modules/FindPythonExtensions.cmake new file mode 100644 index 000000000..59b30c2a2 --- /dev/null +++ b/cmake/Modules/FindPythonExtensions.cmake @@ -0,0 +1,597 @@ +#.rst: +# +# This module defines CMake functions to build Python extension modules and +# stand-alone executables. +# +# The following variables are defined: +# :: +# +# PYTHON_PREFIX - absolute path to the current Python +# distribution's prefix +# PYTHON_SITE_PACKAGES_DIR - absolute path to the current Python +# distribution's site-packages directory +# PYTHON_RELATIVE_SITE_PACKAGES_DIR - path to the current Python +# distribution's site-packages directory +# relative to its prefix +# PYTHON_SEPARATOR - separator string for file path +# components. Equivalent to ``os.sep`` in +# Python. +# PYTHON_PATH_SEPARATOR - separator string for PATH-style +# environment variables. Equivalent to +# ``os.pathsep`` in Python. +# PYTHON_EXTENSION_MODULE_SUFFIX - suffix of the compiled module. For example, on +# Linux, based on environment, it could be ``.cpython-35m-x86_64-linux-gnu.so``. +# +# +# +# The following functions are defined: +# +# .. cmake:command:: python_extension_module +# +# For libraries meant to be used as Python extension modules, either dynamically +# loaded or directly linked. Amend the configuration of the library target +# (created using ``add_library``) with additional options needed to build and +# use the referenced library as a Python extension module. +# +# python_extension_module( +# [LINKED_MODULES_VAR ] +# [FORWARD_DECL_MODULES_VAR ] +# [MODULE_SUFFIX ]) +# +# Only extension modules that are configured to be built as MODULE libraries can +# be runtime-loaded through the standard Python import mechanism. All other +# modules can only be included in standalone applications that are written to +# expect their presence. In addition to being linked against the libraries for +# these modules, such applications must forward declare their entry points and +# initialize them prior to use. To generate these forward declarations and +# initializations, see ``python_modules_header``. +# +# If ```` does not refer to a target, then it is assumed to refer to an +# extension module that is not linked at all, but compiled along with other +# source files directly into an executable. Adding these modules does not cause +# any library configuration modifications, and they are not added to the list of +# linked modules. They still must be forward declared and initialized, however, +# and so are added to the forward declared modules list. +# +# If the associated target is of type ``MODULE_LIBRARY``, the LINK_FLAGS target +# property is used to set symbol visibility and export only the module init function. +# This applies to GNU and MSVC compilers. +# +# Options: +# +# ``LINKED_MODULES_VAR `` +# Name of the variable referencing a list of extension modules whose libraries +# must be linked into the executables of any stand-alone applications that use +# them. By default, the global property ``PY_LINKED_MODULES_LIST`` is used. +# +# ``FORWARD_DECL_MODULES_VAR `` +# Name of the variable referencing a list of extension modules whose entry +# points must be forward declared and called by any stand-alone applications +# that use them. By default, the global property +# ``PY_FORWARD_DECL_MODULES_LIST`` is used. +# +# ``MODULE_SUFFIX `` +# Suffix appended to the python extension module file. +# The default suffix is retrieved using ``sysconfig.get_config_var("SO")"``, +# if not available, the default is then ``.so`` on unix and ``.pyd`` on +# windows. +# Setting the variable ``PYTHON_EXTENSION_MODULE_SUFFIX`` in the caller +# scope defines the value used for all extensions not having a suffix +# explicitly specified using ``MODULE_SUFFIX`` parameter. +# +# +# .. cmake:command:: python_standalone_executable +# +# python_standalone_executable() +# +# For standalone executables that initialize their own Python runtime +# (such as when building source files that include one generated by Cython with +# the --embed option). Amend the configuration of the executable target +# (created using ``add_executable``) with additional options needed to properly +# build the referenced executable. +# +# +# .. cmake:command:: python_modules_header +# +# Generate a header file that contains the forward declarations and +# initialization routines for the given list of Python extension modules. +# ```` is the logical name for the header file (no file extensions). +# ```` is the actual destination filename for the header file +# (e.g.: decl_modules.h). +# +# python_modules_header( [HeaderFilename] +# [FORWARD_DECL_MODULES_LIST ] +# [HEADER_OUTPUT_VAR ] +# [INCLUDE_DIR_OUTPUT_VAR ]) +# +# without the extension is used as the logical name. If only ```` is +# +# If only ```` is provided, and it ends in the ".h" extension, then it +# is assumed to be the ````. The filename of the header file +# provided, and it does not end in the ".h" extension, then the +# ```` is assumed to ``.h``. +# +# The exact contents of the generated header file depend on the logical +# ````. It should be set to a value that corresponds to the target +# application, or for the case of multiple applications, some identifier that +# conveyes its purpose. It is featured in the generated multiple inclusion +# guard as well as the names of the generated initialization routines. +# +# The generated header file includes forward declarations for all listed +# modules, as well as implementations for the following class of routines: +# +# ``int _(void)`` +# Initializes the python extension module, ````. Returns an integer +# handle to the module. +# +# ``void _LoadAllPythonModules(void)`` +# Initializes all listed python extension modules. +# +# ``void CMakeLoadAllPythonModules(void);`` +# Alias for ``_LoadAllPythonModules`` whose name does not depend on +# ````. This function is excluded during preprocessing if the +# preprocessing macro ``EXCLUDE_LOAD_ALL_FUNCTION`` is defined. +# +# ``void Py_Initialize_Wrapper();`` +# Wrapper arpund ``Py_Initialize()`` that initializes all listed python +# extension modules. This function is excluded during preprocessing if the +# preprocessing macro ``EXCLUDE_PY_INIT_WRAPPER`` is defined. If this +# function is generated, then ``Py_Initialize()`` is redefined to a macro +# that calls this function. +# +# Options: +# +# ``FORWARD_DECL_MODULES_LIST `` +# List of extension modules for which to generate forward declarations of +# their entry points and their initializations. By default, the global +# property ``PY_FORWARD_DECL_MODULES_LIST`` is used. +# +# ``HEADER_OUTPUT_VAR `` +# Name of the variable to set to the path to the generated header file. By +# default, ```` is used. +# +# ``INCLUDE_DIR_OUTPUT_VAR `` +# Name of the variable to set to the path to the directory containing the +# generated header file. By default, ``_INCLUDE_DIRS`` is used. +# +# Defined variables: +# +# ```` +# The path to the generated header file +# +# ```` +# Directory containing the generated header file +# +# +# Example usage +# ^^^^^^^^^^^^^ +# +# .. code-block:: cmake +# +# find_package(PythonExtensions) +# find_package(Cython) +# find_package(Boost COMPONENTS python) +# +# # Simple Cython Module -- no executables +# add_cython_target(_module.pyx) +# add_library(_module MODULE ${_module}) +# python_extension_module(_module) +# +# # Mix of Cython-generated code and C++ code using Boost Python +# # Stand-alone executable -- no modules +# include_directories(${Boost_INCLUDE_DIRS}) +# add_cython_target(main.pyx CXX EMBED_MAIN) +# add_executable(main boost_python_module.cxx ${main}) +# target_link_libraries(main ${Boost_LIBRARIES}) +# python_standalone_executable(main) +# +# # stand-alone executable with three extension modules: +# # one statically linked, one dynamically linked, and one loaded at runtime +# # +# # Freely mixes Cython-generated code, code using Boost-Python, and +# # hand-written code using the CPython API. +# +# # module1 -- statically linked +# add_cython_target(module1.pyx) +# add_library(module1 STATIC ${module1}) +# python_extension_module(module1 +# LINKED_MODULES_VAR linked_module_list +# FORWARD_DECL_MODULES_VAR fdecl_module_list) +# +# # module2 -- dynamically linked +# include_directories(${Boost_INCLUDE_DIRS}) +# add_library(module2 SHARED boost_module2.cxx) +# target_link_libraries(module2 ${Boost_LIBRARIES}) +# python_extension_module(module2 +# LINKED_MODULES_VAR linked_module_list +# FORWARD_DECL_MODULES_VAR fdecl_module_list) +# +# # module3 -- loaded at runtime +# add_cython_target(module3a.pyx) +# add_library(module3 MODULE ${module3a} module3b.cxx) +# target_link_libraries(module3 ${Boost_LIBRARIES}) +# python_extension_module(module3 +# LINKED_MODULES_VAR linked_module_list +# FORWARD_DECL_MODULES_VAR fdecl_module_list) +# +# # application executable -- generated header file + other source files +# python_modules_header(modules +# FORWARD_DECL_MODULES_LIST ${fdecl_module_list}) +# include_directories(${modules_INCLUDE_DIRS}) +# +# add_cython_target(mainA) +# add_cython_target(mainC) +# add_executable(main ${mainA} mainB.cxx ${mainC} mainD.c) +# +# target_link_libraries(main ${linked_module_list} ${Boost_LIBRARIES}) +# python_standalone_executable(main) +# +#============================================================================= +# Copyright 2011 Kitware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +find_package(PythonInterp REQUIRED) +if(SKBUILD AND NOT PYTHON_LIBRARY) + set(PYTHON_LIBRARY "no-library-required") + find_package(PythonLibs) + unset(PYTHON_LIBRARY) + unset(PYTHON_LIBRARIES) +else() + find_package(PythonLibs) +endif() +include(targetLinkLibrariesWithDynamicLookup) + +set(_command " +import distutils.sysconfig +import itertools +import os +import os.path +import site +import sys + +result = None +rel_result = None +candidate_lists = [] + +try: + candidate_lists.append((distutils.sysconfig.get_python_lib(),)) +except AttributeError: pass + +try: + candidate_lists.append(site.getsitepackages()) +except AttributeError: pass + +try: + candidate_lists.append((site.getusersitepackages(),)) +except AttributeError: pass + +candidates = itertools.chain.from_iterable(candidate_lists) + +for candidate in candidates: + rel_candidate = os.path.relpath( + candidate, sys.prefix) + if not rel_candidate.startswith(\"..\"): + result = candidate + rel_result = rel_candidate + break + +sys.stdout.write(\";\".join(( + os.sep, + os.pathsep, + sys.prefix, + result, + rel_result, + distutils.sysconfig.get_config_var('EXT_SUFFIX') +))) +") + +execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c "${_command}" + OUTPUT_VARIABLE _list + RESULT_VARIABLE _result) + +list(GET _list 0 _item) +set(PYTHON_SEPARATOR "${_item}") +mark_as_advanced(PYTHON_SEPARATOR) + +list(GET _list 1 _item) +set(PYTHON_PATH_SEPARATOR "${_item}") +mark_as_advanced(PYTHON_PATH_SEPARATOR) + +list(GET _list 2 _item) +set(PYTHON_PREFIX "${_item}") +mark_as_advanced(PYTHON_PREFIX) + +list(GET _list 3 _item) +set(PYTHON_SITE_PACKAGES_DIR "${_item}") +mark_as_advanced(PYTHON_SITE_PACKAGES_DIR) + +list(GET _list 4 _item) +set(PYTHON_RELATIVE_SITE_PACKAGES_DIR "${_item}") +mark_as_advanced(PYTHON_RELATIVE_SITE_PACKAGES_DIR) + +if(NOT DEFINED PYTHON_EXTENSION_MODULE_SUFFIX) + list(GET _list 5 _item) + set(PYTHON_EXTENSION_MODULE_SUFFIX "${_item}") +endif() + +function(_set_python_extension_symbol_visibility _target) + if(PYTHON_VERSION_MAJOR VERSION_GREATER 2) + set(_modinit_prefix "PyInit_") + else() + set(_modinit_prefix "init") + endif() + message("_modinit_prefix:${_modinit_prefix}") + if("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + set_target_properties(${_target} PROPERTIES LINK_FLAGS + "/EXPORT:${_modinit_prefix}${_target}" + ) + elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # Option to not run version script. See https://github.com/scikit-build/scikit-build/issues/668 + if(NOT DEFINED SKBUILD_GNU_SKIP_LOCAL_SYMBOL_EXPORT_OVERRIDE) + set(SKBUILD_GNU_SKIP_LOCAL_SYMBOL_EXPORT_OVERRIDE FALSE) + endif() + set(_script_path + ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_target}-version-script.map + ) + # Export all symbols. See https://github.com/scikit-build/scikit-build/issues/668 + if(SKBUILD_GNU_SKIP_LOCAL_SYMBOL_EXPORT_OVERRIDE) + file(WRITE ${_script_path} + "{global: ${_modinit_prefix}${_target};};" + ) + else() + file(WRITE ${_script_path} + "{global: ${_modinit_prefix}${_target}; local: *;};" + ) + endif() + if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS") + set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS + " -Wl,--version-script=\"${_script_path}\"" + ) + else() + set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS + " -Wl,-M \"${_script_path}\"" + ) + endif() + endif() +endfunction() + +function(python_extension_module _target) + set(one_ops LINKED_MODULES_VAR FORWARD_DECL_MODULES_VAR MODULE_SUFFIX) + cmake_parse_arguments(_args "" "${one_ops}" "" ${ARGN}) + + set(_lib_type "NA") + if(TARGET ${_target}) + get_property(_lib_type TARGET ${_target} PROPERTY TYPE) + endif() + + set(_is_non_lib TRUE) + + set(_is_static_lib FALSE) + if(_lib_type STREQUAL "STATIC_LIBRARY") + set(_is_static_lib TRUE) + set(_is_non_lib FALSE) + endif() + + set(_is_shared_lib FALSE) + if(_lib_type STREQUAL "SHARED_LIBRARY") + set(_is_shared_lib TRUE) + set(_is_non_lib FALSE) + endif() + + set(_is_module_lib FALSE) + if(_lib_type STREQUAL "MODULE_LIBRARY") + set(_is_module_lib TRUE) + set(_is_non_lib FALSE) + endif() + + if(_is_static_lib OR _is_shared_lib OR _is_non_lib) + + if(_is_static_lib OR _is_shared_lib) + if(_args_LINKED_MODULES_VAR) + set(${_args_LINKED_MODULES_VAR} + ${${_args_LINKED_MODULES_VAR}} ${_target} PARENT_SCOPE) + else() + set_property(GLOBAL APPEND PROPERTY PY_LINKED_MODULES_LIST ${_target}) + endif() + endif() + + if(_args_FORWARD_DECL_MODULES_VAR) + set(${_args_FORWARD_DECL_MODULES_VAR} + ${${_args_FORWARD_DECL_MODULES_VAR}} ${_target} PARENT_SCOPE) + else() + set_property(GLOBAL APPEND PROPERTY + PY_FORWARD_DECL_MODULES_LIST ${_target}) + endif() + endif() + + if(NOT _is_non_lib) + include_directories("${PYTHON_INCLUDE_DIRS}") + endif() + + if(_is_module_lib) + set_target_properties(${_target} PROPERTIES + PREFIX "${PYTHON_MODULE_PREFIX}") + endif() + + if(_is_module_lib OR _is_shared_lib) + if(_is_module_lib) + + if(NOT _args_MODULE_SUFFIX) + set(_args_MODULE_SUFFIX "${PYTHON_EXTENSION_MODULE_SUFFIX}") + endif() + + if(_args_MODULE_SUFFIX STREQUAL "" AND WIN32 AND NOT CYGWIN) + set(_args_MODULE_SUFFIX ".pyd") + endif() + + if(NOT _args_MODULE_SUFFIX STREQUAL "") + set_target_properties(${_target} + PROPERTIES SUFFIX ${_args_MODULE_SUFFIX}) + endif() + endif() + + target_link_libraries_with_dynamic_lookup(${_target} ${PYTHON_LIBRARIES}) + + if(_is_module_lib) + _set_python_extension_symbol_visibility(${_target}) + endif() + endif() +endfunction() + +function(python_standalone_executable _target) + include_directories(${PYTHON_INCLUDE_DIRS}) + target_link_libraries(${_target} ${SKBUILD_LINK_LIBRARIES_KEYWORD} ${PYTHON_LIBRARIES}) +endfunction() + +function(python_modules_header _name) + set(one_ops FORWARD_DECL_MODULES_LIST + HEADER_OUTPUT_VAR + INCLUDE_DIR_OUTPUT_VAR) + cmake_parse_arguments(_args "" "${one_ops}" "" ${ARGN}) + + list(GET _args_UNPARSED_ARGUMENTS 0 _arg0) + # if present, use arg0 as the input file path + if(_arg0) + set(_source_file ${_arg0}) + + # otherwise, must determine source file from name, or vice versa + else() + get_filename_component(_name_ext "${_name}" EXT) + + # if extension provided, _name is the source file + if(_name_ext) + set(_source_file ${_name}) + get_filename_component(_name "${_source_file}" NAME_WE) + + # otherwise, assume the source file is ${_name}.h + else() + set(_source_file ${_name}.h) + endif() + endif() + + if(_args_FORWARD_DECL_MODULES_LIST) + set(static_mod_list ${_args_FORWARD_DECL_MODULES_LIST}) + else() + get_property(static_mod_list GLOBAL PROPERTY PY_FORWARD_DECL_MODULES_LIST) + endif() + + string(REPLACE "." "_" _header_name "${_name}") + string(TOUPPER ${_header_name} _header_name_upper) + set(_header_name_upper "_${_header_name_upper}_H") + set(generated_file ${CMAKE_CURRENT_BINARY_DIR}/${_source_file}) + + set(generated_file_tmp "${generated_file}.in") + file(WRITE ${generated_file_tmp} + "/* Created by CMake. DO NOT EDIT; changes will be lost. */\n") + + set(_chunk "") + set(_chunk "${_chunk}#ifndef ${_header_name_upper}\n") + set(_chunk "${_chunk}#define ${_header_name_upper}\n") + set(_chunk "${_chunk}\n") + set(_chunk "${_chunk}#include \n") + set(_chunk "${_chunk}\n") + set(_chunk "${_chunk}#ifdef __cplusplus\n") + set(_chunk "${_chunk}extern \"C\" {\n") + set(_chunk "${_chunk}#endif /* __cplusplus */\n") + set(_chunk "${_chunk}\n") + set(_chunk "${_chunk}#if PY_MAJOR_VERSION < 3\n") + file(APPEND ${generated_file_tmp} "${_chunk}") + + foreach(_module ${static_mod_list}) + file(APPEND ${generated_file_tmp} + "PyMODINIT_FUNC init${PYTHON_MODULE_PREFIX}${_module}(void);\n") + endforeach() + + file(APPEND ${generated_file_tmp} "#else /* PY_MAJOR_VERSION >= 3*/\n") + + foreach(_module ${static_mod_list}) + file(APPEND ${generated_file_tmp} + "PyMODINIT_FUNC PyInit_${PYTHON_MODULE_PREFIX}${_module}(void);\n") + endforeach() + + set(_chunk "") + set(_chunk "${_chunk}#endif /* PY_MAJOR_VERSION >= 3*/\n\n") + set(_chunk "${_chunk}#ifdef __cplusplus\n") + set(_chunk "${_chunk}}\n") + set(_chunk "${_chunk}#endif /* __cplusplus */\n") + set(_chunk "${_chunk}\n") + file(APPEND ${generated_file_tmp} "${_chunk}") + + foreach(_module ${static_mod_list}) + set(_import_function "${_header_name}_${_module}") + set(_prefixed_module "${PYTHON_MODULE_PREFIX}${_module}") + + set(_chunk "") + set(_chunk "${_chunk}int ${_import_function}(void)\n") + set(_chunk "${_chunk}{\n") + set(_chunk "${_chunk} static char name[] = \"${_prefixed_module}\";\n") + set(_chunk "${_chunk} #if PY_MAJOR_VERSION < 3\n") + set(_chunk "${_chunk} return PyImport_AppendInittab(") + set(_chunk "${_chunk}name, init${_prefixed_module});\n") + set(_chunk "${_chunk} #else /* PY_MAJOR_VERSION >= 3 */\n") + set(_chunk "${_chunk} return PyImport_AppendInittab(") + set(_chunk "${_chunk}name, PyInit_${_prefixed_module});\n") + set(_chunk "${_chunk} #endif /* PY_MAJOR_VERSION >= 3 */\n") + set(_chunk "${_chunk}}\n\n") + file(APPEND ${generated_file_tmp} "${_chunk}") + endforeach() + + file(APPEND ${generated_file_tmp} + "void ${_header_name}_LoadAllPythonModules(void)\n{\n") + foreach(_module ${static_mod_list}) + file(APPEND ${generated_file_tmp} " ${_header_name}_${_module}();\n") + endforeach() + file(APPEND ${generated_file_tmp} "}\n\n") + + set(_chunk "") + set(_chunk "${_chunk}#ifndef EXCLUDE_LOAD_ALL_FUNCTION\n") + set(_chunk "${_chunk}void CMakeLoadAllPythonModules(void)\n") + set(_chunk "${_chunk}{\n") + set(_chunk "${_chunk} ${_header_name}_LoadAllPythonModules();\n") + set(_chunk "${_chunk}}\n") + set(_chunk "${_chunk}#endif /* !EXCLUDE_LOAD_ALL_FUNCTION */\n\n") + + set(_chunk "${_chunk}#ifndef EXCLUDE_PY_INIT_WRAPPER\n") + set(_chunk "${_chunk}static void Py_Initialize_Wrapper()\n") + set(_chunk "${_chunk}{\n") + set(_chunk "${_chunk} ${_header_name}_LoadAllPythonModules();\n") + set(_chunk "${_chunk} Py_Initialize();\n") + set(_chunk "${_chunk}}\n") + set(_chunk "${_chunk}#define Py_Initialize Py_Initialize_Wrapper\n") + set(_chunk "${_chunk}#endif /* !EXCLUDE_PY_INIT_WRAPPER */\n\n") + + set(_chunk "${_chunk}#endif /* !${_header_name_upper} */\n") + file(APPEND ${generated_file_tmp} "${_chunk}") + + # with configure_file() cmake complains that you may not use a file created + # using file(WRITE) as input file for configure_file() + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${generated_file_tmp}" "${generated_file}" + OUTPUT_QUIET ERROR_QUIET) + + set(_header_output_var ${_name}) + if(_args_HEADER_OUTPUT_VAR) + set(_header_output_var ${_args_HEADER_OUTPUT_VAR}) + endif() + set(${_header_output_var} ${generated_file} PARENT_SCOPE) + + set(_include_dir_var ${_name}_INCLUDE_DIRS) + if(_args_INCLUDE_DIR_OUTPUT_VAR) + set(_include_dir_var ${_args_INCLUDE_DIR_OUTPUT_VAR}) + endif() + set(${_include_dirs_var} ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE) +endfunction() + +include(UsePythonExtensions) diff --git a/cmake/Modules/UseCython.cmake b/cmake/Modules/UseCython.cmake new file mode 100644 index 000000000..4e0fa7907 --- /dev/null +++ b/cmake/Modules/UseCython.cmake @@ -0,0 +1,383 @@ +#.rst: +# +# The following functions are defined: +# +# .. cmake:command:: add_cython_target +# +# Create a custom rule to generate the source code for a Python extension module +# using cython. +# +# add_cython_target( [] +# [EMBED_MAIN] +# [C | CXX] +# [PY2 | PY3] +# [OUTPUT_VAR ]) +# +# ```` is the name of the new target, and ```` +# is the path to a cython source file. Note that, despite the name, no new +# targets are created by this function. Instead, see ``OUTPUT_VAR`` for +# retrieving the path to the generated source for subsequent targets. +# +# If only ```` is provided, and it ends in the ".pyx" extension, then it +# is assumed to be the ````. The name of the input without the +# extension is used as the target name. If only ```` is provided, and it +# does not end in the ".pyx" extension, then the ```` is assumed to +# be ``.pyx``. +# +# The Cython include search path is amended with any entries found in the +# ``INCLUDE_DIRECTORIES`` property of the directory containing the +# ```` file. Use ``include_directories`` to add to the Cython +# include search path. +# +# Options: +# +# ``EMBED_MAIN`` +# Embed a main() function in the generated output (for stand-alone +# applications that initialize their own Python runtime). +# +# ``C | CXX`` +# Force the generation of either a C or C++ file. By default, a C file is +# generated, unless the C language is not enabled for the project; in this +# case, a C++ file is generated by default. +# +# ``PY2 | PY3`` +# Force compilation using either Python-2 or Python-3 syntax and code +# semantics. By default, Python-2 syntax and semantics are used if the major +# version of Python found is 2. Otherwise, Python-3 syntax and semantics are +# used. +# +# ``OUTPUT_VAR `` +# Set the variable ```` in the parent scope to the path to the +# generated source file. By default, ```` is used as the output +# variable name. +# +# Defined variables: +# +# ```` +# The path of the generated source file. +# +# Cache variables that affect the behavior include: +# +# ``CYTHON_ANNOTATE`` +# Whether to create an annotated .html file when compiling. +# +# ``CYTHON_FLAGS`` +# Additional flags to pass to the Cython compiler. +# +# Example usage +# ^^^^^^^^^^^^^ +# +# .. code-block:: cmake +# +# find_package(Cython) +# +# # Note: In this case, either one of these arguments may be omitted; their +# # value would have been inferred from that of the other. +# add_cython_target(cy_code cy_code.pyx) +# +# add_library(cy_code MODULE ${cy_code}) +# target_link_libraries(cy_code ...) +# +#============================================================================= +# Copyright 2011 Kitware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +# Configuration options. +set(CYTHON_ANNOTATE OFF + CACHE BOOL "Create an annotated .html file when compiling *.pyx.") + +set(CYTHON_FLAGS "" CACHE STRING + "Extra flags to the cython compiler.") +mark_as_advanced(CYTHON_ANNOTATE CYTHON_FLAGS) + +set(CYTHON_CXX_EXTENSION "cxx") +set(CYTHON_C_EXTENSION "c") + +get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) + +function(add_cython_target _name) + set(options EMBED_MAIN C CXX PY2 PY3) + set(options1 OUTPUT_VAR) + cmake_parse_arguments(_args "${options}" "${options1}" "" ${ARGN}) + + list(GET _args_UNPARSED_ARGUMENTS 0 _arg0) + + # if provided, use _arg0 as the input file path + if(_arg0) + set(_source_file ${_arg0}) + + # otherwise, must determine source file from name, or vice versa + else() + get_filename_component(_name_ext "${_name}" EXT) + + # if extension provided, _name is the source file + if(_name_ext) + set(_source_file ${_name}) + get_filename_component(_name "${_source_file}" NAME_WE) + + # otherwise, assume the source file is ${_name}.pyx + else() + set(_source_file ${_name}.pyx) + endif() + endif() + + set(_embed_main FALSE) + + if("C" IN_LIST languages) + set(_output_syntax "C") + elseif("CXX" IN_LIST languages) + set(_output_syntax "CXX") + else() + message(FATAL_ERROR "Either C or CXX must be enabled to use Cython") + endif() + + if(_args_EMBED_MAIN) + set(_embed_main TRUE) + endif() + + if(_args_C) + set(_output_syntax "C") + endif() + + if(_args_CXX) + set(_output_syntax "CXX") + endif() + + # Doesn't select an input syntax - Cython + # defaults to 2 for Cython 2 and 3 for Cython 3 + set(_input_syntax "default") + + if(_args_PY2) + set(_input_syntax "PY2") + endif() + + if(_args_PY3) + set(_input_syntax "PY3") + endif() + + set(embed_arg "") + if(_embed_main) + set(embed_arg "--embed") + endif() + + set(cxx_arg "") + set(extension "c") + if(_output_syntax STREQUAL "CXX") + set(cxx_arg "--cplus") + set(extension "cxx") + endif() + + set(py_version_arg "") + if(_input_syntax STREQUAL "PY2") + set(py_version_arg "-2") + elseif(_input_syntax STREQUAL "PY3") + set(py_version_arg "-3") + endif() + + set(generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}") + set_source_files_properties(${generated_file} PROPERTIES GENERATED TRUE) + + set(_output_var ${_name}) + if(_args_OUTPUT_VAR) + set(_output_var ${_args_OUTPUT_VAR}) + endif() + set(${_output_var} ${generated_file} PARENT_SCOPE) + + file(RELATIVE_PATH generated_file_relative + ${CMAKE_BINARY_DIR} ${generated_file}) + + set(comment "Generating ${_output_syntax} source ${generated_file_relative}") + set(cython_include_directories "") + set(pxd_dependencies "") + set(c_header_dependencies "") + + # Get the include directories. + get_directory_property(cmake_include_directories + DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + INCLUDE_DIRECTORIES) + list(APPEND cython_include_directories ${cmake_include_directories}) + + # Determine dependencies. + # Add the pxd file with the same basename as the given pyx file. + get_source_file_property(pyx_location ${_source_file} LOCATION) + get_filename_component(pyx_path ${pyx_location} PATH) + get_filename_component(pyx_file_basename ${_source_file} NAME_WE) + unset(corresponding_pxd_file CACHE) + find_file(corresponding_pxd_file ${pyx_file_basename}.pxd + PATHS "${pyx_path}" ${cmake_include_directories} + NO_DEFAULT_PATH) + if(corresponding_pxd_file) + list(APPEND pxd_dependencies "${corresponding_pxd_file}") + endif() + + # pxd files to check for additional dependencies + set(pxds_to_check "${_source_file}" "${pxd_dependencies}") + set(pxds_checked "") + set(number_pxds_to_check 1) + while(number_pxds_to_check GREATER 0) + foreach(pxd ${pxds_to_check}) + list(APPEND pxds_checked "${pxd}") + list(REMOVE_ITEM pxds_to_check "${pxd}") + + # look for C headers + file(STRINGS "${pxd}" extern_from_statements + REGEX "cdef[ ]+extern[ ]+from.*$") + foreach(statement ${extern_from_statements}) + # Had trouble getting the quote in the regex + string(REGEX REPLACE + "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" + header "${statement}") + unset(header_location CACHE) + find_file(header_location ${header} PATHS ${cmake_include_directories}) + if(header_location) + list(FIND c_header_dependencies "${header_location}" header_idx) + if(${header_idx} LESS 0) + list(APPEND c_header_dependencies "${header_location}") + endif() + endif() + endforeach() + + # check for pxd dependencies + # Look for cimport statements. + set(module_dependencies "") + file(STRINGS "${pxd}" cimport_statements REGEX cimport) + foreach(statement ${cimport_statements}) + if(${statement} MATCHES from) + string(REGEX REPLACE + "from[ ]+([^ ]+).*" "\\1" + module "${statement}") + else() + string(REGEX REPLACE + "cimport[ ]+([^ ]+).*" "\\1" + module "${statement}") + endif() + list(APPEND module_dependencies ${module}) + endforeach() + + # check for pxi dependencies + # Look for include statements. + set(include_dependencies "") + file(STRINGS "${pxd}" include_statements REGEX include) + foreach(statement ${include_statements}) + string(REGEX REPLACE + "include[ ]+[\"]([^\"]+)[\"].*" "\\1" + module "${statement}") + list(APPEND include_dependencies ${module}) + endforeach() + + list(REMOVE_DUPLICATES module_dependencies) + list(REMOVE_DUPLICATES include_dependencies) + + # Add modules to the files to check, if appropriate. + foreach(module ${module_dependencies}) + unset(pxd_location CACHE) + find_file(pxd_location ${module}.pxd + PATHS "${pyx_path}" ${cmake_include_directories} + NO_DEFAULT_PATH) + if(pxd_location) + list(FIND pxds_checked ${pxd_location} pxd_idx) + if(${pxd_idx} LESS 0) + list(FIND pxds_to_check ${pxd_location} pxd_idx) + if(${pxd_idx} LESS 0) + list(APPEND pxds_to_check ${pxd_location}) + list(APPEND pxd_dependencies ${pxd_location}) + endif() # if it is not already going to be checked + endif() # if it has not already been checked + endif() # if pxd file can be found + endforeach() # for each module dependency discovered + + # Add includes to the files to check, if appropriate. + foreach(_include ${include_dependencies}) + unset(pxi_location CACHE) + find_file(pxi_location ${_include} + PATHS "${pyx_path}" ${cmake_include_directories} + NO_DEFAULT_PATH) + if(pxi_location) + list(FIND pxds_checked ${pxi_location} pxd_idx) + if(${pxd_idx} LESS 0) + list(FIND pxds_to_check ${pxi_location} pxd_idx) + if(${pxd_idx} LESS 0) + list(APPEND pxds_to_check ${pxi_location}) + list(APPEND pxd_dependencies ${pxi_location}) + endif() # if it is not already going to be checked + endif() # if it has not already been checked + endif() # if include file can be found + endforeach() # for each include dependency discovered + endforeach() # for each include file to check + + list(LENGTH pxds_to_check number_pxds_to_check) + endwhile() + + # Set additional flags. + set(annotate_arg "") + if(CYTHON_ANNOTATE) + set(annotate_arg "--annotate") + endif() + + set(cython_debug_arg "") + set(line_directives_arg "") + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR + CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + set(cython_debug_arg "--gdb") + set(line_directives_arg "--line-directives") + endif() + + # Include directory arguments. + list(REMOVE_DUPLICATES cython_include_directories) + set(include_directory_arg "") + foreach(_include_dir ${cython_include_directories}) + set(include_directory_arg + ${include_directory_arg} "--include-dir" "${_include_dir}") + endforeach() + + list(REMOVE_DUPLICATES pxd_dependencies) + list(REMOVE_DUPLICATES c_header_dependencies) + + string(REGEX REPLACE " " ";" CYTHON_FLAGS_LIST "${CYTHON_FLAGS}") + + # Add the command to run the compiler. + add_custom_command(OUTPUT ${generated_file} + COMMAND ${CYTHON_EXECUTABLE} + ARGS ${cxx_arg} ${include_directory_arg} ${py_version_arg} + ${embed_arg} ${annotate_arg} ${cython_debug_arg} + ${line_directives_arg} ${CYTHON_FLAGS_LIST} ${pyx_location} + --output-file ${generated_file} + DEPENDS ${_source_file} + ${pxd_dependencies} + IMPLICIT_DEPENDS ${_output_syntax} + ${c_header_dependencies} + COMMENT ${comment}) + + # NOTE(opadron): I thought about making a proper target, but after trying it + # out, I decided that it would be far too convenient to use the same name as + # the target for the extension module (e.g.: for single-file modules): + # + # ... + # add_cython_target(_module.pyx) + # add_library(_module ${_module}) + # ... + # + # The above example would not be possible since the "_module" target name + # would already be taken by the cython target. Since I can't think of a + # reason why someone would need the custom target instead of just using the + # generated file directly, I decided to leave this commented out. + # + # add_custom_target(${_name} DEPENDS ${generated_file}) + + # Remove their visibility to the user. + set(corresponding_pxd_file "" CACHE INTERNAL "") + set(header_location "" CACHE INTERNAL "") + set(pxd_location "" CACHE INTERNAL "") +endfunction() diff --git a/cmake/Modules/UsePythonExtensions.cmake b/cmake/Modules/UsePythonExtensions.cmake new file mode 100644 index 000000000..c411e20c4 --- /dev/null +++ b/cmake/Modules/UsePythonExtensions.cmake @@ -0,0 +1,320 @@ +#.rst: +# +# The following functions are defined: +# +# .. cmake:command:: add_python_library +# +# Add a library that contains a mix of C, C++, Fortran, Cython, F2PY, Template, +# and Tempita sources. The required targets are automatically generated to +# "lower" source files from their high-level representation to a file that the +# compiler can accept. +# +# +# add_python_library( +# SOURCES [source1 [source2 ...]] +# [INCLUDE_DIRECTORIES [dir1 [dir2 ...]] +# [LINK_LIBRARIES [lib1 [lib2 ...]] +# [DEPENDS [source1 [source2 ...]]]) +# +# +# Example usage +# ^^^^^^^^^^^^^ +# +# .. code-block:: cmake +# +# find_package(PythonExtensions) +# +# file(GLOB arpack_sources ARPACK/SRC/*.f ARPACK/UTIL/*.f) +# +# add_python_library(arpack_scipy +# SOURCES ${arpack_sources} +# ${g77_wrapper_sources} +# INCLUDE_DIRECTORIES ARPACK/SRC +# ) +# +# .. cmake:command:: add_python_extension +# +# Add a extension that contains a mix of C, C++, Fortran, Cython, F2PY, Template, +# and Tempita sources. The required targets are automatically generated to +# "lower" source files from their high-level representation to a file that the +# compiler can accept. +# +# +# add_python_extension( +# SOURCES [source1 [source2 ...]] +# [INCLUDE_DIRECTORIES [dir1 [dir2 ...]] +# [LINK_LIBRARIES [lib1 [lib2 ...]] +# [DEPENDS [source1 [source2 ...]]]) +# +# +# Example usage +# ^^^^^^^^^^^^^ +# +# .. code-block:: cmake +# +# find_package(PythonExtensions) +# +# file(GLOB arpack_sources ARPACK/SRC/*.f ARPACK/UTIL/*.f) +# +# add_python_extension(arpack_scipy +# SOURCES ${arpack_sources} +# ${g77_wrapper_sources} +# INCLUDE_DIRECTORIES ARPACK/SRC +# ) +# +# +#============================================================================= +# Copyright 2011 Kitware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +macro(_remove_whitespace _output) + string(REGEX REPLACE "[ \r\n\t]+" " " ${_output} "${${_output}}") + string(STRIP "${${_output}}" ${_output}) +endmacro() + +function(add_python_library _name) + set(options STATIC SHARED MODULE) + set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS DEPENDS) + cmake_parse_arguments(_args "${options}" "" "${multiValueArgs}" ${ARGN} ) + + # Validate arguments to allow simpler debugging + if(NOT _args_SOURCES) + message( + FATAL_ERROR + "You have called add_python_library for library ${_name} without " + "any source files. This typically indicates a problem with " + "your CMakeLists.txt file" + ) + endif() + + # Initialize the list of sources + set(_sources ${_args_SOURCES}) + + # Generate targets for all *.src files + set(_processed ) + foreach(_source IN LISTS _sources) + if(${_source} MATCHES ".pyf.src$" OR ${_source} MATCHES "\\.f\\.src$") + if(NOT NumPy_FOUND) + message( + FATAL_ERROR + "NumPy is required to process *.src Template files" + ) + endif() + string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source}) + add_custom_command( + OUTPUT ${_source_we} + COMMAND ${NumPy_FROM_TEMPLATE_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/${_source} + ${CMAKE_CURRENT_BINARY_DIR}/${_source_we} + DEPENDS ${_source} ${_args_DEPENDS} + COMMENT "Generating ${_source_we} from template ${_source}" + ) + list(APPEND _processed ${_source_we}) + elseif(${_source} MATCHES "\\.c\\.src$") + if(NOT NumPy_FOUND) + message( + FATAL_ERROR + "NumPy is required to process *.src Template files" + ) + endif() + string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source}) + add_custom_command( + OUTPUT ${_source_we} + COMMAND ${NumPy_CONV_TEMPLATE_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/${_source} + ${CMAKE_CURRENT_BINARY_DIR}/${_source_we} + DEPENDS ${_source} ${_args_DEPENDS} + COMMENT "Generating ${_source_we} from template ${_source}" + ) + list(APPEND _processed ${_source_we}) + elseif(${_source} MATCHES "\\.pyx\\.in$") + if(NOT Cython_FOUND) + message( + FATAL_ERROR + "Cython is required to process *.in Tempita files" + ) + endif() + string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source}) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/${_source} + ${CMAKE_CURRENT_BINARY_DIR}/${_source} + COPYONLY + ) + set(_tempita_command + " + import os; + import sys; + from Cython.Tempita import Template; + cwd = os.getcwd(); + open(os.path.join(cwd, '${_source_we}'), 'w+') + .write( + Template.from_filename(os.path.join(cwd, '${_source}'), + encoding=sys.getdefaultencoding()).substitute() + ) + " + ) + _remove_whitespace(_tempita_command) + add_custom_command( + OUTPUT ${_source_we} + COMMAND ${PYTHON_EXECUTABLE} -c "${_tempita_command}" + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${_source}" + ${_args_DEPENDS} + ) + list(APPEND _processed ${_source_we}) + else() + list(APPEND _processed ${_source}) + endif() + endforeach() + set(_sources ${_processed}) + + # If we're building a Python extension and we're given only Fortran sources, + # We can conclude that we need to generate a Fortran interface file + list(FILTER _processed EXCLUDE REGEX "(\\.f|\\.f90)$") + if(NOT _processed AND _args_MODULE) + if(NOT NumPy_FOUND) + message( + FATAL_ERROR + "NumPy is required to process *.pyf F2PY files" + ) + endif() + set(_sources_abs ) + foreach(_source IN LISTS _sources) + if(NOT IS_ABSOLUTE ${_source}) + set(_source ${CMAKE_CURRENT_SOURCE_DIR}/${_source}) + endif() + list(APPEND _sources_abs ${_source}) + endforeach() + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.pyf + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${F2PY_EXECUTABLE} + ARGS -h ${_name}.pyf -m ${_name} --overwrite-signature + ${_sources_abs} + DEPENDS ${_sources} ${_args_DEPENDS} + COMMENT "Generating ${_name} Fortran interface file" + ) + list(APPEND _sources ${_name}.pyf) + endif() + + # Are there F2PY targets? + set(_has_f2py_targets OFF) + set(_has_cython_targets OFF) + + # Generate targets for all *.pyx and *.pyf files + set(_processed ) + foreach(_source IN LISTS _sources) + if(${_source} MATCHES \\.pyx$) + if(NOT Cython_FOUND) + message( + FATAL_ERROR + "Cython is required to process *.pyx Cython files" + ) + endif() + string(REGEX REPLACE "\\.[^.]*$" "" _pyx_target_name ${_source}) + set(_has_cython_targets ON) + add_cython_target(${_pyx_target_name} + ${_source} + OUTPUT_VAR _pyx_target_output + DEPENDS ${_args_DEPENDS} + ) + list(APPEND _processed ${_pyx_target_output}) + elseif(${_source} MATCHES \\.pyf$) + if(NOT NumPy_FOUND) + message( + FATAL_ERROR + "NumPy is required to process *.pyf F2PY files" + ) + endif() + string(REGEX REPLACE "\\.[^.]*$" "" _pyf_target_name ${_source}) + set(_has_f2py_targets ON) + add_f2py_target(${_pyf_target_name} + ${_source} + OUTPUT_VAR _pyf_target_output + DEPENDS ${_args_DEPENDS} + ) + list(APPEND _processed ${_pyf_target_output}) + else() + list(APPEND _processed ${_source}) + endif() + endforeach() + set(_sources ${_processed}) + + if(_args_SHARED) + add_library(${_name} SHARED ${_sources}) + elseif(_args_MODULE) + add_library(${_name} MODULE ${_sources}) + else() + # Assume static + add_library(${_name} STATIC ${_sources}) + endif() + + target_include_directories(${_name} PRIVATE ${_args_INCLUDE_DIRECTORIES}) + target_link_libraries(${_name} ${SKBUILD_LINK_LIBRARIES_KEYWORD} ${_args_LINK_LIBRARIES}) + + if(_has_f2py_targets) + target_include_directories(${_name} PRIVATE ${F2PY_INCLUDE_DIRS}) + target_link_libraries(${_name} ${SKBUILD_LINK_LIBRARIES_KEYWORD} ${F2PY_LIBRARIES}) + endif() + + if(_args_COMPILE_DEFINITIONS) + target_compile_definitions(${_name} PRIVATE ${_args_COMPILE_DEFINITIONS}) + endif() + + if(_args_DEPENDS) + add_custom_target( + "${_name}_depends" + DEPENDS ${_args_DEPENDS} + ) + add_dependencies(${_name} "${_name}_depends") + endif() +endfunction() + +function(add_python_extension _name) + # FIXME: make sure that extensions with the same name can happen + # in multiple directories + + set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS DEPENDS) + cmake_parse_arguments(_args "" "" "${multiValueArgs}" ${ARGN} ) + + # Validate arguments to allow simpler debugging + if(NOT _args_SOURCES) + message( + FATAL_ERROR + "You have called add_python_extension for library ${_name} without " + "any source files. This typically indicates a problem with " + "your CMakeLists.txt file" + ) + endif() + + add_python_library(${_name} MODULE + SOURCES ${_args_SOURCES} + INCLUDE_DIRECTORIES ${_args_INCLUDE_DIRECTORIES} + LINK_LIBRARIES ${_args_LINK_LIBRARIES} + COMPILE_DEFINITIONS ${_args_COMPILE_DEFINITIONS} + DEPENDS ${_args_DEPENDS} + ) + python_extension_module(${_name}) + + file(RELATIVE_PATH _relative "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") + if(_relative STREQUAL "") + set(_relative ".") + endif() + + install( + TARGETS ${_name} + LIBRARY DESTINATION "${_relative}" + RUNTIME DESTINATION "${_relative}" + ) +endfunction() diff --git a/cmake/Modules/targetLinkLibrariesWithDynamicLookup.cmake b/cmake/Modules/targetLinkLibrariesWithDynamicLookup.cmake new file mode 100644 index 000000000..a583f42cd --- /dev/null +++ b/cmake/Modules/targetLinkLibrariesWithDynamicLookup.cmake @@ -0,0 +1,597 @@ +#.rst: +# +# Public Functions +# ^^^^^^^^^^^^^^^^ +# +# The following functions are defined: +# +# .. cmake:command:: target_link_libraries_with_dynamic_lookup +# +# :: +# +# target_link_libraries_with_dynamic_lookup( []) +# +# +# Useful to "weakly" link a loadable module. For example, it should be used +# when compiling a loadable module when the symbols should be resolve from +# the run-time environment where the module is loaded, and not a specific +# system library. +# +# Like proper linking, except that the given ```` are not necessarily +# linked. Instead, the ```` is produced in a manner that allows for +# symbols unresolved within it to be resolved at runtime, presumably by the +# given ````. If such a target can be produced, the provided +# ```` are not actually linked. +# +# It links a library to a target such that the symbols are resolved at +# run-time not link-time. +# +# The linker is checked to see if it supports undefined +# symbols when linking a shared library. If it does then the library +# is not linked when specified with this function. +# +# On platforms that do not support weak-linking, this function works just +# like ``target_link_libraries``. +# +# .. note:: +# +# For OSX it uses ``undefined dynamic_lookup``. This is similar to using +# ``-shared`` on Linux where undefined symbols are ignored. +# +# For more details, see `blog `_ +# from Tim D. Smith. +# +# +# .. cmake:command:: check_dynamic_lookup +# +# Check if the linker requires a command line flag to allow leaving symbols +# unresolved when producing a target of type ```` that is +# weakly-linked against a dependency of type ````. +# +# ```` +# can be one of "STATIC", "SHARED", "MODULE", or "EXE". +# +# ```` +# can be one of "STATIC", "SHARED", or "MODULE". +# +# Long signature: +# +# :: +# +# check_dynamic_lookup( +# +# +# []) +# +# +# Short signature: +# +# :: +# +# check_dynamic_lookup() # set to "MODULE" +# # set to "SHARED" +# +# +# The result is cached between invocations and recomputed only when the value +# of CMake's linker flag list changes; ``CMAKE_STATIC_LINKER_FLAGS`` if +# ```` is "STATIC", and ``CMAKE_SHARED_LINKER_FLAGS`` otherwise. +# +# +# Defined variables: +# +# ```` +# Whether the current C toolchain supports weak-linking for target binaries of +# type ```` that are weakly-linked against a dependency target of +# type ````. +# +# ```` +# List of flags to add to the linker command to produce a working target +# binary of type ```` that is weakly-linked against a dependency +# target of type ````. +# +# ``HAS_DYNAMIC_LOOKUP__`` +# Cached, global alias for ```` +# +# ``DYNAMIC_LOOKUP_FLAGS__`` +# Cached, global alias for ```` +# +# +# Private Functions +# ^^^^^^^^^^^^^^^^^ +# +# The following private functions are defined: +# +# .. warning:: These functions are not part of the scikit-build API. They +# exist purely as an implementation detail and may change from version +# to version without notice, or even be removed. +# +# We mean it. +# +# +# .. cmake:command:: _get_target_type +# +# :: +# +# _get_target_type( ) +# +# +# Shorthand for querying an abbreviated version of the target type +# of the given ````. +# +# ```` is set to: +# +# - "STATIC" for a STATIC_LIBRARY, +# - "SHARED" for a SHARED_LIBRARY, +# - "MODULE" for a MODULE_LIBRARY, +# - and "EXE" for an EXECUTABLE. +# +# Defined variables: +# +# ```` +# The abbreviated version of the ````'s type. +# +# +# .. cmake:command:: _test_weak_link_project +# +# :: +# +# _test_weak_link_project( +# +# +# ) +# +# +# Attempt to compile and run a test project where a target of type +# ```` is weakly-linked against a dependency of type ````: +# +# - ```` can be one of "STATIC", "SHARED", "MODULE", or "EXE". +# - ```` can be one of "STATIC", "SHARED", or "MODULE". +# +# Defined variables: +# +# ```` +# Whether the current C toolchain can produce a working target binary of type +# ```` that is weakly-linked against a dependency target of type +# ````. +# +# ```` +# List of flags to add to the linker command to produce a working target +# binary of type ```` that is weakly-linked against a dependency +# target of type ````. +# + +function(_get_target_type result_var target) + set(target_type "SHARED_LIBRARY") + if(TARGET ${target}) + get_property(target_type TARGET ${target} PROPERTY TYPE) + endif() + + set(result "STATIC") + + if(target_type STREQUAL "STATIC_LIBRARY") + set(result "STATIC") + endif() + + if(target_type STREQUAL "SHARED_LIBRARY") + set(result "SHARED") + endif() + + if(target_type STREQUAL "MODULE_LIBRARY") + set(result "MODULE") + endif() + + if(target_type STREQUAL "EXECUTABLE") + set(result "EXE") + endif() + + set(${result_var} ${result} PARENT_SCOPE) +endfunction() + + +function(_test_weak_link_project + target_type + lib_type + can_weak_link_var + project_name) + + set(gnu_ld_ignore "-Wl,--unresolved-symbols=ignore-all") + set(osx_dynamic_lookup "-undefined dynamic_lookup") + set(no_flag "") + + if(CMAKE_CROSSCOMPILING) + set(link_flag_spec "no_flag") + set(link_flag "${${link_flag_spec}}") + set(test_skipping_reason "") + set(test_pass FALSE) + + if(APPLE AND NOT CMAKE_CROSSCOMPILING_EMULATOR) + set(link_flag_spec "osx_dynamic_lookup") + set(link_flag "${${link_flag_spec}}") + set(test_skipping_reason " (Cross compiling without emulator on macOS)") + set(test_pass TRUE) + endif() + + if(test_pass) + set(test_description "Weak Link ${target_type} -> ${lib_type} (${link_flag_spec})") + message(STATUS "Performing Test ${test_description} - Assuming Success${test_skipping_reason}") + set(${can_weak_link_var} ${test_pass} PARENT_SCOPE) + set(${project_name} ${link_flag} PARENT_SCOPE) + return() + endif() + endif() + + foreach(link_flag_spec gnu_ld_ignore osx_dynamic_lookup no_flag) + set(link_flag "${${link_flag_spec}}") + + set(test_project_dir "${PROJECT_BINARY_DIR}/CMakeTmp") + set(test_project_dir "${test_project_dir}/${project_name}") + set(test_project_dir "${test_project_dir}/${link_flag_spec}") + set(test_project_dir "${test_project_dir}/${target_type}") + set(test_project_dir "${test_project_dir}/${lib_type}") + + set(test_project_src_dir "${test_project_dir}/src") + set(test_project_bin_dir "${test_project_dir}/build") + + file(MAKE_DIRECTORY ${test_project_src_dir}) + file(MAKE_DIRECTORY ${test_project_bin_dir}) + + set(mod_type "STATIC") + set(link_mod_lib TRUE) + set(link_exe_lib TRUE) + set(link_exe_mod FALSE) + + if("${target_type}" STREQUAL "EXE") + set(link_exe_lib FALSE) + set(link_exe_mod TRUE) + else() + set(mod_type "${target_type}") + endif() + + if("${mod_type}" STREQUAL "MODULE") + set(link_mod_lib FALSE) + endif() + + + file(WRITE "${test_project_src_dir}/CMakeLists.txt" " + cmake_minimum_required(VERSION ${CMAKE_VERSION}) + project(${project_name} C) + + include_directories(${test_project_src_dir}) + + add_library(number ${lib_type} number.c) + add_library(counter ${mod_type} counter.c) + ") + + if("${mod_type}" STREQUAL "MODULE") + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + set_target_properties(counter PROPERTIES PREFIX \"\") + ") + endif() + + if(link_mod_lib) + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(counter ${SKBUILD_LINK_LIBRARIES_KEYWORD} number) + ") + elseif(NOT link_flag STREQUAL "") + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + set_target_properties(counter PROPERTIES LINK_FLAGS \"${link_flag}\") + ") + endif() + + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + add_executable(main main.c) + ") + + if(link_exe_lib) + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(main ${SKBUILD_LINK_LIBRARIES_KEYWORD} number) + ") + elseif(NOT link_flag STREQUAL "") + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(main ${SKBUILD_LINK_LIBRARIES_KEYWORD} \"${link_flag}\") + ") + endif() + + if(link_exe_mod) + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(main ${SKBUILD_LINK_LIBRARIES_KEYWORD} counter) + ") + else() + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(main ${SKBUILD_LINK_LIBRARIES_KEYWORD} \"${CMAKE_DL_LIBS}\") + ") + endif() + + file(WRITE "${test_project_src_dir}/number.c" " + #include + + static int _number; + void set_number(int number) { _number = number; } + int get_number() { return _number; } + ") + + file(WRITE "${test_project_src_dir}/number.h" " + #ifndef _NUMBER_H + #define _NUMBER_H + extern void set_number(int); + extern int get_number(void); + #endif + ") + + file(WRITE "${test_project_src_dir}/counter.c" " + #include + int count() { + int result = get_number(); + set_number(result + 1); + return result; + } + ") + + file(WRITE "${test_project_src_dir}/counter.h" " + #ifndef _COUNTER_H + #define _COUNTER_H + extern int count(void); + #endif + ") + + file(WRITE "${test_project_src_dir}/main.c" " + #include + #include + #include + ") + + if(NOT link_exe_mod) + file(APPEND "${test_project_src_dir}/main.c" " + #include + ") + endif() + + file(APPEND "${test_project_src_dir}/main.c" " + int my_count() { + int result = get_number(); + set_number(result + 1); + return result; + } + + int main(int argc, char **argv) { + int result; + ") + + if(NOT link_exe_mod) + file(APPEND "${test_project_src_dir}/main.c" " + void *counter_module; + int (*count)(void); + + counter_module = dlopen(\"./counter.so\", RTLD_LAZY | RTLD_GLOBAL); + if(!counter_module) goto error; + + count = dlsym(counter_module, \"count\"); + if(!count) goto error; + ") + endif() + + file(APPEND "${test_project_src_dir}/main.c" " + result = count() != 0 ? EXIT_FAILURE : + my_count() != 1 ? EXIT_FAILURE : + my_count() != 2 ? EXIT_FAILURE : + count() != 3 ? EXIT_FAILURE : + count() != 4 ? EXIT_FAILURE : + count() != 5 ? EXIT_FAILURE : + my_count() != 6 ? EXIT_FAILURE : EXIT_SUCCESS; + ") + + if(NOT link_exe_mod) + file(APPEND "${test_project_src_dir}/main.c" " + goto done; + error: + fprintf(stderr, \"Error occurred:\\n %s\\n\", dlerror()); + result = 1; + + done: + if(counter_module) dlclose(counter_module); + ") + endif() + + file(APPEND "${test_project_src_dir}/main.c" " + return result; + } + ") + + set(_rpath_arg) + if(APPLE AND ${CMAKE_VERSION} VERSION_GREATER 2.8.11) + set(_rpath_arg "-DCMAKE_MACOSX_RPATH='${CMAKE_MACOSX_RPATH}'") + endif() + + try_compile(project_compiles + "${test_project_bin_dir}" + "${test_project_src_dir}" + "${project_name}" + CMAKE_FLAGS + "-DCMAKE_SHARED_LINKER_FLAGS='${CMAKE_SHARED_LINKER_FLAGS}'" + "-DCMAKE_ENABLE_EXPORTS=ON" + ${_rpath_arg} + OUTPUT_VARIABLE compile_output) + + set(project_works 1) + set(run_output) + + if(project_compiles) + execute_process(COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} + "${test_project_bin_dir}/main" + WORKING_DIRECTORY "${test_project_bin_dir}" + RESULT_VARIABLE project_works + OUTPUT_VARIABLE run_output + ERROR_VARIABLE run_output) + endif() + + set(test_description + "Weak Link ${target_type} -> ${lib_type} (${link_flag_spec})") + + if(project_works EQUAL 0) + set(project_works TRUE) + message(STATUS "Performing Test ${test_description} - Success") + else() + set(project_works FALSE) + message(STATUS "Performing Test ${test_description} - Failed") + file(APPEND ${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/CMakeError.log + "Performing Test ${test_description} failed with the " + "following output:\n" + "BUILD\n-----\n${compile_output}\nRUN\n---\n${run_output}\n") + endif() + + set(${can_weak_link_var} ${project_works} PARENT_SCOPE) + if(project_works) + set(${project_name} ${link_flag} PARENT_SCOPE) + break() + endif() + endforeach() +endfunction() + +function(check_dynamic_lookup) + # Two signatures are supported: + + if(ARGC EQUAL "1") + # + # check_dynamic_lookup() + # + set(target_type "MODULE") + set(lib_type "SHARED") + set(has_dynamic_lookup_var "${ARGV0}") + set(link_flags_var "unused") + + elseif(ARGC GREATER "2") + # + # check_dynamic_lookup( + # + # + # []) + # + set(target_type "${ARGV0}") + set(lib_type "${ARGV1}") + set(has_dynamic_lookup_var "${ARGV2}") + if(ARGC EQUAL "3") + set(link_flags_var "unused") + else() + set(link_flags_var "${ARGV3}") + endif() + else() + message(FATAL_ERROR "missing arguments") + endif() + + _check_dynamic_lookup( + ${target_type} + ${lib_type} + ${has_dynamic_lookup_var} + ${link_flags_var} + ) + set(${has_dynamic_lookup_var} ${${has_dynamic_lookup_var}} PARENT_SCOPE) + if(NOT "x${link_flags_var}x" STREQUAL "xunusedx") + set(${link_flags_var} ${${link_flags_var}} PARENT_SCOPE) + endif() +endfunction() + +function(_check_dynamic_lookup + target_type + lib_type + has_dynamic_lookup_var + link_flags_var + ) + + # hash the CMAKE_FLAGS passed and check cache to know if we need to rerun + if("${target_type}" STREQUAL "STATIC") + string(MD5 cmake_flags_hash "${CMAKE_STATIC_LINKER_FLAGS}") + else() + string(MD5 cmake_flags_hash "${CMAKE_SHARED_LINKER_FLAGS}") + endif() + + set(cache_var "HAS_DYNAMIC_LOOKUP_${target_type}_${lib_type}") + set(cache_hash_var "HAS_DYNAMIC_LOOKUP_${target_type}_${lib_type}_hash") + set(result_var "DYNAMIC_LOOKUP_FLAGS_${target_type}_${lib_type}") + + if( NOT DEFINED ${cache_hash_var} + OR NOT "${${cache_hash_var}}" STREQUAL "${cmake_flags_hash}") + unset(${cache_var} CACHE) + endif() + + if(NOT DEFINED ${cache_var}) + + if(CMAKE_CROSSCOMPILING AND NOT CMAKE_CROSSCOMPILING_EMULATOR) + set(skip_test TRUE) + endif() + + _test_weak_link_project(${target_type} + ${lib_type} + has_dynamic_lookup + link_flags) + + set(caveat " (when linking ${target_type} against ${lib_type})") + + set(${cache_var} "${has_dynamic_lookup}" + CACHE BOOL + "linker supports dynamic lookup for undefined symbols${caveat}") + mark_as_advanced(${cache_var}) + + set(${result_var} "${link_flags}" + CACHE STRING + "linker flags for dynamic lookup${caveat}") + mark_as_advanced(${result_var}) + + set(${cache_hash_var} "${cmake_flags_hash}" + CACHE INTERNAL "hashed flags for ${cache_var} check") + endif() + + set(${has_dynamic_lookup_var} "${${cache_var}}" PARENT_SCOPE) + set(${link_flags_var} "${${result_var}}" PARENT_SCOPE) +endfunction() + +function(target_link_libraries_with_dynamic_lookup target) + _get_target_type(target_type ${target}) + + set(link_props) + set(link_items) + set(link_libs) + + foreach(lib ${ARGN}) + _get_target_type(lib_type ${lib}) + check_dynamic_lookup(${target_type} + ${lib_type} + has_dynamic_lookup + dynamic_lookup_flags) + + if(has_dynamic_lookup) + if(dynamic_lookup_flags) + if("${target_type}" STREQUAL "EXE") + list(APPEND link_items "${dynamic_lookup_flags}") + else() + list(APPEND link_props "${dynamic_lookup_flags}") + endif() + endif() + elseif(${lib} MATCHES "(debug|optimized|general)") + # See gh-255 + else() + list(APPEND link_libs "${lib}") + endif() + endforeach() + + if(link_props) + list(REMOVE_DUPLICATES link_props) + endif() + + if(link_items) + list(REMOVE_DUPLICATES link_items) + endif() + + if(link_libs) + list(REMOVE_DUPLICATES link_libs) + endif() + + if(link_props) + set_target_properties(${target} + PROPERTIES LINK_FLAGS "${link_props}") + endif() + + set(links "${link_items}" "${link_libs}") + if(links) + target_link_libraries(${target} ${SKBUILD_LINK_LIBRARIES_KEYWORD} "${links}") + endif() +endfunction() diff --git a/pyproject.toml b/pyproject.toml index e445532a6..eb4616479 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,24 +1,69 @@ +[project] +name = "swiftest" +version = "2023.9.0" +authors=[ + {name = 'David A. Minton', email='daminton@purdue.edu'}, + {name = 'Carlisle Wishard'}, + {name = 'Jennifer Pouplin'}, + {name = 'Jake Elliott'}, + {name = 'Dana Singh'}, + {name = 'Kaustub Anand'}, +] +maintainers = [ + {name = 'David A. Minton', email='daminton@purdue.edu'}, +] +readme = "README.md" +requires-python=">=3.8" +license={file = "LICENSE.txt"} +classifiers=[ +'Development Status :: 3 - Alpha', +'Intended Audience :: Science/Research', +'Topic :: Scientific/Engineering :: Astronomy', +'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', +'Programming Language :: Python :: 3', +] +keywords=['astronomy','astrophysics', 'planetary', 'nbody integrator', 'symplectic', 'wisdom-holman'] +dependencies = [ + 'numpy>=1.24.3', + 'scipy>=1.10.1', + 'xarray>=2022.11.0', + 'dask>=2022.1', + 'distributed>=2022.1', + 'bottleneck>=1.3.5', + 'h5netcdf>=1.0.2', + 'netcdf4>=1.6.2', + 'matplotlib>=3.7.1', + 'astropy>=5.1', + 'astroquery>=0.4.6', + 'tqdm>=4.65.0', + 'cython>=3.0.0', +] + +[project.urls] +Repository = 'https://github.itap.purdue.edu/MintonGroup/swiftest' + [build-system] requires = [ - "setuptools>=42", - "scikit-build>=0.17", - "cmake>=3.5.0", + "scikit-build-core", "cython>=3.0.0", - "ninja", "pytest", ] -build-backend = "setuptools.build_meta" +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +cmake.args = ["-DBUILD_SHARED_LIBS=ON","-DUSE_SIMD=OFF"] +sdist.include = ["src/globals/globals_module.f90.in","swiftest/*.py","swiftest/*.pyx","swiftest/*.h"] [tool.cibuildwheel] -environment = {SKBUILD_CONFIGURE_OPTIONS="-DBUILD_SHARED_LIBS=ON -DUSE_SIMD=OFF"} test-command = "pytest {package}/tests" -test-requires = ['pytest','cython'] +test-requires = ['pytest'] test-skip = "cp312-*" skip = "pp* *i686 *-manylinux_i686 *_ppc64le *_s390x *-musllinux* *-win32" +build-verbosity = 1 [tool.cibuildwheel.linux] -environment = {FFLAGS="${FFLAGS} -fPIC", CFLAGS="${CFLAGS} -fPIC", LDFLAGS="${LDFLAGS} -fPIE", LIBS="-lgomp"} +repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" +environment = {NETCDF_FORTRAN_HOME="/usr/local", NETCDF_INCLUDE="/usr/local/include", LD_LIBRARY_PATH="/usr/local/lib:/project/lib", CPATH="/usr/local/include:/project/include"} before-all = [ - "yum install doxygen libxml2-devel -y || apt-get install doxygen libxml2-dev -y", - "buildscripts/build_dependencies.sh -p /usr/local", + "yum install -y epel-release && yum install -y netcdf-fortran netcdf-devel || apt-get install -y netcdf-fortran", ] diff --git a/setup.py b/setup.py deleted file mode 100644 index 2d6141ffa..000000000 --- a/setup.py +++ /dev/null @@ -1,64 +0,0 @@ -""" - Copyright 2022 - David Minton, Carlisle Wishard, Jennifer Pouplin, Jake Elliott, & Dana Singh - This file is part of Swiftest. - Swiftest is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License - as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - Swiftest is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY - without even the implied warranty - of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with Swiftest. - If not, see: https://www.gnu.org/licenses. -""" - -from skbuild import setup -from pathlib import Path - -with open('version.txt') as version_file: - version = version_file.read().strip() - -setup(name='swiftest', - version=version, - author='David A. Minton', - author_email='daminton@purdue.edu', - url='https://github.itap.purdue.edu/MintonGroup/swiftest', - long_description=Path("README.md").read_text(encoding="utf-8"), - long_description_content_type="text/markdown", - python_requires=">=3.8", - license="GPLv3", - classifiers=[ - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 3 - Alpha', - - # Indicate who your project is intended for - 'Intended Audience :: Science/Research', - 'Topic :: Scientific/Engineering :: Astronomy', - - # Pick your license as you wish (should match "license" above) - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - - # Specify the Python versions you support here. In particular, ensure - # that you indicate whether you support Python 2, Python 3 or both. - 'Programming Language :: Python :: 3', - ], - keywords='astronomy astrophysics planetary nbody integrator symplectic wisdom-holman', - install_requires= [ - 'numpy>=1.24.3', - 'scipy>=1.10.1', - 'xarray>=2022.11.0', - 'dask>=2022.1', - 'distributed>=2022.1', - 'bottleneck>=1.3.5', - 'h5netcdf>=1.0.2', - 'netcdf4>=1.6.2', - 'matplotlib>=3.7.1', - 'astropy>=5.1', - 'astroquery>=0.4.6', - 'tqdm>=4.65.0', - 'cython>=3.0.0', - ], - packages=['swiftest'], - test_suite="swiftest.tests", - ) diff --git a/src/globals/globals_module.f90 b/src/globals/globals_module.f90 index ece47b07f..a12c018ce 100644 --- a/src/globals/globals_module.f90 +++ b/src/globals/globals_module.f90 @@ -48,7 +48,7 @@ module globals integer(I4B), parameter :: UPPERCASE_OFFSET = iachar('A') - iachar('a') !! ASCII character set parameter for lower to upper !! conversion - offset between upper and lower - character(*), parameter :: VERSION = "2023.8.1" !! Swiftest version + character(*), parameter :: VERSION = "2023.9.0" !! Swiftest version !> Symbolic name for integrator types character(*), parameter :: UNKNOWN_INTEGRATOR = "UKNOWN INTEGRATOR" diff --git a/version.txt b/version.txt index 4d449f4ac..882e30299 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2023.8.1 \ No newline at end of file +2023.9.0 \ No newline at end of file