blob: f5c8e3d4fc893981d99f27633708375f7cc97a3d [file] [log] [blame]
Dirk Pranke7bbb5472021-11-02 16:33:21 -07001# Copyright 2021 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import dataclasses
16import multiprocessing
17import os
18import queue
19import sys
20import threading
21import time
22import urllib.parse
23
24
25site = 'https://www.chromium.org'
26
Dirk Pranke304c5342021-11-03 12:34:21 -070027REPO_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
28SITE_DIR = os.path.join(REPO_DIR, 'site')
29BUILD_DIR = os.path.join(REPO_DIR, 'build')
Dirk Pranke7bbb5472021-11-02 16:33:21 -070030DEFAULT_TEMPLATE = '/_includes/page.html'
31
32
33
34alternates = [
35 site,
36 'http://dev.chromium.org',
37 'https://dev.chromium.org',
38 'https://sites.google.com/a/chromium.org/dev',
39 'https://ssl.gstatic.com/sites/p/058338',
40 'http://www.gstatic.com/sites/p/058338',
41]
42
43
44def cpu_count():
45 return multiprocessing.cpu_count()
46
47
48def read_text_file(path):
49 return read_binary_file(path).decode('utf-8')
50
51
52def read_binary_file(path):
53 with open(path, 'rb') as fp:
54 return fp.read()
55
56
57def write_binary_file(path, content):
58 with open(path, 'wb') as fp:
59 return fp.write(content)
60
Dirk Pranke304c5342021-11-03 12:34:21 -070061def write_text_file(path, content):
62 with open(path, 'w') as fp:
63 return fp.write(content)
64
Dirk Pranke7bbb5472021-11-02 16:33:21 -070065def read_paths(path):
66 paths = set()
67 with open(path) as fp:
68 for line in fp.readlines():
69 idx = line.find('#')
70 if idx != -1:
71 line = line[:idx]
72 line = line.strip()
73 if line:
74 paths.add(line)
75 return paths
76
77
Dirk Pranke304c5342021-11-03 12:34:21 -070078def to_path(page, top=SITE_DIR, ext='.md'):
Dirk Pranke7bbb5472021-11-02 16:33:21 -070079 page = page.strip()
80 if page == '/':
81 page = ''
82 if os.path.isdir(top + page):
83 return page + '/index' + ext
84 if os.path.exists(top + page):
85 return page
86 if os.path.exists(top + page + ext):
87 return page + ext
88 return page
89
90
91def walk(top, skip=None):
92 skip = skip or set()
93 paths = set()
94 for dirpath, dnames, fnames in os.walk(top):
95 for dname in dnames:
96 rpath = os.path.relpath(os.path.join(dirpath, dname), top)
97 if rpath in skip or dname.startswith('.'):
98 dnames.remove(dname)
99 for fname in fnames:
100 rpath = os.path.relpath(os.path.join(dirpath, fname), top)
101 if rpath in skip or fname.startswith('.'):
102 continue
103 paths.add(rpath)
104 return sorted(paths)
105
106
Dirk Pranke304c5342021-11-03 12:34:21 -0700107def write_if_changed(path, content, mode='wb', encoding='utf-8'):
108 if mode == 'w':
109 content = content.encode(encoding)
Dirk Pranke7bbb5472021-11-02 16:33:21 -0700110 os.makedirs(os.path.dirname(path), exist_ok=True)
111 if os.path.exists(path):
112 with open(path, 'rb') as fp:
113 old_content = fp.read()
114 if content == old_content:
115 return False
116 write_binary_file(path, content)
117 return True
118
119
120def should_update(dest_page, source_pages):
121 if not os.path.exists(dest_page):
122 return True
123
124 dest_pages = [dest_page]
125 max_source_mtime = max(os.stat(p).st_mtime for p in source_pages)
126 max_dest_mtime = max(os.stat(p).st_mtime for p in dest_pages)
127 return max_source_mtime > max_dest_mtime
128
129
130class JobQueue:
131 def __init__(self, handler, jobs, multiprocess=None):
132 self.handler = handler
133 self.jobs = jobs
134 self.pending = set()
135 self.started = set()
136 self.finished = set()
137 if multiprocess is None:
Dirk Pranke512d4012021-11-02 17:41:32 -0700138 self.multiprocess = (jobs > 1)
Dirk Pranke7bbb5472021-11-02 16:33:21 -0700139 else:
140 self.multiprocess = multiprocess
141 if self.multiprocess:
142 self._request_q = multiprocessing.Queue()
143 self._response_q = multiprocessing.Queue()
144 else:
145 self._request_q = queue.Queue()
146 self._response_q = queue.Queue()
147 self._start_time = None
148 self._threads = []
149 self._last_msg = None
150 self._isatty = sys.stdout.isatty()
151
152 def all_tasks(self):
153 return self.pending | self.started | self.finished
154
155 def request(self, task, obj):
156 self.pending.add(task)
157 self._request_q.put(('handle', task, obj))
158
159 def results(self):
160 self._start_time = time.time()
161 self._spawn()
162
163 while self.pending | self.started:
164 msg, task, res, obj = self._response_q.get()
165
166 if msg == 'started':
167 self._mark_started(task)
168 elif msg == 'finished':
169 self._mark_finished(task, res)
170 yield (task, res, obj)
171 else:
172 raise AssertionError
173
174 for _ in self._threads:
175 self._request_q.put(('exit', None, None))
176 for thread in self._threads:
177 thread.join()
178 if self._isatty:
179 print()
180
181 def _spawn(self):
182 args = (self._request_q, self._response_q, self.handler)
183 for i in range(self.jobs):
184 if self.multiprocess:
185 thread = multiprocessing.Process(target=_worker,
186 name='worker-%d' % i,
187 args=args)
188 else:
189 thread = threading.Thread(target=_worker,
190 name='worker-%d' % i,
191 args=args)
192 self._threads.append(thread)
193 thread.start()
194
195 def _mark_started(self, task):
196 self.pending.remove(task)
197 self.started.add(task)
198
199 def _mark_finished(self, task, res):
200 self.started.remove(task)
201 self.finished.add(task)
202 if res:
203 self._print('%s failed:' % task, truncate=False)
204 print()
205 print(res)
206 else:
207 self._print('%s' % task)
208 sys.stdout.flush()
209
210 def _print(self, msg, truncate=True):
211 if not self._isatty:
212 print('[%d/%d] %s' % (len(self.finished), len(self.all_tasks()),
213 msg))
214 return
215
216 if len(msg) > 76 and truncate:
217 msg = msg[:76] + '...'
218 if self._last_msg is not None:
219 print('\r', end='')
220 msg = '[%d/%d] %s' % (len(self.finished), len(self.all_tasks()), msg)
221 print(msg, end='' if self._isatty else '\n')
222 if self._last_msg is not None and len(self._last_msg) > len(msg):
223 print(' ' * (len(self._last_msg) - len(msg)), end='')
224 print('\r', end='')
225 self._last_msg = msg
226
227
228def _worker(request_q, response_q, handler):
229 while True:
230 message, task, obj = request_q.get()
231 if message == 'exit':
232 break
233 elif message == 'handle':
234 response_q.put(('started', task, '', None))
235 res, resp = handler(task, obj)
236 response_q.put(('finished', task, res, resp))
237 else:
238 raise AssertionError