setup.py 16.2 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
import sys
8
9
from distutils.dir_util import copy_tree, create_tree, remove_tree
from distutils.file_util import copy_file
10
from platform import system
11

12
from setuptools import find_packages, setup
13
14
15
from setuptools.command.install import install
from setuptools.command.install_lib import install_lib
from setuptools.command.sdist import sdist
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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')
]
35

36
37

def find_lib():
38
    libpath_py = os.path.join(CURRENT_DIR, 'lightgbm', 'libpath.py')
39
40
41
42
    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']()]
43
    logger.info("Installing lib_lightgbm from: %s" % LIB_PATH)
44
45
46
    return LIB_PATH


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

    def copy_files_helper(folder_name):
50
        src = os.path.join(CURRENT_DIR, os.path.pardir, folder_name)
51
        if os.path.exists(src):
52
            dst = os.path.join(CURRENT_DIR, 'compile', folder_name)
53
54
55
56
57
58
59
            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)
60
            copy_tree(src, dst, verbose=0)
61
        else:
62
            raise Exception('Cannot copy {0} folder'.format(src))
63

64
    if not os.path.isfile(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt')):
65
66
        copy_files_helper('include')
        copy_files_helper('src')
67
68
69
70
        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))
71
72
        if not os.path.exists(os.path.join(CURRENT_DIR, "compile", "windows")):
            os.makedirs(os.path.join(CURRENT_DIR, "compile", "windows"))
73
74
75
76
77
78
79
80
81
        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)
82
83
84
85
        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:
86
87
88
89
            if not os.path.exists(os.path.join(CURRENT_DIR, "compile", "cmake")):
                os.makedirs(os.path.join(CURRENT_DIR, "compile", "cmake"))
            copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "cmake", "IntegratedOpenCL.cmake"),
                      os.path.join(CURRENT_DIR, "compile", "cmake", "IntegratedOpenCL.cmake"),
90
                      verbose=0)
wxchan's avatar
wxchan committed
91
92


93
def clear_path(path):
94
95
96
97
98
99
100
    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:
101
                remove_tree(file_path)
102
103
104
105


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


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

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

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

128
    cmake_cmd = ["cmake", "../compile/"]
129
130
131
    if integrated_opencl:
        use_gpu = False
        cmake_cmd.append("-D__INTEGRATE_OPENCL=ON")
132
    if use_gpu:
133
        cmake_cmd.append("-DUSE_GPU=ON")
134
135
136
137
138
139
140
141
142
143
144
145
        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))
146
147
    elif use_cuda:
        cmake_cmd.append("-DUSE_CUDA=ON")
148
149
    if use_mpi:
        cmake_cmd.append("-DUSE_MPI=ON")
150
151
    if nomp:
        cmake_cmd.append("-DUSE_OPENMP=OFF")
152
153
    if use_hdfs:
        cmake_cmd.append("-DUSE_HDFS=ON")
154

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


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):

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

    def initialize_options(self):
        install.initialize_options(self)
        self.mingw = 0
223
        self.integrated_opencl = 0
224
        self.gpu = 0
225
        self.cuda = 0
226
227
228
229
230
231
232
        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
233
        self.hdfs = 0
234
        self.precompile = 0
235
        self.nomp = 0
236
        self.bit32 = 0
237
238

    def run(self):
239
240
241
242
243
244
245
        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.")
246
        open(LOG_PATH, 'wb').close()
247
        if not self.precompile:
248
            copy_files(integrated_opencl=self.integrated_opencl, use_gpu=self.gpu)
249
            compile_cpp(use_mingw=self.mingw, use_gpu=self.gpu, use_cuda=self.cuda, use_mpi=self.mpi,
250
                        use_hdfs=self.hdfs, boost_root=self.boost_root, boost_dir=self.boost_dir,
251
                        boost_include_dir=self.boost_include_dir, boost_librarydir=self.boost_librarydir,
252
                        opencl_include_dir=self.opencl_include_dir, opencl_library=self.opencl_library,
253
                        nomp=self.nomp, bit32=self.bit32, integrated_opencl=self.integrated_opencl)
254
        install.run(self)
255
256
        if os.path.isfile(LOG_PATH):
            os.remove(LOG_PATH)
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
302
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


303
304
305
class CustomSdist(sdist):

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


if __name__ == "__main__":
320
321
322
323
    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')):
324
325
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, 'VERSION.txt'),
                  os.path.join(CURRENT_DIR, 'lightgbm', 'VERSION.txt'),
326
                  verbose=0)  # type:ignore
327
328
    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()
329

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

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

Guolin Ke's avatar
Guolin Ke committed
335
336
337
    setup(name='lightgbm',
          version=version,
          description='LightGBM Python Package',
338
          long_description=readme,
Guolin Ke's avatar
Guolin Ke committed
339
          install_requires=[
340
              'wheel',
Guolin Ke's avatar
Guolin Ke committed
341
342
              'numpy',
              'scipy',
343
              'scikit-learn!=0.22.0'
Guolin Ke's avatar
Guolin Ke committed
344
          ],
345
346
347
          extras_require={
              'dask': [
                  'dask[array]>=2.0.0',
348
                  'dask[dataframe]>=2.0.0',
349
350
351
352
                  'dask[distributed]>=2.0.0',
                  'pandas',
              ],
          },
Guolin Ke's avatar
Guolin Ke committed
353
354
355
          maintainer='Guolin Ke',
          maintainer_email='guolin.ke@microsoft.com',
          zip_safe=False,
356
357
358
          cmdclass={
              'install': CustomInstall,
              'install_lib': CustomInstallLib,
359
              'bdist_wheel': CustomBdistWheel,
360
361
              'sdist': CustomSdist,
          },
Guolin Ke's avatar
Guolin Ke committed
362
363
          packages=find_packages(),
          include_package_data=True,
364
          license='The MIT License (Microsoft)',
365
          url='https://github.com/microsoft/LightGBM',
366
367
368
369
370
371
372
373
374
375
          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',
376
                       'Programming Language :: Python :: 3.7',
377
                       'Programming Language :: Python :: 3.8',
378
                       'Programming Language :: Python :: 3.9',
379
                       'Topic :: Scientific/Engineering :: Artificial Intelligence'])