setup.py 8.03 KB
Newer Older
1
"""setup for the dlib project
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
2
3
 Copyright (C) 2015  Ehsan Azar (dashesy@linux.com)
 License: Boost Software License   See LICENSE.txt for the full license.
4

5
6
7
8
This file basically just uses CMake to compile the dlib python bindings project
located in the tools/python folder and then puts the outputs into standard
python packages.

9
10
11
12
To build the dlib:
    python setup.py build
To build and install:
    python setup.py install
13
To package the wheel (after pip installing twine and wheel):
14
    python setup.py bdist_wheel
Davis King's avatar
Davis King committed
15
To upload the binary wheel to PyPi
16
    twine upload dist/*.whl
Davis King's avatar
Davis King committed
17
18
To upload the source distribution to PyPi
    python setup.py sdist upload
19
20
To exclude/include certain options in the cmake config use --yes and --no:
    for example:
21
22
    --yes USE_AVX_INSTRUCTIONS: will set -DUSE_AVX_INSTRUCTIONS=yes
    --no USE_AVX_INSTRUCTIONS: will set -DUSE_AVX_INSTRUCTIONS=no
23
Additional options:
24
    --compiler-flags: pass flags onto the compiler, e.g. --compiler-flag "-Os -Wall" passes -Os -Wall onto GCC.
25
26
27
    --clean: delete any previous build folders and rebuild.  You should do this if you change any build options
             by setting --compiler-flags or --yes or --no since last time you ran a build to make sure the changes
             take effect.
28
29
"""
import os
30
import re
31
import sys
32
import shutil
33
import platform
34
import subprocess
35
import multiprocessing
36
from distutils import log
37
from math import ceil
38

39
40
41
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
from distutils.version import LooseVersion
42

43

44
45
def get_extra_cmake_options():
    """read --clean, --yes, --no, and --compiler-flag options from the command line and add them as cmake switches.
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
46
    """
47
48
    _cmake_extra_options = []
    _clean_build_folder = False
49

Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
50
    opt_key = None
51

52
    argv = [arg for arg in sys.argv]  # take a copy
53
    # parse command line options and consume those we care about
54
    for opt_idx, arg in enumerate(argv):
55
56
        if opt_key == 'compiler-flags':
            _cmake_extra_options.append('-DCMAKE_CXX_FLAGS={arg}'.format(arg=arg.strip()))
57
        elif opt_key == 'yes':
58
            _cmake_extra_options.append('-D{arg}=yes'.format(arg=arg.strip()))
59
        elif opt_key == 'no':
60
            _cmake_extra_options.append('-D{arg}=no'.format(arg=arg.strip()))
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
61
62
63

        if opt_key:
            sys.argv.remove(arg)
64
            opt_key = None
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
65
            continue
66

67
68
        if arg == '--clean':
            _clean_build_folder = True
69
70
71
            sys.argv.remove(arg)
            continue

72
73
        if arg in ['--yes', '--no', '--compiler-flags']:
            opt_key = arg[2:].lower()
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
74
75
            sys.argv.remove(arg)
            continue
76

77
    return _cmake_extra_options, _clean_build_folder
78

79
cmake_extra_options,clean_build_folder = get_extra_cmake_options()
80
81


82
83
84
85
class CMakeExtension(Extension):
    def __init__(self, name, sourcedir=''):
        Extension.__init__(self, name, sources=[])
        self.sourcedir = os.path.abspath(sourcedir)
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
86

87
88
89
90
91
92
93
94
95
96
def rmtree(name):
    """remove a directory and its subdirectories.
    """
    def remove_read_only(func, path, exc):
        excvalue = exc[1]
        if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
            os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
            func(path)
        else:
            raise
97

98
99
100
    if os.path.exists(name):
        log.info('Removing old directory {}'.format(name))
        shutil.rmtree(name, ignore_errors=False, onerror=remove_read_only)
101
102


103
class CMakeBuild(build_ext):
104

105
    def get_cmake_version(self):
106
        try:
107
            out = subprocess.check_output(['cmake', '--version'])
108
        except OSError:
109
110
111
            raise RuntimeError("CMake must be installed to build the following extensions: " +
                               ", ".join(e.name for e in self.extensions))
        return re.search(r'version\s*([\d.]+)', out.decode()).group(1)
112

113
114
115
116
    def run(self):
        if platform.system() == "Windows":
            if LooseVersion(self.get_cmake_version()) < '3.1.0':
                raise RuntimeError("CMake >= 3.1.0 is required on Windows")
117

118
119
        for ext in self.extensions:
            self.build_extension(ext)
120

121
122
    def build_extension(self, ext):
        extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
123

124
125
        cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
                      '-DPYTHON_EXECUTABLE=' + sys.executable]
126

127
        cmake_args += cmake_extra_options 
128

129
130
        cfg = 'Debug' if self.debug else 'Release'
        build_args = ['--config', cfg]
131

132
133
134
135
136
137
138
        if platform.system() == "Windows":
            cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
            if sys.maxsize > 2**32:
                cmake_args += ['-A', 'x64']
            build_args += ['--', '/m']
        else:
            cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
139
140
141
            # Do a parallel build
            num_cores = int(ceil(multiprocessing.cpu_count()/2.0)) 
            build_args += ['--', '-j'+str(num_cores)]
142

143
        build_folder = os.path.abspath(self.build_temp)
144

145
146
147
148
        if clean_build_folder:
            rmtree(build_folder)
        if not os.path.exists(build_folder):
            os.makedirs(build_folder)
149

Davis King's avatar
Davis King committed
150
        print("Invoking CMake setup: '{}'".format(['cmake', ext.sourcedir] + cmake_args))
151
        subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=build_folder)
Davis King's avatar
Davis King committed
152
        print("Invoking CMake build: '{}'".format(['cmake', '--build', '.'] + build_args))
153
        subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=build_folder)
154
155


156
157
158
from setuptools.command.test import test as TestCommand
class PyTest(TestCommand):
    user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")]
159

160
161
162
    def initialize_options(self):
        TestCommand.initialize_options(self)
        self.pytest_args = ''
163

164
165
166
167
168
169
    def run_tests(self):
        import shlex
        #import here, cause outside the eggs aren't loaded
        import pytest
        errno = pytest.main(shlex.split(self.pytest_args))
        sys.exit(errno)
170

171
172
173
174
175
176
177
def read_version_from_cmakelists(cmake_file):
    """Read version information
    """
    major = re.findall("set\(CPACK_PACKAGE_VERSION_MAJOR.*\"(.*)\"", open(cmake_file).read())[0]
    minor = re.findall("set\(CPACK_PACKAGE_VERSION_MINOR.*\"(.*)\"", open(cmake_file).read())[0]
    patch = re.findall("set\(CPACK_PACKAGE_VERSION_PATCH.*\"(.*)\"", open(cmake_file).read())[0]
    return major + '.' + minor + '.' + patch
178

179
180
181
182
def read_entire_file(fname):
    """Read text out of a file relative to setup.py.
    """
    return open(os.path.join(fname)).read()
183

184
185
setup(
    name='dlib',
186
    version=read_version_from_cmakelists('dlib/CMakeLists.txt'),
187
    description='A toolkit for making real world machine learning and data analysis applications',
188
    long_description=read_entire_file('README.md'),
189
190
191
192
    author='Davis King',
    author_email='davis@dlib.net',
    url='https://github.com/davisking/dlib',
    license='Boost Software License',
193
194
    ext_modules=[CMakeExtension('dlib','tools/python')],
    cmdclass=dict(build_ext=CMakeBuild, test=PyTest),
195
    zip_safe=False,
196
197
198
    tests_require=['pytest'],
    packages=['dlib'],
    keywords=['dlib', 'Computer Vision', 'Machine Learning'],
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
199
200
201
202
203
204
205
206
207
208
209
210
211
212
    classifiers=[
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: Science/Research',
        'Intended Audience :: Developers',
        'Operating System :: MacOS :: MacOS X',
        'Operating System :: POSIX',
        'Operating System :: POSIX :: Linux',
        'Operating System :: Microsoft',
        'Operating System :: Microsoft :: Windows',
        'Programming Language :: C++',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.6',
        'Programming Language :: Python :: 2.7',
213
214
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
215
        'Topic :: Scientific/Engineering',
Davis King's avatar
Davis King committed
216
        'Topic :: Scientific/Engineering :: Artificial Intelligence',
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
217
218
219
        'Topic :: Scientific/Engineering :: Image Recognition',
        'Topic :: Software Development',
    ],
220
)