1#!/usr/bin/env python 2# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- 3# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 4# 5# MDAnalysis --- https://www.mdanalysis.org 6# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors 7# (see the file AUTHORS for the full list of names) 8# 9# Released under the GNU Public Licence, v2 or any higher version 10# 11# Please cite your use of MDAnalysis in published work: 12# 13# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, 14# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. 15# MDAnalysis: A Python package for the rapid analysis of molecular dynamics 16# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th 17# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. 18# 19# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. 20# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. 21# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 22# 23 24"""Setuptools-based setup script for MDAnalysis. 25 26A working installation of NumPy <http://numpy.scipy.org> is required. 27 28For a basic installation just type the command:: 29 30 python setup.py install 31 32For more in-depth instructions, see the installation section at the 33MDAnalysis Wiki: 34 35 https://github.com/MDAnalysis/mdanalysis/wiki/INSTALL 36 37Also free to ask on the MDAnalysis mailing list for help: 38 39 http://groups.google.com/group/mdnalysis-discussion 40 41(Note that the group really is called `mdnalysis-discussion' because 42Google groups forbids any name that contains the string `anal'.) 43""" 44 45from __future__ import print_function 46from setuptools import setup, Extension, find_packages 47from distutils.ccompiler import new_compiler 48import codecs 49import os 50import sys 51import shutil 52import tempfile 53import warnings 54import platform 55 56# Make sure I have the right Python version. 57if sys.version_info[:2] < (2, 7): 58 print('MDAnalysis requires Python 2.7 or better. Python {0:d}.{1:d} detected'.format(* 59 sys.version_info[:2])) 60 print('Please upgrade your version of Python.') 61 sys.exit(-1) 62 63if sys.version_info[0] < 3: 64 import ConfigParser as configparser 65else: 66 import configparser 67 68 69# NOTE: keep in sync with MDAnalysis.__version__ in version.py 70RELEASE = "0.19.2" 71 72is_release = 'dev' not in RELEASE 73 74# Handle cython modules 75try: 76 # cython has to be >=0.16 <0.28 to support cython.parallel 77 import Cython 78 from Cython.Build import cythonize 79 cython_found = True 80 from distutils.version import LooseVersion 81 82 required_version = "0.16" 83 if not LooseVersion(Cython.__version__) >= LooseVersion(required_version): 84 # We don't necessarily die here. Maybe we already have 85 # the cythonized '.c' files. 86 print("Cython version {0} was found but won't be used: version {1} " 87 "or greater is required because it offers a handy " 88 "parallelization module".format( 89 Cython.__version__, required_version)) 90 cython_found = False 91except ImportError: 92 cython_found = False 93 if not is_release: 94 print("*** package: Cython not found ***") 95 print("MDAnalysis requires cython for development builds") 96 sys.exit(1) 97 98 99class Config(object): 100 """Config wrapper class to get build options 101 102 This class looks for options in the environment variables and the 103 'setup.cfg' file. The order how we look for an option is. 104 105 1. Environment Variable 106 2. set in 'setup.cfg' 107 3. given default 108 109 Environment variables should start with 'MDA_' and be all uppercase. 110 Values passed to environment variables are checked (case-insensitively) 111 for specific strings with boolean meaning: 'True' or '1' will cause `True` 112 to be returned. '0' or 'False' cause `False` to be returned. 113 114 """ 115 116 def __init__(self, fname='setup.cfg'): 117 if os.path.exists(fname): 118 self.config = configparser.SafeConfigParser() 119 self.config.read(fname) 120 121 def get(self, option_name, default=None): 122 environ_name = 'MDA_' + option_name.upper() 123 if environ_name in os.environ: 124 val = os.environ[environ_name] 125 if val.upper() in ('1', 'TRUE'): 126 return True 127 elif val.upper() in ('0', 'FALSE'): 128 return False 129 return val 130 try: 131 option = self.config.get('options', option_name) 132 return option 133 except configparser.NoOptionError: 134 return default 135 136 137class MDAExtension(Extension, object): 138 """Derived class to cleanly handle setup-time (numpy) dependencies. 139 """ 140 # The only setup-time numpy dependency comes when setting up its 141 # include dir. 142 # The actual numpy import and call can be delayed until after pip 143 # has figured it must install numpy. 144 # This is accomplished by passing the get_numpy_include function 145 # as one of the include_dirs. This derived Extension class takes 146 # care of calling it when needed. 147 def __init__(self, *args, **kwargs): 148 self._mda_include_dirs = [] 149 super(MDAExtension, self).__init__(*args, **kwargs) 150 151 @property 152 def include_dirs(self): 153 if not self._mda_include_dirs: 154 for item in self._mda_include_dir_args: 155 try: 156 self._mda_include_dirs.append(item()) #The numpy callable 157 except TypeError: 158 self._mda_include_dirs.append(item) 159 return self._mda_include_dirs 160 161 @include_dirs.setter 162 def include_dirs(self, val): 163 self._mda_include_dir_args = val 164 165 166def get_numpy_include(): 167 # Obtain the numpy include directory. This logic works across numpy 168 # versions. 169 # setuptools forgets to unset numpy's setup flag and we get a crippled 170 # version of it unless we do it ourselves. 171 try: 172 # Python 3 renamed the ``__builin__`` module into ``builtins``. 173 # Here we import the python 2 or the python 3 version of the module 174 # with the python 3 name. This could be done with ``six`` but that 175 # module may not be installed at that point. 176 import builtins 177 except ImportError: 178 import __builtin__ as builtins 179 builtins.__NUMPY_SETUP__ = False 180 try: 181 import numpy as np 182 except ImportError: 183 print('*** package "numpy" not found ***') 184 print('MDAnalysis requires a version of NumPy (>=1.10.4), even for setup.') 185 print('Please get it from http://numpy.scipy.org/ or install it through ' 186 'your package manager.') 187 sys.exit(-1) 188 return np.get_include() 189 190 191def hasfunction(cc, funcname, include=None, extra_postargs=None): 192 # From http://stackoverflow.com/questions/ 193 # 7018879/disabling-output-when-compiling-with-distutils 194 tmpdir = tempfile.mkdtemp(prefix='hasfunction-') 195 devnull = oldstderr = None 196 try: 197 try: 198 fname = os.path.join(tmpdir, 'funcname.c') 199 with open(fname, 'w') as f: 200 if include is not None: 201 f.write('#include {0!s}\n'.format(include)) 202 f.write('int main(void) {\n') 203 f.write(' {0!s};\n'.format(funcname)) 204 f.write('}\n') 205 # Redirect stderr to /dev/null to hide any error messages 206 # from the compiler. 207 # This will have to be changed if we ever have to check 208 # for a function on Windows. 209 devnull = open('/dev/null', 'w') 210 oldstderr = os.dup(sys.stderr.fileno()) 211 os.dup2(devnull.fileno(), sys.stderr.fileno()) 212 objects = cc.compile([fname], output_dir=tmpdir, 213 extra_postargs=extra_postargs) 214 cc.link_executable(objects, os.path.join(tmpdir, "a.out")) 215 except Exception: 216 return False 217 return True 218 finally: 219 if oldstderr is not None: 220 os.dup2(oldstderr, sys.stderr.fileno()) 221 if devnull is not None: 222 devnull.close() 223 shutil.rmtree(tmpdir) 224 225 226def detect_openmp(): 227 """Does this compiler support OpenMP parallelization?""" 228 print("Attempting to autodetect OpenMP support... ", end="") 229 compiler = new_compiler() 230 compiler.add_library('gomp') 231 include = '<omp.h>' 232 extra_postargs = ['-fopenmp'] 233 hasopenmp = hasfunction(compiler, 'omp_get_num_threads()', include=include, 234 extra_postargs=extra_postargs) 235 if hasopenmp: 236 print("Compiler supports OpenMP") 237 else: 238 print("Did not detect OpenMP support.") 239 return hasopenmp 240 241 242def extensions(config): 243 # dev installs must build their own cythonized files. 244 use_cython = config.get('use_cython', default=not is_release) 245 use_openmp = config.get('use_openmp', default=True) 246 247 extra_compile_args = ['-std=c99', '-ffast-math', '-funroll-loops'] 248 define_macros = [] 249 if config.get('debug_cflags', default=False): 250 extra_compile_args.extend(['-Wall', '-pedantic']) 251 define_macros.extend([('DEBUG', '1')]) 252 253 # allow using architecture specific instructions. This allows people to 254 # build optimized versions of MDAnalysis. 255 arch = config.get('march', default=False) 256 if arch: 257 extra_compile_args.append('-march={}'.format(arch)) 258 259 cpp_extra_compile_args = [a for a in extra_compile_args if 'std' not in a] 260 cpp_extra_compile_args.append('-std=c++11') 261 # needed to specify c++ runtime library on OSX 262 if platform.system() == 'Darwin': 263 cpp_extra_compile_args.append('-stdlib=libc++') 264 265 # Needed for large-file seeking under 32bit systems (for xtc/trr indexing 266 # and access). 267 largefile_macros = [ 268 ('_LARGEFILE_SOURCE', None), 269 ('_LARGEFILE64_SOURCE', None), 270 ('_FILE_OFFSET_BITS', '64') 271 ] 272 273 has_openmp = detect_openmp() 274 275 if use_openmp and not has_openmp: 276 print('No openmp compatible compiler found default to serial build.') 277 278 parallel_args = ['-fopenmp'] if has_openmp and use_openmp else [] 279 parallel_libraries = ['gomp'] if has_openmp and use_openmp else [] 280 parallel_macros = [('PARALLEL', None)] if has_openmp and use_openmp else [] 281 282 if use_cython: 283 print('Will attempt to use Cython.') 284 if not cython_found: 285 print("Couldn't find a Cython installation. " 286 "Not recompiling cython extensions.") 287 use_cython = False 288 else: 289 print('Will not attempt to use Cython.') 290 291 source_suffix = '.pyx' if use_cython else '.c' 292 cpp_source_suffix = '.pyx' if use_cython else '.cpp' 293 294 # The callable is passed so that it is only evaluated at install time. 295 296 include_dirs = [get_numpy_include] 297 # Windows automatically handles math library linking 298 # and will not build MDAnalysis if we try to specify one 299 if os.name == 'nt': 300 mathlib = [] 301 else: 302 mathlib = ['m'] 303 304 libdcd = MDAExtension('MDAnalysis.lib.formats.libdcd', 305 ['MDAnalysis/lib/formats/libdcd' + source_suffix], 306 include_dirs=include_dirs + ['MDAnalysis/lib/formats/include'], 307 define_macros=define_macros, 308 extra_compile_args=extra_compile_args) 309 distances = MDAExtension('MDAnalysis.lib.c_distances', 310 ['MDAnalysis/lib/c_distances' + source_suffix], 311 include_dirs=include_dirs + ['MDAnalysis/lib/include'], 312 libraries=mathlib, 313 define_macros=define_macros, 314 extra_compile_args=extra_compile_args) 315 distances_omp = MDAExtension('MDAnalysis.lib.c_distances_openmp', 316 ['MDAnalysis/lib/c_distances_openmp' + source_suffix], 317 include_dirs=include_dirs + ['MDAnalysis/lib/include'], 318 libraries=mathlib + parallel_libraries, 319 define_macros=define_macros + parallel_macros, 320 extra_compile_args=parallel_args + extra_compile_args, 321 extra_link_args=parallel_args) 322 qcprot = MDAExtension('MDAnalysis.lib.qcprot', 323 ['MDAnalysis/lib/qcprot' + source_suffix], 324 include_dirs=include_dirs, 325 define_macros=define_macros, 326 extra_compile_args=extra_compile_args) 327 transformation = MDAExtension('MDAnalysis.lib._transformations', 328 ['MDAnalysis/lib/src/transformations/transformations.c'], 329 libraries=mathlib, 330 define_macros=define_macros, 331 include_dirs=include_dirs, 332 extra_compile_args=extra_compile_args) 333 libmdaxdr = MDAExtension('MDAnalysis.lib.formats.libmdaxdr', 334 sources=['MDAnalysis/lib/formats/libmdaxdr' + source_suffix, 335 'MDAnalysis/lib/formats/src/xdrfile.c', 336 'MDAnalysis/lib/formats/src/xdrfile_xtc.c', 337 'MDAnalysis/lib/formats/src/xdrfile_trr.c', 338 'MDAnalysis/lib/formats/src/trr_seek.c', 339 'MDAnalysis/lib/formats/src/xtc_seek.c', 340 ], 341 include_dirs=include_dirs + ['MDAnalysis/lib/formats/include', 342 'MDAnalysis/lib/formats'], 343 define_macros=largefile_macros + define_macros, 344 extra_compile_args=extra_compile_args) 345 util = MDAExtension('MDAnalysis.lib.formats.cython_util', 346 sources=['MDAnalysis/lib/formats/cython_util' + source_suffix], 347 include_dirs=include_dirs, 348 define_macros=define_macros, 349 extra_compile_args=extra_compile_args) 350 cutil = MDAExtension('MDAnalysis.lib._cutil', 351 sources=['MDAnalysis/lib/_cutil' + cpp_source_suffix], 352 language='c++', 353 libraries=mathlib, 354 include_dirs=include_dirs + ['MDAnalysis/lib/include'], 355 define_macros=define_macros, 356 extra_compile_args=cpp_extra_compile_args) 357 augment = MDAExtension('MDAnalysis.lib._augment', 358 sources=['MDAnalysis/lib/_augment' + cpp_source_suffix], 359 language='c++', 360 include_dirs=include_dirs, 361 define_macros=define_macros, 362 extra_compile_args=cpp_extra_compile_args) 363 364 365 encore_utils = MDAExtension('MDAnalysis.analysis.encore.cutils', 366 sources=['MDAnalysis/analysis/encore/cutils' + source_suffix], 367 include_dirs=include_dirs, 368 define_macros=define_macros, 369 extra_compile_args=extra_compile_args) 370 ap_clustering = MDAExtension('MDAnalysis.analysis.encore.clustering.affinityprop', 371 sources=['MDAnalysis/analysis/encore/clustering/affinityprop' + source_suffix, 372 'MDAnalysis/analysis/encore/clustering/src/ap.c'], 373 include_dirs=include_dirs+['MDAnalysis/analysis/encore/clustering/include'], 374 libraries=mathlib, 375 define_macros=define_macros, 376 extra_compile_args=extra_compile_args) 377 spe_dimred = MDAExtension('MDAnalysis.analysis.encore.dimensionality_reduction.stochasticproxembed', 378 sources=['MDAnalysis/analysis/encore/dimensionality_reduction/stochasticproxembed' + source_suffix, 379 'MDAnalysis/analysis/encore/dimensionality_reduction/src/spe.c'], 380 include_dirs=include_dirs+['MDAnalysis/analysis/encore/dimensionality_reduction/include'], 381 libraries=mathlib, 382 define_macros=define_macros, 383 extra_compile_args=extra_compile_args) 384 nsgrid = MDAExtension('MDAnalysis.lib.nsgrid', 385 ['MDAnalysis/lib/nsgrid' + cpp_source_suffix], 386 include_dirs=include_dirs, 387 language='c++', 388 define_macros=define_macros, 389 extra_compile_args=cpp_extra_compile_args) 390 pre_exts = [libdcd, distances, distances_omp, qcprot, 391 transformation, libmdaxdr, util, encore_utils, 392 ap_clustering, spe_dimred, cutil, augment, nsgrid] 393 394 395 cython_generated = [] 396 if use_cython: 397 extensions = cythonize(pre_exts) 398 for pre_ext, post_ext in zip(pre_exts, extensions): 399 for source in post_ext.sources: 400 if source not in pre_ext.sources: 401 cython_generated.append(source) 402 else: 403 #Let's check early for missing .c files 404 extensions = pre_exts 405 for ext in extensions: 406 for source in ext.sources: 407 if not (os.path.isfile(source) and 408 os.access(source, os.R_OK)): 409 raise IOError("Source file '{}' not found. This might be " 410 "caused by a missing Cython install, or a " 411 "failed/disabled Cython build.".format(source)) 412 return extensions, cython_generated 413 414 415def dynamic_author_list(): 416 """Generate __authors__ from AUTHORS 417 418 This function generates authors.py that contains the list of the 419 authors from the AUTHORS file. This avoids having that list maintained in 420 several places. Note that AUTHORS is sorted chronologically while we want 421 __authors__ in authors.py to be sorted alphabetically. 422 423 The authors are written in AUTHORS as bullet points under the 424 "Chronological list of authors" title. 425 """ 426 authors = [] 427 with codecs.open('AUTHORS', encoding='utf-8') as infile: 428 # An author is a bullet point under the title "Chronological list of 429 # authors". We first want move the cursor down to the title of 430 # interest. 431 for line_no, line in enumerate(infile, start=1): 432 if line.rstrip() == "Chronological list of authors": 433 break 434 else: 435 # If we did not break, it means we did not find the authors. 436 raise IOError('EOF before the list of authors') 437 # Skip the next line as it is the title underlining 438 line = next(infile) 439 line_no += 1 440 if line[:4] != '----': 441 raise IOError('Unexpected content on line {0}, ' 442 'should be a string of "-".'.format(line_no)) 443 # Add each bullet point as an author until the next title underlining 444 for line in infile: 445 if line[:4] in ('----', '====', '~~~~'): 446 # The previous line was a title, hopefully it did not start as 447 # a bullet point so it got ignored. Since we hit a title, we 448 # are done reading the list of authors. 449 break 450 elif line.strip()[:2] == '- ': 451 # This is a bullet point, so it should be an author name. 452 name = line.strip()[2:].strip() 453 authors.append(name) 454 455 # So far, the list of authors is sorted chronologically. We want it 456 # sorted alphabetically of the last name. 457 authors.sort(key=lambda name: name.split()[-1]) 458 # Move Naveen and Elizabeth first, and Oliver last. 459 authors.remove('Naveen Michaud-Agrawal') 460 authors.remove('Elizabeth J. Denning') 461 authors.remove('Oliver Beckstein') 462 authors = (['Naveen Michaud-Agrawal', 'Elizabeth J. Denning'] 463 + authors + ['Oliver Beckstein']) 464 465 # Write the authors.py file. 466 out_path = 'MDAnalysis/authors.py' 467 with codecs.open(out_path, 'w', encoding='utf-8') as outfile: 468 # Write the header 469 header = '''\ 470#-*- coding:utf-8 -*- 471 472# This file is generated from the AUTHORS file during the installation process. 473# Do not edit it as your changes will be overwritten. 474''' 475 print(header, file=outfile) 476 477 # Write the list of authors as a python list 478 template = u'__authors__ = [\n{}\n]' 479 author_string = u',\n'.join(u' u"{}"'.format(name) 480 for name in authors) 481 print(template.format(author_string), file=outfile) 482 483 484if __name__ == '__main__': 485 try: 486 dynamic_author_list() 487 except (OSError, IOError): 488 warnings.warn('Cannot write the list of authors.') 489 490 with open("SUMMARY.txt") as summary: 491 LONG_DESCRIPTION = summary.read() 492 CLASSIFIERS = [ 493 'Development Status :: 4 - Beta', 494 'Environment :: Console', 495 'Intended Audience :: Science/Research', 496 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', 497 'Operating System :: POSIX', 498 'Operating System :: MacOS :: MacOS X', 499 'Operating System :: Microsoft :: Windows ', 500 'Programming Language :: Python', 501 'Programming Language :: Python :: 2', 502 'Programming Language :: Python :: 2.7', 503 'Programming Language :: Python :: 3', 504 'Programming Language :: Python :: 3.4', 505 'Programming Language :: Python :: 3.5', 506 'Programming Language :: Python :: 3.6', 507 'Programming Language :: C', 508 'Topic :: Scientific/Engineering', 509 'Topic :: Scientific/Engineering :: Bio-Informatics', 510 'Topic :: Scientific/Engineering :: Chemistry', 511 'Topic :: Software Development :: Libraries :: Python Modules', 512 ] 513 config = Config() 514 exts, cythonfiles = extensions(config) 515 516 install_requires = [ 517 'numpy>=1.10.4', 518 'biopython>=1.71', 519 'networkx>=1.0', 520 'GridDataFormats>=0.4.0', 521 'six>=1.4.0', 522 'mmtf-python>=1.0.0', 523 'joblib', 524 'scipy>=1.0.0', 525 'matplotlib>=1.5.1', 526 'mock', 527 ] 528 if not os.name == 'nt': 529 install_requires.append('gsd>=1.4.0') 530 531 setup(name='MDAnalysis', 532 version=RELEASE, 533 description=('An object-oriented toolkit to analyze molecular dynamics ' 534 'trajectories generated by CHARMM, Gromacs, NAMD, LAMMPS, or Amber.'), 535 long_description=LONG_DESCRIPTION, 536 long_description_content_type='text/x-rst', 537 author='Naveen Michaud-Agrawal', 538 author_email='naveen.michaudagrawal@gmail.com', 539 maintainer='Richard Gowers', 540 maintainer_email='mdnalysis-discussion@googlegroups.com', 541 url='https://www.mdanalysis.org', 542 download_url='https://github.com/MDAnalysis/mdanalysis/releases', 543 project_urls={'Documentation': 'https://www.mdanalysis.org/docs/', 544 'Issue Tracker': 'https://github.com/mdanalysis/mdanalysis/issues', 545 'User Group': 'https://groups.google.com/forum/#!forum/mdnalysis-discussion', 546 'Source': 'https://github.com/mdanalysis/mdanalysis', 547 }, 548 license='GPL 2', 549 classifiers=CLASSIFIERS, 550 provides=['MDAnalysis'], 551 packages=find_packages(), 552 package_data={'MDAnalysis': 553 ['analysis/data/*.npy', 554 ], 555 }, 556 ext_modules=exts, 557 requires=['numpy (>=1.10.4)', 'biopython (>= 1.71)', 'mmtf (>=1.0.0)', 558 'networkx (>=1.0)', 'GridDataFormats (>=0.3.2)', 'joblib', 559 'scipy (>=1.0.0)', 'matplotlib (>=1.5.1)'], 560 # all standard requirements are available through PyPi and 561 # typically can be installed without difficulties through setuptools 562 setup_requires=[ 563 'numpy>=1.10.4', 564 ], 565 install_requires=install_requires, 566 # extras can be difficult to install through setuptools and/or 567 # you might prefer to use the version available through your 568 # packaging system 569 extras_require={ 570 'AMBER': [ 571 'netCDF4>=1.0', # for fast AMBER writing, also needs HDF5 572 ], 573 'analysis': [ 574 'seaborn', # for annotated heat map and nearest neighbor 575 # plotting in PSA 576 'sklearn', # For clustering and dimensionality reduction 577 # functionality in encore 578 ], 579 }, 580 test_suite="MDAnalysisTests", 581 tests_require=[ 582 'MDAnalysisTests=={0!s}'.format(RELEASE), # same as this release! 583 ], 584 zip_safe=False, # as a zipped egg the *.so files are not found (at 585 # least in Ubuntu/Linux) 586 ) 587 588 # Releases keep their cythonized stuff for shipping. 589 if not config.get('keep_cythonized', default=is_release): 590 for cythonized in cythonfiles: 591 try: 592 os.unlink(cythonized) 593 except OSError as err: 594 print("Warning: failed to delete cythonized file {0}: {1}. " 595 "Moving on.".format(cythonized, err.strerror)) 596