Subversion Repositories QNX 8.QNX8 LLVM/Clang compiler suite

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. # -*- coding: utf-8 -*-
  2. # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  3. # See https://llvm.org/LICENSE.txt for license information.
  4. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  5. """ This module is a collection of methods commonly used in this project. """
  6. import collections
  7. import functools
  8. import json
  9. import logging
  10. import os
  11. import os.path
  12. import re
  13. import shlex
  14. import subprocess
  15. import sys
  16.  
  17. ENVIRONMENT_KEY = 'INTERCEPT_BUILD'
  18.  
  19. Execution = collections.namedtuple('Execution', ['pid', 'cwd', 'cmd'])
  20.  
  21. CtuConfig = collections.namedtuple('CtuConfig', ['collect', 'analyze', 'dir',
  22.                                                  'extdef_map_cmd'])
  23.  
  24.  
  25. def duplicate_check(method):
  26.     """ Predicate to detect duplicated entries.
  27.  
  28.    Unique hash method can be use to detect duplicates. Entries are
  29.    represented as dictionaries, which has no default hash method.
  30.    This implementation uses a set datatype to store the unique hash values.
  31.  
  32.    This method returns a method which can detect the duplicate values. """
  33.  
  34.     def predicate(entry):
  35.         entry_hash = predicate.unique(entry)
  36.         if entry_hash not in predicate.state:
  37.             predicate.state.add(entry_hash)
  38.             return False
  39.         return True
  40.  
  41.     predicate.unique = method
  42.     predicate.state = set()
  43.     return predicate
  44.  
  45.  
  46. def run_build(command, *args, **kwargs):
  47.     """ Run and report build command execution
  48.  
  49.    :param command: array of tokens
  50.    :return: exit code of the process
  51.    """
  52.     environment = kwargs.get('env', os.environ)
  53.     logging.debug('run build %s, in environment: %s', command, environment)
  54.     exit_code = subprocess.call(command, *args, **kwargs)
  55.     logging.debug('build finished with exit code: %d', exit_code)
  56.     return exit_code
  57.  
  58.  
  59. def run_command(command, cwd=None):
  60.     """ Run a given command and report the execution.
  61.  
  62.    :param command: array of tokens
  63.    :param cwd: the working directory where the command will be executed
  64.    :return: output of the command
  65.    """
  66.     def decode_when_needed(result):
  67.         """ check_output returns bytes or string depend on python version """
  68.         return result.decode('utf-8') if isinstance(result, bytes) else result
  69.  
  70.     try:
  71.         directory = os.path.abspath(cwd) if cwd else os.getcwd()
  72.         logging.debug('exec command %s in %s', command, directory)
  73.         output = subprocess.check_output(command,
  74.                                          cwd=directory,
  75.                                          stderr=subprocess.STDOUT)
  76.         return decode_when_needed(output).splitlines()
  77.     except subprocess.CalledProcessError as ex:
  78.         ex.output = decode_when_needed(ex.output).splitlines()
  79.         raise ex
  80.  
  81.  
  82. def reconfigure_logging(verbose_level):
  83.     """ Reconfigure logging level and format based on the verbose flag.
  84.  
  85.    :param verbose_level: number of `-v` flags received by the command
  86.    :return: no return value
  87.    """
  88.     # Exit when nothing to do.
  89.     if verbose_level == 0:
  90.         return
  91.  
  92.     root = logging.getLogger()
  93.     # Tune logging level.
  94.     level = logging.WARNING - min(logging.WARNING, (10 * verbose_level))
  95.     root.setLevel(level)
  96.     # Be verbose with messages.
  97.     if verbose_level <= 3:
  98.         fmt_string = '%(name)s: %(levelname)s: %(message)s'
  99.     else:
  100.         fmt_string = '%(name)s: %(levelname)s: %(funcName)s: %(message)s'
  101.     handler = logging.StreamHandler(sys.stdout)
  102.     handler.setFormatter(logging.Formatter(fmt=fmt_string))
  103.     root.handlers = [handler]
  104.  
  105.  
  106. def command_entry_point(function):
  107.     """ Decorator for command entry methods.
  108.  
  109.    The decorator initialize/shutdown logging and guard on programming
  110.    errors (catch exceptions).
  111.  
  112.    The decorated method can have arbitrary parameters, the return value will
  113.    be the exit code of the process. """
  114.  
  115.     @functools.wraps(function)
  116.     def wrapper(*args, **kwargs):
  117.         """ Do housekeeping tasks and execute the wrapped method. """
  118.  
  119.         try:
  120.             logging.basicConfig(format='%(name)s: %(message)s',
  121.                                 level=logging.WARNING,
  122.                                 stream=sys.stdout)
  123.             # This hack to get the executable name as %(name).
  124.             logging.getLogger().name = os.path.basename(sys.argv[0])
  125.             return function(*args, **kwargs)
  126.         except KeyboardInterrupt:
  127.             logging.warning('Keyboard interrupt')
  128.             return 130  # Signal received exit code for bash.
  129.         except Exception:
  130.             logging.exception('Internal error.')
  131.             if logging.getLogger().isEnabledFor(logging.DEBUG):
  132.                 logging.error("Please report this bug and attach the output "
  133.                               "to the bug report")
  134.             else:
  135.                 logging.error("Please run this command again and turn on "
  136.                               "verbose mode (add '-vvvv' as argument).")
  137.             return 64  # Some non used exit code for internal errors.
  138.         finally:
  139.             logging.shutdown()
  140.  
  141.     return wrapper
  142.  
  143.  
  144. def compiler_wrapper(function):
  145.     """ Implements compiler wrapper base functionality.
  146.  
  147.    A compiler wrapper executes the real compiler, then implement some
  148.    functionality, then returns with the real compiler exit code.
  149.  
  150.    :param function: the extra functionality what the wrapper want to
  151.    do on top of the compiler call. If it throws exception, it will be
  152.    caught and logged.
  153.    :return: the exit code of the real compiler.
  154.  
  155.    The :param function: will receive the following arguments:
  156.  
  157.    :param result:       the exit code of the compilation.
  158.    :param execution:    the command executed by the wrapper. """
  159.  
  160.     def is_cxx_compiler():
  161.         """ Find out was it a C++ compiler call. Compiler wrapper names
  162.        contain the compiler type. C++ compiler wrappers ends with `c++`,
  163.        but might have `.exe` extension on windows. """
  164.  
  165.         wrapper_command = os.path.basename(sys.argv[0])
  166.         return re.match(r'(.+)c\+\+(.*)', wrapper_command)
  167.  
  168.     def run_compiler(executable):
  169.         """ Execute compilation with the real compiler. """
  170.  
  171.         command = executable + sys.argv[1:]
  172.         logging.debug('compilation: %s', command)
  173.         result = subprocess.call(command)
  174.         logging.debug('compilation exit code: %d', result)
  175.         return result
  176.  
  177.     # Get relevant parameters from environment.
  178.     parameters = json.loads(os.environ[ENVIRONMENT_KEY])
  179.     reconfigure_logging(parameters['verbose'])
  180.     # Execute the requested compilation. Do crash if anything goes wrong.
  181.     cxx = is_cxx_compiler()
  182.     compiler = parameters['cxx'] if cxx else parameters['cc']
  183.     result = run_compiler(compiler)
  184.     # Call the wrapped method and ignore it's return value.
  185.     try:
  186.         call = Execution(
  187.             pid=os.getpid(),
  188.             cwd=os.getcwd(),
  189.             cmd=['c++' if cxx else 'cc'] + sys.argv[1:])
  190.         function(result, call)
  191.     except:
  192.         logging.exception('Compiler wrapper failed complete.')
  193.     finally:
  194.         # Always return the real compiler exit code.
  195.         return result
  196.  
  197.  
  198. def wrapper_environment(args):
  199.     """ Set up environment for interpose compiler wrapper."""
  200.  
  201.     return {
  202.         ENVIRONMENT_KEY: json.dumps({
  203.             'verbose': args.verbose,
  204.             'cc': shlex.split(args.cc),
  205.             'cxx': shlex.split(args.cxx)
  206.         })
  207.     }
  208.