Skip to content

Commit

Permalink
add common code for make.py
Browse files Browse the repository at this point in the history
  • Loading branch information
pietroalbini committed Apr 3, 2023
1 parent 114f55e commit 58d9a6d
Showing 1 changed file with 182 additions and 0 deletions.
182 changes: 182 additions & 0 deletions make_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT OR Apache-2.0
# SPDX-FileCopyrightText: Ferrous Systems and AdaCore

# Convenience script to build Sphinx books, including setting up a Python
# virtual environment to install Sphinx into (removing the need to manage
# dependencies globally). Each book should have a `make.py` script that updates
# the submodules, import this shared module, and calls the main function here.

from pathlib import Path
import argparse
import subprocess
import venv
import sys


def build_docs(root, env, builder, clear, serve, debug):
dest = root / "build"

args = ["-b", builder, "-d", dest / "doctrees"]
if debug:
# Disable parallel builds and show exceptions in debug mode.
#
# We can't show exceptions in parallel mode because in parallel mode
# all exceptions will be swallowed up by Python's multiprocessing.
# That's also why we don't show exceptions outside of debug mode.
args += ["-j", "1", "-T"]
else:
# Enable parallel builds:
args += ["-j", "auto"]
if clear:
args.append("-E")
if serve:
args += ["--watch", root / "exts", "--watch", root / "themes"]
else:
# Error out at the *end* of the build if there are warnings:
args += ["-W", "--keep-going"]

commit = current_git_commit(root)
if commit is not None:
args += ["-D", f"html_theme_options.commit={commit}"]

try:
subprocess.run(
[
env.bin("sphinx-autobuild" if serve else "sphinx-build"),
*args,
root / "src",
dest / builder,
],
check=True,
)
except KeyboardInterrupt:
exit(1)
except subprocess.CalledProcessError:
print("\nhint: if you see an exception, pass --debug to see the full traceback")
exit(1)

return dest / builder


def build_linkchecker(root):
repo = root / ".linkchecker"
src = repo / "src" / "tools" / "linkchecker"
bin = src / "target" / "release" / "linkchecker"

if not src.is_dir():
subprocess.run(["git", "init", repo], check=True)

def git(args):
subprocess.run(["git", *args], cwd=repo, check=True)

# Avoid fetching blobs unless needed by the sparse checkout
git(["remote", "add", "origin", "https://github.com/rust-lang/rust"])
git(["config", "remote.origin.promisor", "true"])
git(["config", "remote.origin.partialCloneFilter", "blob:none"])

# Checkout only the linkchecker tool rather than the whole repo
git(["config", "core.sparsecheckout", "true"])
with open(repo / ".git" / "info" / "sparse-checkout", "w") as f:
f.write("/src/tools/linkchecker/")

# Avoid fetching the whole history
git(["fetch", "--depth=1", "origin", "master"])
git(["checkout", "master"])

if not bin.is_file():
subprocess.run(["cargo", "build", "--release"], cwd=src, check=True)

return bin


def current_git_commit(root):
try:
return (
subprocess.run(
["git", "rev-parse", "HEAD"],
check=True,
stdout=subprocess.PIPE,
)
.stdout.decode("utf-8")
.strip()
)
# `git` executable missing from the system
except FileNotFoundError:
print("warning: failed to detect git commit: missing executable git")
return
# `git` returned an error (git will print the actual error to stderr)
except subprocess.CalledProcessError:
print("warning: failed to detect git commit: git returned an error")
return


class VirtualEnv:
def __init__(self, root, path):
self.path = path
self.requirements = root / "shared" / "requirements.txt"
self.installed_requirements = path / "installed-requirements.txt"

if not self.up_to_date():
self.create()

def bin(self, name):
if sys.platform == "win32":
return self.path / "scripts" / name
else:
return self.path / "bin" / name

def up_to_date(self):
if self.installed_requirements.exists():
expected = self.requirements.read_bytes()
installed = self.installed_requirements.read_bytes()
if expected == installed:
return True
return False

def create(self):
venv.EnvBuilder(clear=True, symlinks=True, with_pip=True).create(self.path)
subprocess.run(
[self.bin("pip"), "install", "-r", self.requirements, "--require-hashes"],
check=True,
)
self.installed_requirements.write_bytes(self.requirements.read_bytes())


def main(root):
root = Path(root)

parser = argparse.ArgumentParser()
parser.add_argument(
"-c", "--clear", help="disable incremental builds", action="store_true"
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"-s",
"--serve",
help="start a local server with live reload",
action="store_true",
)
group.add_argument(
"--check-links", help="Check whether all links are valid", action="store_true"
)
group.add_argument(
"--xml", help="Generate Sphinx XML rather than HTML", action="store_true"
)
group.add_argument(
"--debug",
help="Debug mode for the extensions, showing exceptions",
action="store_true",
)
args = parser.parse_args()

env = VirtualEnv(root, root / ".venv")
rendered = build_docs(
root, env, "xml" if args.xml else "html", args.clear, args.serve, args.debug
)

if args.check_links:
linkchecker = build_linkchecker(root)
if subprocess.run([linkchecker, rendered]).returncode != 0:
print("error: linkchecker failed")
exit(1)

0 comments on commit 58d9a6d

Please sign in to comment.