Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Sphinx API hosted on GitHub Pages #832

Merged
merged 11 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/workflows/sphinx.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Sphinx
on:
push:
branches:
- master
paths:
- 'docs/**'
- 'src/**'
pull_request:
paths:
- 'docs/**'
- 'src/**'


jobs:
build:
name: "HTML Artifact: Py${{matrix.python-version}} ${{matrix.os-version}}"
runs-on: ${{ matrix.os-version }}-latest
strategy:
fail-fast: false
matrix:
python-version:
- 2.7
- 3.6
- 3.7
- 3.8
os-version:
- 'macOS'
- 'ubuntu'
- 'windows'

steps:
- name: Checkout
uses: actions/checkout@v1

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}

- name: Sphinx Build
run: python docs/entrypoint.py --no-docker

- uses: actions/upload-artifact@master
with:
name: html-docs-${{ matrix.python-version }}-${{ matrix.os-version }}
path: docs/_build

publish:
name: Publish to GitHub pages
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
needs: build

steps:
- uses: actions/download-artifact@master
with:
name: html-docs-3.7-ubuntu
path: .

- name: Setup git repository
run: |
git config --global color.ui always
git init .
git config --global user.name "github.com/${{ github.actor }}"
git config --global user.email "${{ github.actor }}@${{ github.sha }}"
git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
git checkout -B gh-pages

- name: Git commit, force push gh-pages
run: |
git add .
git commit \
-m "Generated from GitHub "${{ github.workflow }}" Workflow" \
-m "See https://github.com/${{ github.repository }}/runs/${GITHUB_ACTION}" \
&& git push --force origin gh-pages \
|| echo "Nothing new to commit and push"
12 changes: 6 additions & 6 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
needs_sphinx = '1.3'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
Expand All @@ -38,8 +38,8 @@
'sphinx.ext.coverage',
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode',
# 'sphinx.ext.napoleon', # this works with Sphinx 1.3+
'sphinxcontrib.napoleon', # this works with Sphinx <= 1.2.
'sphinx.ext.napoleon',
'sphinx.ext.githubpages',
]

# Add any paths that contain templates here, relative to this directory.
Expand Down Expand Up @@ -79,7 +79,7 @@

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The reST default role (used for this markup: `text`) to use for all
# documents.
Expand All @@ -97,7 +97,7 @@
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = None

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
Expand All @@ -110,7 +110,7 @@

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
html_theme = 'sphinx_rtd_theme'

# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
Expand Down
173 changes: 173 additions & 0 deletions docs/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals


import argparse
import errno
import os
import re
import subprocess
import tempfile


THIS_FILE = os.path.abspath(__file__)
THIS_DIR = os.path.dirname(THIS_FILE)
REZ_SOURCE_DIR = os.getenv("REZ_SOURCE_DIR", os.path.dirname(THIS_DIR))
REQUIREMENTS = ['sphinx_rtd_theme', REZ_SOURCE_DIR]
DEST_DIR = os.path.join("docs", "_build")
PIP_PATH_REGEX = re.compile(r"'([^']+)' which is not on PATH.")


class CliParser(argparse.ArgumentParser):
"""Parser flags, using global variables as defaults."""
INIT_DEFAULTS = {
"prog": "entrypoint",
"description": "Build Sphinx Python API docs",
}

def __init__(self, **kwargs):
"""Setup default arguments and parser description/program name.

If no parser description/program name are given, default ones will
be assigned.

Args:
kwargs (dict[str]):
Same key word arguments taken by
``argparse.ArgumentParser.__init__()``
"""
for key, value in self.INIT_DEFAULTS.items():
kwargs.setdefault(key, value)
super(CliParser, self).__init__(**kwargs)

self.add_argument(
"--no-docker",
action="store_false",
dest="docker",
help="Don't run build processes inside Docker container.",
)
self.add_argument(
"requirement",
nargs="*",
help="Additional packages to pip install.",
)


def construct_docker_run_args():
"""Create subprocess arguments list for running this script inside docker.

Returns:
list[str]: Arguments list for ``subprocess.call()``.
"""
docker_args = ["docker", "run", "--interactive", "--rm"]

if os.sys.stdin.isatty() and os.sys.stdout.isatty():
docker_args.append("--tty")

if os.name == "posix":
user_group_ids = os.getuid(), os.getgid()
docker_args += ["--user", ":".join(map(str, user_group_ids))]

docker_args += [
"--workdir", REZ_SOURCE_DIR,
"--volume", ":".join([REZ_SOURCE_DIR, REZ_SOURCE_DIR]),
"python:{v.major}.{v.minor}".format(v=os.sys.version_info),
"python", THIS_FILE, "--no-docker"
]

return docker_args


def print_call(cmdline_args, *print_args, **print_kwargs):
"""Print command line call for given arguments.


Args:
cmdline_args (list): Command line arguments to print for.
print_args (dict): Additional arguments for print function.
print_kwargs (dict): Keyword arguments for print function.
"""
width = os.getenv('COLUMNS', 80)
out_file = print_kwargs.setdefault('file', os.sys.stdout)
message = '{:=^{width}}{nl}{}{nl:=<{width}}'.format(
" Calling ",
subprocess.list2cmdline(cmdline_args),
nl=os.linesep,
width=width
)
print(message, *print_args, **print_kwargs)
out_file.flush()


def path_with_pip_scripts(install_stderr, path_env=None):
"""Create new PATH variable with missing pip scripts paths added to it.

Args:
install_stderr (str): stderr output from pip install command.
path_env (str): Custom PATH env value to start off with.

Returns:
str: New PATH variable value.
"""
if path_env is None:
path_env = os.getenv('PATH', '')
paths = path_env.split(os.pathsep)

for match in PIP_PATH_REGEX.finditer(install_stderr):
script_path = match.group(1)
if script_path not in paths:
paths.append(script_path)

return os.pathsep.join(paths)


def _cli():
"""Main routine for when called from command line."""
args = CliParser().parse_args()

if args.docker:
docker_args = construct_docker_run_args() + args.requirement
print_call(docker_args)
os.sys.exit(subprocess.call(docker_args))
else:
docs_env = os.environ.copy()

# Fake user's $HOME in container to fix permission issues
if os.name == "posix" and os.path.expanduser("~") == "/":
docs_env['HOME'] = tempfile.mkdtemp()

# Run pip install for required docs building packages
pip_args = ['pip', 'install', '--user']
pip_args += REQUIREMENTS + args.requirement
with tempfile.TemporaryFile() as stderr_file:
subprocess.check_call(pip_args, env=docs_env, stderr=stderr_file)
stderr_file.seek(0)
stderr = str(stderr_file.read())
docs_env['PATH'] = path_with_pip_scripts(stderr)

# Run sphinx-build docs, falling back to use sphinx-build.exe
sphinx_build = 'sphinx-build'
build_args = ['docs', DEST_DIR]
sphinx_build_args = [sphinx_build] + build_args
try:
print_call(sphinx_build_args)
os.sys.exit(subprocess.call(sphinx_build_args, env=docs_env))
except OSError as error:
if error.errno == errno.ENOENT:
# Windows Py2.7 needs full .exe path, see GitHub workflows run:
# https://github.com/wwfxuk/rez/runs/380399547
latest_path = docs_env['PATH'].split(os.pathsep)[-1]
sphinx_build = os.path.join(latest_path, sphinx_build + '.exe')

sphinx_build_args = [sphinx_build] + build_args
print_call(sphinx_build_args)
os.sys.exit(subprocess.call(sphinx_build_args, env=docs_env))
else:
raise


if __name__ == "__main__":
_cli()