From ad3ad59661fc0a3cf37dcc5cf98ac8dc4770853c Mon Sep 17 00:00:00 2001 From: Omar Padron Date: Wed, 24 May 2017 19:20:53 -0400 Subject: [PATCH 1/2] switch to scikit-build packaging --- CMakeLists.txt | 26 +++- setup.py | 202 ++----------------------- symengine/CMakeLists.txt | 7 +- symengine/lib/CMakeLists.txt | 31 ++-- symengine/lib/symengine/CMakeLists.txt | 5 +- symengine/lib/symengine_wrapper.pyx | 3 +- symengine/tests/CMakeLists.txt | 4 +- 7 files changed, 51 insertions(+), 227 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2deccd5d8..58b99aea7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,18 +1,29 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.6) project(python_wrapper) +find_package(PythonInterp REQUIRED) +find_package(PythonLibs REQUIRED) +find_package(PythonExtensions REQUIRED) +find_package(Cython REQUIRED) + set(CMAKE_PREFIX_PATH ${SymEngine_DIR} ${CMAKE_PREFIX_PATH}) -find_package(SymEngine 0.3.0 REQUIRED CONFIG + +find_package(SymEngine 0.3.0 + REQUIRED CONFIG PATH_SUFFIXES lib/cmake/symengine cmake/symengine CMake/) + message("SymEngine_DIR : " ${SymEngine_DIR}) + set(CMAKE_BUILD_TYPE ${SYMENGINE_BUILD_TYPE}) set(CMAKE_CXX_FLAGS_RELEASE ${SYMENGINE_CXX_FLAGS_RELEASE}) set(CMAKE_CXX_FLAGS_DEBUG ${SYMENGINE_CXX_FLAGS_DEBUG}) + include_directories(${SYMENGINE_INCLUDE_DIRS}) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") -find_package(Python REQUIRED) -find_package(Cython REQUIRED) +# set(CMAKE_MODULE_PATH +# ${CMAKE_MODULE_PATH} +# "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") + include_directories(${PYTHON_INCLUDE_PATH}) @@ -22,7 +33,10 @@ if (MINGW AND ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")) endif() if (MINGW AND (CMAKE_BUILD_TYPE STREQUAL "Release")) - try_compile(CHECK_PYTHON_HYPOT "${CMAKE_CURRENT_BINARY_DIR}/" "${CMAKE_SOURCE_DIR}/cmake/check_python_hypot.cpp") + try_compile(CHECK_PYTHON_HYPOT + "${CMAKE_CURRENT_BINARY_DIR}/" + "${CMAKE_SOURCE_DIR}/cmake/check_python_hypot.cpp") + if (NOT ${CHECK_PYTHON_HYPOT}) # include cmath before all headers set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -include cmath") diff --git a/setup.py b/setup.py index 118156f12..7bed7f686 100644 --- a/setup.py +++ b/setup.py @@ -3,8 +3,6 @@ import os import subprocess import sys -from distutils.command.build_ext import build_ext as _build_ext -from distutils.command.build import build as _build # Make sure the system has the right Python version. if sys.version_info[:2] < (2, 7): @@ -12,191 +10,20 @@ "Python %d.%d detected" % sys.version_info[:2]) sys.exit(-1) -# use setuptools by default as per the official advice at: -# packaging.python.org/en/latest/current.html#packaging-tool-recommendations -use_setuptools = True -# set the environment variable USE_DISTUTILS=True to force the use of distutils -use_distutils = getenv('USE_DISTUTILS') -if use_distutils is not None: - if use_distutils.lower() == 'true': - use_setuptools = False - else: - print("Value {} for USE_DISTUTILS treated as False". - format(use_distutils)) +from skbuild import setup -if use_setuptools: - try: - from setuptools import setup - from setuptools.command.install import install as _install - except ImportError: - use_setuptools = False - -if not use_setuptools: - from distutils.core import setup - from distutils.command.install import install as _install - -cmake_opts = [("PYTHON_BIN", sys.executable), - ("CMAKE_INSTALL_RPATH_USE_LINK_PATH", "yes")] -cmake_generator = [None] -cmake_build_type = ["Release"] - - -def process_opts(opts): - return ['-D'+'='.join(o) for o in opts] - - -def get_build_dir(dist): - source_dir = path.dirname(path.realpath(__file__)) - build = dist.get_command_obj('build') - build_ext = dist.get_command_obj('build_ext') - return source_dir if build_ext.inplace else build.build_platlib - - -global_user_options = [ - ('symengine-dir=', None, - 'path to symengine installation or build directory'), - ('generator=', None, 'cmake build generator'), - ('build-type=', None, 'build type: Release or Debug'), - ('define=', 'D', - 'options to cmake :='), -] - - -class BuildWithCmake(_build): - sub_commands = [('build_ext', None)] - - -class BuildExtWithCmake(_build_ext): - _build_opts = _build_ext.user_options - user_options = list(global_user_options) - user_options.extend(_build_opts) - - def initialize_options(self): - _build_ext.initialize_options(self) - self.define = None - self.symengine_dir = None - self.generator = None - self.build_type = "Release" - - def finalize_options(self): - _build_ext.finalize_options(self) - # The argument parsing will result in self.define being a string, but - # it has to be a list of 2-tuples. - # Multiple symbols can be separated with semi-colons. - if self.define: - defines = self.define.split(';') - self.define = [(s.strip(), None) if '=' not in s else - tuple(ss.strip() for ss in s.split('=')) - for s in defines] - cmake_opts.extend(self.define) - if self.symengine_dir: - cmake_opts.extend([('SymEngine_DIR', self.symengine_dir)]) - - if self.generator: - cmake_generator[0] = self.generator - - cmake_build_type[0] = self.build_type - - def cmake_build(self): - source_dir = path.dirname(path.realpath(__file__)) - build_dir = get_build_dir(self.distribution) - if not path.exists(build_dir): - makedirs(build_dir) - if build_dir != source_dir and path.exists("CMakeCache.txt"): - os.remove("CMakeCache.txt") - - cmake_cmd = ["cmake", source_dir, - "-DCMAKE_BUILD_TYPE=" + cmake_build_type[0]] - cmake_cmd.extend(process_opts(cmake_opts)) - if not path.exists(path.join(build_dir, "CMakeCache.txt")): - cmake_cmd.extend(self.get_generator()) - if subprocess.call(cmake_cmd, cwd=build_dir) != 0: - raise EnvironmentError("error calling cmake") - - if subprocess.call(["cmake", "--build", ".", - "--config", cmake_build_type[0]], - cwd=build_dir) != 0: - raise EnvironmentError("error building project") - - def get_generator(self): - if cmake_generator[0]: - return ["-G", cmake_generator[0]] - else: - import platform - import sys - if (platform.system() == "Windows"): - compiler = str(self.compiler).lower() - if ("msys" in compiler): - return ["-G", "MSYS Makefiles"] - elif ("mingw" in compiler): - return ["-G", "MinGW Makefiles"] - elif sys.maxsize > 2**32: - return ["-G", "Visual Studio 14 2015 Win64"] - else: - return ["-G", "Visual Studio 14 2015"] - return [] - - def run(self): - self.cmake_build() - # can't use super() here because - # _build_ext is an old style class in 2.7 - _build_ext.run(self) - - -class InstallWithCmake(_install): - _install_opts = _install.user_options - user_options = list(global_user_options) - user_options.extend(_install_opts) - - def initialize_options(self): - _install.initialize_options(self) - self.define = None - self.symengine_dir = None - self.generator = None - self.build_type = "Release" - - def finalize_options(self): - _install.finalize_options(self) - # The argument parsing will result in self.define being a string, but - # it has to be a list of 2-tuples. - # Multiple symbols can be separated with semi-colons. - if self.define: - defines = self.define.split(';') - self.define = [(s.strip(), None) if '=' not in s else - tuple(ss.strip() for ss in s.split('=')) - for s in defines] - cmake_opts.extend(self.define) - - cmake_build_type[0] = self.build_type - cmake_opts.extend([('PYTHON_INSTALL_PATH', self.install_platlib)]) - cmake_opts.extend([('PYTHON_INSTALL_HEADER_PATH', - self.install_headers)]) - - def cmake_install(self): - source_dir = path.dirname(path.realpath(__file__)) - build_dir = get_build_dir(self.distribution) - cmake_cmd = ["cmake", source_dir] - cmake_cmd.extend(process_opts(cmake_opts)) - - # CMake has to be called here to update PYTHON_INSTALL_PATH - # if build and install were called separately by the user - if subprocess.call(cmake_cmd, cwd=build_dir) != 0: - raise EnvironmentError("error calling cmake") - - if subprocess.call(["cmake", "--build", ".", - "--config", cmake_build_type[0], - "--target", "install"], - cwd=build_dir) != 0: - raise EnvironmentError("error installing") - - import compileall - compileall.compile_dir(path.join(self.install_platlib, "symengine")) - - def run(self): - # can't use super() here because _install is an old style class in 2.7 - _install.run(self) - self.cmake_install() +# cmake_opts = [("PYTHON_BIN", sys.executable), +# ("CMAKE_INSTALL_RPATH_USE_LINK_PATH", "yes")] +# cmake_build_type = ["Release"] +## global_user_options = [ +## ('symengine-dir=', None, +## 'path to symengine installation or build directory'), +## ('generator=', None, 'cmake build generator'), +## ('build-type=', None, 'build type: Release or Debug'), +## ('define=', 'D', +## 'options to cmake :='), +## ] long_description = ''' SymEngine is a standalone fast C++ symbolic manipulation library. @@ -212,11 +39,6 @@ def run(self): author_email="symengine@googlegroups.com", license="MIT", url="https://github.com/symengine/symengine.py", - cmdclass={ - 'build': BuildWithCmake, - 'build_ext': BuildExtWithCmake, - 'install': InstallWithCmake, - }, classifiers=[ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', diff --git a/symengine/CMakeLists.txt b/symengine/CMakeLists.txt index a53857d10..1f34e1d09 100644 --- a/symengine/CMakeLists.txt +++ b/symengine/CMakeLists.txt @@ -1,7 +1,6 @@ add_subdirectory(lib) add_subdirectory(tests) -set(PY_PATH ${PYTHON_INSTALL_PATH}/symengine) -install(FILES __init__.py utilities.py compatibility.py sympy_compat.py - DESTINATION ${PY_PATH} - ) +install(FILES + __init__.py utilities.py compatibility.py sympy_compat.py + DESTINATION ${PYTHON_RELATIVE_SITE_PACKAGES_DIR}/symengine) diff --git a/symengine/lib/CMakeLists.txt b/symengine/lib/CMakeLists.txt index cf733a33a..c05481156 100644 --- a/symengine/lib/CMakeLists.txt +++ b/symengine/lib/CMakeLists.txt @@ -1,37 +1,32 @@ -set(SRC - symengine_wrapper.cpp - pywrapper.cpp - ) + +set(SRC pywrapper.cpp) configure_file(config.pxi.in config.pxi) include_directories(BEFORE ${python_wrapper_BINARY_DIR}/symengine/lib) add_subdirectory(symengine) -cython_add_module_pyx(symengine_wrapper symengine.pxd) -add_python_library(symengine_wrapper ${SRC}) +add_cython_target(symengine_wrapper CXX OUTPUT_VAR symengine_wrapper_src) +add_library(symengine_wrapper MODULE ${symengine_wrapper_src} ${SRC}) target_link_libraries(symengine_wrapper ${SYMENGINE_LIBRARIES}) +python_extension_module(symengine_wrapper) + if (CMAKE_CXX_COMPILER_ID MATCHES GNU|Clang) # Must suppress strict aliasing for this file set_source_files_properties( symengine_wrapper.cpp - PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -Wno-unused-function" - ) + PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -Wno-unused-function") endif() -add_custom_target(cython - COMMAND cython symengine_wrapper.pyx - ) +set(symengine_pkg ${PYTHON_RELATIVE_SITE_PACKAGES_DIR}/symengine) -set(PY_PATH ${PYTHON_INSTALL_PATH}/symengine/lib) install(TARGETS symengine_wrapper - RUNTIME DESTINATION ${PY_PATH} - ARCHIVE DESTINATION ${PY_PATH} - LIBRARY DESTINATION ${PY_PATH} - ) + RUNTIME DESTINATION ${symengine_pkg}/lib + ARCHIVE DESTINATION ${symengine_pkg}/lib + LIBRARY DESTINATION ${symengine_pkg}/lib) + install(FILES __init__.py ${CMAKE_CURRENT_BINARY_DIR}/config.pxi symengine.pxd symengine_wrapper.pxd - DESTINATION ${PY_PATH} - ) + DESTINATION ${symengine_pkg}/lib) diff --git a/symengine/lib/symengine/CMakeLists.txt b/symengine/lib/symengine/CMakeLists.txt index 931ff98a4..23b81f0f3 100644 --- a/symengine/lib/symengine/CMakeLists.txt +++ b/symengine/lib/symengine/CMakeLists.txt @@ -1,4 +1 @@ -install(FILES - pywrapper.h - DESTINATION ${PYTHON_INSTALL_HEADER_PATH} - ) +install(FILES pywrapper.h DESTINATION include) diff --git a/symengine/lib/symengine_wrapper.pyx b/symengine/lib/symengine_wrapper.pyx index ee5c3bacc..566e1834f 100644 --- a/symengine/lib/symengine_wrapper.pyx +++ b/symengine/lib/symengine_wrapper.pyx @@ -4,8 +4,7 @@ from symengine cimport RCP, pair, map_basic_basic, umap_int_basic, umap_int_basi from libcpp cimport bool from libcpp.string cimport string from libcpp.vector cimport vector -from cpython cimport PyObject, Py_XINCREF, Py_XDECREF, \ - PyObject_CallMethodObjArgs +from cpython cimport PyObject, Py_XINCREF, Py_XDECREF, PyObject_CallMethodObjArgs from libc.string cimport memcpy import cython import itertools diff --git a/symengine/tests/CMakeLists.txt b/symengine/tests/CMakeLists.txt index 13ad9239d..73539839b 100644 --- a/symengine/tests/CMakeLists.txt +++ b/symengine/tests/CMakeLists.txt @@ -1,4 +1,3 @@ -set(PY_PATH ${PYTHON_INSTALL_PATH}/symengine/tests) install(FILES __init__.py test_arit.py test_dict_basic.py @@ -17,5 +16,4 @@ install(FILES __init__.py test_var.py test_lambdify.py test_sympy_compat.py - DESTINATION ${PY_PATH} - ) + DESTINATION ${PYTHON_RELATIVE_SITE_PACKAGES_DIR}/symengine/tests) From 0ea7dda055473f74dc91bd2138eeb839a17e8b52 Mon Sep 17 00:00:00 2001 From: Omar Padron Date: Thu, 25 May 2017 13:34:28 -0400 Subject: [PATCH 2/2] add superbuild logic --- CMakeLists.txt | 43 +++++++++++++++++++++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 44 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 58b99aea7..fe25252e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,49 @@ cmake_minimum_required(VERSION 3.6) project(python_wrapper) +# If running directly under scikit-build, set up the superbuild. +if(SKBUILD) + include(ExternalProject) + + file(STRINGS symengine_version.txt SYMENGINE_GIT_TAG LIMIT_COUNT 1) + + ExternalProject_add( + symengine + SOURCE_DIR ${CMAKE_BINARY_DIR}/symengine-src + BINARY_DIR ${CMAKE_BINARY_DIR}/symengine-bin + CMAKE_CACHE_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}/symengine-install + GIT_REPOSITORY git://github.com/symengine/symengine.git + GIT_TAG ${SYMENGINE_GIT_TAG}) + + set(symengine_install_dir + ${CMAKE_BINARY_DIR}/symengine-install) + set(symengine_wrapper_install_dir + ${CMAKE_BINARY_DIR}/symengine-wrapper-install) + + ExternalProject_add( + symengine_wrapper + DEPENDS symengine + SOURCE_DIR ${CMAKE_SOURCE_DIR} + BINARY_DIR ${CMAKE_BINARY_DIR}/symengine-wrapper-bin + CMAKE_CACHE_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${symengine_wrapper_install_dir} + -DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE} + -DPYTHON_VERSION_STRING:STRING=${PYTHON_VERSION_STRING} + -DPYTHON_INCLUDE_DIR:PATH=${PYTHON_INCLUDE_DIR} + -DPYTHON_LIBRARY:FILEPATH=${PYTHON_LIBRARY} + -DSKBUILD:BOOL=NO + -DCMAKE_MODULE_PATH:PATH=${CMAKE_MODULE_PATH} + -DSymEngine_DIR:PATH=${symengine_install_dir} + DOWNLOAD_COMMAND "" + UPDATE_COMMAND "") + + install(DIRECTORY ${symengine_wrapper_install_dir}/ + DESTINATION ${CMAKE_INSTALL_PREFIX}) + + return() +endif() + find_package(PythonInterp REQUIRED) find_package(PythonLibs REQUIRED) find_package(PythonExtensions REQUIRED) diff --git a/setup.py b/setup.py index 7bed7f686..1d1db7757 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ setup(name="symengine", version="0.2.1.dev", + cmake_with_sdist=True, description="Python library providing wrappers to SymEngine", setup_requires=['cython>=0.19.1'], long_description=long_description,