| # Copyright 2017 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from __future__ import division |
| |
| import array |
| import difflib |
| import filecmp |
| import io |
| import operator |
| import os |
| import posixpath |
| import re |
| import shutil |
| import struct |
| import subprocess |
| import sys |
| import tempfile |
| import uuid |
| |
| from functools import reduce |
| |
| |
| def ZapTimestamp(filename): |
| contents = open(filename, 'rb').read() |
| # midl.exe writes timestamp 2147483647 (2^31 - 1) as creation date into its |
| # outputs, but using the local timezone. To make the output timezone- |
| # independent, replace that date with a fixed string of the same length. |
| # Also blank out the minor version number. |
| if filename.endswith('.tlb'): |
| # See https://chromium-review.googlesource.com/c/chromium/src/+/693223 for |
| # a fairly complete description of the .tlb binary format. |
| # TLB files start with a 54 byte header. Offset 0x20 stores how many types |
| # are defined in the file, and the header is followed by that many uint32s. |
| # After that, 15 section headers appear. Each section header is 16 bytes, |
| # starting with offset and length uint32s. |
| # Section 12 in the file contains custom() data. custom() data has a type |
| # (int, string, etc). Each custom data chunk starts with a uint16_t |
| # describing its type. Type 8 is string data, consisting of a uint32_t |
| # len, followed by that many data bytes, followed by 'W' bytes to pad to a |
| # 4 byte boundary. Type 0x13 is uint32 data, followed by 4 data bytes, |
| # followed by two 'W' to pad to a 4 byte boundary. |
| # The custom block always starts with one string containing "Created by |
| # MIDL version 8...", followed by one uint32 containing 0x7fffffff, |
| # followed by another uint32 containing the MIDL compiler version (e.g. |
| # 0x0801026e for v8.1.622 -- 0x26e == 622). These 3 fields take 0x54 bytes. |
| # There might be more custom data after that, but these 3 blocks are always |
| # there for file-level metadata. |
| # All data is little-endian in the file. |
| assert contents[0:8] == b'MSFT\x02\x00\x01\x00' |
| ntypes, = struct.unpack_from('<I', contents, 0x20) |
| custom_off, custom_len = struct.unpack_from( |
| '<II', contents, 0x54 + 4*ntypes + 11*16) |
| assert custom_len >= 0x54 |
| # First: Type string (0x8), followed by 0x3e characters. |
| assert contents[custom_off:custom_off + 6] == b'\x08\x00\x3e\x00\x00\x00' |
| assert re.match( |
| br'Created by MIDL version 8\.\d\d\.\d{4} ' |
| br'at ... Jan 1. ..:..:.. 2038\n', |
| contents[custom_off + 6:custom_off + 6 + 0x3e]) |
| # Second: Type uint32 (0x13) storing 0x7fffffff (followed by WW / 0x57 pad) |
| assert contents[custom_off+6+0x3e:custom_off+6+0x3e+8] == \ |
| b'\x13\x00\xff\xff\xff\x7f\x57\x57' |
| # Third: Type uint32 (0x13) storing MIDL compiler version. |
| assert contents[custom_off + 6 + 0x3e + 8:custom_off + 6 + 0x3e + 8 + |
| 2] == b'\x13\x00' |
| # Replace "Created by" string with fixed string, and fixed MIDL version with |
| # 8.1.622 always. |
| contents = ( |
| contents[0:custom_off + 6] + |
| b'Created by MIDL version 8.xx.xxxx at a redacted point in time\n' + |
| # uint32 (0x13) val 0x7fffffff, WW, uint32 (0x13), val 0x0801026e, WW |
| b'\x13\x00\xff\xff\xff\x7f\x57\x57\x13\x00\x6e\x02\x01\x08\x57\x57' + |
| contents[custom_off + 0x54:]) |
| else: |
| contents = re.sub( |
| br'File created by MIDL compiler version 8\.\d\d\.\d{4} \*/\r\n' |
| br'/\* at ... Jan 1. ..:..:.. 2038', |
| br'File created by MIDL compiler version 8.xx.xxxx */\r\n' |
| br'/* at a redacted point in time', contents) |
| contents = re.sub( |
| br' Oicf, W1, Zp8, env=(.....) \(32b run\), ' |
| br'target_arch=(AMD64|X86) 8\.\d\d\.\d{4}', |
| br' Oicf, W1, Zp8, env=\1 (32b run), target_arch=\2 8.xx.xxxx', |
| contents) |
| # TODO(thakis): If we need more hacks than these, try to verify checked-in |
| # outputs when we're using the hermetic toolchain. |
| # midl.exe older than 8.1.622 omit '//' after #endif, fix that: |
| contents = contents.replace(b'#endif !_MIDL_USE_GUIDDEF_', |
| b'#endif // !_MIDL_USE_GUIDDEF_') |
| # midl.exe puts the midl version into code in one place. To have |
| # predictable output, lie about the midl version if it's not 8.1.622. |
| # This is unfortunate, but remember that there's beauty too in imperfection. |
| contents = contents.replace(b'0x801026c, /* MIDL Version 8.1.620 */', |
| b'0x801026e, /* MIDL Version 8.1.622 */') |
| open(filename, 'wb').write(contents) |
| |
| |
| def get_tlb_contents(tlb_file): |
| # See ZapTimestamp() for a short overview of the .tlb format. |
| contents = open(tlb_file, 'rb').read() |
| assert contents[0:8] == b'MSFT\x02\x00\x01\x00' |
| ntypes, = struct.unpack_from('<I', contents, 0x20) |
| type_off, type_len = struct.unpack_from('<II', contents, 0x54 + 4*ntypes) |
| |
| guid_off, guid_len = struct.unpack_from( |
| '<II', contents, 0x54 + 4*ntypes + 5*16) |
| assert guid_len % 24 == 0 |
| |
| contents = array.array('B', contents) |
| |
| return contents, ntypes, type_off, guid_off, guid_len |
| |
| |
| def recreate_guid_hashtable(contents, ntypes, guid_off, guid_len): |
| # This function is called after changing guids in section 6 (the "guid" |
| # section). This function recreates the GUID hashtable in section 5. Since the |
| # hash table uses chaining, it's easiest to recompute it from scratch rather |
| # than trying to patch it up. |
| hashtab = [0xffffffff] * (0x80 // 4) |
| for guidind in range(guid_off, guid_off + guid_len, 24): |
| guidbytes, typeoff, nextguid = struct.unpack_from( |
| '<16sII', contents, guidind) |
| words = struct.unpack('<8H', guidbytes) |
| # midl seems to use the following simple hash function for GUIDs: |
| guidhash = reduce(operator.xor, [w for w in words]) % (0x80 // 4) |
| nextguid = hashtab[guidhash] |
| struct.pack_into('<I', contents, guidind + 0x14, nextguid) |
| hashtab[guidhash] = guidind - guid_off |
| hash_off, hash_len = struct.unpack_from( |
| '<II', contents, 0x54 + 4*ntypes + 4*16) |
| for i, hashval in enumerate(hashtab): |
| struct.pack_into('<I', contents, hash_off + 4*i, hashval) |
| |
| |
| def overwrite_guids_h(h_file, dynamic_guids): |
| contents = open(h_file, 'rb').read() |
| for key in dynamic_guids: |
| contents = re.sub(key, dynamic_guids[key], contents, flags=re.I) |
| open(h_file, 'wb').write(contents) |
| |
| |
| def get_uuid_format(guid, prefix): |
| formatted_uuid = b'0x%s,0x%s,0x%s,' % (guid[0:8], guid[9:13], guid[14:18]) |
| formatted_uuid += b'%s0x%s,0x%s' % (prefix, guid[19:21], guid[21:23]) |
| for i in range(24, len(guid), 2): |
| formatted_uuid += b',0x' + guid[i:i + 2] |
| return formatted_uuid |
| |
| |
| def get_uuid_format_iid_file(guid): |
| # Convert from "D0E1CACC-C63C-4192-94AB-BF8EAD0E3B83" to |
| # 0xD0E1CACC,0xC63C,0x4192,0x94,0xAB,0xBF,0x8E,0xAD,0x0E,0x3B,0x83. |
| return get_uuid_format(guid, b'') |
| |
| |
| def overwrite_guids_iid(iid_file, dynamic_guids): |
| contents = open(iid_file, 'rb').read() |
| for key in dynamic_guids: |
| contents = re.sub(get_uuid_format_iid_file(key), |
| get_uuid_format_iid_file(dynamic_guids[key]), |
| contents, |
| flags=re.I) |
| open(iid_file, 'wb').write(contents) |
| |
| |
| def get_uuid_format_proxy_file(guid): |
| # Convert from "D0E1CACC-C63C-4192-94AB-BF8EAD0E3B83" to |
| # {0xD0E1CACC,0xC63C,0x4192,{0x94,0xAB,0xBF,0x8E,0xAD,0x0E,0x3B,0x83}}. |
| return get_uuid_format(guid, b'{') |
| |
| |
| def overwrite_guids_proxy(proxy_file, dynamic_guids): |
| contents = open(proxy_file, 'rb').read() |
| for key in dynamic_guids: |
| contents = re.sub(get_uuid_format_proxy_file(key), |
| get_uuid_format_proxy_file(dynamic_guids[key]), |
| contents, |
| flags=re.I) |
| open(proxy_file, 'wb').write(contents) |
| |
| |
| def getguid(contents, offset): |
| # Returns a guid string of the form "D0E1CACC-C63C-4192-94AB-BF8EAD0E3B83". |
| g0, g1, g2, g3 = struct.unpack_from('<IHH8s', contents, offset) |
| g3 = b''.join([b'%02X' % g for g in bytearray(g3)]) |
| return b'%08X-%04X-%04X-%s-%s' % (g0, g1, g2, g3[0:4], g3[4:]) |
| |
| |
| def setguid(contents, offset, guid): |
| guid = uuid.UUID(guid.decode('utf-8')) |
| struct.pack_into('<IHH8s', contents, offset, |
| *(guid.fields[0:3] + (guid.bytes[8:], ))) |
| |
| |
| def overwrite_guids_tlb(tlb_file, dynamic_guids): |
| contents, ntypes, type_off, guid_off, guid_len = get_tlb_contents(tlb_file) |
| |
| for i in range(0, guid_len, 24): |
| current_guid = getguid(contents, guid_off + i) |
| for key in dynamic_guids: |
| if key.lower() == current_guid.lower(): |
| setguid(contents, guid_off + i, dynamic_guids[key]) |
| |
| recreate_guid_hashtable(contents, ntypes, guid_off, guid_len) |
| open(tlb_file, 'wb').write(contents) |
| |
| |
| # Handle multiple guid substitutions, where |dynamic_guids| is of the form |
| # "PLACEHOLDER-GUID-158428a4-6014-4978-83ba-9fad0dabe791=" |
| # "3d852661-c795-4d20-9b95-5561e9a1d2d9," |
| # "PLACEHOLDER-GUID-63B8FFB1-5314-48C9-9C57-93EC8BC6184B=" |
| # "D0E1CACC-C63C-4192-94AB-BF8EAD0E3B83". |
| # |
| # Before specifying |dynamic_guids| in the build, the IDL file is first compiled |
| # with "158428a4-6014-4978-83ba-9fad0dabe791" and |
| # "63B8FFB1-5314-48C9-9C57-93EC8BC6184B". These are the "replaceable" guids, |
| # i.e., guids that can be replaced in future builds. The resulting MIDL outputs |
| # are copied over to src\third_party\win_build_output\. |
| # |
| # Then, in the future, any changes to these guids can be accomplished by |
| # providing |dynamic_guids| of the format above in the build file. These |
| # "dynamic" guid changes by themselves will not require the MIDL compiler and |
| # therefore will not require copying output over to |
| # src\third_party\win_build_output\. |
| # |
| # The pre-generated src\third_party\win_build_output\ files are used for |
| # cross-compiling on other platforms, since the MIDL compiler is Windows-only. |
| def overwrite_guids(h_file, iid_file, proxy_file, tlb_file, dynamic_guids): |
| # Fix up GUIDs in .h, _i.c, _p.c, and .tlb. |
| overwrite_guids_h(h_file, dynamic_guids) |
| overwrite_guids_iid(iid_file, dynamic_guids) |
| overwrite_guids_proxy(proxy_file, dynamic_guids) |
| if tlb_file: |
| overwrite_guids_tlb(tlb_file, dynamic_guids) |
| |
| |
| # This function removes all occurrences of 'PLACEHOLDER-GUID-' from the |
| # template, and if |dynamic_guids| is specified, also replaces the guids within |
| # the file. Finally, it writes the resultant output to the |idl| file. |
| def generate_idl_from_template(idl_template, dynamic_guids, idl): |
| contents = open(idl_template, 'rb').read() |
| contents = re.sub(b'PLACEHOLDER-GUID-', b'', contents, flags=re.I) |
| if dynamic_guids: |
| for key in dynamic_guids: |
| contents = re.sub(key, dynamic_guids[key], contents, flags=re.I) |
| open(idl, 'wb').write(contents) |
| |
| |
| # This function runs the MIDL compiler with the provided arguments. It creates |
| # and returns a tuple of |0,midl_output_dir| on success. |
| def run_midl(args, env_dict): |
| midl_output_dir = tempfile.mkdtemp() |
| delete_midl_output_dir = True |
| |
| try: |
| popen = subprocess.Popen(args + ['/out', midl_output_dir], |
| shell=True, |
| universal_newlines=True, |
| env=env_dict, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT) |
| out, _ = popen.communicate() |
| |
| # Filter junk out of stdout, and write filtered versions. Output we want |
| # to filter is pairs of lines that look like this: |
| # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl |
| # objidl.idl |
| lines = out.splitlines() |
| prefixes = ('Processing ', '64 bit Processing ') |
| processing = set( |
| os.path.basename(x) for x in lines if x.startswith(prefixes)) |
| for line in lines: |
| if not line.startswith(prefixes) and line not in processing: |
| print(line) |
| |
| if popen.returncode != 0: |
| return popen.returncode, midl_output_dir |
| |
| for f in os.listdir(midl_output_dir): |
| ZapTimestamp(os.path.join(midl_output_dir, f)) |
| |
| delete_midl_output_dir = False |
| finally: |
| if os.path.exists(midl_output_dir) and delete_midl_output_dir: |
| shutil.rmtree(midl_output_dir) |
| |
| return 0, midl_output_dir |
| |
| |
| # This function adds support for dynamic generation of guids: when values are |
| # specified as 'uuid5:name', this function will substitute the values with |
| # generated dynamic guids using the uuid5 function. The uuid5 function generates |
| # a guid based on the SHA-1 hash of a namespace identifier (which is the guid |
| # that comes after 'PLACEHOLDER-GUID-') and a name (which is a string, such as a |
| # version string "87.1.2.3"). |
| # |
| # For instance, when |dynamic_guid| is of the form: |
| # "PLACEHOLDER-GUID-158428a4-6014-4978-83ba-9fad0dabe791=uuid5:88.0.4307.0 |
| # ," |
| # "PLACEHOLDER-GUID-63B8FFB1-5314-48C9-9C57-93EC8BC6184B=uuid5:88.0.4307.0 |
| # " |
| # |
| # "PLACEHOLDER-GUID-158428a4-6014-4978-83ba-9fad0dabe791" would be substituted |
| # with uuid5("158428a4-6014-4978-83ba-9fad0dabe791", "88.0.4307.0"), which is |
| # "64700170-AD80-5DE3-924E-2F39D862CFD5". And |
| # "PLACEHOLDER-GUID-63B8FFB1-5314-48C9-9C57-93EC8BC6184B" would be |
| # substituted with uuid5("63B8FFB1-5314-48C9-9C57-93EC8BC6184B", "88.0.4307.0"), |
| # which is "7B6E7538-3C38-5565-BC92-42BCEE268D76". |
| def uuid5_substitutions(dynamic_guids): |
| for key, value in dynamic_guids.items(): |
| if value.startswith('uuid5:'): |
| name = value.split('uuid5:', 1)[1] |
| assert name |
| dynamic_guids[key] = str(uuid.uuid5(uuid.UUID(key), name)).upper() |
| |
| |
| def main(arch, gendir, outdir, dynamic_guids, tlb, h, dlldata, iid, proxy, |
| clang, idl, *flags): |
| # Copy checked-in outputs to final location. |
| source = gendir |
| if os.path.isdir(os.path.join(source, os.path.basename(idl))): |
| source = os.path.join(source, os.path.basename(idl)) |
| source = os.path.join(source, arch.split('.')[1]) # Append 'x86' or 'x64'. |
| source = os.path.normpath(source) |
| |
| source_exists = True |
| if not os.path.isdir(source): |
| source_exists = False |
| if sys.platform != 'win32': |
| print('Directory %s needs to be populated from Windows first' % source) |
| return 1 |
| |
| # This is a brand new IDL file that does not have outputs under |
| # third_party\win_build_output\midl. We create an empty directory for now. |
| os.makedirs(source) |
| |
| common_files = [h, iid] |
| if tlb != 'none': |
| # Not all projects use tlb files. |
| common_files += [tlb] |
| else: |
| tlb = None |
| |
| if dlldata != 'none': |
| # Not all projects use dlldta files. |
| common_files += [dlldata] |
| else: |
| dlldata = None |
| |
| # Not all projects use proxy files |
| if proxy != 'none': |
| # Not all projects use proxy files. |
| common_files += [proxy] |
| else: |
| proxy = None |
| |
| for source_file in common_files: |
| file_path = os.path.join(source, source_file) |
| if not os.path.isfile(file_path): |
| source_exists = False |
| if sys.platform != 'win32': |
| print('File %s needs to be generated from Windows first' % file_path) |
| return 1 |
| |
| # Either this is a brand new IDL file that does not have outputs under |
| # third_party\win_build_output\midl or the file is (unexpectedly) missing. |
| # We create an empty file for now. The rest of the machinery below will |
| # then generate the correctly populated file using the MIDL compiler and |
| # instruct the developer to copy that file under |
| # third_party\win_build_output\midl. |
| open(file_path, 'wb').close() |
| shutil.copy(file_path, outdir) |
| |
| if dynamic_guids != 'none': |
| assert '=' in dynamic_guids |
| if dynamic_guids.startswith("ignore_proxy_stub,"): |
| # TODO(ganesh): The custom proxy/stub file ("_p.c") is not generated |
| # correctly for dynamic IIDs (but correctly if there are only dynamic |
| # CLSIDs). The proxy/stub lookup functions generated by MIDL.exe within |
| # "_p.c" rely on a sorted set of vtable lists, which we are not currently |
| # regenerating. At the moment, no project in Chromium that uses dynamic |
| # IIDs is relying on the custom proxy/stub file. So for now, if |
| # |dynamic_guids| is prefixed with "ignore_proxy_stub,", we exclude the |
| # custom proxy/stub file from the directory comparisons. |
| common_files.remove(proxy) |
| dynamic_guids = dynamic_guids.split("ignore_proxy_stub,", 1)[1] |
| dynamic_guids = re.sub('PLACEHOLDER-GUID-', '', dynamic_guids, flags=re.I) |
| dynamic_guids = dynamic_guids.split(',') |
| dynamic_guids = dict(s.split('=') for s in dynamic_guids) |
| uuid5_substitutions(dynamic_guids) |
| dynamic_guids_bytes = { |
| k.encode('utf-8'): v.encode('utf-8') |
| for k, v in dynamic_guids.items() |
| } |
| if source_exists: |
| overwrite_guids(*(os.path.join(outdir, file) if file else None |
| for file in [h, iid, proxy, tlb]), |
| dynamic_guids=dynamic_guids_bytes) |
| else: |
| dynamic_guids = None |
| |
| # On non-Windows, that's all we can do. |
| if sys.platform != 'win32': |
| return 0 |
| |
| idl_template = None |
| if dynamic_guids: |
| idl_template = idl |
| |
| # posixpath is used here to keep the MIDL-generated files with a uniform |
| # separator of '/' instead of mixed '/' and '\\'. |
| idl = posixpath.join( |
| outdir, |
| os.path.splitext(os.path.basename(idl_template))[0] + '.idl') |
| |
| # |idl_template| can contain one or more occurrences of guids that are |
| # substituted with |dynamic_guids|, and then MIDL is run on the substituted |
| # IDL file. |
| generate_idl_from_template(idl_template, dynamic_guids_bytes, idl) |
| |
| # On Windows, run midl.exe on the input and check that its outputs are |
| # identical to the checked-in outputs (after replacing guids if |
| # |dynamic_guids| is specified). |
| |
| # Read the environment block from the file. This is stored in the format used |
| # by CreateProcess. Drop last 2 NULs, one for list terminator, one for |
| # trailing vs. separator. |
| env_pairs = open(arch).read()[:-2].split('\0') |
| env_dict = dict([item.split('=', 1) for item in env_pairs]) |
| |
| # Extract the /D options and send them to the preprocessor. |
| preprocessor_options = '-E -nologo -Wno-nonportable-include-path' |
| preprocessor_options += ''.join( |
| [' ' + flag for flag in flags if flag.startswith('/D')]) |
| args = ['midl', '/nologo'] + list(flags) + (['/tlb', tlb] if tlb else []) + [ |
| '/h', h |
| ] + (['/dlldata', dlldata] if dlldata else []) + ['/iid', iid] + ( |
| ['/proxy', proxy] if proxy else |
| []) + ['/cpp_cmd', clang, '/cpp_opt', preprocessor_options, idl] |
| |
| returncode, midl_output_dir = run_midl(args, env_dict) |
| if returncode != 0: |
| return returncode |
| |
| # Now compare the output in midl_output_dir to the copied-over outputs. |
| _, mismatch, errors = filecmp.cmpfiles(midl_output_dir, outdir, common_files) |
| assert not errors |
| |
| if mismatch: |
| print('midl.exe output different from files in %s, see %s' % |
| (outdir, midl_output_dir)) |
| for f in mismatch: |
| if f.endswith('.tlb'): continue |
| fromfile = os.path.join(outdir, f) |
| tofile = os.path.join(midl_output_dir, f) |
| print(''.join( |
| difflib.unified_diff( |
| io.open(fromfile).readlines(), |
| io.open(tofile).readlines(), fromfile, tofile))) |
| |
| if dynamic_guids: |
| # |idl_template| can contain one or more occurrences of guids prefixed |
| # with 'PLACEHOLDER-GUID-'. We first remove the extraneous |
| # 'PLACEHOLDER-GUID-' prefix and then run MIDL on the substituted IDL |
| # file. |
| # No guid substitutions are done at this point, because we want to compile |
| # with the placeholder guids and then instruct the user to copy the output |
| # over to |source| which is typically src\third_party\win_build_output\. |
| # In future runs, the placeholder guids in |source| are replaced with the |
| # guids specified in |dynamic_guids|. |
| generate_idl_from_template(idl_template, None, idl) |
| returncode, midl_output_dir = run_midl(args, env_dict) |
| if returncode != 0: |
| return returncode |
| |
| print('To rebaseline:') |
| print(r' copy /y %s\* %s' % (midl_output_dir, source)) |
| return 1 |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(*sys.argv[1:])) |