# Copyright 2020 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import argparse import errno import io import json import os import sys # For Node, EvaluateExpression import grit.node.base # For CheckConditionalElements import grit.format.html_inline _CWD = os.getcwd() class PreprocessIfExprNode(grit.node.base.Node): def __init__(self): super().__init__() def PreprocessIfExpr(self, content, removal_comments_extension): return grit.format.html_inline.CheckConditionalElements( self, content, removal_comments_extension) def EvaluateCondition(self, expr): return grit.node.base.Node.EvaluateExpression(expr, self.defines, self.target_platform, {}) def SetDefines(self, defines): self.defines = defines def SetTargetPlatform(self, target_platform): self.target_platform = target_platform @staticmethod def Construct(defines, target_platform): node = PreprocessIfExprNode() node.SetDefines(defines) node.SetTargetPlatform(target_platform or sys.platform) return node def ParseDefinesArg(definesArg): defines = {} for define in definesArg: parts = [part.strip() for part in define.split('=', 1)] name = parts[0] val = True if len(parts) == 1 else parts[1] if (val == "1" or val == "true"): val = True elif (val == "0" or val == "false"): val = False defines[name] = val return defines def ExtensionForComments(input_file): """Get the file extension that determines the comment style. Returns the file extension that determines the format of the 'grit-removed-lines' comments. '.ts' or '.js' will produce '/*...*/'-style comments, '.html; will produce ''-style comments. """ split = os.path.splitext(input_file) extension = split[1] # .html.ts and .html.js files should still use HTML comments. if os.path.splitext(split[0])[1] == '.html': extension = '.html' return extension def main(argv): parser = argparse.ArgumentParser() parser.add_argument('--in-folder', required=True) parser.add_argument('--out-folder', required=True) parser.add_argument('--out-manifest') parser.add_argument('--in-files', required=True, nargs="*") parser.add_argument('-D', '--defines', action='append') parser.add_argument('-E', '--environment') parser.add_argument('-t', '--target') parser.add_argument('--enable_removal_comments', action='store_true') args = parser.parse_args(argv) in_folder = os.path.normpath(os.path.join(_CWD, args.in_folder)) out_folder = os.path.normpath(os.path.join(_CWD, args.out_folder)) defines = ParseDefinesArg(args.defines) node = PreprocessIfExprNode.Construct(defines, args.target) for input_file in args.in_files: in_path = os.path.join(in_folder, input_file) content = "" with open(in_path, encoding='utf-8') as f: content = f.read() removal_comments_extension = None # None means no removal comments if args.enable_removal_comments: removal_comments_extension = ExtensionForComments(input_file) try: preprocessed = node.PreprocessIfExpr(content, removal_comments_extension) except: raise Exception('Error processing %s' % in_path) out_path = os.path.join(out_folder, input_file) out_dir = os.path.dirname(out_path) assert out_dir.startswith(out_folder), \ 'Cannot preprocess files to locations not under %s.' % out_dir try: os.makedirs(out_dir) except OSError as e: # Ignore directory exists errors. This can happen if two build rules # for overlapping directories hit the makedirs line at the same time. if e.errno != errno.EEXIST: raise # Delete the target file before witing it, as it may be hardlinked to other # files, which can break the build. This is the case in particular if the # file was "copied" to different locations with GN (as GN's copy is actually # a hard link under the hood). See https://crbug.com/1332497 if os.path.exists(out_path): os.remove(out_path) # Detect and delete any stale TypeScript files present in the output folder, # corresponding to input .js files, since they can get picked up by # subsequent ts_library() invocations and cause transient build failures. # This can happen when a file is migrated from JS to TS and a bot is # switched from building a later CL to building an earlier CL. [pathname, extension] = os.path.splitext(out_path) if extension == '.js': to_check = pathname + '.ts' if os.path.exists(to_check): os.remove(to_check) with open(out_path, mode='wb') as f: f.write(preprocessed.encode('utf-8')) if args.out_manifest: manifest_data = {} manifest_data['base_dir'] = '%s' % args.out_folder manifest_data['files'] = args.in_files manifest_file = open( os.path.normpath(os.path.join(_CWD, args.out_manifest)), 'w', encoding='utf-8', newline='\n') json.dump(manifest_data, manifest_file) return if __name__ == '__main__': main(sys.argv[1:])