Skip to content

Commit

Permalink
Updating native-libraries to use doctest for OS X "extra dependenci…
Browse files Browse the repository at this point in the history
…es".

This is part of an effort to keep the doctests from getting stale.

In the process:

- Added a special `scripts/osx/nox-install-for-doctest.sh` which will
  create a "delocated" wheel and install it when running
  `nox -s doctest`
- Added `:linux-only:` and `:mac-os-x-only:` options for the
  `doctest` directive (in `doctest_monkeypatch.py`)

Filed:

sphinx-doc/sphinx#4183
  • Loading branch information
dhermes committed Oct 23, 2017
1 parent 751df41 commit 85250fd
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 24 deletions.
44 changes: 32 additions & 12 deletions docs/doctest_monkeypatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
* ``TestcodeDirective``
* ``TestoutputDirective``
with some extra options ``:windows-skip:`` and ``:windows-only:``.
with some extra options:
* ``:linux-only:``
* ``:mac-os-x-only:``
* ``:windows-skip:``
* ``:windows-only:``
Also monkey-patches ``TestDirective.run`` to honor these directives
and skip a test based on the options.
Expand All @@ -32,15 +37,27 @@

import doctest
import os
import sys

import docutils.parsers.rst
import sphinx.ext.doctest


IS_WINDOWS = os.name == 'nt'
IS_LINUX = sys.platform in ('linux', 'linux2')
IS_MAC_OS_X = sys.platform == 'darwin'
LINUX_ONLY = 'linux-only'
MAC_OS_X_ONLY = 'mac-os-x-only'
WINDOWS_ONLY = 'windows-only'
WINDOWS_SKIP = 'windows-skip'
OPTION_NAMES = (
LINUX_ONLY,
MAC_OS_X_ONLY,
WINDOWS_ONLY,
WINDOWS_SKIP,
)
OLD_RUN = sphinx.ext.doctest.TestDirective.run
SKIP_FLAG = doctest.OPTIONFLAGS_BY_NAME['SKIP']


def custom_run(directive):
Expand All @@ -62,19 +79,26 @@ def custom_run(directive):
"""
node, = OLD_RUN(directive)

if WINDOWS_ONLY in directive.options and WINDOWS_SKIP in directive.options:
num_options = sum(1 for name in OPTION_NAMES if name in directive.options)
if num_options > 1:
raise RuntimeError(
'At most one option can be used among', WINDOWS_ONLY, WINDOWS_SKIP)
'At most one option can be used among', *OPTION_NAMES)

if LINUX_ONLY in directive.options:
if not IS_LINUX:
node['options'][SKIP_FLAG] = True

if MAC_OS_X_ONLY in directive.options:
if not IS_MAC_OS_X:
node['options'][SKIP_FLAG] = True

if WINDOWS_ONLY in directive.options:
if not IS_WINDOWS:
flag = doctest.OPTIONFLAGS_BY_NAME['SKIP']
node['options'][flag] = True # Skip the test
node['options'][SKIP_FLAG] = True

if WINDOWS_SKIP in directive.options:
if IS_WINDOWS:
flag = doctest.OPTIONFLAGS_BY_NAME['SKIP']
node['options'][flag] = True # Skip the test
node['options'][SKIP_FLAG] = True

return [node]

Expand All @@ -87,18 +111,14 @@ def setup(app):
"""
sphinx.ext.doctest.TestDirective.run = custom_run

options = (
WINDOWS_ONLY,
WINDOWS_SKIP,
)
directive_types = (
sphinx.ext.doctest.DoctestDirective,
sphinx.ext.doctest.TestcodeDirective,
sphinx.ext.doctest.TestoutputDirective,
)
for directive in directive_types:
option_spec = directive.option_spec
for option in options:
for option in OPTION_NAMES:
if option in option_spec:
raise RuntimeError(
'Unexpected option in option spec', option)
Expand Down
55 changes: 45 additions & 10 deletions docs/native-libraries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ C Headers

The C headers for ``libbezier`` will be included in the installed package

.. testsetup:: show-headers, show-lib, show-dll, show-pxd
.. testsetup:: show-headers, show-lib, show-dll, show-pxd, os-x-dylibs

import os
import textwrap
Expand Down Expand Up @@ -117,6 +117,10 @@ The C headers for ``libbezier`` will be included in the installed package
# Allow this value to be re-used.
include_directory = get_include()

# OS X specific.
base_dir = os.path.dirname(include_directory.path)
dylibs_directory = os.path.join(base_dir, '.dylibs')

.. doctest:: show-headers

>>> include_directory = bezier.get_include()
Expand All @@ -132,7 +136,7 @@ The C headers for ``libbezier`` will be included in the installed package
surface.h
bezier.h

.. testcleanup:: show-headers, show-lib, show-dll, show-pxd
.. testcleanup:: show-headers, show-lib, show-dll, show-pxd, os-x-dylibs

# Restore the monkey-patched functions.
bezier.get_include = original_get_include
Expand Down Expand Up @@ -275,19 +279,47 @@ Mac OS X
The command line tool `delocate`_ adds a ``bezier/.dylibs`` directory
with copies of ``libgfortran``, ``libquadmath`` and ``libgcc_s``:

.. code-block:: console
.. doctest:: os-x-dylibs
:mac-os-x-only:

$ cd .../site-packages/bezier/.dylibs
$ ls -1
libgcc_s.1.dylib
libgfortran.4.dylib
libquadmath.0.dylib
>>> dylibs_directory
'.../site-packages/bezier/.dylibs'
>>> print_tree(dylibs_directory)
.dylibs/
libgcc_s.1.dylib
libgfortran.4.dylib
libquadmath.0.dylib

For example, the ``_curve_speedup`` module depends on the local copy
of ``libgfortran``:

.. code-block:: console
.. testsetup:: os-x-extension, os-x-delocated-libgfortran

import os
import subprocess

import bezier


def invoke_shell(*args):
print('$ ' + ' '.join(args))
prev_cwd = os.getcwd()
os.chdir(bezier_directory)
# NOTE: We print to the stdout of the doctest, rather than using
# `subprocess.call()` directly.
output_bytes = subprocess.check_output(args).rstrip()
print(output_bytes.decode('utf-8'))
os.chdir(prev_cwd)

bezier_directory = os.path.dirname(bezier.__file__)

.. doctest:: os-x-extension
:options: +NORMALIZE_WHITESPACE
:mac-os-x-only:
:pyversion: >= 3.6

>>> invoke_shell('otool', '-L', '_curve_speedup.cpython-36m-darwin.so')
$ otool -L _curve_speedup.cpython-36m-darwin.so
_curve_speedup.cpython-36m-darwin.so:
@loader_path/.dylibs/libgfortran.4.dylib (...)
Expand All @@ -297,8 +329,11 @@ Though the Python extension modules (``.so`` files) only depend on
``libgfortran``, they indirectly depend on ``libquadmath`` and
``libgcc_s``:

.. code-block:: console
.. doctest:: os-x-delocated-libgfortran
:options: +NORMALIZE_WHITESPACE
:mac-os-x-only:

>>> invoke_shell('otool', '-L', '.dylibs/libgfortran.4.dylib')
$ otool -L .dylibs/libgfortran.4.dylib
.dylibs/libgfortran.4.dylib:
/DLC/bezier/libgfortran.4.dylib (...)
Expand Down
11 changes: 9 additions & 2 deletions nox.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
import glob
import os
import shutil
import sys
import tempfile

import nox
import py.path


NUMPY = 'numpy >= 1.13.3'
if os.name == 'nt':
IS_MAC_OS_X = sys.platform == 'darwin'
IS_WINDOWS = os.name == 'nt'
if IS_WINDOWS:
# Windows wheels don't exist until 1.0.0.
SCIPY = 'scipy >= 1.0.0rc1'
else:
Expand Down Expand Up @@ -198,7 +201,11 @@ def doctest(session):
local_deps = DOCS_DEPS + (MOCK_DEP,)
session.install(*local_deps)
# Install this package.
session.install('.')
if IS_MAC_OS_X:
command = get_path('scripts', 'osx', 'nox-install-for-doctest.sh')
session.run(command)
else:
session.install('.')

# Run the script for building docs and running doctests.
run_args = get_doctest_args(session)
Expand Down
37 changes: 37 additions & 0 deletions scripts/osx/nox-install-for-doctest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -e -x

# 0. Install the ``delocate`` tool.
pip install --upgrade delocate

# 1. Build the wheel from source.
BASIC_DIR=$(mktemp -d)
pip wheel . --wheel-dir ${BASIC_DIR}

# 2. "delocate" the built wheel.
DELOCATED_DIR=$(mktemp -d)
# NOTE: This intentionally does not use ``--check-archs``.
delocate-wheel \
--wheel-dir ${DELOCATED_DIR} \
--verbose \
${BASIC_DIR}/bezier*.whl

# 3. Install from the "delocated" wheel.
pip install ${DELOCATED_DIR}/bezier*.whl

# Clean up temporary directories.
rm -fr ${BASIC_DIR}
rm -fr ${DELOCATED_DIR}

0 comments on commit 85250fd

Please sign in to comment.