• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
2# reserved. Use of this source code is governed by a BSD-style license that
3# can be found in the LICENSE file.
4
5from __future__ import absolute_import
6from __future__ import print_function
7from file_util import *
8import os
9import re
10import shutil
11import string
12import sys
13import textwrap
14import time
15import itertools
16import hashlib
17
18# Determines string type for python 2 and python 3.
19if sys.version_info[0] == 3:
20  string_type = str
21else:
22  string_type = basestring
23
24
25class cef_api_hash:
26  """ CEF API hash calculator """
27
28  def __init__(self, headerdir, debugdir=None, verbose=False):
29    if headerdir is None or len(headerdir) == 0:
30      raise AssertionError("headerdir is not specified")
31
32    self.__headerdir = headerdir
33    self.__debugdir = debugdir
34    self.__verbose = verbose
35    self.__debug_enabled = not (self.__debugdir is
36                                None) and len(self.__debugdir) > 0
37
38    self.platforms = ["windows", "mac", "linux"]
39
40    self.platform_files = {
41        # List of includes_win_capi from cef_paths2.gypi.
42        "windows": [
43            "internal/cef_types_win.h",
44        ],
45        # List of includes_mac_capi from cef_paths2.gypi.
46        "mac": [
47            "internal/cef_types_mac.h",
48        ],
49        # List of includes_linux_capi from cef_paths2.gypi.
50        "linux": [
51            "internal/cef_types_linux.h",
52        ]
53    }
54
55    self.included_files = []
56
57    # List of include/ and include/internal/ files from cef_paths2.gypi.
58    self.excluded_files = [
59        # includes_common
60        "cef_api_hash.h",
61        "cef_base.h",
62        "cef_config.h",
63        "cef_version.h",
64        "internal/cef_export.h",
65        "internal/cef_ptr.h",
66        "internal/cef_string_wrappers.h",
67        "internal/cef_types_wrappers.h",
68        # includes_win
69        "cef_sandbox_win.h",
70        "internal/cef_win.h",
71        # includes_mac
72        "cef_application_mac.h",
73        "cef_sandbox_mac.h",
74        "internal/cef_mac.h",
75        # includes_linux
76        "internal/cef_linux.h",
77    ]
78
79  def calculate(self):
80    filenames = [
81        filename for filename in self.__get_filenames()
82        if not filename in self.excluded_files
83    ]
84
85    objects = []
86    for filename in filenames:
87      if self.__verbose:
88        print("Processing " + filename + "...")
89      content = read_file(os.path.join(self.__headerdir, filename), True)
90      platforms = list([
91          p for p in self.platforms if self.__is_platform_filename(filename, p)
92      ])
93
94      # Parse cef_string.h happens in special case: grab only defined CEF_STRING_TYPE_xxx declaration
95      content_objects = None
96      if filename == "internal/cef_string.h":
97        content_objects = self.__parse_string_type(content)
98      else:
99        content_objects = self.__parse_objects(content)
100
101      for o in content_objects:
102        o["text"] = self.__prepare_text(o["text"])
103        o["platforms"] = platforms
104        o["filename"] = filename
105        objects.append(o)
106
107    # objects will be sorted including filename, to make stable universal hashes
108    objects = sorted(objects, key=lambda o: o["name"] + "@" + o["filename"])
109
110    if self.__debug_enabled:
111      namelen = max([len(o["name"]) for o in objects])
112      filenamelen = max([len(o["filename"]) for o in objects])
113      dumpsig = []
114      for o in objects:
115        dumpsig.append(
116            format(o["name"], str(namelen) + "s") + "|" + format(
117                o["filename"], "" + str(filenamelen) + "s") + "|" + o["text"])
118      self.__write_debug_file("objects.txt", dumpsig)
119
120    revisions = {}
121
122    for platform in itertools.chain(["universal"], self.platforms):
123      sig = self.__get_final_sig(objects, platform)
124      if self.__debug_enabled:
125        self.__write_debug_file(platform + ".sig", sig)
126      revstr = hashlib.sha1(sig.encode('utf-8')).hexdigest()
127      revisions[platform] = revstr
128
129    return revisions
130
131  def __parse_objects(self, content):
132    """ Returns array of objects in content file. """
133    objects = []
134    content = re.sub("//.*\n", "", content)
135
136    # function declarations
137    for m in re.finditer(
138        "\nCEF_EXPORT\s+?.*?\s+?(\w+)\s*?\(.*?\)\s*?;",
139        content,
140        flags=re.DOTALL):
141      object = {"name": m.group(1), "text": m.group(0).strip()}
142      objects.append(object)
143
144    # structs
145    for m in re.finditer(
146        "\ntypedef\s+?struct\s+?(\w+)\s+?\{.*?\}\s+?(\w+)\s*?;",
147        content,
148        flags=re.DOTALL):
149      object = {"name": m.group(2), "text": m.group(0).strip()}
150      objects.append(object)
151
152    # enums
153    for m in re.finditer(
154        "\ntypedef\s+?enum\s+?\{.*?\}\s+?(\w+)\s*?;", content, flags=re.DOTALL):
155      object = {"name": m.group(1), "text": m.group(0).strip()}
156      objects.append(object)
157
158    # typedefs
159    for m in re.finditer("\ntypedef\s+?.*?\s+(\w+);", content, flags=0):
160      object = {"name": m.group(1), "text": m.group(0).strip()}
161      objects.append(object)
162
163    return objects
164
165  def __parse_string_type(self, content):
166    """ Grab defined CEF_STRING_TYPE_xxx """
167    objects = []
168    for m in re.finditer(
169        "\n\s*?#\s*?define\s+?(CEF_STRING_TYPE_\w+)\s+?.*?\n", content,
170        flags=0):
171      object = {
172          "name": m.group(1),
173          "text": m.group(0),
174      }
175      objects.append(object)
176    return objects
177
178  def __prepare_text(self, text):
179    text = text.strip()
180    text = re.sub("\s+", " ", text)
181    text = re.sub("\(\s+", "(", text)
182    return text
183
184  def __get_final_sig(self, objects, platform):
185    sig = []
186
187    for o in objects:
188      if platform == "universal" or platform in o["platforms"]:
189        sig.append(o["text"])
190
191    return "\n".join(sig)
192
193  def __get_filenames(self):
194    """ Returns file names to be processed, relative to headerdir """
195    headers = [
196        os.path.join(self.__headerdir, filename)
197        for filename in self.included_files
198    ]
199
200    capi_dir = os.path.join(self.__headerdir, "capi")
201    headers = itertools.chain(headers, get_files(os.path.join(capi_dir, "*.h")))
202
203    # Also include capi sub-directories.
204    for root, dirs, files in os.walk(capi_dir):
205      for name in dirs:
206        headers = itertools.chain(headers,
207                                  get_files(os.path.join(root, name, "*.h")))
208
209    headers = itertools.chain(
210        headers, get_files(os.path.join(self.__headerdir, "internal", "*.h")))
211
212    for v in self.platform_files.values():
213      headers = itertools.chain(headers,
214                                [os.path.join(self.__headerdir, f) for f in v])
215
216    normalized = [
217        os.path.relpath(filename, self.__headerdir) for filename in headers
218    ]
219    normalized = [f.replace('\\', '/').lower() for f in normalized]
220
221    return list(set(normalized))
222
223  def __is_platform_filename(self, filename, platform):
224    if platform == "universal":
225      return True
226    if not platform in self.platform_files:
227      return False
228    listed = False
229    for p in self.platforms:
230      if filename in self.platform_files[p]:
231        if p == platform:
232          return True
233        else:
234          listed = True
235    return not listed
236
237  def __write_debug_file(self, filename, content):
238    make_dir(self.__debugdir)
239    outfile = os.path.join(self.__debugdir, filename)
240    dir = os.path.dirname(outfile)
241    make_dir(dir)
242    if not isinstance(content, string_type):
243      content = "\n".join(content)
244    write_file(outfile, content)
245
246
247if __name__ == "__main__":
248  from optparse import OptionParser
249  import time
250
251  disc = """
252    This utility calculates CEF API hash.
253    """
254
255  parser = OptionParser(description=disc)
256  parser.add_option(
257      '--cpp-header-dir',
258      dest='cppheaderdir',
259      metavar='DIR',
260      help='input directory for C++ header files [required]')
261  parser.add_option(
262      '--debug-dir',
263      dest='debugdir',
264      metavar='DIR',
265      help='intermediate directory for easy debugging')
266  parser.add_option(
267      '-v',
268      '--verbose',
269      action='store_true',
270      dest='verbose',
271      default=False,
272      help='output detailed status information')
273  (options, args) = parser.parse_args()
274
275  # the cppheader option is required
276  if options.cppheaderdir is None:
277    parser.print_help(sys.stdout)
278    sys.exit()
279
280  # calculate
281  c_start_time = time.time()
282
283  calc = cef_api_hash(options.cppheaderdir, options.debugdir, options.verbose)
284  revisions = calc.calculate()
285
286  c_completed_in = time.time() - c_start_time
287
288  print("{")
289  for k in sorted(revisions.keys()):
290    print(format("\"" + k + "\"", ">12s") + ": \"" + revisions[k] + "\"")
291  print("}")
292  # print
293  # print 'Completed in: ' + str(c_completed_in)
294  # print
295
296  # print "Press any key to continue...";
297  # sys.stdin.readline();
298