1#!/usr/bin/env python3
2
3# Copyright 2016 The Meson development team
4
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8
9#     http://www.apache.org/licenses/LICENSE-2.0
10
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17# ghwt - GitHub WrapTool
18#
19# An emergency wraptool(1) replacement downloader that downloads
20# directly from GitHub in case wrapdb.mesonbuild.com is down.
21
22import urllib.request, json, sys, os, shutil, subprocess
23import configparser, hashlib
24
25req_timeout = 600.0
26private_repos = {'meson', 'wrapweb', 'meson-ci'}
27spdir = 'subprojects'
28
29def gh_get(url):
30    r = urllib.request.urlopen(url, timeout=req_timeout)
31    jd = json.loads(r.read().decode('utf-8'))
32    return jd
33
34def list_projects():
35    jd = gh_get('https://api.github.com/orgs/mesonbuild/repos')
36    entries = [entry['name'] for entry in jd]
37    entries = [e for e in entries if e not in private_repos]
38    entries.sort()
39    for i in entries:
40        print(i)
41    return 0
42
43def unpack(sproj, branch):
44    tmpdir = os.path.join(spdir, sproj + '_ghwt')
45    shutil.rmtree(tmpdir, ignore_errors=True)
46    subprocess.check_call(['git', 'clone', '-b', branch, f'https://github.com/mesonbuild/{sproj}.git', tmpdir])
47    usfile = os.path.join(tmpdir, 'upstream.wrap')
48    assert(os.path.isfile(usfile))
49    config = configparser.ConfigParser(interpolation=None)
50    config.read(usfile)
51    outdir = os.path.join(spdir, sproj)
52    if 'directory' in config['wrap-file']:
53        outdir = os.path.join(spdir, config['wrap-file']['directory'])
54    if os.path.isdir(outdir):
55        print(f'Subproject is already there. To update, nuke the {outdir} dir and reinstall.')
56        shutil.rmtree(tmpdir)
57        return 1
58    us_url = config['wrap-file']['source_url']
59    us = urllib.request.urlopen(us_url, timeout=req_timeout).read()
60    h = hashlib.sha256()
61    h.update(us)
62    dig = h.hexdigest()
63    should = config['wrap-file']['source_hash']
64    if dig != should:
65        print('Incorrect hash on download.')
66        print(' expected:', should)
67        print(' obtained:', dig)
68        return 1
69    ofilename = os.path.join(spdir, config['wrap-file']['source_filename'])
70    with open(ofilename, 'wb') as ofile:
71        ofile.write(us)
72    if 'lead_directory_missing' in config['wrap-file']:
73        os.mkdir(outdir)
74        shutil.unpack_archive(ofilename, outdir)
75    else:
76        shutil.unpack_archive(ofilename, spdir)
77        assert(os.path.isdir(outdir))
78    shutil.move(os.path.join(tmpdir, '.git'), outdir)
79    subprocess.check_call(['git', 'reset', '--hard'], cwd=outdir)
80    shutil.rmtree(tmpdir)
81    shutil.rmtree(os.path.join(outdir, '.git'))
82    os.unlink(ofilename)
83
84def install(sproj, requested_branch=None):
85    if not os.path.isdir(spdir):
86        print('Run this in your source root and make sure there is a subprojects directory in it.')
87        return 1
88    blist = gh_get(f'https://api.github.com/repos/mesonbuild/{sproj}/branches')
89    blist = [b['name'] for b in blist]
90    blist = [b for b in blist if b != 'master']
91    blist.sort()
92    branch = blist[-1]
93    if requested_branch is not None:
94        if requested_branch in blist:
95            branch = requested_branch
96        else:
97            print('Could not find user-requested branch', requested_branch)
98            print('Available branches for', sproj, ':')
99            print(blist)
100            return 1
101    print('Using branch', branch)
102    return unpack(sproj, branch)
103
104def print_help():
105    print('Usage:')
106    print(sys.argv[0], 'list')
107    print(sys.argv[0], 'install', 'package_name', '[branch_name]')
108
109def run(args):
110    if not args or args[0] == '-h' or args[0] == '--help':
111        print_help()
112        return 1
113    command = args[0]
114    args = args[1:]
115    if command == 'list':
116        list_projects()
117        return 0
118    elif command == 'install':
119        if len(args) == 1:
120            return install(args[0])
121        elif len(args) == 2:
122            return install(args[0], args[1])
123        else:
124            print_help()
125            return 1
126    else:
127        print('Unknown command')
128        return 1
129
130if __name__ == '__main__':
131    print('This is an emergency wrap downloader. Use only when wrapdb is down.')
132    sys.exit(run(sys.argv[1:]))
133