1#!/usr/bin/env python3
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this file,
4# You can obtain one at http://mozilla.org/MPL/2.0/.
5
6import pathlib
7import json
8import urllib.request
9import re
10import subprocess
11import sys
12
13
14class Logger:
15    @classmethod
16    def info(cls, s):
17        print('[INFO]', s)
18
19        # Flush to make it apeear immediately in automation log.
20        sys.stdout.flush()
21
22    @classmethod
23    def fetch(cls, url):
24        cls.info(f'Fetching {url}')
25
26    @classmethod
27    def cmd(cls, cmd):
28        def format_cmd(s):
29            if ' ' in s:
30                escaped = s.replace('"', '\"')
31                return f'"{escaped}"'
32            return s
33
34        formatted_command = ' '.join(list(map(format_cmd, cmd)))
35        cls.info(f'$ {formatted_command}')
36
37
38class GitRepository:
39    def __init__(self, path):
40        self.path = path
41
42        self.git_dir = self.path / '.git'
43        if not self.git_dir.exists():
44            print(f'{self.path} is not a Git repository.', file=sys.stderr)
45            sys.exit(1)
46
47    def get_output(self, *args):
48        cmd = ['git'] + list(args)
49        Logger.cmd(cmd)
50        output = subprocess.run(cmd,
51                                capture_output=True,
52                                cwd=self.path)
53
54        return output.stdout.decode()
55
56    def run(self, *args):
57        cmd = ['git'] + list(args)
58        Logger.cmd(cmd)
59        subprocess.run(cmd,
60                       check=True,
61                       cwd=self.path)
62
63    def commit_message(self, rev):
64        return self.get_output('log', '-1', '--pretty=format:%s%n', rev)
65
66
67class MCRemoteRepository:
68    HG_API_URL = 'https://hg.mozilla.org/mozilla-central/'
69
70    @classmethod
71    def call(cls, name, path):
72        url = f'{cls.HG_API_URL}{name}{path}'
73        Logger.fetch(url)
74        req = urllib.request.Request(url, None, {})
75        response = urllib.request.urlopen(req)
76        return response.read()
77
78    @classmethod
79    def call_json(cls, name, path):
80        return json.loads(cls.call(name, path))
81
82    @classmethod
83    def file(cls, rev, path):
84        return cls.call('raw-file', f'/{rev}{path}')
85
86
87class TreeHerder:
88    API_URL = 'https://treeherder.mozilla.org/api/'
89
90    @classmethod
91    def call(cls, name):
92        url = f'{cls.API_URL}{name}'
93        Logger.fetch(url)
94        req = urllib.request.Request(url, None, {
95            'User-Agent': 'smoosh-tools',
96        })
97        response = urllib.request.urlopen(req)
98        return response.read()
99
100    @classmethod
101    def call_json(cls, name):
102        return json.loads(cls.call(name))
103
104    @classmethod
105    def push_id(cls, rev):
106        push = cls.call_json(f'project/mozilla-central/push/?full=true&format=json&count=1&revision={rev}')
107        return push['results'][0]['id']
108
109    @classmethod
110    def jobs(cls, push_id):
111        push = cls.call_json(f'jobs/?push_id={push_id}&format=json')
112        count = push['count']
113        results = []
114        results += push['results']
115
116        page = 2
117        while len(results) < count:
118            push = cls.call_json(f'jobs/?push_id={push_id}&format=json&page={page}')
119            results += push['results']
120            page += 1
121
122        return results
123
124
125class Status:
126    def run(is_ci):
127        Logger.info('Fetching ci_generated branch')
128
129        jsparagus = GitRepository(pathlib.Path('./'))
130        jsparagus.run('fetch', 'origin', 'ci_generated')
131
132        Logger.info('Checking mozilla-central tip revision')
133
134        m_c_rev = MCRemoteRepository.call_json('json-log', '/tip/')['node']
135        cargo_file = MCRemoteRepository.file(
136            m_c_rev,
137            '/js/src/frontend/smoosh/Cargo.toml'
138        ).decode()
139        m = re.search('rev = "(.+)"', cargo_file)
140        ci_generated_rev = m.group(1)
141
142        Logger.info('Checking jsparagus referred by mozilla-central')
143
144        message = jsparagus.commit_message(ci_generated_rev)
145        m = re.search('for ([A-Fa-f0-9]+)', message)
146        master_rev = m.group(1)
147
148        Logger.info('Checking build status')
149
150        push_id = TreeHerder.push_id(m_c_rev)
151        jobs = TreeHerder.jobs(push_id)
152        nonunified_job = None
153        smoosh_job = None
154        for job in jobs:
155            if 'spidermonkey-sm-nonunified-linux64/debug' in job:
156                nonunified_job = job
157            if 'spidermonkey-sm-smoosh-linux64/debug' in job:
158                smoosh_job = job
159
160        def get_result(job):
161            if job:
162                if 'completed' in job:
163                    if 'success' in job:
164                        return 'OK'
165                    else:
166                        return 'NG'
167                else:
168                    return 'not yet finished'
169            else:
170                return 'unknown'
171
172        nonunified_result = get_result(nonunified_job)
173        smoosh_result = get_result(smoosh_job)
174
175        if is_ci:
176            print(f'##[set-output name=mc;]{m_c_rev}')
177            print(f'##[set-output name=jsparagus;]{master_rev}')
178            print(f'##[set-output name=build;]{nonunified_result}')
179            print(f'##[set-output name=test;]{smoosh_result}')
180        else:
181            print(f'mozilla-central tip: {m_c_rev}')
182            print(f'referred jsparagus revision: {master_rev}')
183            print(f'Build status:')
184            print(f'  Build with --enable-smoosh: {nonunified_result}')
185            print(f'  Test with --smoosh: {smoosh_result}')
186
187
188is_ci = False
189if len(sys.argv) > 1:
190    if sys.argv[1] == 'ci':
191        is_ci = True
192
193Status.run(is_ci)
194