Anton Shestakov <av6@dwimlabs.net>, Thu, 14 Sep 2017 17:23:42 +0800
requirements: pytest-cov 2.5.1
checker.py
Permissions: -rwxr-xr-x
from __future__ import absolute_import, print_function from argparse import ArgumentParser, FileType from fnmatch import fnmatchcase from shutil import rmtree from subprocess import check_call, check_output, CalledProcessError from tempfile import mkdtemp from candolint.utils import rel, timestamp def run_ignore_codes(fn, args, codes, **kwargs): return fn(args, **kwargs) except CalledProcessError as e: if e.returncode not in codes: def run(args, silent=False, get_output=False, ignore_codes=None, env=None): full_env = os.environ.copy() print('$ ' + ' '.join(args)) result = run_ignore_codes(fn, args, ignore_codes, env=full_env) result = fn(args, env=full_env) print('# C&O error: {}'.format(e)) def run_git(args, **kwargs): env = kwargs.pop('env', {}) env.update({'LC_ALL': 'en_US.UTF-8'}) return run(['git'] + args, env=env, **kwargs) def run_hg(args, **kwargs): env = kwargs.pop('env', {}) env.update({'HGPLAIN': '1'}) return run(['hg'] + args, env=env, **kwargs) def read_linter_config(name, path=rel('linters')): fd = open(os.path.join(path, '{}.yml'.format(name))) print("# C&O couldn't load linter config: {}".format(e)) return yaml.safe_load(fd) print("# C&O couldn't parse linter config: {}".format(e)) def read_intree_config(): fd = open('candolint.yml') print("# C&O couldn't load in-tree config: {}".format(e)) return yaml.safe_load(fd) print("# C&O couldn't parse in-tree config: {}".format(e)) print("# C&O config doesn't have 'url' defined.") print("# C&O config doesn't have 'scm' defined.") elif config['scm'] not in ('git', 'hg'): print("# C&O checker doesn't support {} yet.".format(config['scm'])) def process_config(lc, pc): 'env': combined(lc.get('env'), pc.get('env')), 'vars': combined(lc.get('vars', {}), pc.get('vars', {})), 'codes': lc.get('codes', (1,)) return [item % c['vars'] for item in items] for key in ('flags', 'post_flags'): c[key] = render(lc.get(key, []) + pc.get(key, [])) c['setup'] = [render(steps) for steps in lc.get('setup', [])] c['exec'] = render(lc['exec']) c['version'] = render(lc['version']) def git_clone(url, dest): return run_git(['clone', url, dest]) return run_hg(['clone', url, dest]) def git_checkout(commit): return run_git(['checkout', commit]) return run_hg(['update', commit]) return run_git(['status']) return run_hg(['summary']) template = (r'# C&O commit: %H%n' r'# C&O commit ref names: %D%n' r'# C&O commit date: %ai%n' r'# C&O commit author: %aN%n' r'# C&O commit message: %s%n') return run_git(['show', '-q', '--format=format:' + template], silent=True) template = (r'# C&O commit: {rev}:{node}\n' r'# C&O commit branch: {branch}\n' r'# C&O commit date: {date|isodatesec}\n' r'# C&O commit author: {author|person}\n' r'# C&O commit message: {desc|firstline}\n') return run_hg(['log', '-r', '.', '-T', template], silent=True) def git_files(include, exclude): files = run_git(cmd, silent=True, get_output=True) files = files.splitlines() files = [f for f in files if not fnmatchcase(f, e)] def hg_files(include, exclude): files = run_hg(cmd, silent=True, get_output=True, ignore_codes=(1,)) files = files.splitlines() def execute(tmp, config, args): if not prevalidate(config): print('# C&O task: clone') print('# C&O project URL: {}'.format(config['url'])) if config['scm'] == 'git': if not git_clone(config['url'], source): elif config['scm'] == 'hg': if not hg_clone(config['url'], source): print('$ cd {}'.format(source)) if args.commit is not None: if config['scm'] == 'git': if not git_checkout(args.commit): elif config['scm'] == 'hg': if not hg_update(args.commit): if config['scm'] == 'git': elif config['scm'] == 'hg': if config['scm'] == 'git': elif config['scm'] == 'hg': print('# C&O task: setup') if args.data is not None: intree = read_intree_config() print('# C&O using in-tree config') print('# C&O in-tree config is required while bootstrapping') print('# C&O not using in-tree config') for linter in config.get('linters', []): print("# C&O skipping linter without 'name': {}".format(linter)) if 'include' not in linter: print("# C&O skipping '{}': no 'include' in config".format(name)) if name in linter_config: lc = read_linter_config(name) print("# C&O skipping '{}': no config".format(name)) print("# C&O skipping '{}': no 'exec' in config".format(name)) installing = [name for name in distinct if name in linter_config] print('# C&O installing linters: {}'.format(' '.join(installing))) if name not in linter_config: lc = process_config(linter_config[name], linter) key = (name, str(lc.get('setup', []))) for item in lc.get('setup', []): if not run(lc['exec'] + lc['version'], env=lc['env']): if name not in installed: print('# C&O linters installed: {}'.format(' '.join(installed))) print('# C&O task: checks') if name not in linter_config: if config['scm'] == 'git': files = git_files(linter['include'], linter.get('exclude', [])) elif config['scm'] == 'hg': files = hg_files(linter['include'], linter.get('exclude', [])) lc = process_config(linter_config[name], linter) cmd = lc['exec'] + lc['flags'] + [f] + lc['post_flags'] if not run(cmd, ignore_codes=lc['codes'], env=lc['env']): def wrapper(config, args): print('# C&O job started: {}'.format(timestamp())) tmp = mkdtemp(prefix='candolint.') if not execute(tmp, config, args): print('# C&O job failed') print('# C&O task: cleanup') print('# C&O job finished: {}'.format(timestamp())) print('# C&O job failed (exception not caught)') print('# C&O job finished: {}'.format(timestamp())) parser = ArgumentParser() group = parser.add_mutually_exclusive_group(required=True) '--config', metavar='FILE', type=FileType('r'), help='project configuration file (YAML)') '--data', metavar='YAML', help='bootstrapping data as a YAML string') parser.add_argument('--commit', help='a commit to update to') args = parser.parse_args() if args.data is not None: config = yaml.safe_load(args.data) config = yaml.safe_load(args.config) if __name__ == '__main__':