blob: 864f5bfa0e2af6912d979a0aae6f0f733fb37e08 [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
27REPO_DIR = os.path.dirname(os.path.dirname(__file__))
28SOURCE_DIR = 'site'
29BUILD_DIR = 'build'
30DEFAULT_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
61def read_paths(path):
62 paths = set()
63 with open(path) as fp:
64 for line in fp.readlines():
65 idx = line.find('#')
66 if idx != -1:
67 line = line[:idx]
68 line = line.strip()
69 if line:
70 paths.add(line)
71 return paths
72
73
74def to_path(page, top=SOURCE_DIR, ext='.md'):
75 page = page.strip()
76 if page == '/':
77 page = ''
78 if os.path.isdir(top + page):
79 return page + '/index' + ext
80 if os.path.exists(top + page):
81 return page
82 if os.path.exists(top + page + ext):
83 return page + ext
84 return page
85
86
87def walk(top, skip=None):
88 skip = skip or set()
89 paths = set()
90 for dirpath, dnames, fnames in os.walk(top):
91 for dname in dnames:
92 rpath = os.path.relpath(os.path.join(dirpath, dname), top)
93 if rpath in skip or dname.startswith('.'):
94 dnames.remove(dname)
95 for fname in fnames:
96 rpath = os.path.relpath(os.path.join(dirpath, fname), top)
97 if rpath in skip or fname.startswith('.'):
98 continue
99 paths.add(rpath)
100 return sorted(paths)
101
102
103def write_if_changed(path, content):
104 os.makedirs(os.path.dirname(path), exist_ok=True)
105 if os.path.exists(path):
106 with open(path, 'rb') as fp:
107 old_content = fp.read()
108 if content == old_content:
109 return False
110 write_binary_file(path, content)
111 return True
112
113
114def should_update(dest_page, source_pages):
115 if not os.path.exists(dest_page):
116 return True
117
118 dest_pages = [dest_page]
119 max_source_mtime = max(os.stat(p).st_mtime for p in source_pages)
120 max_dest_mtime = max(os.stat(p).st_mtime for p in dest_pages)
121 return max_source_mtime > max_dest_mtime
122
123
124class JobQueue:
125 def __init__(self, handler, jobs, multiprocess=None):
126 self.handler = handler
127 self.jobs = jobs
128 self.pending = set()
129 self.started = set()
130 self.finished = set()
131 if multiprocess is None:
Dirk Pranke512d4012021-11-02 17:41:32 -0700132 self.multiprocess = (jobs > 1)
Dirk Pranke7bbb5472021-11-02 16:33:21 -0700133 else:
134 self.multiprocess = multiprocess
135 if self.multiprocess:
136 self._request_q = multiprocessing.Queue()
137 self._response_q = multiprocessing.Queue()
138 else:
139 self._request_q = queue.Queue()
140 self._response_q = queue.Queue()
141 self._start_time = None
142 self._threads = []
143 self._last_msg = None
144 self._isatty = sys.stdout.isatty()
145
146 def all_tasks(self):
147 return self.pending | self.started | self.finished
148
149 def request(self, task, obj):
150 self.pending.add(task)
151 self._request_q.put(('handle', task, obj))
152
153 def results(self):
154 self._start_time = time.time()
155 self._spawn()
156
157 while self.pending | self.started:
158 msg, task, res, obj = self._response_q.get()
159
160 if msg == 'started':
161 self._mark_started(task)
162 elif msg == 'finished':
163 self._mark_finished(task, res)
164 yield (task, res, obj)
165 else:
166 raise AssertionError
167
168 for _ in self._threads:
169 self._request_q.put(('exit', None, None))
170 for thread in self._threads:
171 thread.join()
172 if self._isatty:
173 print()
174
175 def _spawn(self):
176 args = (self._request_q, self._response_q, self.handler)
177 for i in range(self.jobs):
178 if self.multiprocess:
179 thread = multiprocessing.Process(target=_worker,
180 name='worker-%d' % i,
181 args=args)
182 else:
183 thread = threading.Thread(target=_worker,
184 name='worker-%d' % i,
185 args=args)
186 self._threads.append(thread)
187 thread.start()
188
189 def _mark_started(self, task):
190 self.pending.remove(task)
191 self.started.add(task)
192
193 def _mark_finished(self, task, res):
194 self.started.remove(task)
195 self.finished.add(task)
196 if res:
197 self._print('%s failed:' % task, truncate=False)
198 print()
199 print(res)
200 else:
201 self._print('%s' % task)
202 sys.stdout.flush()
203
204 def _print(self, msg, truncate=True):
205 if not self._isatty:
206 print('[%d/%d] %s' % (len(self.finished), len(self.all_tasks()),
207 msg))
208 return
209
210 if len(msg) > 76 and truncate:
211 msg = msg[:76] + '...'
212 if self._last_msg is not None:
213 print('\r', end='')
214 msg = '[%d/%d] %s' % (len(self.finished), len(self.all_tasks()), msg)
215 print(msg, end='' if self._isatty else '\n')
216 if self._last_msg is not None and len(self._last_msg) > len(msg):
217 print(' ' * (len(self._last_msg) - len(msg)), end='')
218 print('\r', end='')
219 self._last_msg = msg
220
221
222def _worker(request_q, response_q, handler):
223 while True:
224 message, task, obj = request_q.get()
225 if message == 'exit':
226 break
227 elif message == 'handle':
228 response_q.put(('started', task, '', None))
229 res, resp = handler(task, obj)
230 response_q.put(('finished', task, res, resp))
231 else:
232 raise AssertionError