From cff8dc519b79f1408b5b8a1b08a6f2bde2274a03 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 1 Apr 2018 17:46:44 +0900 Subject: [PATCH] Fix #4783: Sphinx crashed when drives of srcdir and outdir are different --- CHANGES | 2 ++ sphinx/application.py | 4 ++-- sphinx/builders/__init__.py | 4 ++-- sphinx/builders/gettext.py | 5 ++--- sphinx/environment/__init__.py | 4 ++-- sphinx/ext/doctest.py | 4 ++-- sphinx/testing/util.py | 3 ++- sphinx/util/i18n.py | 6 +++--- sphinx/util/osutil.py | 9 ++++++++- 9 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index a15b43409..48e22847c 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,8 @@ Bugs fixed * #4790: autosummary: too wide two column tables in PDF builds * #4795: Latex customization via ``_templates/longtable.tex_t`` is broken * #4789: imgconverter: confused by convert.exe of Windows +* #4783: On windows, Sphinx crashed when drives of srcdir and outdir are + different Testing -------- diff --git a/sphinx/application.py b/sphinx/application.py index 8b8d02952..7af1c8d39 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -40,7 +40,7 @@ from sphinx.util import pycompat # noqa: F401 from sphinx.util.console import bold # type: ignore from sphinx.util.docutils import is_html5_writer_available, directive_helper from sphinx.util.i18n import find_catalog_source_files -from sphinx.util.osutil import ENOENT, ensuredir +from sphinx.util.osutil import ENOENT, ensuredir, relpath from sphinx.util.tags import Tags if False: @@ -342,7 +342,7 @@ class Sphinx(object): if self.statuscode == 0 and self.builder.epilog: logger.info('') logger.info(self.builder.epilog % { - 'outdir': path.relpath(self.outdir), + 'outdir': relpath(self.outdir), 'project': self.config.project }) except Exception as err: diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 9c1c50c5b..a587f11e0 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -19,7 +19,7 @@ from sphinx.environment.adapters.asset import ImageAdapter from sphinx.util import i18n, logging, status_iterator from sphinx.util.console import bold # type: ignore from sphinx.util.i18n import find_catalog -from sphinx.util.osutil import SEP, ensuredir, relative_uri +from sphinx.util.osutil import SEP, ensuredir, relative_uri, relpath from sphinx.util.parallel import ParallelTasks, SerialTasks, make_chunks, \ parallel_available @@ -237,7 +237,7 @@ class Builder(object): def cat2relpath(cat): # type: (CatalogInfo) -> unicode - return path.relpath(cat.mo_path, self.env.srcdir).replace(path.sep, SEP) + return relpath(cat.mo_path, self.env.srcdir).replace(path.sep, SEP) logger.info(bold('building [mo]: ') + message) for catalog in status_iterator(catalogs, 'writing output... ', "darkgreen", diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index 579e05534..05f7d3a81 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -26,7 +26,7 @@ from sphinx.util import split_index_msg, logging, status_iterator from sphinx.util.console import bold # type: ignore from sphinx.util.i18n import find_catalog from sphinx.util.nodes import extract_messages, traverse_translatable_index -from sphinx.util.osutil import safe_relpath, ensuredir, canon_path +from sphinx.util.osutil import relpath, ensuredir, canon_path from sphinx.util.tags import Tags if False: @@ -284,8 +284,7 @@ class MessageCatalogBuilder(I18nBuilder): if self.config.gettext_location: # generate "#: file1:line1\n#: file2:line2 ..." output.write("#: %s\n" % "\n#: ".join( # type: ignore - "%s:%s" % (canon_path( - safe_relpath(source, self.outdir)), line) + "%s:%s" % (canon_path(relpath(source, self.outdir)), line) for source, line, _ in positions)) if self.config.gettext_uuid: # generate "# uuid1\n# uuid2\n ..." diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index b31021caa..86d69cc3a 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -38,7 +38,7 @@ from sphinx.util.docutils import sphinx_domains, WarningStream from sphinx.util.i18n import find_catalog_files from sphinx.util.matching import compile_matchers from sphinx.util.nodes import is_translatable -from sphinx.util.osutil import SEP, ensuredir +from sphinx.util.osutil import SEP, ensuredir, relpath from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks from sphinx.util.websupport import is_commentable @@ -346,7 +346,7 @@ class BuildEnvironment(object): *filename* should be absolute or relative to the source directory. """ if filename.startswith(self.srcdir): - filename = os.path.relpath(filename, self.srcdir) + filename = relpath(filename, self.srcdir) for suffix in self.config.source_suffix: if filename.endswith(suffix): return filename[:-len(suffix)] diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index 2a889900d..6ed20febc 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -30,7 +30,7 @@ from sphinx.locale import _ from sphinx.util import force_decode, logging from sphinx.util.console import bold # type: ignore from sphinx.util.nodes import set_source_info -from sphinx.util.osutil import fs_encoding +from sphinx.util.osutil import fs_encoding, relpath if False: # For type annotation @@ -372,7 +372,7 @@ Doctest summary """Try to get the file which actually contains the doctest, not the filename of the document it's included in.""" try: - filename = path.relpath(node.source, self.env.srcdir)\ + filename = relpath(node.source, self.env.srcdir)\ .rsplit(':docstring of ', maxsplit=1)[0] except Exception: filename = self.env.doc2path(docname, base=None) diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 60544cfa3..f836a97f4 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -23,6 +23,7 @@ from sphinx.builders.latex import LaTeXBuilder from sphinx.ext.autodoc import AutoDirective from sphinx.pycode import ModuleAnalyzer from sphinx.testing.path import path +from sphinx.util.osutil import relpath if False: from typing import List # NOQA @@ -184,7 +185,7 @@ def find_files(root, suffix=None): dirpath = path(dirpath) for f in [f for f in files if not suffix or f.endswith(suffix)]: fpath = dirpath / f - yield os.path.relpath(fpath, root) + yield relpath(fpath, root) def strip_escseq(text): diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 4989de1c9..ff7f8bd75 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -22,7 +22,7 @@ from babel.messages.pofile import read_po from sphinx.errors import SphinxError from sphinx.util import logging -from sphinx.util.osutil import SEP, walk +from sphinx.util.osutil import SEP, relpath, walk logger = logging.getLogger(__name__) @@ -96,7 +96,7 @@ def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction): domain = find_catalog(docname, compaction) files = [gettext.find(domain, path.join(srcdir, dir_), [lang]) # type: ignore for dir_ in locale_dirs] - files = [path.relpath(f, srcdir) for f in files if f] # type: ignore + files = [relpath(f, srcdir) for f in files if f] # type: ignore return files # type: ignore @@ -137,7 +137,7 @@ def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact filenames = [f for f in filenames if f.endswith('.po')] for filename in filenames: base = path.splitext(filename)[0] - domain = path.relpath(path.join(dirpath, base), base_dir) + domain = relpath(path.join(dirpath, base), base_dir) if gettext_compact and path.sep in domain: domain = path.split(domain)[0] domain = domain.replace(path.sep, SEP) diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index af87caf9a..fdcbda9a6 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -205,14 +205,21 @@ def ustrftime(format, *args): return r.encode().decode('unicode-escape') -def safe_relpath(path, start=None): +def relpath(path, start=os.curdir): # type: (unicode, unicode) -> unicode + """Return a relative filepath to *path* either from the current directory or + from an optional *start* directory. + + This is an alternative of ``os.path.relpath()``. This returns original path + if *path* and *start* are on different drives (for Windows platform). + """ try: return os.path.relpath(path, start) except ValueError: return path +safe_relpath = relpath # for compatibility fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() # type: unicode