1#! /usr/bin/env python
2# Copyright 2018 Google LLC.
3# Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
4
5import json
6import md5
7import multiprocessing
8import os
9import shutil
10import sys
11import tempfile
12import urllib
13import urllib2
14
15from subprocess import check_call, check_output
16
17assert '/' in [os.sep, os.altsep] and os.pardir == '..'
18
19ASSETS = 'platform_tools/android/apps/skqp/src/main/assets'
20BUCKET = 'skia-skqp-assets'
21
22def urlopen(url):
23    cookie = os.environ.get('SKIA_GOLD_COOKIE', '')
24    return urllib2.urlopen(urllib2.Request(url, headers={'Cookie': cookie}))
25
26def make_skqp_model(arg):
27    name, urls, exe = arg
28    tmp = tempfile.mkdtemp()
29    for url in urls:
30        urllib.urlretrieve(url, tmp + '/' + url[url.rindex('/') + 1:])
31    check_call([exe, tmp, ASSETS + '/gmkb/' + name])
32    shutil.rmtree(tmp)
33    sys.stdout.write(name + ' ')
34    sys.stdout.flush()
35
36def goldgetter(meta, exe):
37    assert os.path.exists(exe)
38    jobs = []
39    for rec in meta:
40        urls = [d['URL'] for d in rec['digests']
41                if d['status'] == 'positive' and
42                (set(d['paramset']['config']) & set(['vk', 'gles']))]
43        if urls:
44            jobs.append((rec['testName'], urls, exe))
45    pool = multiprocessing.Pool(processes=20)
46    pool.map(make_skqp_model, jobs)
47    sys.stdout.write('\n')
48    return set((n for n, _, _ in jobs))
49
50def gold(first_commit, last_commit):
51    c1, c2 = (check_output(['git', 'rev-parse', c]).strip()
52            for c in (first_commit, last_commit))
53    f = urlopen('https://public-gold.skia.org/json/export?' + urllib.urlencode([
54        ('fbegin', c1),
55        ('fend', c2),
56        ('query', 'config=gles&config=vk&source_type=gm'),
57        ('pos', 'true'),
58        ('neg', 'false'),
59        ('unt', 'false')
60    ]))
61    j = json.load(f)
62    f.close()
63    return j
64
65def gset(path):
66    s = set()
67    if os.path.isfile(path):
68        with open(path, 'r') as f:
69            for line in f:
70                s.add(line.strip())
71    return s
72
73def make_rendertest_list(models, good, bad):
74    assert good.isdisjoint(bad)
75    do_score = good & models
76    no_score = bad | (good - models)
77    to_delete = models & bad
78    for d in to_delete:
79        path = ASSETS + '/gmkb/' + d
80        if os.path.isdir(path):
81            shutil.rmtree(path)
82    results = dict()
83    for n in do_score:
84        results[n] = 0
85    for n in no_score:
86        results[n] = -1
87    return ''.join('%s,%d\n' % (n, results[n]) for n in sorted(results))
88
89def get_digest(path):
90    m = md5.new()
91    with open(path, 'r') as f:
92        m.update(f.read())
93    return m.hexdigest()
94
95def upload_cmd(path, digest):
96    return ['gsutil', 'cp', path, 'gs://%s/%s' % (BUCKET, digest)]
97
98def upload_model():
99    bucket_url = 'gs://%s/' % BUCKET
100    extant = set((u.replace(bucket_url, '', 1)
101                  for u in check_output(['gsutil', 'ls', bucket_url]).splitlines() if u))
102    cmds = []
103    filelist = []
104    for dirpath, _, filenames in os.walk(ASSETS + '/gmkb'):
105        for filename in filenames:
106            path = os.path.join(dirpath, filename)
107            digest = get_digest(path)
108            if digest not in extant:
109                cmds.append(upload_cmd(path, digest))
110            filelist.append('%s;%s\n' % (digest, os.path.relpath(path, ASSETS)))
111    tmp = tempfile.mkdtemp()
112    filelist_path = tmp + '/x'
113    with open(filelist_path, 'w') as o:
114        for l in filelist:
115            o.write(l)
116    filelist_digest = get_digest(filelist_path)
117    if filelist_digest not in extant:
118        cmds.append(upload_cmd(filelist_path, filelist_digest))
119
120    pool = multiprocessing.Pool(processes=20)
121    pool.map(check_call, cmds)
122    shutil.rmtree(tmp)
123    return filelist_digest
124
125def remove(x):
126    if os.path.isdir(x) and not os.path.islink(x):
127        shutil.rmtree(x)
128    if os.path.exists(x):
129        os.remove(x)
130
131def main(first_commit, last_commit):
132    check_call(upload_cmd('/dev/null', get_digest('/dev/null')))
133
134    os.chdir(os.path.dirname(__file__) + '/../..')
135    remove(ASSETS + '/files.checksum')
136    for d in [ASSETS + '/gmkb', ASSETS + '/skqp', ]:
137        remove(d)
138        os.mkdir(d)
139
140    check_call([sys.executable, 'tools/git-sync-deps'],
141               env=dict(os.environ, GIT_SYNC_DEPS_QUIET='T'))
142    build = 'out/ndebug'
143    check_call(['bin/gn', 'gen', build,
144                '--args=cc="clang" cxx="clang++" is_debug=false'])
145    check_call(['ninja', '-C', build,
146                'jitter_gms', 'list_gpu_unit_tests', 'make_skqp_model'])
147
148    models = goldgetter(gold(first_commit, last_commit), build + '/make_skqp_model')
149
150    check_call([build + '/jitter_gms', 'tools/skqp/bad_gms.txt'])
151
152    with open(ASSETS + '/skqp/rendertests.txt', 'w') as o:
153        o.write(make_rendertest_list(models, gset('good.txt'), gset('bad.txt')))
154
155    remove('good.txt')
156    remove('bad.txt')
157
158    with open(ASSETS + '/skqp/unittests.txt', 'w') as o:
159        o.write(check_output([build + '/list_gpu_unit_tests']))
160
161    with open(ASSETS + '/files.checksum', 'w') as o:
162        o.write(upload_model() + '\n')
163
164    sys.stdout.write(ASSETS + '/files.checksum\n')
165    sys.stdout.write(ASSETS + '/skqp/rendertests.txt\n')
166    sys.stdout.write(ASSETS + '/skqp/unittests.txt\n')
167
168if __name__ == '__main__':
169    if len(sys.argv) != 3:
170        sys.stderr.write('Usage:\n  %s C1 C2\n\n' % sys.argv[0])
171        sys.exit(1)
172    main(sys.argv[1], sys.argv[2])
173