diff --git a/setup.py b/setup.py index 52d3d2241ef..01e91808543 100644 --- a/setup.py +++ b/setup.py @@ -119,6 +119,7 @@ PACKAGE_DIRECTORIES = { } INSTALL_REQUIRES = ( + 'six>=1.10', 'enum34>=1.0.4', 'futures>=2.2.0', # TODO(atash): eventually split the grpcio package into a metapackage @@ -131,6 +132,7 @@ SETUP_REQUIRES = ( ) + INSTALL_REQUIRES COMMAND_CLASS = { + 'install': commands.Install, 'doc': commands.SphinxDocumentation, 'build_proto_modules': commands.BuildProtoModules, 'build_project_metadata': commands.BuildProjectMetadata, @@ -138,6 +140,7 @@ COMMAND_CLASS = { 'build_ext': commands.BuildExt, 'gather': commands.Gather, 'run_interop': commands.RunInterop, + 'bdist_egg_grpc_custom': commands.BdistEggCustomName, } # Ensure that package data is copied over before any commands have been run: @@ -187,7 +190,7 @@ else: setuptools.setup( name='grpcio', - version='0.12.0b5', + version='0.12.0b6', license=LICENSE, ext_modules=CYTHON_EXTENSION_MODULES, packages=list(PACKAGES), diff --git a/src/python/grpcio/commands.py b/src/python/grpcio/commands.py index 29b506abe0a..a6b8ad3fc05 100644 --- a/src/python/grpcio/commands.py +++ b/src/python/grpcio/commands.py @@ -30,15 +30,22 @@ """Provides distutils command classes for the GRPC Python setup process.""" import distutils +import glob import os import os.path +import platform import re +import shutil import subprocess import sys +import traceback import setuptools +from setuptools.command import bdist_egg from setuptools.command import build_ext from setuptools.command import build_py +from setuptools.command import easy_install +from setuptools.command import install from setuptools.command import test import support @@ -58,6 +65,129 @@ class CommandError(Exception): """Simple exception class for GRPC custom commands.""" +# TODO(atash): Remove this once PyPI has better Linux bdist support. See +# https://bitbucket.org/pypa/pypi/issues/120/binary-wheels-for-linux-are-not-supported +def _get_linux_bdist_egg(decorated_basename, target_egg_basename): + """Returns a string path to a .egg file for Linux to install. + + If we can retrieve a pre-compiled egg from online, uses it. Else, emits a + warning and builds from source. + """ + # Break import style to ensure that setup.py has had a chance to install the + # relevant package eggs. + from six.moves.urllib import request + decorated_path = decorated_basename + '.egg' + try: + url = ( + 'https://storage.googleapis.com/grpc-precompiled-binaries/' + 'python/{target}' + .format(target=decorated_path)) + egg_data = request.urlopen(url).read() + except IOError as error: + raise CommandError( + '{}\n\nCould not find the bdist egg {}: {}' + .format(traceback.format_exc(), decorated_path, error.message)) + # Our chosen local egg path. + egg_path = target_egg_basename + '.egg' + try: + with open(egg_path, 'w') as egg_file: + egg_file.write(egg_data) + except IOError as error: + raise CommandError( + '{}\n\nCould not write grpcio egg: {}' + .format(traceback.format_exc(), error.message)) + return egg_path + + +class EggNameMixin(object): + + def egg_name(self, with_custom): + """ + Args: + with_custom: Boolean describing whether or not to decorate the egg name + with custom gRPC-specific target information. + """ + egg_command = self.get_finalized_command('bdist_egg') + base = os.path.splitext(os.path.basename(egg_command.egg_output))[0] + if with_custom: + flavor = 'ucs2' if sys.maxunicode == 65535 else 'ucs4' + return '{base}-{flavor}'.format(base=base, flavor=flavor) + else: + return base + + +class Install(install.install, EggNameMixin): + """Custom Install command for gRPC Python. + + This is for bdist shims and whatever else we might need a custom install + command for. + """ + + user_options = install.install.user_options + [ + # TODO(atash): remove this once manylinux gets on PyPI. See + # https://bitbucket.org/pypa/pypi/issues/120/binary-wheels-for-linux-are-not-supported + ('use-linux-bdist', None, + 'Whether to retrieve a binary for Linux instead of building from ' + 'source.'), + ] + + def initialize_options(self): + install.install.initialize_options(self) + self.use_linux_bdist = False + + def finalize_options(self): + install.install.finalize_options(self) + + def run(self): + if self.use_linux_bdist: + try: + egg_path = _get_linux_bdist_egg(self.egg_name(True), + self.egg_name(False)) + except CommandError as error: + sys.stderr.write( + '\nWARNING: Failed to acquire grpcio prebuilt binary:\n' + '{}.\n\n'.format(error.message)) + raise + try: + self._run_bdist_retrieval_install(egg_path) + except Exception as error: + # if anything else happens (and given how there's no way to really know + # what's happening in setuptools here, I mean *anything*), warn the user + # and fall back to building from source. + sys.stderr.write( + '{}\nWARNING: Failed to install grpcio prebuilt binary.\n\n' + .format(traceback.format_exc())) + install.install.run(self) + else: + install.install.run(self) + + # TODO(atash): Remove this once PyPI has better Linux bdist support. See + # https://bitbucket.org/pypa/pypi/issues/120/binary-wheels-for-linux-are-not-supported + def _run_bdist_retrieval_install(self, bdist_egg): + easy_install = self.distribution.get_command_class('easy_install') + easy_install_command = easy_install( + self.distribution, args='x', root=self.root, record=self.record, + ) + easy_install_command.ensure_finalized() + easy_install_command.always_copy_from = '.' + easy_install_command.package_index.scan(glob.glob('*.egg')) + arguments = [bdist_egg] + if setuptools.bootstrap_install_from: + args.insert(0, setuptools.bootstrap_install_from) + easy_install_command.args = arguments + easy_install_command.run() + setuptools.bootstrap_install_from = None + + +class BdistEggCustomName(bdist_egg.bdist_egg, EggNameMixin): + """Thin wrapper around the bdist_egg command to build with our custom name.""" + + def run(self): + bdist_egg.bdist_egg.run(self) + target = os.path.join(self.dist_dir, '{}.egg'.format(self.egg_name(True))) + shutil.move(self.get_outputs()[0], target) + + class SphinxDocumentation(setuptools.Command): """Command to generate documentation via sphinx."""