Skip to content

[WIP] [PROPOSAL] use scikit-build for packaging #144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 63 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,72 @@
cmake_minimum_required(VERSION 2.8)
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)
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})

Expand All @@ -22,7 +76,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")
Expand Down
203 changes: 13 additions & 190 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,200 +3,27 @@
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):
print("SymEngine requires Python 2.7 or newer. "
"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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try:
    from skbuild import setup
except ImportError:
    print('scikit-build is required to build from source.', file=sys.stderr)
    print('Please run:', file=sys.stderr)
    print('', file=sys.stderr)
    print('  python -m pip install scikit-build')
    sys.exit(1)


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 <var>:<type>=<value>'),
]


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 <var>:<type>=<value>'),
## ]

long_description = '''
SymEngine is a standalone fast C++ symbolic manipulation library.
Expand All @@ -205,18 +32,14 @@ def run(self):

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,
author="SymEngine development team",
author_email="[email protected]",
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',
Expand Down
7 changes: 3 additions & 4 deletions symengine/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
31 changes: 13 additions & 18 deletions symengine/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 1 addition & 4 deletions symengine/lib/symengine/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
install(FILES
pywrapper.h
DESTINATION ${PYTHON_INSTALL_HEADER_PATH}
)
install(FILES pywrapper.h DESTINATION include)
Loading