1*8975f5c5SAndroid Build Coastguard Worker# Copyright 2015 The Chromium Authors 2*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 3*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 4*8975f5c5SAndroid Build Coastguard Worker 5*8975f5c5SAndroid Build Coastguard Worker# Runs the Microsoft Message Compiler (mc.exe). 6*8975f5c5SAndroid Build Coastguard Worker# 7*8975f5c5SAndroid Build Coastguard Worker# Usage: message_compiler.py <environment_file> [<args to mc.exe>*] 8*8975f5c5SAndroid Build Coastguard Worker 9*8975f5c5SAndroid Build Coastguard Worker 10*8975f5c5SAndroid Build Coastguard Workerimport difflib 11*8975f5c5SAndroid Build Coastguard Workerimport filecmp 12*8975f5c5SAndroid Build Coastguard Workerimport os 13*8975f5c5SAndroid Build Coastguard Workerimport re 14*8975f5c5SAndroid Build Coastguard Workerimport shutil 15*8975f5c5SAndroid Build Coastguard Workerimport subprocess 16*8975f5c5SAndroid Build Coastguard Workerimport sys 17*8975f5c5SAndroid Build Coastguard Workerimport tempfile 18*8975f5c5SAndroid Build Coastguard Worker 19*8975f5c5SAndroid Build Coastguard Workerdef main(): 20*8975f5c5SAndroid Build Coastguard Worker env_file, rest = sys.argv[1], sys.argv[2:] 21*8975f5c5SAndroid Build Coastguard Worker 22*8975f5c5SAndroid Build Coastguard Worker # Parse some argument flags. 23*8975f5c5SAndroid Build Coastguard Worker header_dir = None 24*8975f5c5SAndroid Build Coastguard Worker resource_dir = None 25*8975f5c5SAndroid Build Coastguard Worker input_file = None 26*8975f5c5SAndroid Build Coastguard Worker for i, arg in enumerate(rest): 27*8975f5c5SAndroid Build Coastguard Worker if arg == '-h' and len(rest) > i + 1: 28*8975f5c5SAndroid Build Coastguard Worker assert header_dir == None 29*8975f5c5SAndroid Build Coastguard Worker header_dir = rest[i + 1] 30*8975f5c5SAndroid Build Coastguard Worker elif arg == '-r' and len(rest) > i + 1: 31*8975f5c5SAndroid Build Coastguard Worker assert resource_dir == None 32*8975f5c5SAndroid Build Coastguard Worker resource_dir = rest[i + 1] 33*8975f5c5SAndroid Build Coastguard Worker elif arg.endswith('.mc') or arg.endswith('.man'): 34*8975f5c5SAndroid Build Coastguard Worker assert input_file == None 35*8975f5c5SAndroid Build Coastguard Worker input_file = arg 36*8975f5c5SAndroid Build Coastguard Worker 37*8975f5c5SAndroid Build Coastguard Worker # Copy checked-in outputs to final location. 38*8975f5c5SAndroid Build Coastguard Worker THIS_DIR = os.path.abspath(os.path.dirname(__file__)) 39*8975f5c5SAndroid Build Coastguard Worker assert header_dir == resource_dir 40*8975f5c5SAndroid Build Coastguard Worker source = os.path.join(THIS_DIR, "..", "..", 41*8975f5c5SAndroid Build Coastguard Worker "third_party", "win_build_output", 42*8975f5c5SAndroid Build Coastguard Worker re.sub(r'^(?:[^/]+/)?gen/', 'mc/', header_dir)) 43*8975f5c5SAndroid Build Coastguard Worker # Set copy_function to shutil.copy to update the timestamp on the destination. 44*8975f5c5SAndroid Build Coastguard Worker shutil.copytree(source, 45*8975f5c5SAndroid Build Coastguard Worker header_dir, 46*8975f5c5SAndroid Build Coastguard Worker copy_function=shutil.copy, 47*8975f5c5SAndroid Build Coastguard Worker dirs_exist_ok=True) 48*8975f5c5SAndroid Build Coastguard Worker 49*8975f5c5SAndroid Build Coastguard Worker # On non-Windows, that's all we can do. 50*8975f5c5SAndroid Build Coastguard Worker if sys.platform != 'win32': 51*8975f5c5SAndroid Build Coastguard Worker return 52*8975f5c5SAndroid Build Coastguard Worker 53*8975f5c5SAndroid Build Coastguard Worker # On Windows, run mc.exe on the input and check that its outputs are 54*8975f5c5SAndroid Build Coastguard Worker # identical to the checked-in outputs. 55*8975f5c5SAndroid Build Coastguard Worker 56*8975f5c5SAndroid Build Coastguard Worker # Read the environment block from the file. This is stored in the format used 57*8975f5c5SAndroid Build Coastguard Worker # by CreateProcess. Drop last 2 NULs, one for list terminator, one for 58*8975f5c5SAndroid Build Coastguard Worker # trailing vs. separator. 59*8975f5c5SAndroid Build Coastguard Worker env_pairs = open(env_file).read()[:-2].split('\0') 60*8975f5c5SAndroid Build Coastguard Worker env_dict = dict([item.split('=', 1) for item in env_pairs]) 61*8975f5c5SAndroid Build Coastguard Worker 62*8975f5c5SAndroid Build Coastguard Worker extension = os.path.splitext(input_file)[1] 63*8975f5c5SAndroid Build Coastguard Worker if extension in ['.man', '.mc']: 64*8975f5c5SAndroid Build Coastguard Worker # For .man files, mc's output changed significantly from Version 10.0.15063 65*8975f5c5SAndroid Build Coastguard Worker # to Version 10.0.16299. We should always have the output of the current 66*8975f5c5SAndroid Build Coastguard Worker # default SDK checked in and compare to that. Early out if a different SDK 67*8975f5c5SAndroid Build Coastguard Worker # is active. This also happens with .mc files. 68*8975f5c5SAndroid Build Coastguard Worker # TODO(thakis): Check in new baselines and compare to 16299 instead once 69*8975f5c5SAndroid Build Coastguard Worker # we use the 2017 Fall Creator's Update by default. 70*8975f5c5SAndroid Build Coastguard Worker mc_help = subprocess.check_output(['mc.exe', '/?'], env=env_dict, 71*8975f5c5SAndroid Build Coastguard Worker stderr=subprocess.STDOUT, shell=True) 72*8975f5c5SAndroid Build Coastguard Worker version = re.search(br'Message Compiler\s+Version (\S+)', mc_help).group(1) 73*8975f5c5SAndroid Build Coastguard Worker if version != b'10.0.22621': 74*8975f5c5SAndroid Build Coastguard Worker return 75*8975f5c5SAndroid Build Coastguard Worker 76*8975f5c5SAndroid Build Coastguard Worker # mc writes to stderr, so this explicitly redirects to stdout and eats it. 77*8975f5c5SAndroid Build Coastguard Worker try: 78*8975f5c5SAndroid Build Coastguard Worker tmp_dir = tempfile.mkdtemp() 79*8975f5c5SAndroid Build Coastguard Worker delete_tmp_dir = True 80*8975f5c5SAndroid Build Coastguard Worker if header_dir: 81*8975f5c5SAndroid Build Coastguard Worker rest[rest.index('-h') + 1] = tmp_dir 82*8975f5c5SAndroid Build Coastguard Worker header_dir = tmp_dir 83*8975f5c5SAndroid Build Coastguard Worker if resource_dir: 84*8975f5c5SAndroid Build Coastguard Worker rest[rest.index('-r') + 1] = tmp_dir 85*8975f5c5SAndroid Build Coastguard Worker resource_dir = tmp_dir 86*8975f5c5SAndroid Build Coastguard Worker 87*8975f5c5SAndroid Build Coastguard Worker # This needs shell=True to search the path in env_dict for the mc 88*8975f5c5SAndroid Build Coastguard Worker # executable. 89*8975f5c5SAndroid Build Coastguard Worker subprocess.check_output(['mc.exe'] + rest, 90*8975f5c5SAndroid Build Coastguard Worker env=env_dict, 91*8975f5c5SAndroid Build Coastguard Worker stderr=subprocess.STDOUT, 92*8975f5c5SAndroid Build Coastguard Worker shell=True) 93*8975f5c5SAndroid Build Coastguard Worker # We require all source code (in particular, the header generated here) to 94*8975f5c5SAndroid Build Coastguard Worker # be UTF-8. jinja can output the intermediate .mc file in UTF-8 or UTF-16LE. 95*8975f5c5SAndroid Build Coastguard Worker # However, mc.exe only supports Unicode via the -u flag, and it assumes when 96*8975f5c5SAndroid Build Coastguard Worker # that is specified that the input is UTF-16LE (and errors out on UTF-8 97*8975f5c5SAndroid Build Coastguard Worker # files, assuming they're ANSI). Even with -u specified and UTF16-LE input, 98*8975f5c5SAndroid Build Coastguard Worker # it generates an ANSI header, and includes broken versions of the message 99*8975f5c5SAndroid Build Coastguard Worker # text in the comment before the value. To work around this, for any invalid 100*8975f5c5SAndroid Build Coastguard Worker # // comment lines, we simply drop the line in the header after building it. 101*8975f5c5SAndroid Build Coastguard Worker # Also, mc.exe apparently doesn't always write #define lines in 102*8975f5c5SAndroid Build Coastguard Worker # deterministic order, so manually sort each block of #defines. 103*8975f5c5SAndroid Build Coastguard Worker if header_dir: 104*8975f5c5SAndroid Build Coastguard Worker header_file = os.path.join( 105*8975f5c5SAndroid Build Coastguard Worker header_dir, os.path.splitext(os.path.basename(input_file))[0] + '.h') 106*8975f5c5SAndroid Build Coastguard Worker header_contents = [] 107*8975f5c5SAndroid Build Coastguard Worker with open(header_file, 'rb') as f: 108*8975f5c5SAndroid Build Coastguard Worker define_block = [] # The current contiguous block of #defines. 109*8975f5c5SAndroid Build Coastguard Worker for line in f.readlines(): 110*8975f5c5SAndroid Build Coastguard Worker if line.startswith(b'//') and b'?' in line: 111*8975f5c5SAndroid Build Coastguard Worker continue 112*8975f5c5SAndroid Build Coastguard Worker if line.startswith(b'#define '): 113*8975f5c5SAndroid Build Coastguard Worker define_block.append(line) 114*8975f5c5SAndroid Build Coastguard Worker continue 115*8975f5c5SAndroid Build Coastguard Worker # On the first non-#define line, emit the sorted preceding #define 116*8975f5c5SAndroid Build Coastguard Worker # block. 117*8975f5c5SAndroid Build Coastguard Worker header_contents += sorted(define_block, key=lambda s: s.split()[-1]) 118*8975f5c5SAndroid Build Coastguard Worker define_block = [] 119*8975f5c5SAndroid Build Coastguard Worker header_contents.append(line) 120*8975f5c5SAndroid Build Coastguard Worker # If the .h file ends with a #define block, flush the final block. 121*8975f5c5SAndroid Build Coastguard Worker header_contents += sorted(define_block, key=lambda s: s.split()[-1]) 122*8975f5c5SAndroid Build Coastguard Worker with open(header_file, 'wb') as f: 123*8975f5c5SAndroid Build Coastguard Worker f.write(b''.join(header_contents)) 124*8975f5c5SAndroid Build Coastguard Worker 125*8975f5c5SAndroid Build Coastguard Worker # mc.exe invocation and post-processing are complete, now compare the output 126*8975f5c5SAndroid Build Coastguard Worker # in tmp_dir to the checked-in outputs. 127*8975f5c5SAndroid Build Coastguard Worker diff = filecmp.dircmp(tmp_dir, source) 128*8975f5c5SAndroid Build Coastguard Worker if diff.diff_files or set(diff.left_list) != set(diff.right_list): 129*8975f5c5SAndroid Build Coastguard Worker print('mc.exe output different from files in %s, see %s' % (source, 130*8975f5c5SAndroid Build Coastguard Worker tmp_dir)) 131*8975f5c5SAndroid Build Coastguard Worker diff.report() 132*8975f5c5SAndroid Build Coastguard Worker for f in diff.diff_files: 133*8975f5c5SAndroid Build Coastguard Worker if f.endswith('.bin'): continue 134*8975f5c5SAndroid Build Coastguard Worker fromfile = os.path.join(source, f) 135*8975f5c5SAndroid Build Coastguard Worker tofile = os.path.join(tmp_dir, f) 136*8975f5c5SAndroid Build Coastguard Worker print(''.join( 137*8975f5c5SAndroid Build Coastguard Worker difflib.unified_diff( 138*8975f5c5SAndroid Build Coastguard Worker open(fromfile).readlines(), 139*8975f5c5SAndroid Build Coastguard Worker open(tofile).readlines(), fromfile, tofile))) 140*8975f5c5SAndroid Build Coastguard Worker delete_tmp_dir = False 141*8975f5c5SAndroid Build Coastguard Worker sys.exit(1) 142*8975f5c5SAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 143*8975f5c5SAndroid Build Coastguard Worker print(e.output) 144*8975f5c5SAndroid Build Coastguard Worker sys.exit(e.returncode) 145*8975f5c5SAndroid Build Coastguard Worker finally: 146*8975f5c5SAndroid Build Coastguard Worker if os.path.exists(tmp_dir) and delete_tmp_dir: 147*8975f5c5SAndroid Build Coastguard Worker shutil.rmtree(tmp_dir) 148*8975f5c5SAndroid Build Coastguard Worker 149*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 150*8975f5c5SAndroid Build Coastguard Worker main() 151