files: # Copyright 2012-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.
  
  """A library of random helper functionality."""
  from pathlib import Path
  import sys
  import stat
  import time
  import platform, subprocess, operator, os, shlex, shutil, re
  import collections
  from enum import Enum
  from functools import lru_cache, update_wrapper
  from itertools import tee, filterfalse
  import typing as T
  import uuid
  
  from mesonbuild import mlog
  
  _T = T.TypeVar('_T')
  _U = T.TypeVar('_U')
  
  have_fcntl = False
  have_msvcrt = False
  # {subproject: project_meson_version}
  project_meson_versions = {}
  
  try:
      import fcntl
      have_fcntl = True
  except Exception:
      pass
  
  try:
      import msvcrt
      have_msvcrt = True
  except Exception:
      pass
  
  from glob import glob
  
  if os.path.basename(sys.executable) == 'meson.exe':
      # In Windows and using the MSI installed executable.
      python_command = [sys.executable, 'runpython']
  else:
      python_command = [sys.executable]
  meson_command = None
  
  GIT = shutil.which('git')
  def git(cmd: T.List[str], workingdir: str, **kwargs) -> subprocess.CompletedProcess:
      pc = subprocess.run([GIT, '-C', workingdir] + cmd,
                          # Redirect stdin to DEVNULL otherwise git messes up the
                          # console and ANSI colors stop working on Windows.
                          stdin=subprocess.DEVNULL, **kwargs)
      # Sometimes git calls git recursively, such as `git submodule update
      # --recursive` which will be without the above workaround, so set the
      # console mode again just in case.
      if platform.system().lower() == 'windows':
          mlog._windows_ansi()
      return pc
  
  
  def set_meson_command(mainfile):
      global python_command
      global meson_command
      # On UNIX-like systems `meson` is a Python script
      # On Windows `meson` and `meson.exe` are wrapper exes
      if not mainfile.endswith('.py'):
          meson_command = [mainfile]
      elif os.path.isabs(mainfile) and mainfile.endswith('mesonmain.py'):
          # Can't actually run meson with an absolute path to mesonmain.py, it must be run as -m mesonbuild.mesonmain
          meson_command = python_command + ['-m', 'mesonbuild.mesonmain']
      else:
          # Either run uninstalled, or full path to meson-script.py
          meson_command = python_command + [mainfile]
      # We print this value for unit tests.
      if 'MESON_COMMAND_TESTS' in os.environ:
          mlog.log('meson_command is {!r}'.format(meson_command))
  
  def is_ascii_string(astring) -> bool:
      try:
          if isinstance(astring, str):
              astring.encode('ascii')
          elif isinstance(astring, bytes):
              astring.decode('ascii')
      except UnicodeDecodeError:
          return False
      return True
  
  def check_direntry_issues(direntry_array):
      import locale
      # Warn if the locale is not UTF-8. This can cause various unfixable issues
      # such as os.stat not being able to decode filenames with unicode in them.
      # There is no way to reset both the preferred encoding and the filesystem
      # encoding, so we can just warn about it.
      e = locale.getpreferredencoding()
      if e.upper() != 'UTF-8' and not is_windows():
          if not isinstance(direntry_array, list):
              direntry_array = [direntry_array]
          for de in direntry_array:
              if is_ascii_string(de):
                  continue
              mlog.warning('''You are using {!r} which is not a Unicode-compatible '
  locale but you are trying to access a file system entry called {!r} which is
  not pure ASCII. This may cause problems.
  '''.format(e, de), file=sys.stderr)
  
  # Put this in objects that should not get dumped to pickle files
  # by accident.
  import threading
  an_unpicklable_object = threading.Lock()
  
  class MesonException(Exception):
      '''Exceptions thrown by Meson'''
  
      def get_msg_with_context(self):
          s = ''
          if hasattr(self, 'lineno') and hasattr(self, 'file'):
              s = get_error_location_string(self.file, self.lineno) + ' '
          s += str(self)
          return s
  
  class EnvironmentException(MesonException):
      '''Exceptions thrown while processing and creating the build environment'''
  
  class FileMode:
      # The first triad is for owner permissions, the second for group permissions,
      # and the third for others (everyone else).
      # For the 1st character:
      #  'r' means can read
      #  '-' means not allowed
      # For the 2nd character:
      #  'w' means can write
      #  '-' means not allowed
      # For the 3rd character:
      #  'x' means can execute
      #  's' means can execute and setuid/setgid is set (owner/group triads only)
      #  'S' means cannot execute and setuid/setgid is set (owner/group triads only)
      #  't' means can
  
  [... the rest is omitted ...]
files: mesonbuild/mesonlib.py
files: # Copyright 2012-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.
  
  """A library of random helper functionality."""
  from pathlib import Path
  import sys
  import stat
  import time
  import platform, subprocess, operator, os, shlex, shutil, re
  import collections
  from enum import Enum
  from functools import lru_cache, update_wrapper
  from itertools import tee, filterfalse
  import typing as T
  import uuid
  
  from mesonbuild import mlog
  
  _T = T.TypeVar('_T')
  _U = T.TypeVar('_U')
  
  have_fcntl = False
  have_msvcrt = False
  # {subproject: project_meson_version}
  project_meson_versions = {}
  
  try:
      import fcntl
      have_fcntl = True
  except Exception:
      pass
  
  try:
      import msvcrt
      have_msvcrt = True
  except Exception:
      pass
  
  from glob import glob
  
  if os.path.basename(sys.executable) == 'meson.exe':
      # In Windows and using the MSI installed executable.
      python_command = [sys.executable, 'runpython']
  else:
      python_command = [sys.executable]
  meson_command = None
  
  def set_meson_command(mainfile):
      global python_command
      global meson_command
      # On UNIX-like systems `meson` is a Python script
      # On Windows `meson` and `meson.exe` are wrapper exes
      if not mainfile.endswith('.py'):
          meson_command = [mainfile]
      elif os.path.isabs(mainfile) and mainfile.endswith('mesonmain.py'):
          # Can't actually run meson with an absolute path to mesonmain.py, it must be run as -m mesonbuild.mesonmain
          meson_command = python_command + ['-m', 'mesonbuild.mesonmain']
      else:
          # Either run uninstalled, or full path to meson-script.py
          meson_command = python_command + [mainfile]
      # We print this value for unit tests.
      if 'MESON_COMMAND_TESTS' in os.environ:
          mlog.log('meson_command is {!r}'.format(meson_command))
  
  def is_ascii_string(astring) -> bool:
      try:
          if isinstance(astring, str):
              astring.encode('ascii')
          elif isinstance(astring, bytes):
              astring.decode('ascii')
      except UnicodeDecodeError:
          return False
      return True
  
  def check_direntry_issues(direntry_array):
      import locale
      # Warn if the locale is not UTF-8. This can cause various unfixable issues
      # such as os.stat not being able to decode filenames with unicode in them.
      # There is no way to reset both the preferred encoding and the filesystem
      # encoding, so we can just warn about it.
      e = locale.getpreferredencoding()
      if e.upper() != 'UTF-8' and not is_windows():
          if not isinstance(direntry_array, list):
              direntry_array = [direntry_array]
          for de in direntry_array:
              if is_ascii_string(de):
                  continue
              mlog.warning('''You are using {!r} which is not a Unicode-compatible '
  locale but you are trying to access a file system entry called {!r} which is
  not pure ASCII. This may cause problems.
  '''.format(e, de), file=sys.stderr)
  
  # Put this in objects that should not get dumped to pickle files
  # by accident.
  import threading
  an_unpicklable_object = threading.Lock()
  
  class MesonException(Exception):
      '''Exceptions thrown by Meson'''
  
      def get_msg_with_context(self):
          s = ''
          if hasattr(self, 'lineno') and hasattr(self, 'file'):
              s = get_error_location_string(self.file, self.lineno) + ' '
          s += str(self)
          return s
  
  class EnvironmentException(MesonException):
      '''Exceptions thrown while processing and creating the build environment'''
  
  class FileMode:
      # The first triad is for owner permissions, the second for group permissions,
      # and the third for others (everyone else).
      # For the 1st character:
      #  'r' means can read
      #  '-' means not allowed
      # For the 2nd character:
      #  'w' means can write
      #  '-' means not allowed
      # For the 3rd character:
      #  'x' means can execute
      #  's' means can execute and setuid/setgid is set (owner/group triads only)
      #  'S' means cannot execute and setuid/setgid is set (owner/group triads only)
      #  't' means can execute and sticky bit is set ("others" triads only)
      #  'T' means cannot execute and sticky bit is set ("others" triads only)
      #  '-' means none of these are allowed
      #
      # The meanings of 'rwx' perms is not obvious for directories; see:
      # https://www.hackinglinuxexposed.com/articles/20030424.html
      #
      # For information on this notation such as setuid/setgid/sticky bits, see:
      # https://en.wikipedia.org/wiki/File_system_permissions#Symbolic_notation
      symbolic_perms_regex = re.compile('[r-][w-][xsS-]' # Owner perms
                                        '[r-][
  
  [... the rest is omitted ...]
files: mesonbuild/mesonlib.py
files: import os, subprocess
  import argparse
  
  from . import mlog
  from .mesonlib import git, Popen_safe
  from .wrap.wrap import API_ROOT, PackageDefinition, Resolver, WrapException
  from .wrap import wraptool
  
  def update_wrapdb_file(wrap, repo_dir, options):
      patch_url = wrap.get('patch_url')
      branch, revision = wraptool.parse_patch_url(patch_url)
      new_branch, new_revision = wraptool.get_latest_version(wrap.name)
      if new_branch == branch and new_revision == revision:
          mlog.log('  -> Up to date.')
          return
      wraptool.update_wrap_file(wrap.filename, wrap.name, new_branch, new_revision)
      msg = ['  -> New wrap file downloaded.']
      # Meson reconfigure won't use the new wrap file as long as the source
      # directory exists. We don't delete it ourself to avoid data loss in case
      # user has changes in their copy.
      if os.path.isdir(repo_dir):
          msg += ['To use it, delete', mlog.bold(repo_dir), 'and run', mlog.bold('meson --reconfigure')]
      mlog.log(*msg)
  
  def update_file(wrap, repo_dir, options):
      patch_url = wrap.values.get('patch_url', '')
      if patch_url.startswith(API_ROOT):
          update_wrapdb_file(wrap, repo_dir, options)
      elif not os.path.isdir(repo_dir):
          # The subproject is not needed, or it is a tarball extracted in
          # 'libfoo-1.0' directory and the version has been bumped and the new
          # directory is 'libfoo-2.0'. In that case forcing a meson
          # reconfigure will download and use the new tarball.
          mlog.log('  -> Subproject has not been checked out. Run', mlog.bold('meson --reconfigure'), 'to fetch it if needed.')
      else:
          # The subproject has not changed, or the new source and/or patch
          # tarballs should be extracted in the same directory than previous
          # version.
          mlog.log('  -> Subproject has not changed, or the new source/patch needs to be extracted on the same location.\n' +
                   '     In that case, delete', mlog.bold(repo_dir), 'and run', mlog.bold('meson --reconfigure'))
  
  def git_output(cmd, workingdir):
      return git(cmd, workingdir, check=True, universal_newlines=True,
                 stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout
  
  def git_show(repo_dir):
      commit_message = git_output(['show', '--quiet', '--pretty=format:%h%n%d%n%s%n[%an]'], repo_dir)
      parts = [s.strip() for s in commit_message.split('\n')]
      mlog.log('  ->', mlog.yellow(parts[0]), mlog.red(parts[1]), parts[2], mlog.blue(parts[3]))
  
  def update_git(wrap, repo_dir, options):
      if not os.path.isdir(repo_dir):
          mlog.log('  -> Not used.')
          return
      revision = wrap.get('revision')
      ret = git_output(['rev-parse', '--abbrev-ref', 'HEAD'], repo_dir).strip()
      if ret == 'HEAD':
          try:
              # We are currently in detached mode, just checkout the new revision
              git_output(['fetch'], repo_dir)
              git_output(['checkout', revision], repo_dir)
          except subprocess.CalledProcessError as e:
              out = e.output.decode().strip()
              mlog.log('  -> Could not checkout revision', mlog.cyan(revision))
              mlog.log(mlog.red(out))
              mlog.log(mlog.red(str(e)))
              return
      elif ret == revision:
          try:
              # We are in the same branch, pull latest commits
              git_output(['-c', 'rebase.autoStash=true', 'pull', '--rebase'], repo_dir)
          except subprocess.CalledProcessError as e:
              out = e.output.decode().strip()
              mlog.log('  -> Could not rebase', mlog.bold(repo_dir), 'please fix and try again.')
              mlog.log(mlog.red(out))
              mlog.log(mlog.red(str(e)))
              return
      else:
          # We are in another branch, probably user created their own branch and
          # we should rebase it on top of wrap's branch.
          if options.rebase:
              try:
                  git_output(['fetch'], repo_dir)
                  git_output(['-c', 'rebase.autoStash=true', 'rebase', revision], repo_dir)
              except subprocess.CalledProcessError as e:
                  out = e.output.decode().strip()
                  mlog.log('  -> Could not rebase', mlog.bold(repo_dir), 'please fix and try again.')
                  mlog.log(mlog.red(out))
                  mlog.log(mlog.red(str(e)))
                  return
          else:
              mlog.log('  -> Target revision is', mlog.bold(revision), 'but currently in branch is', mlog.bold(ret), '\n' +
                       '     To rebase your branch on top of', mlog.bold(revision), 'use', mlog.bold('--rebase'), 'option.')
              return
  
      git_output(['submodule', 'update', '--checkout', '--recursive'], repo_dir)
      git_show(repo_dir)
  
  def update_hg(wrap, repo_dir, options):
      if not os.path.isdir(repo_dir):
          mlog.log('  -> Not used.')
          return
      revno = wrap.get('revision')
      if revno.lower() == 'tip':
          # Failure to do pull is not a fatal error,
          # because otherwise you can't develop without
          # a working net connection.
          subprocess
  
  [... the rest is omitted ...]
files: mesonbuild/msubprojects.py
files: import os, subprocess
  import argparse
  
  from . import mlog
  from .mesonlib import Popen_safe
  from .wrap.wrap import API_ROOT, PackageDefinition, Resolver, WrapException
  from .wrap import wraptool
  
  def update_wrapdb_file(wrap, repo_dir, options):
      patch_url = wrap.get('patch_url')
      branch, revision = wraptool.parse_patch_url(patch_url)
      new_branch, new_revision = wraptool.get_latest_version(wrap.name)
      if new_branch == branch and new_revision == revision:
          mlog.log('  -> Up to date.')
          return
      wraptool.update_wrap_file(wrap.filename, wrap.name, new_branch, new_revision)
      msg = ['  -> New wrap file downloaded.']
      # Meson reconfigure won't use the new wrap file as long as the source
      # directory exists. We don't delete it ourself to avoid data loss in case
      # user has changes in their copy.
      if os.path.isdir(repo_dir):
          msg += ['To use it, delete', mlog.bold(repo_dir), 'and run', mlog.bold('meson --reconfigure')]
      mlog.log(*msg)
  
  def update_file(wrap, repo_dir, options):
      patch_url = wrap.values.get('patch_url', '')
      if patch_url.startswith(API_ROOT):
          update_wrapdb_file(wrap, repo_dir, options)
      elif not os.path.isdir(repo_dir):
          # The subproject is not needed, or it is a tarball extracted in
          # 'libfoo-1.0' directory and the version has been bumped and the new
          # directory is 'libfoo-2.0'. In that case forcing a meson
          # reconfigure will download and use the new tarball.
          mlog.log('  -> Subproject has not been checked out. Run', mlog.bold('meson --reconfigure'), 'to fetch it if needed.')
      else:
          # The subproject has not changed, or the new source and/or patch
          # tarballs should be extracted in the same directory than previous
          # version.
          mlog.log('  -> Subproject has not changed, or the new source/patch needs to be extracted on the same location.\n' +
                   '     In that case, delete', mlog.bold(repo_dir), 'and run', mlog.bold('meson --reconfigure'))
  
  def git_output(cmd, workingdir):
      return subprocess.check_output(['git', '-C', workingdir] + cmd,
                                     # Redirect stdin to DEVNULL otherwise git
                                     # messes up the console and ANSI colors stop
                                     # working on Windows.
                                     stdin=subprocess.DEVNULL,
                                     stderr=subprocess.STDOUT).decode()
  
  def git_show(repo_dir):
      commit_message = git_output(['show', '--quiet', '--pretty=format:%h%n%d%n%s%n[%an]'], repo_dir)
      parts = [s.strip() for s in commit_message.split('\n')]
      mlog.log('  ->', mlog.yellow(parts[0]), mlog.red(parts[1]), parts[2], mlog.blue(parts[3]))
  
  def update_git(wrap, repo_dir, options):
      if not os.path.isdir(repo_dir):
          mlog.log('  -> Not used.')
          return
      revision = wrap.get('revision')
      ret = git_output(['rev-parse', '--abbrev-ref', 'HEAD'], repo_dir).strip()
      if ret == 'HEAD':
          try:
              # We are currently in detached mode, just checkout the new revision
              git_output(['fetch'], repo_dir)
              git_output(['checkout', revision], repo_dir)
          except subprocess.CalledProcessError as e:
              out = e.output.decode().strip()
              mlog.log('  -> Could not checkout revision', mlog.cyan(revision))
              mlog.log(mlog.red(out))
              mlog.log(mlog.red(str(e)))
              return
      elif ret == revision:
          try:
              # We are in the same branch, pull latest commits
              git_output(['-c', 'rebase.autoStash=true', 'pull', '--rebase'], repo_dir)
          except subprocess.CalledProcessError as e:
              out = e.output.decode().strip()
              mlog.log('  -> Could not rebase', mlog.bold(repo_dir), 'please fix and try again.')
              mlog.log(mlog.red(out))
              mlog.log(mlog.red(str(e)))
              return
      else:
          # We are in another branch, probably user created their own branch and
          # we should rebase it on top of wrap's branch.
          if options.rebase:
              try:
                  git_output(['fetch'], repo_dir)
                  git_output(['-c', 'rebase.autoStash=true', 'rebase', revision], repo_dir)
              except subprocess.CalledProcessError as e:
                  out = e.output.decode().strip()
                  mlog.log('  -> Could not rebase', mlog.bold(repo_dir), 'please fix and try again.')
                  mlog.log(mlog.red(out))
                  mlog.log(mlog.red(str(e)))
                  return
          else:
              mlog.log('  -> Target revision is', mlog.bold(revision), 'but currently in branch is', mlog.bold(ret), '\n' +
                       '     To rebase your branch on top of', mlog.bold(revision), 'use', mlog.bold('--rebase'), 'option.')
              return
  
      git_output(['submodule', 'update', '--checkout', '--recursive'], repo_dir)
      git_show(repo_dir)
  
  def update_hg(wrap, repo_dir, options):
      if not os.path.isdir(repo_dir):
          mlog.log('  -> Not used.')
          return
      revno = wrap.get('revision')
      if revno.lower() == 'tip':
          # Failure
  
  [... the rest is omitted ...]
files: mesonbuild/msubprojects.py
files: # 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 contextlib
  import urllib.request
  import urllib.error
  import urllib.parse
  import os
  import hashlib
  import shutil
  import tempfile
  import stat
  import subprocess
  import sys
  import configparser
  import typing as T
  
  from . import WrapMode
  from ..mesonlib import git, GIT, ProgressBar, MesonException
  
  if T.TYPE_CHECKING:
      import http.client
  
  try:
      # Importing is just done to check if SSL exists, so all warnings
      # regarding 'imported but unused' can be safely ignored
      import ssl  # noqa
      has_ssl = True
      API_ROOT = 'https://wrapdb.mesonbuild.com/v1/'
  except ImportError:
      has_ssl = False
      API_ROOT = 'http://wrapdb.mesonbuild.com/v1/'
  
  REQ_TIMEOUT = 600.0
  SSL_WARNING_PRINTED = False
  WHITELIST_SUBDOMAIN = 'wrapdb.mesonbuild.com'
  
  def quiet_git(cmd: T.List[str], workingdir: str) -> T.Tuple[bool, str]:
      if not GIT:
          return False, 'Git program not found.'
      pc = git(cmd, workingdir, universal_newlines=True,
               stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      if pc.returncode != 0:
          return False, pc.stderr
      return True, pc.stdout
  
  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
  
  def whitelist_wrapdb(urlstr: str) -> urllib.parse.ParseResult:
      """ raises WrapException if not whitelisted subdomain """
      url = urllib.parse.urlparse(urlstr)
      if not url.hostname:
          raise WrapException('{} is not a valid URL'.format(urlstr))
      if not url.hostname.endswith(WHITELIST_SUBDOMAIN):
          raise WrapException('{} is not a whitelisted WrapDB URL'.format(urlstr))
      if has_ssl and not url.scheme == 'https':
          raise WrapException('WrapDB did not have expected SSL https url, instead got {}'.format(urlstr))
      return url
  
  def open_wrapdburl(urlstring: str) -> 'http.client.HTTPResponse':
      global SSL_WARNING_PRINTED
  
      url = whitelist_wrapdb(urlstring)
      if has_ssl:
          try:
              return urllib.request.urlopen(urllib.parse.urlunparse(url), timeout=REQ_TIMEOUT)
          except urllib.error.URLError as excp:
              raise WrapException('WrapDB connection failed to {} with error {}'.format(urlstring, excp))
  
      # following code is only for those without Python SSL
      nossl_url = url._replace(scheme='http')
      if not SSL_WARNING_PRINTED:
          mlog.warning('SSL module not available in {}: WrapDB traffic not authenticated.'.format(sys.executable))
          SSL_WARNING_PRINTED = True
      try:
          return urllib.request.urlopen(urllib.parse.urlunparse(nossl_url), timeout=REQ_TIMEOUT)
      except urllib.error.URLError as excp:
          raise WrapException('WrapDB connection failed to {} with error {}'.format(urlstring, excp))
  
  
  class WrapException(MesonException):
      pass
  
  class WrapNotFoundException(WrapException):
      pass
  
  class PackageDefinition:
      def __init__(self, fname: str):
          self.filename = fname
          self.basename = os.path.basename(fname)
          self.name = self.basename[:-5]
          try:
              self.config = configparser.ConfigParser(interpolation=None)
              self.config.read(fname)
          except configparser.Error:
              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-'):
              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: str) -> str:
          try:
              return self.values[key]
          except KeyError:
              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:
      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:
         
  
  [... the rest is omitted ...]
files: mesonbuild/wrap/wrap.py
files: # 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 contextlib
  import urllib.request
  import urllib.error
  import urllib.parse
  import os
  import platform
  import hashlib
  import shutil
  import tempfile
  import stat
  import subprocess
  import sys
  import configparser
  import typing as T
  
  from . import WrapMode
  from ..mesonlib import ProgressBar, MesonException
  
  if T.TYPE_CHECKING:
      import http.client
  
  try:
      # Importing is just done to check if SSL exists, so all warnings
      # regarding 'imported but unused' can be safely ignored
      import ssl  # noqa
      has_ssl = True
      API_ROOT = 'https://wrapdb.mesonbuild.com/v1/'
  except ImportError:
      has_ssl = False
      API_ROOT = 'http://wrapdb.mesonbuild.com/v1/'
  
  GIT = shutil.which('git')
  REQ_TIMEOUT = 600.0
  SSL_WARNING_PRINTED = False
  WHITELIST_SUBDOMAIN = 'wrapdb.mesonbuild.com'
  
  def git(cmd: T.List[str], workingdir: str, **kwargs) -> subprocess.CompletedProcess:
      pc = subprocess.run([GIT, '-C', workingdir] + cmd,
                          # Redirect stdin to DEVNULL otherwise git messes up the
                          # console and ANSI colors stop working on Windows.
                          stdin=subprocess.DEVNULL, **kwargs)
      # Sometimes git calls git recursively, such as `git submodule update
      # --recursive` which will be without the above workaround, so set the
      # console mode again just in case.
      if platform.system().lower() == 'windows':
          mlog._windows_ansi()
      return pc
  
  def quiet_git(cmd: T.List[str], workingdir: str) -> T.Tuple[bool, str]:
      if not GIT:
          return False, 'Git program not found.'
      pc = git(cmd, workingdir, universal_newlines=True,
               stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      if pc.returncode != 0:
          return False, pc.stderr
      return True, pc.stdout
  
  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
  
  def whitelist_wrapdb(urlstr: str) -> urllib.parse.ParseResult:
      """ raises WrapException if not whitelisted subdomain """
      url = urllib.parse.urlparse(urlstr)
      if not url.hostname:
          raise WrapException('{} is not a valid URL'.format(urlstr))
      if not url.hostname.endswith(WHITELIST_SUBDOMAIN):
          raise WrapException('{} is not a whitelisted WrapDB URL'.format(urlstr))
      if has_ssl and not url.scheme == 'https':
          raise WrapException('WrapDB did not have expected SSL https url, instead got {}'.format(urlstr))
      return url
  
  def open_wrapdburl(urlstring: str) -> 'http.client.HTTPResponse':
      global SSL_WARNING_PRINTED
  
      url = whitelist_wrapdb(urlstring)
      if has_ssl:
          try:
              return urllib.request.urlopen(urllib.parse.urlunparse(url), timeout=REQ_TIMEOUT)
          except urllib.error.URLError as excp:
              raise WrapException('WrapDB connection failed to {} with error {}'.format(urlstring, excp))
  
      # following code is only for those without Python SSL
      nossl_url = url._replace(scheme='http')
      if not SSL_WARNING_PRINTED:
          mlog.warning('SSL module not available in {}: WrapDB traffic not authenticated.'.format(sys.executable))
          SSL_WARNING_PRINTED = True
      try:
          return urllib.request.urlopen(urllib.parse.urlunparse(nossl_url), timeout=REQ_TIMEOUT)
      except urllib.error.URLError as excp:
          raise WrapException('WrapDB connection failed to {} with error {}'.format(urlstring, excp))
  
  
  class WrapException(MesonException):
      pass
  
  class WrapNotFoundException(WrapException):
      pass
  
  class PackageDefinition:
      def __init__(self, fname: str):
          self.filename = fname
          self.basename = os.path.basename(fname)
          self.name = self.basename[:-5]
          try:
              self.config = configparser.ConfigParser(interpolation=None)
              self.config.read(fname)
          except configparser.Error:
              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-'):
              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: str) -> str:
          try:
              return self.values[key]
          except KeyError:
              m = 'Missing key {!r} in {}'
              raise WrapException(m.format(key, self.bas
  
  [... the rest is omitted ...]
files: mesonbuild/wrap/wrap.py