setup.py 12.8 KB
Newer Older
wxchan's avatar
wxchan committed
1
# coding: utf-8
2
# pylint: disable=invalid-name, exec-used, C0111
wxchan's avatar
wxchan committed
3
4
"""Setup lightgbm package."""
from __future__ import absolute_import
5

Guolin Ke's avatar
Guolin Ke committed
6
import distutils
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
    logging.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
38
39
            shutil.rmtree(dst, ignore_errors=True)
            distutils.dir_util.copy_tree(src, dst)
        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
48
49
50
        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"),
                                      os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.sln"))
        distutils.file_util.copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "windows", "LightGBM.vcxproj"),
                                      os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.vcxproj"))
Guolin Ke's avatar
Guolin Ke committed
51
        if use_gpu:
52
            copy_files_helper('compute')
53
54
55
56
        distutils.file_util.copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "CMakeLists.txt"),
                                      os.path.join(CURRENT_DIR, "compile", "CMakeLists.txt"))
        distutils.file_util.copy_file(os.path.join(CURRENT_DIR, os.path.pardir, "LICENSE"),
                                      os.path.join(CURRENT_DIR, "LICENSE"))
wxchan's avatar
wxchan committed
57
58


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


85
def compile_cpp(use_mingw=False, use_gpu=False, use_mpi=False, use_hdfs=False,
86
87
88
                boost_root=None, boost_dir=None, boost_include_dir=None,
                boost_librarydir=None, opencl_include_dir=None,
                opencl_library=None):
89

90
91
92
93
    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"))
94

95
96
    logger.info("Starting to compile the library.")

97
    cmake_cmd = ["cmake", "../compile/"]
98
    if use_gpu:
99
        cmake_cmd.append("-DUSE_GPU=ON")
100
101
102
103
104
105
106
107
108
109
110
111
112
113
        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")
114
115
    if use_hdfs:
        cmake_cmd.append("-DUSE_HDFS=ON")
116
    if system() in ('Windows', 'Microsoft'):
117
        if use_mingw:
118
119
            if use_mpi:
                raise Exception('MPI version cannot be compiled by MinGW due to the miss of MPI library in it')
120
121
            logger.info("Starting to compile with CMake and MinGW.")
            silent_call(cmake_cmd + ["-G", "MinGW Makefiles"], raise_error=True,
122
                        error_msg='Please install CMake and all required dependencies first')
123
124
            silent_call(["mingw32-make.exe", "_lightgbm"], raise_error=True,
                        error_msg='Please install MinGW first')
125
        else:
126
            status = 1
127
            lib_path = os.path.join(CURRENT_DIR, "compile", "windows", "x64", "DLL", "lib_lightgbm.dll")
128
            if not use_gpu and not use_hdfs:
129
                logger.info("Starting to compile with MSBuild from existing solution file.")
130
                platform_toolsets = ("v141", "v140")
131
                for pt in platform_toolsets:
132
133
                    status = silent_call(["MSBuild",
                                          os.path.join(CURRENT_DIR, "compile", "windows", "LightGBM.sln"),
134
135
136
137
138
139
                                          "/p:Configuration=DLL",
                                          "/p:Platform=x64",
                                          "/p:PlatformToolset={0}".format(pt)])
                    if status == 0 and os.path.exists(lib_path):
                        break
                    else:
140
                        clear_path(os.path.join(CURRENT_DIR, "compile", "windows", "x64"))
141
142
143
                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):
144
                vs_versions = ("Visual Studio 15 2017 Win64", "Visual Studio 14 2015 Win64")
145
146
147
148
149
150
                for vs in vs_versions:
                    logger.info("Starting to compile with %s." % vs)
                    status = silent_call(cmake_cmd + ["-G", vs])
                    if status == 0:
                        break
                    else:
151
                        clear_path(os.path.join(CURRENT_DIR, "build_cpp"))
152
                if status != 0:
153
                    raise Exception("\n".join(('Please install Visual Studio or MS Build and all required dependencies first',
154
                                    LOG_NOTICE)))
155
156
                silent_call(["cmake", "--build", ".", "--target", "_lightgbm", "--config", "Release"], raise_error=True,
                            error_msg='Please install CMake first')
157
    else:  # Linux, Darwin (macOS), etc.
158
        logger.info("Starting to compile with CMake.")
159
        silent_call(cmake_cmd, raise_error=True, error_msg='Please install CMake and all required dependencies first')
160
161
        silent_call(["make", "_lightgbm"], raise_error=True,
                    error_msg='An error has occurred while building lightgbm library file')
162
    os.chdir(CURRENT_DIR)
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178


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 + [
179
180
181
        ('mingw', 'm', 'Compile with MinGW'),
        ('gpu', 'g', 'Compile GPU version'),
        ('mpi', None, 'Compile MPI version'),
182
        ('hdfs', 'h', 'Compile HDFS version'),
183
184
185
186
187
188
189
        ('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')
190
191
192
193
194
195
    ]

    def initialize_options(self):
        install.initialize_options(self)
        self.mingw = 0
        self.gpu = 0
196
197
198
199
200
201
202
        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
203
        self.hdfs = 0
204
205
206
        self.precompile = 0

    def run(self):
207
        open(LOG_PATH, 'wb').close()
208
209
        if not self.precompile:
            copy_files(use_gpu=self.gpu)
210
            compile_cpp(use_mingw=self.mingw, use_gpu=self.gpu, use_mpi=self.mpi, use_hdfs=self.hdfs,
211
212
213
                        boost_root=self.boost_root, boost_dir=self.boost_dir,
                        boost_include_dir=self.boost_include_dir, boost_librarydir=self.boost_librarydir,
                        opencl_include_dir=self.opencl_include_dir, opencl_library=self.opencl_library)
214
        install.run(self)
215
216
        if os.path.isfile(LOG_PATH):
            os.remove(LOG_PATH)
217
218
219
220
221


class CustomSdist(sdist):

    def run(self):
Guolin Ke's avatar
Guolin Ke committed
222
        copy_files(use_gpu=True)
223
224
225
226
227
228
229
        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'))
230
        sdist.run(self)
231
232
        if os.path.isfile(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt')):
            os.remove(os.path.join(CURRENT_DIR, '_IS_SOURCE_PACKAGE.txt'))
233
234
235
236


if __name__ == "__main__":
    if (8 * struct.calcsize("P")) != 64:
237
        raise Exception('Cannot install LightGBM in 32-bit Python, please use 64-bit Python instead.')
238

239
240
241
242
243
244
245
246
    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'),
                                      os.path.join(CURRENT_DIR, 'lightgbm', 'VERSION.txt'))
    version = open(os.path.join(CURRENT_DIR, 'lightgbm', 'VERSION.txt')).read().strip()
    readme = open(os.path.join(CURRENT_DIR, 'README.rst')).read()
247

248
    sys.path.insert(0, CURRENT_DIR)
249

250
251
252
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger('LightGBM')

Guolin Ke's avatar
Guolin Ke committed
253
254
255
    setup(name='lightgbm',
          version=version,
          description='LightGBM Python Package',
256
          long_description=readme,
Guolin Ke's avatar
Guolin Ke committed
257
258
259
260
261
262
263
264
          install_requires=[
              'numpy',
              'scipy',
              'scikit-learn'
          ],
          maintainer='Guolin Ke',
          maintainer_email='guolin.ke@microsoft.com',
          zip_safe=False,
265
266
267
268
269
          cmdclass={
              'install': CustomInstall,
              'install_lib': CustomInstallLib,
              'sdist': CustomSdist,
          },
Guolin Ke's avatar
Guolin Ke committed
270
271
          packages=find_packages(),
          include_package_data=True,
272
          license='The MIT License (Microsoft)',
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
          url='https://github.com/Microsoft/LightGBM',
          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.4',
                       'Programming Language :: Python :: 3.5',
                       'Programming Language :: Python :: 3.6',
                       'Topic :: Scientific/Engineering :: Artificial Intelligence'])