| # 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( | |
| "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, 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. | |
| 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) | |
| return root_dir, toc_dest_path | |
| 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): | |
| """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. | |
| """ | |
| 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:] | |
| if src != toc_path and rewriter.can_rewrite(src): | |
| archive.writestr(dest, get_versioned_content(src, version)) | |
| else: | |
| archive.write(src, dest) | |
| def get_versioned_content(path, version): | |
| """Rewrites links in the given file to point at versioned docs. | |
| Args: | |
| path: Absolute 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, version) | |
| def main(unused_argv): | |
| version = validate_flag("version") | |
| output_path = validate_flag("output_path") | |
| root_dir, toc_path = create_docs_tree( | |
| version=version, | |
| toc_path=validate_flag("toc_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, | |
| ) | |
| if __name__ == "__main__": | |
| FLAGS(sys.argv) | |
| app.run(main) |