Commit 3e8d38ec authored by rbpotter's avatar rbpotter Committed by Commit Bot

WebUI: Add a new preprocess_if_expr rule to preprocess only <if expr>s

preprocess_grit takes the same path as preprocess="true" for a grd file
entry. This includes inlining <include>s, which is no longer supported
(see styleguide at [1]). Add a new preprocess_if_expr rule, which is
copied from preprocess_grit, with the following modifications:
(1) Runs only the preprocessing of <if expr>s ("Conditional Elements")
    and not the rest of the preprocess="true" HTML inlining path
(2) Depends on fewer pieces of grit, by no longer using the full DoInline
    method and inlining/simplifying some logic for parsing defines.

This new rule shares the same inputs as preprocess_grit. In followup
CLs, users of preprocess_grit can be migrated by simply importing the
new rule and replacing all usages of preprocess_grit with
preprocess_if_expr. Once all usages have been migrated, preprocess_grit
will be deleted.

[1] https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/web/web.md#Preprocessing

Change-Id: Iccb00ea036b336a2ad16cb74e75fbfe0264c751b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2557262
Commit-Queue: Rebekah Potter <rbpotter@chromium.org>
Reviewed-by: default avatardpapad <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#831117}
parent dfd92f85
......@@ -16,7 +16,9 @@ def RunUnittests(input_api, output_api):
output_api, [
input_api.os_path.join('grit', 'test_suite_all.py'),
input_api.os_path.join(input_api.PresubmitLocalPath(),
'preprocess_grit_test.py')
'preprocess_grit_test.py'),
input_api.os_path.join(input_api.PresubmitLocalPath(),
'preprocess_if_expr_test.py')
],
run_on_python3=False) # See https://crbug.com/1145395.
......
......@@ -255,6 +255,52 @@ class InlinedData:
self.inlined_data = inlined_data
self.inlined_files = inlined_files
def CheckConditionalElements(grd_node, str):
def IsConditionSatisfied(src_match):
expr1 = src_match.group('expr1') or ''
expr2 = src_match.group('expr2') or ''
return grd_node is None or grd_node.EvaluateCondition(expr1 + expr2)
"""Helper function to conditionally inline inner elements"""
while True:
begin_if = _BEGIN_IF_BLOCK.search(str)
if begin_if is None:
if _END_IF_BLOCK.search(str) is not None:
raise Exception('Unmatched </if>')
return str
condition_satisfied = IsConditionSatisfied(begin_if)
leading = str[0:begin_if.start()]
content_start = begin_if.end()
# Find matching "if" block end.
count = 1
pos = begin_if.end()
while True:
end_if = _END_IF_BLOCK.search(str, pos)
if end_if is None:
raise Exception('Unmatched <if>')
next_if = _BEGIN_IF_BLOCK.search(str, pos)
if next_if is None or next_if.start() >= end_if.end():
count = count - 1
if count == 0:
break
pos = end_if.end()
else:
count = count + 1
pos = next_if.end()
content = str[content_start:end_if.start()]
trailing = str[end_if.end():]
if condition_satisfied:
str = leading + CheckConditionalElements(grd_node, content) + trailing
else:
str = leading + trailing
def DoInline(
input_filename, grd_node, allow_external_script=False,
preprocess_only=False, names_only=False, strip_whitespace=False,
......@@ -317,50 +363,6 @@ def DoInline(
filename = filename_expansion_function(filename)
return os.path.normpath(os.path.join(base_path, filename))
def IsConditionSatisfied(src_match):
expr1 = src_match.group('expr1') or ''
expr2 = src_match.group('expr2') or ''
return grd_node is None or grd_node.EvaluateCondition(expr1 + expr2)
def CheckConditionalElements(str):
"""Helper function to conditionally inline inner elements"""
while True:
begin_if = _BEGIN_IF_BLOCK.search(str)
if begin_if is None:
if _END_IF_BLOCK.search(str) is not None:
raise Exception('Unmatched </if>')
return str
condition_satisfied = IsConditionSatisfied(begin_if)
leading = str[0:begin_if.start()]
content_start = begin_if.end()
# Find matching "if" block end.
count = 1
pos = begin_if.end()
while True:
end_if = _END_IF_BLOCK.search(str, pos)
if end_if is None:
raise Exception('Unmatched <if>')
next_if = _BEGIN_IF_BLOCK.search(str, pos)
if next_if is None or next_if.start() >= end_if.end():
count = count - 1
if count == 0:
break
pos = end_if.end()
else:
count = count + 1
pos = next_if.end()
content = str[content_start:end_if.start()]
trailing = str[end_if.end():]
if condition_satisfied:
str = leading + CheckConditionalElements(content) + trailing
else:
str = leading + trailing
def InlineFileContents(src_match,
pattern,
inlined_files=inlined_files,
......@@ -483,7 +485,7 @@ def DoInline(
# this twice. The first pass is so that we don't even bother calling
# InlineScript, InlineCSSFile and InlineIncludeFiles on text we're eventually
# going to throw out anyway.
flat_text = CheckConditionalElements(flat_text)
flat_text = CheckConditionalElements(grd_node, flat_text)
flat_text = _INCLUDE_RE.sub(InlineIncludeFiles, flat_text)
......@@ -506,7 +508,7 @@ def DoInline(
# Check conditional elements, second pass. This catches conditionals in any
# of the text we just inlined.
flat_text = CheckConditionalElements(flat_text)
flat_text = CheckConditionalElements(grd_node, flat_text)
# Allow custom modifications before inlining images.
if rewrite_function:
......
# Copyright 2020 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.
import("//build/config/python.gni")
import("//tools/grit/grit_defines.gni")
template("preprocess_if_expr") {
# TODO(crbug.com/1112471): Get this to run cleanly under Python 3.
python2_action(target_name) {
script = "//tools/grit/preprocess_if_expr.py"
if (defined(invoker.deps)) {
deps = invoker.deps
}
inputs = []
outputs = []
foreach(in_file, invoker.in_files) {
inputs += [ invoker.in_folder + "/" + in_file ]
outputs += [ invoker.out_folder + "/" + in_file ]
}
args = [
"--in-folder",
rebase_path(invoker.in_folder, root_build_dir),
"--out-folder",
rebase_path(invoker.out_folder, root_build_dir),
"--in-files",
] + invoker.in_files + grit_defines
if (defined(invoker.defines)) {
foreach(define, invoker.defines) {
args += [
"-D",
define,
]
}
}
if (defined(invoker.out_manifest)) {
args += [
"--out-manifest",
rebase_path(invoker.out_manifest, root_build_dir),
]
outputs += [ invoker.out_manifest ]
}
}
}
# Copyright 2020 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.
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(PreprocessIfExprNode, self).__init__()
def PreprocessIfExpr(self, content):
return grit.format.html_inline.CheckConditionalElements(self, content)
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_arg in definesArg:
define, = define_arg
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 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', nargs="*", action='append')
parser.add_argument('-E', '--environment')
parser.add_argument('-t', '--target')
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:
content = ""
with io.open(os.path.join(in_folder, input_file),
encoding='utf-8',
mode='r') as f:
content = f.read()
preprocessed = node.PreprocessIfExpr(content)
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
with io.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)), 'wb')
json.dump(manifest_data, manifest_file)
return
if __name__ == '__main__':
main(sys.argv[1:])
# Copyright 2020 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.
import os
import shutil
import tempfile
import unittest
import preprocess_if_expr
_HERE_DIR = os.path.dirname(__file__)
class PreprocessIfExprTest(unittest.TestCase):
def setUp(self):
self._out_folder = None
def tearDown(self):
if self._out_folder:
shutil.rmtree(self._out_folder)
def _read_out_file(self, file_name):
assert self._out_folder
return open(os.path.join(self._out_folder, file_name), 'r').read()
def _run_test(self, defines, file_name):
assert not self._out_folder
self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
preprocess_if_expr.main([
'--in-folder',
os.path.join(_HERE_DIR, 'preprocess_tests'),
'--out-folder',
self._out_folder,
'--in-files',
file_name,
] + defines)
def testPreprocess(self):
self._run_test(['-D', 'foo', '-D', 'bar'], 'test_with_ifexpr.js')
actual = self._read_out_file('test_with_ifexpr.js')
self.assertIn('I should be included in HTML', actual)
self.assertIn('I should be included in JS', actual)
self.assertNotIn('I should be excluded from HTML', actual)
self.assertNotIn('I should be excluded from JS', actual)
if __name__ == '__main__':
unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment