setup.py 9.94 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
To upload the source distribution to PyPi
Davis King's avatar
Davis King committed
18
19
    python setup.py sdist 
    twine upload dist/dlib-*.tar.gz
20
21
To exclude/include certain options in the cmake config use --yes and --no:
    for example:
22
23
    --yes USE_AVX_INSTRUCTIONS: will set -DUSE_AVX_INSTRUCTIONS=yes
    --no USE_AVX_INSTRUCTIONS: will set -DUSE_AVX_INSTRUCTIONS=no
24
Additional options:
25
26
    --compiler-flags: pass flags onto the compiler, e.g. --compiler-flags "-Os -Wall" passes -Os -Wall onto GCC.
    -G: Set the CMake generator.  E.g. -G "Visual Studio 14 2015"
27
28
29
    --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.
30
    --set: set arbitrary options e.g. --set CUDA_HOST_COMPILER=/usr/bin/gcc-6.4.0
31
32
"""
import os
33
import re
34
import sys
35
import shutil
36
import platform
37
import subprocess
38
import multiprocessing
39
from distutils import log
40
from math import ceil,floor
41

42
43
44
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
from distutils.version import LooseVersion
45

46

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

Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
53
    opt_key = None
54

55
    argv = [arg for arg in sys.argv]  # take a copy
56
    # parse command line options and consume those we care about
57
    for arg in argv:
58
59
        if opt_key == 'compiler-flags':
            _cmake_extra_options.append('-DCMAKE_CXX_FLAGS={arg}'.format(arg=arg.strip()))
60
61
        elif opt_key == 'G':
            _cmake_extra_options += ['-G', arg.strip()]
62
        elif opt_key == 'yes':
63
            _cmake_extra_options.append('-D{arg}=yes'.format(arg=arg.strip()))
64
        elif opt_key == 'no':
65
            _cmake_extra_options.append('-D{arg}=no'.format(arg=arg.strip()))
66
67
        elif opt_key == 'set':
            _cmake_extra_options.append('-D{arg}'.format(arg=arg.strip()))
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
68
69
70

        if opt_key:
            sys.argv.remove(arg)
71
            opt_key = None
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
72
            continue
73

74
75
        if arg == '--clean':
            _clean_build_folder = True
76
77
78
            sys.argv.remove(arg)
            continue

79
        if arg in ['--yes', '--no', '--set', '--compiler-flags']:
80
            opt_key = arg[2:].lower()
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
81
82
            sys.argv.remove(arg)
            continue
83
84
85
86
        if arg in ['-G']:
            opt_key = arg[1:]
            sys.argv.remove(arg)
            continue
87

88
    return _cmake_extra_options, _clean_build_folder
89

90
cmake_extra_options,clean_build_folder = get_extra_cmake_options()
91
92


93
94
95
96
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
97

98
99
100
101
102
103
104
105
106
107
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
108

109
110
111
    if os.path.exists(name):
        log.info('Removing old directory {}'.format(name))
        shutil.rmtree(name, ignore_errors=False, onerror=remove_read_only)
112
113


114
class CMakeBuild(build_ext):
115

116
    def get_cmake_version(self):
117
        try:
118
            out = subprocess.check_output(['cmake', '--version'])
119
        except OSError:
Davis King's avatar
Davis King committed
120
121
122
123
            raise RuntimeError("\n*******************************************************************\n" +
                                  " CMake must be installed to build the following extensions: " +
                               ", ".join(e.name for e in self.extensions) + 
                               "\n*******************************************************************\n")
124
        return re.search(r'version\s*([\d.]+)', out.decode()).group(1)
125

126
    def run(self):
Davis King's avatar
Davis King committed
127
        cmake_version = self.get_cmake_version()
128
        if platform.system() == "Windows":
Davis King's avatar
Davis King committed
129
            if LooseVersion(cmake_version) < '3.1.0':
130
                raise RuntimeError("CMake >= 3.1.0 is required on Windows")
131

132
133
        for ext in self.extensions:
            self.build_extension(ext)
134

135
136
    def build_extension(self, ext):
        extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
137

138
139
        cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
                      '-DPYTHON_EXECUTABLE=' + sys.executable]
140

141
        cmake_args += cmake_extra_options 
142

143
144
        cfg = 'Debug' if self.debug else 'Release'
        build_args = ['--config', cfg]
145

146
147
148
149
        if platform.system() == "Windows":
            cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
            if sys.maxsize > 2**32:
                cmake_args += ['-A', 'x64']
Davis King's avatar
Davis King committed
150
151
            # Do a parallel build
            build_args += ['--', '/m'] 
152
153
        else:
            cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
154
            # Do a parallel build
155
            build_args += ['--', '-j'+str(num_available_cpu_cores(2))]
156

157
        build_folder = os.path.abspath(self.build_temp)
158

159
160
161
162
        if clean_build_folder:
            rmtree(build_folder)
        if not os.path.exists(build_folder):
            os.makedirs(build_folder)
163

Davis King's avatar
Davis King committed
164
165
166
        cmake_setup = ['cmake', ext.sourcedir] + cmake_args
        cmake_build = ['cmake', '--build', '.'] + build_args

167
        print("Building extension for Python {}".format(sys.version.split('\n',1)[0]))
Davis King's avatar
Davis King committed
168
        print("Invoking CMake setup: '{}'".format(' '.join(cmake_setup)))
169
        sys.stdout.flush()
Davis King's avatar
Davis King committed
170
171
        subprocess.check_call(cmake_setup, cwd=build_folder)
        print("Invoking CMake build: '{}'".format(' '.join(cmake_build)))
172
        sys.stdout.flush()
Davis King's avatar
Davis King committed
173
        subprocess.check_call(cmake_build, cwd=build_folder)
174

175
def num_available_cpu_cores(ram_per_build_process_in_gb):
Davis King's avatar
Davis King committed
176
    if 'TRAVIS' in os.environ and os.environ['TRAVIS']=='true':
Davis King's avatar
Davis King committed
177
178
179
        # 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 
180
181
182
    try:
        mem_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES')  
        mem_gib = mem_bytes/(1024.**3)
183
184
        num_cores = multiprocessing.cpu_count() 
        # make sure we have enough ram for each build process.
185
186
187
188
189
190
191
        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.

192

193
194
195
from setuptools.command.test import test as TestCommand
class PyTest(TestCommand):
    user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")]
196

197
198
    def initialize_options(self):
        TestCommand.initialize_options(self)
199
        self.pytest_args = '--ignore docs --ignore dlib'
200

201
202
203
204
205
206
    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)
207

208
209
210
211
212
213
214
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
215

216
217
218
219
def read_entire_file(fname):
    """Read text out of a file relative to setup.py.
    """
    return open(os.path.join(fname)).read()
220

221
222
setup(
    name='dlib',
223
    version=read_version_from_cmakelists('dlib/CMakeLists.txt'),
224
    description='A toolkit for making real world machine learning and data analysis applications',
225
    long_description='See http://dlib.net for documentation.',
226
227
228
229
    author='Davis King',
    author_email='davis@dlib.net',
    url='https://github.com/davisking/dlib',
    license='Boost Software License',
230
231
    ext_modules=[CMakeExtension('dlib','tools/python')],
    cmdclass=dict(build_ext=CMakeBuild, test=PyTest),
232
    zip_safe=False,
Davis E. King's avatar
Davis E. King committed
233
    tests_require=['pytest==3.8'],
234
    install_requires=['cmake'],
235
236
    packages=['dlib'],
    keywords=['dlib', 'Computer Vision', 'Machine Learning'],
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
237
238
239
240
241
242
243
244
245
246
247
248
249
250
    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',
251
252
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
Davis King's avatar
Davis King committed
253
254
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
255
        'Topic :: Scientific/Engineering',
Davis King's avatar
Davis King committed
256
        'Topic :: Scientific/Engineering :: Artificial Intelligence',
Ehsan Azarnasab's avatar
Ehsan Azarnasab committed
257
258
259
        'Topic :: Scientific/Engineering :: Image Recognition',
        'Topic :: Software Development',
    ],
260
)