summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2018-01-28 00:18:33 +0900
committerTakeshi KOMIYA <i.tkomiya@gmail.com>2018-01-28 00:18:33 +0900
commitaf25fa123d2d2cf0ea8073b61120f5205f07deb8 (patch)
tree8c94fcdb0b30527b68bc2c33918655e5f3b05d1a
parent6897b80fc4357ef99d2f5398d872f35e06c15af6 (diff)
parentb6efff799069666d035932f1b413ae58627a1375 (diff)
downloadsphinx-git-af25fa123d2d2cf0ea8073b61120f5205f07deb8.tar.gz
Merge branch 'stable' into 1.7-release
-rw-r--r--CHANGES2
-rw-r--r--sphinx/builders/html.py103
-rw-r--r--sphinx/ext/apidoc.py13
-rw-r--r--tests/roots/test-apidoc-pep420/a/b/e/__init__.py0
-rw-r--r--tests/roots/test-apidoc-pep420/a/b/e/f.py1
-rw-r--r--tests/test_ext_apidoc.py60
6 files changed, 142 insertions, 37 deletions
diff --git a/CHANGES b/CHANGES
index f4d28a243..8856450ea 100644
--- a/CHANGES
+++ b/CHANGES
@@ -204,6 +204,8 @@ Bugs fixed
* #4438: math: math with labels with whitespace cause html error
* #2437: make full reference for classes, aliased with "alias of"
* #4434: pure numbers as link targets produce warning
+* #4477: Build fails after building specific files
+* #4449: apidoc: include "empty" packages that contain modules
Testing
--------
diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index dcbc59280..c53a01406 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -54,9 +54,11 @@ from sphinx.environment.adapters.indexentries import IndexEntries
if False:
# For type annotation
- from typing import Any, Dict, Iterable, Iterator, List, Type, Tuple, Union # NOQA
- from sphinx.domains import Domain, Index # NOQA
+ from typing import Any, Dict, IO, Iterable, Iterator, List, Type, Tuple, Union # NOQA
from sphinx.application import Sphinx # NOQA
+ from sphinx.config import Config # NOQA
+ from sphinx.domains import Domain, Index # NOQA
+ from sphinx.util.tags import Tags # NOQA
# Experimental HTML5 Writer
if is_html5_writer_available():
@@ -147,6 +149,56 @@ class Stylesheet(text_type):
return self
+class BuildInfo(object):
+ """buildinfo file manipulator.
+
+ HTMLBuilder and its family are storing their own envdata to ``.buildinfo``.
+ This class is a manipulator for the file.
+ """
+
+ @classmethod
+ def load(cls, f):
+ # type: (IO) -> BuildInfo
+ try:
+ lines = f.readlines()
+ assert lines[0].rstrip() == '# Sphinx build info version 1'
+ assert lines[2].startswith('config: ')
+ assert lines[3].startswith('tags: ')
+
+ build_info = BuildInfo()
+ build_info.config_hash = lines[2].split()[1].strip()
+ build_info.tags_hash = lines[3].split()[1].strip()
+ return build_info
+ except Exception as exc:
+ raise ValueError('build info file is broken: %r' % exc)
+
+ def __init__(self, config=None, tags=None):
+ # type: (Config, Tags) -> None
+ self.config_hash = u''
+ self.tags_hash = u''
+
+ if config:
+ values = dict((c.name, c.value) for c in config.filter('html'))
+ self.config_hash = get_stable_hash(values)
+
+ if tags:
+ self.tags_hash = get_stable_hash(sorted(tags))
+
+ def __eq__(self, other): # type: ignore
+ # type: (BuildInfo) -> bool
+ return (self.config_hash == other.config_hash and
+ self.tags_hash == other.tags_hash)
+
+ def dump(self, f):
+ # type: (IO) -> None
+ f.write('# Sphinx build info version 1\n'
+ '# This file hashes the configuration used when building these files.'
+ ' When it is not found, a full rebuild will be done.\n'
+ 'config: %s\n'
+ 'tags: %s\n' %
+ (self.config_hash, self.tags_hash))
+
+
class StandaloneHTMLBuilder(Builder):
"""
Builds standalone HTML docs.
@@ -191,9 +243,7 @@ class StandaloneHTMLBuilder(Builder):
def init(self):
# type: () -> None
- # a hash of all config values that, if changed, cause a full rebuild
- self.config_hash = '' # type: unicode
- self.tags_hash = '' # type: unicode
+ self.build_info = BuildInfo(self.config, self.tags)
# basename of images directory
self.imagedir = '_images'
# section numbers for headings in the currently visited document
@@ -274,32 +324,19 @@ class StandaloneHTMLBuilder(Builder):
def get_outdated_docs(self):
# type: () -> Iterator[unicode]
- cfgdict = dict((confval.name, confval.value) for confval in self.config.filter('html'))
- self.config_hash = get_stable_hash(cfgdict)
- self.tags_hash = get_stable_hash(sorted(self.tags))
- old_config_hash = old_tags_hash = ''
try:
with open(path.join(self.outdir, '.buildinfo')) as fp:
- version = fp.readline()
- if version.rstrip() != '# Sphinx build info version 1':
- raise ValueError
- fp.readline() # skip commentary
- cfg, old_config_hash = fp.readline().strip().split(': ')
- if cfg != 'config':
- raise ValueError
- tag, old_tags_hash = fp.readline().strip().split(': ')
- if tag != 'tags':
- raise ValueError
- except ValueError:
- logger.warning('unsupported build info format in %r, building all',
- path.join(self.outdir, '.buildinfo'))
- except Exception:
+ buildinfo = BuildInfo.load(fp)
+
+ if self.build_info != buildinfo:
+ for docname in self.env.found_docs:
+ yield docname
+ return
+ except ValueError as exc:
+ logger.warning('Failed to read build info file: %r', exc)
+ except IOError:
+ # ignore errors on reading
pass
- if old_config_hash != self.config_hash or \
- old_tags_hash != self.tags_hash:
- for docname in self.env.found_docs:
- yield docname
- return
if self.templates:
template_mtime = self.templates.newest_template_mtime()
@@ -777,14 +814,9 @@ class StandaloneHTMLBuilder(Builder):
def write_buildinfo(self):
# type: () -> None
- # write build info file
try:
with open(path.join(self.outdir, '.buildinfo'), 'w') as fp:
- fp.write('# Sphinx build info version 1\n'
- '# This file hashes the configuration used when building'
- ' these files. When it is not found, a full rebuild will'
- ' be done.\nconfig: %s\ntags: %s\n' %
- (self.config_hash, self.tags_hash))
+ self.build_info.dump(fp)
except IOError as exc:
logger.warning('Failed to write build info file: %r', exc)
@@ -1257,8 +1289,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
def init(self):
# type: () -> None
- self.config_hash = ''
- self.tags_hash = ''
+ self.build_info = BuildInfo(self.config, self.tags)
self.imagedir = '_images'
self.current_docname = None
self.theme = None # no theme necessary
diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py
index cec9d8138..efe8b780a 100644
--- a/sphinx/ext/apidoc.py
+++ b/sphinx/ext/apidoc.py
@@ -18,6 +18,7 @@
from __future__ import print_function
import argparse
+import glob
import os
import sys
from os import path
@@ -194,7 +195,17 @@ def shall_skip(module, opts):
# skip it if there is nothing (or just \n or \r\n) in the file
if path.exists(module) and path.getsize(module) <= 2:
- return True
+ skip = True
+ if os.path.basename(module) == '__init__.py':
+ pattern = path.join(path.dirname(module), '*.py')
+ # We only want to skip packages if they do not contain any
+ # .py files other than __init__.py.
+ other_modules = list(glob.glob(pattern))
+ other_modules.remove(module)
+ skip = not other_modules
+
+ if skip:
+ return True
# skip if it has a "private" name and this is selected
filename = path.basename(module)
diff --git a/tests/roots/test-apidoc-pep420/a/b/e/__init__.py b/tests/roots/test-apidoc-pep420/a/b/e/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/roots/test-apidoc-pep420/a/b/e/__init__.py
diff --git a/tests/roots/test-apidoc-pep420/a/b/e/f.py b/tests/roots/test-apidoc-pep420/a/b/e/f.py
new file mode 100644
index 000000000..a09affe86
--- /dev/null
+++ b/tests/roots/test-apidoc-pep420/a/b/e/f.py
@@ -0,0 +1 @@
+"Module f"
diff --git a/tests/test_ext_apidoc.py b/tests/test_ext_apidoc.py
index 2bfc8016e..8a60816bd 100644
--- a/tests/test_ext_apidoc.py
+++ b/tests/test_ext_apidoc.py
@@ -67,6 +67,7 @@ def test_pep_0420_enabled(make_app, apidoc):
outdir = apidoc.outdir
assert (outdir / 'conf.py').isfile()
assert (outdir / 'a.b.c.rst').isfile()
+ assert (outdir / 'a.b.e.rst').isfile()
assert (outdir / 'a.b.x.rst').isfile()
with open(outdir / 'a.b.c.rst') as f:
@@ -74,6 +75,10 @@ def test_pep_0420_enabled(make_app, apidoc):
assert "automodule:: a.b.c.d\n" in rst
assert "automodule:: a.b.c\n" in rst
+ with open(outdir / 'a.b.e.rst') as f:
+ rst = f.read()
+ assert "automodule:: a.b.e.f\n" in rst
+
with open(outdir / 'a.b.x.rst') as f:
rst = f.read()
assert "automodule:: a.b.x.y\n" in rst
@@ -86,12 +91,67 @@ def test_pep_0420_enabled(make_app, apidoc):
builddir = outdir / '_build' / 'text'
assert (builddir / 'a.b.c.txt').isfile()
+ assert (builddir / 'a.b.e.txt').isfile()
+ assert (builddir / 'a.b.x.txt').isfile()
+
+ with open(builddir / 'a.b.c.txt') as f:
+ txt = f.read()
+ assert "a.b.c package\n" in txt
+
+ with open(builddir / 'a.b.e.txt') as f:
+ txt = f.read()
+ assert "a.b.e.f module\n" in txt
+
+ with open(builddir / 'a.b.x.txt') as f:
+ txt = f.read()
+ assert "a.b.x namespace\n" in txt
+
+
+@pytest.mark.apidoc(
+ coderoot='test-apidoc-pep420/a',
+ options=["--implicit-namespaces", "--separate"],
+)
+def test_pep_0420_enabled_separate(make_app, apidoc):
+ outdir = apidoc.outdir
+ assert (outdir / 'conf.py').isfile()
+ assert (outdir / 'a.b.c.rst').isfile()
+ assert (outdir / 'a.b.e.rst').isfile()
+ assert (outdir / 'a.b.e.f.rst').isfile()
+ assert (outdir / 'a.b.x.rst').isfile()
+ assert (outdir / 'a.b.x.y.rst').isfile()
+
+ with open(outdir / 'a.b.c.rst') as f:
+ rst = f.read()
+ assert ".. toctree::\n\n a.b.c.d\n" in rst
+
+ with open(outdir / 'a.b.e.rst') as f:
+ rst = f.read()
+ assert ".. toctree::\n\n a.b.e.f\n" in rst
+
+ with open(outdir / 'a.b.x.rst') as f:
+ rst = f.read()
+ assert ".. toctree::\n\n a.b.x.y\n" in rst
+
+ app = make_app('text', srcdir=outdir)
+ app.build()
+ print(app._status.getvalue())
+ print(app._warning.getvalue())
+
+ builddir = outdir / '_build' / 'text'
+ assert (builddir / 'a.b.c.txt').isfile()
+ assert (builddir / 'a.b.e.txt').isfile()
+ assert (builddir / 'a.b.e.f.txt').isfile()
assert (builddir / 'a.b.x.txt').isfile()
+ assert (builddir / 'a.b.x.y.txt').isfile()
with open(builddir / 'a.b.c.txt') as f:
txt = f.read()
assert "a.b.c package\n" in txt
+ with open(builddir / 'a.b.e.f.txt') as f:
+ txt = f.read()
+ assert "a.b.e.f module\n" in txt
+
with open(builddir / 'a.b.x.txt') as f:
txt = f.read()
assert "a.b.x namespace\n" in txt