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

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

Powered by Google App Engine
This is Rietveld 408576698