blob: 61dc28e414b2cd0849d1f1d97aa1b2a4420527fe [file] [log] [blame]
# Lint as: python3
# pylint: disable=g-direct-third-party-import
# Copyright 2022 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A tool for building the documentation for a Bazel release."""
import os
import shutil
import sys
import tarfile
import tempfile
import zipfile
from absl import app
from absl import flags
from scripts.docs import rewriter
FLAGS = flags.FLAGS
flags.DEFINE_string("version", None, "Name of the Bazel release.")
flags.DEFINE_string(
"toc_path",
None,
"Path to the _toc.yaml file that contains the table of contents for the versions menu.",
)
flags.DEFINE_string(
"buttons_path",
None,
"Path to the _buttons.html file that contains the version indicator.",
)
flags.DEFINE_string(
"narrative_docs_path",
None,
"Path of the archive (zip or tar) that contains the narrative documentation.",
)
flags.DEFINE_string(
"reference_docs_path",
None,
"Path of the archive (zip or tar) that contains the reference documentation.",
)
flags.DEFINE_string(
"output_path", None,
"Location where the zip'ed documentation should be written to.")
_ARCHIVE_FUNCTIONS = {".tar": tarfile.open, ".zip": zipfile.ZipFile}
def validate_flag(name):
"""Ensures that a flag is set, and returns its value (if yes).
This function exits with an error if the flag was not set.
Args:
name: Name of the flag.
Returns:
The value of the flag, if set.
"""
value = getattr(FLAGS, name, None)
if value:
return value
print("Missing --{} flag.".format(name), file=sys.stderr)
exit(1)
def create_docs_tree(
version, toc_path, buttons_path, narrative_docs_path, reference_docs_path
):
"""Creates a directory tree containing the docs for the Bazel version.
Args:
version: Version of this Bazel release.
toc_path: Absolute path to the _toc.yaml file that lists the most recent
Bazel versions.
buttons_path: Absolute path of the _buttons.html file that contains the
version indicator.
narrative_docs_path: Absolute path of an archive that contains the narrative
documentation (can be .zip or .tar).
reference_docs_path: Absolute path of an archive that contains the reference
documentation (can be .zip or .tar).
Returns:
The absolute paths of the root of the directory tree and of
the final _toc.yaml file.
"""
root_dir = tempfile.mkdtemp()
versions_dir = os.path.join(root_dir, "versions")
os.makedirs(versions_dir)
toc_dest_path = os.path.join(versions_dir, "_toc.yaml")
shutil.copyfile(toc_path, toc_dest_path)
release_dir = os.path.join(versions_dir, version)
os.makedirs(release_dir)
try_extract(narrative_docs_path, release_dir)
try_extract(reference_docs_path, release_dir)
buttons_dest_path = os.path.join(release_dir, "_buttons.html")
os.remove(buttons_dest_path)
shutil.copyfile(buttons_path, buttons_dest_path)
return root_dir, toc_dest_path, release_dir
def try_extract(archive_path, output_dir):
"""Tries to extract the given archive into the given directory.
This function will raise an error if the archive type is not supported.
Args:
archive_path: Absolute path of an archive that should be extracted. Can be
.tar or .zip.
output_dir: Absolute path of the directory into which the archive should be
extracted
Raises:
ValueError: If the archive has an unsupported file type.
"""
_, ext = os.path.splitext(archive_path)
open_func = _ARCHIVE_FUNCTIONS.get(ext)
if not open_func:
raise ValueError("File {}: Invalid file extension '{}'. Allowed: {}".format(
archive_path, ext, _ARCHIVE_FUNCTIONS.keys.join(", ")))
with open_func(archive_path, "r") as archive:
archive.extractall(output_dir)
def build_archive(version, root_dir, toc_path, output_path, release_dir):
"""Builds a documentation archive for the given Bazel release.
This function reads all documentation files from the tree rooted in root_dir,
fixes all links so that they point at versioned files, then builds a zip
archive of all files.
Args:
version: Version of the Bazel release whose documentation is being built.
root_dir: Absolute path of the directory that contains the documentation
tree.
toc_path: Absolute path of the _toc.yaml file.
output_path: Absolute path where the archive should be written to.
release_dir: Absolute path of the root directory for this version.
"""
with zipfile.ZipFile(output_path, "w") as archive:
for root, _, files in os.walk(root_dir):
for f in files:
src = os.path.join(root, f)
dest = src[len(root_dir) + 1:]
rel_path = os.path.relpath(src, release_dir)
if src != toc_path and rewriter.can_rewrite(src):
archive.writestr(dest, get_versioned_content(src, rel_path, version))
else:
archive.write(src, dest)
def get_versioned_content(path, rel_path, version):
"""Rewrites links in the given file to point at versioned docs.
Args:
path: Absolute path of the file that should be rewritten.
rel_path: Relative path of the file that should be rewritten.
version: Version of the Bazel release whose documentation is being built.
Returns:
The content of the given file, with rewritten links.
"""
with open(path, "rt", encoding="utf-8") as f:
content = f.read()
return rewriter.rewrite_links(path, content, rel_path, version)
def main(unused_argv):
version = validate_flag("version")
output_path = validate_flag("output_path")
root_dir, toc_path, release_dir = create_docs_tree(
version=version,
toc_path=validate_flag("toc_path"),
buttons_path=validate_flag("buttons_path"),
narrative_docs_path=validate_flag("narrative_docs_path"),
reference_docs_path=validate_flag("reference_docs_path"),
)
build_archive(
version=version,
root_dir=root_dir,
toc_path=toc_path,
output_path=output_path,
release_dir=release_dir,
)
if __name__ == "__main__":
FLAGS(sys.argv)
app.run(main)