setup.py 13.4 KB
Newer Older
1
# pygolang | pythonic package setup
2
# Copyright (C) 2018-2020  Nexedi SA and Contributors.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#                          Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
20 21 22 23 24 25

from setuptools import find_packages
# setuptools has Library but this days it is not well supported and test for it
# has been killed https://github.com/pypa/setuptools/commit/654c26f78a30
# -> use setuptools_dso instead.
from setuptools_dso import DSO
26 27
from setuptools.command.install_scripts import install_scripts as _install_scripts
from setuptools.command.develop import develop as _develop
28
from distutils import sysconfig
29
from os.path import dirname, join
30
import sys, re
Kirill Smelkov's avatar
Kirill Smelkov committed
31

32 33 34 35 36
# read file content
def readfile(path):
    with open(path, 'r') as f:
        return f.read()

37 38 39
# reuse golang.pyx.build to build pygolang extensions.
# we have to be careful and inject synthetic golang package in order to be
# able to import golang.pyx.build without built/working golang.
40 41 42
trun = {}
exec(readfile('trun'), trun)
trun['ximport_empty_golangmod']()
43 44
from golang.pyx.build import setup, Extension as Ext

Kirill Smelkov's avatar
Kirill Smelkov committed
45

46 47 48 49 50 51 52 53 54 55 56 57 58 59
# grep searches text for pattern.
# return re.Match object or raises if pattern was not found.
def grep1(pattern, text):
    rex = re.compile(pattern, re.MULTILINE)
    m = rex.search(text)
    if m is None:
        raise RuntimeError('%r not found' % pattern)
    return m

# find our version
_ = readfile(join(dirname(__file__), 'golang/__init__.py'))
_ = grep1('^__version__ = "(.*)"$', _)
version = _.group(1)

60 61 62 63 64 65 66 67 68 69 70 71 72
# XInstallGPython customly installs bin/gpython.
#
# console_scripts generated by setuptools do lots of imports. However we need
# gevent.monkey.patch_all() to be done first - before all other imports. We
# could use plain scripts for gpython, however even for plain scripts
# setuptools wants to inject pkg_resources import for develop install, and
# pkg_resources does import lots of modules.
#
# -> generate the script via our custom install, but keep gpython listed as
# console_scripts entry point, so that pip knows to remove the file on develop
# uninstall.
#
# NOTE in some cases (see below e.g. about bdist_wheel) we accept for gpython
Kirill Smelkov's avatar
Kirill Smelkov committed
73
# to be generated not via XInstallGPython - because in those cases pkg_resources
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
# and entry points are not used - just plain `import gpython`.
class XInstallGPython:
    gpython_installed = 0

    # NOTE cannot override write_script, because base class - _install_scripts
    # or _develop, is old-style and super does not work with it.
    #def write_script(self, script_name, script, mode="t", blockers=()):
    #    script_name, script = self.transform_script(script_name, script)
    #    super(XInstallGPython, self).write_script(script_name, script, mode, blockers)

    # transform_script transform to-be installed script to override installed gpython content.
    #
    # (script_name, script) -> (script_name, script)
    def transform_script(self, script_name, script):
        # on windows setuptools installs 3 files:
        #   gpython-script.py
        #   gpython.exe
        #   gpython.exe.manifest
        # we want to override .py only.
        #
        # for-windows build could be cross - e.g. from linux via bdist_wininst -
        # -> we can't rely on os.name. Rely on just script name.
        if script_name in ('gpython', 'gpython-script.py'):
            script  = '#!%s\n' % sys.executable
            script += '\nfrom gpython import main; main()\n'
            self.gpython_installed += 1

        return script_name, script


# install_scripts is custom scripts installer that takes gpython into account.
class install_scripts(XInstallGPython, _install_scripts):
    def write_script(self, script_name, script, mode="t", blockers=()):
        script_name, script = self.transform_script(script_name, script)
        _install_scripts.write_script(self, script_name, script, mode, blockers)

    def run(self):
        _install_scripts.run(self)
        # bdist_wheel disables generation of scripts for entry-points[1]
        # and pip/setuptools regenerate them when installing the wheel[2].
        #
        #   [1] https://github.com/pypa/wheel/commit/0d7f398b
        #   [2] https://github.com/pypa/wheel/commit/9aaa6628
        #
        # since setup.py is not included into the wheel, we cannot control
        # entry-point installation when the wheel is installed. However,
        # console script generated when installing the wheel looks like:
        #
        #   #!/path/to/python
        #   # -*- coding: utf-8 -*-
        #   import re
        #   import sys
        #
        #   from gpython import main
        #
        #   if __name__ == '__main__':
        #       sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
        #       sys.exit(main())
        #
        # which does not import pkg_resources. Since we also double-check in
        # gpython itself that pkg_resources and other modules are not imported,
        # we are ok with this.
        if not self.no_ep:
            # regular install
            assert self.gpython_installed == 1
        else:
            # bdist_wheel
            assert self.gpython_installed == 0
            assert len(self.outfiles) == 0


# develop, similarly to install_scripts, is used to handle gpython in `pip install -e` mode.
class develop(XInstallGPython, _develop):
    def write_script(self, script_name, script, mode="t", blockers=()):
        script_name, script = self.transform_script(script_name, script)
        _develop.write_script(self, script_name, script, mode, blockers)

    def install_egg_scripts(self, dist):
        _develop.install_egg_scripts(self, dist)
        assert self.gpython_installed == 1


156 157 158
# requirements of packages under "golang." namespace
R = {
    'cmd.pybench':      {'pytest'},
159
    'pyx.build':        {'setuptools', 'wheel', 'cython', 'setuptools_dso >= 1.4'},
160 161 162 163 164 165 166 167 168 169 170 171 172 173
    'x.perf.benchlib':  {'numpy'},
}
# TODO generate `a.b -> a`, e.g. x.perf = join(x.perf.*); x = join(x.*)
Rall = set()
for pkg in R:
    Rall.update(R[pkg])
R['all'] = Rall

# extras_require <- R
extras_require = {}
for k in sorted(R.keys()):
    extras_require[k] = list(sorted(R[k]))


Kirill Smelkov's avatar
Kirill Smelkov committed
174
setup(
175
    name        = 'pygolang',
176
    version     = version,
177
    description = 'Go-like features for Python and Cython',
Kirill Smelkov's avatar
Kirill Smelkov committed
178 179
    long_description = '%s\n----\n\n%s' % (
                            readfile('README.rst'), readfile('CHANGELOG.rst')),
Kirill Smelkov's avatar
Kirill Smelkov committed
180
    long_description_content_type  = 'text/x-rst',
181
    url         = 'https://lab.nexedi.com/nexedi/pygolang',
Kirill Smelkov's avatar
Kirill Smelkov committed
182
    project_urls= {
183 184
        'Bug Tracker':   'https://lab.nexedi.com/nexedi/pygolang/issues',
        'Source Code':   'https://lab.nexedi.com/nexedi/pygolang',
Kirill Smelkov's avatar
Kirill Smelkov committed
185 186
        'Documentation': 'https://pypi.org/project/pygolang',
    },
Kirill Smelkov's avatar
Kirill Smelkov committed
187 188 189 190
    license     = 'GPLv3+ with wide exception for Open-Source',
    author      = 'Kirill Smelkov',
    author_email= 'kirr@nexedi.com',

191
    keywords    = 'golang go channel goroutine concurrency GOPATH python import gpython gevent cython nogil GIL',
Kirill Smelkov's avatar
Kirill Smelkov committed
192

193
    packages    = find_packages(),
194

195 196
    x_dsos      = [DSO('golang.runtime.libgolang',
                        ['golang/runtime/libgolang.cpp',
197
                         'golang/context.cpp',
Kirill Smelkov's avatar
Kirill Smelkov committed
198
                         'golang/errors.cpp',
Kirill Smelkov's avatar
Kirill Smelkov committed
199
                         'golang/fmt.cpp',
200
                         'golang/io.cpp',
Kirill Smelkov's avatar
Kirill Smelkov committed
201
                         'golang/strings.cpp',
202 203 204 205
                         'golang/sync.cpp',
                         'golang/time.cpp'],
                        depends = [
                            'golang/libgolang.h',
206
                            'golang/context.h',
Kirill Smelkov's avatar
Kirill Smelkov committed
207
                            'golang/cxx.h',
Kirill Smelkov's avatar
Kirill Smelkov committed
208
                            'golang/errors.h',
Kirill Smelkov's avatar
Kirill Smelkov committed
209
                            'golang/fmt.h',
210
                            'golang/io.h',
Kirill Smelkov's avatar
Kirill Smelkov committed
211
                            'golang/strings.h',
212 213
                            'golang/sync.h',
                            'golang/time.h'],
214
                        include_dirs    = ['.', '3rdparty/include'],
215
                        define_macros   = [('BUILDING_LIBGOLANG', None)],
216
                        extra_compile_args = ['-std=gnu++11'], # not c++11 as linux/list.h uses typeof
217 218 219 220 221 222 223 224
                        soversion       = '0.1'),

                    DSO('golang.runtime.libpyxruntime',
                        ['golang/runtime/libpyxruntime.cpp'],
                        depends = ['golang/pyx/runtime.h'],
                        include_dirs    = ['.', sysconfig.get_python_inc()],
                        define_macros   = [('BUILDING_LIBPYXRUNTIME', None)],
                        extra_compile_args = ['-std=c++11'],
225 226
                        soversion       = '0.1',
                        dsos = ['golang.runtime.libgolang'])],
227

228 229 230 231
    ext_modules = [
                    Ext('golang._golang',
                        ['golang/_golang.pyx']),

232 233 234 235 236 237 238
                    Ext('golang.runtime._runtime_thread',
                        ['golang/runtime/_runtime_thread.pyx'],
                        language = "c"),

                    Ext('golang.runtime._runtime_gevent',
                        ['golang/runtime/_runtime_gevent.pyx'],
                        language = 'c'),
Kirill Smelkov's avatar
Kirill Smelkov committed
239

240 241 242 243
                    Ext('golang.pyx.runtime',
                        ['golang/pyx/runtime.pyx'],
                        dsos = ['golang.runtime.libpyxruntime']),

Kirill Smelkov's avatar
Kirill Smelkov committed
244 245 246 247 248
                    Ext('golang._golang_test',
                        ['golang/_golang_test.pyx',
                         'golang/runtime/libgolang_test_c.c',
                         'golang/runtime/libgolang_test.cpp']),

249 250 251 252
                    Ext('golang.pyx._runtime_test',
                        ['golang/pyx/_runtime_test.pyx'],
                        dsos = ['golang.runtime.libpyxruntime']),

253 254 255
                    Ext('golang._context',
                        ['golang/_context.pyx']),

256 257 258 259
                    Ext('golang._cxx_test',
                        ['golang/_cxx_test.pyx',
                         'golang/cxx_test.cpp']),

260 261
                    Ext('golang._errors',
                        ['golang/_errors.pyx']),
262 263 264 265
                    Ext('golang._errors_test',
                        ['golang/_errors_test.pyx',
                         'golang/errors_test.cpp']),

266 267
                    Ext('golang._fmt',
                        ['golang/_fmt.pyx']),
268 269 270 271
                    Ext('golang._fmt_test',
                        ['golang/_fmt_test.pyx',
                         'golang/fmt_test.cpp']),

272 273 274
                    Ext('golang._io',
                        ['golang/_io.pyx']),

275 276 277 278
                    Ext('golang._strings_test',
                        ['golang/_strings_test.pyx',
                         'golang/strings_test.cpp']),

279
                    Ext('golang._sync',
280 281 282
                        ['golang/_sync.pyx'],
                        dsos = ['golang.runtime.libpyxruntime'],
                        define_macros = [('_LIBGOLANG_SYNC_INTERNAL_API', None)]),
283
                    Ext('golang._sync_test',
284 285
                        ['golang/_sync_test.pyx',
                         'golang/sync_test.cpp']),
286

Kirill Smelkov's avatar
Kirill Smelkov committed
287
                    Ext('golang._time',
288 289
                        ['golang/_time.pyx'],
                        dsos = ['golang.runtime.libpyxruntime']),
290
                  ],
291
    include_package_data = True,
Kirill Smelkov's avatar
Kirill Smelkov committed
292

293 294 295 296 297 298 299
    install_requires = ['gevent', 'six', 'decorator', 'Importing;python_version<="2.7"',
                        # pyx.build -> setuptools_dso uses multiprocessing
                        # FIXME geventmp fails on python2, but setuptools_dso
                        # uses multiprocessing only on Python3, so for now we
                        # are ok. https://github.com/karellen/geventmp/pull/2
                        'geventmp;python_version>="3"',
                       ],
300
    extras_require   = extras_require,
Kirill Smelkov's avatar
Kirill Smelkov committed
301

302
    entry_points= {'console_scripts': [
303 304 305
                        # NOTE gpython is handled specially - see XInstallGPython.
                        'gpython  = gpython:main',

306 307 308 309
                        'py.bench = golang.cmd.pybench:main',
                      ]
                  },

310 311 312 313 314
    cmdclass    = {
        'install_scripts':  install_scripts,
        'develop':          develop,
    },

Kirill Smelkov's avatar
Kirill Smelkov committed
315 316
    classifiers = [_.strip() for _ in """\
        Development Status :: 3 - Alpha
317
        Intended Audience :: Developers
Kirill Smelkov's avatar
Kirill Smelkov committed
318 319
        Programming Language :: Python
        Programming Language :: Cython
320
        Programming Language :: Python :: 2
Kirill Smelkov's avatar
Kirill Smelkov committed
321 322 323 324 325 326 327 328 329
        Programming Language :: Python :: 2.7
        Programming Language :: Python :: 3
        Programming Language :: Python :: 3.5
        Programming Language :: Python :: 3.6
        Programming Language :: Python :: 3.7
        Programming Language :: Python :: Implementation :: CPython
        Programming Language :: Python :: Implementation :: PyPy
        Topic :: Software Development :: Interpreters
        Topic :: Software Development :: Libraries :: Python Modules\
Kirill Smelkov's avatar
Kirill Smelkov committed
330 331
    """.splitlines()]
)