setup.py 16 KB
Newer Older
wxchan's avatar
wxchan committed
1
2
# coding: utf-8
"""Setup lightgbm package."""
3
import logging
4
5
import os
import struct
6
import subprocess
7
8
import sys

9
from platform import system
10
from setuptools import find_packages, setup
11
12
13
from setuptools.command.install import install
from setuptools.command.install_lib import install_lib
from setuptools.command.sdist import sdist
14
from distutils.dir_util import copy_tree, create_tree, remove_tree
15
from distutils.file_util import copy_file
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from wheel.bdist_wheel import bdist_wheel


LIGHTGBM_OPTIONS = [
    ('mingw', 'm', 'Compile with MinGW'),
    ('integrated-opencl', None, 'Compile integrated OpenCL version'),
    ('gpu', 'g', 'Compile GPU version'),
    ('cuda', None, 'Compile CUDA version'),
    ('mpi', None, 'Compile MPI version'),
    ('nomp', None, 'Compile version without OpenMP support'),
    ('hdfs', 'h', 'Compile HDFS version'),
    ('bit32', None, 'Compile 32-bit version'),
    ('precompile', 'p', 'Use precompiled library'),
    ('boost-root=', None, 'Boost preferred installation prefix'),
    ('boost-dir=', None, 'Directory with Boost package configuration file'),
    ('boost-include-dir=', None, 'Directory containing Boost headers'),
    ('boost-librarydir=', None, 'Preferred Boost library directory'),
    ('opencl-include-dir=', None, 'OpenCL include directory'),
    ('opencl-library=', None, 'Path to OpenCL library')
]
36

37
38

def find_lib():
39
    libpath_py = os.path.join(CURRENT_DIR, 'lightgbm', 'libpath.py')
40
41
42
43
    libpath = {'__file__': libpath_py}
    exec(compile(open(libpath_py, "rb").read(), libpath_py, 'exec'), libpath, libpath)

    LIB_PATH = [os.path.relpath(path, CURRENT_DIR) for path in libpath['find_lib_path']()]
44
    logger.info("Installing lib_lightgbm from: %s" % LIB_PATH)
45
46
47
    return LIB_PATH


48
def copy_files(integrated_opencl=False, use_gpu=False):
49
50

    def copy_files_helper(folder_name):
51
        src = os.path.join(CURRENT_DIR, os.path.pardir, folder_name)
52
        if os.path.exists(src):
53
            dst = os.path.join(CURRENT_DIR, 'compile', folder_name)
54
55
56
57
58
59
60
            if os.path.exists(dst):
                if os.path.isdir:
                    # see https://github.com/pypa/distutils/pull/21
                    remove_tree(dst)
                else:
                    os.remove(dst)
            create_tree(src, dst, verbose=0)
61
            copy_tree(src, dst, verbose=0)
62
        else:
63
            raise Exception('Cannot copy {0} folder'.format(src))
64

65
    if not os.path.isfile(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt')):
66
67
        copy_files_helper('include')
        copy_files_helper('src')
68
69
70
71
        for submodule in os.listdir(os.path.join(CURRENT_DIR, os.path.pardir, 'external_libs')):
            if submodule == 'compute' and not use_gpu:
                continue
            copy_files_helper(os.path.join('external_libs', submodule))
72
73
        if not os.path.exists(os.path.join(CURRENT_DIR, "compile", "windows")):
            os.makedirs(os.path.join(CURRENT_DIR, "compile", "windows"))
74
75
76
77
78
79
80
81
82
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "windows", "LightGBM.sln"),
                  os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.sln"),
                  verbose=0)
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "windows", "LightGBM.vcxproj"),
                  os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.vcxproj"),
                  verbose=0)
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "LICENSE"),
                  os.path.join(CURRENT_DIR, "LICENSE"),
                  verbose=0)
83
84
85
86
87
88
89
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "CMakeLists.txt"),
                  os.path.join(CURRENT_DIR, "compile", "CMakeLists.txt"),
                  verbose=0)
        if integrated_opencl:
            copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "CMakeIntegratedOpenCL.cmake"),
                      os.path.join(CURRENT_DIR, "compile", "CMakeIntegratedOpenCL.cmake"),
                      verbose=0)
wxchan's avatar
wxchan committed
90
91


92
def clear_path(path):
93
94
95
96
97
98
99
    if os.path.isdir(path):
        contents = os.listdir(path)
        for file_name in contents:
            file_path = os.path.join(path, file_name)
            if os.path.isfile(file_path):
                os.remove(file_path)
            else:
100
                remove_tree(file_path)
101
102
103
104


def silent_call(cmd, raise_error=False, error_msg=''):
    try:
105
        with open(LOG_PATH, "ab") as log:
106
            subprocess.check_call(cmd, stderr=log, stdout=log)
107
108
        return 0
    except Exception as err:
109
        if raise_error:
110
            raise Exception("\n".join((error_msg, LOG_NOTICE)))
111
        return 1
112
113


114
def compile_cpp(use_mingw=False, use_gpu=False, use_cuda=False, use_mpi=False,
115
116
                use_hdfs=False, boost_root=None, boost_dir=None,
                boost_include_dir=None, boost_librarydir=None,
117
                opencl_include_dir=None, opencl_library=None,
118
                nomp=False, bit32=False, integrated_opencl=False):
119

120
    if os.path.exists(os.path.join(CURRENT_DIR, "build_cpp")):
121
        remove_tree(os.path.join(CURRENT_DIR, "build_cpp"))
122
123
    os.makedirs(os.path.join(CURRENT_DIR, "build_cpp"))
    os.chdir(os.path.join(CURRENT_DIR, "build_cpp"))
124

125
126
    logger.info("Starting to compile the library.")

127
    cmake_cmd = ["cmake", "../compile/"]
128
129
130
    if integrated_opencl:
        use_gpu = False
        cmake_cmd.append("-D__INTEGRATE_OPENCL=ON")
131
    if use_gpu:
132
        cmake_cmd.append("-DUSE_GPU=ON")
133
134
135
136
137
138
139
140
141
142
143
144
        if boost_root:
            cmake_cmd.append("-DBOOST_ROOT={0}".format(boost_root))
        if boost_dir:
            cmake_cmd.append("-DBoost_DIR={0}".format(boost_dir))
        if boost_include_dir:
            cmake_cmd.append("-DBoost_INCLUDE_DIR={0}".format(boost_include_dir))
        if boost_librarydir:
            cmake_cmd.append("-DBOOST_LIBRARYDIR={0}".format(boost_librarydir))
        if opencl_include_dir:
            cmake_cmd.append("-DOpenCL_INCLUDE_DIR={0}".format(opencl_include_dir))
        if opencl_library:
            cmake_cmd.append("-DOpenCL_LIBRARY={0}".format(opencl_library))
145
146
    elif use_cuda:
        cmake_cmd.append("-DUSE_CUDA=ON")
147
148
    if use_mpi:
        cmake_cmd.append("-DUSE_MPI=ON")
149
150
    if nomp:
        cmake_cmd.append("-DUSE_OPENMP=OFF")
151
152
    if use_hdfs:
        cmake_cmd.append("-DUSE_HDFS=ON")
153

154
    if system() in {'Windows', 'Microsoft'}:
155
        if use_mingw:
156
157
            if use_mpi:
                raise Exception('MPI version cannot be compiled by MinGW due to the miss of MPI library in it')
158
159
            logger.info("Starting to compile with CMake and MinGW.")
            silent_call(cmake_cmd + ["-G", "MinGW Makefiles"], raise_error=True,
160
                        error_msg='Please install CMake and all required dependencies first')
161
162
            silent_call(["mingw32-make.exe", "_lightgbm"], raise_error=True,
                        error_msg='Please install MinGW first')
163
        else:
164
            status = 1
165
            lib_path = os.path.join(CURRENT_DIR, "compile", "windows", "x64", "DLL", "lib_lightgbm.dll")
166
            if not any((use_gpu, use_cuda, use_mpi, use_hdfs, nomp, bit32, integrated_opencl)):
167
                logger.info("Starting to compile with MSBuild from existing solution file.")
168
                platform_toolsets = ("v142", "v141", "v140")
169
                for pt in platform_toolsets:
170
171
                    status = silent_call(["MSBuild",
                                          os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.sln"),
172
173
174
175
176
177
                                          "/p:Configuration=DLL",
                                          "/p:Platform=x64",
                                          "/p:PlatformToolset={0}".format(pt)])
                    if status == 0 and os.path.exists(lib_path):
                        break
                    else:
178
                        clear_path(os.path.join(CURRENT_DIR, "compile", "windows", "x64"))
179
180
181
                if status != 0 or not os.path.exists(lib_path):
                    logger.warning("Compilation with MSBuild from existing solution file failed.")
            if status != 0 or not os.path.exists(lib_path):
182
                arch = "Win32" if bit32 else "x64"
183
                vs_versions = ("Visual Studio 16 2019", "Visual Studio 15 2017", "Visual Studio 14 2015")
184
                for vs in vs_versions:
185
                    logger.info("Starting to compile with %s (%s).", vs, arch)
186
                    status = silent_call(cmake_cmd + ["-G", vs, "-A", arch])
187
188
189
                    if status == 0:
                        break
                    else:
190
                        clear_path(os.path.join(CURRENT_DIR, "build_cpp"))
191
                if status != 0:
192
                    raise Exception("\n".join(('Please install Visual Studio or MS Build and all required dependencies first',
193
                                    LOG_NOTICE)))
194
195
                silent_call(["cmake", "--build", ".", "--target", "_lightgbm", "--config", "Release"], raise_error=True,
                            error_msg='Please install CMake first')
196
    else:  # Linux, Darwin (macOS), etc.
197
        logger.info("Starting to compile with CMake.")
198
199
200
        silent_call(cmake_cmd, raise_error=True, error_msg='Please install CMake and all required dependencies first')
        silent_call(["make", "_lightgbm", "-j4"], raise_error=True,
                    error_msg='An error has occurred while building lightgbm library file')
201
    os.chdir(CURRENT_DIR)
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216


class CustomInstallLib(install_lib):

    def install(self):
        outfiles = install_lib.install(self)
        src = find_lib()[0]
        dst = os.path.join(self.install_dir, 'lightgbm')
        dst, _ = self.copy_file(src, dst)
        outfiles.append(dst)
        return outfiles


class CustomInstall(install):

217
    user_options = install.user_options + LIGHTGBM_OPTIONS
218
219
220
221

    def initialize_options(self):
        install.initialize_options(self)
        self.mingw = 0
222
        self.integrated_opencl = 0
223
        self.gpu = 0
224
        self.cuda = 0
225
226
227
228
229
230
231
        self.boost_root = None
        self.boost_dir = None
        self.boost_include_dir = None
        self.boost_librarydir = None
        self.opencl_include_dir = None
        self.opencl_library = None
        self.mpi = 0
232
        self.hdfs = 0
233
        self.precompile = 0
234
        self.nomp = 0
235
        self.bit32 = 0
236
237

    def run(self):
238
239
240
241
242
243
244
        if (8 * struct.calcsize("P")) != 64:
            if self.bit32:
                logger.warning("You're installing 32-bit version. "
                               "This version is slow and untested, so use it on your own risk.")
            else:
                raise Exception("Cannot install LightGBM in 32-bit Python, "
                                "please use 64-bit Python instead.")
245
        open(LOG_PATH, 'wb').close()
246
        if not self.precompile:
247
            copy_files(integrated_opencl=self.integrated_opencl, use_gpu=self.gpu)
248
            compile_cpp(use_mingw=self.mingw, use_gpu=self.gpu, use_cuda=self.cuda, use_mpi=self.mpi,
249
                        use_hdfs=self.hdfs, boost_root=self.boost_root, boost_dir=self.boost_dir,
250
                        boost_include_dir=self.boost_include_dir, boost_librarydir=self.boost_librarydir,
251
                        opencl_include_dir=self.opencl_include_dir, opencl_library=self.opencl_library,
252
                        nomp=self.nomp, bit32=self.bit32, integrated_opencl=self.integrated_opencl)
253
        install.run(self)
254
255
        if os.path.isfile(LOG_PATH):
            os.remove(LOG_PATH)
256
257


258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
class CustomBdistWheel(bdist_wheel):

    user_options = bdist_wheel.user_options + LIGHTGBM_OPTIONS

    def initialize_options(self):
        bdist_wheel.initialize_options(self)
        self.mingw = 0
        self.integrated_opencl = 0
        self.gpu = 0
        self.cuda = 0
        self.boost_root = None
        self.boost_dir = None
        self.boost_include_dir = None
        self.boost_librarydir = None
        self.opencl_include_dir = None
        self.opencl_library = None
        self.mpi = 0
        self.hdfs = 0
        self.precompile = 0
        self.nomp = 0
        self.bit32 = 0

    def finalize_options(self):
        bdist_wheel.finalize_options(self)

        install = self.reinitialize_command('install')

        install.mingw = self.mingw
        install.integrated_opencl = self.integrated_opencl
        install.gpu = self.gpu
        install.cuda = self.cuda
        install.boost_root = self.boost_root
        install.boost_dir = self.boost_dir
        install.boost_include_dir = self.boost_include_dir
        install.boost_librarydir = self.boost_librarydir
        install.opencl_include_dir = self.opencl_include_dir
        install.opencl_library = self.opencl_library
        install.mpi = self.mpi
        install.hdfs = self.hdfs
        install.precompile = self.precompile
        install.nomp = self.nomp
        install.bit32 = self.bit32


302
303
304
class CustomSdist(sdist):

    def run(self):
305
        copy_files(integrated_opencl=True, use_gpu=True)
306
307
        open(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt'), 'w').close()
        if os.path.exists(os.path.join(CURRENT_DIR, 'lightgbm', 'Release')):
308
            remove_tree(os.path.join(CURRENT_DIR, 'lightgbm', 'Release'))
309
        if os.path.exists(os.path.join(CURRENT_DIR, 'lightgbm', 'windows', 'x64')):
310
            remove_tree(os.path.join(CURRENT_DIR, 'lightgbm', 'windows', 'x64'))
311
312
        if os.path.isfile(os.path.join(CURRENT_DIR, 'lightgbm', 'lib_lightgbm.so')):
            os.remove(os.path.join(CURRENT_DIR, 'lightgbm', 'lib_lightgbm.so'))
313
        sdist.run(self)
314
315
        if os.path.isfile(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt')):
            os.remove(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt'))
316
317
318


if __name__ == "__main__":
319
320
321
322
    CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
    LOG_PATH = os.path.join(os.path.expanduser('~'), 'LightGBM_compilation.log')
    LOG_NOTICE = "The full version of error log was saved into {0}".format(LOG_PATH)
    if os.path.isfile(os.path.join(CURRENT_DIR, os.path.pardir, 'VERSION.txt')):
323
324
325
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, 'VERSION.txt'),
                  os.path.join(CURRENT_DIR, 'lightgbm', 'VERSION.txt'),
                  verbose=0)
326
327
    version = open(os.path.join(CURRENT_DIR, 'lightgbm', 'VERSION.txt'), encoding='utf-8').read().strip()
    readme = open(os.path.join(CURRENT_DIR, 'README.rst'), encoding='utf-8').read()
328

329
    sys.path.insert(0, CURRENT_DIR)
330

331
332
333
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger('LightGBM')

Guolin Ke's avatar
Guolin Ke committed
334
335
336
    setup(name='lightgbm',
          version=version,
          description='LightGBM Python Package',
337
          long_description=readme,
Guolin Ke's avatar
Guolin Ke committed
338
          install_requires=[
339
              'wheel',
Guolin Ke's avatar
Guolin Ke committed
340
341
              'numpy',
              'scipy',
342
              'scikit-learn!=0.22.0'
Guolin Ke's avatar
Guolin Ke committed
343
          ],
344
345
346
          extras_require={
              'dask': [
                  'dask[array]>=2.0.0',
347
                  'dask[dataframe]>=2.0.0',
348
349
350
351
                  'dask[distributed]>=2.0.0',
                  'pandas',
              ],
          },
Guolin Ke's avatar
Guolin Ke committed
352
353
354
          maintainer='Guolin Ke',
          maintainer_email='guolin.ke@microsoft.com',
          zip_safe=False,
355
356
357
          cmdclass={
              'install': CustomInstall,
              'install_lib': CustomInstallLib,
358
              'bdist_wheel': CustomBdistWheel,
359
360
              'sdist': CustomSdist,
          },
Guolin Ke's avatar
Guolin Ke committed
361
362
          packages=find_packages(),
          include_package_data=True,
363
          license='The MIT License (Microsoft)',
364
          url='https://github.com/microsoft/LightGBM',
365
366
367
368
369
370
371
372
373
374
          classifiers=['Development Status :: 5 - Production/Stable',
                       'Intended Audience :: Science/Research',
                       'License :: OSI Approved :: MIT License',
                       'Natural Language :: English',
                       'Operating System :: MacOS',
                       'Operating System :: Microsoft :: Windows',
                       'Operating System :: POSIX',
                       'Operating System :: Unix',
                       'Programming Language :: Python :: 3',
                       'Programming Language :: Python :: 3.6',
375
                       'Programming Language :: Python :: 3.7',
376
                       'Programming Language :: Python :: 3.8',
377
                       'Topic :: Scientific/Engineering :: Artificial Intelligence'])