diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 9b3fdc7df..98424ec60 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2261,19 +2261,21 @@ external dependencies (including libraries) must go to "dependencies".''') r = wrap.Resolver(subproject_dir_abs, self.coredata.get_builtin_option('wrap_mode')) try: resolved = r.resolve(dirname) - except RuntimeError as e: - # if the reason subproject execution failed was because - # the directory doesn't exist, try to give some helpful - # advice if it's a nested subproject that needs - # promotion... - self.print_nested_info(dirname) - - if required: - msg = 'Subproject directory {!r} does not exist and cannot be downloaded:\n{}' - raise InterpreterException(msg.format(os.path.join(self.subproject_dir, dirname), e)) - - mlog.log('\nSubproject ', mlog.bold(dirname), 'is buildable:', mlog.red('NO'), '(disabling)\n') - return self.disabled_subproject(dirname) + except wrap.WrapException as e: + subprojdir = os.path.join(self.subproject_dir, r.directory) + if not required: + mlog.log('\nSubproject ', mlog.bold(subprojdir), 'is buildable:', mlog.red('NO'), '(disabling)\n') + return self.disabled_subproject(dirname) + + if isinstance(e, wrap.WrapNotFoundException): + # if the reason subproject execution failed was because + # the directory doesn't exist, try to give some helpful + # advice if it's a nested subproject that needs + # promotion... + self.print_nested_info(dirname) + + msg = 'Failed to initialize {!r}:\n{}' + raise InterpreterException(msg.format(subprojdir, e)) subdir = os.path.join(self.subproject_dir, resolved) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) @@ -2979,26 +2981,21 @@ external dependencies (including libraries) must go to "dependencies".''') return Disabler() def print_nested_info(self, dependency_name): - message_templ = '''\nDependency %s not found but it is available in a sub-subproject. -To use it in the current project, promote it by going in the project source -root and issuing %s. - -''' + message = ['Dependency', mlog.bold(dependency_name), 'not found but it is available in a sub-subproject.\n' + + 'To use it in the current project, promote it by going in the project source\n' + 'root and issuing'] sprojs = mesonlib.detect_subprojects('subprojects', self.source_root) if dependency_name not in sprojs: return found = sprojs[dependency_name] if len(found) > 1: - suffix = 'one of the following commands' + message.append('one of the following commands:') else: - suffix = 'the following command' - message = message_templ % (dependency_name, suffix) - cmds = [] - command_templ = 'meson wrap promote ' + message.append('the following command:') + command_templ = '\nmeson wrap promote {}' for l in found: - cmds.append(command_templ + l[len(self.source_root) + 1:]) - final_message = message + '\n'.join(cmds) - print(final_message) + message.append(mlog.bold(command_templ.format(l[len(self.source_root) + 1:]))) + mlog.warning(*message) def get_subproject_infos(self, kwargs): fbinfo = kwargs['fallback'] diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 017142944..67da23c6d 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -18,8 +18,8 @@ import urllib.request, os, hashlib, shutil, tempfile, stat import subprocess import sys import configparser -from pathlib import Path from . import WrapMode +from ..mesonlib import MesonException try: import ssl @@ -67,19 +67,35 @@ def open_wrapdburl(urlstring): urlstring = 'http' + urlstring[5:] return urllib.request.urlopen(urlstring, timeout=req_timeout) +class WrapException(MesonException): + pass + +class WrapNotFoundException(WrapException): + pass class PackageDefinition: def __init__(self, fname): - self.config = configparser.ConfigParser() - self.config.read(fname) + self.basename = os.path.basename(fname) + try: + self.config = configparser.ConfigParser() + self.config.read(fname) + except: + raise WrapException('Failed to parse {}'.format(self.basename)) + if len(self.config.sections()) < 1: + raise WrapException('Missing sections in {}'.format(self.basename)) self.wrap_section = self.config.sections()[0] if not self.wrap_section.startswith('wrap-'): - raise RuntimeError('Invalid format of package file') + m = '{!r} is not a valid first section in {}' + raise WrapException(m.format(self.wrap_section, self.basename)) self.type = self.wrap_section[5:] self.values = dict(self.config[self.wrap_section]) def get(self, key): - return self.values[key] + try: + return self.values[key] + except KeyError: + m = 'Missing key {!r} in {}' + raise WrapException(m.format(key, self.basename)) def has_patch(self): return 'patch_url' in self.values @@ -92,32 +108,30 @@ class Resolver: def resolve(self, packagename): self.packagename = packagename + self.directory = packagename # We always have to load the wrap file, if it exists, because it could # override the default directory name. p = self.load_wrap() - directory = packagename if p and 'directory' in p.values: - directory = p.get('directory') - dirname = os.path.join(self.subdir_root, directory) - subprojdir = os.path.join(*Path(dirname).parts[-2:]) + self.directory = p.get('directory') + dirname = os.path.join(self.subdir_root, self.directory) meson_file = os.path.join(dirname, 'meson.build') # The directory is there and has meson.build? Great, use it. if os.path.exists(meson_file): - return directory + return self.directory # Check if the subproject is a git submodule self.resolve_git_submodule(dirname) if os.path.exists(dirname): if not os.path.isdir(dirname): - m = '{!r} already exists and is not a dir; cannot use as subproject' - raise RuntimeError(m.format(subprojdir)) + raise WrapException('Path already exists but is not a directory') else: # A wrap file is required to download if not p: - m = 'No {}.wrap found for {!r}' - raise RuntimeError(m.format(packagename, subprojdir)) + m = 'Subproject directory not found and {}.wrap file not found' + raise WrapNotFoundException(m.format(self.packagename)) if p.type == 'file': self.get_file(p) @@ -130,14 +144,13 @@ class Resolver: elif p.type == "svn": self.get_svn(p) else: - raise AssertionError('Unreachable code.') + raise WrapException('Unknown wrap type {!r}'.format(p.type)) # A meson.build file is required in the directory if not os.path.exists(meson_file): - m = '{!r} is not empty and has no meson.build files' - raise RuntimeError(m.format(subprojdir)) + raise WrapException('Subproject exists but has no meson.build file') - return directory + return self.directory def load_wrap(self): fname = os.path.join(self.subdir_root, self.packagename + '.wrap') @@ -150,7 +163,7 @@ class Resolver: # Git submodules are ok (see above)! if self.wrap_mode is WrapMode.nodownload: m = 'Automatic wrap-based subproject downloading is disabled' - raise RuntimeError(m) + raise WrapException(m) def resolve_git_submodule(self, dirname): # Are we in a git repository? @@ -163,15 +176,15 @@ class Resolver: return False # Submodule has not been added, add it if out.startswith(b'+'): - mlog.warning('git submodule {} might be out of date'.format(dirname)) + mlog.warning('git submodule might be out of date') return True elif out.startswith(b'U'): - raise RuntimeError('submodule {} has merge conflicts'.format(dirname)) + raise WrapException('git submodule has merge conflicts') # Submodule exists, but is deinitialized or wasn't initialized elif out.startswith(b'-'): if subprocess.call(['git', '-C', self.subdir_root, 'submodule', 'update', '--init', dirname]) == 0: return True - raise RuntimeError('Failed to git submodule init {!r}'.format(dirname)) + raise WrapException('git submodule failed to init') # Submodule looks fine, but maybe it wasn't populated properly. Do a checkout. elif out.startswith(b' '): subprocess.call(['git', 'checkout', '.'], cwd=dirname) @@ -182,7 +195,7 @@ class Resolver: # It is not a submodule, just a folder that exists in the main repository. return False m = 'Unknown git submodule output: {!r}' - raise RuntimeError(m.format(out)) + raise WrapException(m.format(out)) def get_file(self, p): path = self.get_file_internal(p, 'source') @@ -190,12 +203,9 @@ class Resolver: extract_dir = self.subdir_root # Some upstreams ship packages that do not have a leading directory. # Create one for them. - try: - p.get('lead_directory_missing') + if 'lead_directory_missing' in p.values: os.mkdir(target_dir) extract_dir = target_dir - except KeyError: - pass shutil.unpack_archive(path, extract_dir) if p.has_patch(): self.apply_patch(p) @@ -285,7 +295,7 @@ class Resolver: h.update(f.read()) dhash = h.hexdigest() if dhash != expected: - raise RuntimeError('Incorrect hash for %s:\n %s expected\n %s actual.' % (what, expected, dhash)) + raise WrapException('Incorrect hash for %s:\n %s expected\n %s actual.' % (what, expected, dhash)) def download(self, p, what, ofname): self.check_can_download() @@ -295,7 +305,7 @@ class Resolver: expected = p.get(what + '_hash') if dhash != expected: os.remove(tmpfile) - raise RuntimeError('Incorrect hash for %s:\n %s expected\n %s actual.' % (what, expected, dhash)) + raise WrapException('Incorrect hash for %s:\n %s expected\n %s actual.' % (what, expected, dhash)) os.rename(tmpfile, ofname) def get_file_internal(self, p, what):