"vscode:/vscode.git/clone" did not exist on "64557819d94275783ba5adc107f7d916405e030f"
setup.py 8.92 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,floor
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
            # Do a parallel build
140
            build_args += ['--', '-j'+str(num_available_cpu_cores(4))]
141

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

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

Davis King's avatar
Davis King committed
149
150
151
152
153
154
155
        cmake_setup = ['cmake', ext.sourcedir] + cmake_args
        cmake_build = ['cmake', '--build', '.'] + build_args

        print("Invoking CMake setup: '{}'".format(' '.join(cmake_setup)))
        subprocess.check_call(cmake_setup, cwd=build_folder)
        print("Invoking CMake build: '{}'".format(' '.join(cmake_build)))
        subprocess.check_call(cmake_build, cwd=build_folder)
156

157
def num_available_cpu_cores(ram_per_build_process_in_gb):
Davis King's avatar
Davis King committed
158
    if 'TRAVIS' in os.environ and os.environ['TRAVIS']=='true':
Davis King's avatar
Davis King committed
159
160
161
        # When building on travis-ci, just use 2 cores since travis-ci limits
        # you to that regardless of what the hardware might suggest.
        return 2 
162
163
164
165
166
167
168
169
170
171
172
173
174
    try:
        mem_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES')  
        mem_gib = mem_bytes/(1024.**3)
        # Assume hyperthreading so divide by 2
        num_cores = int(ceil(multiprocessing.cpu_count()/2.0)) 
        # Require 4gb of ram per build thread.
        mem_cores = int(floor(mem_gib/float(ram_per_build_process_in_gb)+0.5));
        # We are limited either by RAM or CPU cores.  So pick the limiting amount
        # and return that.
        return max(min(num_cores, mem_cores), 1)
    except ValueError:
        return 2 # just assume 2 if we can't get the os to tell us the right answer.

175

176
177
178
from setuptools.command.test import test as TestCommand
class PyTest(TestCommand):
    user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")]
179

180
181
182
    def initialize_options(self):
        TestCommand.initialize_options(self)
        self.pytest_args = ''
183

184
185
186
187
188
189
    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)
190

191
192
193
194
195
196
197
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
198

199
200
201
202
def read_entire_file(fname):
    """Read text out of a file relative to setup.py.
    """
    return open(os.path.join(fname)).read()
203

204
205
setup(
    name='dlib',
206
    version=read_version_from_cmakelists('dlib/CMakeLists.txt'),
207
    description='A toolkit for making real world machine learning and data analysis applications',
208
    long_description=read_entire_file('README.md'),
209
210
211
212
    author='Davis King',
    author_email='davis@dlib.net',
    url='https://github.com/davisking/dlib',
    license='Boost Software License',
213
214
    ext_modules=[CMakeExtension('dlib','tools/python')],
    cmdclass=dict(build_ext=CMakeBuild, test=PyTest),
215
    zip_safe=False,
216
217
218
    tests_require=['pytest'],
    packages=['dlib'],
    keywords=['dlib', 'Computer Vision', 'Machine Learning'],
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
219
220
221
222
223
224
225
226
227
228
229
230
231
232
    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',
233
234
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
235
        'Topic :: Scientific/Engineering',
Davis King's avatar
Davis King committed
236
        'Topic :: Scientific/Engineering :: Artificial Intelligence',
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
237
238
239
        'Topic :: Scientific/Engineering :: Image Recognition',
        'Topic :: Software Development',
    ],
240
)