files: # Copyright 2014-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 os
  from . import (coredata, mesonlib, build)
  
  def add_arguments(parser):
      coredata.register_builtin_arguments(parser)
      parser.add_argument('builddir', nargs='?', default='.')
      parser.add_argument('--clearcache', action='store_true', default=False,
                          help='Clear cached state (e.g. found dependencies)')
  
  
  class ConfException(mesonlib.MesonException):
      pass
  
  
  class Conf:
      def __init__(self, build_dir):
          self.build_dir = build_dir
          if not os.path.isdir(os.path.join(build_dir, 'meson-private')):
              raise ConfException('Directory %s does not seem to be a Meson build directory.' % build_dir)
          self.build = build.load(self.build_dir)
          self.coredata = coredata.load(self.build_dir)
  
      def clear_cache(self):
          self.coredata.deps = {}
  
      def set_options(self, options):
          self.coredata.set_options(options)
  
      def save(self):
          # Only called if something has changed so overwrite unconditionally.
          coredata.save(self.coredata, self.build_dir)
          # We don't write the build file because any changes to it
          # are erased when Meson is executed the next time, i.e. when
          # Ninja is run.
  
      @staticmethod
      def print_aligned(arr):
          def make_lower_case(val):
              if isinstance(val, bool):
                  return str(val).lower()
              elif isinstance(val, list):
                  return [make_lower_case(i) for i in val]
              else:
                  return str(val)
  
          if not arr:
              return
  
          titles = {'name': 'Option', 'descr': 'Description', 'value': 'Current Value', 'choices': 'Possible Values'}
  
          name_col = [titles['name'], '-' * len(titles['name'])]
          value_col = [titles['value'], '-' * len(titles['value'])]
          choices_col = [titles['choices'], '-' * len(titles['choices'])]
          descr_col = [titles['descr'], '-' * len(titles['descr'])]
  
          choices_found = False
          for opt in arr:
              name_col.append(opt['name'])
              descr_col.append(opt['descr'])
              if isinstance(opt['value'], list):
                  value_col.append('[{0}]'.format(', '.join(make_lower_case(opt['value']))))
              else:
                  value_col.append(make_lower_case(opt['value']))
              if opt['choices']:
                  choices_found = True
                  if isinstance(opt['choices'], list):
                      choices_col.append('[{0}]'.format(', '.join(make_lower_case(opt['choices']))))
                  else:
                      choices_col.append(make_lower_case(opt['choices']))
              else:
                  choices_col.append('')
  
          col_widths = (max([len(i) for i in name_col], default=0),
                        max([len(i) for i in value_col], default=0),
                        max([len(i) for i in choices_col], default=0),
                        max([len(i) for i in descr_col], default=0))
  
          for line in zip(name_col, value_col, choices_col, descr_col):
              if choices_found:
                  print('  {0:{width[0]}} {1:{width[1]}} {2:{width[2]}} {3:{width[3]}}'.format(*line, width=col_widths))
              else:
                  print('  {0:{width[0]}} {1:{width[1]}} {3:{width[3]}}'.format(*line, width=col_widths))
  
      def print_options(self, title, options):
          print('\n{}:'.format(title))
          if not options:
              print('  No {}\n'.format(title.lower()))
          arr = []
          for k in sorted(options):
              o = options[k]
              d = o.description
              v = o.value
              c = o.choices
              if isinstance(o, coredata.UserUmaskOption):
                  v = format(v, '04o')
              arr.append({'name': k, 'descr': d, 'value': v, 'choices': c})
          self.print_aligned(arr)
  
      def print_conf(self):
          print('Core properties:')
          print('  Source dir', self.build.environment.source_dir)
          print('  Build dir ', self.build.environment.build_dir)
  
          dir_option_names = ['prefix',
                              'libdir',
                              'libexecdir',
                              'bindir',
                              'sbindir',
                              'includedir',
                              'datadir',
                              'mandir',
                              'infodir',
                              'localedir',
                              'sysconfdir',
                              'localstatedir',
                              'sharedstatedir']
          test_option_names = ['stdsplit',
                               'errorlogs']
          core_option_names = [k for k in self.coredata.builtins if k not in dir_option_names + test_option_names]
  
          dir_options = {k: o for k, o in self.coredata.builtins.items() if k in dir_option_names}
          test_options = {k: o for k, o in self.coredata.builtins.items() if k in test_option_names}
          core_options = {k: o for k, o in self.coredata.builtins.items() if k in core_option_names}
  
          self.print_options('Core options', core_options)
          self.print_options('Backend options', self.coredata.backend_options)
          self.print_options('Base options', self.coredata.base_options)
          self.print_options('Compiler options', self.coredata.compiler_options)
          self.print_options('Directories', dir_options)
          self.print_options('Project options', self.coredata.user_options)
          self.print_options('Testing options', test_options)
  
  
  def run(options):
      coredata.parse_cmd_line_options(options)
      builddir = os.path.abspath(os.path.realpath(options.builddir))
      try:
          c = Conf(builddir)
          save = False
          if len(options.cmd_line_options) > 0:
              c.set_options(options.cmd_line_options)
              save = True
          elif options.clearcache:
              c.clear_cache()
              save = True
          else:
              c.print_conf()
          if save:
              c.save()
      except ConfException as e:
          print('Meson configurator encountered an error:')
          raise e
      return 0
  
files: mesonbuild/mconf.py
files: # Copyright 2014-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 os
  import argparse
  from . import (coredata, mesonlib, build)
  
  def buildparser():
      parser = argparse.ArgumentParser(prog='meson configure')
      coredata.register_builtin_arguments(parser)
  
      parser.add_argument('builddir', nargs='?', default='.')
      parser.add_argument('--clearcache', action='store_true', default=False,
                          help='Clear cached state (e.g. found dependencies)')
      return parser
  
  
  class ConfException(mesonlib.MesonException):
      pass
  
  
  class Conf:
      def __init__(self, build_dir):
          self.build_dir = build_dir
          if not os.path.isdir(os.path.join(build_dir, 'meson-private')):
              raise ConfException('Directory %s does not seem to be a Meson build directory.' % build_dir)
          self.build = build.load(self.build_dir)
          self.coredata = coredata.load(self.build_dir)
  
      def clear_cache(self):
          self.coredata.deps = {}
  
      def set_options(self, options):
          self.coredata.set_options(options)
  
      def save(self):
          # Only called if something has changed so overwrite unconditionally.
          coredata.save(self.coredata, self.build_dir)
          # We don't write the build file because any changes to it
          # are erased when Meson is executed the next time, i.e. when
          # Ninja is run.
  
      @staticmethod
      def print_aligned(arr):
          def make_lower_case(val):
              if isinstance(val, bool):
                  return str(val).lower()
              elif isinstance(val, list):
                  return [make_lower_case(i) for i in val]
              else:
                  return str(val)
  
          if not arr:
              return
  
          titles = {'name': 'Option', 'descr': 'Description', 'value': 'Current Value', 'choices': 'Possible Values'}
  
          name_col = [titles['name'], '-' * len(titles['name'])]
          value_col = [titles['value'], '-' * len(titles['value'])]
          choices_col = [titles['choices'], '-' * len(titles['choices'])]
          descr_col = [titles['descr'], '-' * len(titles['descr'])]
  
          choices_found = False
          for opt in arr:
              name_col.append(opt['name'])
              descr_col.append(opt['descr'])
              if isinstance(opt['value'], list):
                  value_col.append('[{0}]'.format(', '.join(make_lower_case(opt['value']))))
              else:
                  value_col.append(make_lower_case(opt['value']))
              if opt['choices']:
                  choices_found = True
                  if isinstance(opt['choices'], list):
                      choices_col.append('[{0}]'.format(', '.join(make_lower_case(opt['choices']))))
                  else:
                      choices_col.append(make_lower_case(opt['choices']))
              else:
                  choices_col.append('')
  
          col_widths = (max([len(i) for i in name_col], default=0),
                        max([len(i) for i in value_col], default=0),
                        max([len(i) for i in choices_col], default=0),
                        max([len(i) for i in descr_col], default=0))
  
          for line in zip(name_col, value_col, choices_col, descr_col):
              if choices_found:
                  print('  {0:{width[0]}} {1:{width[1]}} {2:{width[2]}} {3:{width[3]}}'.format(*line, width=col_widths))
              else:
                  print('  {0:{width[0]}} {1:{width[1]}} {3:{width[3]}}'.format(*line, width=col_widths))
  
      def print_options(self, title, options):
          print('\n{}:'.format(title))
          if not options:
              print('  No {}\n'.format(title.lower()))
          arr = []
          for k in sorted(options):
              o = options[k]
              d = o.description
              v = o.value
              c = o.choices
              if isinstance(o, coredata.UserUmaskOption):
                  v = format(v, '04o')
              arr.append({'name': k, 'descr': d, 'value': v, 'choices': c})
          self.print_aligned(arr)
  
      def print_conf(self):
          print('Core properties:')
          print('  Source dir', self.build.environment.source_dir)
          print('  Build dir ', self.build.environment.build_dir)
  
          dir_option_names = ['prefix',
                              'libdir',
                              'libexecdir',
                              'bindir',
                              'sbindir',
                              'includedir',
                              'datadir',
                              'mandir',
                              'infodir',
                              'localedir',
                              'sysconfdir',
                              'localstatedir',
                              'sharedstatedir']
          test_option_names = ['stdsplit',
                               'errorlogs']
          core_option_names = [k for k in self.coredata.builtins if k not in dir_option_names + test_option_names]
  
          dir_options = {k: o for k, o in self.coredata.builtins.items() if k in dir_option_names}
          test_options = {k: o for k, o in self.coredata.builtins.items() if k in test_option_names}
          core_options = {k: o for k, o in self.coredata.builtins.items() if k in core_option_names}
  
          self.print_options('Core options', core_options)
          self.print_options('Backend options', self.coredata.backend_options)
          self.print_options('Base options', self.coredata.base_options)
          self.print_options('Compiler options', self.coredata.compiler_options)
          self.print_options('Directories', dir_options)
          self.print_options('Project options', self.coredata.user_options)
          self.print_options('Testing options', test_options)
  
  
  def run(args):
      args = mesonlib.expand_arguments(args)
      options = buildparser().parse_args(args)
      coredata.parse_cmd_line_options(options)
      builddir = os.path.abspath(os.path.realpath(options.builddir))
      try:
          c = Conf(builddir)
          save = False
          if len(options.cmd_line_options) > 0:
              c.set_options(options.cmd_line_options)
              save = True
          elif options.clearcache:
              c.clear_cache()
              save = True
          else:
              c.print_conf()
          if save:
              c.save()
      except ConfException as e:
          print('Meson configurator encountered an error:')
          raise e
      return 0
  
files: mesonbuild/mconf.py
files: # Copyright 2012-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 sys
  import os.path
  import importlib
  import traceback
  import argparse
  
  from . import mesonlib
  from . import mlog
  from . import mconf, minit, minstall, mintro, msetup, mtest, rewriter
  from .mesonlib import MesonException
  from .environment import detect_msys2_arch
  from .wrap import wraptool
  
  
  class CommandLineParser:
      def __init__(self):
          self.commands = {}
          self.parser = argparse.ArgumentParser(prog='meson')
          self.subparsers = self.parser.add_subparsers(title='Commands',
                                                       description='If no command is specified it defaults to setup command.')
          self.add_command('setup', msetup.add_arguments, msetup.run,
                           help='Configure the project')
          self.add_command('configure', mconf.add_arguments, mconf.run,
                           help='Change project options',)
          self.add_command('install', minstall.add_arguments, minstall.run,
                           help='Install the project')
          self.add_command('introspect', mintro.add_arguments, mintro.run,
                           help='Introspect project')
          self.add_command('init', minit.add_arguments, minit.run,
                           help='Create a new project')
          self.add_command('test', mtest.add_arguments, mtest.run,
                           help='Run tests')
          self.add_command('rewrite', rewriter.add_arguments, rewriter.run,
                           help='Edit project files')
          self.add_command('wrap', wraptool.add_arguments, wraptool.run,
                           help='Wrap tools')
          self.add_command('runpython', self.add_runpython_arguments, self.run_runpython_command,
                           help='Run a python script')
          self.add_command('help', self.add_help_arguments, self.run_help_command,
                           help='Print help of a subcommand')
  
      def add_command(self, name, add_arguments_func, run_func, help):
          p = self.subparsers.add_parser(name, help=help)
          add_arguments_func(p)
          p.set_defaults(run_func=run_func)
          self.commands[name] = p
  
      def add_runpython_arguments(self, parser):
          parser.add_argument('script_file')
          parser.add_argument('script_args', nargs=argparse.REMAINDER)
  
      def run_runpython_command(self, options):
          import runpy
          sys.argv[1:] = options.script_args
          runpy.run_path(options.script_file, run_name='__main__')
          return 0
  
      def add_help_arguments(self, parser):
          parser.add_argument('command', nargs='?')
  
      def run_help_command(self, options):
          if options.command:
              self.commands[options.command].print_help()
          else:
              self.parser.print_help()
          return 0
  
      def run(self, args):
          # If first arg is not a known command, assume user wants to run the setup
          # command.
          known_commands = list(self.commands.keys()) + ['-h', '--help']
          if len(args) == 0 or args[0] not in known_commands:
              args = ['setup'] + args
  
          args = mesonlib.expand_arguments(args)
          options = self.parser.parse_args(args)
  
          try:
              return options.run_func(options)
          except MesonException as e:
              mlog.exception(e)
              logfile = mlog.shutdown()
              if logfile is not None:
                  mlog.log("\nA full log can be found at", mlog.bold(logfile))
              if os.environ.get('MESON_FORCE_BACKTRACE'):
                  raise
              return 1
          except Exception as e:
              if os.environ.get('MESON_FORCE_BACKTRACE'):
                  raise
              traceback.print_exc()
              return 2
          finally:
              mlog.shutdown()
  
  def run_script_command(script_name, script_args):
      # Map script name to module name for those that doesn't match
      script_map = {'exe': 'meson_exe',
                    'install': 'meson_install',
                    'delsuffix': 'delwithsuffix',
                    'gtkdoc': 'gtkdochelper',
                    'hotdoc': 'hotdochelper',
                    'regencheck': 'regen_checker'}
      module_name = script_map.get(script_name, script_name)
  
      try:
          module = importlib.import_module('mesonbuild.scripts.' + module_name)
      except ModuleNotFoundError as e:
          mlog.exception(e)
          return 1
  
      try:
          return module.run(script_args)
      except MesonException as e:
          mlog.error('Error in {} helper script:'.format(script_name))
          mlog.exception(e)
          return 1
  
  def run(original_args, mainfile):
      if sys.version_info < (3, 5):
          print('Meson works correctly only with python 3.5+.')
          print('You have python %s.' % sys.version)
          print('Please update your environment')
          return 1
  
      # https://github.com/mesonbuild/meson/issues/3653
      if sys.platform.lower() == 'msys':
          mlog.error('This python3 seems to be msys/python on MSYS2 Windows, which is known to have path semantics incompatible with Meson')
          msys2_arch = detect_msys2_arch()
          if msys2_arch:
              mlog.error('Please install and use mingw-w64-i686-python3 and/or mingw-w64-x86_64-python3 with Pacman')
          else:
              mlog.error('Please download and use Python as detailed at: https://mesonbuild.com/Getting-meson.html')
          return 2
  
      # Set the meson command that will be used to run scripts and so on
      mesonlib.set_meson_command(mainfile)
  
      args = original_args[:]
  
      # Special handling of internal commands called from backends, they don't
      # need to go through argparse.
      if len(args) >= 2 and args[0] == '--internal':
          if args[1] == 'regenerate':
              # Rewrite "meson --internal regenerate" command line to
              # "meson --reconfigure"
              args = ['--reconfigure'] + args[2:]
          else:
              return run_script_command(args[1], args[2:])
  
      return CommandLineParser().run(args)
  
  def main():
      # Always resolve the command path so Ninja can find it for regen, tests, etc.
      if 'meson.exe' in sys.executable:
          assert(os.path.isabs(sys.executable))
          launcher = sys.executable
      else:
          launcher = os.path.realpath(sys.argv[0])
      return run(sys.argv[1:], launcher)
  
  if __name__ == '__main__':
      sys.exit(main())
  
files: mesonbuild/mesonmain.py
files: # Copyright 2012-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 sys
  import os.path
  import importlib
  
  from . import mesonlib
  from . import mlog
  from .mesonlib import MesonException
  from .environment import detect_msys2_arch
  
  def run_script_command(script_name, script_args):
      # Map script name to module name for those that doesn't match
      script_map = {'exe': 'meson_exe',
                    'install': 'meson_install',
                    'delsuffix': 'delwithsuffix',
                    'gtkdoc': 'gtkdochelper',
                    'hotdoc': 'hotdochelper',
                    'regencheck': 'regen_checker'}
      module_name = script_map.get(script_name, script_name)
  
      try:
          module = importlib.import_module('mesonbuild.scripts.' + module_name)
      except ModuleNotFoundError as e:
          mlog.exception(e)
          return 1
  
      try:
          return module.run(script_args)
      except MesonException as e:
          mlog.error('Error in {} helper script:'.format(script_name))
          mlog.exception(e)
          return 1
  
  def run(original_args, mainfile):
      if sys.version_info < (3, 5):
          print('Meson works correctly only with python 3.5+.')
          print('You have python %s.' % sys.version)
          print('Please update your environment')
          return 1
      # https://github.com/mesonbuild/meson/issues/3653
      if sys.platform.lower() == 'msys':
          mlog.error('This python3 seems to be msys/python on MSYS2 Windows, which is known to have path semantics incompatible with Meson')
          msys2_arch = detect_msys2_arch()
          if msys2_arch:
              mlog.error('Please install and use mingw-w64-i686-python3 and/or mingw-w64-x86_64-python3 with Pacman')
          else:
              mlog.error('Please download and use Python as detailed at: https://mesonbuild.com/Getting-meson.html')
          return 2
  
      # Set the meson command that will be used to run scripts and so on
      mesonlib.set_meson_command(mainfile)
  
      args = original_args[:]
  
      # Special handling of internal commands called from backends, they don't
      # need to go through argparse.
      if len(args) >= 2 and args[0] == '--internal':
          if args[1] == 'regenerate':
              # Rewrite "meson --internal regenerate" command line to
              # "meson --reconfigure"
              args = ['--reconfigure'] + args[2:]
          else:
              return run_script_command(args[1], args[2:])
  
      if len(args) > 0:
          # First check if we want to run a subcommand.
          cmd_name = args[0]
          remaining_args = args[1:]
          # "help" is a special case: Since printing of the help may be
          # delegated to a subcommand, we edit cmd_name before executing
          # the rest of the logic here.
          if cmd_name == 'help':
              remaining_args += ['--help']
              args = remaining_args
              cmd_name = args[0]
          if cmd_name == 'test':
              from . import mtest
              return mtest.run(remaining_args)
          elif cmd_name == 'install':
              from . import minstall
              return minstall.run(remaining_args)
          elif cmd_name == 'introspect':
              from . import mintro
              return mintro.run(remaining_args)
          elif cmd_name == 'rewrite':
              from . import rewriter
              return rewriter.run(remaining_args)
          elif cmd_name == 'configure':
              try:
                  from . import mconf
                  return mconf.run(remaining_args)
              except MesonException as e:
                  mlog.exception(e)
                  sys.exit(1)
          elif cmd_name == 'wrap':
              from .wrap import wraptool
              return wraptool.run(remaining_args)
          elif cmd_name == 'init':
              from . import minit
              return minit.run(remaining_args)
          elif cmd_name == 'runpython':
              import runpy
              script_file = remaining_args[0]
              sys.argv[1:] = remaining_args[1:]
              runpy.run_path(script_file, run_name='__main__')
              sys.exit(0)
          else:
              # If cmd_name is not a known command, assume user wants to run the
              # setup command.
              from . import msetup
              if cmd_name != 'setup':
                  remaining_args = args
              return msetup.run(remaining_args)
  
      return 0
  
  def main():
      # Always resolve the command path so Ninja can find it for regen, tests, etc.
      if 'meson.exe' in sys.executable:
          assert(os.path.isabs(sys.executable))
          launcher = sys.executable
      else:
          launcher = os.path.realpath(sys.argv[0])
      return run(sys.argv[1:], launcher)
  
  if __name__ == '__main__':
      sys.exit(main())
  
files: mesonbuild/mesonmain.py
files: # Copyright 2017 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.
  
  """Code that creates simple startup projects."""
  
  import os, sys, re, shutil, subprocess
  from glob import glob
  from mesonbuild import mesonlib
  from mesonbuild.environment import detect_ninja
  
  lib_h_template = '''#pragma once
  #if defined _WIN32 || defined __CYGWIN__
    #ifdef BUILDING_{utoken}
      #define {utoken}_PUBLIC __declspec(dllexport)
    #else
      #define {utoken}_PUBLIC __declspec(dllimport)
    #endif
  #else
    #ifdef BUILDING_{utoken}
        #define {utoken}_PUBLIC __attribute__ ((visibility ("default")))
    #else
        #define {utoken}_PUBLIC
    #endif
  #endif
  
  int {utoken}_PUBLIC {function_name}();
  
  '''
  
  lib_c_template = '''#include <{header_file}>
  
  /* This function will not be exported and is not
   * directly callable by users of this library.
   */
  int internal_function() {{
      return 0;
  }}
  
  int {function_name}() {{
      return internal_function();
  }}
  '''
  
  lib_c_test_template = '''#include <{header_file}>
  #include <stdio.h>
  
  int main(int argc, char **argv) {{
      if(argc != 1) {{
          printf("%s takes no arguments.\\n", argv[0]);
          return 1;
      }}
      return {function_name}();
  }}
  '''
  
  lib_c_meson_template = '''project('{project_name}', 'c',
    version : '{version}',
    default_options : ['warning_level=3'])
  
  # These arguments are only used to build the shared library
  # not the executables that use the library.
  lib_args = ['-DBUILDING_{utoken}']
  
  shlib = shared_library('{lib_name}', '{source_file}',
    install : true,
    c_args : lib_args,
    gnu_symbol_visibility : 'hidden',
  )
  
  test_exe = executable('{test_exe_name}', '{test_source_file}',
    link_with : shlib)
  test('{test_name}', test_exe)
  
  # Make this library usable as a Meson subproject.
  {ltoken}_dep = declare_dependency(
    include_directories: include_directories('.'),
    link_with : shlib)
  
  # Make this library usable from the system's
  # package manager.
  install_headers('{header_file}', subdir : '{header_dir}')
  
  pkg_mod = import('pkgconfig')
  pkg_mod.generate(
    name : '{project_name}',
    filebase : '{ltoken}',
    description : 'Meson sample project.',
    subdirs : '{header_dir}',
    libraries : shlib,
    version : '{version}',
  )
  '''
  
  hello_c_template = '''#include <stdio.h>
  
  #define PROJECT_NAME "{project_name}"
  
  int main(int argc, char **argv) {{
      if(argc != 1) {{
          printf("%s takes no arguments.\\n", argv[0]);
          return 1;
      }}
      printf("This is project %s.\\n", PROJECT_NAME);
      return 0;
  }}
  '''
  
  hello_c_meson_template = '''project('{project_name}', 'c',
    version : '{version}',
    default_options : ['warning_level=3'])
  
  exe = executable('{exe_name}', '{source_name}',
    install : true)
  
  test('basic', exe)
  '''
  
  hello_cpp_template = '''#include <iostream>
  
  #define PROJECT_NAME "{project_name}"
  
  int main(int argc, char **argv) {{
      if(argc != 1) {{
          std::cout << argv[0] <<  "takes no arguments.\\n";
          return 1;
      }}
      std::cout << "This is project " << PROJECT_NAME << ".\\n";
      return 0;
  }}
  '''
  
  hello_cpp_meson_template = '''project('{project_name}', 'cpp',
    version : '{version}',
    default_options : ['warning_level=3',
                       'cpp_std=c++14'])
  
  exe = executable('{exe_name}', '{source_name}',
    install : true)
  
  test('basic', exe)
  '''
  
  lib_hpp_template = '''#pragma once
  #if defined _WIN32 || defined __CYGWIN__
    #ifdef BUILDING_{utoken}
      #define {utoken}_PUBLIC __declspec(dllexport)
    #else
      #define {utoken}_PUBLIC __declspec(dllimport)
    #endif
  #else
    #ifdef BUILDING_{utoken}
        #define {utoken}_PUBLIC __attribute__ ((visibility ("default")))
    #else
        #define {utoken}_PUBLIC
    #endif
  #endif
  
  namespace {namespace} {{
  
  class {utoken}_PUBLIC {class_name} {{
  
  public:
    {class_name}();
    int get_number() const;
  
  private:
  
    int number;
  
  }};
  
  }}
  
  '''
  
  lib_cpp_template = '''#include <{header_file}>
  
  namespace {namespace} {{
  
  {class_name}::{class_name}() {{
      number = 6;
  }}
  
  int {class_name}::get_number() const {{
    return number;
  }}
  
  }}
  '''
  
  lib_cpp_test_template = '''#include <{header_file}>
  #include <iostream>
  
  int main(int argc, char **argv) {{
      if(argc != 1) {{
          std::cout << argv[0] << " takes no arguments.\\n";
          return 1;
      }}
      {namespace}::{class_name} c;
      return c.get_number() != 6;
  }}
  '''
  
  lib_cpp_meson_template = '''project('{project_name}', 'cpp',
    version : '{version}',
    default_options : ['warning_level=3', 'cpp_std=c++14'])
  
  # These arguments are only used to build the shared library
  # not the executables that use the library.
  lib_args = ['-DBUILDING_{utoken}']
  
  shlib = shared_library('{lib_name}', '{source_file}',
    install : true,
    cpp_args : lib_args,
    gnu_symbol_visibility : 'hidden',
  )
  
  test_exe = executable('{test_exe_name}', '{test_source_file}',
    link_with : shlib)
  test('{test_name}', test_exe)
  
  # Make this library usable as a Meson subproject.
  {ltoken}_dep = declare_dependency(
    include_directories: include_directories('.'),
    link_with : shlib)
  
  # Make this library usable from the system's
  # package manager.
  install_headers('{header_file}', subdir : '{header_dir}')
  
  pkg_mod = import('pkgconfig')
  pkg_mod.generate(
    name : '{project_name}',
    filebase : '{ltoken}',
    description : 'Meson sample project.',
    subdirs : '{header_dir}',
    libraries : shlib,
    version : '{version}',
  )
  '''
  
  info_message = '''Sample project created. To build it run the
  following commands:
  
  meson builddir
  ninja -C builddir
  '''
  
  def create_exe_c_sample(project_name, project_version):
      lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
      source_name = lowercase_token + '.c'
      open(source_name, 'w').write(hello_c_template.format(project_name=project_name))
      open('meson.build', 'w').write(hello_c_meson_template.format(project_name=project_name,
                                                                   exe_name=lowercase_token,
                                                                   source_name=source_name,
                                                                   version=project_version))
  
  def create_lib_c_sample(project_name, version):
      lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
      uppercase_token = lowercase_token.upper()
      function_name = lowercase_token[0:3] + '_func'
      lib_h_name = lowercase_token + '.h'
      lib_c_name = lowercase_token + '.c'
      test_c_name = lowercase_token + '_test.c'
      kwargs = {'utoken': uppercase_token,
                'ltoken': lowercase_token,
                'header_dir': lowercase_token,
                'function_name': function_name,
                'header_file': lib_h_name,
                'source_file': lib_c_name,
                'test_source_file': test_c_name,
                'test_exe_name': lowercase_token,
                'project_name': project_name,
                'lib_name': lowercase_token,
                'test_name': lowercase_token,
                'version': version,
                }
      open(lib_h_name, 'w').write(lib_h_template.format(**kwargs))
      open(lib_c_name, 'w').write(lib_c_template.format(**kwargs))
      open(test_c_name, 'w').write(lib_c_test_template.format(**kwargs))
      open('meson.build', 'w').write(lib_c_meson_template.format(**kwargs))
  
  def create_exe_cpp_sample(project_name, project_version):
      lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
      source_name = lowercase_token + '.cpp'
      open(source_name, 'w').write(hello_cpp_template.format(project_name=project_name))
      open('meson.build', 'w').write(hello_cpp_meson_template.format(project_name=project_name,
                                                                     exe_name=lowercase_token,
                                                                     source_name=source_name,
                                                                     version=project_version))
  
  def create_lib_cpp_sample(project_name, version):
      lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
      uppercase_token = lowercase_token.upper()
      class_name = uppercase_token[0] + lowercase_token[1:]
      namespace = lowercase_token
      lib_h_name = lowercase_token + '.hpp'
      lib_c_name = lowercase_token + '.cpp'
      test_c_name = lowercase_token + '_test.cpp'
      kwargs = {'utoken': uppercase_token,
                'ltoken': lowercase_token,
                'header_dir': lowercase_token,
                'class_name': class_name,
                'namespace': namespace,
                'header_file': lib_h_name,
                'source_file': lib_c_name,
                'test_source_file': test_c_name,
                'test_exe_name': lowercase_token,
                'project_name': project_name,
                'lib_name': lowercase_token,
                'test_name': lowercase_token,
                'version': version,
                }
      open(lib_h_name, 'w').write(lib_hpp_template.format(**kwargs))
      open(lib_c_name, 'w').write(lib_cpp_template.format(**kwargs))
      open(test_c_name, 'w').write(lib_cpp_test_template.format(**kwargs))
      open('meson.build', 'w').write(lib_cpp_meson_template.format(**kwargs))
  
  def create_sample(options):
      if options.language == 'c':
          if options.type == 'executable':
              create_exe_c_sample(options.name, options.version)
          elif options.type == 'library':
              create_lib_c_sample(options.name, options.version)
          else:
              raise RuntimeError('Unreachable code')
      elif options.language == 'cpp':
          if options.type == 'executable':
              create_exe_cpp_sample(options.name, options.version)
          elif options.type == 'library':
              create_lib_cpp_sample(options.name, options.version)
          else:
              raise RuntimeError('Unreachable code')
      else:
          raise RuntimeError('Unreachable code')
      print(info_message)
  
  def autodetect_options(options, sample=False):
      if not options.name:
          options.name = os.path.basename(os.getcwd())
          if not re.match('[a-zA-Z_][a-zA-Z0-9]*', options.name) and sample:
              print('Name of current directory "{}" is not usable as a sample project name.\n'
                    'Specify a project name with --name.'.format(options.name))
              sys.exit(1)
          print('Using "{}" (name of current directory) as project name.'
                .format(options.name))
      if not options.executable:
          options.executable = options.name
          print('Using "{}" (project name) as name of executable to build.'
                .format(options.executable))
      if sample:
          # The rest of the autodetection is not applicable to generating sample projects.
          return
      if not options.srcfiles:
          srcfiles = []
          for f in os.listdir():
              if f.endswith('.cc') or f.endswith('.cpp') or f.endswith('.c'):
                  srcfiles.append(f)
          if not srcfiles:
              print("No recognizable source files found.\n"
                    "Run me in an empty directory to create a sample project.")
              sys.exit(1)
          options.srcfiles = srcfiles
          print("Detected source files: " + ' '.join(srcfiles))
      if not options.language:
          for f in options.srcfiles:
              if f.endswith('.cc') or f.endswith('.cpp'):
                  options.language = 'cpp'
                  break
              if f.endswith('.c'):
                  options.language = 'c'
                  break
          if not options.language:
              print("Can't autodetect language, please specify it with -l.")
              sys.exit(1)
          print("Detected language: " + options.language)
  
  meson_executable_template = '''project('{project_name}', '{language}',
    version : '{version}',
    default_options : [{default_options}])
  
  executable('{executable}',
             {sourcespec},{depspec}
             install : true)
  '''
  
  def create_meson_build(options):
      if options.type != 'executable':
          print('\nGenerating a meson.build file from existing sources is\n'
                'supported only for project type "executable".\n'
                'Run me in an empty directory to create a sample project.')
          sys.exit(1)
      default_options = ['warning_level=3']
      if options.language == 'cpp':
          # This shows how to set this very common option.
          default_options += ['cpp_std=c++14']
      # If we get a meson.build autoformatter one day, this code could
      # be simplified quite a bit.
      formatted_default_options = ', '.join("'{}'".format(x) for x in default_options)
      sourcespec = ',\n           '.join("'{}'".format(x) for x in options.srcfiles)
      depspec = ''
      if options.deps:
          depspec = '\n           dependencies : [\n              '
          depspec += ',\n              '.join("dependency('{}')".format(x)
                                              for x in options.deps.split(','))
          depspec += '],'
      content = meson_executable_template.format(project_name=options.name,
                                                 language=options.language,
                                                 version=options.version,
                                                 executable=options.executable,
                                                 sourcespec=sourcespec,
                                                 depspec=depspec,
                                                 default_options=formatted_default_options)
      open('meson.build', 'w').write(content)
      print('Generated meson.build file:\n\n' + content)
  
  def add_arguments(parser):
      parser.add_argument("srcfiles", metavar="sourcefile", nargs="*",
                          help="source files. default: all recognized files in current directory")
      parser.add_argument("-n", "--name", help="project name. default: name of current directory")
      parser.add_argument("-e", "--executable", help="executable name. default: project name")
      parser.add_argument("-d", "--deps", help="dependencies, comma-separated")
      parser.add_argument("-l", "--language", choices=['c', 'cpp'],
                          help="project language. default: autodetected based on source files")
      parser.add_argument("-b", "--build", help="build after generation", action='store_true')
      parser.add_argument("--builddir", help="directory for build", default='build')
      parser.add_argument("-f", "--force", action="store_true",
                          help="force overwrite of existing files and directories.")
      parser.add_argument('--type', default='executable',
                          choices=['executable', 'library'])
      parser.add_argument('--version', default='0.1')
  
  def run(options):
      if len(glob('*')) == 0:
          autodetect_options(options, sample=True)
          if not options.language:
              print('Defaulting to generating a C language project.')
              options.language = 'c'
          create_sample(options)
      else:
          autodetect_options(options)
          if os.path.isfile('meson.build') and not options.force:
              print('meson.build already exists. Use --force to overwrite.')
              sys.exit(1)
          create_meson_build(options)
      if options.build:
          if os.path.isdir(options.builddir) and options.force:
              print('Build directory already exists, deleting it.')
              shutil.rmtree(options.builddir)
          print('Building...')
          cmd = mesonlib.meson_command + [options.builddir]
          err = subprocess.call(cmd)
          if err:
              sys.exit(1)
          cmd = [detect_ninja(), '-C', options.builddir]
          err = subprocess.call(cmd)
          if err:
              sys.exit(1)
      return 0
  
files: mesonbuild/minit.py
files: # Copyright 2017 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.
  
  """Code that creates simple startup projects."""
  
  import os, sys, argparse, re, shutil, subprocess
  from glob import glob
  from mesonbuild import mesonlib
  from mesonbuild.environment import detect_ninja
  
  lib_h_template = '''#pragma once
  #if defined _WIN32 || defined __CYGWIN__
    #ifdef BUILDING_{utoken}
      #define {utoken}_PUBLIC __declspec(dllexport)
    #else
      #define {utoken}_PUBLIC __declspec(dllimport)
    #endif
  #else
    #ifdef BUILDING_{utoken}
        #define {utoken}_PUBLIC __attribute__ ((visibility ("default")))
    #else
        #define {utoken}_PUBLIC
    #endif
  #endif
  
  int {utoken}_PUBLIC {function_name}();
  
  '''
  
  lib_c_template = '''#include <{header_file}>
  
  /* This function will not be exported and is not
   * directly callable by users of this library.
   */
  int internal_function() {{
      return 0;
  }}
  
  int {function_name}() {{
      return internal_function();
  }}
  '''
  
  lib_c_test_template = '''#include <{header_file}>
  #include <stdio.h>
  
  int main(int argc, char **argv) {{
      if(argc != 1) {{
          printf("%s takes no arguments.\\n", argv[0]);
          return 1;
      }}
      return {function_name}();
  }}
  '''
  
  lib_c_meson_template = '''project('{project_name}', 'c',
    version : '{version}',
    default_options : ['warning_level=3'])
  
  # These arguments are only used to build the shared library
  # not the executables that use the library.
  lib_args = ['-DBUILDING_{utoken}']
  
  shlib = shared_library('{lib_name}', '{source_file}',
    install : true,
    c_args : lib_args,
    gnu_symbol_visibility : 'hidden',
  )
  
  test_exe = executable('{test_exe_name}', '{test_source_file}',
    link_with : shlib)
  test('{test_name}', test_exe)
  
  # Make this library usable as a Meson subproject.
  {ltoken}_dep = declare_dependency(
    include_directories: include_directories('.'),
    link_with : shlib)
  
  # Make this library usable from the system's
  # package manager.
  install_headers('{header_file}', subdir : '{header_dir}')
  
  pkg_mod = import('pkgconfig')
  pkg_mod.generate(
    name : '{project_name}',
    filebase : '{ltoken}',
    description : 'Meson sample project.',
    subdirs : '{header_dir}',
    libraries : shlib,
    version : '{version}',
  )
  '''
  
  hello_c_template = '''#include <stdio.h>
  
  #define PROJECT_NAME "{project_name}"
  
  int main(int argc, char **argv) {{
      if(argc != 1) {{
          printf("%s takes no arguments.\\n", argv[0]);
          return 1;
      }}
      printf("This is project %s.\\n", PROJECT_NAME);
      return 0;
  }}
  '''
  
  hello_c_meson_template = '''project('{project_name}', 'c',
    version : '{version}',
    default_options : ['warning_level=3'])
  
  exe = executable('{exe_name}', '{source_name}',
    install : true)
  
  test('basic', exe)
  '''
  
  hello_cpp_template = '''#include <iostream>
  
  #define PROJECT_NAME "{project_name}"
  
  int main(int argc, char **argv) {{
      if(argc != 1) {{
          std::cout << argv[0] <<  "takes no arguments.\\n";
          return 1;
      }}
      std::cout << "This is project " << PROJECT_NAME << ".\\n";
      return 0;
  }}
  '''
  
  hello_cpp_meson_template = '''project('{project_name}', 'cpp',
    version : '{version}',
    default_options : ['warning_level=3',
                       'cpp_std=c++14'])
  
  exe = executable('{exe_name}', '{source_name}',
    install : true)
  
  test('basic', exe)
  '''
  
  lib_hpp_template = '''#pragma once
  #if defined _WIN32 || defined __CYGWIN__
    #ifdef BUILDING_{utoken}
      #define {utoken}_PUBLIC __declspec(dllexport)
    #else
      #define {utoken}_PUBLIC __declspec(dllimport)
    #endif
  #else
    #ifdef BUILDING_{utoken}
        #define {utoken}_PUBLIC __attribute__ ((visibility ("default")))
    #else
        #define {utoken}_PUBLIC
    #endif
  #endif
  
  namespace {namespace} {{
  
  class {utoken}_PUBLIC {class_name} {{
  
  public:
    {class_name}();
    int get_number() const;
  
  private:
  
    int number;
  
  }};
  
  }}
  
  '''
  
  lib_cpp_template = '''#include <{header_file}>
  
  namespace {namespace} {{
  
  {class_name}::{class_name}() {{
      number = 6;
  }}
  
  int {class_name}::get_number() const {{
    return number;
  }}
  
  }}
  '''
  
  lib_cpp_test_template = '''#include <{header_file}>
  #include <iostream>
  
  int main(int argc, char **argv) {{
      if(argc != 1) {{
          std::cout << argv[0] << " takes no arguments.\\n";
          return 1;
      }}
      {namespace}::{class_name} c;
      return c.get_number() != 6;
  }}
  '''
  
  lib_cpp_meson_template = '''project('{project_name}', 'cpp',
    version : '{version}',
    default_options : ['warning_level=3', 'cpp_std=c++14'])
  
  # These arguments are only used to build the shared library
  # not the executables that use the library.
  lib_args = ['-DBUILDING_{utoken}']
  
  shlib = shared_library('{lib_name}', '{source_file}',
    install : true,
    cpp_args : lib_args,
    gnu_symbol_visibility : 'hidden',
  )
  
  test_exe = executable('{test_exe_name}', '{test_source_file}',
    link_with : shlib)
  test('{test_name}', test_exe)
  
  # Make this library usable as a Meson subproject.
  {ltoken}_dep = declare_dependency(
    include_directories: include_directories('.'),
    link_with : shlib)
  
  # Make this library usable from the system's
  # package manager.
  install_headers('{header_file}', subdir : '{header_dir}')
  
  pkg_mod = import('pkgconfig')
  pkg_mod.generate(
    name : '{project_name}',
    filebase : '{ltoken}',
    description : 'Meson sample project.',
    subdirs : '{header_dir}',
    libraries : shlib,
    version : '{version}',
  )
  '''
  
  info_message = '''Sample project created. To build it run the
  following commands:
  
  meson builddir
  ninja -C builddir
  '''
  
  def create_exe_c_sample(project_name, project_version):
      lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
      source_name = lowercase_token + '.c'
      open(source_name, 'w').write(hello_c_template.format(project_name=project_name))
      open('meson.build', 'w').write(hello_c_meson_template.format(project_name=project_name,
                                                                   exe_name=lowercase_token,
                                                                   source_name=source_name,
                                                                   version=project_version))
  
  def create_lib_c_sample(project_name, version):
      lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
      uppercase_token = lowercase_token.upper()
      function_name = lowercase_token[0:3] + '_func'
      lib_h_name = lowercase_token + '.h'
      lib_c_name = lowercase_token + '.c'
      test_c_name = lowercase_token + '_test.c'
      kwargs = {'utoken': uppercase_token,
                'ltoken': lowercase_token,
                'header_dir': lowercase_token,
                'function_name': function_name,
                'header_file': lib_h_name,
                'source_file': lib_c_name,
                'test_source_file': test_c_name,
                'test_exe_name': lowercase_token,
                'project_name': project_name,
                'lib_name': lowercase_token,
                'test_name': lowercase_token,
                'version': version,
                }
      open(lib_h_name, 'w').write(lib_h_template.format(**kwargs))
      open(lib_c_name, 'w').write(lib_c_template.format(**kwargs))
      open(test_c_name, 'w').write(lib_c_test_template.format(**kwargs))
      open('meson.build', 'w').write(lib_c_meson_template.format(**kwargs))
  
  def create_exe_cpp_sample(project_name, project_version):
      lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
      source_name = lowercase_token + '.cpp'
      open(source_name, 'w').write(hello_cpp_template.format(project_name=project_name))
      open('meson.build', 'w').write(hello_cpp_meson_template.format(project_name=project_name,
                                                                     exe_name=lowercase_token,
                                                                     source_name=source_name,
                                                                     version=project_version))
  
  def create_lib_cpp_sample(project_name, version):
      lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
      uppercase_token = lowercase_token.upper()
      class_name = uppercase_token[0] + lowercase_token[1:]
      namespace = lowercase_token
      lib_h_name = lowercase_token + '.hpp'
      lib_c_name = lowercase_token + '.cpp'
      test_c_name = lowercase_token + '_test.cpp'
      kwargs = {'utoken': uppercase_token,
                'ltoken': lowercase_token,
                'header_dir': lowercase_token,
                'class_name': class_name,
                'namespace': namespace,
                'header_file': lib_h_name,
                'source_file': lib_c_name,
                'test_source_file': test_c_name,
                'test_exe_name': lowercase_token,
                'project_name': project_name,
                'lib_name': lowercase_token,
                'test_name': lowercase_token,
                'version': version,
                }
      open(lib_h_name, 'w').write(lib_hpp_template.format(**kwargs))
      open(lib_c_name, 'w').write(lib_cpp_template.format(**kwargs))
      open(test_c_name, 'w').write(lib_cpp_test_template.format(**kwargs))
      open('meson.build', 'w').write(lib_cpp_meson_template.format(**kwargs))
  
  def create_sample(options):
      if options.language == 'c':
          if options.type == 'executable':
              create_exe_c_sample(options.name, options.version)
          elif options.type == 'library':
              create_lib_c_sample(options.name, options.version)
          else:
              raise RuntimeError('Unreachable code')
      elif options.language == 'cpp':
          if options.type == 'executable':
              create_exe_cpp_sample(options.name, options.version)
          elif options.type == 'library':
              create_lib_cpp_sample(options.name, options.version)
          else:
              raise RuntimeError('Unreachable code')
      else:
          raise RuntimeError('Unreachable code')
      print(info_message)
  
  def autodetect_options(options, sample=False):
      if not options.name:
          options.name = os.path.basename(os.getcwd())
          if not re.match('[a-zA-Z_][a-zA-Z0-9]*', options.name) and sample:
              print('Name of current directory "{}" is not usable as a sample project name.\n'
                    'Specify a project name with --name.'.format(options.name))
              sys.exit(1)
          print('Using "{}" (name of current directory) as project name.'
                .format(options.name))
      if not options.executable:
          options.executable = options.name
          print('Using "{}" (project name) as name of executable to build.'
                .format(options.executable))
      if sample:
          # The rest of the autodetection is not applicable to generating sample projects.
          return
      if not options.srcfiles:
          srcfiles = []
          for f in os.listdir():
              if f.endswith('.cc') or f.endswith('.cpp') or f.endswith('.c'):
                  srcfiles.append(f)
          if not srcfiles:
              print("No recognizable source files found.\n"
                    "Run me in an empty directory to create a sample project.")
              sys.exit(1)
          options.srcfiles = srcfiles
          print("Detected source files: " + ' '.join(srcfiles))
      if not options.language:
          for f in options.srcfiles:
              if f.endswith('.cc') or f.endswith('.cpp'):
                  options.language = 'cpp'
                  break
              if f.endswith('.c'):
                  options.language = 'c'
                  break
          if not options.language:
              print("Can't autodetect language, please specify it with -l.")
              sys.exit(1)
          print("Detected language: " + options.language)
  
  meson_executable_template = '''project('{project_name}', '{language}',
    version : '{version}',
    default_options : [{default_options}])
  
  executable('{executable}',
             {sourcespec},{depspec}
             install : true)
  '''
  
  def create_meson_build(options):
      if options.type != 'executable':
          print('\nGenerating a meson.build file from existing sources is\n'
                'supported only for project type "executable".\n'
                'Run me in an empty directory to create a sample project.')
          sys.exit(1)
      default_options = ['warning_level=3']
      if options.language == 'cpp':
          # This shows how to set this very common option.
          default_options += ['cpp_std=c++14']
      # If we get a meson.build autoformatter one day, this code could
      # be simplified quite a bit.
      formatted_default_options = ', '.join("'{}'".format(x) for x in default_options)
      sourcespec = ',\n           '.join("'{}'".format(x) for x in options.srcfiles)
      depspec = ''
      if options.deps:
          depspec = '\n           dependencies : [\n              '
          depspec += ',\n              '.join("dependency('{}')".format(x)
                                              for x in options.deps.split(','))
          depspec += '],'
      content = meson_executable_template.format(project_name=options.name,
                                                 language=options.language,
                                                 version=options.version,
                                                 executable=options.executable,
                                                 sourcespec=sourcespec,
                                                 depspec=depspec,
                                                 default_options=formatted_default_options)
      open('meson.build', 'w').write(content)
      print('Generated meson.build file:\n\n' + content)
  
  def run(args):
      parser = argparse.ArgumentParser(prog='meson')
      parser.add_argument("srcfiles", metavar="sourcefile", nargs="*",
                          help="source files. default: all recognized files in current directory")
      parser.add_argument("-n", "--name", help="project name. default: name of current directory")
      parser.add_argument("-e", "--executable", help="executable name. default: project name")
      parser.add_argument("-d", "--deps", help="dependencies, comma-separated")
      parser.add_argument("-l", "--language", choices=['c', 'cpp'],
                          help="project language. default: autodetected based on source files")
      parser.add_argument("-b", "--build", help="build after generation", action='store_true')
      parser.add_argument("--builddir", help="directory for build", default='build')
      parser.add_argument("-f", "--force", action="store_true",
                          help="force overwrite of existing files and directories.")
      parser.add_argument('--type', default='executable',
                          choices=['executable', 'library'])
      parser.add_argument('--version', default='0.1')
      options = parser.parse_args(args)
      if len(glob('*')) == 0:
          autodetect_options(options, sample=True)
          if not options.language:
              print('Defaulting to generating a C language project.')
              options.language = 'c'
          create_sample(options)
      else:
          autodetect_options(options)
          if os.path.isfile('meson.build') and not options.force:
              print('meson.build already exists. Use --force to overwrite.')
              sys.exit(1)
          create_meson_build(options)
      if options.build:
          if os.path.isdir(options.builddir) and options.force:
              print('Build directory already exists, deleting it.')
              shutil.rmtree(options.builddir)
          print('Building...')
          cmd = mesonlib.meson_command + [options.builddir]
          err = subprocess.call(cmd)
          if err:
              sys.exit(1)
          cmd = [detect_ninja(), '-C', options.builddir]
          err = subprocess.call(cmd)
          if err:
              sys.exit(1)
      return 0
  
files: mesonbuild/minit.py
files: # Copyright 2013-2014 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 sys, pickle, os, shutil, subprocess, gzip, errno
  import shlex
  from glob import glob
  from .scripts import depfixer
  from .scripts import destdir_join
  from .mesonlib import is_windows, Popen_safe
  from .mtest import rebuild_all
  try:
      from __main__ import __file__ as main_file
  except ImportError:
      # Happens when running as meson.exe which is native Windows.
      # This is only used for pkexec which is not, so this is fine.
      main_file = None
  
  symlink_warning = '''Warning: trying to copy a symlink that points to a file. This will copy the file,
  but this will be changed in a future version of Meson to copy the symlink as is. Please update your
  build definitions so that it will not break when the change happens.'''
  
  selinux_updates = []
  
  def add_arguments(parser):
      parser.add_argument('-C', default='.', dest='wd',
                          help='directory to cd into before running')
      parser.add_argument('--no-rebuild', default=False, action='store_true',
                          help='Do not rebuild before installing.')
      parser.add_argument('--only-changed', default=False, action='store_true',
                          help='Only overwrite files that are older than the copied file.')
  
  class DirMaker:
      def __init__(self, lf):
          self.lf = lf
          self.dirs = []
  
      def makedirs(self, path, exist_ok=False):
          dirname = os.path.normpath(path)
          dirs = []
          while dirname != os.path.dirname(dirname):
              if not os.path.exists(dirname):
                  dirs.append(dirname)
              dirname = os.path.dirname(dirname)
          os.makedirs(path, exist_ok=exist_ok)
  
          # store the directories in creation order, with the parent directory
          # before the child directories. Future calls of makedir() will not
          # create the parent directories, so the last element in the list is
          # the last one to be created. That is the first one to be removed on
          # __exit__
          dirs.reverse()
          self.dirs += dirs
  
      def __enter__(self):
          return self
  
      def __exit__(self, type, value, traceback):
          self.dirs.reverse()
          for d in self.dirs:
              append_to_log(self.lf, d)
  
  def is_executable(path, follow_symlinks=False):
      '''Checks whether any of the "x" bits are set in the source file mode.'''
      return bool(os.stat(path, follow_symlinks=follow_symlinks).st_mode & 0o111)
  
  def append_to_log(lf, line):
      lf.write(line)
      if not line.endswith('\n'):
          lf.write('\n')
      lf.flush()
  
  def set_chown(path, user=None, group=None, dir_fd=None, follow_symlinks=True):
      # shutil.chown will call os.chown without passing all the parameters
      # and particularly follow_symlinks, thus we replace it temporary
      # with a lambda with all the parameters so that follow_symlinks will
      # be actually passed properly.
      # Not nice, but better than actually rewriting shutil.chown until
      # this python bug is fixed: https://bugs.python.org/issue18108
      real_os_chown = os.chown
      try:
          os.chown = lambda p, u, g: real_os_chown(p, u, g,
                                                   dir_fd=dir_fd,
                                                   follow_symlinks=follow_symlinks)
          shutil.chown(path, user, group)
      except:
          raise
      finally:
          os.chown = real_os_chown
  
  def set_chmod(path, mode, dir_fd=None, follow_symlinks=True):
      try:
          os.chmod(path, mode, dir_fd=dir_fd, follow_symlinks=follow_symlinks)
      except (NotImplementedError, OSError, SystemError) as e:
          if not os.path.islink(path):
              os.chmod(path, mode, dir_fd=dir_fd)
  
  def sanitize_permissions(path, umask):
      if umask is None:
          return
      new_perms = 0o777 if is_executable(path, follow_symlinks=False) else 0o666
      new_perms &= ~umask
      try:
          set_chmod(path, new_perms, follow_symlinks=False)
      except PermissionError as e:
          msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
          print(msg.format(path, new_perms, e.strerror))
  
  def set_mode(path, mode, default_umask):
      if mode is None or (mode.perms_s or mode.owner or mode.group) is None:
          # Just sanitize permissions with the default umask
          sanitize_permissions(path, default_umask)
          return
      # No chown() on Windows, and must set one of owner/group
      if not is_windows() and (mode.owner or mode.group) is not None:
          try:
              set_chown(path, mode.owner, mode.group, follow_symlinks=False)
          except PermissionError as e:
              msg = '{!r}: Unable to set owner {!r} and group {!r}: {}, ignoring...'
              print(msg.format(path, mode.owner, mode.group, e.strerror))
          except LookupError:
              msg = '{!r}: Non-existent owner {!r} or group {!r}: ignoring...'
              print(msg.format(path, mode.owner, mode.group))
          except OSError as e:
              if e.errno == errno.EINVAL:
                  msg = '{!r}: Non-existent numeric owner {!r} or group {!r}: ignoring...'
                  print(msg.format(path, mode.owner, mode.group))
              else:
                  raise
      # Must set permissions *after* setting owner/group otherwise the
      # setuid/setgid bits will get wiped by chmod
      # NOTE: On Windows you can set read/write perms; the rest are ignored
      if mode.perms_s is not None:
          try:
              set_chmod(path, mode.perms, follow_symlinks=False)
          except PermissionError as e:
              msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
              print(msg.format(path, mode.perms_s, e.strerror))
      else:
          sanitize_permissions(path, default_umask)
  
  def restore_selinux_contexts():
      '''
      Restores the SELinux context for files in @selinux_updates
  
      If $DESTDIR is set, do not warn if the call fails.
      '''
      try:
          subprocess.check_call(['selinuxenabled'])
      except (FileNotFoundError, PermissionError, subprocess.CalledProcessError) as e:
          # If we don't have selinux or selinuxenabled returned 1, failure
          # is ignored quietly.
          return
  
      if not shutil.which('restorecon'):
          # If we don't have restorecon, failure is ignored quietly.
          return
  
      with subprocess.Popen(['restorecon', '-F', '-f-', '-0'],
                            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
          out, err = proc.communicate(input=b'\0'.join(os.fsencode(f) for f in selinux_updates) + b'\0')
          if proc.returncode != 0 and not os.environ.get('DESTDIR'):
              print('Failed to restore SELinux context of installed files...',
                    'Standard output:', out.decode(),
                    'Standard error:', err.decode(), sep='\n')
  
  
  def get_destdir_path(d, path):
      if os.path.isabs(path):
          output = destdir_join(d.destdir, path)
      else:
          output = os.path.join(d.fullprefix, path)
      return output
  
  
  def check_for_stampfile(fname):
      '''Some languages e.g. Rust have output files
      whose names are not known at configure time.
      Check if this is the case and return the real
      file instead.'''
      if fname.endswith('.so') or fname.endswith('.dll'):
          if os.stat(fname).st_size == 0:
              (base, suffix) = os.path.splitext(fname)
              files = glob(base + '-*' + suffix)
              if len(files) > 1:
                  print("Stale dynamic library files in build dir. Can't install.")
                  sys.exit(1)
              if len(files) == 1:
                  return files[0]
      elif fname.endswith('.a') or fname.endswith('.lib'):
          if os.stat(fname).st_size == 0:
              (base, suffix) = os.path.splitext(fname)
              files = glob(base + '-*' + '.rlib')
              if len(files) > 1:
                  print("Stale static library files in build dir. Can't install.")
                  sys.exit(1)
              if len(files) == 1:
                  return files[0]
      return fname
  
  class Installer:
  
      def __init__(self, options, lf):
          self.options = options
          self.lf = lf
  
      def should_preserve_existing_file(self, from_file, to_file):
          if not self.options.only_changed:
              return False
          # Always replace danging symlinks
          if os.path.islink(from_file) and not os.path.isfile(from_file):
              return False
          from_time = os.stat(from_file).st_mtime
          to_time = os.stat(to_file).st_mtime
          return from_time <= to_time
  
      def do_copyfile(self, from_file, to_file):
          outdir = os.path.split(to_file)[0]
          if not os.path.isfile(from_file) and not os.path.islink(from_file):
              raise RuntimeError('Tried to install something that isn\'t a file:'
                                 '{!r}'.format(from_file))
          # copyfile fails if the target file already exists, so remove it to
          # allow overwriting a previous install. If the target is not a file, we
          # want to give a readable error.
          if os.path.exists(to_file):
              if not os.path.isfile(to_file):
                  raise RuntimeError('Destination {!r} already exists and is not '
                                     'a file'.format(to_file))
              if self.should_preserve_existing_file(from_file, to_file):
                  append_to_log(self.lf, '# Preserving old file %s\n' % to_file)
                  print('Preserving existing file %s.' % to_file)
                  return False
              os.remove(to_file)
          print('Installing %s to %s' % (from_file, outdir))
          if os.path.islink(from_file):
              if not os.path.exists(from_file):
                  # Dangling symlink. Replicate as is.
                  shutil.copy(from_file, outdir, follow_symlinks=False)
              else:
                  # Remove this entire branch when changing the behaviour to duplicate
                  # symlinks rather than copying what they point to.
                  print(symlink_warning)
                  shutil.copyfile(from_file, to_file)
                  shutil.copystat(from_file, to_file)
          else:
              shutil.copyfile(from_file, to_file)
              shutil.copystat(from_file, to_file)
          selinux_updates.append(to_file)
          append_to_log(self.lf, to_file)
          return True
  
      def do_copydir(self, data, src_dir, dst_dir, exclude, install_mode):
          '''
          Copies the contents of directory @src_dir into @dst_dir.
  
          For directory
              /foo/
                bar/
                  excluded
                  foobar
                file
          do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}) creates
              /dst/
                dir/
                  bar/
                    foobar
                  file
  
          Args:
              src_dir: str, absolute path to the source directory
              dst_dir: str, absolute path to the destination directory
              exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs),
                       each element of the set is a path relative to src_dir.
          '''
          if not os.path.isabs(src_dir):
              raise ValueError('src_dir must be absolute, got %s' % src_dir)
          if not os.path.isabs(dst_dir):
              raise ValueError('dst_dir must be absolute, got %s' % dst_dir)
          if exclude is not None:
              exclude_files, exclude_dirs = exclude
          else:
              exclude_files = exclude_dirs = set()
          for root, dirs, files in os.walk(src_dir):
              assert os.path.isabs(root)
              for d in dirs[:]:
                  abs_src = os.path.join(root, d)
                  filepart = os.path.relpath(abs_src, start=src_dir)
                  abs_dst = os.path.join(dst_dir, filepart)
                  # Remove these so they aren't visited by os.walk at all.
                  if filepart in exclude_dirs:
                      dirs.remove(d)
                      continue
                  if os.path.isdir(abs_dst):
                      continue
                  if os.path.exists(abs_dst):
                      print('Tried to copy directory %s but a file of that name already exists.' % abs_dst)
                      sys.exit(1)
                  data.dirmaker.makedirs(abs_dst)
                  shutil.copystat(abs_src, abs_dst)
                  sanitize_permissions(abs_dst, data.install_umask)
              for f in files:
                  abs_src = os.path.join(root, f)
                  filepart = os.path.relpath(abs_src, start=src_dir)
                  if filepart in exclude_files:
                      continue
                  abs_dst = os.path.join(dst_dir, filepart)
                  if os.path.isdir(abs_dst):
                      print('Tried to copy file %s but a directory of that name already exists.' % abs_dst)
                  if os.path.exists(abs_dst):
                      os.remove(abs_dst)
                  parent_dir = os.path.dirname(abs_dst)
                  if not os.path.isdir(parent_dir):
                      os.mkdir(parent_dir)
                      shutil.copystat(os.path.dirname(abs_src), parent_dir)
                  # FIXME: what about symlinks?
                  self.do_copyfile(abs_src, abs_dst)
                  set_mode(abs_dst, install_mode, data.install_umask)
                  append_to_log(self.lf, abs_dst)
  
      def do_install(self, datafilename):
          with open(datafilename, 'rb') as ifile:
              d = pickle.load(ifile)
          d.destdir = os.environ.get('DESTDIR', '')
          d.fullprefix = destdir_join(d.destdir, d.prefix)
  
          if d.install_umask is not None:
              os.umask(d.install_umask)
  
          try:
              d.dirmaker = DirMaker(self.lf)
              with d.dirmaker:
                  self.install_subdirs(d) # Must be first, because it needs to delete the old subtree.
                  self.install_targets(d)
                  self.install_headers(d)
                  self.install_man(d)
                  self.install_data(d)
                  restore_selinux_contexts()
                  self.run_install_script(d)
          except PermissionError:
              if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ:
                  print('Installation failed due to insufficient permissions.')
                  print('Attempting to use polkit to gain elevated privileges...')
                  os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:],
                            '-C', os.getcwd())
              else:
                  raise
  
      def install_subdirs(self, d):
          for (src_dir, dst_dir, mode, exclude) in d.install_subdirs:
              full_dst_dir = get_destdir_path(d, dst_dir)
              print('Installing subdir %s to %s' % (src_dir, full_dst_dir))
              d.dirmaker.makedirs(full_dst_dir, exist_ok=True)
              self.do_copydir(d, src_dir, full_dst_dir, exclude, mode)
  
      def install_data(self, d):
          for i in d.data:
              fullfilename = i[0]
              outfilename = get_destdir_path(d, i[1])
              mode = i[2]
              outdir = os.path.dirname(outfilename)
              d.dirmaker.makedirs(outdir, exist_ok=True)
              self.do_copyfile(fullfilename, outfilename)
              set_mode(outfilename, mode, d.install_umask)
  
      def install_man(self, d):
          for m in d.man:
              full_source_filename = m[0]
              outfilename = get_destdir_path(d, m[1])
              outdir = os.path.dirname(outfilename)
              d.dirmaker.makedirs(outdir, exist_ok=True)
              install_mode = m[2]
              if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'):
                  with open(outfilename, 'wb') as of:
                      with open(full_source_filename, 'rb') as sf:
                          # Set mtime and filename for reproducibility.
                          with gzip.GzipFile(fileobj=of, mode='wb', filename='', mtime=0) as gz:
                              gz.write(sf.read())
                  shutil.copystat(full_source_filename, outfilename)
                  print('Installing %s to %s' % (full_source_filename, outdir))
                  append_to_log(self.lf, outfilename)
              else:
                  self.do_copyfile(full_source_filename, outfilename)
              set_mode(outfilename, install_mode, d.install_umask)
  
      def install_headers(self, d):
          for t in d.headers:
              fullfilename = t[0]
              fname = os.path.basename(fullfilename)
              outdir = get_destdir_path(d, t[1])
              outfilename = os.path.join(outdir, fname)
              install_mode = t[2]
              d.dirmaker.makedirs(outdir, exist_ok=True)
              self.do_copyfile(fullfilename, outfilename)
              set_mode(outfilename, install_mode, d.install_umask)
  
      def run_install_script(self, d):
          env = {'MESON_SOURCE_ROOT': d.source_dir,
                 'MESON_BUILD_ROOT': d.build_dir,
                 'MESON_INSTALL_PREFIX': d.prefix,
                 'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix,
                 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]),
                 }
  
          child_env = os.environ.copy()
          child_env.update(env)
  
          for i in d.install_scripts:
              script = i['exe']
              args = i['args']
              name = ' '.join(script + args)
              print('Running custom install script {!r}'.format(name))
              try:
                  rc = subprocess.call(script + args, env=child_env)
                  if rc != 0:
                      sys.exit(rc)
              except OSError:
                  print('Failed to run install script {!r}'.format(name))
                  sys.exit(1)
  
      def install_targets(self, d):
          for t in d.targets:
              if not os.path.exists(t.fname):
                  # For example, import libraries of shared modules are optional
                  if t.optional:
                      print('File {!r} not found, skipping'.format(t.fname))
                      continue
                  else:
                      raise RuntimeError('File {!r} could not be found'.format(t.fname))
              fname = check_for_stampfile(t.fname)
              outdir = get_destdir_path(d, t.outdir)
              outname = os.path.join(outdir, os.path.basename(fname))
              final_path = os.path.join(d.prefix, t.outdir, os.path.basename(fname))
              aliases = t.aliases
              should_strip = t.strip
              install_rpath = t.install_rpath
              install_name_mappings = t.install_name_mappings
              install_mode = t.install_mode
              d.dirmaker.makedirs(outdir, exist_ok=True)
              if not os.path.exists(fname):
                  raise RuntimeError('File {!r} could not be found'.format(fname))
              elif os.path.isfile(fname):
                  self.do_copyfile(fname, outname)
                  set_mode(outname, install_mode, d.install_umask)
                  if should_strip and d.strip_bin is not None:
                      if fname.endswith('.jar'):
                          print('Not stripping jar target:', os.path.basename(fname))
                          continue
                      print('Stripping target {!r}'.format(fname))
                      ps, stdo, stde = Popen_safe(d.strip_bin + [outname])
                      if ps.returncode != 0:
                          print('Could not strip file.\n')
                          print('Stdout:\n%s\n' % stdo)
                          print('Stderr:\n%s\n' % stde)
                          sys.exit(1)
                  pdb_filename = os.path.splitext(fname)[0] + '.pdb'
                  if not should_strip and os.path.exists(pdb_filename):
                      pdb_outname = os.path.splitext(outname)[0] + '.pdb'
                      self.do_copyfile(pdb_filename, pdb_outname)
                      set_mode(pdb_outname, install_mode, d.install_umask)
              elif os.path.isdir(fname):
                  fname = os.path.join(d.build_dir, fname.rstrip('/'))
                  outname = os.path.join(outdir, os.path.basename(fname))
                  self.do_copydir(d, fname, outname, None, install_mode)
              else:
                  raise RuntimeError('Unknown file type for {!r}'.format(fname))
              printed_symlink_error = False
              for alias, to in aliases.items():
                  try:
                      symlinkfilename = os.path.join(outdir, alias)
                      try:
                          os.remove(symlinkfilename)
                      except FileNotFoundError:
                          pass
                      os.symlink(to, symlinkfilename)
                      append_to_log(self.lf, symlinkfilename)
                  except (NotImplementedError, OSError):
                      if not printed_symlink_error:
                          print("Symlink creation does not work on this platform. "
                                "Skipping all symlinking.")
                          printed_symlink_error = True
              if os.path.isfile(outname):
                  try:
                      depfixer.fix_rpath(outname, install_rpath, final_path,
                                         install_name_mappings, verbose=False)
                  except SystemExit as e:
                      if isinstance(e.code, int) and e.code == 0:
                          pass
                      else:
                          raise
  
  def run(opts):
      datafilename = 'meson-private/install.dat'
      private_dir = os.path.dirname(datafilename)
      log_dir = os.path.join(private_dir, '../meson-logs')
      if not os.path.exists(os.path.join(opts.wd, datafilename)):
          sys.exit('Install data not found. Run this command in build directory root.')
      log_dir = os.path.join(private_dir, '../meson-logs')
      if not opts.no_rebuild:
          if not rebuild_all(opts.wd):
              sys.exit(-1)
      os.chdir(opts.wd)
      with open(os.path.join(log_dir, 'install-log.txt'), 'w') as lf:
          installer = Installer(opts, lf)
          append_to_log(lf, '# List of files installed by Meson')
          append_to_log(lf, '# Does not contain files installed by custom scripts.')
          installer.do_install(datafilename)
      return 0
  
files: mesonbuild/minstall.py
files: # Copyright 2013-2014 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 sys, pickle, os, shutil, subprocess, gzip, errno
  import shlex
  import argparse
  from glob import glob
  from .scripts import depfixer
  from .scripts import destdir_join
  from .mesonlib import is_windows, Popen_safe
  from .mtest import rebuild_all
  try:
      from __main__ import __file__ as main_file
  except ImportError:
      # Happens when running as meson.exe which is native Windows.
      # This is only used for pkexec which is not, so this is fine.
      main_file = None
  
  symlink_warning = '''Warning: trying to copy a symlink that points to a file. This will copy the file,
  but this will be changed in a future version of Meson to copy the symlink as is. Please update your
  build definitions so that it will not break when the change happens.'''
  
  selinux_updates = []
  
  def buildparser():
      parser = argparse.ArgumentParser(prog='meson install')
      parser.add_argument('-C', default='.', dest='wd',
                          help='directory to cd into before running')
      parser.add_argument('--no-rebuild', default=False, action='store_true',
                          help='Do not rebuild before installing.')
      parser.add_argument('--only-changed', default=False, action='store_true',
                          help='Only overwrite files that are older than the copied file.')
      return parser
  
  class DirMaker:
      def __init__(self, lf):
          self.lf = lf
          self.dirs = []
  
      def makedirs(self, path, exist_ok=False):
          dirname = os.path.normpath(path)
          dirs = []
          while dirname != os.path.dirname(dirname):
              if not os.path.exists(dirname):
                  dirs.append(dirname)
              dirname = os.path.dirname(dirname)
          os.makedirs(path, exist_ok=exist_ok)
  
          # store the directories in creation order, with the parent directory
          # before the child directories. Future calls of makedir() will not
          # create the parent directories, so the last element in the list is
          # the last one to be created. That is the first one to be removed on
          # __exit__
          dirs.reverse()
          self.dirs += dirs
  
      def __enter__(self):
          return self
  
      def __exit__(self, type, value, traceback):
          self.dirs.reverse()
          for d in self.dirs:
              append_to_log(self.lf, d)
  
  def is_executable(path, follow_symlinks=False):
      '''Checks whether any of the "x" bits are set in the source file mode.'''
      return bool(os.stat(path, follow_symlinks=follow_symlinks).st_mode & 0o111)
  
  def append_to_log(lf, line):
      lf.write(line)
      if not line.endswith('\n'):
          lf.write('\n')
      lf.flush()
  
  def set_chown(path, user=None, group=None, dir_fd=None, follow_symlinks=True):
      # shutil.chown will call os.chown without passing all the parameters
      # and particularly follow_symlinks, thus we replace it temporary
      # with a lambda with all the parameters so that follow_symlinks will
      # be actually passed properly.
      # Not nice, but better than actually rewriting shutil.chown until
      # this python bug is fixed: https://bugs.python.org/issue18108
      real_os_chown = os.chown
      try:
          os.chown = lambda p, u, g: real_os_chown(p, u, g,
                                                   dir_fd=dir_fd,
                                                   follow_symlinks=follow_symlinks)
          shutil.chown(path, user, group)
      except:
          raise
      finally:
          os.chown = real_os_chown
  
  def set_chmod(path, mode, dir_fd=None, follow_symlinks=True):
      try:
          os.chmod(path, mode, dir_fd=dir_fd, follow_symlinks=follow_symlinks)
      except (NotImplementedError, OSError, SystemError) as e:
          if not os.path.islink(path):
              os.chmod(path, mode, dir_fd=dir_fd)
  
  def sanitize_permissions(path, umask):
      if umask is None:
          return
      new_perms = 0o777 if is_executable(path, follow_symlinks=False) else 0o666
      new_perms &= ~umask
      try:
          set_chmod(path, new_perms, follow_symlinks=False)
      except PermissionError as e:
          msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
          print(msg.format(path, new_perms, e.strerror))
  
  def set_mode(path, mode, default_umask):
      if mode is None or (mode.perms_s or mode.owner or mode.group) is None:
          # Just sanitize permissions with the default umask
          sanitize_permissions(path, default_umask)
          return
      # No chown() on Windows, and must set one of owner/group
      if not is_windows() and (mode.owner or mode.group) is not None:
          try:
              set_chown(path, mode.owner, mode.group, follow_symlinks=False)
          except PermissionError as e:
              msg = '{!r}: Unable to set owner {!r} and group {!r}: {}, ignoring...'
              print(msg.format(path, mode.owner, mode.group, e.strerror))
          except LookupError:
              msg = '{!r}: Non-existent owner {!r} or group {!r}: ignoring...'
              print(msg.format(path, mode.owner, mode.group))
          except OSError as e:
              if e.errno == errno.EINVAL:
                  msg = '{!r}: Non-existent numeric owner {!r} or group {!r}: ignoring...'
                  print(msg.format(path, mode.owner, mode.group))
              else:
                  raise
      # Must set permissions *after* setting owner/group otherwise the
      # setuid/setgid bits will get wiped by chmod
      # NOTE: On Windows you can set read/write perms; the rest are ignored
      if mode.perms_s is not None:
          try:
              set_chmod(path, mode.perms, follow_symlinks=False)
          except PermissionError as e:
              msg = '{!r}: Unable to set permissions {!r}: {}, ignoring...'
              print(msg.format(path, mode.perms_s, e.strerror))
      else:
          sanitize_permissions(path, default_umask)
  
  def restore_selinux_contexts():
      '''
      Restores the SELinux context for files in @selinux_updates
  
      If $DESTDIR is set, do not warn if the call fails.
      '''
      try:
          subprocess.check_call(['selinuxenabled'])
      except (FileNotFoundError, PermissionError, subprocess.CalledProcessError) as e:
          # If we don't have selinux or selinuxenabled returned 1, failure
          # is ignored quietly.
          return
  
      if not shutil.which('restorecon'):
          # If we don't have restorecon, failure is ignored quietly.
          return
  
      with subprocess.Popen(['restorecon', '-F', '-f-', '-0'],
                            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
          out, err = proc.communicate(input=b'\0'.join(os.fsencode(f) for f in selinux_updates) + b'\0')
          if proc.returncode != 0 and not os.environ.get('DESTDIR'):
              print('Failed to restore SELinux context of installed files...',
                    'Standard output:', out.decode(),
                    'Standard error:', err.decode(), sep='\n')
  
  
  def get_destdir_path(d, path):
      if os.path.isabs(path):
          output = destdir_join(d.destdir, path)
      else:
          output = os.path.join(d.fullprefix, path)
      return output
  
  
  def check_for_stampfile(fname):
      '''Some languages e.g. Rust have output files
      whose names are not known at configure time.
      Check if this is the case and return the real
      file instead.'''
      if fname.endswith('.so') or fname.endswith('.dll'):
          if os.stat(fname).st_size == 0:
              (base, suffix) = os.path.splitext(fname)
              files = glob(base + '-*' + suffix)
              if len(files) > 1:
                  print("Stale dynamic library files in build dir. Can't install.")
                  sys.exit(1)
              if len(files) == 1:
                  return files[0]
      elif fname.endswith('.a') or fname.endswith('.lib'):
          if os.stat(fname).st_size == 0:
              (base, suffix) = os.path.splitext(fname)
              files = glob(base + '-*' + '.rlib')
              if len(files) > 1:
                  print("Stale static library files in build dir. Can't install.")
                  sys.exit(1)
              if len(files) == 1:
                  return files[0]
      return fname
  
  class Installer:
  
      def __init__(self, options, lf):
          self.options = options
          self.lf = lf
  
      def should_preserve_existing_file(self, from_file, to_file):
          if not self.options.only_changed:
              return False
          # Always replace danging symlinks
          if os.path.islink(from_file) and not os.path.isfile(from_file):
              return False
          from_time = os.stat(from_file).st_mtime
          to_time = os.stat(to_file).st_mtime
          return from_time <= to_time
  
      def do_copyfile(self, from_file, to_file):
          outdir = os.path.split(to_file)[0]
          if not os.path.isfile(from_file) and not os.path.islink(from_file):
              raise RuntimeError('Tried to install something that isn\'t a file:'
                                 '{!r}'.format(from_file))
          # copyfile fails if the target file already exists, so remove it to
          # allow overwriting a previous install. If the target is not a file, we
          # want to give a readable error.
          if os.path.exists(to_file):
              if not os.path.isfile(to_file):
                  raise RuntimeError('Destination {!r} already exists and is not '
                                     'a file'.format(to_file))
              if self.should_preserve_existing_file(from_file, to_file):
                  append_to_log(self.lf, '# Preserving old file %s\n' % to_file)
                  print('Preserving existing file %s.' % to_file)
                  return False
              os.remove(to_file)
          print('Installing %s to %s' % (from_file, outdir))
          if os.path.islink(from_file):
              if not os.path.exists(from_file):
                  # Dangling symlink. Replicate as is.
                  shutil.copy(from_file, outdir, follow_symlinks=False)
              else:
                  # Remove this entire branch when changing the behaviour to duplicate
                  # symlinks rather than copying what they point to.
                  print(symlink_warning)
                  shutil.copyfile(from_file, to_file)
                  shutil.copystat(from_file, to_file)
          else:
              shutil.copyfile(from_file, to_file)
              shutil.copystat(from_file, to_file)
          selinux_updates.append(to_file)
          append_to_log(self.lf, to_file)
          return True
  
      def do_copydir(self, data, src_dir, dst_dir, exclude, install_mode):
          '''
          Copies the contents of directory @src_dir into @dst_dir.
  
          For directory
              /foo/
                bar/
                  excluded
                  foobar
                file
          do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}) creates
              /dst/
                dir/
                  bar/
                    foobar
                  file
  
          Args:
              src_dir: str, absolute path to the source directory
              dst_dir: str, absolute path to the destination directory
              exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs),
                       each element of the set is a path relative to src_dir.
          '''
          if not os.path.isabs(src_dir):
              raise ValueError('src_dir must be absolute, got %s' % src_dir)
          if not os.path.isabs(dst_dir):
              raise ValueError('dst_dir must be absolute, got %s' % dst_dir)
          if exclude is not None:
              exclude_files, exclude_dirs = exclude
          else:
              exclude_files = exclude_dirs = set()
          for root, dirs, files in os.walk(src_dir):
              assert os.path.isabs(root)
              for d in dirs[:]:
                  abs_src = os.path.join(root, d)
                  filepart = os.path.relpath(abs_src, start=src_dir)
                  abs_dst = os.path.join(dst_dir, filepart)
                  # Remove these so they aren't visited by os.walk at all.
                  if filepart in exclude_dirs:
                      dirs.remove(d)
                      continue
                  if os.path.isdir(abs_dst):
                      continue
                  if os.path.exists(abs_dst):
                      print('Tried to copy directory %s but a file of that name already exists.' % abs_dst)
                      sys.exit(1)
                  data.dirmaker.makedirs(abs_dst)
                  shutil.copystat(abs_src, abs_dst)
                  sanitize_permissions(abs_dst, data.install_umask)
              for f in files:
                  abs_src = os.path.join(root, f)
                  filepart = os.path.relpath(abs_src, start=src_dir)
                  if filepart in exclude_files:
                      continue
                  abs_dst = os.path.join(dst_dir, filepart)
                  if os.path.isdir(abs_dst):
                      print('Tried to copy file %s but a directory of that name already exists.' % abs_dst)
                  if os.path.exists(abs_dst):
                      os.remove(abs_dst)
                  parent_dir = os.path.dirname(abs_dst)
                  if not os.path.isdir(parent_dir):
                      os.mkdir(parent_dir)
                      shutil.copystat(os.path.dirname(abs_src), parent_dir)
                  # FIXME: what about symlinks?
                  self.do_copyfile(abs_src, abs_dst)
                  set_mode(abs_dst, install_mode, data.install_umask)
                  append_to_log(self.lf, abs_dst)
  
      def do_install(self, datafilename):
          with open(datafilename, 'rb') as ifile:
              d = pickle.load(ifile)
          d.destdir = os.environ.get('DESTDIR', '')
          d.fullprefix = destdir_join(d.destdir, d.prefix)
  
          if d.install_umask is not None:
              os.umask(d.install_umask)
  
          try:
              d.dirmaker = DirMaker(self.lf)
              with d.dirmaker:
                  self.install_subdirs(d) # Must be first, because it needs to delete the old subtree.
                  self.install_targets(d)
                  self.install_headers(d)
                  self.install_man(d)
                  self.install_data(d)
                  restore_selinux_contexts()
                  self.run_install_script(d)
          except PermissionError:
              if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ:
                  print('Installation failed due to insufficient permissions.')
                  print('Attempting to use polkit to gain elevated privileges...')
                  os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:],
                            '-C', os.getcwd())
              else:
                  raise
  
      def install_subdirs(self, d):
          for (src_dir, dst_dir, mode, exclude) in d.install_subdirs:
              full_dst_dir = get_destdir_path(d, dst_dir)
              print('Installing subdir %s to %s' % (src_dir, full_dst_dir))
              d.dirmaker.makedirs(full_dst_dir, exist_ok=True)
              self.do_copydir(d, src_dir, full_dst_dir, exclude, mode)
  
      def install_data(self, d):
          for i in d.data:
              fullfilename = i[0]
              outfilename = get_destdir_path(d, i[1])
              mode = i[2]
              outdir = os.path.dirname(outfilename)
              d.dirmaker.makedirs(outdir, exist_ok=True)
              self.do_copyfile(fullfilename, outfilename)
              set_mode(outfilename, mode, d.install_umask)
  
      def install_man(self, d):
          for m in d.man:
              full_source_filename = m[0]
              outfilename = get_destdir_path(d, m[1])
              outdir = os.path.dirname(outfilename)
              d.dirmaker.makedirs(outdir, exist_ok=True)
              install_mode = m[2]
              if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'):
                  with open(outfilename, 'wb') as of:
                      with open(full_source_filename, 'rb') as sf:
                          # Set mtime and filename for reproducibility.
                          with gzip.GzipFile(fileobj=of, mode='wb', filename='', mtime=0) as gz:
                              gz.write(sf.read())
                  shutil.copystat(full_source_filename, outfilename)
                  print('Installing %s to %s' % (full_source_filename, outdir))
                  append_to_log(self.lf, outfilename)
              else:
                  self.do_copyfile(full_source_filename, outfilename)
              set_mode(outfilename, install_mode, d.install_umask)
  
      def install_headers(self, d):
          for t in d.headers:
              fullfilename = t[0]
              fname = os.path.basename(fullfilename)
              outdir = get_destdir_path(d, t[1])
              outfilename = os.path.join(outdir, fname)
              install_mode = t[2]
              d.dirmaker.makedirs(outdir, exist_ok=True)
              self.do_copyfile(fullfilename, outfilename)
              set_mode(outfilename, install_mode, d.install_umask)
  
      def run_install_script(self, d):
          env = {'MESON_SOURCE_ROOT': d.source_dir,
                 'MESON_BUILD_ROOT': d.build_dir,
                 'MESON_INSTALL_PREFIX': d.prefix,
                 'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix,
                 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]),
                 }
  
          child_env = os.environ.copy()
          child_env.update(env)
  
          for i in d.install_scripts:
              script = i['exe']
              args = i['args']
              name = ' '.join(script + args)
              print('Running custom install script {!r}'.format(name))
              try:
                  rc = subprocess.call(script + args, env=child_env)
                  if rc != 0:
                      sys.exit(rc)
              except OSError:
                  print('Failed to run install script {!r}'.format(name))
                  sys.exit(1)
  
      def install_targets(self, d):
          for t in d.targets:
              if not os.path.exists(t.fname):
                  # For example, import libraries of shared modules are optional
                  if t.optional:
                      print('File {!r} not found, skipping'.format(t.fname))
                      continue
                  else:
                      raise RuntimeError('File {!r} could not be found'.format(t.fname))
              fname = check_for_stampfile(t.fname)
              outdir = get_destdir_path(d, t.outdir)
              outname = os.path.join(outdir, os.path.basename(fname))
              final_path = os.path.join(d.prefix, t.outdir, os.path.basename(fname))
              aliases = t.aliases
              should_strip = t.strip
              install_rpath = t.install_rpath
              install_name_mappings = t.install_name_mappings
              install_mode = t.install_mode
              d.dirmaker.makedirs(outdir, exist_ok=True)
              if not os.path.exists(fname):
                  raise RuntimeError('File {!r} could not be found'.format(fname))
              elif os.path.isfile(fname):
                  self.do_copyfile(fname, outname)
                  set_mode(outname, install_mode, d.install_umask)
                  if should_strip and d.strip_bin is not None:
                      if fname.endswith('.jar'):
                          print('Not stripping jar target:', os.path.basename(fname))
                          continue
                      print('Stripping target {!r}'.format(fname))
                      ps, stdo, stde = Popen_safe(d.strip_bin + [outname])
                      if ps.returncode != 0:
                          print('Could not strip file.\n')
                          print('Stdout:\n%s\n' % stdo)
                          print('Stderr:\n%s\n' % stde)
                          sys.exit(1)
                  pdb_filename = os.path.splitext(fname)[0] + '.pdb'
                  if not should_strip and os.path.exists(pdb_filename):
                      pdb_outname = os.path.splitext(outname)[0] + '.pdb'
                      self.do_copyfile(pdb_filename, pdb_outname)
                      set_mode(pdb_outname, install_mode, d.install_umask)
              elif os.path.isdir(fname):
                  fname = os.path.join(d.build_dir, fname.rstrip('/'))
                  outname = os.path.join(outdir, os.path.basename(fname))
                  self.do_copydir(d, fname, outname, None, install_mode)
              else:
                  raise RuntimeError('Unknown file type for {!r}'.format(fname))
              printed_symlink_error = False
              for alias, to in aliases.items():
                  try:
                      symlinkfilename = os.path.join(outdir, alias)
                      try:
                          os.remove(symlinkfilename)
                      except FileNotFoundError:
                          pass
                      os.symlink(to, symlinkfilename)
                      append_to_log(self.lf, symlinkfilename)
                  except (NotImplementedError, OSError):
                      if not printed_symlink_error:
                          print("Symlink creation does not work on this platform. "
                                "Skipping all symlinking.")
                          printed_symlink_error = True
              if os.path.isfile(outname):
                  try:
                      depfixer.fix_rpath(outname, install_rpath, final_path,
                                         install_name_mappings, verbose=False)
                  except SystemExit as e:
                      if isinstance(e.code, int) and e.code == 0:
                          pass
                      else:
                          raise
  
  def run(args):
      parser = buildparser()
      opts = parser.parse_args(args)
      datafilename = 'meson-private/install.dat'
      private_dir = os.path.dirname(datafilename)
      log_dir = os.path.join(private_dir, '../meson-logs')
      if not os.path.exists(os.path.join(opts.wd, datafilename)):
          sys.exit('Install data not found. Run this command in build directory root.')
      log_dir = os.path.join(private_dir, '../meson-logs')
      if not opts.no_rebuild:
          if not rebuild_all(opts.wd):
              sys.exit(-1)
      os.chdir(opts.wd)
      with open(os.path.join(log_dir, 'install-log.txt'), 'w') as lf:
          installer = Installer(opts, lf)
          append_to_log(lf, '# List of files installed by Meson')
          append_to_log(lf, '# Does not contain files installed by custom scripts.')
          installer.do_install(datafilename)
      return 0
  
  if __name__ == '__main__':
      sys.exit(run(sys.argv[1:]))
  
files: mesonbuild/minstall.py
files: # Copyright 2014-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.
  
  """This is a helper script for IDE developers. It allows you to
  extract information such as list of targets, files, compiler flags,
  tests and so on. All output is in JSON for simple parsing.
  
  Currently only works for the Ninja backend. Others use generated
  project files and don't need this info."""
  
  import json
  from . import build, mtest, coredata as cdata
  from . import mesonlib
  from .backend import ninjabackend
  import sys, os
  import pathlib
  
  def add_arguments(parser):
      parser.add_argument('--targets', action='store_true', dest='list_targets', default=False,
                          help='List top level targets.')
      parser.add_argument('--installed', action='store_true', dest='list_installed', default=False,
                          help='List all installed files and directories.')
      parser.add_argument('--target-files', action='store', dest='target_files', default=None,
                          help='List source files for a given target.')
      parser.add_argument('--buildsystem-files', action='store_true', dest='buildsystem_files', default=False,
                          help='List files that make up the build system.')
      parser.add_argument('--buildoptions', action='store_true', dest='buildoptions', default=False,
                          help='List all build options.')
      parser.add_argument('--tests', action='store_true', dest='tests', default=False,
                          help='List all unit tests.')
      parser.add_argument('--benchmarks', action='store_true', dest='benchmarks', default=False,
                          help='List all benchmarks.')
      parser.add_argument('--dependencies', action='store_true', dest='dependencies', default=False,
                          help='List external dependencies.')
      parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False,
                          help='Information about projects.')
      parser.add_argument('builddir', nargs='?', default='.', help='The build directory')
  
  def determine_installed_path(target, installdata):
      install_target = None
      for i in installdata.targets:
          if os.path.basename(i.fname) == target.get_filename(): # FIXME, might clash due to subprojects.
              install_target = i
              break
      if install_target is None:
          raise RuntimeError('Something weird happened. File a bug.')
      outname = os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))
      # Normalize the path by using os.path.sep consistently, etc.
      # Does not change the effective path.
      return str(pathlib.PurePath(outname))
  
  
  def list_installed(installdata):
      res = {}
      if installdata is not None:
          for t in installdata.targets:
              res[os.path.join(installdata.build_dir, t.fname)] = \
                  os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname))
          for path, installpath, unused_prefix in installdata.data:
              res[path] = os.path.join(installdata.prefix, installpath)
          for path, installdir, unused_custom_install_mode in installdata.headers:
              res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path))
          for path, installpath, unused_custom_install_mode in installdata.man:
              res[path] = os.path.join(installdata.prefix, installpath)
      print(json.dumps(res))
  
  
  def list_targets(coredata, builddata, installdata):
      tlist = []
      for (idname, target) in builddata.get_targets().items():
          t = {'name': target.get_basename(), 'id': idname}
          fname = target.get_filename()
          if isinstance(fname, list):
              fname = [os.path.join(target.subdir, x) for x in fname]
          else:
              fname = os.path.join(target.subdir, fname)
          t['filename'] = fname
          if isinstance(target, build.Executable):
              typename = 'executable'
          elif isinstance(target, build.SharedLibrary):
              typename = 'shared library'
          elif isinstance(target, build.StaticLibrary):
              typename = 'static library'
          elif isinstance(target, build.CustomTarget):
              typename = 'custom'
          elif isinstance(target, build.RunTarget):
              typename = 'run'
          else:
              typename = 'unknown'
          t['type'] = typename
          if installdata and target.should_install():
              t['installed'] = True
              t['install_filename'] = determine_installed_path(target, installdata)
          else:
              t['installed'] = False
          t['build_by_default'] = target.build_by_default
          tlist.append(t)
      print(json.dumps(tlist))
  
  def list_target_files(target_name, coredata, builddata):
      try:
          t = builddata.targets[target_name]
          sources = t.sources + t.extra_files
      except KeyError:
          print("Unknown target %s." % target_name)
          sys.exit(1)
      out = []
      for i in sources:
          if isinstance(i, mesonlib.File):
              i = os.path.join(i.subdir, i.fname)
          out.append(i)
      print(json.dumps(out))
  
  def list_buildoptions(coredata, builddata):
      optlist = []
      add_keys(optlist, coredata.user_options)
      add_keys(optlist, coredata.compiler_options)
      add_keys(optlist, coredata.base_options)
      add_keys(optlist, coredata.builtins)
      print(json.dumps(optlist))
  
  def add_keys(optlist, options):
      keys = list(options.keys())
      keys.sort()
      for key in keys:
          opt = options[key]
          optdict = {'name': key, 'value': opt.value}
          if isinstance(opt, cdata.UserStringOption):
              typestr = 'string'
          elif isinstance(opt, cdata.UserBooleanOption):
              typestr = 'boolean'
          elif isinstance(opt, cdata.UserComboOption):
              optdict['choices'] = opt.choices
              typestr = 'combo'
          elif isinstance(opt, cdata.UserIntegerOption):
              typestr = 'integer'
          elif isinstance(opt, cdata.UserArrayOption):
              typestr = 'array'
          else:
              raise RuntimeError("Unknown option type")
          optdict['type'] = typestr
          optdict['description'] = opt.description
          optlist.append(optdict)
  
  def list_buildsystem_files(builddata):
      src_dir = builddata.environment.get_source_dir()
      # I feel dirty about this. But only slightly.
      filelist = []
      for root, _, files in os.walk(src_dir):
          for f in files:
              if f == 'meson.build' or f == 'meson_options.txt':
                  filelist.append(os.path.relpath(os.path.join(root, f), src_dir))
      print(json.dumps(filelist))
  
  def list_deps(coredata):
      result = []
      for d in coredata.deps.values():
          if d.found():
              result += [{'name': d.name,
                          'compile_args': d.get_compile_args(),
                          'link_args': d.get_link_args()}]
      print(json.dumps(result))
  
  def list_tests(testdata):
      result = []
      for t in testdata:
          to = {}
          if isinstance(t.fname, str):
              fname = [t.fname]
          else:
              fname = t.fname
          to['cmd'] = fname + t.cmd_args
          if isinstance(t.env, build.EnvironmentVariables):
              to['env'] = t.env.get_env(os.environ)
          else:
              to['env'] = t.env
          to['name'] = t.name
          to['workdir'] = t.workdir
          to['timeout'] = t.timeout
          to['suite'] = t.suite
          to['is_parallel'] = t.is_parallel
          result.append(to)
      print(json.dumps(result))
  
  def list_projinfo(builddata):
      result = {'name': builddata.project_name, 'version': builddata.project_version}
      subprojects = []
      for k, v in builddata.subprojects.items():
          c = {'name': k,
               'version': v}
          subprojects.append(c)
      result['subprojects'] = subprojects
      print(json.dumps(result))
  
  def run(options):
      datadir = 'meson-private'
      if options.builddir is not None:
          datadir = os.path.join(options.builddir, datadir)
      if not os.path.isdir(datadir):
          print('Current directory is not a build dir. Please specify it or '
                'change the working directory to it.')
          return 1
  
      coredata = cdata.load(options.builddir)
      builddata = build.load(options.builddir)
      testdata = mtest.load_tests(options.builddir)
      benchmarkdata = mtest.load_benchmarks(options.builddir)
  
      # Install data is only available with the Ninja backend
      try:
          installdata = ninjabackend.load(options.builddir)
      except FileNotFoundError:
          installdata = None
  
      if options.list_targets:
          list_targets(coredata, builddata, installdata)
      elif options.list_installed:
          list_installed(installdata)
      elif options.target_files is not None:
          list_target_files(options.target_files, coredata, builddata)
      elif options.buildsystem_files:
          list_buildsystem_files(builddata)
      elif options.buildoptions:
          list_buildoptions(coredata, builddata)
      elif options.tests:
          list_tests(testdata)
      elif options.benchmarks:
          list_tests(benchmarkdata)
      elif options.dependencies:
          list_deps(coredata)
      elif options.projectinfo:
          list_projinfo(builddata)
      else:
          print('No command specified')
          return 1
      return 0
  
files: mesonbuild/mintro.py
files: # Copyright 2014-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.
  
  """This is a helper script for IDE developers. It allows you to
  extract information such as list of targets, files, compiler flags,
  tests and so on. All output is in JSON for simple parsing.
  
  Currently only works for the Ninja backend. Others use generated
  project files and don't need this info."""
  
  import json
  from . import build, mtest, coredata as cdata
  from . import mesonlib
  from .backend import ninjabackend
  import argparse
  import sys, os
  import pathlib
  
  def buildparser():
      parser = argparse.ArgumentParser(prog='meson introspect')
      parser.add_argument('--targets', action='store_true', dest='list_targets', default=False,
                          help='List top level targets.')
      parser.add_argument('--installed', action='store_true', dest='list_installed', default=False,
                          help='List all installed files and directories.')
      parser.add_argument('--target-files', action='store', dest='target_files', default=None,
                          help='List source files for a given target.')
      parser.add_argument('--buildsystem-files', action='store_true', dest='buildsystem_files', default=False,
                          help='List files that make up the build system.')
      parser.add_argument('--buildoptions', action='store_true', dest='buildoptions', default=False,
                          help='List all build options.')
      parser.add_argument('--tests', action='store_true', dest='tests', default=False,
                          help='List all unit tests.')
      parser.add_argument('--benchmarks', action='store_true', dest='benchmarks', default=False,
                          help='List all benchmarks.')
      parser.add_argument('--dependencies', action='store_true', dest='dependencies', default=False,
                          help='List external dependencies.')
      parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False,
                          help='Information about projects.')
      parser.add_argument('builddir', nargs='?', default='.', help='The build directory')
      return parser
  
  def determine_installed_path(target, installdata):
      install_target = None
      for i in installdata.targets:
          if os.path.basename(i.fname) == target.get_filename(): # FIXME, might clash due to subprojects.
              install_target = i
              break
      if install_target is None:
          raise RuntimeError('Something weird happened. File a bug.')
      outname = os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))
      # Normalize the path by using os.path.sep consistently, etc.
      # Does not change the effective path.
      return str(pathlib.PurePath(outname))
  
  
  def list_installed(installdata):
      res = {}
      if installdata is not None:
          for t in installdata.targets:
              res[os.path.join(installdata.build_dir, t.fname)] = \
                  os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname))
          for path, installpath, unused_prefix in installdata.data:
              res[path] = os.path.join(installdata.prefix, installpath)
          for path, installdir, unused_custom_install_mode in installdata.headers:
              res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path))
          for path, installpath, unused_custom_install_mode in installdata.man:
              res[path] = os.path.join(installdata.prefix, installpath)
      print(json.dumps(res))
  
  
  def list_targets(coredata, builddata, installdata):
      tlist = []
      for (idname, target) in builddata.get_targets().items():
          t = {'name': target.get_basename(), 'id': idname}
          fname = target.get_filename()
          if isinstance(fname, list):
              fname = [os.path.join(target.subdir, x) for x in fname]
          else:
              fname = os.path.join(target.subdir, fname)
          t['filename'] = fname
          if isinstance(target, build.Executable):
              typename = 'executable'
          elif isinstance(target, build.SharedLibrary):
              typename = 'shared library'
          elif isinstance(target, build.StaticLibrary):
              typename = 'static library'
          elif isinstance(target, build.CustomTarget):
              typename = 'custom'
          elif isinstance(target, build.RunTarget):
              typename = 'run'
          else:
              typename = 'unknown'
          t['type'] = typename
          if installdata and target.should_install():
              t['installed'] = True
              t['install_filename'] = determine_installed_path(target, installdata)
          else:
              t['installed'] = False
          t['build_by_default'] = target.build_by_default
          tlist.append(t)
      print(json.dumps(tlist))
  
  def list_target_files(target_name, coredata, builddata):
      try:
          t = builddata.targets[target_name]
          sources = t.sources + t.extra_files
      except KeyError:
          print("Unknown target %s." % target_name)
          sys.exit(1)
      out = []
      for i in sources:
          if isinstance(i, mesonlib.File):
              i = os.path.join(i.subdir, i.fname)
          out.append(i)
      print(json.dumps(out))
  
  def list_buildoptions(coredata, builddata):
      optlist = []
      add_keys(optlist, coredata.user_options)
      add_keys(optlist, coredata.compiler_options)
      add_keys(optlist, coredata.base_options)
      add_keys(optlist, coredata.builtins)
      print(json.dumps(optlist))
  
  def add_keys(optlist, options):
      keys = list(options.keys())
      keys.sort()
      for key in keys:
          opt = options[key]
          optdict = {'name': key, 'value': opt.value}
          if isinstance(opt, cdata.UserStringOption):
              typestr = 'string'
          elif isinstance(opt, cdata.UserBooleanOption):
              typestr = 'boolean'
          elif isinstance(opt, cdata.UserComboOption):
              optdict['choices'] = opt.choices
              typestr = 'combo'
          elif isinstance(opt, cdata.UserIntegerOption):
              typestr = 'integer'
          elif isinstance(opt, cdata.UserArrayOption):
              typestr = 'array'
          else:
              raise RuntimeError("Unknown option type")
          optdict['type'] = typestr
          optdict['description'] = opt.description
          optlist.append(optdict)
  
  def list_buildsystem_files(builddata):
      src_dir = builddata.environment.get_source_dir()
      # I feel dirty about this. But only slightly.
      filelist = []
      for root, _, files in os.walk(src_dir):
          for f in files:
              if f == 'meson.build' or f == 'meson_options.txt':
                  filelist.append(os.path.relpath(os.path.join(root, f), src_dir))
      print(json.dumps(filelist))
  
  def list_deps(coredata):
      result = []
      for d in coredata.deps.values():
          if d.found():
              result += [{'name': d.name,
                          'compile_args': d.get_compile_args(),
                          'link_args': d.get_link_args()}]
      print(json.dumps(result))
  
  def list_tests(testdata):
      result = []
      for t in testdata:
          to = {}
          if isinstance(t.fname, str):
              fname = [t.fname]
          else:
              fname = t.fname
          to['cmd'] = fname + t.cmd_args
          if isinstance(t.env, build.EnvironmentVariables):
              to['env'] = t.env.get_env(os.environ)
          else:
              to['env'] = t.env
          to['name'] = t.name
          to['workdir'] = t.workdir
          to['timeout'] = t.timeout
          to['suite'] = t.suite
          to['is_parallel'] = t.is_parallel
          result.append(to)
      print(json.dumps(result))
  
  def list_projinfo(builddata):
      result = {'name': builddata.project_name, 'version': builddata.project_version}
      subprojects = []
      for k, v in builddata.subprojects.items():
          c = {'name': k,
               'version': v}
          subprojects.append(c)
      result['subprojects'] = subprojects
      print(json.dumps(result))
  
  def run(args):
      datadir = 'meson-private'
      options = buildparser().parse_args(args)
      if options.builddir is not None:
          datadir = os.path.join(options.builddir, datadir)
      if not os.path.isdir(datadir):
          print('Current directory is not a build dir. Please specify it or '
                'change the working directory to it.')
          return 1
  
      coredata = cdata.load(options.builddir)
      builddata = build.load(options.builddir)
      testdata = mtest.load_tests(options.builddir)
      benchmarkdata = mtest.load_benchmarks(options.builddir)
  
      # Install data is only available with the Ninja backend
      try:
          installdata = ninjabackend.load(options.builddir)
      except FileNotFoundError:
          installdata = None
  
      if options.list_targets:
          list_targets(coredata, builddata, installdata)
      elif options.list_installed:
          list_installed(installdata)
      elif options.target_files is not None:
          list_target_files(options.target_files, coredata, builddata)
      elif options.buildsystem_files:
          list_buildsystem_files(builddata)
      elif options.buildoptions:
          list_buildoptions(coredata, builddata)
      elif options.tests:
          list_tests(testdata)
      elif options.benchmarks:
          list_tests(benchmarkdata)
      elif options.dependencies:
          list_deps(coredata)
      elif options.projectinfo:
          list_projinfo(builddata)
      else:
          print('No command specified')
          return 1
      return 0
  
files: mesonbuild/mintro.py
files: # Copyright 2016-2018 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 time
  import sys, stat
  import datetime
  import os.path
  import platform
  import cProfile as profile
  import argparse
  
  from . import environment, interpreter, mesonlib
  from . import build
  from . import mlog, coredata
  from .mesonlib import MesonException
  from .wrap import WrapMode
  
  def add_arguments(parser):
      coredata.register_builtin_arguments(parser)
      parser.add_argument('--cross-file', default=None,
                          help='File describing cross compilation environment.')
      parser.add_argument('-v', '--version', action='version',
                          version=coredata.version)
      # See the mesonlib.WrapMode enum for documentation
      parser.add_argument('--wrap-mode', default=None,
                          type=wrapmodetype, choices=WrapMode,
                          help='Special wrap mode to use')
      parser.add_argument('--profile-self', action='store_true', dest='profile',
                          help=argparse.SUPPRESS)
      parser.add_argument('--fatal-meson-warnings', action='store_true', dest='fatal_warnings',
                          help='Make all Meson warnings fatal')
      parser.add_argument('--reconfigure', action='store_true',
                          help='Set options and reconfigure the project. Useful when new ' +
                               'options have been added to the project and the default value ' +
                               'is not working.')
      parser.add_argument('builddir', nargs='?', default=None)
      parser.add_argument('sourcedir', nargs='?', default=None)
  
  def wrapmodetype(string):
      try:
          return getattr(WrapMode, string)
      except AttributeError:
          msg = ', '.join([t.name.lower() for t in WrapMode])
          msg = 'invalid argument {!r}, use one of {}'.format(string, msg)
          raise argparse.ArgumentTypeError(msg)
  
  class MesonApp:
      def __init__(self, options):
          (self.source_dir, self.build_dir) = self.validate_dirs(options.builddir,
                                                                 options.sourcedir,
                                                                 options.reconfigure)
          self.options = options
  
      def has_build_file(self, dirname):
          fname = os.path.join(dirname, environment.build_filename)
          return os.path.exists(fname)
  
      def validate_core_dirs(self, dir1, dir2):
          if dir1 is None:
              if dir2 is None:
                  if not os.path.exists('meson.build') and os.path.exists('../meson.build'):
                      dir2 = '..'
                  else:
                      raise MesonException('Must specify at least one directory name.')
              dir1 = os.getcwd()
          if dir2 is None:
              dir2 = os.getcwd()
          ndir1 = os.path.abspath(os.path.realpath(dir1))
          ndir2 = os.path.abspath(os.path.realpath(dir2))
          if not os.path.exists(ndir1):
              os.makedirs(ndir1)
          if not os.path.exists(ndir2):
              os.makedirs(ndir2)
          if not stat.S_ISDIR(os.stat(ndir1).st_mode):
              raise MesonException('%s is not a directory' % dir1)
          if not stat.S_ISDIR(os.stat(ndir2).st_mode):
              raise MesonException('%s is not a directory' % dir2)
          if os.path.samefile(dir1, dir2):
              raise MesonException('Source and build directories must not be the same. Create a pristine build directory.')
          if self.has_build_file(ndir1):
              if self.has_build_file(ndir2):
                  raise MesonException('Both directories contain a build file %s.' % environment.build_filename)
              return ndir1, ndir2
          if self.has_build_file(ndir2):
              return ndir2, ndir1
          raise MesonException('Neither directory contains a build file %s.' % environment.build_filename)
  
      def validate_dirs(self, dir1, dir2, reconfigure):
          (src_dir, build_dir) = self.validate_core_dirs(dir1, dir2)
          priv_dir = os.path.join(build_dir, 'meson-private/coredata.dat')
          if os.path.exists(priv_dir):
              if not reconfigure:
                  print('Directory already configured.\n'
                        '\nJust run your build command (e.g. ninja) and Meson will regenerate as necessary.\n'
                        'If ninja fails, run "ninja reconfigure" or "meson --reconfigure"\n'
                        'to force Meson to regenerate.\n'
                        '\nIf build failures persist, manually wipe your build directory to clear any\n'
                        'stored system data.\n'
                        '\nTo change option values, run "meson configure" instead.')
                  sys.exit(0)
          else:
              if reconfigure:
                  print('Directory does not contain a valid build tree:\n{}'.format(build_dir))
                  sys.exit(1)
          return src_dir, build_dir
  
      def check_pkgconfig_envvar(self, env):
          curvar = os.environ.get('PKG_CONFIG_PATH', '')
          if curvar != env.coredata.pkgconf_envvar:
              mlog.warning('PKG_CONFIG_PATH has changed between invocations from "%s" to "%s".' %
                           (env.coredata.pkgconf_envvar, curvar))
              env.coredata.pkgconf_envvar = curvar
  
      def generate(self):
          env = environment.Environment(self.source_dir, self.build_dir, self.options)
          mlog.initialize(env.get_log_dir(), self.options.fatal_warnings)
          if self.options.profile:
              mlog.set_timestamp_start(time.monotonic())
          with mesonlib.BuildDirLock(self.build_dir):
              self._generate(env)
  
      def _generate(self, env):
          mlog.debug('Build started at', datetime.datetime.now().isoformat())
          mlog.debug('Main binary:', sys.executable)
          mlog.debug('Python system:', platform.system())
          mlog.log(mlog.bold('The Meson build system'))
          self.check_pkgconfig_envvar(env)
          mlog.log('Version:', coredata.version)
          mlog.log('Source dir:', mlog.bold(self.source_dir))
          mlog.log('Build dir:', mlog.bold(self.build_dir))
          if env.is_cross_build():
              mlog.log('Build type:', mlog.bold('cross build'))
          else:
              mlog.log('Build type:', mlog.bold('native build'))
          b = build.Build(env)
  
          intr = interpreter.Interpreter(b)
          if env.is_cross_build():
              mlog.log('Host machine cpu family:', mlog.bold(intr.builtin['host_machine'].cpu_family_method([], {})))
              mlog.log('Host machine cpu:', mlog.bold(intr.builtin['host_machine'].cpu_method([], {})))
              mlog.log('Target machine cpu family:', mlog.bold(intr.builtin['target_machine'].cpu_family_method([], {})))
              mlog.log('Target machine cpu:', mlog.bold(intr.builtin['target_machine'].cpu_method([], {})))
          mlog.log('Build machine cpu family:', mlog.bold(intr.builtin['build_machine'].cpu_family_method([], {})))
          mlog.log('Build machine cpu:', mlog.bold(intr.builtin['build_machine'].cpu_method([], {})))
          if self.options.profile:
              fname = os.path.join(self.build_dir, 'meson-private', 'profile-interpreter.log')
              profile.runctx('intr.run()', globals(), locals(), filename=fname)
          else:
              intr.run()
          # Print all default option values that don't match the current value
          for def_opt_name, def_opt_value, cur_opt_value in intr.get_non_matching_default_options():
              mlog.log('Option', mlog.bold(def_opt_name), 'is:',
                       mlog.bold(str(cur_opt_value)),
                       '[default: {}]'.format(str(def_opt_value)))
          try:
              dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat')
              # We would like to write coredata as late as possible since we use the existence of
              # this file to check if we generated the build file successfully. Since coredata
              # includes settings, the build files must depend on it and appear newer. However, due
              # to various kernel caches, we cannot guarantee that any time in Python is exactly in
              # sync with the time that gets applied to any files. Thus, we dump this file as late as
              # possible, but before build files, and if any error occurs, delete it.
              cdf = env.dump_coredata()
              if self.options.profile:
                  fname = 'profile-{}-backend.log'.format(intr.backend.name)
                  fname = os.path.join(self.build_dir, 'meson-private', fname)
                  profile.runctx('intr.backend.generate(intr)', globals(), locals(), filename=fname)
              else:
                  intr.backend.generate(intr)
              build.save(b, dumpfile)
              # Post-conf scripts must be run after writing coredata or else introspection fails.
              intr.backend.run_postconf_scripts()
          except:
              if 'cdf' in locals():
                  old_cdf = cdf + '.prev'
                  if os.path.exists(old_cdf):
                      os.replace(old_cdf, cdf)
                  else:
                      os.unlink(cdf)
              raise
  
  def run(options):
      coredata.parse_cmd_line_options(options)
      app = MesonApp(options)
      app.generate()
      return 0
  
files: mesonbuild/msetup.py
files: # Copyright 2016-2018 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 time
  import sys, stat
  import datetime
  import os.path
  import platform
  import cProfile as profile
  import argparse
  import traceback
  
  from . import environment, interpreter, mesonlib
  from . import build
  from . import mlog, coredata
  from .mesonlib import MesonException
  from .wrap import WrapMode
  
  def buildparser():
      parser = argparse.ArgumentParser(prog='meson')
      coredata.register_builtin_arguments(parser)
      parser.add_argument('--cross-file', default=None,
                          help='File describing cross compilation environment.')
      parser.add_argument('-v', '--version', action='version',
                          version=coredata.version)
      # See the mesonlib.WrapMode enum for documentation
      parser.add_argument('--wrap-mode', default=None,
                          type=wrapmodetype, choices=WrapMode,
                          help='Special wrap mode to use')
      parser.add_argument('--profile-self', action='store_true', dest='profile',
                          help=argparse.SUPPRESS)
      parser.add_argument('--fatal-meson-warnings', action='store_true', dest='fatal_warnings',
                          help='Make all Meson warnings fatal')
      parser.add_argument('--reconfigure', action='store_true',
                          help='Set options and reconfigure the project. Useful when new ' +
                               'options have been added to the project and the default value ' +
                               'is not working.')
      parser.add_argument('builddir', nargs='?', default=None)
      parser.add_argument('sourcedir', nargs='?', default=None)
      return parser
  
  def wrapmodetype(string):
      try:
          return getattr(WrapMode, string)
      except AttributeError:
          msg = ', '.join([t.name.lower() for t in WrapMode])
          msg = 'invalid argument {!r}, use one of {}'.format(string, msg)
          raise argparse.ArgumentTypeError(msg)
  
  class MesonApp:
      def __init__(self, options):
          (self.source_dir, self.build_dir) = self.validate_dirs(options.builddir,
                                                                 options.sourcedir,
                                                                 options.reconfigure)
          self.options = options
  
      def has_build_file(self, dirname):
          fname = os.path.join(dirname, environment.build_filename)
          return os.path.exists(fname)
  
      def validate_core_dirs(self, dir1, dir2):
          if dir1 is None:
              if dir2 is None:
                  if not os.path.exists('meson.build') and os.path.exists('../meson.build'):
                      dir2 = '..'
                  else:
                      raise MesonException('Must specify at least one directory name.')
              dir1 = os.getcwd()
          if dir2 is None:
              dir2 = os.getcwd()
          ndir1 = os.path.abspath(os.path.realpath(dir1))
          ndir2 = os.path.abspath(os.path.realpath(dir2))
          if not os.path.exists(ndir1):
              os.makedirs(ndir1)
          if not os.path.exists(ndir2):
              os.makedirs(ndir2)
          if not stat.S_ISDIR(os.stat(ndir1).st_mode):
              raise MesonException('%s is not a directory' % dir1)
          if not stat.S_ISDIR(os.stat(ndir2).st_mode):
              raise MesonException('%s is not a directory' % dir2)
          if os.path.samefile(dir1, dir2):
              raise MesonException('Source and build directories must not be the same. Create a pristine build directory.')
          if self.has_build_file(ndir1):
              if self.has_build_file(ndir2):
                  raise MesonException('Both directories contain a build file %s.' % environment.build_filename)
              return ndir1, ndir2
          if self.has_build_file(ndir2):
              return ndir2, ndir1
          raise MesonException('Neither directory contains a build file %s.' % environment.build_filename)
  
      def validate_dirs(self, dir1, dir2, reconfigure):
          (src_dir, build_dir) = self.validate_core_dirs(dir1, dir2)
          priv_dir = os.path.join(build_dir, 'meson-private/coredata.dat')
          if os.path.exists(priv_dir):
              if not reconfigure:
                  print('Directory already configured.\n'
                        '\nJust run your build command (e.g. ninja) and Meson will regenerate as necessary.\n'
                        'If ninja fails, run "ninja reconfigure" or "meson --reconfigure"\n'
                        'to force Meson to regenerate.\n'
                        '\nIf build failures persist, manually wipe your build directory to clear any\n'
                        'stored system data.\n'
                        '\nTo change option values, run "meson configure" instead.')
                  sys.exit(0)
          else:
              if reconfigure:
                  print('Directory does not contain a valid build tree:\n{}'.format(build_dir))
                  sys.exit(1)
          return src_dir, build_dir
  
      def check_pkgconfig_envvar(self, env):
          curvar = os.environ.get('PKG_CONFIG_PATH', '')
          if curvar != env.coredata.pkgconf_envvar:
              mlog.warning('PKG_CONFIG_PATH has changed between invocations from "%s" to "%s".' %
                           (env.coredata.pkgconf_envvar, curvar))
              env.coredata.pkgconf_envvar = curvar
  
      def generate(self):
          env = environment.Environment(self.source_dir, self.build_dir, self.options)
          mlog.initialize(env.get_log_dir(), self.options.fatal_warnings)
          if self.options.profile:
              mlog.set_timestamp_start(time.monotonic())
          with mesonlib.BuildDirLock(self.build_dir):
              self._generate(env)
  
      def _generate(self, env):
          mlog.debug('Build started at', datetime.datetime.now().isoformat())
          mlog.debug('Main binary:', sys.executable)
          mlog.debug('Python system:', platform.system())
          mlog.log(mlog.bold('The Meson build system'))
          self.check_pkgconfig_envvar(env)
          mlog.log('Version:', coredata.version)
          mlog.log('Source dir:', mlog.bold(self.source_dir))
          mlog.log('Build dir:', mlog.bold(self.build_dir))
          if env.is_cross_build():
              mlog.log('Build type:', mlog.bold('cross build'))
          else:
              mlog.log('Build type:', mlog.bold('native build'))
          b = build.Build(env)
  
          intr = interpreter.Interpreter(b)
          if env.is_cross_build():
              mlog.log('Host machine cpu family:', mlog.bold(intr.builtin['host_machine'].cpu_family_method([], {})))
              mlog.log('Host machine cpu:', mlog.bold(intr.builtin['host_machine'].cpu_method([], {})))
              mlog.log('Target machine cpu family:', mlog.bold(intr.builtin['target_machine'].cpu_family_method([], {})))
              mlog.log('Target machine cpu:', mlog.bold(intr.builtin['target_machine'].cpu_method([], {})))
          mlog.log('Build machine cpu family:', mlog.bold(intr.builtin['build_machine'].cpu_family_method([], {})))
          mlog.log('Build machine cpu:', mlog.bold(intr.builtin['build_machine'].cpu_method([], {})))
          if self.options.profile:
              fname = os.path.join(self.build_dir, 'meson-private', 'profile-interpreter.log')
              profile.runctx('intr.run()', globals(), locals(), filename=fname)
          else:
              intr.run()
          # Print all default option values that don't match the current value
          for def_opt_name, def_opt_value, cur_opt_value in intr.get_non_matching_default_options():
              mlog.log('Option', mlog.bold(def_opt_name), 'is:',
                       mlog.bold(str(cur_opt_value)),
                       '[default: {}]'.format(str(def_opt_value)))
          try:
              dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat')
              # We would like to write coredata as late as possible since we use the existence of
              # this file to check if we generated the build file successfully. Since coredata
              # includes settings, the build files must depend on it and appear newer. However, due
              # to various kernel caches, we cannot guarantee that any time in Python is exactly in
              # sync with the time that gets applied to any files. Thus, we dump this file as late as
              # possible, but before build files, and if any error occurs, delete it.
              cdf = env.dump_coredata()
              if self.options.profile:
                  fname = 'profile-{}-backend.log'.format(intr.backend.name)
                  fname = os.path.join(self.build_dir, 'meson-private', fname)
                  profile.runctx('intr.backend.generate(intr)', globals(), locals(), filename=fname)
              else:
                  intr.backend.generate(intr)
              build.save(b, dumpfile)
              # Post-conf scripts must be run after writing coredata or else introspection fails.
              intr.backend.run_postconf_scripts()
          except:
              if 'cdf' in locals():
                  old_cdf = cdf + '.prev'
                  if os.path.exists(old_cdf):
                      os.replace(old_cdf, cdf)
                  else:
                      os.unlink(cdf)
              raise
  
  def run(args):
      args = mesonlib.expand_arguments(args)
      options = buildparser().parse_args(args)
      coredata.parse_cmd_line_options(options)
      try:
          app = MesonApp(options)
      except Exception as e:
          # Log directory does not exist, so just print
          # to stdout.
          print('Error during basic setup:\n')
          print(e)
          return 1
      try:
          app.generate()
      except Exception as e:
          if isinstance(e, MesonException):
              mlog.exception(e)
              # Path to log file
              mlog.shutdown()
              logfile = os.path.join(app.build_dir, environment.Environment.log_dir, mlog.log_fname)
              mlog.log("\nA full log can be found at", mlog.bold(logfile))
              if os.environ.get('MESON_FORCE_BACKTRACE'):
                  raise
              return 1
          else:
              if os.environ.get('MESON_FORCE_BACKTRACE'):
                  raise
              traceback.print_exc()
              return 2
      finally:
          mlog.shutdown()
      return 0
  
files: mesonbuild/msetup.py
files: # Copyright 2016-2017 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 tool to run tests in many different ways.
  
  import shlex
  import subprocess, sys, os, argparse
  import pickle
  from mesonbuild import build
  from mesonbuild import environment
  from mesonbuild.dependencies import ExternalProgram
  from mesonbuild.mesonlib import substring_is_in_list, MesonException
  from mesonbuild import mlog
  
  import time, datetime, multiprocessing, json
  import concurrent.futures as conc
  import platform
  import signal
  import random
  from copy import deepcopy
  import enum
  
  # GNU autotools interprets a return code of 77 from tests it executes to
  # mean that the test should be skipped.
  GNU_SKIP_RETURNCODE = 77
  
  def is_windows():
      platname = platform.system().lower()
      return platname == 'windows' or 'mingw' in platname
  
  def is_cygwin():
      platname = platform.system().lower()
      return 'cygwin' in platname
  
  def determine_worker_count():
      varname = 'MESON_TESTTHREADS'
      if varname in os.environ:
          try:
              num_workers = int(os.environ[varname])
          except ValueError:
              print('Invalid value in %s, using 1 thread.' % varname)
              num_workers = 1
      else:
          try:
              # Fails in some weird environments such as Debian
              # reproducible build.
              num_workers = multiprocessing.cpu_count()
          except Exception:
              num_workers = 1
      return num_workers
  
  def add_arguments(parser):
      parser.add_argument('--repeat', default=1, dest='repeat', type=int,
                          help='Number of times to run the tests.')
      parser.add_argument('--no-rebuild', default=False, action='store_true',
                          help='Do not rebuild before running tests.')
      parser.add_argument('--gdb', default=False, dest='gdb', action='store_true',
                          help='Run test under gdb.')
      parser.add_argument('--list', default=False, dest='list', action='store_true',
                          help='List available tests.')
      parser.add_argument('--wrapper', default=None, dest='wrapper', type=shlex.split,
                          help='wrapper to run tests with (e.g. Valgrind)')
      parser.add_argument('-C', default='.', dest='wd',
                          help='directory to cd into before running')
      parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE',
                          help='Only run tests belonging to the given suite.')
      parser.add_argument('--no-suite', default=[], dest='exclude_suites', action='append', metavar='SUITE',
                          help='Do not run tests belonging to the given suite.')
      parser.add_argument('--no-stdsplit', default=True, dest='split', action='store_false',
                          help='Do not split stderr and stdout in test logs.')
      parser.add_argument('--print-errorlogs', default=False, action='store_true',
                          help="Whether to print failing tests' logs.")
      parser.add_argument('--benchmark', default=False, action='store_true',
                          help="Run benchmarks instead of tests.")
      parser.add_argument('--logbase', default='testlog',
                          help="Base name for log file.")
      parser.add_argument('--num-processes', default=determine_worker_count(), type=int,
                          help='How many parallel processes to use.')
      parser.add_argument('-v', '--verbose', default=False, action='store_true',
                          help='Do not redirect stdout and stderr')
      parser.add_argument('-q', '--quiet', default=False, action='store_true',
                          help='Produce less output to the terminal.')
      parser.add_argument('-t', '--timeout-multiplier', type=float, default=None,
                          help='Define a multiplier for test timeout, for example '
                          ' when running tests in particular conditions they might take'
                          ' more time to execute.')
      parser.add_argument('--setup', default=None, dest='setup',
                          help='Which test setup to use.')
      parser.add_argument('--test-args', default=[], type=shlex.split,
                          help='Arguments to pass to the specified test(s) or all tests')
      parser.add_argument('args', nargs='*',
                          help='Optional list of tests to run')
  
  
  def returncode_to_status(retcode):
      # Note: We can't use `os.WIFSIGNALED(result.returncode)` and the related
      # functions here because the status returned by subprocess is munged. It
      # returns a negative value if the process was killed by a signal rather than
      # the raw status returned by `wait()`. Also, If a shell sits between Meson
      # the the actual unit test that shell is likely to convert a termination due
      # to a signal into an exit status of 128 plus the signal number.
      if retcode < 0:
          signum = -retcode
          try:
              signame = signal.Signals(signum).name
          except ValueError:
              signame = 'SIGinvalid'
          return '(killed by signal %d %s)' % (signum, signame)
  
      if retcode <= 128:
          return '(exit status %d)' % (retcode,)
  
      signum = retcode - 128
      try:
          signame = signal.Signals(signum).name
      except ValueError:
          signame = 'SIGinvalid'
      return '(exit status %d or signal %d %s)' % (retcode, signum, signame)
  
  def env_tuple_to_str(env):
      return ''.join(["%s='%s' " % (k, v) for k, v in env])
  
  
  class TestException(MesonException):
      pass
  
  
  @enum.unique
  class TestResult(enum.Enum):
  
      OK = 'OK'
      TIMEOUT = 'TIMEOUT'
      SKIP = 'SKIP'
      FAIL = 'FAIL'
  
  
  class TestRun:
      def __init__(self, res, returncode, should_fail, duration, stdo, stde, cmd,
                   env):
          assert isinstance(res, TestResult)
          self.res = res
          self.returncode = returncode
          self.duration = duration
          self.stdo = stdo
          self.stde = stde
          self.cmd = cmd
          self.env = env
          self.should_fail = should_fail
  
      def get_log(self):
          res = '--- command ---\n'
          if self.cmd is None:
              res += 'NONE\n'
          else:
              test_only_env = set(self.env.items()) - set(os.environ.items())
              res += '{}{}\n'.format(env_tuple_to_str(test_only_env), ' '.join(self.cmd))
          if self.stdo:
              res += '--- stdout ---\n'
              res += self.stdo
          if self.stde:
              if res[-1:] != '\n':
                  res += '\n'
              res += '--- stderr ---\n'
              res += self.stde
          if res[-1:] != '\n':
              res += '\n'
          res += '-------\n\n'
          return res
  
  def decode(stream):
      if stream is None:
          return ''
      try:
          return stream.decode('utf-8')
      except UnicodeDecodeError:
          return stream.decode('iso-8859-1', errors='ignore')
  
  def write_json_log(jsonlogfile, test_name, result):
      jresult = {'name': test_name,
                 'stdout': result.stdo,
                 'result': result.res.value,
                 'duration': result.duration,
                 'returncode': result.returncode,
                 'command': result.cmd}
      if isinstance(result.env, dict):
          jresult['env'] = result.env
      else:
          jresult['env'] = result.env.get_env(os.environ)
      if result.stde:
          jresult['stderr'] = result.stde
      jsonlogfile.write(json.dumps(jresult) + '\n')
  
  def run_with_mono(fname):
      if fname.endswith('.exe') and not (is_windows() or is_cygwin()):
          return True
      return False
  
  def load_benchmarks(build_dir):
      datafile = os.path.join(build_dir, 'meson-private', 'meson_benchmark_setup.dat')
      if not os.path.isfile(datafile):
          raise TestException('Directory ${!r} does not seem to be a Meson build directory.'.format(build_dir))
      with open(datafile, 'rb') as f:
          obj = pickle.load(f)
      return obj
  
  def load_tests(build_dir):
      datafile = os.path.join(build_dir, 'meson-private', 'meson_test_setup.dat')
      if not os.path.isfile(datafile):
          raise TestException('Directory ${!r} does not seem to be a Meson build directory.'.format(build_dir))
      with open(datafile, 'rb') as f:
          obj = pickle.load(f)
      return obj
  
  
  class SingleTestRunner:
  
      def __init__(self, test, env, options):
          self.test = test
          self.env = env
          self.options = options
  
      def _get_cmd(self):
          if self.test.fname[0].endswith('.jar'):
              return ['java', '-jar'] + self.test.fname
          elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]):
              return ['mono'] + self.test.fname
          else:
              if self.test.is_cross_built:
                  if self.test.exe_runner is None:
                      # Can not run test on cross compiled executable
                      # because there is no execute wrapper.
                      return None
                  else:
                      if not self.test.exe_runner.found():
                          msg = 'The exe_wrapper defined in the cross file {!r} was not ' \
                                'found. Please check the command and/or add it to PATH.'
                          raise TestException(msg.format(self.test.exe_runner.name))
                      return self.test.exe_runner.get_command() + self.test.fname
              else:
                  return self.test.fname
  
      def run(self):
          cmd = self._get_cmd()
          if cmd is None:
              skip_stdout = 'Not run because can not execute cross compiled binaries.'
              return TestRun(res=TestResult.SKIP, returncode=GNU_SKIP_RETURNCODE,
                             should_fail=self.test.should_fail, duration=0.0,
                             stdo=skip_stdout, stde=None, cmd=None, env=self.test.env)
          else:
              wrap = TestHarness.get_wrapper(self.options)
              if self.options.gdb:
                  self.test.timeout = None
              return self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args)
  
      def _run_cmd(self, cmd):
          starttime = time.time()
  
          if len(self.test.extra_paths) > 0:
              self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH']
              if substring_is_in_list('wine', cmd):
                  wine_paths = ['Z:' + p for p in self.test.extra_paths]
                  wine_path = ';'.join(wine_paths)
                  # Don't accidentally end with an `;` because that will add the
                  # current directory and might cause unexpected behaviour
                  if 'WINEPATH' in self.env:
                      self.env['WINEPATH'] = wine_path + ';' + self.env['WINEPATH']
                  else:
                      self.env['WINEPATH'] = wine_path
  
          # If MALLOC_PERTURB_ is not set, or if it is set to an empty value,
          # (i.e., the test or the environment don't explicitly set it), set
          # it ourselves. We do this unconditionally for regular tests
          # because it is extremely useful to have.
          # Setting MALLOC_PERTURB_="0" will completely disable this feature.
          if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_']) and not self.options.benchmark:
              self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255))
  
          stdout = None
          stderr = None
          if not self.options.verbose:
              stdout = subprocess.PIPE
              stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT
  
          # Let gdb handle ^C instead of us
          if self.options.gdb:
              previous_sigint_handler = signal.getsignal(signal.SIGINT)
              # Make the meson executable ignore SIGINT while gdb is running.
              signal.signal(signal.SIGINT, signal.SIG_IGN)
  
          def preexec_fn():
              if self.options.gdb:
                  # Restore the SIGINT handler for the child process to
                  # ensure it can handle it.
                  signal.signal(signal.SIGINT, signal.SIG_DFL)
              else:
                  # We don't want setsid() in gdb because gdb needs the
                  # terminal in order to handle ^C and not show tcsetpgrp()
                  # errors avoid not being able to use the terminal.
                  os.setsid()
  
          p = subprocess.Popen(cmd,
                               stdout=stdout,
                               stderr=stderr,
                               env=self.env,
                               cwd=self.test.workdir,
                               preexec_fn=preexec_fn if not is_windows() else None)
          timed_out = False
          kill_test = False
          if self.test.timeout is None:
              timeout = None
          elif self.options.timeout_multiplier is not None:
              timeout = self.test.timeout * self.options.timeout_multiplier
          else:
              timeout = self.test.timeout
          try:
              (stdo, stde) = p.communicate(timeout=timeout)
          except subprocess.TimeoutExpired:
              if self.options.verbose:
                  print('%s time out (After %d seconds)' % (self.test.name, timeout))
              timed_out = True
          except KeyboardInterrupt:
              mlog.warning('CTRL-C detected while running %s' % (self.test.name))
              kill_test = True
          finally:
              if self.options.gdb:
                  # Let us accept ^C again
                  signal.signal(signal.SIGINT, previous_sigint_handler)
  
          if kill_test or timed_out:
              # Python does not provide multiplatform support for
              # killing a process and all its children so we need
              # to roll our own.
              if is_windows():
                  subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)])
              else:
                  try:
                      # Kill the process group that setsid() created.
                      os.killpg(p.pid, signal.SIGKILL)
                  except ProcessLookupError:
                      # Sometimes (e.g. with Wine) this happens.
                      # There's nothing we can do (maybe the process
                      # already died) so carry on.
                      pass
              try:
                  (stdo, stde) = p.communicate(timeout=1)
              except subprocess.TimeoutExpired:
                  # An earlier kill attempt has not worked for whatever reason.
                  # Try to kill it one last time with a direct call.
                  # If the process has spawned children, they will remain around.
                  p.kill()
                  try:
                      (stdo, stde) = p.communicate(timeout=1)
                  except subprocess.TimeoutExpired:
                      stdo = b'Test process could not be killed.'
                      stde = b''
              except ValueError:
                  stdo = b'Could not read output. Maybe the process has redirected its stdout/stderr?'
                  stde = b''
          endtime = time.time()
          duration = endtime - starttime
          stdo = decode(stdo)
          if stde:
              stde = decode(stde)
          if timed_out:
              res = TestResult.TIMEOUT
          elif p.returncode == GNU_SKIP_RETURNCODE:
              res = TestResult.SKIP
          elif self.test.should_fail == bool(p.returncode):
              res = TestResult.OK
          else:
              res = TestResult.FAIL
          return TestRun(res, p.returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env)
  
  
  class TestHarness:
      def __init__(self, options):
          self.options = options
          self.collected_logs = []
          self.fail_count = 0
          self.success_count = 0
          self.skip_count = 0
          self.timeout_count = 0
          self.is_run = False
          self.tests = None
          self.suites = None
          self.logfilename = None
          self.logfile = None
          self.jsonlogfile = None
          if self.options.benchmark:
              self.tests = load_benchmarks(options.wd)
          else:
              self.tests = load_tests(options.wd)
          self.load_suites()
  
      def __del__(self):
          if self.logfile:
              self.logfile.close()
          if self.jsonlogfile:
              self.jsonlogfile.close()
  
      def merge_suite_options(self, options, test):
          if ':' in options.setup:
              if options.setup not in self.build_data.test_setups:
                  sys.exit("Unknown test setup '%s'." % options.setup)
              current = self.build_data.test_setups[options.setup]
          else:
              full_name = test.project_name + ":" + options.setup
              if full_name not in self.build_data.test_setups:
                  sys.exit("Test setup '%s' not found from project '%s'." % (options.setup, test.project_name))
              current = self.build_data.test_setups[full_name]
          if not options.gdb:
              options.gdb = current.gdb
          if options.timeout_multiplier is None:
              options.timeout_multiplier = current.timeout_multiplier
      #    if options.env is None:
      #        options.env = current.env # FIXME, should probably merge options here.
          if options.wrapper is not None and current.exe_wrapper is not None:
              sys.exit('Conflict: both test setup and command line specify an exe wrapper.')
          if options.wrapper is None:
              options.wrapper = current.exe_wrapper
          return current.env.get_env(os.environ.copy())
  
      def get_test_runner(self, test):
          options = deepcopy(self.options)
          if options.setup:
              env = self.merge_suite_options(options, test)
          else:
              env = os.environ.copy()
          if isinstance(test.env, build.EnvironmentVariables):
              test.env = test.env.get_env(env)
          env.update(test.env)
          return SingleTestRunner(test, env, options)
  
      def process_test_result(self, result):
          if result.res is TestResult.TIMEOUT:
              self.timeout_count += 1
              self.fail_count += 1
          elif result.res is TestResult.SKIP:
              self.skip_count += 1
          elif result.res is TestResult.OK:
              self.success_count += 1
          elif result.res is TestResult.FAIL:
              self.fail_count += 1
          else:
              sys.exit('Unknown test result encountered: {}'.format(result.res))
  
      def print_stats(self, numlen, tests, name, result, i):
          startpad = ' ' * (numlen - len('%d' % (i + 1)))
          num = '%s%d/%d' % (startpad, i + 1, len(tests))
          padding1 = ' ' * (38 - len(name))
          padding2 = ' ' * (8 - len(result.res.value))
          status = ''
  
          if result.res is TestResult.FAIL:
              status = returncode_to_status(result.returncode)
          result_str = '%s %s  %s%s%s%5.2f s %s' % \
              (num, name, padding1, result.res.value, padding2, result.duration,
               status)
          if not self.options.quiet or result.res is not TestResult.OK:
              if result.res is not TestResult.OK and mlog.colorize_console:
                  if result.res in (TestResult.FAIL, TestResult.TIMEOUT):
                      decorator = mlog.red
                  elif result.res is TestResult.SKIP:
                      decorator = mlog.yellow
                  else:
                      sys.exit('Unreachable code was ... well ... reached.')
                  print(decorator(result_str).get_text(True))
              else:
                  print(result_str)
          result_str += "\n\n" + result.get_log()
          if (result.returncode != GNU_SKIP_RETURNCODE) \
                  and (result.returncode != 0) != result.should_fail:
              if self.options.print_errorlogs:
                  self.collected_logs.append(result_str)
          if self.logfile:
              self.logfile.write(result_str)
          if self.jsonlogfile:
              write_json_log(self.jsonlogfile, name, result)
  
      def print_summary(self):
          msg = '''
  OK:      %4d
  FAIL:    %4d
  SKIP:    %4d
  TIMEOUT: %4d
  ''' % (self.success_count, self.fail_count, self.skip_count, self.timeout_count)
          print(msg)
          if self.logfile:
              self.logfile.write(msg)
  
      def print_collected_logs(self):
          if len(self.collected_logs) > 0:
              if len(self.collected_logs) > 10:
                  print('\nThe output from 10 first failed tests:\n')
              else:
                  print('\nThe output from the failed tests:\n')
              for log in self.collected_logs[:10]:
                  lines = log.splitlines()
                  if len(lines) > 104:
                      print('\n'.join(lines[0:4]))
                      print('--- Listing only the last 100 lines from a long log. ---')
                      lines = lines[-100:]
                  for line in lines:
                      print(line)
  
      def doit(self):
          if self.is_run:
              raise RuntimeError('Test harness object can only be used once.')
          self.is_run = True
          tests = self.get_tests()
          if not tests:
              return 0
          self.run_tests(tests)
          return self.fail_count
  
      @staticmethod
      def split_suite_string(suite):
          if ':' in suite:
              return suite.split(':', 1)
          else:
              return suite, ""
  
      @staticmethod
      def test_in_suites(test, suites):
          for suite in suites:
              (prj_match, st_match) = TestHarness.split_suite_string(suite)
              for prjst in test.suite:
                  (prj, st) = TestHarness.split_suite_string(prjst)
  
                  # the SUITE can be passed as
                  #     suite_name
                  # or
                  #     project_name:suite_name
                  # so we need to select only the test belonging to project_name
  
                  # this if hanlde the first case (i.e., SUITE == suite_name)
  
                  # in this way we can run tests belonging to different
                  # (sub)projects which share the same suite_name
                  if not st_match and st == prj_match:
                      return True
  
                  # these two conditions are needed to handle the second option
                  # i.e., SUITE == project_name:suite_name
  
                  # in this way we select the only the tests of
                  # project_name with suite_name
                  if prj_match and prj != prj_match:
                      continue
                  if st_match and st != st_match:
                      continue
                  return True
          return False
  
      def test_suitable(self, test):
          return (not self.options.include_suites or TestHarness.test_in_suites(test, self.options.include_suites)) \
              and not TestHarness.test_in_suites(test, self.options.exclude_suites)
  
      def load_suites(self):
          ss = set()
          for t in self.tests:
              for s in t.suite:
                  ss.add(s)
          self.suites = list(ss)
  
      def get_tests(self):
          if not self.tests:
              print('No tests defined.')
              return []
  
          if len(self.options.include_suites) or len(self.options.exclude_suites):
              tests = []
              for tst in self.tests:
                  if self.test_suitable(tst):
                      tests.append(tst)
          else:
              tests = self.tests
  
          if self.options.args:
              tests = [t for t in tests if t.name in self.options.args]
  
          if not tests:
              print('No suitable tests defined.')
              return []
  
          for test in tests:
              test.rebuilt = False
  
          return tests
  
      def open_log_files(self):
          if not self.options.logbase or self.options.verbose:
              return None, None, None, None
  
          namebase = None
          logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase)
  
          if self.options.wrapper:
              namebase = os.path.basename(self.get_wrapper(self.options)[0])
          elif self.options.setup:
              namebase = self.options.setup.replace(":", "_")
  
          if namebase:
              logfile_base += '-' + namebase.replace(' ', '_')
          self.logfilename = logfile_base + '.txt'
          self.jsonlogfilename = logfile_base + '.json'
  
          self.jsonlogfile = open(self.jsonlogfilename, 'w', encoding='utf-8')
          self.logfile = open(self.logfilename, 'w', encoding='utf-8')
  
          self.logfile.write('Log of Meson test suite run on %s\n\n'
                             % datetime.datetime.now().isoformat())
          inherit_env = env_tuple_to_str(os.environ.items())
          self.logfile.write('Inherited environment: {}\n\n'.format(inherit_env))
  
      @staticmethod
      def get_wrapper(options):
          wrap = []
          if options.gdb:
              wrap = ['gdb', '--quiet', '--nh']
              if options.repeat > 1:
                  wrap += ['-ex', 'run', '-ex', 'quit']
              # Signal the end of arguments to gdb
              wrap += ['--args']
          if options.wrapper:
              wrap += options.wrapper
          assert(isinstance(wrap, list))
          return wrap
  
      def get_pretty_suite(self, test):
          if len(self.suites) > 1:
              rv = TestHarness.split_suite_string(test.suite[0])[0]
              s = "+".join(TestHarness.split_suite_string(s)[1] for s in test.suite)
              if len(s):
                  rv += ":"
              return rv + s + " / " + test.name
          else:
              return test.name
  
      def run_tests(self, tests):
          executor = None
          futures = []
          numlen = len('%d' % len(tests))
          self.open_log_files()
          startdir = os.getcwd()
          if self.options.wd:
              os.chdir(self.options.wd)
          self.build_data = build.load(os.getcwd())
  
          try:
              for _ in range(self.options.repeat):
                  for i, test in enumerate(tests):
                      visible_name = self.get_pretty_suite(test)
  
                      if not test.is_parallel or self.options.gdb:
                          self.drain_futures(futures)
                          futures = []
                          single_test = self.get_test_runner(test)
                          res = single_test.run()
                          self.process_test_result(res)
                          self.print_stats(numlen, tests, visible_name, res, i)
                      else:
                          if not executor:
                              executor = conc.ThreadPoolExecutor(max_workers=self.options.num_processes)
                          single_test = self.get_test_runner(test)
                          f = executor.submit(single_test.run)
                          futures.append((f, numlen, tests, visible_name, i))
                      if self.options.repeat > 1 and self.fail_count:
                          break
                  if self.options.repeat > 1 and self.fail_count:
                      break
  
              self.drain_futures(futures)
              self.print_summary()
              self.print_collected_logs()
  
              if self.logfilename:
                  print('Full log written to %s' % self.logfilename)
          finally:
              os.chdir(startdir)
  
      def drain_futures(self, futures):
          for i in futures:
              (result, numlen, tests, name, i) = i
              if self.options.repeat > 1 and self.fail_count:
                  result.cancel()
              if self.options.verbose:
                  result.result()
              self.process_test_result(result.result())
              self.print_stats(numlen, tests, name, result.result(), i)
  
      def run_special(self):
          '''Tests run by the user, usually something like "under gdb 1000 times".'''
          if self.is_run:
              raise RuntimeError('Can not use run_special after a full run.')
          tests = self.get_tests()
          if not tests:
              return 0
          self.run_tests(tests)
          return self.fail_count
  
  
  def list_tests(th):
      tests = th.get_tests()
      for t in tests:
          print(th.get_pretty_suite(t))
  
  def rebuild_all(wd):
      if not os.path.isfile(os.path.join(wd, 'build.ninja')):
          print('Only ninja backend is supported to rebuild tests before running them.')
          return True
  
      ninja = environment.detect_ninja()
      if not ninja:
          print("Can't find ninja, can't rebuild test.")
          return False
  
      p = subprocess.Popen([ninja, '-C', wd])
      p.communicate()
  
      if p.returncode != 0:
          print('Could not rebuild')
          return False
  
      return True
  
  def run(options):
      if options.benchmark:
          options.num_processes = 1
  
      if options.verbose and options.quiet:
          print('Can not be both quiet and verbose at the same time.')
          return 1
  
      check_bin = None
      if options.gdb:
          options.verbose = True
          if options.wrapper:
              print('Must not specify both a wrapper and gdb at the same time.')
              return 1
          check_bin = 'gdb'
  
      if options.wrapper:
          check_bin = options.wrapper[0]
  
      if check_bin is not None:
          exe = ExternalProgram(check_bin, silent=True)
          if not exe.found():
              print('Could not find requested program: {!r}'.format(check_bin))
              return 1
      options.wd = os.path.abspath(options.wd)
  
      if not options.list and not options.no_rebuild:
          if not rebuild_all(options.wd):
              return 1
  
      try:
          th = TestHarness(options)
          if options.list:
              list_tests(th)
              return 0
          if not options.args:
              return th.doit()
          return th.run_special()
      except TestException as e:
          print('Meson test encountered an error:\n')
          if os.environ.get('MESON_FORCE_BACKTRACE'):
              raise e
          else:
              print(e)
          return 1
  
  def run_with_args(args):
      parser = argparse.ArgumentParser(prog='meson test')
      add_arguments(parser)
      options = parser.parse_args(args)
      return run(options)
  
files: mesonbuild/mtest.py
files: # Copyright 2016-2017 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 tool to run tests in many different ways.
  
  import shlex
  import subprocess, sys, os, argparse
  import pickle
  from mesonbuild import build
  from mesonbuild import environment
  from mesonbuild.dependencies import ExternalProgram
  from mesonbuild.mesonlib import substring_is_in_list, MesonException
  from mesonbuild import mlog
  
  import time, datetime, multiprocessing, json
  import concurrent.futures as conc
  import platform
  import signal
  import random
  from copy import deepcopy
  import enum
  
  # GNU autotools interprets a return code of 77 from tests it executes to
  # mean that the test should be skipped.
  GNU_SKIP_RETURNCODE = 77
  
  def is_windows():
      platname = platform.system().lower()
      return platname == 'windows' or 'mingw' in platname
  
  def is_cygwin():
      platname = platform.system().lower()
      return 'cygwin' in platname
  
  def determine_worker_count():
      varname = 'MESON_TESTTHREADS'
      if varname in os.environ:
          try:
              num_workers = int(os.environ[varname])
          except ValueError:
              print('Invalid value in %s, using 1 thread.' % varname)
              num_workers = 1
      else:
          try:
              # Fails in some weird environments such as Debian
              # reproducible build.
              num_workers = multiprocessing.cpu_count()
          except Exception:
              num_workers = 1
      return num_workers
  
  def buildparser():
      parser = argparse.ArgumentParser(prog='meson test')
      parser.add_argument('--repeat', default=1, dest='repeat', type=int,
                          help='Number of times to run the tests.')
      parser.add_argument('--no-rebuild', default=False, action='store_true',
                          help='Do not rebuild before running tests.')
      parser.add_argument('--gdb', default=False, dest='gdb', action='store_true',
                          help='Run test under gdb.')
      parser.add_argument('--list', default=False, dest='list', action='store_true',
                          help='List available tests.')
      parser.add_argument('--wrapper', default=None, dest='wrapper', type=shlex.split,
                          help='wrapper to run tests with (e.g. Valgrind)')
      parser.add_argument('-C', default='.', dest='wd',
                          help='directory to cd into before running')
      parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE',
                          help='Only run tests belonging to the given suite.')
      parser.add_argument('--no-suite', default=[], dest='exclude_suites', action='append', metavar='SUITE',
                          help='Do not run tests belonging to the given suite.')
      parser.add_argument('--no-stdsplit', default=True, dest='split', action='store_false',
                          help='Do not split stderr and stdout in test logs.')
      parser.add_argument('--print-errorlogs', default=False, action='store_true',
                          help="Whether to print failing tests' logs.")
      parser.add_argument('--benchmark', default=False, action='store_true',
                          help="Run benchmarks instead of tests.")
      parser.add_argument('--logbase', default='testlog',
                          help="Base name for log file.")
      parser.add_argument('--num-processes', default=determine_worker_count(), type=int,
                          help='How many parallel processes to use.')
      parser.add_argument('-v', '--verbose', default=False, action='store_true',
                          help='Do not redirect stdout and stderr')
      parser.add_argument('-q', '--quiet', default=False, action='store_true',
                          help='Produce less output to the terminal.')
      parser.add_argument('-t', '--timeout-multiplier', type=float, default=None,
                          help='Define a multiplier for test timeout, for example '
                          ' when running tests in particular conditions they might take'
                          ' more time to execute.')
      parser.add_argument('--setup', default=None, dest='setup',
                          help='Which test setup to use.')
      parser.add_argument('--test-args', default=[], type=shlex.split,
                          help='Arguments to pass to the specified test(s) or all tests')
      parser.add_argument('args', nargs='*',
                          help='Optional list of tests to run')
      return parser
  
  
  def returncode_to_status(retcode):
      # Note: We can't use `os.WIFSIGNALED(result.returncode)` and the related
      # functions here because the status returned by subprocess is munged. It
      # returns a negative value if the process was killed by a signal rather than
      # the raw status returned by `wait()`. Also, If a shell sits between Meson
      # the the actual unit test that shell is likely to convert a termination due
      # to a signal into an exit status of 128 plus the signal number.
      if retcode < 0:
          signum = -retcode
          try:
              signame = signal.Signals(signum).name
          except ValueError:
              signame = 'SIGinvalid'
          return '(killed by signal %d %s)' % (signum, signame)
  
      if retcode <= 128:
          return '(exit status %d)' % (retcode,)
  
      signum = retcode - 128
      try:
          signame = signal.Signals(signum).name
      except ValueError:
          signame = 'SIGinvalid'
      return '(exit status %d or signal %d %s)' % (retcode, signum, signame)
  
  def env_tuple_to_str(env):
      return ''.join(["%s='%s' " % (k, v) for k, v in env])
  
  
  class TestException(MesonException):
      pass
  
  
  @enum.unique
  class TestResult(enum.Enum):
  
      OK = 'OK'
      TIMEOUT = 'TIMEOUT'
      SKIP = 'SKIP'
      FAIL = 'FAIL'
  
  
  class TestRun:
      def __init__(self, res, returncode, should_fail, duration, stdo, stde, cmd,
                   env):
          assert isinstance(res, TestResult)
          self.res = res
          self.returncode = returncode
          self.duration = duration
          self.stdo = stdo
          self.stde = stde
          self.cmd = cmd
          self.env = env
          self.should_fail = should_fail
  
      def get_log(self):
          res = '--- command ---\n'
          if self.cmd is None:
              res += 'NONE\n'
          else:
              test_only_env = set(self.env.items()) - set(os.environ.items())
              res += '{}{}\n'.format(env_tuple_to_str(test_only_env), ' '.join(self.cmd))
          if self.stdo:
              res += '--- stdout ---\n'
              res += self.stdo
          if self.stde:
              if res[-1:] != '\n':
                  res += '\n'
              res += '--- stderr ---\n'
              res += self.stde
          if res[-1:] != '\n':
              res += '\n'
          res += '-------\n\n'
          return res
  
  def decode(stream):
      if stream is None:
          return ''
      try:
          return stream.decode('utf-8')
      except UnicodeDecodeError:
          return stream.decode('iso-8859-1', errors='ignore')
  
  def write_json_log(jsonlogfile, test_name, result):
      jresult = {'name': test_name,
                 'stdout': result.stdo,
                 'result': result.res.value,
                 'duration': result.duration,
                 'returncode': result.returncode,
                 'command': result.cmd}
      if isinstance(result.env, dict):
          jresult['env'] = result.env
      else:
          jresult['env'] = result.env.get_env(os.environ)
      if result.stde:
          jresult['stderr'] = result.stde
      jsonlogfile.write(json.dumps(jresult) + '\n')
  
  def run_with_mono(fname):
      if fname.endswith('.exe') and not (is_windows() or is_cygwin()):
          return True
      return False
  
  def load_benchmarks(build_dir):
      datafile = os.path.join(build_dir, 'meson-private', 'meson_benchmark_setup.dat')
      if not os.path.isfile(datafile):
          raise TestException('Directory ${!r} does not seem to be a Meson build directory.'.format(build_dir))
      with open(datafile, 'rb') as f:
          obj = pickle.load(f)
      return obj
  
  def load_tests(build_dir):
      datafile = os.path.join(build_dir, 'meson-private', 'meson_test_setup.dat')
      if not os.path.isfile(datafile):
          raise TestException('Directory ${!r} does not seem to be a Meson build directory.'.format(build_dir))
      with open(datafile, 'rb') as f:
          obj = pickle.load(f)
      return obj
  
  
  class SingleTestRunner:
  
      def __init__(self, test, env, options):
          self.test = test
          self.env = env
          self.options = options
  
      def _get_cmd(self):
          if self.test.fname[0].endswith('.jar'):
              return ['java', '-jar'] + self.test.fname
          elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]):
              return ['mono'] + self.test.fname
          else:
              if self.test.is_cross_built:
                  if self.test.exe_runner is None:
                      # Can not run test on cross compiled executable
                      # because there is no execute wrapper.
                      return None
                  else:
                      if not self.test.exe_runner.found():
                          msg = 'The exe_wrapper defined in the cross file {!r} was not ' \
                                'found. Please check the command and/or add it to PATH.'
                          raise TestException(msg.format(self.test.exe_runner.name))
                      return self.test.exe_runner.get_command() + self.test.fname
              else:
                  return self.test.fname
  
      def run(self):
          cmd = self._get_cmd()
          if cmd is None:
              skip_stdout = 'Not run because can not execute cross compiled binaries.'
              return TestRun(res=TestResult.SKIP, returncode=GNU_SKIP_RETURNCODE,
                             should_fail=self.test.should_fail, duration=0.0,
                             stdo=skip_stdout, stde=None, cmd=None, env=self.test.env)
          else:
              wrap = TestHarness.get_wrapper(self.options)
              if self.options.gdb:
                  self.test.timeout = None
              return self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args)
  
      def _run_cmd(self, cmd):
          starttime = time.time()
  
          if len(self.test.extra_paths) > 0:
              self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH']
              if substring_is_in_list('wine', cmd):
                  wine_paths = ['Z:' + p for p in self.test.extra_paths]
                  wine_path = ';'.join(wine_paths)
                  # Don't accidentally end with an `;` because that will add the
                  # current directory and might cause unexpected behaviour
                  if 'WINEPATH' in self.env:
                      self.env['WINEPATH'] = wine_path + ';' + self.env['WINEPATH']
                  else:
                      self.env['WINEPATH'] = wine_path
  
          # If MALLOC_PERTURB_ is not set, or if it is set to an empty value,
          # (i.e., the test or the environment don't explicitly set it), set
          # it ourselves. We do this unconditionally for regular tests
          # because it is extremely useful to have.
          # Setting MALLOC_PERTURB_="0" will completely disable this feature.
          if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_']) and not self.options.benchmark:
              self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255))
  
          stdout = None
          stderr = None
          if not self.options.verbose:
              stdout = subprocess.PIPE
              stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT
  
          # Let gdb handle ^C instead of us
          if self.options.gdb:
              previous_sigint_handler = signal.getsignal(signal.SIGINT)
              # Make the meson executable ignore SIGINT while gdb is running.
              signal.signal(signal.SIGINT, signal.SIG_IGN)
  
          def preexec_fn():
              if self.options.gdb:
                  # Restore the SIGINT handler for the child process to
                  # ensure it can handle it.
                  signal.signal(signal.SIGINT, signal.SIG_DFL)
              else:
                  # We don't want setsid() in gdb because gdb needs the
                  # terminal in order to handle ^C and not show tcsetpgrp()
                  # errors avoid not being able to use the terminal.
                  os.setsid()
  
          p = subprocess.Popen(cmd,
                               stdout=stdout,
                               stderr=stderr,
                               env=self.env,
                               cwd=self.test.workdir,
                               preexec_fn=preexec_fn if not is_windows() else None)
          timed_out = False
          kill_test = False
          if self.test.timeout is None:
              timeout = None
          elif self.options.timeout_multiplier is not None:
              timeout = self.test.timeout * self.options.timeout_multiplier
          else:
              timeout = self.test.timeout
          try:
              (stdo, stde) = p.communicate(timeout=timeout)
          except subprocess.TimeoutExpired:
              if self.options.verbose:
                  print('%s time out (After %d seconds)' % (self.test.name, timeout))
              timed_out = True
          except KeyboardInterrupt:
              mlog.warning('CTRL-C detected while running %s' % (self.test.name))
              kill_test = True
          finally:
              if self.options.gdb:
                  # Let us accept ^C again
                  signal.signal(signal.SIGINT, previous_sigint_handler)
  
          if kill_test or timed_out:
              # Python does not provide multiplatform support for
              # killing a process and all its children so we need
              # to roll our own.
              if is_windows():
                  subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)])
              else:
                  try:
                      # Kill the process group that setsid() created.
                      os.killpg(p.pid, signal.SIGKILL)
                  except ProcessLookupError:
                      # Sometimes (e.g. with Wine) this happens.
                      # There's nothing we can do (maybe the process
                      # already died) so carry on.
                      pass
              try:
                  (stdo, stde) = p.communicate(timeout=1)
              except subprocess.TimeoutExpired:
                  # An earlier kill attempt has not worked for whatever reason.
                  # Try to kill it one last time with a direct call.
                  # If the process has spawned children, they will remain around.
                  p.kill()
                  try:
                      (stdo, stde) = p.communicate(timeout=1)
                  except subprocess.TimeoutExpired:
                      stdo = b'Test process could not be killed.'
                      stde = b''
              except ValueError:
                  stdo = b'Could not read output. Maybe the process has redirected its stdout/stderr?'
                  stde = b''
          endtime = time.time()
          duration = endtime - starttime
          stdo = decode(stdo)
          if stde:
              stde = decode(stde)
          if timed_out:
              res = TestResult.TIMEOUT
          elif p.returncode == GNU_SKIP_RETURNCODE:
              res = TestResult.SKIP
          elif self.test.should_fail == bool(p.returncode):
              res = TestResult.OK
          else:
              res = TestResult.FAIL
          return TestRun(res, p.returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env)
  
  
  class TestHarness:
      def __init__(self, options):
          self.options = options
          self.collected_logs = []
          self.fail_count = 0
          self.success_count = 0
          self.skip_count = 0
          self.timeout_count = 0
          self.is_run = False
          self.tests = None
          self.suites = None
          self.logfilename = None
          self.logfile = None
          self.jsonlogfile = None
          if self.options.benchmark:
              self.tests = load_benchmarks(options.wd)
          else:
              self.tests = load_tests(options.wd)
          self.load_suites()
  
      def __del__(self):
          if self.logfile:
              self.logfile.close()
          if self.jsonlogfile:
              self.jsonlogfile.close()
  
      def merge_suite_options(self, options, test):
          if ':' in options.setup:
              if options.setup not in self.build_data.test_setups:
                  sys.exit("Unknown test setup '%s'." % options.setup)
              current = self.build_data.test_setups[options.setup]
          else:
              full_name = test.project_name + ":" + options.setup
              if full_name not in self.build_data.test_setups:
                  sys.exit("Test setup '%s' not found from project '%s'." % (options.setup, test.project_name))
              current = self.build_data.test_setups[full_name]
          if not options.gdb:
              options.gdb = current.gdb
          if options.timeout_multiplier is None:
              options.timeout_multiplier = current.timeout_multiplier
      #    if options.env is None:
      #        options.env = current.env # FIXME, should probably merge options here.
          if options.wrapper is not None and current.exe_wrapper is not None:
              sys.exit('Conflict: both test setup and command line specify an exe wrapper.')
          if options.wrapper is None:
              options.wrapper = current.exe_wrapper
          return current.env.get_env(os.environ.copy())
  
      def get_test_runner(self, test):
          options = deepcopy(self.options)
          if options.setup:
              env = self.merge_suite_options(options, test)
          else:
              env = os.environ.copy()
          if isinstance(test.env, build.EnvironmentVariables):
              test.env = test.env.get_env(env)
          env.update(test.env)
          return SingleTestRunner(test, env, options)
  
      def process_test_result(self, result):
          if result.res is TestResult.TIMEOUT:
              self.timeout_count += 1
              self.fail_count += 1
          elif result.res is TestResult.SKIP:
              self.skip_count += 1
          elif result.res is TestResult.OK:
              self.success_count += 1
          elif result.res is TestResult.FAIL:
              self.fail_count += 1
          else:
              sys.exit('Unknown test result encountered: {}'.format(result.res))
  
      def print_stats(self, numlen, tests, name, result, i):
          startpad = ' ' * (numlen - len('%d' % (i + 1)))
          num = '%s%d/%d' % (startpad, i + 1, len(tests))
          padding1 = ' ' * (38 - len(name))
          padding2 = ' ' * (8 - len(result.res.value))
          status = ''
  
          if result.res is TestResult.FAIL:
              status = returncode_to_status(result.returncode)
          result_str = '%s %s  %s%s%s%5.2f s %s' % \
              (num, name, padding1, result.res.value, padding2, result.duration,
               status)
          if not self.options.quiet or result.res is not TestResult.OK:
              if result.res is not TestResult.OK and mlog.colorize_console:
                  if result.res in (TestResult.FAIL, TestResult.TIMEOUT):
                      decorator = mlog.red
                  elif result.res is TestResult.SKIP:
                      decorator = mlog.yellow
                  else:
                      sys.exit('Unreachable code was ... well ... reached.')
                  print(decorator(result_str).get_text(True))
              else:
                  print(result_str)
          result_str += "\n\n" + result.get_log()
          if (result.returncode != GNU_SKIP_RETURNCODE) \
                  and (result.returncode != 0) != result.should_fail:
              if self.options.print_errorlogs:
                  self.collected_logs.append(result_str)
          if self.logfile:
              self.logfile.write(result_str)
          if self.jsonlogfile:
              write_json_log(self.jsonlogfile, name, result)
  
      def print_summary(self):
          msg = '''
  OK:      %4d
  FAIL:    %4d
  SKIP:    %4d
  TIMEOUT: %4d
  ''' % (self.success_count, self.fail_count, self.skip_count, self.timeout_count)
          print(msg)
          if self.logfile:
              self.logfile.write(msg)
  
      def print_collected_logs(self):
          if len(self.collected_logs) > 0:
              if len(self.collected_logs) > 10:
                  print('\nThe output from 10 first failed tests:\n')
              else:
                  print('\nThe output from the failed tests:\n')
              for log in self.collected_logs[:10]:
                  lines = log.splitlines()
                  if len(lines) > 104:
                      print('\n'.join(lines[0:4]))
                      print('--- Listing only the last 100 lines from a long log. ---')
                      lines = lines[-100:]
                  for line in lines:
                      print(line)
  
      def doit(self):
          if self.is_run:
              raise RuntimeError('Test harness object can only be used once.')
          self.is_run = True
          tests = self.get_tests()
          if not tests:
              return 0
          self.run_tests(tests)
          return self.fail_count
  
      @staticmethod
      def split_suite_string(suite):
          if ':' in suite:
              return suite.split(':', 1)
          else:
              return suite, ""
  
      @staticmethod
      def test_in_suites(test, suites):
          for suite in suites:
              (prj_match, st_match) = TestHarness.split_suite_string(suite)
              for prjst in test.suite:
                  (prj, st) = TestHarness.split_suite_string(prjst)
  
                  # the SUITE can be passed as
                  #     suite_name
                  # or
                  #     project_name:suite_name
                  # so we need to select only the test belonging to project_name
  
                  # this if hanlde the first case (i.e., SUITE == suite_name)
  
                  # in this way we can run tests belonging to different
                  # (sub)projects which share the same suite_name
                  if not st_match and st == prj_match:
                      return True
  
                  # these two conditions are needed to handle the second option
                  # i.e., SUITE == project_name:suite_name
  
                  # in this way we select the only the tests of
                  # project_name with suite_name
                  if prj_match and prj != prj_match:
                      continue
                  if st_match and st != st_match:
                      continue
                  return True
          return False
  
      def test_suitable(self, test):
          return (not self.options.include_suites or TestHarness.test_in_suites(test, self.options.include_suites)) \
              and not TestHarness.test_in_suites(test, self.options.exclude_suites)
  
      def load_suites(self):
          ss = set()
          for t in self.tests:
              for s in t.suite:
                  ss.add(s)
          self.suites = list(ss)
  
      def get_tests(self):
          if not self.tests:
              print('No tests defined.')
              return []
  
          if len(self.options.include_suites) or len(self.options.exclude_suites):
              tests = []
              for tst in self.tests:
                  if self.test_suitable(tst):
                      tests.append(tst)
          else:
              tests = self.tests
  
          if self.options.args:
              tests = [t for t in tests if t.name in self.options.args]
  
          if not tests:
              print('No suitable tests defined.')
              return []
  
          for test in tests:
              test.rebuilt = False
  
          return tests
  
      def open_log_files(self):
          if not self.options.logbase or self.options.verbose:
              return None, None, None, None
  
          namebase = None
          logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase)
  
          if self.options.wrapper:
              namebase = os.path.basename(self.get_wrapper(self.options)[0])
          elif self.options.setup:
              namebase = self.options.setup.replace(":", "_")
  
          if namebase:
              logfile_base += '-' + namebase.replace(' ', '_')
          self.logfilename = logfile_base + '.txt'
          self.jsonlogfilename = logfile_base + '.json'
  
          self.jsonlogfile = open(self.jsonlogfilename, 'w', encoding='utf-8')
          self.logfile = open(self.logfilename, 'w', encoding='utf-8')
  
          self.logfile.write('Log of Meson test suite run on %s\n\n'
                             % datetime.datetime.now().isoformat())
          inherit_env = env_tuple_to_str(os.environ.items())
          self.logfile.write('Inherited environment: {}\n\n'.format(inherit_env))
  
      @staticmethod
      def get_wrapper(options):
          wrap = []
          if options.gdb:
              wrap = ['gdb', '--quiet', '--nh']
              if options.repeat > 1:
                  wrap += ['-ex', 'run', '-ex', 'quit']
              # Signal the end of arguments to gdb
              wrap += ['--args']
          if options.wrapper:
              wrap += options.wrapper
          assert(isinstance(wrap, list))
          return wrap
  
      def get_pretty_suite(self, test):
          if len(self.suites) > 1:
              rv = TestHarness.split_suite_string(test.suite[0])[0]
              s = "+".join(TestHarness.split_suite_string(s)[1] for s in test.suite)
              if len(s):
                  rv += ":"
              return rv + s + " / " + test.name
          else:
              return test.name
  
      def run_tests(self, tests):
          executor = None
          futures = []
          numlen = len('%d' % len(tests))
          self.open_log_files()
          startdir = os.getcwd()
          if self.options.wd:
              os.chdir(self.options.wd)
          self.build_data = build.load(os.getcwd())
  
          try:
              for _ in range(self.options.repeat):
                  for i, test in enumerate(tests):
                      visible_name = self.get_pretty_suite(test)
  
                      if not test.is_parallel or self.options.gdb:
                          self.drain_futures(futures)
                          futures = []
                          single_test = self.get_test_runner(test)
                          res = single_test.run()
                          self.process_test_result(res)
                          self.print_stats(numlen, tests, visible_name, res, i)
                      else:
                          if not executor:
                              executor = conc.ThreadPoolExecutor(max_workers=self.options.num_processes)
                          single_test = self.get_test_runner(test)
                          f = executor.submit(single_test.run)
                          futures.append((f, numlen, tests, visible_name, i))
                      if self.options.repeat > 1 and self.fail_count:
                          break
                  if self.options.repeat > 1 and self.fail_count:
                      break
  
              self.drain_futures(futures)
              self.print_summary()
              self.print_collected_logs()
  
              if self.logfilename:
                  print('Full log written to %s' % self.logfilename)
          finally:
              os.chdir(startdir)
  
      def drain_futures(self, futures):
          for i in futures:
              (result, numlen, tests, name, i) = i
              if self.options.repeat > 1 and self.fail_count:
                  result.cancel()
              if self.options.verbose:
                  result.result()
              self.process_test_result(result.result())
              self.print_stats(numlen, tests, name, result.result(), i)
  
      def run_special(self):
          '''Tests run by the user, usually something like "under gdb 1000 times".'''
          if self.is_run:
              raise RuntimeError('Can not use run_special after a full run.')
          tests = self.get_tests()
          if not tests:
              return 0
          self.run_tests(tests)
          return self.fail_count
  
  
  def list_tests(th):
      tests = th.get_tests()
      for t in tests:
          print(th.get_pretty_suite(t))
  
  def rebuild_all(wd):
      if not os.path.isfile(os.path.join(wd, 'build.ninja')):
          print('Only ninja backend is supported to rebuild tests before running them.')
          return True
  
      ninja = environment.detect_ninja()
      if not ninja:
          print("Can't find ninja, can't rebuild test.")
          return False
  
      p = subprocess.Popen([ninja, '-C', wd])
      p.communicate()
  
      if p.returncode != 0:
          print('Could not rebuild')
          return False
  
      return True
  
  def run(args):
      options = buildparser().parse_args(args)
  
      if options.benchmark:
          options.num_processes = 1
  
      if options.verbose and options.quiet:
          print('Can not be both quiet and verbose at the same time.')
          return 1
  
      check_bin = None
      if options.gdb:
          options.verbose = True
          if options.wrapper:
              print('Must not specify both a wrapper and gdb at the same time.')
              return 1
          check_bin = 'gdb'
  
      if options.wrapper:
          check_bin = options.wrapper[0]
  
      if check_bin is not None:
          exe = ExternalProgram(check_bin, silent=True)
          if not exe.found():
              print('Could not find requested program: {!r}'.format(check_bin))
              return 1
      options.wd = os.path.abspath(options.wd)
  
      if not options.list and not options.no_rebuild:
          if not rebuild_all(options.wd):
              return 1
  
      try:
          th = TestHarness(options)
          if options.list:
              list_tests(th)
              return 0
          if not options.args:
              return th.doit()
          return th.run_special()
      except TestException as e:
          print('Meson test encountered an error:\n')
          if os.environ.get('MESON_FORCE_BACKTRACE'):
              raise e
          else:
              print(e)
          return 1
  
files: mesonbuild/mtest.py
files: #!/usr/bin/env python3
  # Copyright 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.
  
  # This class contains the basic functionality needed to run any interpreter
  # or an interpreter-based tool.
  
  # This tool is used to manipulate an existing Meson build definition.
  #
  # - add a file to a target
  # - remove files from a target
  # - move targets
  # - reindent?
  
  import mesonbuild.astinterpreter
  from mesonbuild.mesonlib import MesonException
  from mesonbuild import mlog
  import sys, traceback
  
  def add_arguments(parser):
      parser.add_argument('--sourcedir', default='.',
                          help='Path to source directory.')
      parser.add_argument('--target', default=None,
                          help='Name of target to edit.')
      parser.add_argument('--filename', default=None,
                          help='Name of source file to add or remove to target.')
      parser.add_argument('commands', nargs='+')
  
  def run(options):
      if options.target is None or options.filename is None:
          sys.exit("Must specify both target and filename.")
      print('This tool is highly experimental, use with care.')
      rewriter = mesonbuild.astinterpreter.AstInterpreter(options.sourcedir, '')
      try:
          if options.commands[0] == 'add':
              rewriter.add_source(options.target, options.filename)
          elif options.commands[0] == 'remove':
              rewriter.remove_source(options.target, options.filename)
          else:
              sys.exit('Unknown command: ' + options.commands[0])
      except Exception as e:
          if isinstance(e, MesonException):
              mlog.exception(e)
          else:
              traceback.print_exc()
          return 1
      return 0
  
files: mesonbuild/rewriter.py
files: #!/usr/bin/env python3
  # Copyright 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.
  
  # This class contains the basic functionality needed to run any interpreter
  # or an interpreter-based tool.
  
  # This tool is used to manipulate an existing Meson build definition.
  #
  # - add a file to a target
  # - remove files from a target
  # - move targets
  # - reindent?
  
  import mesonbuild.astinterpreter
  from mesonbuild.mesonlib import MesonException
  from mesonbuild import mlog
  import sys, traceback
  import argparse
  
  def buildparser():
      parser = argparse.ArgumentParser(prog='meson rewrite')
  
      parser.add_argument('--sourcedir', default='.',
                          help='Path to source directory.')
      parser.add_argument('--target', default=None,
                          help='Name of target to edit.')
      parser.add_argument('--filename', default=None,
                          help='Name of source file to add or remove to target.')
      parser.add_argument('commands', nargs='+')
      return parser
  
  def run(args):
      options = buildparser().parse_args(args)
      if options.target is None or options.filename is None:
          sys.exit("Must specify both target and filename.")
      print('This tool is highly experimental, use with care.')
      rewriter = mesonbuild.astinterpreter.AstInterpreter(options.sourcedir, '')
      try:
          if options.commands[0] == 'add':
              rewriter.add_source(options.target, options.filename)
          elif options.commands[0] == 'remove':
              rewriter.remove_source(options.target, options.filename)
          else:
              sys.exit('Unknown command: ' + options.commands[0])
      except Exception as e:
          if isinstance(e, MesonException):
              mlog.exception(e)
          else:
              traceback.print_exc()
          return 1
      return 0
  
files: mesonbuild/rewriter.py
files: # 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
  
  from .. import mesonlib
  
  def add_arguments(parser):
      subparsers = parser.add_subparsers(title='Commands', dest='command')
      subparsers.required = True
  
      p = subparsers.add_parser('list', help='show all available projects')
      p.set_defaults(wrap_func=list_projects)
  
      p = subparsers.add_parser('search', help='search the db by name')
      p.add_argument('name')
      p.set_defaults(wrap_func=search)
  
      p = subparsers.add_parser('install', help='install the specified project')
      p.add_argument('name')
      p.set_defaults(wrap_func=install)
  
      p = subparsers.add_parser('update', help='update the project to its newest available release')
      p.add_argument('name')
      p.set_defaults(wrap_func=update)
  
      p = subparsers.add_parser('info', help='show available versions of a project')
      p.add_argument('name')
      p.set_defaults(wrap_func=info)
  
      p = subparsers.add_parser('status', help='show installed and available versions of your projects')
      p.set_defaults(wrap_func=status)
  
      p = subparsers.add_parser('promote', help='bring a subsubproject up to the master project')
      p.add_argument('project_path')
      p.set_defaults(wrap_func=promote)
  
  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(options):
      projects = get_projectlist()
      for p in projects:
          print(p)
  
  def search(options):
      name = options.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(options):
      name = options.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()
      with open(wrapfile, 'wb') as f:
          f.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(options):
      name = options.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
      with open(wrapfile, 'wb') as f:
          f.write(data)
      print('Updated', name, 'to branch', new_branch, 'revision', new_revision)
  
  def info(options):
      name = options.name
      jd = get_result(API_ROOT + 'projects/' + name)
      versions = jd['versions']
      if not versions:
          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 do_promotion(from_path, spdir_name):
      if os.path.isfile(from_path):
          assert(from_path.endswith('.wrap'))
          shutil.copy(from_path, spdir_name)
      elif os.path.isdir(from_path):
          sproj_name = os.path.basename(from_path)
          outputdir = os.path.join(spdir_name, sproj_name)
          if os.path.exists(outputdir):
              sys.exit('Output dir %s already exists. Will not overwrite.' % outputdir)
          shutil.copytree(from_path, outputdir, ignore=shutil.ignore_patterns('subprojects'))
  
  def promote(options):
      argument = options.project_path
      spdir_name = 'subprojects'
      sprojs = mesonlib.detect_subprojects(spdir_name)
  
      # check if the argument is a full path to a subproject directory or wrap file
      system_native_path_argument = argument.replace('/', os.sep)
      for _, matches in sprojs.items():
          if system_native_path_argument in matches:
              do_promotion(system_native_path_argument, spdir_name)
              return
  
      # otherwise the argument is just a subproject basename which must be unambiguous
      if argument not in sprojs:
          sys.exit('Subproject %s not found in directory tree.' % argument)
      matches = sprojs[argument]
      if len(matches) > 1:
          print('There is more than one version of %s in tree. Please specify which one to promote:\n' % argument)
          for s in matches:
              print(s)
          sys.exit(1)
      do_promotion(matches[0], spdir_name)
  
  def status(options):
      print('Subproject status')
      for w in glob('subprojects/*.wrap'):
          name = os.path.basename(w)[:-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(options):
      options.wrap_func(options)
      return 0
  
files: mesonbuild/wrap/wraptool.py
files: # 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
  import argparse
  
  from glob import glob
  
  from .wrap import API_ROOT, open_wrapdburl
  
  from .. import mesonlib
  
  def add_arguments(parser):
      subparsers = parser.add_subparsers(title='Commands', dest='command')
      subparsers.required = True
  
      p = subparsers.add_parser('list', help='show all available projects')
      p.set_defaults(wrap_func=list_projects)
  
      p = subparsers.add_parser('search', help='search the db by name')
      p.add_argument('name')
      p.set_defaults(wrap_func=search)
  
      p = subparsers.add_parser('install', help='install the specified project')
      p.add_argument('name')
      p.set_defaults(wrap_func=install)
  
      p = subparsers.add_parser('update', help='update the project to its newest available release')
      p.add_argument('name')
      p.set_defaults(wrap_func=update)
  
      p = subparsers.add_parser('info', help='show available versions of a project')
      p.add_argument('name')
      p.set_defaults(wrap_func=info)
  
      p = subparsers.add_parser('status', help='show installed and available versions of your projects')
      p.set_defaults(wrap_func=status)
  
      p = subparsers.add_parser('promote', help='bring a subsubproject up to the master project')
      p.add_argument('project_path')
      p.set_defaults(wrap_func=promote)
  
  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(options):
      projects = get_projectlist()
      for p in projects:
          print(p)
  
  def search(options):
      name = options.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(options):
      name = options.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()
      with open(wrapfile, 'wb') as f:
          f.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(options):
      name = options.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
      with open(wrapfile, 'wb') as f:
          f.write(data)
      print('Updated', name, 'to branch', new_branch, 'revision', new_revision)
  
  def info(options):
      name = options.name
      jd = get_result(API_ROOT + 'projects/' + name)
      versions = jd['versions']
      if not versions:
          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 do_promotion(from_path, spdir_name):
      if os.path.isfile(from_path):
          assert(from_path.endswith('.wrap'))
          shutil.copy(from_path, spdir_name)
      elif os.path.isdir(from_path):
          sproj_name = os.path.basename(from_path)
          outputdir = os.path.join(spdir_name, sproj_name)
          if os.path.exists(outputdir):
              sys.exit('Output dir %s already exists. Will not overwrite.' % outputdir)
          shutil.copytree(from_path, outputdir, ignore=shutil.ignore_patterns('subprojects'))
  
  def promote(options):
      argument = options.project_path
      spdir_name = 'subprojects'
      sprojs = mesonlib.detect_subprojects(spdir_name)
  
      # check if the argument is a full path to a subproject directory or wrap file
      system_native_path_argument = argument.replace('/', os.sep)
      for _, matches in sprojs.items():
          if system_native_path_argument in matches:
              do_promotion(system_native_path_argument, spdir_name)
              return
  
      # otherwise the argument is just a subproject basename which must be unambiguous
      if argument not in sprojs:
          sys.exit('Subproject %s not found in directory tree.' % argument)
      matches = sprojs[argument]
      if len(matches) > 1:
          print('There is more than one version of %s in tree. Please specify which one to promote:\n' % argument)
          for s in matches:
              print(s)
          sys.exit(1)
      do_promotion(matches[0], spdir_name)
  
  def status(options):
      print('Subproject status')
      for w in glob('subprojects/*.wrap'):
          name = os.path.basename(w)[:-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):
      parser = argparse.ArgumentParser(prog='wraptool')
      add_arguments(parser)
      options = parser.parse_args(args)
      options.wrap_func(options)
      return 0
  
files: mesonbuild/wrap/wraptool.py
files: #!/usr/bin/env python3
  
  # Copyright 2012-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 itertools
  import os
  import subprocess
  import shutil
  import sys
  import signal
  from io import StringIO
  from ast import literal_eval
  from enum import Enum
  import tempfile
  from pathlib import Path, PurePath
  from mesonbuild import build
  from mesonbuild import environment
  from mesonbuild import mesonlib
  from mesonbuild import mlog
  from mesonbuild import mtest
  from mesonbuild.mesonlib import stringlistify, Popen_safe
  from mesonbuild.coredata import backendlist
  import argparse
  import xml.etree.ElementTree as ET
  import time
  import multiprocessing
  from concurrent.futures import ProcessPoolExecutor
  import re
  from run_tests import get_fake_options, run_configure, get_meson_script
  from run_tests import get_backend_commands, get_backend_args_for_dir, Backend
  from run_tests import ensure_backend_detects_changes
  
  
  class BuildStep(Enum):
      configure = 1
      build = 2
      test = 3
      install = 4
      clean = 5
      validate = 6
  
  
  class TestResult:
      def __init__(self, msg, step, stdo, stde, mlog, conftime=0, buildtime=0, testtime=0):
          self.msg = msg
          self.step = step
          self.stdo = stdo
          self.stde = stde
          self.mlog = mlog
          self.conftime = conftime
          self.buildtime = buildtime
          self.testtime = testtime
  
  
  class AutoDeletedDir:
      def __init__(self, d):
          self.dir = d
  
      def __enter__(self):
          os.makedirs(self.dir, exist_ok=True)
          return self.dir
  
      def __exit__(self, _type, value, traceback):
          # We don't use tempfile.TemporaryDirectory, but wrap the
          # deletion in the AutoDeletedDir class because
          # it fails on Windows due antivirus programs
          # holding files open.
          mesonlib.windows_proof_rmtree(self.dir)
  
  failing_logs = []
  print_debug = 'MESON_PRINT_TEST_OUTPUT' in os.environ
  under_ci = not {'TRAVIS', 'APPVEYOR'}.isdisjoint(os.environ)
  do_debug = under_ci or print_debug
  no_meson_log_msg = 'No meson-log.txt found.'
  
  system_compiler = None
  
  class StopException(Exception):
      def __init__(self):
          super().__init__('Stopped by user')
  
  stop = False
  def stop_handler(signal, frame):
      global stop
      stop = True
  signal.signal(signal.SIGINT, stop_handler)
  signal.signal(signal.SIGTERM, stop_handler)
  
  def setup_commands(optbackend):
      global do_debug, backend, backend_flags
      global compile_commands, clean_commands, test_commands, install_commands, uninstall_commands
      backend = optbackend
      msbuild_exe = shutil.which('msbuild')
      # Auto-detect backend if unspecified
      if backend is None:
          if msbuild_exe is not None:
              backend = 'vs' # Meson will auto-detect VS version to use
          else:
              backend = 'ninja'
      # Set backend arguments for Meson
      if backend.startswith('vs'):
          backend_flags = ['--backend=' + backend]
          backend = Backend.vs
      elif backend == 'xcode':
          backend_flags = ['--backend=xcode']
          backend = Backend.xcode
      elif backend == 'ninja':
          backend_flags = ['--backend=ninja']
          backend = Backend.ninja
      else:
          raise RuntimeError('Unknown backend: {!r}'.format(backend))
      compile_commands, clean_commands, test_commands, install_commands, \
          uninstall_commands = get_backend_commands(backend, do_debug)
  
  def get_relative_files_list_from_dir(fromdir):
      paths = []
      for (root, _, files) in os.walk(fromdir):
          reldir = os.path.relpath(root, start=fromdir)
          for f in files:
              path = os.path.join(reldir, f).replace('\\', '/')
              if path.startswith('./'):
                  path = path[2:]
              paths.append(path)
      return paths
  
  def platform_fix_name(fname, compiler, env):
      if '?lib' in fname:
          if mesonlib.for_cygwin(env.is_cross_build(), env):
              fname = re.sub(r'lib/\?lib(.*)\.so$', r'bin/cyg\1.dll', fname)
              fname = re.sub(r'\?lib(.*)\.dll$', r'cyg\1.dll', fname)
          else:
              fname = re.sub(r'\?lib', 'lib', fname)
  
      if fname.endswith('?exe'):
          fname = fname[:-4]
          if mesonlib.for_windows(env.is_cross_build(), env) or mesonlib.for_cygwin(env.is_cross_build(), env):
              return fname + '.exe'
  
      if fname.startswith('?msvc:'):
          fname = fname[6:]
          if compiler != 'cl':
              return None
  
      if fname.startswith('?gcc:'):
          fname = fname[5:]
          if compiler == 'cl':
              return None
  
      if fname.startswith('?cygwin:'):
          fname = fname[8:]
          if compiler == 'cl' or not mesonlib.for_cygwin(env.is_cross_build(), env):
              return None
  
      return fname
  
  def validate_install(srcdir, installdir, compiler, env):
      # List of installed files
      info_file = os.path.join(srcdir, 'installed_files.txt')
      # If this exists, the test does not install any other files
      noinst_file = 'usr/no-installed-files'
      expected = {}
      ret_msg = ''
      # Generate list of expected files
      if os.path.exists(os.path.join(installdir, noinst_file)):
          expected[noinst_file] = False
      elif os.path.exists(info_file):
          with open(info_file) as f:
              for line in f:
                  line = platform_fix_name(line.strip(), compiler, env)
                  if line:
                      expected[line] = False
      # Check if expected files were found
      for fname in expected:
          file_path = os.path.join(installdir, fname)
          if os.path.exists(file_path) or os.path.islink(file_path):
              expected[fname] = True
      for (fname, found) in expected.items():
          if not found:
              ret_msg += 'Expected file {0} missing.\n'.format(fname)
      # Check if there are any unexpected files
      found = get_relative_files_list_from_dir(installdir)
      for fname in found:
          if fname not in expected:
              ret_msg += 'Extra file {0} found.\n'.format(fname)
      return ret_msg
  
  def log_text_file(logfile, testdir, stdo, stde):
      global stop, executor, futures
      logfile.write('%s\nstdout\n\n---\n' % testdir.as_posix())
      logfile.write(stdo)
      logfile.write('\n\n---\n\nstderr\n\n---\n')
      logfile.write(stde)
      logfile.write('\n\n---\n\n')
      if print_debug:
          try:
              print(stdo)
          except UnicodeError:
              sanitized_out = stdo.encode('ascii', errors='replace').decode()
              print(sanitized_out)
          try:
              print(stde, file=sys.stderr)
          except UnicodeError:
              sanitized_err = stde.encode('ascii', errors='replace').decode()
              print(sanitized_err, file=sys.stderr)
      if stop:
          print("Aborting..")
          for f in futures:
              f[2].cancel()
          executor.shutdown()
          raise StopException()
  
  
  def bold(text):
      return mlog.bold(text).get_text(mlog.colorize_console)
  
  
  def green(text):
      return mlog.green(text).get_text(mlog.colorize_console)
  
  
  def red(text):
      return mlog.red(text).get_text(mlog.colorize_console)
  
  
  def yellow(text):
      return mlog.yellow(text).get_text(mlog.colorize_console)
  
  
  def run_test_inprocess(testdir):
      old_stdout = sys.stdout
      sys.stdout = mystdout = StringIO()
      old_stderr = sys.stderr
      sys.stderr = mystderr = StringIO()
      old_cwd = os.getcwd()
      os.chdir(testdir)
      test_log_fname = Path('meson-logs', 'testlog.txt')
      try:
          returncode_test = mtest.run_with_args(['--no-rebuild'])
          if test_log_fname.exists():
              test_log = test_log_fname.open(errors='ignore').read()
          else:
              test_log = ''
          returncode_benchmark = mtest.run_with_args(['--no-rebuild', '--benchmark', '--logbase', 'benchmarklog'])
      finally:
          sys.stdout = old_stdout
          sys.stderr = old_stderr
          os.chdir(old_cwd)
      return max(returncode_test, returncode_benchmark), mystdout.getvalue(), mystderr.getvalue(), test_log
  
  def parse_test_args(testdir):
      args = []
      try:
          with open(os.path.join(testdir, 'test_args.txt'), 'r') as f:
              content = f.read()
              try:
                  args = literal_eval(content)
              except Exception:
                  raise Exception('Malformed test_args file.')
              args = stringlistify(args)
      except FileNotFoundError:
          pass
      return args
  
  # Build directory name must be the same so CCache works over
  # consecutive invocations.
  def create_deterministic_builddir(src_dir):
      import hashlib
      rel_dirname = 'b ' + hashlib.sha256(src_dir.encode(errors='ignore')).hexdigest()[0:10]
      os.mkdir(rel_dirname)
      abs_pathname = os.path.join(os.getcwd(), rel_dirname)
      return abs_pathname
  
  def run_test(skipped, testdir, extra_args, compiler, backend, flags, commands, should_fail):
      if skipped:
          return None
      with AutoDeletedDir(create_deterministic_builddir(testdir)) as build_dir:
          with AutoDeletedDir(tempfile.mkdtemp(prefix='i ', dir=os.getcwd())) as install_dir:
              try:
                  return _run_test(testdir, build_dir, install_dir, extra_args, compiler, backend, flags, commands, should_fail)
              finally:
                  mlog.shutdown() # Close the log file because otherwise Windows wets itself.
  
  def pass_prefix_to_test(dirname):
      if '39 prefix absolute' in dirname:
          return False
      return True
  
  def pass_libdir_to_test(dirname):
      if '8 install' in dirname:
          return False
      if '38 libdir must be inside prefix' in dirname:
          return False
      if '196 install_mode' in dirname:
          return False
      return True
  
  def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backend, flags, commands, should_fail):
      compile_commands, clean_commands, install_commands, uninstall_commands = commands
      test_args = parse_test_args(testdir)
      gen_start = time.time()
      # Configure in-process
      if pass_prefix_to_test(testdir):
          gen_args = ['--prefix', '/usr']
      else:
          gen_args = []
      if pass_libdir_to_test(testdir):
          gen_args += ['--libdir', 'lib']
      gen_args += [testdir, test_build_dir] + flags + test_args + extra_args
      (returncode, stdo, stde) = run_configure(gen_args)
      try:
          logfile = Path(test_build_dir, 'meson-logs', 'meson-log.txt')
          mesonlog = logfile.open(errors='ignore', encoding='utf-8').read()
      except Exception:
          mesonlog = no_meson_log_msg
      gen_time = time.time() - gen_start
      if should_fail == 'meson':
          if returncode == 1:
              return TestResult('', BuildStep.configure, stdo, stde, mesonlog, gen_time)
          elif returncode != 0:
              return TestResult('Test exited with unexpected status {}'.format(returncode), BuildStep.configure, stdo, stde, mesonlog, gen_time)
          else:
              return TestResult('Test that should have failed succeeded', BuildStep.configure, stdo, stde, mesonlog, gen_time)
      if returncode != 0:
          return TestResult('Generating the build system failed.', BuildStep.configure, stdo, stde, mesonlog, gen_time)
      builddata = build.load(test_build_dir)
      # Touch the meson.build file to force a regenerate so we can test that
      # regeneration works before a build is run.
      ensure_backend_detects_changes(backend)
      os.utime(os.path.join(testdir, 'meson.build'))
      # Build with subprocess
      dir_args = get_backend_args_for_dir(backend, test_build_dir)
      build_start = time.time()
      pc, o, e = Popen_safe(compile_commands + dir_args, cwd=test_build_dir)
      build_time = time.time() - build_start
      stdo += o
      stde += e
      if should_fail == 'build':
          if pc.returncode != 0:
              return TestResult('', BuildStep.build, stdo, stde, mesonlog, gen_time)
          return TestResult('Test that should have failed to build succeeded', BuildStep.build, stdo, stde, mesonlog, gen_time)
      if pc.returncode != 0:
          return TestResult('Compiling source code failed.', BuildStep.build, stdo, stde, mesonlog, gen_time, build_time)
      # Touch the meson.build file to force a regenerate so we can test that
      # regeneration works after a build is complete.
      ensure_backend_detects_changes(backend)
      os.utime(os.path.join(testdir, 'meson.build'))
      test_start = time.time()
      # Test in-process
      (returncode, tstdo, tstde, test_log) = run_test_inprocess(test_build_dir)
      test_time = time.time() - test_start
      stdo += tstdo
      stde += tstde
      mesonlog += test_log
      if should_fail == 'test':
          if returncode != 0:
              return TestResult('', BuildStep.test, stdo, stde, mesonlog, gen_time)
          return TestResult('Test that should have failed to run unit tests succeeded', BuildStep.test, stdo, stde, mesonlog, gen_time)
      if returncode != 0:
          return TestResult('Running unit tests failed.', BuildStep.test, stdo, stde, mesonlog, gen_time, build_time, test_time)
      # Do installation, if the backend supports it
      if install_commands:
          env = os.environ.copy()
          env['DESTDIR'] = install_dir
          # Install with subprocess
          pi, o, e = Popen_safe(install_commands, cwd=test_build_dir, env=env)
          stdo += o
          stde += e
          if pi.returncode != 0:
              return TestResult('Running install failed.', BuildStep.install, stdo, stde, mesonlog, gen_time, build_time, test_time)
      # Clean with subprocess
      env = os.environ.copy()
      pi, o, e = Popen_safe(clean_commands + dir_args, cwd=test_build_dir, env=env)
      stdo += o
      stde += e
      if pi.returncode != 0:
          return TestResult('Running clean failed.', BuildStep.clean, stdo, stde, mesonlog, gen_time, build_time, test_time)
      if not install_commands:
          return TestResult('', BuildStep.install, '', '', mesonlog, gen_time, build_time, test_time)
      return TestResult(validate_install(testdir, install_dir, compiler, builddata.environment),
                        BuildStep.validate, stdo, stde, mesonlog, gen_time, build_time, test_time)
  
  def gather_tests(testdir: Path):
      tests = [t.name for t in testdir.glob('*')]
      tests = [t for t in tests if not t.startswith('.')] # Filter non-tests files (dot files, etc)
      testlist = [(int(t.split()[0]), t) for t in tests]
      testlist.sort()
      tests = [testdir / t[1] for t in testlist]
      return tests
  
  def have_d_compiler():
      if shutil.which("ldc2"):
          return True
      elif shutil.which("ldc"):
          return True
      elif shutil.which("gdc"):
          return True
      elif shutil.which("dmd"):
          return True
      return False
  
  def have_objc_compiler():
      with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir:
          env = environment.Environment(None, build_dir, get_fake_options('/'))
          try:
              objc_comp = env.detect_objc_compiler(False)
          except mesonlib.MesonException:
              return False
          if not objc_comp:
              return False
          try:
              objc_comp.sanity_check(env.get_scratch_dir(), env)
          except mesonlib.MesonException:
              return False
      return True
  
  def have_objcpp_compiler():
      with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir:
          env = environment.Environment(None, build_dir, get_fake_options('/'))
          try:
              objcpp_comp = env.detect_objcpp_compiler(False)
          except mesonlib.MesonException:
              return False
          if not objcpp_comp:
              return False
          try:
              objcpp_comp.sanity_check(env.get_scratch_dir(), env)
          except mesonlib.MesonException:
              return False
      return True
  
  def have_java():
      if shutil.which('javac') and shutil.which('java'):
          return True
      return False
  
  def skippable(suite, test):
      if not under_ci:
          return True
  
      if not suite.endswith('frameworks'):
          return True
  
      # gtk-doc test may be skipped, pending upstream fixes for spaces in
      # filenames landing in the distro used for CI
      if test.endswith('10 gtk-doc'):
          return True
  
      # No frameworks test should be skipped on linux CI, as we expect all
      # prerequisites to be installed
      if mesonlib.is_linux():
          return False
  
      # Boost test should only be skipped for windows CI build matrix entries
      # which don't define BOOST_ROOT
      if test.endswith('1 boost'):
          if mesonlib.is_windows():
              return 'BOOST_ROOT' not in os.environ
          return False
  
      # Other framework tests are allowed to be skipped on other platforms
      return True
  
  def skip_csharp(backend):
      if backend is not Backend.ninja:
          return True
      if not shutil.which('resgen'):
          return True
      if shutil.which('mcs'):
          return False
      if shutil.which('csc'):
          # Only support VS2017 for now. Earlier versions fail
          # under CI in mysterious ways.
          try:
              stdo = subprocess.check_output(['csc', '/version'])
          except subprocess.CalledProcessError:
              return True
          # Having incrementing version numbers would be too easy.
          # Microsoft reset the versioning back to 1.0 (from 4.x)
          # when they got the Roslyn based compiler. Thus there
          # is NO WAY to reliably do version number comparisons.
          # Only support the version that ships with VS2017.
          return not stdo.startswith(b'2.')
      return True
  
  def detect_tests_to_run():
      # Name, subdirectory, skip condition.
      all_tests = [
          ('common', 'common', False),
          ('failing-meson', 'failing', False),
          ('failing-build', 'failing build', False),
          ('failing-test',  'failing test', False),
  
          ('platform-osx', 'osx', not mesonlib.is_osx()),
          ('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()),
          ('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()),
  
          ('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()),
          ('C#', 'csharp', skip_csharp(backend)),
          ('vala', 'vala', backend is not Backend.ninja or not shutil.which('valac')),
          ('rust', 'rust', backend is not Backend.ninja or not shutil.which('rustc')),
          ('d', 'd', backend is not Backend.ninja or not have_d_compiler()),
          ('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or mesonlib.is_windows() or not have_objc_compiler()),
          ('objective c++', 'objcpp', backend not in (Backend.ninja, Backend.xcode) or mesonlib.is_windows() or not have_objcpp_compiler()),
          ('fortran', 'fortran', backend is not Backend.ninja or not shutil.which('gfortran')),
          ('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')),
          ('python3', 'python3', backend is not Backend.ninja),
          ('fpga', 'fpga', shutil.which('yosys') is None),
          ('frameworks', 'frameworks', False),
          ('nasm', 'nasm', False),
      ]
      gathered_tests = [(name, gather_tests(Path('test cases', subdir)), skip) for name, subdir, skip in all_tests]
      return gathered_tests
  
  def run_tests(all_tests, log_name_base, extra_args):
      global logfile
      txtname = log_name_base + '.txt'
      with open(txtname, 'w', encoding='utf-8', errors='ignore') as lf:
          logfile = lf
          return _run_tests(all_tests, log_name_base, extra_args)
  
  def _run_tests(all_tests, log_name_base, extra_args):
      global stop, executor, futures, system_compiler
      xmlname = log_name_base + '.xml'
      junit_root = ET.Element('testsuites')
      conf_time = 0
      build_time = 0
      test_time = 0
      passing_tests = 0
      failing_tests = 0
      skipped_tests = 0
      commands = (compile_commands, clean_commands, install_commands, uninstall_commands)
  
      try:
          # This fails in some CI environments for unknown reasons.
          num_workers = multiprocessing.cpu_count()
      except Exception as e:
          print('Could not determine number of CPUs due to the following reason:' + str(e))
          print('Defaulting to using only one process')
          num_workers = 1
      # Due to Ninja deficiency, almost 50% of build time
      # is spent waiting. Do something useful instead.
      #
      # Remove this once the following issue has been resolved:
      # https://github.com/mesonbuild/meson/pull/2082
      num_workers *= 2
      executor = ProcessPoolExecutor(max_workers=num_workers)
  
      for name, test_cases, skipped in all_tests:
          current_suite = ET.SubElement(junit_root, 'testsuite', {'name': name, 'tests': str(len(test_cases))})
          print()
          if skipped:
              print(bold('Not running %s tests.' % name))
          else:
              print(bold('Running %s tests.' % name))
          print()
          futures = []
          for t in test_cases:
              # Jenkins screws us over by automatically sorting test cases by name
              # and getting it wrong by not doing logical number sorting.
              (testnum, testbase) = t.name.split(' ', 1)
              testname = '%.3d %s' % (int(testnum), testbase)
              should_fail = False
              if name.startswith('failing'):
                  should_fail = name.split('failing-')[1]
              result = executor.submit(run_test, skipped, t.as_posix(), extra_args, system_compiler, backend, backend_flags, commands, should_fail)
              futures.append((testname, t, result))
          for (testname, t, result) in futures:
              sys.stdout.flush()
              result = result.result()
              if (result is None) or (('MESON_SKIP_TEST' in result.stdo) and (skippable(name, t.as_posix()))):
                  print(yellow('Skipping:'), t.as_posix())
                  current_test = ET.SubElement(current_suite, 'testcase', {'name': testname,
                                                                           'classname': name})
                  ET.SubElement(current_test, 'skipped', {})
                  skipped_tests += 1
              else:
                  without_install = "" if len(install_commands) > 0 else " (without install)"
                  if result.msg != '':
                      print(red('Failed test{} during {}: {!r}'.format(without_install, result.step.name, t.as_posix())))
                      print('Reason:', result.msg)
                      failing_tests += 1
                      if result.step == BuildStep.configure and result.mlog != no_meson_log_msg:
                          # For configure failures, instead of printing stdout,
                          # print the meson log if available since it's a superset
                          # of stdout and often has very useful information.
                          failing_logs.append(result.mlog)
                      else:
                          failing_logs.append(result.stdo)
                      failing_logs.append(result.stde)
                  else:
                      print('Succeeded test%s: %s' % (without_install, t.as_posix()))
                      passing_tests += 1
                  conf_time += result.conftime
                  build_time += result.buildtime
                  test_time += result.testtime
                  total_time = conf_time + build_time + test_time
                  log_text_file(logfile, t, result.stdo, result.stde)
                  current_test = ET.SubElement(current_suite, 'testcase', {'name': testname,
                                                                           'classname': name,
                                                                           'time': '%.3f' % total_time})
                  if result.msg != '':
                      ET.SubElement(current_test, 'failure', {'message': result.msg})
                  stdoel = ET.SubElement(current_test, 'system-out')
                  stdoel.text = result.stdo
                  stdeel = ET.SubElement(current_test, 'system-err')
                  stdeel.text = result.stde
      print("\nTotal configuration time: %.2fs" % conf_time)
      print("Total build time: %.2fs" % build_time)
      print("Total test time: %.2fs" % test_time)
      ET.ElementTree(element=junit_root).write(xmlname, xml_declaration=True, encoding='UTF-8')
      return passing_tests, failing_tests, skipped_tests
  
  def check_file(fname):
      linenum = 1
      with open(fname, 'rb') as f:
          lines = f.readlines()
      for line in lines:
          if line.startswith(b'\t'):
              print("File %s contains a literal tab on line %d. Only spaces are permitted." % (fname, linenum))
              sys.exit(1)
          if b'\r' in line:
              print("File %s contains DOS line ending on line %d. Only unix-style line endings are permitted." % (fname, linenum))
              sys.exit(1)
          linenum += 1
  
  def check_format():
      check_suffixes = {'.c',
                        '.cpp',
                        '.cxx',
                        '.cc',
                        '.rs',
                        '.f90',
                        '.vala',
                        '.d',
                        '.s',
                        '.m',
                        '.mm',
                        '.asm',
                        '.java',
                        '.txt',
                        '.py',
                        '.swift',
                        '.build',
                        '.md',
                        }
      for (root, _, files) in os.walk('.'):
          if '.dub' in root: # external deps are here
              continue
          for fname in files:
              if os.path.splitext(fname)[1].lower() in check_suffixes:
                  bn = os.path.basename(fname)
                  if bn == 'sitemap.txt' or bn == 'meson-test-run.txt':
                      continue
                  fullname = os.path.join(root, fname)
                  check_file(fullname)
  
  def check_meson_commands_work():
      global backend, compile_commands, test_commands, install_commands
      testdir = PurePath('test cases', 'common', '1 trivial').as_posix()
      meson_commands = mesonlib.python_command + [get_meson_script()]
      with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir:
          print('Checking that configuring works...')
          gen_cmd = meson_commands + [testdir, build_dir] + backend_flags
          pc, o, e = Popen_safe(gen_cmd)
          if pc.returncode != 0:
              raise RuntimeError('Failed to configure {!r}:\n{}\n{}'.format(testdir, e, o))
          print('Checking that building works...')
          dir_args = get_backend_args_for_dir(backend, build_dir)
          pc, o, e = Popen_safe(compile_commands + dir_args, cwd=build_dir)
          if pc.returncode != 0:
              raise RuntimeError('Failed to build {!r}:\n{}\n{}'.format(testdir, e, o))
          print('Checking that testing works...')
          pc, o, e = Popen_safe(test_commands, cwd=build_dir)
          if pc.returncode != 0:
              raise RuntimeError('Failed to test {!r}:\n{}\n{}'.format(testdir, e, o))
          if install_commands:
              print('Checking that installing works...')
              pc, o, e = Popen_safe(install_commands, cwd=build_dir)
              if pc.returncode != 0:
                  raise RuntimeError('Failed to install {!r}:\n{}\n{}'.format(testdir, e, o))
  
  
  def detect_system_compiler():
      global system_compiler
      if shutil.which('cl'):
          system_compiler = 'cl'
      elif shutil.which('cc'):
          system_compiler = 'cc'
      elif shutil.which('gcc'):
          system_compiler = 'gcc'
      else:
          raise RuntimeError("Could not find C compiler.")
  
  if __name__ == '__main__':
      parser = argparse.ArgumentParser(description="Run the test suite of Meson.")
      parser.add_argument('extra_args', nargs='*',
                          help='arguments that are passed directly to Meson (remember to have -- before these).')
      parser.add_argument('--backend', default=None, dest='backend',
                          choices=backendlist)
      options = parser.parse_args()
      setup_commands(options.backend)
  
      detect_system_compiler()
      script_dir = os.path.split(__file__)[0]
      if script_dir != '':
          os.chdir(script_dir)
      check_format()
      check_meson_commands_work()
      try:
          all_tests = detect_tests_to_run()
          (passing_tests, failing_tests, skipped_tests) = run_tests(all_tests, 'meson-test-run', options.extra_args)
      except StopException:
          pass
      print('\nTotal passed tests:', green(str(passing_tests)))
      print('Total failed tests:', red(str(failing_tests)))
      print('Total skipped tests:', yellow(str(skipped_tests)))
      if failing_tests > 0:
          print('\nMesonlogs of failing tests\n')
          for l in failing_logs:
              try:
                  print(l, '\n')
              except UnicodeError:
                  print(l.encode('ascii', errors='replace').decode(), '\n')
      for name, dirs, skip in all_tests:
          dirs = (x.name for x in dirs)
          for k, g in itertools.groupby(dirs, key=lambda x: x.split()[0]):
              tests = list(g)
              if len(tests) != 1:
                  print('WARNING: The %s suite contains duplicate "%s" tests: "%s"' % (name, k, '", "'.join(tests)))
      sys.exit(failing_tests)
  
files: run_project_tests.py
files: #!/usr/bin/env python3
  
  # Copyright 2012-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 itertools
  import os
  import subprocess
  import shutil
  import sys
  import signal
  from io import StringIO
  from ast import literal_eval
  from enum import Enum
  import tempfile
  from pathlib import Path, PurePath
  from mesonbuild import build
  from mesonbuild import environment
  from mesonbuild import mesonlib
  from mesonbuild import mlog
  from mesonbuild import mtest
  from mesonbuild.mesonlib import stringlistify, Popen_safe
  from mesonbuild.coredata import backendlist
  import argparse
  import xml.etree.ElementTree as ET
  import time
  import multiprocessing
  from concurrent.futures import ProcessPoolExecutor
  import re
  from run_tests import get_fake_options, run_configure, get_meson_script
  from run_tests import get_backend_commands, get_backend_args_for_dir, Backend
  from run_tests import ensure_backend_detects_changes
  
  
  class BuildStep(Enum):
      configure = 1
      build = 2
      test = 3
      install = 4
      clean = 5
      validate = 6
  
  
  class TestResult:
      def __init__(self, msg, step, stdo, stde, mlog, conftime=0, buildtime=0, testtime=0):
          self.msg = msg
          self.step = step
          self.stdo = stdo
          self.stde = stde
          self.mlog = mlog
          self.conftime = conftime
          self.buildtime = buildtime
          self.testtime = testtime
  
  
  class AutoDeletedDir:
      def __init__(self, d):
          self.dir = d
  
      def __enter__(self):
          os.makedirs(self.dir, exist_ok=True)
          return self.dir
  
      def __exit__(self, _type, value, traceback):
          # We don't use tempfile.TemporaryDirectory, but wrap the
          # deletion in the AutoDeletedDir class because
          # it fails on Windows due antivirus programs
          # holding files open.
          mesonlib.windows_proof_rmtree(self.dir)
  
  failing_logs = []
  print_debug = 'MESON_PRINT_TEST_OUTPUT' in os.environ
  under_ci = not {'TRAVIS', 'APPVEYOR'}.isdisjoint(os.environ)
  do_debug = under_ci or print_debug
  no_meson_log_msg = 'No meson-log.txt found.'
  
  system_compiler = None
  
  class StopException(Exception):
      def __init__(self):
          super().__init__('Stopped by user')
  
  stop = False
  def stop_handler(signal, frame):
      global stop
      stop = True
  signal.signal(signal.SIGINT, stop_handler)
  signal.signal(signal.SIGTERM, stop_handler)
  
  def setup_commands(optbackend):
      global do_debug, backend, backend_flags
      global compile_commands, clean_commands, test_commands, install_commands, uninstall_commands
      backend = optbackend
      msbuild_exe = shutil.which('msbuild')
      # Auto-detect backend if unspecified
      if backend is None:
          if msbuild_exe is not None:
              backend = 'vs' # Meson will auto-detect VS version to use
          else:
              backend = 'ninja'
      # Set backend arguments for Meson
      if backend.startswith('vs'):
          backend_flags = ['--backend=' + backend]
          backend = Backend.vs
      elif backend == 'xcode':
          backend_flags = ['--backend=xcode']
          backend = Backend.xcode
      elif backend == 'ninja':
          backend_flags = ['--backend=ninja']
          backend = Backend.ninja
      else:
          raise RuntimeError('Unknown backend: {!r}'.format(backend))
      compile_commands, clean_commands, test_commands, install_commands, \
          uninstall_commands = get_backend_commands(backend, do_debug)
  
  def get_relative_files_list_from_dir(fromdir):
      paths = []
      for (root, _, files) in os.walk(fromdir):
          reldir = os.path.relpath(root, start=fromdir)
          for f in files:
              path = os.path.join(reldir, f).replace('\\', '/')
              if path.startswith('./'):
                  path = path[2:]
              paths.append(path)
      return paths
  
  def platform_fix_name(fname, compiler, env):
      if '?lib' in fname:
          if mesonlib.for_cygwin(env.is_cross_build(), env):
              fname = re.sub(r'lib/\?lib(.*)\.so$', r'bin/cyg\1.dll', fname)
              fname = re.sub(r'\?lib(.*)\.dll$', r'cyg\1.dll', fname)
          else:
              fname = re.sub(r'\?lib', 'lib', fname)
  
      if fname.endswith('?exe'):
          fname = fname[:-4]
          if mesonlib.for_windows(env.is_cross_build(), env) or mesonlib.for_cygwin(env.is_cross_build(), env):
              return fname + '.exe'
  
      if fname.startswith('?msvc:'):
          fname = fname[6:]
          if compiler != 'cl':
              return None
  
      if fname.startswith('?gcc:'):
          fname = fname[5:]
          if compiler == 'cl':
              return None
  
      if fname.startswith('?cygwin:'):
          fname = fname[8:]
          if compiler == 'cl' or not mesonlib.for_cygwin(env.is_cross_build(), env):
              return None
  
      return fname
  
  def validate_install(srcdir, installdir, compiler, env):
      # List of installed files
      info_file = os.path.join(srcdir, 'installed_files.txt')
      # If this exists, the test does not install any other files
      noinst_file = 'usr/no-installed-files'
      expected = {}
      ret_msg = ''
      # Generate list of expected files
      if os.path.exists(os.path.join(installdir, noinst_file)):
          expected[noinst_file] = False
      elif os.path.exists(info_file):
          with open(info_file) as f:
              for line in f:
                  line = platform_fix_name(line.strip(), compiler, env)
                  if line:
                      expected[line] = False
      # Check if expected files were found
      for fname in expected:
          file_path = os.path.join(installdir, fname)
          if os.path.exists(file_path) or os.path.islink(file_path):
              expected[fname] = True
      for (fname, found) in expected.items():
          if not found:
              ret_msg += 'Expected file {0} missing.\n'.format(fname)
      # Check if there are any unexpected files
      found = get_relative_files_list_from_dir(installdir)
      for fname in found:
          if fname not in expected:
              ret_msg += 'Extra file {0} found.\n'.format(fname)
      return ret_msg
  
  def log_text_file(logfile, testdir, stdo, stde):
      global stop, executor, futures
      logfile.write('%s\nstdout\n\n---\n' % testdir.as_posix())
      logfile.write(stdo)
      logfile.write('\n\n---\n\nstderr\n\n---\n')
      logfile.write(stde)
      logfile.write('\n\n---\n\n')
      if print_debug:
          try:
              print(stdo)
          except UnicodeError:
              sanitized_out = stdo.encode('ascii', errors='replace').decode()
              print(sanitized_out)
          try:
              print(stde, file=sys.stderr)
          except UnicodeError:
              sanitized_err = stde.encode('ascii', errors='replace').decode()
              print(sanitized_err, file=sys.stderr)
      if stop:
          print("Aborting..")
          for f in futures:
              f[2].cancel()
          executor.shutdown()
          raise StopException()
  
  
  def bold(text):
      return mlog.bold(text).get_text(mlog.colorize_console)
  
  
  def green(text):
      return mlog.green(text).get_text(mlog.colorize_console)
  
  
  def red(text):
      return mlog.red(text).get_text(mlog.colorize_console)
  
  
  def yellow(text):
      return mlog.yellow(text).get_text(mlog.colorize_console)
  
  
  def run_test_inprocess(testdir):
      old_stdout = sys.stdout
      sys.stdout = mystdout = StringIO()
      old_stderr = sys.stderr
      sys.stderr = mystderr = StringIO()
      old_cwd = os.getcwd()
      os.chdir(testdir)
      test_log_fname = Path('meson-logs', 'testlog.txt')
      try:
          returncode_test = mtest.run(['--no-rebuild'])
          if test_log_fname.exists():
              test_log = test_log_fname.open(errors='ignore').read()
          else:
              test_log = ''
          returncode_benchmark = mtest.run(['--no-rebuild', '--benchmark', '--logbase', 'benchmarklog'])
      finally:
          sys.stdout = old_stdout
          sys.stderr = old_stderr
          os.chdir(old_cwd)
      return max(returncode_test, returncode_benchmark), mystdout.getvalue(), mystderr.getvalue(), test_log
  
  def parse_test_args(testdir):
      args = []
      try:
          with open(os.path.join(testdir, 'test_args.txt'), 'r') as f:
              content = f.read()
              try:
                  args = literal_eval(content)
              except Exception:
                  raise Exception('Malformed test_args file.')
              args = stringlistify(args)
      except FileNotFoundError:
          pass
      return args
  
  # Build directory name must be the same so CCache works over
  # consecutive invocations.
  def create_deterministic_builddir(src_dir):
      import hashlib
      rel_dirname = 'b ' + hashlib.sha256(src_dir.encode(errors='ignore')).hexdigest()[0:10]
      os.mkdir(rel_dirname)
      abs_pathname = os.path.join(os.getcwd(), rel_dirname)
      return abs_pathname
  
  def run_test(skipped, testdir, extra_args, compiler, backend, flags, commands, should_fail):
      if skipped:
          return None
      with AutoDeletedDir(create_deterministic_builddir(testdir)) as build_dir:
          with AutoDeletedDir(tempfile.mkdtemp(prefix='i ', dir=os.getcwd())) as install_dir:
              try:
                  return _run_test(testdir, build_dir, install_dir, extra_args, compiler, backend, flags, commands, should_fail)
              finally:
                  mlog.shutdown() # Close the log file because otherwise Windows wets itself.
  
  def pass_prefix_to_test(dirname):
      if '39 prefix absolute' in dirname:
          return False
      return True
  
  def pass_libdir_to_test(dirname):
      if '8 install' in dirname:
          return False
      if '38 libdir must be inside prefix' in dirname:
          return False
      if '196 install_mode' in dirname:
          return False
      return True
  
  def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backend, flags, commands, should_fail):
      compile_commands, clean_commands, install_commands, uninstall_commands = commands
      test_args = parse_test_args(testdir)
      gen_start = time.time()
      # Configure in-process
      if pass_prefix_to_test(testdir):
          gen_args = ['--prefix', '/usr']
      else:
          gen_args = []
      if pass_libdir_to_test(testdir):
          gen_args += ['--libdir', 'lib']
      gen_args += [testdir, test_build_dir] + flags + test_args + extra_args
      (returncode, stdo, stde) = run_configure(gen_args)
      try:
          logfile = Path(test_build_dir, 'meson-logs', 'meson-log.txt')
          mesonlog = logfile.open(errors='ignore', encoding='utf-8').read()
      except Exception:
          mesonlog = no_meson_log_msg
      gen_time = time.time() - gen_start
      if should_fail == 'meson':
          if returncode == 1:
              return TestResult('', BuildStep.configure, stdo, stde, mesonlog, gen_time)
          elif returncode != 0:
              return TestResult('Test exited with unexpected status {}'.format(returncode), BuildStep.configure, stdo, stde, mesonlog, gen_time)
          else:
              return TestResult('Test that should have failed succeeded', BuildStep.configure, stdo, stde, mesonlog, gen_time)
      if returncode != 0:
          return TestResult('Generating the build system failed.', BuildStep.configure, stdo, stde, mesonlog, gen_time)
      builddata = build.load(test_build_dir)
      # Touch the meson.build file to force a regenerate so we can test that
      # regeneration works before a build is run.
      ensure_backend_detects_changes(backend)
      os.utime(os.path.join(testdir, 'meson.build'))
      # Build with subprocess
      dir_args = get_backend_args_for_dir(backend, test_build_dir)
      build_start = time.time()
      pc, o, e = Popen_safe(compile_commands + dir_args, cwd=test_build_dir)
      build_time = time.time() - build_start
      stdo += o
      stde += e
      if should_fail == 'build':
          if pc.returncode != 0:
              return TestResult('', BuildStep.build, stdo, stde, mesonlog, gen_time)
          return TestResult('Test that should have failed to build succeeded', BuildStep.build, stdo, stde, mesonlog, gen_time)
      if pc.returncode != 0:
          return TestResult('Compiling source code failed.', BuildStep.build, stdo, stde, mesonlog, gen_time, build_time)
      # Touch the meson.build file to force a regenerate so we can test that
      # regeneration works after a build is complete.
      ensure_backend_detects_changes(backend)
      os.utime(os.path.join(testdir, 'meson.build'))
      test_start = time.time()
      # Test in-process
      (returncode, tstdo, tstde, test_log) = run_test_inprocess(test_build_dir)
      test_time = time.time() - test_start
      stdo += tstdo
      stde += tstde
      mesonlog += test_log
      if should_fail == 'test':
          if returncode != 0:
              return TestResult('', BuildStep.test, stdo, stde, mesonlog, gen_time)
          return TestResult('Test that should have failed to run unit tests succeeded', BuildStep.test, stdo, stde, mesonlog, gen_time)
      if returncode != 0:
          return TestResult('Running unit tests failed.', BuildStep.test, stdo, stde, mesonlog, gen_time, build_time, test_time)
      # Do installation, if the backend supports it
      if install_commands:
          env = os.environ.copy()
          env['DESTDIR'] = install_dir
          # Install with subprocess
          pi, o, e = Popen_safe(install_commands, cwd=test_build_dir, env=env)
          stdo += o
          stde += e
          if pi.returncode != 0:
              return TestResult('Running install failed.', BuildStep.install, stdo, stde, mesonlog, gen_time, build_time, test_time)
      # Clean with subprocess
      env = os.environ.copy()
      pi, o, e = Popen_safe(clean_commands + dir_args, cwd=test_build_dir, env=env)
      stdo += o
      stde += e
      if pi.returncode != 0:
          return TestResult('Running clean failed.', BuildStep.clean, stdo, stde, mesonlog, gen_time, build_time, test_time)
      if not install_commands:
          return TestResult('', BuildStep.install, '', '', mesonlog, gen_time, build_time, test_time)
      return TestResult(validate_install(testdir, install_dir, compiler, builddata.environment),
                        BuildStep.validate, stdo, stde, mesonlog, gen_time, build_time, test_time)
  
  def gather_tests(testdir: Path):
      tests = [t.name for t in testdir.glob('*')]
      tests = [t for t in tests if not t.startswith('.')] # Filter non-tests files (dot files, etc)
      testlist = [(int(t.split()[0]), t) for t in tests]
      testlist.sort()
      tests = [testdir / t[1] for t in testlist]
      return tests
  
  def have_d_compiler():
      if shutil.which("ldc2"):
          return True
      elif shutil.which("ldc"):
          return True
      elif shutil.which("gdc"):
          return True
      elif shutil.which("dmd"):
          return True
      return False
  
  def have_objc_compiler():
      with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir:
          env = environment.Environment(None, build_dir, get_fake_options('/'))
          try:
              objc_comp = env.detect_objc_compiler(False)
          except mesonlib.MesonException:
              return False
          if not objc_comp:
              return False
          try:
              objc_comp.sanity_check(env.get_scratch_dir(), env)
          except mesonlib.MesonException:
              return False
      return True
  
  def have_objcpp_compiler():
      with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir:
          env = environment.Environment(None, build_dir, get_fake_options('/'))
          try:
              objcpp_comp = env.detect_objcpp_compiler(False)
          except mesonlib.MesonException:
              return False
          if not objcpp_comp:
              return False
          try:
              objcpp_comp.sanity_check(env.get_scratch_dir(), env)
          except mesonlib.MesonException:
              return False
      return True
  
  def have_java():
      if shutil.which('javac') and shutil.which('java'):
          return True
      return False
  
  def skippable(suite, test):
      if not under_ci:
          return True
  
      if not suite.endswith('frameworks'):
          return True
  
      # gtk-doc test may be skipped, pending upstream fixes for spaces in
      # filenames landing in the distro used for CI
      if test.endswith('10 gtk-doc'):
          return True
  
      # No frameworks test should be skipped on linux CI, as we expect all
      # prerequisites to be installed
      if mesonlib.is_linux():
          return False
  
      # Boost test should only be skipped for windows CI build matrix entries
      # which don't define BOOST_ROOT
      if test.endswith('1 boost'):
          if mesonlib.is_windows():
              return 'BOOST_ROOT' not in os.environ
          return False
  
      # Other framework tests are allowed to be skipped on other platforms
      return True
  
  def skip_csharp(backend):
      if backend is not Backend.ninja:
          return True
      if not shutil.which('resgen'):
          return True
      if shutil.which('mcs'):
          return False
      if shutil.which('csc'):
          # Only support VS2017 for now. Earlier versions fail
          # under CI in mysterious ways.
          try:
              stdo = subprocess.check_output(['csc', '/version'])
          except subprocess.CalledProcessError:
              return True
          # Having incrementing version numbers would be too easy.
          # Microsoft reset the versioning back to 1.0 (from 4.x)
          # when they got the Roslyn based compiler. Thus there
          # is NO WAY to reliably do version number comparisons.
          # Only support the version that ships with VS2017.
          return not stdo.startswith(b'2.')
      return True
  
  def detect_tests_to_run():
      # Name, subdirectory, skip condition.
      all_tests = [
          ('common', 'common', False),
          ('failing-meson', 'failing', False),
          ('failing-build', 'failing build', False),
          ('failing-test',  'failing test', False),
  
          ('platform-osx', 'osx', not mesonlib.is_osx()),
          ('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()),
          ('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()),
  
          ('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()),
          ('C#', 'csharp', skip_csharp(backend)),
          ('vala', 'vala', backend is not Backend.ninja or not shutil.which('valac')),
          ('rust', 'rust', backend is not Backend.ninja or not shutil.which('rustc')),
          ('d', 'd', backend is not Backend.ninja or not have_d_compiler()),
          ('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or mesonlib.is_windows() or not have_objc_compiler()),
          ('objective c++', 'objcpp', backend not in (Backend.ninja, Backend.xcode) or mesonlib.is_windows() or not have_objcpp_compiler()),
          ('fortran', 'fortran', backend is not Backend.ninja or not shutil.which('gfortran')),
          ('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')),
          ('python3', 'python3', backend is not Backend.ninja),
          ('fpga', 'fpga', shutil.which('yosys') is None),
          ('frameworks', 'frameworks', False),
          ('nasm', 'nasm', False),
      ]
      gathered_tests = [(name, gather_tests(Path('test cases', subdir)), skip) for name, subdir, skip in all_tests]
      return gathered_tests
  
  def run_tests(all_tests, log_name_base, extra_args):
      global logfile
      txtname = log_name_base + '.txt'
      with open(txtname, 'w', encoding='utf-8', errors='ignore') as lf:
          logfile = lf
          return _run_tests(all_tests, log_name_base, extra_args)
  
  def _run_tests(all_tests, log_name_base, extra_args):
      global stop, executor, futures, system_compiler
      xmlname = log_name_base + '.xml'
      junit_root = ET.Element('testsuites')
      conf_time = 0
      build_time = 0
      test_time = 0
      passing_tests = 0
      failing_tests = 0
      skipped_tests = 0
      commands = (compile_commands, clean_commands, install_commands, uninstall_commands)
  
      try:
          # This fails in some CI environments for unknown reasons.
          num_workers = multiprocessing.cpu_count()
      except Exception as e:
          print('Could not determine number of CPUs due to the following reason:' + str(e))
          print('Defaulting to using only one process')
          num_workers = 1
      # Due to Ninja deficiency, almost 50% of build time
      # is spent waiting. Do something useful instead.
      #
      # Remove this once the following issue has been resolved:
      # https://github.com/mesonbuild/meson/pull/2082
      num_workers *= 2
      executor = ProcessPoolExecutor(max_workers=num_workers)
  
      for name, test_cases, skipped in all_tests:
          current_suite = ET.SubElement(junit_root, 'testsuite', {'name': name, 'tests': str(len(test_cases))})
          print()
          if skipped:
              print(bold('Not running %s tests.' % name))
          else:
              print(bold('Running %s tests.' % name))
          print()
          futures = []
          for t in test_cases:
              # Jenkins screws us over by automatically sorting test cases by name
              # and getting it wrong by not doing logical number sorting.
              (testnum, testbase) = t.name.split(' ', 1)
              testname = '%.3d %s' % (int(testnum), testbase)
              should_fail = False
              if name.startswith('failing'):
                  should_fail = name.split('failing-')[1]
              result = executor.submit(run_test, skipped, t.as_posix(), extra_args, system_compiler, backend, backend_flags, commands, should_fail)
              futures.append((testname, t, result))
          for (testname, t, result) in futures:
              sys.stdout.flush()
              result = result.result()
              if (result is None) or (('MESON_SKIP_TEST' in result.stdo) and (skippable(name, t.as_posix()))):
                  print(yellow('Skipping:'), t.as_posix())
                  current_test = ET.SubElement(current_suite, 'testcase', {'name': testname,
                                                                           'classname': name})
                  ET.SubElement(current_test, 'skipped', {})
                  skipped_tests += 1
              else:
                  without_install = "" if len(install_commands) > 0 else " (without install)"
                  if result.msg != '':
                      print(red('Failed test{} during {}: {!r}'.format(without_install, result.step.name, t.as_posix())))
                      print('Reason:', result.msg)
                      failing_tests += 1
                      if result.step == BuildStep.configure and result.mlog != no_meson_log_msg:
                          # For configure failures, instead of printing stdout,
                          # print the meson log if available since it's a superset
                          # of stdout and often has very useful information.
                          failing_logs.append(result.mlog)
                      else:
                          failing_logs.append(result.stdo)
                      failing_logs.append(result.stde)
                  else:
                      print('Succeeded test%s: %s' % (without_install, t.as_posix()))
                      passing_tests += 1
                  conf_time += result.conftime
                  build_time += result.buildtime
                  test_time += result.testtime
                  total_time = conf_time + build_time + test_time
                  log_text_file(logfile, t, result.stdo, result.stde)
                  current_test = ET.SubElement(current_suite, 'testcase', {'name': testname,
                                                                           'classname': name,
                                                                           'time': '%.3f' % total_time})
                  if result.msg != '':
                      ET.SubElement(current_test, 'failure', {'message': result.msg})
                  stdoel = ET.SubElement(current_test, 'system-out')
                  stdoel.text = result.stdo
                  stdeel = ET.SubElement(current_test, 'system-err')
                  stdeel.text = result.stde
      print("\nTotal configuration time: %.2fs" % conf_time)
      print("Total build time: %.2fs" % build_time)
      print("Total test time: %.2fs" % test_time)
      ET.ElementTree(element=junit_root).write(xmlname, xml_declaration=True, encoding='UTF-8')
      return passing_tests, failing_tests, skipped_tests
  
  def check_file(fname):
      linenum = 1
      with open(fname, 'rb') as f:
          lines = f.readlines()
      for line in lines:
          if line.startswith(b'\t'):
              print("File %s contains a literal tab on line %d. Only spaces are permitted." % (fname, linenum))
              sys.exit(1)
          if b'\r' in line:
              print("File %s contains DOS line ending on line %d. Only unix-style line endings are permitted." % (fname, linenum))
              sys.exit(1)
          linenum += 1
  
  def check_format():
      check_suffixes = {'.c',
                        '.cpp',
                        '.cxx',
                        '.cc',
                        '.rs',
                        '.f90',
                        '.vala',
                        '.d',
                        '.s',
                        '.m',
                        '.mm',
                        '.asm',
                        '.java',
                        '.txt',
                        '.py',
                        '.swift',
                        '.build',
                        '.md',
                        }
      for (root, _, files) in os.walk('.'):
          if '.dub' in root: # external deps are here
              continue
          for fname in files:
              if os.path.splitext(fname)[1].lower() in check_suffixes:
                  bn = os.path.basename(fname)
                  if bn == 'sitemap.txt' or bn == 'meson-test-run.txt':
                      continue
                  fullname = os.path.join(root, fname)
                  check_file(fullname)
  
  def check_meson_commands_work():
      global backend, compile_commands, test_commands, install_commands
      testdir = PurePath('test cases', 'common', '1 trivial').as_posix()
      meson_commands = mesonlib.python_command + [get_meson_script()]
      with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir:
          print('Checking that configuring works...')
          gen_cmd = meson_commands + [testdir, build_dir] + backend_flags
          pc, o, e = Popen_safe(gen_cmd)
          if pc.returncode != 0:
              raise RuntimeError('Failed to configure {!r}:\n{}\n{}'.format(testdir, e, o))
          print('Checking that building works...')
          dir_args = get_backend_args_for_dir(backend, build_dir)
          pc, o, e = Popen_safe(compile_commands + dir_args, cwd=build_dir)
          if pc.returncode != 0:
              raise RuntimeError('Failed to build {!r}:\n{}\n{}'.format(testdir, e, o))
          print('Checking that testing works...')
          pc, o, e = Popen_safe(test_commands, cwd=build_dir)
          if pc.returncode != 0:
              raise RuntimeError('Failed to test {!r}:\n{}\n{}'.format(testdir, e, o))
          if install_commands:
              print('Checking that installing works...')
              pc, o, e = Popen_safe(install_commands, cwd=build_dir)
              if pc.returncode != 0:
                  raise RuntimeError('Failed to install {!r}:\n{}\n{}'.format(testdir, e, o))
  
  
  def detect_system_compiler():
      global system_compiler
      if shutil.which('cl'):
          system_compiler = 'cl'
      elif shutil.which('cc'):
          system_compiler = 'cc'
      elif shutil.which('gcc'):
          system_compiler = 'gcc'
      else:
          raise RuntimeError("Could not find C compiler.")
  
  if __name__ == '__main__':
      parser = argparse.ArgumentParser(description="Run the test suite of Meson.")
      parser.add_argument('extra_args', nargs='*',
                          help='arguments that are passed directly to Meson (remember to have -- before these).')
      parser.add_argument('--backend', default=None, dest='backend',
                          choices=backendlist)
      options = parser.parse_args()
      setup_commands(options.backend)
  
      detect_system_compiler()
      script_dir = os.path.split(__file__)[0]
      if script_dir != '':
          os.chdir(script_dir)
      check_format()
      check_meson_commands_work()
      try:
          all_tests = detect_tests_to_run()
          (passing_tests, failing_tests, skipped_tests) = run_tests(all_tests, 'meson-test-run', options.extra_args)
      except StopException:
          pass
      print('\nTotal passed tests:', green(str(passing_tests)))
      print('Total failed tests:', red(str(failing_tests)))
      print('Total skipped tests:', yellow(str(skipped_tests)))
      if failing_tests > 0:
          print('\nMesonlogs of failing tests\n')
          for l in failing_logs:
              try:
                  print(l, '\n')
              except UnicodeError:
                  print(l.encode('ascii', errors='replace').decode(), '\n')
      for name, dirs, skip in all_tests:
          dirs = (x.name for x in dirs)
          for k, g in itertools.groupby(dirs, key=lambda x: x.split()[0]):
              tests = list(g)
              if len(tests) != 1:
                  print('WARNING: The %s suite contains duplicate "%s" tests: "%s"' % (name, k, '", "'.join(tests)))
      sys.exit(failing_tests)
  
files: run_project_tests.py
files: #!/usr/bin/env python3
  
  # Copyright 2012-2017 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 os
  import sys
  import time
  import shutil
  import subprocess
  import tempfile
  import platform
  from io import StringIO
  from enum import Enum
  from glob import glob
  from pathlib import Path
  
  import mesonbuild
  from mesonbuild import mesonlib
  from mesonbuild import mesonmain
  from mesonbuild import mtest
  from mesonbuild import mlog
  from mesonbuild.environment import Environment, detect_ninja
  
  
  # Fake classes and objects for mocking
  class FakeBuild:
      def __init__(self, env):
          self.environment = env
  
  class FakeCompilerOptions:
      def __init__(self):
          self.value = []
  
  def get_fake_options(prefix):
      import argparse
      opts = argparse.Namespace()
      opts.cross_file = None
      opts.wrap_mode = None
      opts.prefix = prefix
      opts.cmd_line_options = {}
      return opts
  
  def get_fake_env(sdir, bdir, prefix):
      env = Environment(sdir, bdir, get_fake_options(prefix))
      env.coredata.compiler_options['c_args'] = FakeCompilerOptions()
      return env
  
  
  Backend = Enum('Backend', 'ninja vs xcode')
  
  if 'MESON_EXE' in os.environ:
      import shlex
      meson_exe = shlex.split(os.environ['MESON_EXE'])
  else:
      meson_exe = None
  
  if mesonlib.is_windows() or mesonlib.is_cygwin():
      exe_suffix = '.exe'
  else:
      exe_suffix = ''
  
  def get_meson_script():
      '''
      Guess the meson that corresponds to the `mesonbuild` that has been imported
      so we can run configure and other commands in-process, since mesonmain.run
      needs to know the meson_command to use.
  
      Also used by run_unittests.py to determine what meson to run when not
      running in-process (which is the default).
      '''
      # Is there a meson.py next to the mesonbuild currently in use?
      mesonbuild_dir = Path(mesonbuild.__file__).resolve().parent.parent
      meson_script = mesonbuild_dir / 'meson.py'
      if meson_script.is_file():
          return str(meson_script)
      # Then if mesonbuild is in PYTHONPATH, meson must be in PATH
      mlog.warning('Could not find meson.py next to the mesonbuild module. '
                   'Trying system meson...')
      meson_cmd = shutil.which('meson')
      if meson_cmd:
          return meson_cmd
      raise RuntimeError('Could not find {!r} or a meson in PATH'.format(meson_script))
  
  def get_backend_args_for_dir(backend, builddir):
      '''
      Visual Studio backend needs to be given the solution to build
      '''
      if backend is Backend.vs:
          sln_name = glob(os.path.join(builddir, '*.sln'))[0]
          return [os.path.split(sln_name)[-1]]
      return []
  
  def find_vcxproj_with_target(builddir, target):
      import re, fnmatch
      t, ext = os.path.splitext(target)
      if ext:
          p = '<TargetName>{}</TargetName>\s*<TargetExt>\{}</TargetExt>'.format(t, ext)
      else:
          p = '<TargetName>{}</TargetName>'.format(t)
      for root, dirs, files in os.walk(builddir):
          for f in fnmatch.filter(files, '*.vcxproj'):
              f = os.path.join(builddir, f)
              with open(f, 'r', encoding='utf-8') as o:
                  if re.search(p, o.read(), flags=re.MULTILINE):
                      return f
      raise RuntimeError('No vcxproj matching {!r} in {!r}'.format(p, builddir))
  
  def get_builddir_target_args(backend, builddir, target):
      dir_args = []
      if not target:
          dir_args = get_backend_args_for_dir(backend, builddir)
      if target is None:
          return dir_args
      if backend is Backend.vs:
          vcxproj = find_vcxproj_with_target(builddir, target)
          target_args = [vcxproj]
      elif backend is Backend.xcode:
          target_args = ['-target', target]
      elif backend is Backend.ninja:
          target_args = [target]
      else:
          raise AssertionError('Unknown backend: {!r}'.format(backend))
      return target_args + dir_args
  
  def get_backend_commands(backend, debug=False):
      install_cmd = []
      uninstall_cmd = []
      if backend is Backend.vs:
          cmd = ['msbuild']
          clean_cmd = cmd + ['/target:Clean']
          test_cmd = cmd + ['RUN_TESTS.vcxproj']
      elif backend is Backend.xcode:
          cmd = ['xcodebuild']
          # In Xcode9 new build system's clean command fails when using a custom build directory.
          # Maybe use it when CI uses Xcode10 we can remove '-UseNewBuildSystem=FALSE'
          clean_cmd = cmd + ['-alltargets', 'clean', '-UseNewBuildSystem=FALSE']
          test_cmd = cmd + ['-target', 'RUN_TESTS']
      elif backend is Backend.ninja:
          # We need at least 1.6 because of -w dupbuild=err
          cmd = [detect_ninja('1.6'), '-w', 'dupbuild=err', '-d', 'explain']
          if cmd[0] is None:
              raise RuntimeError('Could not find Ninja v1.6 or newer')
          if debug:
              cmd += ['-v']
          clean_cmd = cmd + ['clean']
          test_cmd = cmd + ['test', 'benchmark']
          install_cmd = cmd + ['install']
          uninstall_cmd = cmd + ['uninstall']
      else:
          raise AssertionError('Unknown backend: {!r}'.format(backend))
      return cmd, clean_cmd, test_cmd, install_cmd, uninstall_cmd
  
  def ensure_backend_detects_changes(backend):
      # We're using a ninja with QuLogic's patch for sub-1s resolution timestamps
      # and not running on HFS+ which only stores dates in seconds:
      # https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#HFSPlusDates
      # FIXME: Upgrade Travis image to Apple FS when that becomes available
      if 'MESON_FIXED_NINJA' in os.environ and not mesonlib.is_osx():
          return
      # This is needed to increase the difference between build.ninja's
      # timestamp and the timestamp of whatever you changed due to a Ninja
      # bug: https://github.com/ninja-build/ninja/issues/371
      if backend is Backend.ninja:
          time.sleep(1)
  
  def run_mtest_inprocess(commandlist):
      old_stdout = sys.stdout
      sys.stdout = mystdout = StringIO()
      old_stderr = sys.stderr
      sys.stderr = mystderr = StringIO()
      try:
          returncode = mtest.run_with_args(commandlist)
      finally:
          sys.stdout = old_stdout
          sys.stderr = old_stderr
      return returncode, mystdout.getvalue(), mystderr.getvalue()
  
  def run_configure_inprocess(commandlist):
      old_stdout = sys.stdout
      sys.stdout = mystdout = StringIO()
      old_stderr = sys.stderr
      sys.stderr = mystderr = StringIO()
      try:
          returncode = mesonmain.run(commandlist, get_meson_script())
      finally:
          sys.stdout = old_stdout
          sys.stderr = old_stderr
      return returncode, mystdout.getvalue(), mystderr.getvalue()
  
  def run_configure_external(full_command):
      pc, o, e = mesonlib.Popen_safe(full_command)
      return pc.returncode, o, e
  
  def run_configure(commandlist):
      global meson_exe
      if meson_exe:
          return run_configure_external(meson_exe + commandlist)
      return run_configure_inprocess(commandlist)
  
  def print_system_info():
      print(mlog.bold('System information.').get_text(mlog.colorize_console))
      print('Architecture:', platform.architecture())
      print('Machine:', platform.machine())
      print('Platform:', platform.system())
      print('Processor:', platform.processor())
      print('System:', platform.system())
      print('')
  
  if __name__ == '__main__':
      print_system_info()
      # Enable coverage early...
      enable_coverage = '--cov' in sys.argv
      if enable_coverage:
          os.makedirs('.coverage', exist_ok=True)
          sys.argv.remove('--cov')
          import coverage
          coverage.process_startup()
      returncode = 0
      # Iterate over list in reverse order to find the last --backend arg
      backend = Backend.ninja
      cross = False
      # FIXME: PLEASE convert to argparse
      for arg in reversed(sys.argv[1:]):
          if arg.startswith('--backend'):
              if arg.startswith('--backend=vs'):
                  backend = Backend.vs
              elif arg == '--backend=xcode':
                  backend = Backend.xcode
          if arg.startswith('--cross'):
              cross = True
              if arg == '--cross=mingw':
                  cross = 'mingw'
              elif arg == '--cross=arm':
                  cross = 'arm'
      # Running on a developer machine? Be nice!
      if not mesonlib.is_windows() and not mesonlib.is_haiku() and 'TRAVIS' not in os.environ:
          os.nice(20)
      # Appveyor sets the `platform` environment variable which completely messes
      # up building with the vs2010 and vs2015 backends.
      #
      # Specifically, MSBuild reads the `platform` environment variable to set
      # the configured value for the platform (Win32/x64/arm), which breaks x86
      # builds.
      #
      # Appveyor setting this also breaks our 'native build arch' detection for
      # Windows in environment.py:detect_windows_arch() by overwriting the value
      # of `platform` set by vcvarsall.bat.
      #
      # While building for x86, `platform` should be unset.
      if 'APPVEYOR' in os.environ and os.environ['arch'] == 'x86':
          os.environ.pop('platform')
      # Run tests
      print(mlog.bold('Running unittests.').get_text(mlog.colorize_console))
      print()
      # Can't pass arguments to unit tests, so set the backend to use in the environment
      env = os.environ.copy()
      env['MESON_UNIT_TEST_BACKEND'] = backend.name
      with tempfile.TemporaryDirectory() as td:
          # Enable coverage on all subsequent processes.
          if enable_coverage:
              with open(os.path.join(td, 'usercustomize.py'), 'w') as f:
                  f.write('import coverage\n'
                          'coverage.process_startup()\n')
              env['COVERAGE_PROCESS_START'] = '.coveragerc'
              env['PYTHONPATH'] = os.pathsep.join([td] + env.get('PYTHONPATH', []))
          if not cross:
              returncode += subprocess.call(mesonlib.python_command + ['run_meson_command_tests.py', '-v'], env=env)
              returncode += subprocess.call(mesonlib.python_command + ['run_unittests.py', '-v'], env=env)
              returncode += subprocess.call(mesonlib.python_command + ['run_project_tests.py'] + sys.argv[1:], env=env)
          else:
              cross_test_args = mesonlib.python_command + ['run_cross_test.py']
              if cross is True or cross == 'arm':
                  print(mlog.bold('Running armhf cross tests.').get_text(mlog.colorize_console))
                  print()
                  returncode += subprocess.call(cross_test_args + ['cross/ubuntu-armhf.txt'], env=env)
              if cross is True or cross == 'mingw':
                  print(mlog.bold('Running mingw-w64 64-bit cross tests.').get_text(mlog.colorize_console))
                  print()
                  returncode += subprocess.call(cross_test_args + ['cross/linux-mingw-w64-64bit.txt'], env=env)
      sys.exit(returncode)
  
files: run_tests.py
files: #!/usr/bin/env python3
  
  # Copyright 2012-2017 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 os
  import sys
  import time
  import shutil
  import subprocess
  import tempfile
  import platform
  from io import StringIO
  from enum import Enum
  from glob import glob
  from pathlib import Path
  
  import mesonbuild
  from mesonbuild import mesonlib
  from mesonbuild import mesonmain
  from mesonbuild import mtest
  from mesonbuild import mlog
  from mesonbuild.environment import Environment, detect_ninja
  
  
  # Fake classes and objects for mocking
  class FakeBuild:
      def __init__(self, env):
          self.environment = env
  
  class FakeCompilerOptions:
      def __init__(self):
          self.value = []
  
  def get_fake_options(prefix):
      import argparse
      opts = argparse.Namespace()
      opts.cross_file = None
      opts.wrap_mode = None
      opts.prefix = prefix
      opts.cmd_line_options = {}
      return opts
  
  def get_fake_env(sdir, bdir, prefix):
      env = Environment(sdir, bdir, get_fake_options(prefix))
      env.coredata.compiler_options['c_args'] = FakeCompilerOptions()
      return env
  
  
  Backend = Enum('Backend', 'ninja vs xcode')
  
  if 'MESON_EXE' in os.environ:
      import shlex
      meson_exe = shlex.split(os.environ['MESON_EXE'])
  else:
      meson_exe = None
  
  if mesonlib.is_windows() or mesonlib.is_cygwin():
      exe_suffix = '.exe'
  else:
      exe_suffix = ''
  
  def get_meson_script():
      '''
      Guess the meson that corresponds to the `mesonbuild` that has been imported
      so we can run configure and other commands in-process, since mesonmain.run
      needs to know the meson_command to use.
  
      Also used by run_unittests.py to determine what meson to run when not
      running in-process (which is the default).
      '''
      # Is there a meson.py next to the mesonbuild currently in use?
      mesonbuild_dir = Path(mesonbuild.__file__).resolve().parent.parent
      meson_script = mesonbuild_dir / 'meson.py'
      if meson_script.is_file():
          return str(meson_script)
      # Then if mesonbuild is in PYTHONPATH, meson must be in PATH
      mlog.warning('Could not find meson.py next to the mesonbuild module. '
                   'Trying system meson...')
      meson_cmd = shutil.which('meson')
      if meson_cmd:
          return meson_cmd
      raise RuntimeError('Could not find {!r} or a meson in PATH'.format(meson_script))
  
  def get_backend_args_for_dir(backend, builddir):
      '''
      Visual Studio backend needs to be given the solution to build
      '''
      if backend is Backend.vs:
          sln_name = glob(os.path.join(builddir, '*.sln'))[0]
          return [os.path.split(sln_name)[-1]]
      return []
  
  def find_vcxproj_with_target(builddir, target):
      import re, fnmatch
      t, ext = os.path.splitext(target)
      if ext:
          p = '<TargetName>{}</TargetName>\s*<TargetExt>\{}</TargetExt>'.format(t, ext)
      else:
          p = '<TargetName>{}</TargetName>'.format(t)
      for root, dirs, files in os.walk(builddir):
          for f in fnmatch.filter(files, '*.vcxproj'):
              f = os.path.join(builddir, f)
              with open(f, 'r', encoding='utf-8') as o:
                  if re.search(p, o.read(), flags=re.MULTILINE):
                      return f
      raise RuntimeError('No vcxproj matching {!r} in {!r}'.format(p, builddir))
  
  def get_builddir_target_args(backend, builddir, target):
      dir_args = []
      if not target:
          dir_args = get_backend_args_for_dir(backend, builddir)
      if target is None:
          return dir_args
      if backend is Backend.vs:
          vcxproj = find_vcxproj_with_target(builddir, target)
          target_args = [vcxproj]
      elif backend is Backend.xcode:
          target_args = ['-target', target]
      elif backend is Backend.ninja:
          target_args = [target]
      else:
          raise AssertionError('Unknown backend: {!r}'.format(backend))
      return target_args + dir_args
  
  def get_backend_commands(backend, debug=False):
      install_cmd = []
      uninstall_cmd = []
      if backend is Backend.vs:
          cmd = ['msbuild']
          clean_cmd = cmd + ['/target:Clean']
          test_cmd = cmd + ['RUN_TESTS.vcxproj']
      elif backend is Backend.xcode:
          cmd = ['xcodebuild']
          # In Xcode9 new build system's clean command fails when using a custom build directory.
          # Maybe use it when CI uses Xcode10 we can remove '-UseNewBuildSystem=FALSE'
          clean_cmd = cmd + ['-alltargets', 'clean', '-UseNewBuildSystem=FALSE']
          test_cmd = cmd + ['-target', 'RUN_TESTS']
      elif backend is Backend.ninja:
          # We need at least 1.6 because of -w dupbuild=err
          cmd = [detect_ninja('1.6'), '-w', 'dupbuild=err', '-d', 'explain']
          if cmd[0] is None:
              raise RuntimeError('Could not find Ninja v1.6 or newer')
          if debug:
              cmd += ['-v']
          clean_cmd = cmd + ['clean']
          test_cmd = cmd + ['test', 'benchmark']
          install_cmd = cmd + ['install']
          uninstall_cmd = cmd + ['uninstall']
      else:
          raise AssertionError('Unknown backend: {!r}'.format(backend))
      return cmd, clean_cmd, test_cmd, install_cmd, uninstall_cmd
  
  def ensure_backend_detects_changes(backend):
      # We're using a ninja with QuLogic's patch for sub-1s resolution timestamps
      # and not running on HFS+ which only stores dates in seconds:
      # https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#HFSPlusDates
      # FIXME: Upgrade Travis image to Apple FS when that becomes available
      if 'MESON_FIXED_NINJA' in os.environ and not mesonlib.is_osx():
          return
      # This is needed to increase the difference between build.ninja's
      # timestamp and the timestamp of whatever you changed due to a Ninja
      # bug: https://github.com/ninja-build/ninja/issues/371
      if backend is Backend.ninja:
          time.sleep(1)
  
  def run_mtest_inprocess(commandlist):
      old_stdout = sys.stdout
      sys.stdout = mystdout = StringIO()
      old_stderr = sys.stderr
      sys.stderr = mystderr = StringIO()
      try:
          returncode = mtest.run(commandlist)
      finally:
          sys.stdout = old_stdout
          sys.stderr = old_stderr
      return returncode, mystdout.getvalue(), mystderr.getvalue()
  
  def run_configure_inprocess(commandlist):
      old_stdout = sys.stdout
      sys.stdout = mystdout = StringIO()
      old_stderr = sys.stderr
      sys.stderr = mystderr = StringIO()
      try:
          returncode = mesonmain.run(commandlist, get_meson_script())
      finally:
          sys.stdout = old_stdout
          sys.stderr = old_stderr
      return returncode, mystdout.getvalue(), mystderr.getvalue()
  
  def run_configure_external(full_command):
      pc, o, e = mesonlib.Popen_safe(full_command)
      return pc.returncode, o, e
  
  def run_configure(commandlist):
      global meson_exe
      if meson_exe:
          return run_configure_external(meson_exe + commandlist)
      return run_configure_inprocess(commandlist)
  
  def print_system_info():
      print(mlog.bold('System information.').get_text(mlog.colorize_console))
      print('Architecture:', platform.architecture())
      print('Machine:', platform.machine())
      print('Platform:', platform.system())
      print('Processor:', platform.processor())
      print('System:', platform.system())
      print('')
  
  if __name__ == '__main__':
      print_system_info()
      # Enable coverage early...
      enable_coverage = '--cov' in sys.argv
      if enable_coverage:
          os.makedirs('.coverage', exist_ok=True)
          sys.argv.remove('--cov')
          import coverage
          coverage.process_startup()
      returncode = 0
      # Iterate over list in reverse order to find the last --backend arg
      backend = Backend.ninja
      cross = False
      # FIXME: PLEASE convert to argparse
      for arg in reversed(sys.argv[1:]):
          if arg.startswith('--backend'):
              if arg.startswith('--backend=vs'):
                  backend = Backend.vs
              elif arg == '--backend=xcode':
                  backend = Backend.xcode
          if arg.startswith('--cross'):
              cross = True
              if arg == '--cross=mingw':
                  cross = 'mingw'
              elif arg == '--cross=arm':
                  cross = 'arm'
      # Running on a developer machine? Be nice!
      if not mesonlib.is_windows() and not mesonlib.is_haiku() and 'TRAVIS' not in os.environ:
          os.nice(20)
      # Appveyor sets the `platform` environment variable which completely messes
      # up building with the vs2010 and vs2015 backends.
      #
      # Specifically, MSBuild reads the `platform` environment variable to set
      # the configured value for the platform (Win32/x64/arm), which breaks x86
      # builds.
      #
      # Appveyor setting this also breaks our 'native build arch' detection for
      # Windows in environment.py:detect_windows_arch() by overwriting the value
      # of `platform` set by vcvarsall.bat.
      #
      # While building for x86, `platform` should be unset.
      if 'APPVEYOR' in os.environ and os.environ['arch'] == 'x86':
          os.environ.pop('platform')
      # Run tests
      print(mlog.bold('Running unittests.').get_text(mlog.colorize_console))
      print()
      # Can't pass arguments to unit tests, so set the backend to use in the environment
      env = os.environ.copy()
      env['MESON_UNIT_TEST_BACKEND'] = backend.name
      with tempfile.TemporaryDirectory() as td:
          # Enable coverage on all subsequent processes.
          if enable_coverage:
              with open(os.path.join(td, 'usercustomize.py'), 'w') as f:
                  f.write('import coverage\n'
                          'coverage.process_startup()\n')
              env['COVERAGE_PROCESS_START'] = '.coveragerc'
              env['PYTHONPATH'] = os.pathsep.join([td] + env.get('PYTHONPATH', []))
          if not cross:
              returncode += subprocess.call(mesonlib.python_command + ['run_meson_command_tests.py', '-v'], env=env)
              returncode += subprocess.call(mesonlib.python_command + ['run_unittests.py', '-v'], env=env)
              returncode += subprocess.call(mesonlib.python_command + ['run_project_tests.py'] + sys.argv[1:], env=env)
          else:
              cross_test_args = mesonlib.python_command + ['run_cross_test.py']
              if cross is True or cross == 'arm':
                  print(mlog.bold('Running armhf cross tests.').get_text(mlog.colorize_console))
                  print()
                  returncode += subprocess.call(cross_test_args + ['cross/ubuntu-armhf.txt'], env=env)
              if cross is True or cross == 'mingw':
                  print(mlog.bold('Running mingw-w64 64-bit cross tests.').get_text(mlog.colorize_console))
                  print()
                  returncode += subprocess.call(cross_test_args + ['cross/linux-mingw-w64-64bit.txt'], env=env)
      sys.exit(returncode)
  
files: run_tests.py