| # 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) |