preprocess_if_expr.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # Copyright 2020 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. import argparse
  5. import errno
  6. import io
  7. import json
  8. import os
  9. import sys
  10. # For Node, EvaluateExpression
  11. import grit.node.base
  12. # For CheckConditionalElements
  13. import grit.format.html_inline
  14. _CWD = os.getcwd()
  15. class PreprocessIfExprNode(grit.node.base.Node):
  16. def __init__(self):
  17. super(PreprocessIfExprNode, self).__init__()
  18. def PreprocessIfExpr(self, content, removal_comments_extension):
  19. return grit.format.html_inline.CheckConditionalElements(
  20. self, content, removal_comments_extension)
  21. def EvaluateCondition(self, expr):
  22. return grit.node.base.Node.EvaluateExpression(expr, self.defines,
  23. self.target_platform, {})
  24. def SetDefines(self, defines):
  25. self.defines = defines
  26. def SetTargetPlatform(self, target_platform):
  27. self.target_platform = target_platform
  28. @staticmethod
  29. def Construct(defines, target_platform):
  30. node = PreprocessIfExprNode()
  31. node.SetDefines(defines)
  32. node.SetTargetPlatform(target_platform or sys.platform)
  33. return node
  34. def ParseDefinesArg(definesArg):
  35. defines = {}
  36. for define in definesArg:
  37. parts = [part.strip() for part in define.split('=', 1)]
  38. name = parts[0]
  39. val = True if len(parts) == 1 else parts[1]
  40. if (val == "1" or val == "true"):
  41. val = True
  42. elif (val == "0" or val == "false"):
  43. val = False
  44. defines[name] = val
  45. return defines
  46. def ExtensionForComments(input_file):
  47. """Get the file extension that determines the comment style.
  48. Returns the file extension that determines the format of the
  49. 'grit-removed-lines' comments. '.ts' or '.js' will produce '/*...*/'-style
  50. comments, '.html; will produce '<!-- -->'-style comments.
  51. """
  52. split = os.path.splitext(input_file)
  53. extension = split[1]
  54. # .html.ts and .html.js files should still use HTML comments.
  55. if os.path.splitext(split[0])[1] == '.html':
  56. extension = '.html'
  57. return extension
  58. def main(argv):
  59. parser = argparse.ArgumentParser()
  60. parser.add_argument('--in-folder', required=True)
  61. parser.add_argument('--out-folder', required=True)
  62. parser.add_argument('--out-manifest')
  63. parser.add_argument('--in-files', required=True, nargs="*")
  64. parser.add_argument('-D', '--defines', action='append')
  65. parser.add_argument('-E', '--environment')
  66. parser.add_argument('-t', '--target')
  67. parser.add_argument('--enable_removal_comments', action='store_true')
  68. args = parser.parse_args(argv)
  69. in_folder = os.path.normpath(os.path.join(_CWD, args.in_folder))
  70. out_folder = os.path.normpath(os.path.join(_CWD, args.out_folder))
  71. defines = ParseDefinesArg(args.defines)
  72. node = PreprocessIfExprNode.Construct(defines, args.target)
  73. for input_file in args.in_files:
  74. in_path = os.path.join(in_folder, input_file)
  75. content = ""
  76. with io.open(in_path, encoding='utf-8', mode='r') as f:
  77. content = f.read()
  78. removal_comments_extension = None # None means no removal comments
  79. if args.enable_removal_comments:
  80. removal_comments_extension = ExtensionForComments(input_file)
  81. try:
  82. preprocessed = node.PreprocessIfExpr(content, removal_comments_extension)
  83. except:
  84. raise Exception('Error processing %s' % in_path)
  85. out_path = os.path.join(out_folder, input_file)
  86. out_dir = os.path.dirname(out_path)
  87. assert out_dir.startswith(out_folder), \
  88. 'Cannot preprocess files to locations not under %s.' % out_dir
  89. try:
  90. os.makedirs(out_dir)
  91. except OSError as e:
  92. # Ignore directory exists errors. This can happen if two build rules
  93. # for overlapping directories hit the makedirs line at the same time.
  94. if e.errno != errno.EEXIST:
  95. raise
  96. # Delete the target file before witing it, as it may be hardlinked to other
  97. # files, which can break the build. This is the case in particular if the
  98. # file was "copied" to different locations with GN (as GN's copy is actually
  99. # a hard link under the hood). See https://crbug.com/1332497
  100. if os.path.exists(out_path):
  101. os.remove(out_path)
  102. # Detect and delete any stale TypeScript files present in the output folder,
  103. # corresponding to input .js files, since they can get picked up by
  104. # subsequent ts_library() invocations and cause transient build failures.
  105. # This can happen when a file is migrated from JS to TS and a bot is
  106. # switched from building a later CL to building an earlier CL.
  107. [pathname, extension] = os.path.splitext(out_path)
  108. if extension == '.js':
  109. to_check = pathname + '.ts'
  110. if os.path.exists(to_check):
  111. os.remove(to_check)
  112. with io.open(out_path, mode='wb') as f:
  113. f.write(preprocessed.encode('utf-8'))
  114. if args.out_manifest:
  115. manifest_data = {}
  116. manifest_data['base_dir'] = '%s' % args.out_folder
  117. manifest_data['files'] = args.in_files
  118. manifest_file = io.open(
  119. os.path.normpath(os.path.join(_CWD, args.out_manifest)), 'w',
  120. encoding='utf-8', newline='\n')
  121. json.dump(manifest_data, manifest_file)
  122. return
  123. if __name__ == '__main__':
  124. main(sys.argv[1:])