diff --git a/src/python/src/.gitignore b/src/python/src/.gitignore index 144e5012377..d89f3db999d 100644 --- a/src/python/src/.gitignore +++ b/src/python/src/.gitignore @@ -2,3 +2,7 @@ MANIFEST grpcio.egg-info/ build/ dist/ +*.egg +*.egg/ +*.eggs/ +doc/ diff --git a/src/python/src/MANIFEST.in b/src/python/src/MANIFEST.in index 6f32db05488..498b55f20ab 100644 --- a/src/python/src/MANIFEST.in +++ b/src/python/src/MANIFEST.in @@ -1 +1,2 @@ graft grpc +include commands.py diff --git a/src/python/src/commands.py b/src/python/src/commands.py new file mode 100644 index 00000000000..8e878550114 --- /dev/null +++ b/src/python/src/commands.py @@ -0,0 +1,75 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Provides distutils command classes for the GRPC Python setup process.""" + +import os +import os.path +import sys + +import setuptools + +_CONF_PY_ADDENDUM = """ +extensions.append('sphinx.ext.napoleon') +napoleon_google_docstring = True +napoleon_numpy_docstring = True + +html_theme = 'sphinx_rtd_theme' +""" + +class SphinxDocumentation(setuptools.Command): + """Command to generate documentation via sphinx.""" + + description = '' + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + # We import here to ensure that setup.py has had a chance to install the + # relevant package eggs first. + import sphinx + import sphinx.apidoc + metadata = self.distribution.metadata + src_dir = os.path.join( + os.getcwd(), self.distribution.package_dir['grpc']) + sys.path.append(src_dir) + sphinx.apidoc.main([ + '', '--force', '--full', '-H', metadata.name, '-A', metadata.author, + '-V', metadata.version, '-R', metadata.version, + '-o', os.path.join('doc', 'src'), src_dir]) + conf_filepath = os.path.join('doc', 'src', 'conf.py') + with open(conf_filepath, 'a') as conf_file: + conf_file.write(_CONF_PY_ADDENDUM) + sphinx.main(['', os.path.join('doc', 'src'), os.path.join('doc', 'build')]) + diff --git a/src/python/src/setup.cfg b/src/python/src/setup.cfg new file mode 100644 index 00000000000..8f696136320 --- /dev/null +++ b/src/python/src/setup.cfg @@ -0,0 +1,2 @@ +[build_ext] +inplace=1 diff --git a/src/python/src/setup.py b/src/python/src/setup.py index a857ae98cc2..0310a83a7ba 100644 --- a/src/python/src/setup.py +++ b/src/python/src/setup.py @@ -30,11 +30,17 @@ """A setup module for the GRPC Python package.""" import os +import os.path import sys from distutils import core as _core import setuptools +# Ensure we're in the proper directory whether or not we're being used by pip. +os.chdir(os.path.dirname(os.path.abspath(__file__))) + +# Break import-style to ensure we can actually find our commands module. +import commands # Use environment variables to determine whether or not the Cython extension # should *use* Cython or use the generated C files. Note that this requires the @@ -98,15 +104,27 @@ _PACKAGE_DIRECTORIES = { 'grpc.framework': 'grpc/framework', } +_INSTALL_REQUIRES = ( + 'enum34==1.0.4', + 'futures==2.2.0', + 'protobuf==3.0.0a3' +) + +_SETUP_REQUIRES = ( + 'sphinx>=1.3', +) + _INSTALL_REQUIRES + +_COMMAND_CLASS = { + 'doc': commands.SphinxDocumentation +} + setuptools.setup( name='grpcio', version='0.10.0a0', ext_modules=_EXTENSION_MODULES, packages=list(_PACKAGES), package_dir=_PACKAGE_DIRECTORIES, - install_requires=[ - 'enum34==1.0.4', - 'futures==2.2.0', - 'protobuf==3.0.0a3' - ] + install_requires=_INSTALL_REQUIRES, + setup_requires=_SETUP_REQUIRES, + cmdclass=_COMMAND_CLASS ) diff --git a/tools/distrib/python/.gitignore b/tools/distrib/python/.gitignore new file mode 100644 index 00000000000..8ff3b086e3f --- /dev/null +++ b/tools/distrib/python/.gitignore @@ -0,0 +1 @@ +distrib_virtualenv/ diff --git a/tools/distrib/python/docgen.py b/tools/distrib/python/docgen.py new file mode 100755 index 00000000000..3ab84a6ba1d --- /dev/null +++ b/tools/distrib/python/docgen.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import argparse +import os +import os.path +import shutil +import subprocess +import tempfile + +parser = argparse.ArgumentParser() +parser.add_argument('--config', metavar='c', type=str, nargs=1, + help='GRPC/GPR libraries build configuration', + default='opt') +parser.add_argument('--submit', action='store_true') +parser.add_argument('--gh-user', type=str, help='GitHub user to push as.') +parser.add_argument('--gh-repo-owner', type=str, + help=('Owner of the GitHub repository to be pushed; ' + 'defaults to --gh-user.')) +parser.add_argument('--doc-branch', type=str) +args = parser.parse_args() + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +PROJECT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, '..', '..', '..')) + +CONFIG = args.config +SETUP_PATH = os.path.join(PROJECT_ROOT, 'src/python/src/setup.py') +DOC_PATH = os.path.join(PROJECT_ROOT, 'src/python/src/doc/build') +INCLUDE_PATH = os.path.join(PROJECT_ROOT, 'include') +LIBRARY_PATH = os.path.join(PROJECT_ROOT, 'libs/{}'.format(CONFIG)) +VIRTUALENV_DIR = os.path.join(SCRIPT_DIR, 'distrib_virtualenv') +VIRTUALENV_PYTHON_PATH = os.path.join(VIRTUALENV_DIR, 'bin', 'python') + +environment = os.environ.copy() +environment.update({ + 'CONFIG': CONFIG, + 'CFLAGS': '-I{}'.format(INCLUDE_PATH), + 'LDFLAGS': '-L{}'.format(LIBRARY_PATH), + 'LD_LIBRARY_PATH': LIBRARY_PATH +}) + +subprocess_arguments_list = [ + {'args': ['make'], 'cwd': PROJECT_ROOT}, + {'args': ['virtualenv', VIRTUALENV_DIR], 'env': environment}, + {'args': [VIRTUALENV_PYTHON_PATH, SETUP_PATH, 'build'], 'env': environment}, + {'args': [VIRTUALENV_PYTHON_PATH, SETUP_PATH, 'doc'], 'env': environment}, +] + +for subprocess_arguments in subprocess_arguments_list: + subprocess.check_call(**subprocess_arguments) + +if args.submit: + assert args.gh_user + assert args.doc_branch + github_user = args.gh_user + github_repository_owner = ( + args.gh_repo_owner if args.gh_repo_owner else gh_user) + # Create a temporary directory out of tree, checkout gh-pages from the + # specified repository, edit it, and push it. It's up to the user to then go + # onto GitHub and make a PR against grpc/grpc:gh-pages. + repo_parent_dir = tempfile.mkdtemp() + repo_dir = os.path.join(repo_parent_dir, 'grpc') + python_doc_dir = os.path.join(repo_dir, 'python') + doc_branch = args.doc_branch + + subprocess.check_call([ + 'git', 'clone', 'https://{}@github.com/{}/grpc'.format( + github_user, github_repository_owner) + ], cwd=repo_parent_dir) + subprocess.check_call([ + 'git', 'remote', 'add', 'upstream', 'https://github.com/grpc/grpc' + ], cwd=repo_dir) + subprocess.check_call(['git', 'fetch', 'upstream'], cwd=repo_dir) + subprocess.check_call([ + 'git', 'checkout', 'upstream/gh-pages', '-b', doc_branch + ], cwd=repo_dir) + shutil.rmtree(python_doc_dir, ignore_errors=True) + shutil.copytree(DOC_PATH, python_doc_dir) + subprocess.check_call(['git', 'add', '--all'], cwd=repo_dir) + subprocess.check_call([ + 'git', 'commit', '-m', 'Auto-update Python documentation' + ], cwd=repo_dir) + subprocess.check_call([ + 'git', 'push', '--set-upstream', 'origin', doc_branch + ], cwd=repo_dir) + shutil.rmtree(repo_parent_dir)