setup.py 13.6 KB
Newer Older
wxchan's avatar
wxchan committed
1
2
3
# coding: utf-8
"""Setup lightgbm package."""
from __future__ import absolute_import
4

5
import io
6
import logging
7
import os
Guolin Ke's avatar
Guolin Ke committed
8
import shutil
9
import struct
10
import subprocess
11
12
import sys

13
from platform import system
14
from setuptools import find_packages, setup
15
16
17
from setuptools.command.install import install
from setuptools.command.install_lib import install_lib
from setuptools.command.sdist import sdist
18
19
from distutils.dir_util import copy_tree
from distutils.file_util import copy_file
20

21
22

def find_lib():
23
    libpath_py = os.path.join(CURRENT_DIR, 'lightgbm', 'libpath.py')
24
25
26
27
    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']()]
28
    logger.info("Installing lib_lightgbm from: %s" % LIB_PATH)
29
30
31
32
33
34
    return LIB_PATH


def copy_files(use_gpu=False):

    def copy_files_helper(folder_name):
35
        src = os.path.join(CURRENT_DIR, os.path.pardir, folder_name)
36
        if os.path.exists(src):
37
            dst = os.path.join(CURRENT_DIR, 'compile', folder_name)
38
            shutil.rmtree(dst, ignore_errors=True)
39
            copy_tree(src, dst, verbose=0)
40
        else:
41
            raise Exception('Cannot copy {0} folder'.format(src))
42

43
    if not os.path.isfile(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt')):
44
45
        copy_files_helper('include')
        copy_files_helper('src')
46
47
        if not os.path.exists(os.path.join(CURRENT_DIR, "compile", "windows")):
            os.makedirs(os.path.join(CURRENT_DIR, "compile", "windows"))
48
49
50
51
52
53
        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)
Guolin Ke's avatar
Guolin Ke committed
54
        if use_gpu:
55
            copy_files_helper('compute')
56
57
58
59
60
61
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "CMakeLists.txt"),
                  os.path.join(CURRENT_DIR, "compile", "CMakeLists.txt"),
                  verbose=0)
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "LICENSE"),
                  os.path.join(CURRENT_DIR, "LICENSE"),
                  verbose=0)
wxchan's avatar
wxchan committed
62
63


64
def clear_path(path):
65
66
67
68
69
70
71
72
73
74
75
76
    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:
                shutil.rmtree(file_path)


def silent_call(cmd, raise_error=False, error_msg=''):
    try:
77
        output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
78
        with open(LOG_PATH, "ab") as log:
79
80
81
82
            log.write(output)
        return 0
    except Exception as err:
        if isinstance(err, subprocess.CalledProcessError):
83
            with open(LOG_PATH, "ab") as log:
84
                log.write(err.output)
85
        if raise_error:
86
            raise Exception("\n".join((error_msg, LOG_NOTICE)))
87
        return 1
88
89


90
def compile_cpp(use_mingw=False, use_gpu=False, use_cuda=False, use_mpi=False,
91
92
                use_hdfs=False, boost_root=None, boost_dir=None,
                boost_include_dir=None, boost_librarydir=None,
93
                opencl_include_dir=None, opencl_library=None,
94
                nomp=False, bit32=False):
95

96
97
98
99
    if os.path.exists(os.path.join(CURRENT_DIR, "build_cpp")):
        shutil.rmtree(os.path.join(CURRENT_DIR, "build_cpp"))
    os.makedirs(os.path.join(CURRENT_DIR, "build_cpp"))
    os.chdir(os.path.join(CURRENT_DIR, "build_cpp"))
100

101
102
    logger.info("Starting to compile the library.")

103
    cmake_cmd = ["cmake", "../compile/"]
104
    if use_gpu:
105
        cmake_cmd.append("-DUSE_GPU=ON")
106
107
108
109
110
111
112
113
114
115
116
117
        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))
118
119
    elif use_cuda:
        cmake_cmd.append("-DUSE_CUDA=ON")
120
121
    if use_mpi:
        cmake_cmd.append("-DUSE_MPI=ON")
122
123
    if nomp:
        cmake_cmd.append("-DUSE_OPENMP=OFF")
124
125
    if use_hdfs:
        cmake_cmd.append("-DUSE_HDFS=ON")
126

127
    if system() in {'Windows', 'Microsoft'}:
128
        if use_mingw:
129
130
            if use_mpi:
                raise Exception('MPI version cannot be compiled by MinGW due to the miss of MPI library in it')
131
132
            logger.info("Starting to compile with CMake and MinGW.")
            silent_call(cmake_cmd + ["-G", "MinGW Makefiles"], raise_error=True,
133
                        error_msg='Please install CMake and all required dependencies first')
134
135
            silent_call(["mingw32-make.exe", "_lightgbm"], raise_error=True,
                        error_msg='Please install MinGW first')
136
        else:
137
            status = 1
138
            lib_path = os.path.join(CURRENT_DIR, "compile", "windows", "x64", "DLL", "lib_lightgbm.dll")
139
            if not any((use_gpu, use_mpi, use_hdfs, nomp, bit32)):
140
                logger.info("Starting to compile with MSBuild from existing solution file.")
141
                platform_toolsets = ("v142", "v141", "v140")
142
                for pt in platform_toolsets:
143
144
                    status = silent_call(["MSBuild",
                                          os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.sln"),
145
146
147
148
149
150
                                          "/p:Configuration=DLL",
                                          "/p:Platform=x64",
                                          "/p:PlatformToolset={0}".format(pt)])
                    if status == 0 and os.path.exists(lib_path):
                        break
                    else:
151
                        clear_path(os.path.join(CURRENT_DIR, "compile", "windows", "x64"))
152
153
154
                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):
155
                arch = "Win32" if bit32 else "x64"
156
                vs_versions = ("Visual Studio 16 2019", "Visual Studio 15 2017", "Visual Studio 14 2015")
157
158
                for vs in vs_versions:
                    logger.info("Starting to compile with %s." % vs)
159
                    status = silent_call(cmake_cmd + ["-G", vs, "-A", arch])
160
161
162
                    if status == 0:
                        break
                    else:
163
                        clear_path(os.path.join(CURRENT_DIR, "build_cpp"))
164
                if status != 0:
165
                    raise Exception("\n".join(('Please install Visual Studio or MS Build and all required dependencies first',
166
                                    LOG_NOTICE)))
167
168
                silent_call(["cmake", "--build", ".", "--target", "_lightgbm", "--config", "Release"], raise_error=True,
                            error_msg='Please install CMake first')
169
    else:  # Linux, Darwin (macOS), etc.
170
        logger.info("Starting to compile with CMake.")
171
172
173
        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')
174
    os.chdir(CURRENT_DIR)
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190


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

    user_options = install.user_options + [
191
192
        ('mingw', 'm', 'Compile with MinGW'),
        ('gpu', 'g', 'Compile GPU version'),
193
        ('cuda', None, 'Compile CUDA version'),
194
        ('mpi', None, 'Compile MPI version'),
195
        ('nomp', None, 'Compile version without OpenMP support'),
196
        ('hdfs', 'h', 'Compile HDFS version'),
197
        ('bit32', None, 'Compile 32-bit version'),
198
199
200
201
202
203
        ('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'),
204
        ('opencl-library=', None, 'Path to OpenCL library')
205
206
207
208
209
210
    ]

    def initialize_options(self):
        install.initialize_options(self)
        self.mingw = 0
        self.gpu = 0
211
        self.cuda = 0
212
213
214
215
216
217
218
        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
219
        self.hdfs = 0
220
        self.precompile = 0
221
        self.nomp = 0
222
        self.bit32 = 0
223
224

    def run(self):
225
226
227
228
229
230
231
        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.")
232
        open(LOG_PATH, 'wb').close()
233
234
        if not self.precompile:
            copy_files(use_gpu=self.gpu)
235
            compile_cpp(use_mingw=self.mingw, use_gpu=self.gpu, use_cuda=self.cuda, use_mpi=self.mpi,
236
                        use_hdfs=self.hdfs, boost_root=self.boost_root, boost_dir=self.boost_dir,
237
                        boost_include_dir=self.boost_include_dir, boost_librarydir=self.boost_librarydir,
238
                        opencl_include_dir=self.opencl_include_dir, opencl_library=self.opencl_library,
239
                        nomp=self.nomp, bit32=self.bit32)
240
        install.run(self)
241
242
        if os.path.isfile(LOG_PATH):
            os.remove(LOG_PATH)
243
244
245
246
247


class CustomSdist(sdist):

    def run(self):
Guolin Ke's avatar
Guolin Ke committed
248
        copy_files(use_gpu=True)
249
250
251
252
253
254
255
        open(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt'), 'w').close()
        if os.path.exists(os.path.join(CURRENT_DIR, 'lightgbm', 'Release')):
            shutil.rmtree(os.path.join(CURRENT_DIR, 'lightgbm', 'Release'))
        if os.path.exists(os.path.join(CURRENT_DIR, 'lightgbm', 'windows', 'x64')):
            shutil.rmtree(os.path.join(CURRENT_DIR, 'lightgbm', 'windows', 'x64'))
        if os.path.isfile(os.path.join(CURRENT_DIR, 'lightgbm', 'lib_lightgbm.so')):
            os.remove(os.path.join(CURRENT_DIR, 'lightgbm', 'lib_lightgbm.so'))
256
        sdist.run(self)
257
258
        if os.path.isfile(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt')):
            os.remove(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt'))
259
260
261


if __name__ == "__main__":
262
263
264
265
    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')):
266
267
268
        copy_file(os.path.join(CURRENT_DIR, os.path.pardir, 'VERSION.txt'),
                  os.path.join(CURRENT_DIR, 'lightgbm', 'VERSION.txt'),
                  verbose=0)
269
270
    version = io.open(os.path.join(CURRENT_DIR, 'lightgbm', 'VERSION.txt'), encoding='utf-8').read().strip()
    readme = io.open(os.path.join(CURRENT_DIR, 'README.rst'), encoding='utf-8').read()
271

272
    sys.path.insert(0, CURRENT_DIR)
273

274
275
276
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger('LightGBM')

Guolin Ke's avatar
Guolin Ke committed
277
278
279
    setup(name='lightgbm',
          version=version,
          description='LightGBM Python Package',
280
          long_description=readme,
Guolin Ke's avatar
Guolin Ke committed
281
282
283
          install_requires=[
              'numpy',
              'scipy',
284
              'scikit-learn!=0.22.0'
Guolin Ke's avatar
Guolin Ke committed
285
286
287
288
          ],
          maintainer='Guolin Ke',
          maintainer_email='guolin.ke@microsoft.com',
          zip_safe=False,
289
290
291
292
293
          cmdclass={
              'install': CustomInstall,
              'install_lib': CustomInstallLib,
              'sdist': CustomSdist,
          },
Guolin Ke's avatar
Guolin Ke committed
294
295
          packages=find_packages(),
          include_package_data=True,
296
          license='The MIT License (Microsoft)',
297
          url='https://github.com/microsoft/LightGBM',
298
299
300
301
302
303
304
305
306
307
308
309
          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 :: 2',
                       'Programming Language :: Python :: 2.7',
                       'Programming Language :: Python :: 3',
                       'Programming Language :: Python :: 3.6',
310
                       'Programming Language :: Python :: 3.7',
311
                       'Programming Language :: Python :: 3.8',
312
                       'Topic :: Scientific/Engineering :: Artificial Intelligence'])