chromium/tools/check_ecs_deps/check_ecs_deps.py

#!/usr/bin/env python
# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

''' Verifies that builds of the embedded content_shell do not included
unnecessary dependencies.'''

from __future__ import print_function

import os
import re
import string
import subprocess
import sys
import optparse

kUndesiredLibraryList = [
  'libX11',
  'libXau',
  'libXcomposite',
  'libXcursor',
  'libXdamage',
  'libXdmcp',
  'libXext',
  'libXfixes',
  'libXi',
  'libXrandr',
  'libXrender',
  'libXtst',
  'libasound',
  'libcairo',
  'libdbus',
  'libffi',
  'libgconf',
  'libgio',
  'libglib',
  'libgmodule',
  'libgobject',
  'libpango',
  'libpcre',
  'libpixman',
  'libpng',
  'libselinux',
  'libudev',
  'libxcb',
]

kAllowedLibraryList = [
  # Toolchain libraries (gcc/glibc)
  'ld-linux',
  'libc',
  'libdl',
  'libgcc_s',
  'libm',
  'libpthread',
  'libresolv',
  'librt',
  'libstdc++',
  'linux-vdso',

  # Needed for default ozone platforms
  'libdrm',

  # NSS & NSPR
  'libnss3',
  'libnssutil3',
  'libnspr4',
  'libplc4',
  'libplds4',
  'libsmime3',

  # OpenSSL
  'libcrypto',

  # Miscellaneous
  'libcap',
  'libexpat',
  'libfontconfig',
  'libz',
]

binary_target = 'content_shell'

def stdmsg(_final, errors):
  if errors:
    for message in errors:
      print(message)


def bbmsg(final, errors):
  if errors:
    for message in errors:
      print('@@@STEP_TEXT@%s@@@' % message)
  if final:
    print('\n@@@STEP_%s@@@' % final)


def _main():
  output = {
    'message': lambda x: stdmsg(None, x),
    'fail': lambda x: stdmsg('FAILED', x),
    'warn': lambda x: stdmsg('WARNING', x),
    'abend': lambda x: stdmsg('FAILED', x),
    'ok': lambda x: stdmsg('SUCCESS', x),
    'verbose': lambda x: None,
  }

  parser = optparse.OptionParser(
      "usage: %prog -b <dir> --target <Debug|Release>")
  parser.add_option("", "--annotate", dest='annotate', action='store_true',
      default=False, help="include buildbot annotations in output")
  parser.add_option("", "--noannotate", dest='annotate', action='store_false')
  parser.add_option("-b", "--build-dir",
                    help="the location of the compiler output")
  parser.add_option("--target", help="Debug or Release")
  parser.add_option('-v', '--verbose', default=False, action='store_true')

  options, args = parser.parse_args()
  if args:
    parser.usage()
    return -1

  # Bake target into build_dir.
  if options.target and options.build_dir:
    assert (options.target !=
            os.path.basename(os.path.dirname(options.build_dir)))
    options.build_dir = os.path.join(os.path.abspath(options.build_dir),
                                     options.target)

  if options.build_dir != None:
    build_dir = os.path.abspath(options.build_dir)
  else:
    build_dir = os.getcwd()

  target = os.path.join(build_dir, binary_target)

  if options.annotate:
    output.update({
      'message': lambda x: bbmsg(None, x),
      'fail': lambda x: bbmsg('FAILURE', x),
      'warn': lambda x: bbmsg('WARNINGS', x),
      'abend': lambda x: bbmsg('EXCEPTIONS', x),
      'ok': lambda x: bbmsg(None, x),
    })

  if options.verbose:
    output['verbose'] = lambda x: stdmsg(None, x)

  forbidden_regexp = re.compile(string.join(map(re.escape,
                                                kUndesiredLibraryList), '|'))
  mapping_regexp = re.compile(r"\s*([^/]*) => (.*)")
  blessed_regexp = re.compile(r"(%s)[-0-9.]*\.so" % string.join(map(re.escape,
      kAllowedLibraryList), '|'))
  built_regexp = re.compile(re.escape(build_dir + os.sep))

  success = 0
  warning = 0

  p = subprocess.Popen(['ldd', target], stdout=subprocess.PIPE,
      stderr=subprocess.PIPE)
  out, err = p.communicate()

  if err != '':
    output['abend']([
      'Failed to execute ldd to analyze dependencies for ' + target + ':',
      '    ' + err,
    ])
    return 1

  if out == '':
    output['abend']([
      'No output to scan for forbidden dependencies.'
    ])
    return 1

  success = 1
  deps = string.split(out, '\n')
  for d in deps:
    libmatch = mapping_regexp.match(d)
    if libmatch:
      lib = libmatch.group(1)
      source = libmatch.group(2)
      if forbidden_regexp.search(lib):
        success = 0
        output['message'](['Forbidden library: ' + lib])
      elif built_regexp.match(source):
        output['verbose'](['Built library: ' + lib])
      elif blessed_regexp.match(lib):
        output['verbose'](['Blessed library: ' + lib])
      else:
        warning = 1
        output['message'](['Unexpected library: ' + lib])

  if success == 1:
    if warning == 1:
      output['warn'](None)
    else:
      output['ok'](None)
    return 0
  else:
    output['fail'](None)
    return 1

if __name__ == "__main__":
  # handle arguments...
  # do something reasonable if not run with one...
  sys.exit(_main())