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