blob: 3aa1c2341e4fe5d730dd3b4595c79ace80bd2e0f [file] [log] [blame]
fweikert69895ba2022-07-13 04:56:04 -07001# Lint as: python3
2# pylint: disable=g-direct-third-party-import
3# Copyright 2022 The Bazel Authors. All rights reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""A tool for building the documentation for a Bazel release."""
17import os
18import shutil
19import sys
20import tarfile
21import tempfile
22import zipfile
23
24from absl import app
25from absl import flags
26
27from scripts.docs import rewriter
28
29FLAGS = flags.FLAGS
30
31flags.DEFINE_string("version", None, "Name of the Bazel release.")
32flags.DEFINE_string(
33 "toc_path",
34 None,
35 "Path to the _toc.yaml file that contains the table of contents for the versions menu.",
36)
37flags.DEFINE_string(
38 "narrative_docs_path",
39 None,
40 "Path of the archive (zip or tar) that contains the narrative documentation.",
41)
42flags.DEFINE_string(
43 "reference_docs_path",
44 None,
45 "Path of the archive (zip or tar) that contains the reference documentation.",
46)
47flags.DEFINE_string(
48 "output_path", None,
49 "Location where the zip'ed documentation should be written to.")
50
51_ARCHIVE_FUNCTIONS = {".tar": tarfile.open, ".zip": zipfile.ZipFile}
52
53
54def validate_flag(name):
55 """Ensures that a flag is set, and returns its value (if yes).
56
57 This function exits with an error if the flag was not set.
58
59 Args:
60 name: Name of the flag.
61
62 Returns:
63 The value of the flag, if set.
64 """
65 value = getattr(FLAGS, name, None)
66 if value:
67 return value
68
69 print("Missing --{} flag.".format(name), file=sys.stderr)
70 exit(1)
71
72
73def create_docs_tree(version, toc_path, narrative_docs_path,
74 reference_docs_path):
75 """Creates a directory tree containing the docs for the Bazel version.
76
77 Args:
78 version: Version of this Bazel release.
79 toc_path: Absolute path to the _toc.yaml file that lists the most recent
80 Bazel versions.
81 narrative_docs_path: Absolute path of an archive that contains the narrative
82 documentation (can be .zip or .tar).
83 reference_docs_path: Absolute path of an archive that contains the reference
84 documentation (can be .zip or .tar).
85
86 Returns:
87 The absolute paths of the root of the directory tree and of
88 the final _toc.yaml file.
89 """
90 root_dir = tempfile.mkdtemp()
91
92 versions_dir = os.path.join(root_dir, "versions")
93 os.makedirs(versions_dir)
94
95 toc_dest_path = os.path.join(versions_dir, "_toc.yaml")
96 shutil.copyfile(toc_path, toc_dest_path)
97
98 release_dir = os.path.join(versions_dir, version)
99 os.makedirs(release_dir)
100
101 try_extract(narrative_docs_path, release_dir)
102 try_extract(reference_docs_path, release_dir)
103
104 return root_dir, toc_dest_path
105
106
107def try_extract(archive_path, output_dir):
108 """Tries to extract the given archive into the given directory.
109
110 This function will raise an error if the archive type is not supported.
111
112 Args:
113 archive_path: Absolute path of an archive that should be extracted. Can be
114 .tar or .zip.
115 output_dir: Absolute path of the directory into which the archive should be
116 extracted
117
118 Raises:
119 ValueError: If the archive has an unsupported file type.
120 """
121 _, ext = os.path.splitext(archive_path)
122 open_func = _ARCHIVE_FUNCTIONS.get(ext)
123 if not open_func:
124 raise ValueError("File {}: Invalid file extension '{}'. Allowed: {}".format(
125 archive_path, ext, _ARCHIVE_FUNCTIONS.keys.join(", ")))
126
127 with open_func(archive_path, "r") as archive:
128 archive.extractall(output_dir)
129
130
131def build_archive(version, root_dir, toc_path, output_path):
132 """Builds a documentation archive for the given Bazel release.
133
134 This function reads all documentation files from the tree rooted in root_dir,
135 fixes all links so that they point at versioned files, then builds a zip
136 archive of all files.
137
138 Args:
139 version: Version of the Bazel release whose documentation is being built.
140 root_dir: Absolute path of the directory that contains the documentation
141 tree.
142 toc_path: Absolute path of the _toc.yaml file.
143 output_path: Absolute path where the archive should be written to.
144 """
145 with zipfile.ZipFile(output_path, "w") as archive:
146 for root, _, files in os.walk(root_dir):
147 for f in files:
148 src = os.path.join(root, f)
149 dest = src[len(root_dir) + 1:]
150
151 if src != toc_path and rewriter.can_rewrite(src):
152 archive.writestr(dest, get_versioned_content(src, version))
153 else:
154 archive.write(src, dest)
155
156
157def get_versioned_content(path, version):
158 """Rewrites links in the given file to point at versioned docs.
159
160 Args:
161 path: Absolute path of the file that should be rewritten.
162 version: Version of the Bazel release whose documentation is being built.
163
164 Returns:
165 The content of the given file, with rewritten links.
166 """
167 with open(path, "rt", encoding="utf-8") as f:
168 content = f.read()
169
170 return rewriter.rewrite_links(path, content, version)
171
172
173def main(unused_argv):
174 version = validate_flag("version")
175 output_path = validate_flag("output_path")
176 root_dir, toc_path = create_docs_tree(
177 version=version,
178 toc_path=validate_flag("toc_path"),
179 narrative_docs_path=validate_flag("narrative_docs_path"),
180 reference_docs_path=validate_flag("reference_docs_path"),
181 )
182
183 build_archive(
184 version=version,
185 root_dir=root_dir,
186 toc_path=toc_path,
187 output_path=output_path,
188 )
189
190
191if __name__ == "__main__":
192 FLAGS(sys.argv)
193 app.run(main)