Skip to content

Commit 7180826

Browse files
committed
In place cmake build + rpath on linux, at least
1 parent 812768f commit 7180826

File tree

2 files changed

+219
-45
lines changed

2 files changed

+219
-45
lines changed

CMakeLists.txt

+10-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
228228
EXECUTE_PROCESS(COMMAND ln ${MORE_ARGS} -sf ${BUILD_OUTPUT_ROOT_DIRECTORY}
229229
${CMAKE_CURRENT_BINARY_DIR}/build/latest)
230230
else()
231-
set(BUILD_OUTPUT_ROOT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${BUILD_SUBDIR_NAME}/")
231+
# set(BUILD_OUTPUT_ROOT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${BUILD_SUBDIR_NAME}/")
232+
set(BUILD_OUTPUT_ROOT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
233+
232234
endif()
233235

234236
# where to put generated archives (.a files)
@@ -587,6 +589,9 @@ foreach(pyx_api_file
587589
set_source_files_properties(${pyx_api_file} PROPERTIES CYTHON_API 1)
588590
endforeach(pyx_api_file)
589591

592+
set(USE_RELATIVE_RPATH ON)
593+
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
594+
590595
foreach(module native)
591596
string(REPLACE "." ";" directories ${module})
592597
list(GET directories -1 module_name)
@@ -617,6 +622,10 @@ foreach(module native)
617622
set(module_install_rpath "${module_install_rpath}/..")
618623
math(EXPR i "${i} - 1" )
619624
endwhile(${i} GREATER 0)
625+
626+
# for inplace development for now
627+
set(module_install_rpath "${CMAKE_SOURCE_DIR}/pandas/")
628+
620629
set_target_properties(${module_name} PROPERTIES
621630
INSTALL_RPATH ${module_install_rpath})
622631
target_link_libraries(${module_name} pandas)

setup.py

+209-44
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
BSD license. Parts are from lxml (https://github.com/lxml/lxml)
77
"""
88

9+
import glob
910
import os
11+
import os.path as osp
1012
import sys
1113
import shutil
12-
import warnings
1314
import re
1415
import platform
1516
from distutils.version import LooseVersion
17+
from distutils import sysconfig
18+
19+
from os.path import join as pjoin
1620

1721
# versioning
1822
import versioneer
@@ -26,6 +30,7 @@
2630
except ImportError:
2731
_CYTHON_INSTALLED = False
2832

33+
2934
try:
3035
import pkg_resources
3136
from setuptools import setup, Command
@@ -35,6 +40,7 @@
3540
from distutils.core import setup, Command
3641
_have_setuptools = False
3742

43+
3844
setuptools_kwargs = {}
3945
min_numpy_ver = '1.7.0'
4046
if sys.version_info[0] >= 3:
@@ -53,46 +59,199 @@
5359
else:
5460
setuptools_kwargs = {
5561
'install_requires': ['python-dateutil',
56-
'pytz >= 2011k',
62+
'pytz >= 2011k',
5763
'numpy >= %s' % min_numpy_ver],
5864
'setup_requires': ['numpy >= %s' % min_numpy_ver],
5965
'zip_safe': False,
6066
}
6167

6268
if not _have_setuptools:
6369
try:
64-
import numpy
65-
import dateutil
70+
import numpy # noqa
71+
import dateutil # noqa
6672
setuptools_kwargs = {}
6773
except ImportError:
6874
sys.exit("install requires: 'python-dateutil < 2','numpy'."
6975
" use pip or easy_install."
7076
"\n $ pip install 'python-dateutil < 2' 'numpy'")
7177

78+
79+
# Check if we're running 64-bit Python
80+
is_64_bit = sys.maxsize > 2**32
81+
82+
# Check if this is a debug build of Python.
83+
if hasattr(sys, 'gettotalrefcount'):
84+
build_type = 'Debug'
85+
else:
86+
build_type = 'Release'
87+
88+
7289
from distutils.extension import Extension
7390
from distutils.command.build import build
7491
from distutils.command.build_ext import build_ext as _build_ext
7592

7693
try:
7794
if not _CYTHON_INSTALLED:
7895
raise ImportError('No supported version of Cython installed.')
79-
from Cython.Distutils import build_ext as _build_ext
96+
from Cython.Distutils import build_ext as _build_ext # noqa
8097
cython = True
8198
except ImportError:
8299
cython = False
83100

84-
from os.path import join as pjoin
85-
86101

87102
class build_ext(_build_ext):
103+
88104
def build_extensions(self):
89105
numpy_incl = pkg_resources.resource_filename('numpy', 'core/include')
90106

91107
for ext in self.extensions:
92-
if hasattr(ext, 'include_dirs') and not numpy_incl in ext.include_dirs:
108+
if (hasattr(ext, 'include_dirs') and
109+
numpy_incl not in ext.include_dirs):
93110
ext.include_dirs.append(numpy_incl)
94111
_build_ext.build_extensions(self)
95112

113+
def run(self):
114+
self._run_cmake()
115+
_build_ext.run(self)
116+
117+
# adapted from cmake_build_ext in dynd-python
118+
# github.com/libdynd/dynd-python
119+
120+
description = "Build the C-extension for libpandas and regular pandas"
121+
user_options = ([('extra-cmake-args=', None,
122+
'extra arguments for CMake')] +
123+
_build_ext.user_options)
124+
125+
def initialize_options(self):
126+
_build_ext.initialize_options(self)
127+
self.extra_cmake_args = ''
128+
129+
def _run_cmake(self):
130+
# The directory containing this setup.py
131+
source = osp.dirname(osp.abspath(__file__))
132+
133+
# The staging directory for the module being built
134+
build_temp = pjoin(os.getcwd(), self.build_temp)
135+
136+
# Change to the build directory
137+
saved_cwd = os.getcwd()
138+
if not os.path.isdir(self.build_temp):
139+
self.mkpath(self.build_temp)
140+
os.chdir(self.build_temp)
141+
142+
# Detect if we built elsewhere
143+
if os.path.isfile('CMakeCache.txt'):
144+
cachefile = open('CMakeCache.txt', 'r')
145+
cachedir = re.search('CMAKE_CACHEFILE_DIR:INTERNAL=(.*)',
146+
cachefile.read()).group(1)
147+
cachefile.close()
148+
if (cachedir != build_temp):
149+
return
150+
151+
pyexe_option = '-DPYTHON_EXECUTABLE=%s' % sys.executable
152+
static_lib_option = ''
153+
build_tests_option = ''
154+
155+
if sys.platform != 'win32':
156+
cmake_command = ['cmake', self.extra_cmake_args, pyexe_option,
157+
build_tests_option,
158+
static_lib_option, source]
159+
160+
self.spawn(cmake_command)
161+
self.spawn(['make'])
162+
else:
163+
import shlex
164+
cmake_generator = 'Visual Studio 14 2015'
165+
if is_64_bit:
166+
cmake_generator += ' Win64'
167+
# Generate the build files
168+
extra_cmake_args = shlex.split(self.extra_cmake_args)
169+
cmake_command = (['cmake'] + extra_cmake_args +
170+
[source, pyexe_option,
171+
static_lib_option,
172+
build_tests_option,
173+
'-G', cmake_generator])
174+
if "-G" in self.extra_cmake_args:
175+
cmake_command = cmake_command[:-2]
176+
177+
self.spawn(cmake_command)
178+
# Do the build
179+
self.spawn(['cmake', '--build', '.', '--config', build_type])
180+
181+
if self.inplace:
182+
# a bit hacky
183+
build_lib = saved_cwd
184+
else:
185+
build_lib = pjoin(os.getcwd(), self.build_lib)
186+
187+
# Move the built libpandas library to the place expected by the Python
188+
# build
189+
if sys.platform != 'win32':
190+
name, = glob.glob('libpandas.*')
191+
try:
192+
os.makedirs(pjoin(build_lib, 'pandas'))
193+
except OSError:
194+
pass
195+
shutil.move(name, pjoin(build_lib, 'pandas', name))
196+
else:
197+
shutil.move(pjoin(build_type, 'pandas.dll'),
198+
pjoin(build_lib, 'pandas', 'pandas.dll'))
199+
200+
# Move the built C-extension to the place expected by the Python build
201+
self._found_names = []
202+
for name in self.get_cmake_cython_names():
203+
built_path = self.get_ext_built(name)
204+
if not os.path.exists(built_path):
205+
raise RuntimeError('libpandas C-extension failed to build:',
206+
os.path.abspath(built_path))
207+
208+
ext_path = pjoin(build_lib, self._get_cmake_ext_path(name))
209+
if os.path.exists(ext_path):
210+
os.remove(ext_path)
211+
self.mkpath(os.path.dirname(ext_path))
212+
print('Moving built libpandas C-extension', built_path,
213+
'to build path', ext_path)
214+
shutil.move(self.get_ext_built(name), ext_path)
215+
self._found_names.append(name)
216+
217+
os.chdir(saved_cwd)
218+
219+
def _get_inplace_dir(self):
220+
pass
221+
222+
def _get_cmake_ext_path(self, name):
223+
# Get the package directory from build_py
224+
build_py = self.get_finalized_command('build_py')
225+
package_dir = build_py.get_package_dir('pandas')
226+
# This is the name of the pandas C-extension
227+
suffix = sysconfig.get_config_var('EXT_SUFFIX')
228+
if suffix is None:
229+
suffix = sysconfig.get_config_var('SO')
230+
filename = name + suffix
231+
return pjoin(package_dir, filename)
232+
233+
def get_ext_built(self, name):
234+
if sys.platform == 'win32':
235+
head, tail = os.path.split(name)
236+
suffix = sysconfig.get_config_var('SO')
237+
return pjoin(head, build_type, tail + suffix)
238+
else:
239+
suffix = sysconfig.get_config_var('SO')
240+
return name + suffix
241+
242+
def get_cmake_cython_names(self):
243+
return ['native']
244+
245+
def get_names(self):
246+
return self._found_names
247+
248+
def get_outputs(self):
249+
# Just the C extensions
250+
cmake_exts = [self._get_cmake_ext_path(name)
251+
for name in self.get_names()]
252+
regular_exts = _build_ext.get_outputs(self)
253+
return regular_exts + cmake_exts
254+
96255

97256
DESCRIPTION = ("Powerful data structures for data analysis, time series,"
98257
"and statistics")
@@ -186,6 +345,7 @@ def build_extensions(self):
186345
'Topic :: Scientific/Engineering',
187346
]
188347

348+
189349
class CleanCommand(Command):
190350
"""Custom distutils command to clean the .so and .pyc files."""
191351

@@ -196,22 +356,22 @@ def initialize_options(self):
196356
self._clean_me = []
197357
self._clean_trees = []
198358

199-
base = pjoin('pandas','src')
200-
dt = pjoin(base,'datetime')
359+
base = pjoin('pandas', 'src')
360+
dt = pjoin(base, 'datetime')
201361
src = base
202-
parser = pjoin(base,'parser')
203-
ujson_python = pjoin(base,'ujson','python')
204-
ujson_lib = pjoin(base,'ujson','lib')
205-
self._clean_exclude = [pjoin(dt,'np_datetime.c'),
206-
pjoin(dt,'np_datetime_strings.c'),
207-
pjoin(src,'period_helper.c'),
208-
pjoin(parser,'tokenizer.c'),
209-
pjoin(parser,'io.c'),
210-
pjoin(ujson_python,'ujson.c'),
211-
pjoin(ujson_python,'objToJSON.c'),
212-
pjoin(ujson_python,'JSONtoObj.c'),
213-
pjoin(ujson_lib,'ultrajsonenc.c'),
214-
pjoin(ujson_lib,'ultrajsondec.c'),
362+
parser = pjoin(base, 'parser')
363+
ujson_python = pjoin(base, 'ujson', 'python')
364+
ujson_lib = pjoin(base, 'ujson', 'lib')
365+
self._clean_exclude = [pjoin(dt, 'np_datetime.c'),
366+
pjoin(dt, 'np_datetime_strings.c'),
367+
pjoin(src, 'period_helper.c'),
368+
pjoin(parser, 'tokenizer.c'),
369+
pjoin(parser, 'io.c'),
370+
pjoin(ujson_python, 'ujson.c'),
371+
pjoin(ujson_python, 'objToJSON.c'),
372+
pjoin(ujson_python, 'JSONtoObj.c'),
373+
pjoin(ujson_lib, 'ultrajsonenc.c'),
374+
pjoin(ujson_lib, 'ultrajsondec.c'),
215375
]
216376

217377
for root, dirs, files in os.walk('pandas'):
@@ -252,6 +412,7 @@ def run(self):
252412
# class as it encodes the version info
253413
sdist_class = cmdclass['sdist']
254414

415+
255416
class CheckSDist(sdist_class):
256417
"""Custom sdist that ensures Cython has compiled all pyx files to c."""
257418

@@ -469,13 +630,13 @@ def pxd(name):
469630
extensions.extend([sparse_ext])
470631

471632
testing_ext = Extension('pandas._testing',
472-
sources=[srcpath('testing', suffix=suffix)],
473-
include_dirs=[],
474-
libraries=libraries)
633+
sources=[srcpath('testing', suffix=suffix)],
634+
include_dirs=[],
635+
libraries=libraries)
475636

476637
extensions.extend([testing_ext])
477638

478-
#----------------------------------------------------------------------
639+
# ---------------------------------------------------------------------
479640
# msgpack stuff here
480641

481642
if sys.byteorder == 'big':
@@ -484,31 +645,35 @@ def pxd(name):
484645
macros = [('__LITTLE_ENDIAN__', '1')]
485646

486647
packer_ext = Extension('pandas.msgpack._packer',
487-
depends=['pandas/src/msgpack/pack.h',
488-
'pandas/src/msgpack/pack_template.h'],
489-
sources = [srcpath('_packer',
490-
suffix=suffix if suffix == '.pyx' else '.cpp',
648+
depends=['pandas/src/msgpack/pack.h',
649+
'pandas/src/msgpack/pack_template.h'],
650+
sources=[
651+
srcpath('_packer', suffix=suffix
652+
if suffix == '.pyx' else '.cpp',
491653
subdir='msgpack')],
492-
language='c++',
493-
include_dirs=['pandas/src/msgpack'] + common_include,
494-
define_macros=macros)
654+
language='c++',
655+
include_dirs=['pandas/src/msgpack'] + common_include,
656+
define_macros=macros)
657+
495658
unpacker_ext = Extension('pandas.msgpack._unpacker',
496-
depends=['pandas/src/msgpack/unpack.h',
497-
'pandas/src/msgpack/unpack_define.h',
498-
'pandas/src/msgpack/unpack_template.h'],
499-
sources = [srcpath('_unpacker',
500-
suffix=suffix if suffix == '.pyx' else '.cpp',
501-
subdir='msgpack')],
502-
language='c++',
503-
include_dirs=['pandas/src/msgpack'] + common_include,
504-
define_macros=macros)
659+
depends=['pandas/src/msgpack/unpack.h',
660+
'pandas/src/msgpack/unpack_define.h',
661+
'pandas/src/msgpack/unpack_template.h'],
662+
sources=[
663+
srcpath('_unpacker',
664+
suffix=suffix
665+
if suffix == '.pyx' else '.cpp',
666+
subdir='msgpack')],
667+
language='c++',
668+
include_dirs=['pandas/src/msgpack'] + common_include,
669+
define_macros=macros)
505670
extensions.append(packer_ext)
506671
extensions.append(unpacker_ext)
507672

508673
if suffix == '.pyx' and 'setuptools' in sys.modules:
509674
# undo dumb setuptools bug clobbering .pyx sources back to .c
510675
for ext in extensions:
511-
if ext.sources[0].endswith(('.c','.cpp')):
676+
if ext.sources[0].endswith(('.c', '.cpp')):
512677
root, _ = os.path.splitext(ext.sources[0])
513678
ext.sources[0] = root + suffix
514679

0 commit comments

Comments
 (0)