1#!/usr/bin/env nix-shell
2#!nix-shell -i python3 -p 'python3.withPackages(ps: with ps; [ requests pyquery click ])'
3
4# To use, just execute this script with --help to display help.
5
6import subprocess
7import json
8import sys
9
10import click
11import requests
12from pyquery import PyQuery as pq
13
14
15maintainers_json = subprocess.check_output([
16 'nix-instantiate', '-E', 'import ./maintainers/maintainer-list.nix {}', '--eval', '--json'
17])
18maintainers = json.loads(maintainers_json)
19MAINTAINERS = {v: k for k, v in maintainers.items()}
20
21
22def get_response_text(url):
23 return pq(requests.get(url).text) # IO
24
25EVAL_FILE = {
26 'nixos': 'nixos/release.nix',
27 'nixpkgs': 'pkgs/top-level/release.nix',
28}
29
30
31def get_maintainers(attr_name):
32 try:
33 nixname = attr_name.split('.')
34 meta_json = subprocess.check_output([
35 'nix-instantiate',
36 '--eval',
37 '--strict',
38 '-A',
39 '.'.join(nixname[1:]) + '.meta',
40 EVAL_FILE[nixname[0]],
41 '--json'])
42 meta = json.loads(meta_json)
43 if meta.get('maintainers'):
44 return [MAINTAINERS[name] for name in meta['maintainers'] if MAINTAINERS.get(name)]
45 except:
46 return []
47
48def print_build(table_row):
49 a = pq(table_row)('a')[1]
50 print("- [ ] [{}]({})".format(a.text, a.get('href')), flush=True)
51
52 maintainers = get_maintainers(a.text)
53 if maintainers:
54 print(" - maintainers: {}".format(", ".join(map(lambda u: '@' + u, maintainers))))
55 # TODO: print last three persons that touched this file
56 # TODO: pinpoint the diff that broke this build, or maybe it's transient or maybe it never worked?
57
58 sys.stdout.flush()
59
60@click.command()
61@click.option(
62 '--jobset',
63 default="nixos/release-17.09",
64 help='Hydra project like nixos/release-17.09')
65def cli(jobset):
66 """
67 Given a Hydra project, inspect latest evaluation
68 and print a summary of failed builds
69 """
70
71 url = "http://hydra.nixos.org/jobset/{}".format(jobset)
72
73 # get the last evaluation
74 click.echo(click.style(
75 'Getting latest evaluation for {}'.format(url), fg='green'))
76 d = get_response_text(url)
77 evaluations = d('#tabs-evaluations').find('a[class="row-link"]')
78 latest_eval_url = evaluations[0].get('href')
79
80 # parse last evaluation page
81 click.echo(click.style(
82 'Parsing evaluation {}'.format(latest_eval_url), fg='green'))
83 d = get_response_text(latest_eval_url + '?full=1')
84
85 # TODO: aborted evaluations
86 # TODO: dependency failed without propagated builds
87 print('\nFailures:')
88 for tr in d('img[alt="Failed"]').parents('tr'):
89 print_build(tr)
90
91 print('\nDependency failures:')
92 for tr in d('img[alt="Dependency failed"]').parents('tr'):
93 print_build(tr)
94
95
96if __name__ == "__main__":
97 try:
98 cli()
99 except Exception as e:
100 import pdb;pdb.post_mortem()