123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- # Copyright 2015 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- # Runs the Microsoft Message Compiler (mc.exe).
- #
- # Usage: message_compiler.py <environment_file> [<args to mc.exe>*]
- from __future__ import print_function
- import difflib
- import distutils.dir_util
- import filecmp
- import os
- import re
- import shutil
- import subprocess
- import sys
- import tempfile
- def main():
- env_file, rest = sys.argv[1], sys.argv[2:]
- # Parse some argument flags.
- header_dir = None
- resource_dir = None
- input_file = None
- for i, arg in enumerate(rest):
- if arg == '-h' and len(rest) > i + 1:
- assert header_dir == None
- header_dir = rest[i + 1]
- elif arg == '-r' and len(rest) > i + 1:
- assert resource_dir == None
- resource_dir = rest[i + 1]
- elif arg.endswith('.mc') or arg.endswith('.man'):
- assert input_file == None
- input_file = arg
- # Copy checked-in outputs to final location.
- THIS_DIR = os.path.abspath(os.path.dirname(__file__))
- assert header_dir == resource_dir
- source = os.path.join(THIS_DIR, "..", "..",
- "third_party", "win_build_output",
- re.sub(r'^(?:[^/]+/)?gen/', 'mc/', header_dir))
- distutils.dir_util.copy_tree(source, header_dir, preserve_times=False)
- # On non-Windows, that's all we can do.
- if sys.platform != 'win32':
- return
- # On Windows, run mc.exe on the input and check that its outputs are
- # identical to the checked-in outputs.
- # 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(env_file).read()[:-2].split('\0')
- env_dict = dict([item.split('=', 1) for item in env_pairs])
- extension = os.path.splitext(input_file)[1]
- if extension in ['.man', '.mc']:
- # For .man files, mc's output changed significantly from Version 10.0.15063
- # to Version 10.0.16299. We should always have the output of the current
- # default SDK checked in and compare to that. Early out if a different SDK
- # is active. This also happens with .mc files.
- # TODO(thakis): Check in new baselines and compare to 16299 instead once
- # we use the 2017 Fall Creator's Update by default.
- mc_help = subprocess.check_output(['mc.exe', '/?'], env=env_dict,
- stderr=subprocess.STDOUT, shell=True)
- version = re.search(br'Message Compiler\s+Version (\S+)', mc_help).group(1)
- if version != '10.0.15063':
- return
- # mc writes to stderr, so this explicitly redirects to stdout and eats it.
- try:
- tmp_dir = tempfile.mkdtemp()
- delete_tmp_dir = True
- if header_dir:
- rest[rest.index('-h') + 1] = tmp_dir
- header_dir = tmp_dir
- if resource_dir:
- rest[rest.index('-r') + 1] = tmp_dir
- resource_dir = tmp_dir
- # This needs shell=True to search the path in env_dict for the mc
- # executable.
- subprocess.check_output(['mc.exe'] + rest,
- env=env_dict,
- stderr=subprocess.STDOUT,
- shell=True)
- # We require all source code (in particular, the header generated here) to
- # be UTF-8. jinja can output the intermediate .mc file in UTF-8 or UTF-16LE.
- # However, mc.exe only supports Unicode via the -u flag, and it assumes when
- # that is specified that the input is UTF-16LE (and errors out on UTF-8
- # files, assuming they're ANSI). Even with -u specified and UTF16-LE input,
- # it generates an ANSI header, and includes broken versions of the message
- # text in the comment before the value. To work around this, for any invalid
- # // comment lines, we simply drop the line in the header after building it.
- # Also, mc.exe apparently doesn't always write #define lines in
- # deterministic order, so manually sort each block of #defines.
- if header_dir:
- header_file = os.path.join(
- header_dir, os.path.splitext(os.path.basename(input_file))[0] + '.h')
- header_contents = []
- with open(header_file, 'rb') as f:
- define_block = [] # The current contiguous block of #defines.
- for line in f.readlines():
- if line.startswith('//') and '?' in line:
- continue
- if line.startswith('#define '):
- define_block.append(line)
- continue
- # On the first non-#define line, emit the sorted preceding #define
- # block.
- header_contents += sorted(define_block, key=lambda s: s.split()[-1])
- define_block = []
- header_contents.append(line)
- # If the .h file ends with a #define block, flush the final block.
- header_contents += sorted(define_block, key=lambda s: s.split()[-1])
- with open(header_file, 'wb') as f:
- f.write(''.join(header_contents))
- # mc.exe invocation and post-processing are complete, now compare the output
- # in tmp_dir to the checked-in outputs.
- diff = filecmp.dircmp(tmp_dir, source)
- if diff.diff_files or set(diff.left_list) != set(diff.right_list):
- print('mc.exe output different from files in %s, see %s' % (source,
- tmp_dir))
- diff.report()
- for f in diff.diff_files:
- if f.endswith('.bin'): continue
- fromfile = os.path.join(source, f)
- tofile = os.path.join(tmp_dir, f)
- print(''.join(
- difflib.unified_diff(
- open(fromfile, 'U').readlines(),
- open(tofile, 'U').readlines(), fromfile, tofile)))
- delete_tmp_dir = False
- sys.exit(1)
- except subprocess.CalledProcessError as e:
- print(e.output)
- sys.exit(e.returncode)
- finally:
- if os.path.exists(tmp_dir) and delete_tmp_dir:
- shutil.rmtree(tmp_dir)
- if __name__ == '__main__':
- main()
|