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