123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- # -*- coding: utf-8 -*-
- import itertools
- import argparse
- import json
- import logging
- import os
- import pathlib
- import re
- import subprocess
- import sys
- import tempfile
- from sphinx.cmd import build as sphinx_build
- from sphinx import config as sphinx_config
- from sphinx import project as sphinx_project
- from . import sphinx
- from . import git
- def main(argv=None):
- if not argv:
- argv = sys.argv[1:]
- parser = argparse.ArgumentParser()
- parser.add_argument('sourcedir', help='path to documentation source files')
- parser.add_argument('outputdir', help='path to output directory')
- parser.add_argument('filenames', nargs='*', help='a list of specific files to rebuild. Ignored if -a is specified')
- parser.add_argument('-c', metavar='PATH', dest='confdir', help='path where configuration file (conf.py) is located (default: same as SOURCEDIR)')
- parser.add_argument('-C', action='store_true', dest='noconfig', help='use no config file at all, only -D options')
- parser.add_argument('-D', metavar='setting=value', action='append', dest='define', default=[], help='override a setting in configuration file')
- parser.add_argument('--dump-metadata', action='store_true', help='dump generated metadata and exit')
- args, argv = parser.parse_known_args(argv)
- if args.noconfig:
- return 1
- # Conf-overrides
- confoverrides = {}
- for d in args.define:
- key, _, value = d.partition('=')
- confoverrides[key] = value
- # Parse config
- config = sphinx_config.Config.read(
- os.path.abspath(args.confdir if args.confdir else args.sourcedir),
- confoverrides,
- )
- config.add("smv_tag_whitelist", sphinx.DEFAULT_TAG_WHITELIST, "html", str)
- config.add("smv_branch_whitelist", sphinx.DEFAULT_TAG_WHITELIST, "html", str)
- config.add("smv_remote_whitelist", sphinx.DEFAULT_REMOTE_WHITELIST, "html", str)
- config.add("smv_released_pattern", sphinx.DEFAULT_RELEASED_PATTERN, "html", str)
- config.add("smv_outputdir_format", sphinx.DEFAULT_OUTPUTDIR_FORMAT, "html", str)
- config.add("smv_prefer_remote_refs", False, "html", bool)
- config.pre_init_values()
- config.init_values()
- # Get git references
- gitroot = pathlib.Path('.').resolve()
- gitrefs = git.get_refs(
- str(gitroot),
- config.smv_tag_whitelist,
- config.smv_branch_whitelist,
- config.smv_remote_whitelist,
- )
- # Order git refs
- if config.smv_prefer_remote_refs:
- gitrefs = sorted(gitrefs, key=lambda x: (not x.is_remote, *x))
- else:
- gitrefs = sorted(gitrefs, key=lambda x: (x.is_remote, *x))
- logger = logging.getLogger(__name__)
- # Get Sourcedir
- sourcedir = os.path.relpath(args.sourcedir, str(gitroot))
- if args.confdir:
- confdir = os.path.relpath(args.confdir, str(gitroot))
- else:
- confdir = sourcedir
- with tempfile.TemporaryDirectory() as tmp:
- # Generate Metadata
- metadata = {}
- outputdirs = set()
- for gitref in gitrefs:
- # Clone Git repo
- repopath = os.path.join(tmp, gitref.commit)
- try:
- git.copy_tree(gitroot.as_uri(), repopath, gitref)
- except (OSError, subprocess.CalledProcessError):
- logger.error(
- "Failed to copy git tree for %s to %s",
- gitref.refname, repopath)
- continue
- # Find config
- confpath = os.path.join(repopath, confdir)
- try:
- current_config = sphinx_config.Config.read(
- confpath,
- confoverrides,
- )
- except (OSError, sphinx_config.ConfigError):
- logger.error(
- "Failed load config for %s from %s",
- gitref.refname, confpath)
- continue
- current_config.pre_init_values()
- current_config.init_values()
- # Ensure that there are not duplicate output dirs
- outputdir = config.smv_outputdir_format.format(
- ref=gitref,
- config=current_config,
- )
- if outputdir in outputdirs:
- logger.warning(
- "outputdir '%s' for %s conflicts with other versions",
- outputdir, gitref.refname)
- continue
- outputdirs.add(outputdir)
- # Get List of files
- source_suffixes = current_config.source_suffix
- if isinstance(source_suffixes, str):
- source_suffixes = [current_config.source_suffix]
- current_sourcedir = os.path.join(repopath, sourcedir)
- project = sphinx_project.Project(current_sourcedir, source_suffixes)
- metadata[gitref.name] = {
- "name": gitref.name,
- "version": current_config.version,
- "release": current_config.release,
- "is_released": bool(
- re.match(config.smv_released_pattern, gitref.refname)),
- "source": gitref.source,
- "creatordate": gitref.creatordate.strftime("%Y-%m-%d %H:%M:%S %z"),
- "sourcedir": current_sourcedir,
- "outputdir": outputdir,
- "docnames": list(project.discover())
- }
- if args.dump_metadata:
- print(json.dumps(metadata, indent=2))
- return
- if not metadata:
- logger.error("No matching refs found!")
- return
- # Write Metadata
- metadata_path = os.path.abspath(os.path.join(tmp, "versions.json"))
- with open(metadata_path, mode='w') as fp:
- json.dump(metadata, fp, indent=2)
- # Run Sphinx
- argv.extend(["-D", "smv_metadata_path={}".format(metadata_path)])
- for version_name, data in metadata.items():
- outdir = os.path.join(args.outputdir, data["outputdir"])
- os.makedirs(outdir, exist_ok=True)
- current_argv = argv.copy()
- current_argv.extend([
- *itertools.chain(*(('-D', d) for d in args.define)),
- "-D", "smv_current_version={}".format(version_name),
- "-c", args.confdir,
- data["sourcedir"],
- outdir,
- *args.filenames,
- ])
- status = sphinx_build.build_main(current_argv)
- if status not in (0, None):
- break
|