setup.py 7.78 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
35
import subprocess
from distutils import log
36

37
38
39
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
from distutils.version import LooseVersion
40

41

42
43
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
44
    """
45
46
    _cmake_extra_options = []
    _clean_build_folder = False
47

Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
48
    opt_key = None
49

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

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

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

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

75
    return _cmake_extra_options, _clean_build_folder
76

77
cmake_extra_options,clean_build_folder = get_extra_cmake_options()
78
79


80
81
82
83
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
84

85
86
87
88
89
90
91
92
93
94
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
95

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


101
class CMakeBuild(build_ext):
102

103
    def get_cmake_version(self):
104
        try:
105
            out = subprocess.check_output(['cmake', '--version'])
106
        except OSError:
107
108
109
            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)
110

111
112
113
114
    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")
115

116
117
        for ext in self.extensions:
            self.build_extension(ext)
118

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

122
123
        cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
                      '-DPYTHON_EXECUTABLE=' + sys.executable]
124

125
        cmake_args += cmake_extra_options 
126

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

130
131
132
133
134
135
136
137
        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]
            build_args += ['--', '-j2']
138

139
        build_folder = os.path.abspath(self.build_temp)
140

141
142
143
144
        if clean_build_folder:
            rmtree(build_folder)
        if not os.path.exists(build_folder):
            os.makedirs(build_folder)
145

146
147
148
        print("Invoking CMake: '{}'".format(['cmake', ext.sourcedir] + cmake_args))
        subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=build_folder)
        subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=build_folder)
149
150


151
152
153
from setuptools.command.test import test as TestCommand
class PyTest(TestCommand):
    user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")]
154

155
156
157
    def initialize_options(self):
        TestCommand.initialize_options(self)
        self.pytest_args = ''
158

159
160
161
162
163
164
    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)
165

166
167
168
169
170
171
172
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
173

174
175
176
177
def read_entire_file(fname):
    """Read text out of a file relative to setup.py.
    """
    return open(os.path.join(fname)).read()
178

179
180
setup(
    name='dlib',
181
    version=read_version_from_cmakelists('dlib/CMakeLists.txt'),
182
    description='A toolkit for making real world machine learning and data analysis applications',
183
    long_description=read_entire_file('README.md'),
184
185
186
187
    author='Davis King',
    author_email='davis@dlib.net',
    url='https://github.com/davisking/dlib',
    license='Boost Software License',
188
189
    ext_modules=[CMakeExtension('dlib','tools/python')],
    cmdclass=dict(build_ext=CMakeBuild, test=PyTest),
190
    zip_safe=False,
191
192
193
    tests_require=['pytest'],
    packages=['dlib'],
    keywords=['dlib', 'Computer Vision', 'Machine Learning'],
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
194
195
196
197
198
199
200
201
202
203
204
205
206
207
    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',
208
209
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
210
        'Topic :: Scientific/Engineering',
Davis King's avatar
Davis King committed
211
        'Topic :: Scientific/Engineering :: Artificial Intelligence',
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
212
213
214
        'Topic :: Scientific/Engineering :: Image Recognition',
        'Topic :: Software Development',
    ],
215
)