diff options
Diffstat (limited to 'mesonbuild/wrap/wrap.py')
-rw-r--r-- | mesonbuild/wrap/wrap.py | 207 |
1 files changed, 161 insertions, 46 deletions
diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 1715cd3..aba220e 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -27,7 +27,9 @@ import sys import configparser import typing as T +from pathlib import Path from . import WrapMode +from .. import coredata from ..mesonlib import git, GIT, ProgressBar, MesonException if T.TYPE_CHECKING: @@ -59,7 +61,10 @@ def quiet_git(cmd: T.List[str], workingdir: str) -> T.Tuple[bool, str]: def verbose_git(cmd: T.List[str], workingdir: str, check: bool = False) -> bool: if not GIT: return False - return git(cmd, workingdir, check=check).returncode == 0 + try: + return git(cmd, workingdir, check=check).returncode == 0 + except subprocess.CalledProcessError: + raise WrapException('Git command failed') def whitelist_wrapdb(urlstr: str) -> urllib.parse.ParseResult: """ raises WrapException if not whitelisted subdomain """ @@ -102,13 +107,31 @@ class WrapNotFoundException(WrapException): class PackageDefinition: def __init__(self, fname: str): self.filename = fname + self.type = None + self.values = {} # type: T.Dict[str, str] + self.provided_deps = {} # type: T.Dict[str, T.Optional[str]] + self.provided_programs = [] # type: T.List[str] self.basename = os.path.basename(fname) - self.name = self.basename[:-5] + self.name = self.basename + if self.name.endswith('.wrap'): + self.name = self.name[:-5] + self.provided_deps[self.name] = None + if fname.endswith('.wrap'): + self.parse_wrap(fname) + self.directory = self.values.get('directory', self.name) + if os.path.dirname(self.directory): + raise WrapException('Directory key must be a name and not a path') + + def parse_wrap(self, fname: str): try: self.config = configparser.ConfigParser(interpolation=None) self.config.read(fname) except configparser.Error: raise WrapException('Failed to parse {}'.format(self.basename)) + self.parse_wrap_section() + self.parse_provide_section() + + def parse_wrap_section(self): if len(self.config.sections()) < 1: raise WrapException('Missing sections in {}'.format(self.basename)) self.wrap_section = self.config.sections()[0] @@ -118,6 +141,27 @@ class PackageDefinition: self.type = self.wrap_section[5:] self.values = dict(self.config[self.wrap_section]) + def parse_provide_section(self): + if self.config.has_section('provide'): + for k, v in self.config['provide'].items(): + if k == 'dependency_names': + # A comma separated list of dependency names that does not + # need a variable name + names = {n.strip(): None for n in v.split(',')} + self.provided_deps.update(names) + continue + if k == 'program_names': + # A comma separated list of program names + names = [n.strip() for n in v.split(',')] + self.provided_programs += names + continue + if not v: + m = ('Empty dependency variable name for {!r} in {}. ' + 'If the subproject uses meson.override_dependency() ' + 'it can be added in the "dependency_names" special key.') + raise WrapException(m.format(k, self.basename)) + self.provided_deps[k] = v + def get(self, key: str) -> str: try: return self.values[key] @@ -125,36 +169,87 @@ class PackageDefinition: m = 'Missing key {!r} in {}' raise WrapException(m.format(key, self.basename)) - def has_patch(self) -> bool: - return 'patch_url' in self.values - -def load_wrap(subdir_root: str, packagename: str) -> PackageDefinition: +def get_directory(subdir_root: str, packagename: str) -> str: fname = os.path.join(subdir_root, packagename + '.wrap') if os.path.isfile(fname): - return PackageDefinition(fname) - return None - -def get_directory(subdir_root: str, packagename: str): - directory = packagename - # We always have to load the wrap file, if it exists, because it could - # override the default directory name. - wrap = load_wrap(subdir_root, packagename) - if wrap and 'directory' in wrap.values: - directory = wrap.get('directory') - if os.path.dirname(directory): - raise WrapException('Directory key must be a name and not a path') - return wrap, directory + wrap = PackageDefinition(fname) + return wrap.directory + return packagename class Resolver: def __init__(self, subdir_root: str, wrap_mode=WrapMode.default): self.wrap_mode = wrap_mode self.subdir_root = subdir_root self.cachedir = os.path.join(self.subdir_root, 'packagecache') - - def resolve(self, packagename: str, method: str) -> str: + self.filesdir = os.path.join(self.subdir_root, 'packagefiles') + self.wraps = {} # type: T.Dict[str, PackageDefinition] + self.provided_deps = {} # type: T.Dict[str, PackageDefinition] + self.provided_programs = {} # type: T.Dict[str, PackageDefinition] + self.load_wraps() + + def load_wraps(self): + if not os.path.isdir(self.subdir_root): + return + root, dirs, files = next(os.walk(self.subdir_root)) + for i in files: + if not i.endswith('.wrap'): + continue + fname = os.path.join(self.subdir_root, i) + wrap = PackageDefinition(fname) + self.wraps[wrap.name] = wrap + if wrap.directory in dirs: + dirs.remove(wrap.directory) + # Add dummy package definition for directories not associated with a wrap file. + for i in dirs: + if i in ['packagecache', 'packagefiles']: + continue + fname = os.path.join(self.subdir_root, i) + wrap = PackageDefinition(fname) + self.wraps[wrap.name] = wrap + + for wrap in self.wraps.values(): + for k in wrap.provided_deps.keys(): + if k in self.provided_deps: + prev_wrap = self.provided_deps[k] + m = 'Multiple wrap files provide {!r} dependency: {} and {}' + raise WrapException(m.format(k, wrap.basename, prev_wrap.basename)) + self.provided_deps[k] = wrap + for k in wrap.provided_programs: + if k in self.provided_programs: + prev_wrap = self.provided_programs[k] + m = 'Multiple wrap files provide {!r} program: {} and {}' + raise WrapException(m.format(k, wrap.basename, prev_wrap.basename)) + self.provided_programs[k] = wrap + + def find_dep_provider(self, packagename: str): + # Return value is in the same format as fallback kwarg: + # ['subproject_name', 'variable_name'], or 'subproject_name'. + wrap = self.provided_deps.get(packagename) + if wrap: + dep_var = wrap.provided_deps.get(packagename) + if dep_var: + return [wrap.name, dep_var] + return wrap.name + return None + + def find_program_provider(self, names: T.List[str]): + for name in names: + wrap = self.provided_programs.get(name) + if wrap: + return wrap.name + return None + + def resolve(self, packagename: str, method: str, current_subproject: str = '') -> str: + self.current_subproject = current_subproject self.packagename = packagename - self.wrap, self.directory = get_directory(self.subdir_root, self.packagename) + self.directory = packagename + self.wrap = self.wraps.get(packagename) + if not self.wrap: + m = 'Subproject directory not found and {}.wrap file not found' + raise WrapNotFoundException(m.format(self.packagename)) + self.directory = self.wrap.directory self.dirname = os.path.join(self.subdir_root, self.directory) + meson_file = os.path.join(self.dirname, 'meson.build') cmake_file = os.path.join(self.dirname, 'CMakeLists.txt') @@ -174,11 +269,6 @@ class Resolver: if not os.path.isdir(self.dirname): raise WrapException('Path already exists but is not a directory') else: - # A wrap file is required to download - if not self.wrap: - m = 'Subproject directory not found and {}.wrap file not found' - raise WrapNotFoundException(m.format(self.packagename)) - if self.wrap.type == 'file': self.get_file() else: @@ -191,6 +281,7 @@ class Resolver: self.get_svn() else: raise WrapException('Unknown wrap type {!r}'.format(self.wrap.type)) + self.apply_patch() # A meson.build or CMakeLists.txt file is required in the directory if method == 'meson' and not os.path.exists(meson_file): @@ -250,8 +341,6 @@ class Resolver: os.mkdir(self.dirname) extract_dir = self.dirname shutil.unpack_archive(path, extract_dir) - if self.wrap.has_patch(): - self.apply_patch() def get_git(self) -> None: if not GIT: @@ -330,7 +419,8 @@ class Resolver: raise WrapException('{} may be a WrapDB-impersonating URL'.format(urlstring)) else: try: - resp = urllib.request.urlopen(urlstring, timeout=REQ_TIMEOUT) + req = urllib.request.Request(urlstring, headers={'User-Agent': 'mesonbuild/{}'.format(coredata.version)}) + resp = urllib.request.urlopen(req, timeout=REQ_TIMEOUT) except urllib.error.URLError as e: mlog.log(str(e)) raise WrapException('could not get {} is the internet available?'.format(urlstring)) @@ -363,7 +453,9 @@ class Resolver: hashvalue = h.hexdigest() return hashvalue, tmpfile.name - def check_hash(self, what: str, path: str) -> None: + def check_hash(self, what: str, path: str, hash_required: bool = True) -> None: + if what + '_hash' not in self.wrap.values and not hash_required: + return expected = self.wrap.get(what + '_hash') h = hashlib.sha256() with open(path, 'rb') as f: @@ -393,26 +485,49 @@ class Resolver: def get_file_internal(self, what: str) -> str: filename = self.wrap.get(what + '_filename') - cache_path = os.path.join(self.cachedir, filename) + if what + '_url' in self.wrap.values: + cache_path = os.path.join(self.cachedir, filename) + + if os.path.exists(cache_path): + self.check_hash(what, cache_path) + mlog.log('Using', mlog.bold(self.packagename), what, 'from cache.') + return cache_path - if os.path.exists(cache_path): - self.check_hash(what, cache_path) - mlog.log('Using', mlog.bold(self.packagename), what, 'from cache.') + if not os.path.isdir(self.cachedir): + os.mkdir(self.cachedir) + self.download(what, cache_path) return cache_path + else: + from ..interpreterbase import FeatureNew + FeatureNew('Local wrap patch files without {}_url'.format(what), '0.55.0').use(self.current_subproject) + path = Path(self.filesdir) / filename + + if not path.exists(): + raise WrapException('File "{}" does not exist'.format(path)) + self.check_hash(what, path.as_posix(), hash_required=False) - if not os.path.isdir(self.cachedir): - os.mkdir(self.cachedir) - self.download(what, cache_path) - return cache_path + return path.as_posix() def apply_patch(self) -> None: - path = self.get_file_internal('patch') - try: - shutil.unpack_archive(path, self.subdir_root) - except Exception: - with tempfile.TemporaryDirectory() as workdir: - shutil.unpack_archive(path, workdir) - self.copy_tree(workdir, self.subdir_root) + if 'patch_filename' in self.wrap.values and 'patch_directory' in self.wrap.values: + m = 'Wrap file {!r} must not have both "patch_filename" and "patch_directory"' + raise WrapException(m.format(self.wrap.basename)) + if 'patch_filename' in self.wrap.values: + path = self.get_file_internal('patch') + try: + shutil.unpack_archive(path, self.subdir_root) + except Exception: + with tempfile.TemporaryDirectory() as workdir: + shutil.unpack_archive(path, workdir) + self.copy_tree(workdir, self.subdir_root) + elif 'patch_directory' in self.wrap.values: + from ..interpreterbase import FeatureNew + FeatureNew('patch_directory', '0.55.0').use(self.current_subproject) + patch_dir = self.wrap.values['patch_directory'] + src_dir = os.path.join(self.filesdir, patch_dir) + if not os.path.isdir(src_dir): + raise WrapException('patch directory does not exists: {}'.format(patch_dir)) + self.copy_tree(src_dir, self.dirname) def copy_tree(self, root_src_dir: str, root_dst_dir: str) -> None: """ |