#!/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())
