1import os
2import os.path
3import platform
4import sys
5from distutils.core import Command
6from glob import glob
7
8from setuptools import (
9    Extension,
10    setup,
11)
12from setuptools.command.sdist import sdist
13
14
15def main():
16    metadata = dict(
17        scripts=glob("scripts/*.py"),
18        cmdclass=command_classes)
19
20    if len(sys.argv) >= 2 and \
21            ('--help' in sys.argv[1:] or sys.argv[1] in ('--help-commands', 'egg_info', '--version', 'clean')):
22        # For these actions, NumPy is not required.
23        #
24        # They are required to succeed without Numpy for example when
25        # pip is used to install when Numpy is not yet present in
26        # the system.
27        pass
28    else:
29        try:
30            import numpy
31            # Suppress numpy tests
32            numpy.test = None
33        except Exception as e:
34            raise Exception(f"numpy must be installed to build: {e}")
35        metadata['ext_modules'] = get_extension_modules(numpy_include=numpy.get_include())
36
37    setup(**metadata)
38
39
40# ---- Commands -------------------------------------------------------------
41
42# Use build_ext from Cython if found
43command_classes = {}
44try:
45    import Cython.Distutils
46    command_classes['build_ext'] = Cython.Distutils.build_ext
47
48    class build_ext_sdist(sdist):
49        def run(self):
50            # Make sure the compiled Cython files in the distribution are up-to-date
51            self.run_command("build_ext")
52            super().run()
53
54    command_classes['sdist'] = build_ext_sdist
55except ImportError:
56    pass
57
58# Use epydoc if found
59try:
60    import epydoc.cli
61
62    # Create command class to build API documentation
63    class BuildAPIDocs(Command):
64        user_options = []
65
66        def initialize_options(self):
67            pass
68
69        def finalize_options(self):
70            pass
71
72        def run(self):
73            # Save working directory and args
74            old_argv = sys.argv
75            old_cwd = os.getcwd()
76            # Build command line for Epydoc
77            sys.argv = """epydoc.py bx --verbose --html --simple-term
78                                       --exclude=._
79                                       --exclude=_tests
80                                       --docformat=reStructuredText
81                                       --output=../doc/docbuild/html/apidoc""".split()
82            # Make output directory
83            if not os.path.exists("./doc/docbuild/html/apidoc"):
84                os.mkdir("./doc/docbuild/html/apidoc")
85            # Move to lib directory (so bx package is in current directory)
86            os.chdir("./lib")
87            # Invoke epydoc
88            epydoc.cli.cli()
89            # Restore args and working directory
90            sys.argv = old_argv
91            os.chdir(old_cwd)
92    # Add to extra_commands
93    command_classes['build_apidocs'] = BuildAPIDocs
94except Exception:
95    pass
96
97# ---- Extension Modules ----------------------------------------------------
98
99# # suppress C++ #warning, e.g., to silence NumPy deprecation warnings:
100# from functools import partial
101# _Extension = Extension
102# Extension = partial(_Extension, extra_compile_args=["-Wno-cpp"])
103
104
105def get_extension_modules(numpy_include=None):
106    extensions = []
107    # Bitsets
108    extensions.append(Extension("bx.bitset",
109                                ["lib/bx/bitset.pyx",
110                                 "src/binBits.c",
111                                 "src/kent/bits.c",
112                                 "src/kent/common.c"],
113                                include_dirs=["src/kent", "src"]))
114    # Interval intersection
115    extensions.append(Extension("bx.intervals.intersection", ["lib/bx/intervals/intersection.pyx"]))
116    # Alignment object speedups
117    extensions.append(Extension("bx.align._core", ["lib/bx/align/_core.pyx"]))
118    # NIB reading speedups
119    extensions.append(Extension("bx.seq._nib", ["lib/bx/seq/_nib.pyx"]))
120    # 2bit reading speedups
121    extensions.append(Extension("bx.seq._twobit", ["lib/bx/seq/_twobit.pyx"]))
122    # Translation if character / integer strings
123    extensions.append(Extension("bx._seqmapping", ["lib/bx/_seqmapping.pyx"]))
124    # BGZF
125    extensions.append(Extension("bx.misc.bgzf",
126                                ["lib/bx/misc/bgzf.pyx", "src/samtools/bgzf.c"],
127                                include_dirs=["src/samtools"],
128                                libraries=['z']))
129
130    # The following extensions won't (currently) compile on windows
131    if platform.system() not in ('Microsoft', 'Windows'):
132        # Interval clustering
133        extensions.append(Extension("bx.intervals.cluster",
134                                    ["lib/bx/intervals/cluster.pyx",
135                                     "src/cluster.c"],
136                                    include_dirs=["src"]))
137        # Position weight matrices
138        extensions.append(Extension("bx.pwm._position_weight_matrix",
139                                    ["lib/bx/pwm/_position_weight_matrix.pyx", "src/pwm_utils.c"],
140                                    include_dirs=["src"]))
141
142        extensions.append(Extension("bx.motif._pwm", ["lib/bx/motif/_pwm.pyx"],
143                                    include_dirs=[numpy_include]))
144
145        # Sparse arrays with summaries organized as trees on disk
146        extensions.append(Extension("bx.arrays.array_tree", ["lib/bx/arrays/array_tree.pyx"], include_dirs=[numpy_include]))
147
148        # Reading UCSC "big binary index" files
149        extensions.append(Extension("bx.bbi.bpt_file", ["lib/bx/bbi/bpt_file.pyx"]))
150        extensions.append(Extension("bx.bbi.cirtree_file", ["lib/bx/bbi/cirtree_file.pyx"]))
151        extensions.append(Extension("bx.bbi.bbi_file", ["lib/bx/bbi/bbi_file.pyx"], include_dirs=[numpy_include]))
152        extensions.append(Extension("bx.bbi.bigwig_file", ["lib/bx/bbi/bigwig_file.pyx"], include_dirs=[numpy_include]))
153        extensions.append(Extension("bx.bbi.bigbed_file", ["lib/bx/bbi/bigbed_file.pyx"], include_dirs=[numpy_include]))
154
155        # EPO and Chain arithmetics and IO speedups
156        extensions.append(Extension("bx.align._epo", ["lib/bx/align/_epo.pyx"], include_dirs=[numpy_include]))
157
158        # Reading UCSC bed and wiggle formats
159        extensions.append(Extension("bx.arrays.bed", ["lib/bx/arrays/bed.pyx"]))
160        extensions.append(Extension("bx.arrays.wiggle", ["lib/bx/arrays/wiggle.pyx"]))
161
162        # CpG masking
163        extensions.append(Extension("bx.align.sitemask._cpg",
164                                    ["lib/bx/align/sitemask/_cpg.pyx",
165                                     "lib/bx/align/sitemask/find_cpg.c"]))
166
167        # Counting n-grams in integer strings
168        extensions.append(Extension("bx.intseq.ngramcount", ["lib/bx/intseq/ngramcount.pyx"],
169                                    include_dirs=["src"]))
170
171        # Seekable access to bzip2 files
172        extensions.append(Extension("bx.misc._seekbzip2",
173                                    ["lib/bx/misc/_seekbzip2.pyx",
174                                     "src/bunzip/micro-bunzip.c"],
175                                    include_dirs=["src/bunzip"]))
176    return extensions
177
178
179if __name__ == "__main__":
180    main()
181