Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(389)

Side by Side Diff: gm/rebaseline_server/server.py

Issue 310093003: rebaseline_server: download actual-results.json files from GCS instead of SVN (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: extract gs_utils and url_utils into their own files Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 """ 3 """
4 Copyright 2013 Google Inc. 4 Copyright 2013 Google Inc.
5 5
6 Use of this source code is governed by a BSD-style license that can be 6 Use of this source code is governed by a BSD-style license that can be
7 found in the LICENSE file. 7 found in the LICENSE file.
8 8
9 HTTP server for our HTML rebaseline viewer. 9 HTTP server for our HTML rebaseline viewer.
10 """ 10 """
(...skipping 10 matching lines...) Expand all
21 import socket 21 import socket
22 import subprocess 22 import subprocess
23 import sys 23 import sys
24 import thread 24 import thread
25 import threading 25 import threading
26 import time 26 import time
27 import urlparse 27 import urlparse
28 28
29 # Imports from within Skia 29 # Imports from within Skia
30 # 30 #
31 # We need to add the 'tools' directory for svn.py, and the 'gm' directory for 31 # We need to add the 'gm' directory for gm_json.py .
32 # gm_json.py . 32 # Make sure this dir is in the PYTHONPATH, but add it at the *end*
33 # that directory.
34 # Make sure that the 'tools' dir is in the PYTHONPATH, but add it at the *end*
35 # so any dirs that are already in the PYTHONPATH will be preferred. 33 # so any dirs that are already in the PYTHONPATH will be preferred.
36 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) 34 PARENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
37 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY) 35 GM_DIRECTORY = os.path.dirname(PARENT_DIRECTORY)
38 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY) 36 TRUNK_DIRECTORY = os.path.dirname(GM_DIRECTORY)
39 TOOLS_DIRECTORY = os.path.join(TRUNK_DIRECTORY, 'tools')
40 if TOOLS_DIRECTORY not in sys.path:
41 sys.path.append(TOOLS_DIRECTORY)
42 import svn
43 if GM_DIRECTORY not in sys.path: 37 if GM_DIRECTORY not in sys.path:
44 sys.path.append(GM_DIRECTORY) 38 sys.path.append(GM_DIRECTORY)
45 import gm_json 39 import gm_json
46 40
47 # Imports from local dir 41 # Imports from local dir
48 # 42 #
49 # Note: we import results under a different name, to avoid confusion with the 43 # Note: we import results under a different name, to avoid confusion with the
50 # Server.results() property. See discussion at 44 # Server.results() property. See discussion at
51 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p y#newcode44 45 # https://codereview.chromium.org/195943004/diff/1/gm/rebaseline_server/server.p y#newcode44
52 import compare_configs 46 import compare_configs
53 import compare_to_expectations 47 import compare_to_expectations
48 import download_actuals
49 import gs_utils
54 import imagepairset 50 import imagepairset
55 import results as results_mod 51 import results as results_mod
56 52
57 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)') 53 PATHSPLIT_RE = re.compile('/([^/]+)/(.+)')
58 54
59 # A simple dictionary of file name extensions to MIME types. The empty string 55 # A simple dictionary of file name extensions to MIME types. The empty string
60 # entry is used as the default when no extension was given or if the extension 56 # entry is used as the default when no extension was given or if the extension
61 # has no entry in this dictionary. 57 # has no entry in this dictionary.
62 MIME_TYPE_MAP = {'': 'application/octet-stream', 58 MIME_TYPE_MAP = {'': 'application/octet-stream',
63 'html': 'text/html', 59 'html': 'text/html',
64 'css': 'text/css', 60 'css': 'text/css',
65 'png': 'image/png', 61 'png': 'image/png',
66 'js': 'application/javascript', 62 'js': 'application/javascript',
67 'json': 'application/json' 63 'json': 'application/json'
68 } 64 }
69 65
70 # Keys that server.py uses to create the toplevel content header. 66 # Keys that server.py uses to create the toplevel content header.
71 # NOTE: Keep these in sync with static/constants.js 67 # NOTE: Keep these in sync with static/constants.js
72 KEY__EDITS__MODIFICATIONS = 'modifications' 68 KEY__EDITS__MODIFICATIONS = 'modifications'
73 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash' 69 KEY__EDITS__OLD_RESULTS_HASH = 'oldResultsHash'
74 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType' 70 KEY__EDITS__OLD_RESULTS_TYPE = 'oldResultsType'
75 71
76 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR 72 DEFAULT_ACTUALS_DIR = results_mod.DEFAULT_ACTUALS_DIR
77 DEFAULT_ACTUALS_REPO_REVISION = 'HEAD' 73 DEFAULT_GM_SUMMARIES_BUCKET = download_actuals.GM_SUMMARIES_BUCKET
78 DEFAULT_ACTUALS_REPO_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual' 74 DEFAULT_JSON_FILENAME = download_actuals.DEFAULT_JSON_FILENAME
79 DEFAULT_PORT = 8888 75 DEFAULT_PORT = 8888
80 76
81 # Directory, relative to PARENT_DIRECTORY, within which the server will serve 77 # Directory, relative to PARENT_DIRECTORY, within which the server will serve
82 # out live results (not static files). 78 # out live results (not static files).
83 RESULTS_SUBDIR = 'results' 79 RESULTS_SUBDIR = 'results'
84 # Directory, relative to PARENT_DIRECTORY, within which the server will serve 80 # Directory, relative to PARENT_DIRECTORY, within which the server will serve
85 # out static files. 81 # out static files.
86 STATIC_CONTENTS_SUBDIR = 'static' 82 STATIC_CONTENTS_SUBDIR = 'static'
87 # All of the GENERATED_*_SUBDIRS are relative to STATIC_CONTENTS_SUBDIR 83 # All of the GENERATED_*_SUBDIRS are relative to STATIC_CONTENTS_SUBDIR
88 GENERATED_HTML_SUBDIR = 'generated-html' 84 GENERATED_HTML_SUBDIR = 'generated-html'
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
132 """Returns routable IP address of this host (the IP address of its network 128 """Returns routable IP address of this host (the IP address of its network
133 interface that would be used for most traffic, not its localhost 129 interface that would be used for most traffic, not its localhost
134 interface). See http://stackoverflow.com/a/166589 """ 130 interface). See http://stackoverflow.com/a/166589 """
135 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 131 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
136 sock.connect(('8.8.8.8', 80)) 132 sock.connect(('8.8.8.8', 80))
137 host = sock.getsockname()[0] 133 host = sock.getsockname()[0]
138 sock.close() 134 sock.close()
139 return host 135 return host
140 136
141 137
142 def _create_svn_checkout(dir_path, repo_url):
143 """Creates local checkout of an SVN repository at the specified directory
144 path, returning an svn.Svn object referring to the local checkout.
145
146 Args:
147 dir_path: path to the local checkout; if this directory does not yet exist,
148 it will be created and the repo will be checked out into it
149 repo_url: URL of SVN repo to check out into dir_path (unless the local
150 checkout already exists)
151 Returns: an svn.Svn object referring to the local checkout.
152 """
153 local_checkout = svn.Svn(dir_path)
154 if not os.path.isdir(dir_path):
155 os.makedirs(dir_path)
156 local_checkout.Checkout(repo_url, '.')
157 return local_checkout
158
159
160 def _create_index(file_path, config_pairs): 138 def _create_index(file_path, config_pairs):
161 """Creates an index file linking to all results available from this server. 139 """Creates an index file linking to all results available from this server.
162 140
163 Prior to https://codereview.chromium.org/215503002 , we had a static 141 Prior to https://codereview.chromium.org/215503002 , we had a static
164 index.html within our repo. But now that the results may or may not include 142 index.html within our repo. But now that the results may or may not include
165 config comparisons, index.html needs to be generated differently depending 143 config comparisons, index.html needs to be generated differently depending
166 on which results are included. 144 on which results are included.
167 145
168 TODO(epoger): Instead of including raw HTML within the Python code, 146 TODO(epoger): Instead of including raw HTML within the Python code,
169 consider restoring the index.html file as a template and using django (or 147 consider restoring the index.html file as a template and using django (or
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
206 file_handle.write('</li>') 184 file_handle.write('</li>')
207 file_handle.write('</ul>') 185 file_handle.write('</ul>')
208 file_handle.write('</ul></body></html>') 186 file_handle.write('</ul></body></html>')
209 187
210 188
211 class Server(object): 189 class Server(object):
212 """ HTTP server for our HTML rebaseline viewer. """ 190 """ HTTP server for our HTML rebaseline viewer. """
213 191
214 def __init__(self, 192 def __init__(self,
215 actuals_dir=DEFAULT_ACTUALS_DIR, 193 actuals_dir=DEFAULT_ACTUALS_DIR,
216 actuals_repo_revision=DEFAULT_ACTUALS_REPO_REVISION, 194 json_filename=DEFAULT_JSON_FILENAME,
217 actuals_repo_url=DEFAULT_ACTUALS_REPO_URL, 195 gm_summaries_bucket=DEFAULT_GM_SUMMARIES_BUCKET,
218 port=DEFAULT_PORT, export=False, editable=True, 196 port=DEFAULT_PORT, export=False, editable=True,
219 reload_seconds=0, config_pairs=None, builder_regex_list=None): 197 reload_seconds=0, config_pairs=None, builder_regex_list=None):
220 """ 198 """
221 Args: 199 Args:
222 actuals_dir: directory under which we will check out the latest actual 200 actuals_dir: directory under which we will check out the latest actual
223 GM results 201 GM results
224 actuals_repo_revision: revision of actual-results.json files to process 202 json_filename: basename of the JSON summary file to load for each builder
225 actuals_repo_url: SVN repo to download actual-results.json files from; 203 gm_summaries_bucket: Google Storage bucket to download json_filename
226 if None or '', don't fetch new actual-results files at all, 204 files from; if None or '', don't fetch new actual-results files
227 just compare to whatever files are already in actuals_dir 205 at all, just compare to whatever files are already in actuals_dir
228 port: which TCP port to listen on for HTTP requests 206 port: which TCP port to listen on for HTTP requests
229 export: whether to allow HTTP clients on other hosts to access this server 207 export: whether to allow HTTP clients on other hosts to access this server
230 editable: whether HTTP clients are allowed to submit new baselines 208 editable: whether HTTP clients are allowed to submit new baselines
231 reload_seconds: polling interval with which to check for new results; 209 reload_seconds: polling interval with which to check for new results;
232 if 0, don't check for new results at all 210 if 0, don't check for new results at all
233 config_pairs: List of (string, string) tuples; for each tuple, compare 211 config_pairs: List of (string, string) tuples; for each tuple, compare
234 actual results of these two configs. If None or empty, 212 actual results of these two configs. If None or empty,
235 don't compare configs at all. 213 don't compare configs at all.
236 builder_regex_list: List of regular expressions specifying which builders 214 builder_regex_list: List of regular expressions specifying which builders
237 we will process. If None, process all builders. 215 we will process. If None, process all builders.
238 """ 216 """
239 self._actuals_dir = actuals_dir 217 self._actuals_dir = actuals_dir
240 self._actuals_repo_revision = actuals_repo_revision 218 self._json_filename = json_filename
241 self._actuals_repo_url = actuals_repo_url 219 self._gm_summaries_bucket = gm_summaries_bucket
242 self._port = port 220 self._port = port
243 self._export = export 221 self._export = export
244 self._editable = editable 222 self._editable = editable
245 self._reload_seconds = reload_seconds 223 self._reload_seconds = reload_seconds
246 self._config_pairs = config_pairs or [] 224 self._config_pairs = config_pairs or []
247 self._builder_regex_list = builder_regex_list 225 self._builder_regex_list = builder_regex_list
248 _create_index( 226 _create_index(
249 file_path=os.path.join( 227 file_path=os.path.join(
250 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR, 228 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, GENERATED_HTML_SUBDIR,
251 "index.html"), 229 "index.html"),
252 config_pairs=config_pairs) 230 config_pairs=config_pairs)
253 # TODO(epoger): Create shareable functions within download_actuals.py that
254 # we can use both there and here to download the actual image results.
255 if actuals_repo_url:
256 self._actuals_repo = _create_svn_checkout(
257 dir_path=actuals_dir, repo_url=actuals_repo_url)
258 231
259 # Reentrant lock that must be held whenever updating EITHER of: 232 # Reentrant lock that must be held whenever updating EITHER of:
260 # 1. self._results 233 # 1. self._results
261 # 2. the expected or actual results on local disk 234 # 2. the expected or actual results on local disk
262 self.results_rlock = threading.RLock() 235 self.results_rlock = threading.RLock()
263 # self._results will be filled in by calls to update_results() 236 # self._results will be filled in by calls to update_results()
264 self._results = None 237 self._results = None
265 238
266 @property 239 @property
267 def results(self): 240 def results(self):
(...skipping 27 matching lines...) Expand all
295 the same time. 268 the same time.
296 269
297 Args: 270 Args:
298 invalidate: if True, invalidate self._results immediately upon entry; 271 invalidate: if True, invalidate self._results immediately upon entry;
299 otherwise, we will let readers see those results until we 272 otherwise, we will let readers see those results until we
300 replace them 273 replace them
301 """ 274 """
302 with self.results_rlock: 275 with self.results_rlock:
303 if invalidate: 276 if invalidate:
304 self._results = None 277 self._results = None
305 if self._actuals_repo_url: 278 if self._gm_summaries_bucket:
306 logging.info( 279 logging.info(
307 'Updating actual GM results in %s to revision %s from repo %s ...' 280 'Updating GM result summaries in %s from gm_summaries_bucket %s ...'
308 % ( 281 % (self._actuals_dir, self._gm_summaries_bucket))
309 self._actuals_dir, self._actuals_repo_revision, 282
310 self._actuals_repo_url)) 283 # Clean out actuals_dir first, in case some builders have gone away
311 self._actuals_repo.Update( 284 # since we last ran.
312 path='.', revision=self._actuals_repo_revision) 285 if os.path.isdir(self._actuals_dir):
286 shutil.rmtree(self._actuals_dir)
287
288 # Get the list of builders we care about.
289 all_builders = download_actuals.get_builders_list(
290 summaries_bucket=self._gm_summaries_bucket)
291 if self._builder_regex_list:
292 matching_builders = []
293 for builder in all_builders:
294 for regex in self._builder_regex_list:
295 if re.match(regex, builder):
296 matching_builders.append(builder)
297 break # go on to the next builder, no need to try more regexes
298 else:
299 matching_builders = all_builders
300
301 # Download the JSON file for each builder we care about.
302 #
303 # TODO(epoger): When this is a large number of builders, we would be
304 # better off downloading them in parallel!
305 for builder in matching_builders:
306 gs_utils.download_file(
307 source_bucket=self._gm_summaries_bucket,
308 source_path=posixpath.join(builder, self._json_filename),
309 dest_path=os.path.join(self._actuals_dir, builder,
310 self._json_filename),
311 create_subdirs_if_needed=True)
313 312
314 # We only update the expectations dir if the server was run with a 313 # We only update the expectations dir if the server was run with a
315 # nonzero --reload argument; otherwise, we expect the user to maintain 314 # nonzero --reload argument; otherwise, we expect the user to maintain
316 # her own expectations as she sees fit. 315 # her own expectations as she sees fit.
317 # 316 #
318 # Because the Skia repo is moving from SVN to git, and git does not 317 # Because the Skia repo is hosted using git, and git does not
319 # support updating a single directory tree, we have to update the entire 318 # support updating a single directory tree, we have to update the entire
320 # repo checkout. 319 # repo checkout.
321 # 320 #
322 # Because Skia uses depot_tools, we have to update using "gclient sync" 321 # Because Skia uses depot_tools, we have to update using "gclient sync"
323 # instead of raw git (or SVN) update. Happily, this will work whether 322 # instead of raw git commands.
324 # the checkout was created using git or SVN. 323 #
324 # TODO(epoger): Fetch latest expectations in some other way.
325 # Eric points out that our official documentation recommends an
326 # unmanaged Skia checkout, so "gclient sync" will not bring down updated
327 # expectations from origin/master-- you'd have to do a "git pull" of
328 # some sort instead.
329 # However, the live rebaseline_server at
330 # http://skia-tree-status.appspot.com/redirect/rebaseline-server (which
331 # is probably the only user of the --reload flag!) uses a managed
332 # checkout, so "gclient sync" works in that case.
333 # Probably the best idea is to avoid all of this nonsense by fetching
334 # updated expectations into a temp directory, and leaving the rest of
335 # the checkout alone. This could be done using "git show", or by
336 # downloading individual expectation JSON files from
337 # skia.googlesource.com .
325 if self._reload_seconds: 338 if self._reload_seconds:
326 logging.info( 339 logging.info(
327 'Updating expected GM results in %s by syncing Skia repo ...' % 340 'Updating expected GM results in %s by syncing Skia repo ...' %
328 compare_to_expectations.DEFAULT_EXPECTATIONS_DIR) 341 compare_to_expectations.DEFAULT_EXPECTATIONS_DIR)
329 _run_command(['gclient', 'sync'], TRUNK_DIRECTORY) 342 _run_command(['gclient', 'sync'], TRUNK_DIRECTORY)
330 343
331 self._results = compare_to_expectations.ExpectationComparisons( 344 self._results = compare_to_expectations.ExpectationComparisons(
332 actuals_root=self._actuals_dir, 345 actuals_root=self._actuals_dir,
333 generated_images_root=os.path.join( 346 generated_images_root=os.path.join(
334 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR, 347 PARENT_DIRECTORY, STATIC_CONTENTS_SUBDIR,
(...skipping 281 matching lines...) Expand 10 before | Expand all | Expand 10 after
616 def main(): 629 def main():
617 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', 630 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
618 datefmt='%m/%d/%Y %H:%M:%S', 631 datefmt='%m/%d/%Y %H:%M:%S',
619 level=logging.INFO) 632 level=logging.INFO)
620 parser = argparse.ArgumentParser() 633 parser = argparse.ArgumentParser()
621 parser.add_argument('--actuals-dir', 634 parser.add_argument('--actuals-dir',
622 help=('Directory into which we will check out the latest ' 635 help=('Directory into which we will check out the latest '
623 'actual GM results. If this directory does not ' 636 'actual GM results. If this directory does not '
624 'exist, it will be created. Defaults to %(default)s'), 637 'exist, it will be created. Defaults to %(default)s'),
625 default=DEFAULT_ACTUALS_DIR) 638 default=DEFAULT_ACTUALS_DIR)
626 parser.add_argument('--actuals-repo', 639 # TODO(epoger): Before https://codereview.chromium.org/310093003 ,
627 help=('URL of SVN repo to download actual-results.json ' 640 # when this tool downloaded the JSON summaries from skia-autogen,
628 'files from. Defaults to %(default)s ; if set to ' 641 # it had an --actuals-revision the caller could specify to download
629 'empty string, just compare to actual-results ' 642 # actual results as of a specific point in time. We should add similar
630 'already found in ACTUALS_DIR.'), 643 # functionality when retrieving the summaries from Google Storage.
631 default=DEFAULT_ACTUALS_REPO_URL)
632 parser.add_argument('--actuals-revision',
633 help=('revision of actual-results.json files to process. '
634 'Defaults to %(default)s . Beware of setting this '
635 'argument in conjunction with --editable; you '
636 'probably only want to edit results at HEAD.'),
637 default=DEFAULT_ACTUALS_REPO_REVISION)
638 parser.add_argument('--builders', metavar='BUILDER_REGEX', nargs='+', 644 parser.add_argument('--builders', metavar='BUILDER_REGEX', nargs='+',
639 help=('Only process builders matching these regular ' 645 help=('Only process builders matching these regular '
640 'expressions. If unspecified, process all ' 646 'expressions. If unspecified, process all '
641 'builders.')) 647 'builders.'))
642 parser.add_argument('--compare-configs', action='store_true', 648 parser.add_argument('--compare-configs', action='store_true',
643 help=('In addition to generating differences between ' 649 help=('In addition to generating differences between '
644 'expectations and actuals, also generate ' 650 'expectations and actuals, also generate '
645 'differences between these config pairs: ' 651 'differences between these config pairs: '
646 + str(CONFIG_PAIRS_TO_COMPARE))) 652 + str(CONFIG_PAIRS_TO_COMPARE)))
647 parser.add_argument('--editable', action='store_true', 653 parser.add_argument('--editable', action='store_true',
648 help=('Allow HTTP clients to submit new baselines.')) 654 help=('Allow HTTP clients to submit new baselines.'))
649 parser.add_argument('--export', action='store_true', 655 parser.add_argument('--export', action='store_true',
650 help=('Instead of only allowing access from HTTP clients ' 656 help=('Instead of only allowing access from HTTP clients '
651 'on localhost, allow HTTP clients on other hosts ' 657 'on localhost, allow HTTP clients on other hosts '
652 'to access this server. WARNING: doing so will ' 658 'to access this server. WARNING: doing so will '
653 'allow users on other hosts to modify your ' 659 'allow users on other hosts to modify your '
654 'GM expectations, if combined with --editable.')) 660 'GM expectations, if combined with --editable.'))
661 parser.add_argument('--gm-summaries-bucket',
662 help=('Google Cloud Storage bucket to download '
663 'JSON_FILENAME files from. '
664 'Defaults to %(default)s ; if set to '
665 'empty string, just compare to actual-results '
666 'already found in ACTUALS_DIR.'),
667 default=DEFAULT_GM_SUMMARIES_BUCKET)
668 parser.add_argument('--json-filename',
669 help=('JSON summary filename to read for each builder; '
670 'defaults to %(default)s.'),
671 default=DEFAULT_JSON_FILENAME)
655 parser.add_argument('--port', type=int, 672 parser.add_argument('--port', type=int,
656 help=('Which TCP port to listen on for HTTP requests; ' 673 help=('Which TCP port to listen on for HTTP requests; '
657 'defaults to %(default)s'), 674 'defaults to %(default)s'),
658 default=DEFAULT_PORT) 675 default=DEFAULT_PORT)
659 parser.add_argument('--reload', type=int, 676 parser.add_argument('--reload', type=int,
660 help=('How often (a period in seconds) to update the ' 677 help=('How often (a period in seconds) to update the '
661 'results. If specified, both expected and actual ' 678 'results. If specified, both expected and actual '
662 'results will be updated by running "gclient sync" ' 679 'results will be updated by running "gclient sync" '
663 'on your Skia checkout as a whole. ' 680 'on your Skia checkout as a whole. '
664 'By default, we do not reload at all, and you ' 681 'By default, we do not reload at all, and you '
665 'must restart the server to pick up new data.'), 682 'must restart the server to pick up new data.'),
666 default=0) 683 default=0)
667 args = parser.parse_args() 684 args = parser.parse_args()
668 if args.compare_configs: 685 if args.compare_configs:
669 config_pairs = CONFIG_PAIRS_TO_COMPARE 686 config_pairs = CONFIG_PAIRS_TO_COMPARE
670 else: 687 else:
671 config_pairs = None 688 config_pairs = None
672 689
673 global _SERVER 690 global _SERVER
674 _SERVER = Server(actuals_dir=args.actuals_dir, 691 _SERVER = Server(actuals_dir=args.actuals_dir,
675 actuals_repo_revision=args.actuals_revision, 692 json_filename=args.json_filename,
676 actuals_repo_url=args.actuals_repo, 693 gm_summaries_bucket=args.gm_summaries_bucket,
677 port=args.port, export=args.export, editable=args.editable, 694 port=args.port, export=args.export, editable=args.editable,
678 reload_seconds=args.reload, config_pairs=config_pairs, 695 reload_seconds=args.reload, config_pairs=config_pairs,
679 builder_regex_list=args.builders) 696 builder_regex_list=args.builders)
680 _SERVER.run() 697 _SERVER.run()
681 698
682 699
683 if __name__ == '__main__': 700 if __name__ == '__main__':
684 main() 701 main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698