blob: 61dc28e414b2cd0849d1f1d97aa1b2a4420527fe [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(
Fabian Meumertzheim25627072024-06-18 05:58:09 -070038 "buttons_path",
39 None,
40 "Path to the _buttons.html file that contains the version indicator.",
41)
42flags.DEFINE_string(
fweikert69895ba2022-07-13 04:56:04 -070043 "narrative_docs_path",
44 None,
45 "Path of the archive (zip or tar) that contains the narrative documentation.",
46)
47flags.DEFINE_string(
48 "reference_docs_path",
49 None,
50 "Path of the archive (zip or tar) that contains the reference documentation.",
51)
52flags.DEFINE_string(
53 "output_path", None,
54 "Location where the zip'ed documentation should be written to.")
55
56_ARCHIVE_FUNCTIONS = {".tar": tarfile.open, ".zip": zipfile.ZipFile}
57
58
59def validate_flag(name):
60 """Ensures that a flag is set, and returns its value (if yes).
61
62 This function exits with an error if the flag was not set.
63
64 Args:
65 name: Name of the flag.
66
67 Returns:
68 The value of the flag, if set.
69 """
70 value = getattr(FLAGS, name, None)
71 if value:
72 return value
73
74 print("Missing --{} flag.".format(name), file=sys.stderr)
75 exit(1)
76
77
Fabian Meumertzheim25627072024-06-18 05:58:09 -070078def create_docs_tree(
79 version, toc_path, buttons_path, narrative_docs_path, reference_docs_path
80):
fweikert69895ba2022-07-13 04:56:04 -070081 """Creates a directory tree containing the docs for the Bazel version.
82
83 Args:
84 version: Version of this Bazel release.
85 toc_path: Absolute path to the _toc.yaml file that lists the most recent
86 Bazel versions.
Fabian Meumertzheim25627072024-06-18 05:58:09 -070087 buttons_path: Absolute path of the _buttons.html file that contains the
88 version indicator.
fweikert69895ba2022-07-13 04:56:04 -070089 narrative_docs_path: Absolute path of an archive that contains the narrative
90 documentation (can be .zip or .tar).
91 reference_docs_path: Absolute path of an archive that contains the reference
92 documentation (can be .zip or .tar).
93
94 Returns:
95 The absolute paths of the root of the directory tree and of
96 the final _toc.yaml file.
97 """
98 root_dir = tempfile.mkdtemp()
99
100 versions_dir = os.path.join(root_dir, "versions")
101 os.makedirs(versions_dir)
102
103 toc_dest_path = os.path.join(versions_dir, "_toc.yaml")
104 shutil.copyfile(toc_path, toc_dest_path)
105
106 release_dir = os.path.join(versions_dir, version)
107 os.makedirs(release_dir)
108
109 try_extract(narrative_docs_path, release_dir)
110 try_extract(reference_docs_path, release_dir)
111
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700112 buttons_dest_path = os.path.join(release_dir, "_buttons.html")
113 os.remove(buttons_dest_path)
114 shutil.copyfile(buttons_path, buttons_dest_path)
115
116 return root_dir, toc_dest_path, release_dir
fweikert69895ba2022-07-13 04:56:04 -0700117
118
119def try_extract(archive_path, output_dir):
120 """Tries to extract the given archive into the given directory.
121
122 This function will raise an error if the archive type is not supported.
123
124 Args:
125 archive_path: Absolute path of an archive that should be extracted. Can be
126 .tar or .zip.
127 output_dir: Absolute path of the directory into which the archive should be
128 extracted
129
130 Raises:
131 ValueError: If the archive has an unsupported file type.
132 """
133 _, ext = os.path.splitext(archive_path)
134 open_func = _ARCHIVE_FUNCTIONS.get(ext)
135 if not open_func:
136 raise ValueError("File {}: Invalid file extension '{}'. Allowed: {}".format(
137 archive_path, ext, _ARCHIVE_FUNCTIONS.keys.join(", ")))
138
139 with open_func(archive_path, "r") as archive:
140 archive.extractall(output_dir)
141
142
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700143def build_archive(version, root_dir, toc_path, output_path, release_dir):
fweikert69895ba2022-07-13 04:56:04 -0700144 """Builds a documentation archive for the given Bazel release.
145
146 This function reads all documentation files from the tree rooted in root_dir,
147 fixes all links so that they point at versioned files, then builds a zip
148 archive of all files.
149
150 Args:
151 version: Version of the Bazel release whose documentation is being built.
152 root_dir: Absolute path of the directory that contains the documentation
153 tree.
154 toc_path: Absolute path of the _toc.yaml file.
155 output_path: Absolute path where the archive should be written to.
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700156 release_dir: Absolute path of the root directory for this version.
fweikert69895ba2022-07-13 04:56:04 -0700157 """
158 with zipfile.ZipFile(output_path, "w") as archive:
159 for root, _, files in os.walk(root_dir):
160 for f in files:
161 src = os.path.join(root, f)
162 dest = src[len(root_dir) + 1:]
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700163 rel_path = os.path.relpath(src, release_dir)
fweikert69895ba2022-07-13 04:56:04 -0700164
165 if src != toc_path and rewriter.can_rewrite(src):
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700166 archive.writestr(dest, get_versioned_content(src, rel_path, version))
fweikert69895ba2022-07-13 04:56:04 -0700167 else:
168 archive.write(src, dest)
169
170
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700171def get_versioned_content(path, rel_path, version):
fweikert69895ba2022-07-13 04:56:04 -0700172 """Rewrites links in the given file to point at versioned docs.
173
174 Args:
175 path: Absolute path of the file that should be rewritten.
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700176 rel_path: Relative path of the file that should be rewritten.
fweikert69895ba2022-07-13 04:56:04 -0700177 version: Version of the Bazel release whose documentation is being built.
178
179 Returns:
180 The content of the given file, with rewritten links.
181 """
182 with open(path, "rt", encoding="utf-8") as f:
183 content = f.read()
184
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700185 return rewriter.rewrite_links(path, content, rel_path, version)
fweikert69895ba2022-07-13 04:56:04 -0700186
187
188def main(unused_argv):
189 version = validate_flag("version")
190 output_path = validate_flag("output_path")
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700191 root_dir, toc_path, release_dir = create_docs_tree(
fweikert69895ba2022-07-13 04:56:04 -0700192 version=version,
193 toc_path=validate_flag("toc_path"),
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700194 buttons_path=validate_flag("buttons_path"),
fweikert69895ba2022-07-13 04:56:04 -0700195 narrative_docs_path=validate_flag("narrative_docs_path"),
196 reference_docs_path=validate_flag("reference_docs_path"),
197 )
198
199 build_archive(
200 version=version,
201 root_dir=root_dir,
202 toc_path=toc_path,
203 output_path=output_path,
Fabian Meumertzheim25627072024-06-18 05:58:09 -0700204 release_dir=release_dir,
fweikert69895ba2022-07-13 04:56:04 -0700205 )
206
207
208if __name__ == "__main__":
209 FLAGS(sys.argv)
210 app.run(main)