-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathsetup.py
More file actions
332 lines (282 loc) · 12.3 KB
/
setup.py
File metadata and controls
332 lines (282 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/usr/bin/env python
"""Build and install gmx Python module against an existing Gromacs install.
Build and install libgromacs and libgmxapi, then
gmxapi_DIR=/path/to/gromacs python setup.py install --user
or
pip install path/to/setup_py_dir --user
Note that setup.py by itself doesn't like to be run from other directories than the one in which
it is located. In general, just use pip. If you don't want to use pip, just plan on cluttering your
source directory. Sorry.
"""
import os
import platform
import subprocess
import sys
from warnings import warn
import setuptools
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
from setuptools.command.test import test as TestCommand
#import gmx.version
__version__ = '0.0.6'
extra_link_args=[]
# readthedocs.org isn't very specific about promising any particular value...
build_for_readthedocs = False
if os.getenv('READTHEDOCS') is not None:
build_for_readthedocs = True
if os.getenv('BUILDGROMACS') is not None:
build_gromacs = True
else:
if build_for_readthedocs:
build_gromacs = True
else:
build_gromacs = False
def get_gromacs(url, cmake_args=[], build_args=[]):
"""Download, build, and install a local copy of gromacs to a temporary location.
"""
try:
import ssl
except:
warn("get_gromacs needs ssl support, but `import ssl` fails")
raise
try:
from urllib.request import urlopen
except:
from urllib2 import urlopen
import tempfile
# import tarfile
import zipfile
import shutil
# make temporary source directory
sourcedir = tempfile.mkdtemp()
try:
with tempfile.TemporaryFile(suffix='.zip', dir=sourcedir) as fh:
fh.write(urlopen(url).read())
fh.seek(0)
# archive = tarfile.open(fileobj=fh)
archive = zipfile.ZipFile(fh)
# # Get top-level directory name in archive
# root = archive.next().name
root = archive.namelist()[0]
# Extract all under top-level to source directory
archive.extractall(path=sourcedir)
except:
shutil.rmtree(sourcedir, ignore_errors=True)
raise
# make temporary build directory
build_temp = tempfile.mkdtemp()
# run CMake to configure with installation directory in extension staging area
env = os.environ.copy()
try:
import cmake
cmake_bin = os.path.join(cmake.CMAKE_BIN_DIR, 'cmake')
except:
raise
try:
subprocess.check_call([cmake_bin, os.path.join(sourcedir, root)] + cmake_args, cwd=build_temp, env=env)
except:
warn("Not removing source directory {} or build directory {}".format(sourcedir, build_temp))
raise
# run CMake to build and install
try:
subprocess.check_call([cmake_bin, '--build', '.', '--target', 'install'] + build_args, cwd=build_temp)
except:
warn("Not removing source directory {} or build directory {}".format(sourcedir, build_temp))
raise
shutil.rmtree(build_temp, ignore_errors=True)
shutil.rmtree(sourcedir, ignore_errors=True)
class Tox(TestCommand):
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
#import here, cause outside the eggs aren't loaded
import tox
errcode = tox.cmdline(self.test_args)
sys.exit(errcode)
# As of Python 3.6, CCompiler has a `has_flag` method.
# cf http://bugs.python.org/issue26689
def has_flag(compiler, flagname):
"""Return a boolean indicating whether a flag name is supported on
the specified compiler.
"""
import tempfile
with tempfile.NamedTemporaryFile('w', suffix='.cpp') as f:
f.write('int main (int argc, char **argv) { return 0; }')
try:
compiler.compile([f.name], extra_postargs=[flagname])
except setuptools.distutils.errors.CompileError:
return False
return True
def cpp_flag(compiler):
"""Return the -std=c++[11/14] compiler flag.
The c++14 is prefered over c++11 (when it is available).
"""
# if has_flag(compiler, '-std=c++14'):
if False and has_flag(compiler, '-std=c++14'):
return '-std=c++14'
elif has_flag(compiler, '-std=c++11'):
return '-std=c++11'
else:
raise RuntimeError('Unsupported compiler -- at least C++11 support '
'is needed!')
class CMakeGromacsBuild(build_ext):
def run(self):
try:
import cmake
cmake_bin = os.path.join(cmake.CMAKE_BIN_DIR, 'cmake')
except:
raise
try:
out = subprocess.check_output([cmake_bin, '--version'])
except OSError:
raise RuntimeError("CMake must be installed to build the following extensions: " +
", ".join(e.name for e in self.extensions))
for ext in self.extensions:
self.build_extension(ext)
def build_extension(self, ext):
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
# Note distutils prints extra output if DISTUTILS_DEBUG environement variable is set.
# The setup.py build command allows a --debug flag that will have set self.debug
# cfg = 'Debug' if self.debug, else 'Release'
cfg = 'Release'
if self.debug:
cfg = 'Debug'
build_args = ['--config', cfg]
cmake_args = ['-DPYTHON_EXECUTABLE=' + sys.executable,
]
env = os.environ.copy()
if 'CC' in env:
cmake_args.append("-DCMAKE_C_COMPILER={}".format(env['CC']))
if 'CXX' in env:
cmake_args.append("-DCMAKE_CXX_COMPILER={}".format(env['CXX']))
if platform.system() == "Windows":
if sys.maxsize > 2**32:
cmake_args += ['-A', 'x64']
build_args += ['--', '/m']
else:
cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
if build_for_readthedocs:
# save some RAM
# We're pushing the limits of the readthedocs build host provisions. We might soon need
# a binary package or mock library for libgmxapi / libgromacs.
build_args += ['--', '-j2']
else:
build_args += ['--', '-j8']
# Find installed GROMACS
GROMACS_DIR = os.getenv('GROMACS_DIR')
if GROMACS_DIR is None:
gmxapi_DIR = os.getenv('gmxapi_DIR')
if gmxapi_DIR is not None:
GROMACS_DIR = gmxapi_DIR
else:
GROMACS_DIR = ""
# Build and install a private copy of GROMACS, if necessary.
# This could be replaced with a pypi gromacs bundle. We also may prefer to move to scikit-build.
# Refer to the 'cmake' pypi package for a simple example of bundling a non-Python package to satisfy a dependency.
# There is no caching: gromacs is downloaded and rebuilt each time. On readthedocs that should be okay since
# libgmxapi is likely to update more frequently than gmxpy.
# Linking is a pain because the package is relocated to the site-packages directory. We should really do this
# in two stages.
if build_gromacs:
# TODO! We need to distinguish dev branch builds from master branch builds or always build with the
# master branch of the dependency. For one thing, as is, this line needs to be toggled for every release.
gromacs_url = "https://github.com/kassonlab/gromacs-gmxapi/archive/devel.zip"
gmxapi_DIR = os.path.join(extdir, 'data/gromacs')
if build_for_readthedocs:
extra_cmake_args = ['-DCMAKE_INSTALL_PREFIX=' + gmxapi_DIR,
'-DGMX_FFT_LIBRARY=fftpack',
'-DGMX_GPU=OFF',
'-DGMX_OPENMP=OFF',
'-DGMX_SIMD=None',
'-DGMX_USE_RDTSCP=OFF',
'-DGMX_MPI=OFF']
else:
extra_cmake_args = ['-DCMAKE_INSTALL_PREFIX=' + gmxapi_DIR,
'-DGMX_BUILD_OWN_FFTW=ON',
'-DGMX_GPU=OFF',
'-DGMX_THREAD_MPI=ON']
# Warning: make sure not to recursively build the Python module...
get_gromacs(gromacs_url,
cmake_args + extra_cmake_args,
build_args)
GROMACS_DIR = gmxapi_DIR
env['GROMACS_DIR'] = GROMACS_DIR
#
staging_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gmx')
print("__file__ is {} at {}".format(__file__, os.path.abspath(__file__)))
# Compiled library will be put directly into extdir by CMake
print("extdir is {}".format(extdir))
print("staging_dir is {}".format(staging_dir))
# CMake will be run in working directory build_temp
print("build_temp is {}".format(self.build_temp))
if not os.path.exists(self.build_temp):
os.makedirs(self.build_temp)
# cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir]
cmake_args += ['-DGMXAPI_INSTALL_PATH=' + extdir]
# if platform.system() == "Windows":
# cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
try:
import cmake
cmake_bin = os.path.join(cmake.CMAKE_BIN_DIR, 'cmake')
except:
raise
cmake_command = [cmake_bin, ext.sourcedir] + cmake_args
print("Calling CMake: {}".format(' '.join(cmake_command)))
subprocess.check_call(cmake_command, cwd=self.build_temp, env=env)
cmake_command = [cmake_bin, '--build', '.', '--target', 'install'] + build_args
print("Calling CMake: {}".format(' '.join(cmake_command)))
subprocess.check_call(cmake_command, cwd=self.build_temp)
class CMakeExtension(Extension):
def __init__(self, name, sourcedir=''):
# We are not relying on distutils to build, so we don't give any sources to Extension
Extension.__init__(self, name, sources=[])
# but we will use the sourcedir when our overridden build_extension calls cmake.
self.sourcedir = os.path.abspath(sourcedir)
package_dir=os.path.join('src','gmx')
# For better or worse, it seems pretty common for distutils to freely stomp around in the source directory during builds.
with open(os.path.join(package_dir, 'version.py'), 'w') as fh:
fh.write("# Version file generated by setup.py\n")
fh.write("__version__ = '{}'\n".format(__version__))
major, minor, patch = __version__.split('.')
fh.write("major = {}\n".format(major))
fh.write("minor = {}\n".format(minor))
fh.write("patch = {}\n".format(patch))
fh.write("def api_is_at_least(a, b, c):\n")
fh.write(" return (major >= a) and (minor >= b) and (patch >= c)\n\n")
fh.write("release = False\n")
package_data = {
'gmx': ['data/topol.tpr'],
}
if build_gromacs:
package_data['gmx'].append('data/gromacs')
setup(
name='gmx',
packages=['gmx', 'gmx.test'],
package_dir = {'gmx': package_dir},
version=__version__,
# Require Python 2.7 or 3.3+
python_requires = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4',
# If cmake package causes weird build errors like "missing skbuild", try uninstalling and reinstalling the cmake
# package with pip in the current (virtual) environment: `pip uninstall cmake; pip install cmake`
setup_requires=['setuptools>=28', 'scikit-build', 'cmake'],
#install_requires=['docutils', 'cmake', 'sphinx_rtd_theme'],
# optional targets:
# docs requires 'docutils', 'sphinx>=1.4', 'sphinx_rtd_theme'
# build_gromacs requires 'cmake>=3.4'
install_requires=['setuptools>=28', 'scikit-build', 'cmake', 'networkx'],
author='M. Eric Irrgang',
author_email='ericirrgang@gmail.com',
description='GROMACS Python module',
license = 'LGPL',
url = 'https://bitbucket.org/kassonlab/gmxpy',
#keywords = '',
ext_modules = [CMakeExtension(
'gmx.core'
)],
# Bundle some files needed for testing
package_data = package_data,
cmdclass={'build_ext': CMakeGromacsBuild},
zip_safe=False
)