blob: 126583f2e64e4b9998acaeb9df3557459aca23df [file] [log] [blame]
#!/usr/bin/python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import print_function
import argparse
import os
import re
import sys
import traceback
TOOLS_DIR = os.path.abspath(os.path.dirname(__file__))
SCRIPTS_DIR = os.path.dirname(TOOLS_DIR)
# This adjusts sys.path, so must be done before we import other modules.
if not SCRIPTS_DIR in sys.path: # pragma: no cover
sys.path.append(SCRIPTS_DIR)
from common import chromium_utils
from common import env
from common import filesystem
class Tool(object):
def __init__(self):
self.fs = filesystem.Filesystem()
self.stdout = sys.stdout
self.stderr = sys.stderr
self.build_dir = env.Build
self.build_internal_dir = env.BuildInternal
def print_(self, *args, **kwargs):
kwargs.setdefault('file', self.stdout)
print(*args, **kwargs)
def main(self, argv):
args = self.parse_args(argv)
return args.func(args)
def parse_args(self, argv):
parser = argparse.ArgumentParser()
subps = parser.add_subparsers()
def add_common_args(subp):
subp.add_argument('master_dirname', nargs='*',
help='Path to master config directory (must contain '
'a builders.pyl file).')
subp.add_argument('--external-only', action='store_true')
subp.add_argument('--internal-only', action='store_true')
subp = subps.add_parser('gen', help=self.run_gen.__doc__)
add_common_args(subp)
subp.set_defaults(func=self.run_gen)
subp = subps.add_parser('check', help=self.run_check.__doc__)
add_common_args(subp)
subp.set_defaults(func=self.run_check)
subp = subps.add_parser('help', help=self.run_help.__doc__)
subp.add_argument(nargs='?', action='store', dest='subcommand',
help='The command to get help for.')
subp.set_defaults(func=self.run_help)
return parser.parse_args(argv)
def run_check(self, args):
"""Checks that the master configs are up-to-date."""
files_to_write, ret = self._generate(args)
for path in sorted(files_to_write):
self.print_('%s is out-of-date.' % self.fs.relpath(path))
return 1 if files_to_write else ret
def run_gen(self, args):
"""Generates and updates master configs."""
files_to_write, ret = self._generate(args)
for path in sorted(files_to_write):
if self.fs.exists(self.fs.join(self.build_dir, self.fs.dirname(path))):
d = self.build_dir
else:
d = self.build_internal_dir
self.fs.write_text_file(self.fs.join(d, path), files_to_write[path])
self.print_('Wrote %s.' % self.fs.relpath(path, d))
return ret
def run_help(self, args):
"""Get help on a subcommand."""
if args.subcommand:
return self.main([args.subcommand, '--help'])
return self.main(['--help'])
def _generate(self, args):
files_to_write = {}
paths = self._builders_paths(args)
failed = False
for path in paths:
try:
self._process_one_builders_file(path, files_to_write)
except SyntaxError as e:
msg = ''.join(traceback.format_exception_only(type(e), e))
self.print_(msg)
failed = True
except chromium_utils.BuildersFileError as e:
self.print_(e)
failed = True
if failed or not paths:
return {}, 1
return files_to_write, 0
def _builders_paths(self, args):
builders_paths = []
failed = False
fs = self.fs
if args.master_dirname:
for d in args.master_dirname:
builders_path = fs.join(d, 'builders.pyl')
if not fs.exists(builders_path):
self.print_('%s not found' % builders_path, file=self.stderr)
failed = True
else:
builders_paths.append(builders_path)
else:
masters_dirs = []
if not args.internal_only:
masters_dirs.append(fs.join(self.build_dir, 'masters'))
if not args.external_only and self.build_internal_dir:
masters_dirs.append(fs.join(self.build_internal_dir, 'masters'))
for masters_dir in masters_dirs:
for master_dir in fs.listdirs(masters_dir):
builders_path = fs.join(masters_dir, master_dir, 'builders.pyl')
if fs.exists(builders_path):
builders_paths.append(builders_path)
if failed:
return []
if not builders_paths:
self.print_('No builders.pyl files found.', file=self.stderr)
return []
return builders_paths
def _process_one_builders_file(self, builders_path, files_to_write):
fs = self.fs
out_dir = fs.dirname(builders_path)
builders_contents = fs.read_text_file(builders_path)
values = chromium_utils.ParseBuildersFileContents(builders_path,
builders_contents)
template_subpath = fs.join('scripts', 'tools', 'buildbot_tool_templates')
template_dir = fs.join(self.build_dir, template_subpath)
for filename in fs.listfiles(template_dir):
template = fs.read_text_file(fs.join(template_dir, filename))
new_contents = self._expand(template, values,
'%s/%s' % (template_subpath, filename))
path = fs.join(out_dir, filename)
if fs.exists(path) and fs.read_text_file(path) == new_contents:
continue
files_to_write[path] = new_contents
def _expand(self, template, values, path):
try:
contents = template % values
except:
self.print_("Error populating template %s" % path, file=self.stderr)
raise
contents = self._update_generated_file_disclaimer(contents, path)
return contents.strip() + '\n'
def _update_generated_file_disclaimer(self, contents, path):
pattern = '# This file is used by scripts/tools/buildbot-tool.*'
replacement = ('# This file was generated from\n'
'# %s\n'
'# by "../../build/scripts/tools/buildbot-tool gen .".\n'
'# DO NOT EDIT BY HAND!\n' % path)
return re.sub(pattern, replacement, contents)
if __name__ == '__main__': # pragma: no cover
tool = Tool()
sys.exit(tool.main(sys.argv[1:]))