1#!/usr/bin/env python
2# Copyright 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6''' Verifies that builds of the embedded content_shell do not included
7unnecessary dependencies.'''
8
9import os
10import re
11import string
12import subprocess
13import sys
14import optparse
15
16kUndesiredLibraryList = [
17  'libX11',
18  'libXau',
19  'libXcomposite',
20  'libXcursor',
21  'libXdamage',
22  'libXdmcp',
23  'libXext',
24  'libXfixes',
25  'libXi',
26  'libXrandr',
27  'libXrender',
28  'libXtst',
29  'libasound',
30  'libcairo',
31  'libdbus',
32  'libffi',
33  'libgconf',
34  'libgio',
35  'libglib',
36  'libgmodule',
37  'libgobject',
38  'libpango',
39  'libpcre',
40  'libpixman',
41  'libpng',
42  'libselinux',
43  'libudev',
44  'libxcb',
45]
46
47kAllowedLibraryList = [
48  # Toolchain libraries (gcc/glibc)
49  'ld-linux',
50  'libc',
51  'libdl',
52  'libgcc_s',
53  'libm',
54  'libpthread',
55  'libresolv',
56  'librt',
57  'libstdc++',
58  'linux-vdso',
59
60  # Needed for default ozone platforms
61  'libdrm',
62
63  # NSS & NSPR
64  'libnss3',
65  'libnssutil3',
66  'libnspr4',
67  'libplc4',
68  'libplds4',
69  'libsmime3',
70
71  # OpenSSL
72  'libcrypto',
73
74  # Miscellaneous
75  'libcap',
76  'libexpat',
77  'libfontconfig',
78  'libz',
79]
80
81binary_target = 'content_shell'
82
83def stdmsg(_final, errors):
84  if errors:
85    for message in errors:
86      print message
87
88def bbmsg(final, errors):
89  if errors:
90    for message in errors:
91      print '@@@STEP_TEXT@%s@@@' % message
92  if final:
93    print '\n@@@STEP_%s@@@' % final
94
95
96def _main():
97  output = {
98    'message': lambda x: stdmsg(None, x),
99    'fail': lambda x: stdmsg('FAILED', x),
100    'warn': lambda x: stdmsg('WARNING', x),
101    'abend': lambda x: stdmsg('FAILED', x),
102    'ok': lambda x: stdmsg('SUCCESS', x),
103    'verbose': lambda x: None,
104  }
105
106  parser = optparse.OptionParser(
107      "usage: %prog -b <dir> --target <Debug|Release>")
108  parser.add_option("", "--annotate", dest='annotate', action='store_true',
109      default=False, help="include buildbot annotations in output")
110  parser.add_option("", "--noannotate", dest='annotate', action='store_false')
111  parser.add_option("-b", "--build-dir",
112                    help="the location of the compiler output")
113  parser.add_option("--target", help="Debug or Release")
114  parser.add_option('-v', '--verbose', default=False, action='store_true')
115
116  options, args = parser.parse_args()
117  if args:
118    parser.usage()
119    return -1
120
121  # Bake target into build_dir.
122  if options.target and options.build_dir:
123    assert (options.target !=
124            os.path.basename(os.path.dirname(options.build_dir)))
125    options.build_dir = os.path.join(os.path.abspath(options.build_dir),
126                                     options.target)
127
128  if options.build_dir != None:
129    build_dir = os.path.abspath(options.build_dir)
130  else:
131    build_dir = os.getcwd()
132
133  target = os.path.join(build_dir, binary_target)
134
135  if options.annotate:
136    output.update({
137      'message': lambda x: bbmsg(None, x),
138      'fail': lambda x: bbmsg('FAILURE', x),
139      'warn': lambda x: bbmsg('WARNINGS', x),
140      'abend': lambda x: bbmsg('EXCEPTIONS', x),
141      'ok': lambda x: bbmsg(None, x),
142    })
143
144  if options.verbose:
145    output['verbose'] = lambda x: stdmsg(None, x)
146
147  forbidden_regexp = re.compile(string.join(map(re.escape,
148                                                kUndesiredLibraryList), '|'))
149  mapping_regexp = re.compile(r"\s*([^/]*) => (.*)")
150  blessed_regexp = re.compile(r"(%s)[-0-9.]*\.so" % string.join(map(re.escape,
151      kAllowedLibraryList), '|'))
152  built_regexp = re.compile(re.escape(build_dir + os.sep))
153
154  success = 0
155  warning = 0
156
157  p = subprocess.Popen(['ldd', target], stdout=subprocess.PIPE,
158      stderr=subprocess.PIPE)
159  out, err = p.communicate()
160
161  if err != '':
162    output['abend']([
163      'Failed to execute ldd to analyze dependencies for ' + target + ':',
164      '    ' + err,
165    ])
166    return 1
167
168  if out == '':
169    output['abend']([
170      'No output to scan for forbidden dependencies.'
171    ])
172    return 1
173
174  success = 1
175  deps = string.split(out, '\n')
176  for d in deps:
177    libmatch = mapping_regexp.match(d)
178    if libmatch:
179      lib = libmatch.group(1)
180      source = libmatch.group(2)
181      if forbidden_regexp.search(lib):
182        success = 0
183        output['message'](['Forbidden library: ' + lib])
184      elif built_regexp.match(source):
185        output['verbose'](['Built library: ' + lib])
186      elif blessed_regexp.match(lib):
187        output['verbose'](['Blessed library: ' + lib])
188      else:
189        warning = 1
190        output['message'](['Unexpected library: ' + lib])
191
192  if success == 1:
193    if warning == 1:
194      output['warn'](None)
195    else:
196      output['ok'](None)
197    return 0
198  else:
199    output['fail'](None)
200    return 1
201
202if __name__ == "__main__":
203  # handle arguments...
204  # do something reasonable if not run with one...
205  sys.exit(_main())
206