diff options
Diffstat (limited to 'mesonbuild/wrap')
-rw-r--r-- | mesonbuild/wrap/wrap.py | 212 | ||||
-rwxr-xr-x | mesonbuild/wrap/wraptool.py | 200 |
2 files changed, 412 insertions, 0 deletions
diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py new file mode 100644 index 0000000..2818fa0 --- /dev/null +++ b/mesonbuild/wrap/wrap.py @@ -0,0 +1,212 @@ +# Copyright 2015 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .. import mlog +import urllib.request, os, hashlib, shutil +import subprocess +import sys + +try: + import ssl + has_ssl = True + API_ROOT = 'https://wrapdb.mesonbuild.com/v1/' +except ImportError: + has_ssl = False + API_ROOT = 'http://wrapdb.mesonbuild.com/v1/' + +def build_ssl_context(): + ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ctx.options |= ssl.OP_NO_SSLv2 + ctx.options |= ssl.OP_NO_SSLv3 + ctx.verify_mode = ssl.CERT_REQUIRED + ctx.load_default_certs() + return ctx + +def open_wrapdburl(urlstring): + global ssl_warning_printed + if has_ssl: + try: + return urllib.request.urlopen(urlstring)#, context=build_ssl_context()) + except urllib.error.URLError: + if not ssl_warning_printed: + print('SSL connection failed. Falling back to unencrypted connections.') + ssl_warning_printed = True + if not ssl_warning_printed: + print('Warning: SSL not available, traffic not authenticated.', + file=sys.stderr) + ssl_warning_printed = True + # Trying to open SSL connection to wrapdb fails because the + # certificate is not known. + if urlstring.startswith('https'): + urlstring = 'http' + urlstring[5:] + return urllib.request.urlopen(urlstring) + + +class PackageDefinition: + def __init__(self, fname): + self.values = {} + ifile = open(fname) + first = ifile.readline().strip() + + if first == '[wrap-file]': + self.type = 'file' + elif first == '[wrap-git]': + self.type = 'git' + else: + raise RuntimeError('Invalid format of package file') + for line in ifile: + line = line.strip() + if line == '': + continue + (k, v) = line.split('=', 1) + k = k.strip() + v = v.strip() + self.values[k] = v + + def get(self, key): + return self.values[key] + + def has_patch(self): + return 'patch_url' in self.values + +class Resolver: + def __init__(self, subdir_root): + self.subdir_root = subdir_root + self.cachedir = os.path.join(self.subdir_root, 'packagecache') + + def resolve(self, packagename): + fname = os.path.join(self.subdir_root, packagename + '.wrap') + dirname = os.path.join(self.subdir_root, packagename) + if not os.path.isfile(fname): + if os.path.isdir(dirname): + # No wrap file but dir exists -> user put it there manually. + return packagename + return None + p = PackageDefinition(fname) + if p.type == 'file': + if not os.path.isdir(self.cachedir): + os.mkdir(self.cachedir) + self.download(p, packagename) + self.extract_package(p) + elif p.type == 'git': + self.get_git(p) + else: + raise RuntimeError('Unreachable code.') + return p.get('directory') + + def get_git(self, p): + checkoutdir = os.path.join(self.subdir_root, p.get('directory')) + revno = p.get('revision') + is_there = os.path.isdir(checkoutdir) + if is_there: + if revno.lower() == 'head': + subprocess.check_call(['git', 'pull'], cwd=checkoutdir) + else: + if subprocess.call(['git', 'checkout', revno], cwd=checkoutdir) != 0: + subprocess.check_call(['git', 'fetch'], cwd=checkoutdir) + subprocess.check_call(['git', 'checkout', revno], + cwd=checkoutdir) + else: + subprocess.check_call(['git', 'clone', p.get('url'), + p.get('directory')], cwd=self.subdir_root) + if revno.lower() != 'head': + subprocess.check_call(['git', 'checkout', revno], + cwd=checkoutdir) + + + def get_data(self, url): + blocksize = 10*1024 + if url.startswith('https://wrapdb.mesonbuild.com'): + resp = open_wrapdburl(url) + else: + resp = urllib.request.urlopen(url) + dlsize = int(resp.info()['Content-Length']) + print('Download size:', dlsize) + print('Downloading: ', end='') + sys.stdout.flush() + printed_dots = 0 + blocks = [] + downloaded = 0 + while True: + block = resp.read(blocksize) + if block == b'': + break + downloaded += len(block) + blocks.append(block) + ratio = int(downloaded/dlsize * 10) + while printed_dots < ratio: + print('.', end='') + sys.stdout.flush() + printed_dots += 1 + print('') + resp.close() + return b''.join(blocks) + + def get_hash(self, data): + h = hashlib.sha256() + h.update(data) + hashvalue = h.hexdigest() + return hashvalue + + def download(self, p, packagename): + ofname = os.path.join(self.cachedir, p.get('source_filename')) + if os.path.exists(ofname): + mlog.log('Using', mlog.bold(packagename), 'from cache.') + return + srcurl = p.get('source_url') + mlog.log('Dowloading', mlog.bold(packagename), 'from', mlog.bold(srcurl)) + srcdata = self.get_data(srcurl) + dhash = self.get_hash(srcdata) + expected = p.get('source_hash') + if dhash != expected: + raise RuntimeError('Incorrect hash for source %s:\n %s expected\n %s actual.' % (packagename, expected, dhash)) + open(ofname, 'wb').write(srcdata) + if p.has_patch(): + purl = p.get('patch_url') + mlog.log('Downloading patch from', mlog.bold(purl)) + pdata = self.get_data(purl) + phash = self.get_hash(pdata) + expected = p.get('patch_hash') + if phash != expected: + raise RuntimeError('Incorrect hash for patch %s:\n %s expected\n %s actual' % (packagename, expected, phash)) + open(os.path.join(self.cachedir, p.get('patch_filename')), 'wb').write(pdata) + else: + mlog.log('Package does not require patch.') + + def extract_package(self, package): + if sys.version_info < (3, 5): + try: + import lzma + del lzma + try: + shutil.register_unpack_format('xztar', ['.tar.xz', '.txz'], shutil._unpack_tarfile, [], "xz'ed tar-file") + except shutil.RegistryError: + pass + except ImportError: + pass + target_dir = os.path.join(self.subdir_root, package.get('directory')) + if os.path.isdir(target_dir): + return + extract_dir = self.subdir_root + # Some upstreams ship packages that do not have a leading directory. + # Create one for them. + try: + package.get('lead_directory_missing') + os.mkdir(target_dir) + extract_dir = target_dir + except KeyError: + pass + shutil.unpack_archive(os.path.join(self.cachedir, package.get('source_filename')), extract_dir) + if package.has_patch(): + shutil.unpack_archive(os.path.join(self.cachedir, package.get('patch_filename')), self.subdir_root) diff --git a/mesonbuild/wrap/wraptool.py b/mesonbuild/wrap/wraptool.py new file mode 100755 index 0000000..d2f0a28 --- /dev/null +++ b/mesonbuild/wrap/wraptool.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 + +# Copyright 2015-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import sys, os +import configparser +import shutil + +from glob import glob + +from .wrap import API_ROOT, open_wrapdburl + +help_templ = '''This program allows you to manage your Wrap dependencies +using the online wrap database http://wrapdb.mesonbuild.com. + +Run this command in your top level source directory. + +Usage: + +%s <command> [options] + +Commands: + + list - show all available projects + search - search the db by name + install - install the specified project + update - update the project to its newest available release + info - show available versions of a project + status - show installed and available versions of your projects + +''' + + +def print_help(): + print(help_templ % sys.argv[0]) + +def get_result(urlstring): + u = open_wrapdburl(urlstring) + data = u.read().decode('utf-8') + jd = json.loads(data) + if jd['output'] != 'ok': + print('Got bad output from server.') + print(data) + sys.exit(1) + return jd + +def get_projectlist(): + jd = get_result(API_ROOT + 'projects') + projects = jd['projects'] + return projects + +def list_projects(): + projects = get_projectlist() + for p in projects: + print(p) + +def search(name): + jd = get_result(API_ROOT + 'query/byname/' + name) + for p in jd['projects']: + print(p) + +def get_latest_version(name): + jd = get_result(API_ROOT + 'query/get_latest/' + name) + branch = jd['branch'] + revision = jd['revision'] + return (branch, revision) + +def install(name): + if not os.path.isdir('subprojects'): + print('Subprojects dir not found. Run this script in your source root directory.') + sys.exit(1) + if os.path.isdir(os.path.join('subprojects', name)): + print('Subproject directory for this project already exists.') + sys.exit(1) + wrapfile = os.path.join('subprojects', name + '.wrap') + if os.path.exists(wrapfile): + print('Wrap file already exists.') + sys.exit(1) + (branch, revision) = get_latest_version(name) + u = open_wrapdburl(API_ROOT + 'projects/%s/%s/%s/get_wrap' % (name, branch, revision)) + data = u.read() + open(wrapfile, 'wb').write(data) + print('Installed', name, 'branch', branch, 'revision', revision) + +def get_current_version(wrapfile): + cp = configparser.ConfigParser() + cp.read(wrapfile) + cp = cp['wrap-file'] + patch_url = cp['patch_url'] + arr = patch_url.split('/') + branch = arr[-3] + revision = int(arr[-2]) + return (branch, revision, cp['directory'], cp['source_filename'], cp['patch_filename']) + +def update(name): + if not os.path.isdir('subprojects'): + print('Subprojects dir not found. Run this command in your source root directory.') + sys.exit(1) + wrapfile = os.path.join('subprojects', name + '.wrap') + if not os.path.exists(wrapfile): + print('Project', name, 'is not in use.') + sys.exit(1) + (branch, revision, subdir, src_file, patch_file) = get_current_version(wrapfile) + (new_branch, new_revision) = get_latest_version(name) + if new_branch == branch and new_revision == revision: + print('Project', name, 'is already up to date.') + sys.exit(0) + u = open_wrapdburl(API_ROOT + 'projects/%s/%s/%d/get_wrap' % (name, new_branch, new_revision)) + data = u.read() + shutil.rmtree(os.path.join('subprojects', subdir), ignore_errors=True) + try: + os.unlink(os.path.join('subprojects/packagecache', src_file)) + except FileNotFoundError: + pass + try: + os.unlink(os.path.join('subprojects/packagecache', patch_file)) + except FileNotFoundError: + pass + open(wrapfile, 'wb').write(data) + print('Updated', name, 'to branch', new_branch, 'revision', new_revision) + +def info(name): + jd = get_result(API_ROOT + 'projects/' + name) + versions = jd['versions'] + if len(versions) == 0: + print('No available versions of', name) + sys.exit(0) + print('Available versions of %s:' % name) + for v in versions: + print(' ', v['branch'], v['revision']) + +def status(): + print('Subproject status') + for w in glob('subprojects/*.wrap'): + name = os.path.split(w)[1][:-5] + try: + (latest_branch, latest_revision) = get_latest_version(name) + except Exception: + print('', name, 'not available in wrapdb.') + continue + try: + (current_branch, current_revision, _, _, _) = get_current_version(w) + except Exception: + print('Wrap file not from wrapdb.') + continue + if current_branch == latest_branch and current_revision == latest_revision: + print('', name, 'up to date. Branch %s, revision %d.' % (current_branch, current_revision)) + else: + print('', name, 'not up to date. Have %s %d, but %s %d is available.' % (current_branch, current_revision, latest_branch, latest_revision)) + +def run(args): + if len(sys.argv) < 1 or sys.argv[0] == '-h' or sys.argv[1] == '--help': + print_help() + return 0 + command = args[0] + args = args[1:] + if command == 'list': + list_projects() + elif command == 'search': + if len(args) != 1: + print('Search requires exactly one argument.') + return 1 + search(args[0]) + elif command == 'install': + if len(args) != 1: + print('Install requires exactly one argument.') + return 1 + install(args[0]) + elif command == 'update': + if len(args) != 1: + print('update requires exactly one argument.') + return 1 + update(args[0]) + elif command == 'info': + if len(args) != 1: + print('info requires exactly one argument.') + return 1 + info(args[0]) + elif command == 'status': + status() + else: + print('Unknown command', command) + return 1 + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) |