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

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

Powered by Google App Engine
This is Rietveld 408576698