aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/wrap/wrap.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/wrap/wrap.py')
-rw-r--r--mesonbuild/wrap/wrap.py207
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:
"""