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

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

14
from platform import system
15
from setuptools import find_packages, setup
16
17
18
from setuptools.command.install import install
from setuptools.command.install_lib import install_lib
from setuptools.command.sdist import sdist
19

20
21

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


def copy_files(use_gpu=False):

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

42
    if not os.path.isfile(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt')):
43
44
        copy_files_helper('include')
        copy_files_helper('src')
45
46
47
        if not os.path.exists(os.path.join(CURRENT_DIR, "compile", "windows")):
            os.makedirs(os.path.join(CURRENT_DIR, "compile", "windows"))
        distutils.file_util.copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "windows", "LightGBM.sln"),
48
49
                                      os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.sln"),
                                      verbose=0)
50
        distutils.file_util.copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "windows", "LightGBM.vcxproj"),
51
52
                                      os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.vcxproj"),
                                      verbose=0)
Guolin Ke's avatar
Guolin Ke committed
53
        if use_gpu:
54
            copy_files_helper('compute')
55
        distutils.file_util.copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "CMakeLists.txt"),
56
57
                                      os.path.join(CURRENT_DIR, "compile", "CMakeLists.txt"),
                                      verbose=0)
58
        distutils.file_util.copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "LICENSE"),
59
60
                                      os.path.join(CURRENT_DIR, "LICENSE"),
                                      verbose=0)
wxchan's avatar
wxchan committed
61
62


63
def clear_path(path):
64
65
66
67
68
69
70
71
72
73
74
75
    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:
76
        output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
77
        with open(LOG_PATH, "ab") as log:
78
79
80
81
            log.write(output)
        return 0
    except Exception as err:
        if isinstance(err, subprocess.CalledProcessError):
82
            with open(LOG_PATH, "ab") as log:
83
                log.write(err.output)
84
        if raise_error:
85
            raise Exception("\n".join((error_msg, LOG_NOTICE)))
86
        return 1
87
88


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

95
96
97
98
    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"))
99

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

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

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


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

    def initialize_options(self):
        install.initialize_options(self)
        self.mingw = 0
        self.gpu = 0
207
208
209
210
211
212
213
        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
214
        self.hdfs = 0
215
        self.precompile = 0
216
        self.nomp = 0
217
        self.bit32 = 0
218
219

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


class CustomSdist(sdist):

    def run(self):
Guolin Ke's avatar
Guolin Ke committed
243
        copy_files(use_gpu=True)
244
245
246
247
248
249
250
        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'))
251
        sdist.run(self)
252
253
        if os.path.isfile(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt')):
            os.remove(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt'))
254
255
256


if __name__ == "__main__":
257
258
259
260
261
    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')):
        distutils.file_util.copy_file(os.path.join(CURRENT_DIR, os.path.pardir, 'VERSION.txt'),
262
263
                                      os.path.join(CURRENT_DIR, 'lightgbm', 'VERSION.txt'),
                                      verbose=0)
264
265
    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()
266

267
    sys.path.insert(0, CURRENT_DIR)
268

269
270
271
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger('LightGBM')

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