2020-02-19 17:18:09 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
2020-02-25 12:32:29 +01:00
|
|
|
import itertools
|
2020-02-23 17:10:33 +01:00
|
|
|
import argparse
|
2020-02-19 17:18:09 +01:00
|
|
|
import json
|
2020-02-23 17:10:33 +01:00
|
|
|
import logging
|
|
|
|
import os
|
2020-02-19 17:18:09 +01:00
|
|
|
import pathlib
|
2020-02-23 17:10:33 +01:00
|
|
|
import re
|
2020-04-11 03:12:43 +02:00
|
|
|
import string
|
2020-02-19 17:18:09 +01:00
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import tempfile
|
2020-04-28 19:36:16 +02:00
|
|
|
import shutil
|
2020-02-19 17:18:09 +01:00
|
|
|
|
|
|
|
from sphinx.cmd import build as sphinx_build
|
2020-02-23 17:10:33 +01:00
|
|
|
from sphinx import config as sphinx_config
|
2020-02-19 17:18:09 +01:00
|
|
|
from sphinx import project as sphinx_project
|
|
|
|
|
|
|
|
from . import sphinx
|
|
|
|
from . import git
|
|
|
|
|
|
|
|
|
|
|
|
def main(argv=None):
|
|
|
|
if not argv:
|
|
|
|
argv = sys.argv[1:]
|
|
|
|
|
2020-02-23 17:10:33 +01:00
|
|
|
parser = argparse.ArgumentParser()
|
2020-03-11 13:28:44 +01:00
|
|
|
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",
|
|
|
|
)
|
2020-02-23 17:10:33 +01:00
|
|
|
args, argv = parser.parse_known_args(argv)
|
|
|
|
if args.noconfig:
|
|
|
|
return 1
|
|
|
|
|
|
|
|
# Conf-overrides
|
|
|
|
confoverrides = {}
|
2020-02-19 17:18:09 +01:00
|
|
|
for d in args.define:
|
2020-03-11 13:28:44 +01:00
|
|
|
key, _, value = d.partition("=")
|
2020-02-23 17:10:33 +01:00
|
|
|
confoverrides[key] = value
|
2020-02-19 17:18:09 +01:00
|
|
|
|
2020-02-23 17:10:33 +01:00
|
|
|
# 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)
|
2020-03-11 13:28:44 +01:00
|
|
|
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
|
|
|
|
)
|
2020-03-11 11:23:24 +01:00
|
|
|
config.add("smv_prefer_remote_refs", False, "html", bool)
|
2020-02-23 17:11:00 +01:00
|
|
|
config.pre_init_values()
|
|
|
|
config.init_values()
|
2020-02-23 17:10:33 +01:00
|
|
|
|
|
|
|
# Get git references
|
2020-03-11 13:28:44 +01:00
|
|
|
gitroot = pathlib.Path(".").resolve()
|
2020-02-23 17:10:33 +01:00
|
|
|
gitrefs = git.get_refs(
|
|
|
|
str(gitroot),
|
|
|
|
config.smv_tag_whitelist,
|
|
|
|
config.smv_branch_whitelist,
|
|
|
|
config.smv_remote_whitelist,
|
|
|
|
)
|
|
|
|
|
2020-03-11 11:23:24 +01:00
|
|
|
# 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))
|
|
|
|
|
2020-02-23 17:10:33 +01:00
|
|
|
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
|
2020-02-19 17:18:09 +01:00
|
|
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
# Generate Metadata
|
|
|
|
metadata = {}
|
|
|
|
outputdirs = set()
|
2020-02-23 17:10:33 +01:00
|
|
|
for gitref in gitrefs:
|
2020-02-19 17:18:09 +01:00
|
|
|
# Clone Git repo
|
2020-02-23 17:10:33 +01:00
|
|
|
repopath = os.path.join(tmp, gitref.commit)
|
2020-02-19 17:18:09 +01:00
|
|
|
try:
|
2020-02-23 17:10:33 +01:00
|
|
|
git.copy_tree(gitroot.as_uri(), repopath, gitref)
|
2020-02-21 14:01:09 +01:00
|
|
|
except (OSError, subprocess.CalledProcessError):
|
2020-02-23 17:10:33 +01:00
|
|
|
logger.error(
|
|
|
|
"Failed to copy git tree for %s to %s",
|
2020-03-11 13:28:44 +01:00
|
|
|
gitref.refname,
|
|
|
|
repopath,
|
|
|
|
)
|
2020-02-23 17:10:33 +01:00
|
|
|
continue
|
|
|
|
|
|
|
|
# Find config
|
|
|
|
confpath = os.path.join(repopath, confdir)
|
|
|
|
try:
|
|
|
|
current_config = sphinx_config.Config.read(
|
2020-03-11 13:28:44 +01:00
|
|
|
confpath, confoverrides,
|
2020-02-23 17:10:33 +01:00
|
|
|
)
|
2020-02-23 18:45:32 +01:00
|
|
|
except (OSError, sphinx_config.ConfigError):
|
2020-02-23 17:10:33 +01:00
|
|
|
logger.error(
|
|
|
|
"Failed load config for %s from %s",
|
2020-03-11 13:28:44 +01:00
|
|
|
gitref.refname,
|
|
|
|
confpath,
|
|
|
|
)
|
2020-04-28 19:36:16 +02:00
|
|
|
shutil.rmtree(repopath)
|
2020-02-23 17:10:33 +01:00
|
|
|
continue
|
2020-02-23 17:11:00 +01:00
|
|
|
current_config.pre_init_values()
|
|
|
|
current_config.init_values()
|
2020-02-23 17:10:33 +01:00
|
|
|
|
|
|
|
# Ensure that there are not duplicate output dirs
|
|
|
|
outputdir = config.smv_outputdir_format.format(
|
2020-03-11 13:28:44 +01:00
|
|
|
ref=gitref, config=current_config,
|
2020-02-23 17:10:33 +01:00
|
|
|
)
|
|
|
|
if outputdir in outputdirs:
|
|
|
|
logger.warning(
|
|
|
|
"outputdir '%s' for %s conflicts with other versions",
|
2020-03-11 13:28:44 +01:00
|
|
|
outputdir,
|
|
|
|
gitref.refname,
|
|
|
|
)
|
2020-04-28 19:36:16 +02:00
|
|
|
shutil.rmtree(repopath)
|
2020-02-19 17:18:09 +01:00
|
|
|
continue
|
2020-02-23 17:10:33 +01:00
|
|
|
outputdirs.add(outputdir)
|
2020-02-19 17:18:09 +01:00
|
|
|
|
|
|
|
# Get List of files
|
2020-02-23 17:10:33 +01:00
|
|
|
source_suffixes = current_config.source_suffix
|
2020-02-19 17:18:09 +01:00
|
|
|
if isinstance(source_suffixes, str):
|
2020-02-23 17:10:33 +01:00
|
|
|
source_suffixes = [current_config.source_suffix]
|
2020-02-25 12:31:49 +01:00
|
|
|
|
|
|
|
current_sourcedir = os.path.join(repopath, sourcedir)
|
2020-03-11 13:28:44 +01:00
|
|
|
project = sphinx_project.Project(
|
|
|
|
current_sourcedir, source_suffixes
|
|
|
|
)
|
2020-02-23 17:10:33 +01:00
|
|
|
metadata[gitref.name] = {
|
|
|
|
"name": gitref.name,
|
|
|
|
"version": current_config.version,
|
|
|
|
"release": current_config.release,
|
|
|
|
"is_released": bool(
|
2020-03-11 13:28:44 +01:00
|
|
|
re.match(config.smv_released_pattern, gitref.refname)
|
|
|
|
),
|
2020-02-23 17:10:33 +01:00
|
|
|
"source": gitref.source,
|
2020-03-11 13:28:44 +01:00
|
|
|
"creatordate": gitref.creatordate.strftime(sphinx.DATE_FMT),
|
2020-02-25 12:31:49 +01:00
|
|
|
"sourcedir": current_sourcedir,
|
2020-04-15 14:20:51 +02:00
|
|
|
"outputdir": os.path.join(
|
|
|
|
os.path.abspath(args.outputdir), outputdir
|
|
|
|
),
|
|
|
|
"confdir": os.path.abspath(confdir),
|
2020-03-11 13:28:44 +01:00
|
|
|
"docnames": list(project.discover()),
|
2020-02-19 17:18:09 +01:00
|
|
|
}
|
2020-02-23 17:10:33 +01:00
|
|
|
|
|
|
|
if args.dump_metadata:
|
|
|
|
print(json.dumps(metadata, indent=2))
|
|
|
|
return
|
|
|
|
|
2020-02-25 15:55:23 +01:00
|
|
|
if not metadata:
|
|
|
|
logger.error("No matching refs found!")
|
|
|
|
return
|
|
|
|
|
2020-02-23 17:10:33 +01:00
|
|
|
# Write Metadata
|
2020-02-19 17:18:09 +01:00
|
|
|
metadata_path = os.path.abspath(os.path.join(tmp, "versions.json"))
|
2020-03-11 13:28:44 +01:00
|
|
|
with open(metadata_path, mode="w") as fp:
|
2020-02-19 17:18:09 +01:00
|
|
|
json.dump(metadata, fp, indent=2)
|
|
|
|
|
|
|
|
# Run Sphinx
|
|
|
|
argv.extend(["-D", "smv_metadata_path={}".format(metadata_path)])
|
|
|
|
for version_name, data in metadata.items():
|
2020-04-11 03:12:43 +02:00
|
|
|
os.makedirs(data["outputdir"], exist_ok=True)
|
|
|
|
|
2020-04-15 14:20:00 +02:00
|
|
|
defines = itertools.chain(
|
|
|
|
*(
|
|
|
|
("-D", string.Template(d).safe_substitute(data))
|
|
|
|
for d in args.define
|
|
|
|
)
|
|
|
|
)
|
2020-02-23 17:10:33 +01:00
|
|
|
|
2020-02-19 17:18:09 +01:00
|
|
|
current_argv = argv.copy()
|
2020-03-11 13:28:44 +01:00
|
|
|
current_argv.extend(
|
|
|
|
[
|
2020-04-11 03:12:43 +02:00
|
|
|
*defines,
|
2020-03-11 13:28:44 +01:00
|
|
|
"-D",
|
|
|
|
"smv_current_version={}".format(version_name),
|
|
|
|
"-c",
|
2020-04-11 03:12:43 +02:00
|
|
|
data["confdir"],
|
2020-03-11 13:28:44 +01:00
|
|
|
data["sourcedir"],
|
2020-04-11 03:12:43 +02:00
|
|
|
data["outputdir"],
|
2020-03-11 13:28:44 +01:00
|
|
|
*args.filenames,
|
|
|
|
]
|
|
|
|
)
|
2020-04-11 03:12:43 +02:00
|
|
|
logger.debug("Running sphinx-build with args: %r", current_argv)
|
2020-02-19 17:18:09 +01:00
|
|
|
status = sphinx_build.build_main(current_argv)
|
|
|
|
if status not in (0, None):
|
|
|
|
break
|
2020-04-28 19:36:16 +02:00
|
|
|
|
|
|
|
tmp.cleanup()
|