Skip to content

Commit

Permalink
Share strings between native and Java
Browse files Browse the repository at this point in the history
Creates a script that can be used to generate Java files to
expose native strings to Java, giving us a single source of
truth for these constants.

Bug: 937280
Change-Id: I1f0804bdfff8101dfd149612acd44360ae0dc2c0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1495747
Commit-Queue: Ian Vollick <vollick@chromium.org>
Auto-Submit: Ian Vollick <vollick@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Andrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#638754}
  • Loading branch information
Ian Vollick authored and Commit Bot committed Mar 7, 2019
1 parent a9528b2 commit b99472e
Show file tree
Hide file tree
Showing 18 changed files with 612 additions and 113 deletions.
1 change: 1 addition & 0 deletions PRESUBMIT.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@
'build/android/gyp/generate_linker_version_script.pydeps',
'build/android/gyp/ijar.pydeps',
'build/android/gyp/java_cpp_enum.pydeps',
'build/android/gyp/java_cpp_strings.pydeps',
'build/android/gyp/javac.pydeps',
'build/android/gyp/jinja_template.pydeps',
'build/android/gyp/lint.pydeps',
Expand Down
46 changes: 13 additions & 33 deletions build/android/gyp/java_cpp_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import zipfile

from util import build_utils
from util import java_cpp_utils

# List of C++ types that are compatible with the Java code generated by this
# script.
Expand Down Expand Up @@ -115,8 +116,8 @@ def StripEntries(entries):
self.comments = StripEntries(self.comments)

def _NormalizeNames(self):
self.entries = _TransformKeys(self.entries, _KCamelToShouty)
self.comments = _TransformKeys(self.comments, _KCamelToShouty)
self.entries = _TransformKeys(self.entries, java_cpp_utils.KCamelToShouty)
self.comments = _TransformKeys(self.comments, java_cpp_utils.KCamelToShouty)


def _TransformKeys(d, func):
Expand All @@ -133,25 +134,6 @@ def _TransformKeys(d, func):
return ret


def _KCamelToShouty(s):
"""Convert |s| from kCamelCase or CamelCase to SHOUTY_CASE.
kFooBar -> FOO_BAR
FooBar -> FOO_BAR
FooBAR9 -> FOO_BAR9
FooBARBaz -> FOO_BAR_BAZ
"""
if not re.match(r'^k?([A-Z][^A-Z]+|[A-Z0-9]+)+$', s):
return s
# Strip the leading k.
s = re.sub(r'^k', '', s)
# Add _ between title words and anything else.
s = re.sub(r'([^_])([A-Z][^A-Z_0-9]+)', r'\1_\2', s)
# Add _ between lower -> upper transitions.
s = re.sub(r'([^A-Z_0-9])([A-Z])', r'\1_\2', s)
return s.upper()


class DirectiveSet(object):
class_name_override_key = 'CLASS_NAME_OVERRIDE'
enum_package_key = 'ENUM_PACKAGE'
Expand Down Expand Up @@ -334,8 +316,6 @@ def _ParseRegularLine(self, line):
if single_line_enum:
self._ParseSingleLineEnum(single_line_enum.group('enum_entries'))

def GetScriptName():
return os.path.basename(os.path.abspath(sys.argv[0]))

def DoGenerate(source_paths):
for source_path in source_paths:
Expand Down Expand Up @@ -395,15 +375,15 @@ def GenerateOutput(source_path, enum_definition):
}
enum_comments = enum_definition.comments.get(enum_name)
if enum_comments:
enum_comments_indent = ' * '
comments_line_wrapper = textwrap.TextWrapper(
initial_indent=enum_comments_indent,
subsequent_indent=enum_comments_indent,
width=100)
enum_entries_string.append(' /**')
enum_entries_string.append(
'\n'.join(comments_line_wrapper.wrap(enum_comments)))
enum_entries_string.append(' */')
enum_comments_indent = ' * '
comments_line_wrapper = textwrap.TextWrapper(
initial_indent=enum_comments_indent,
subsequent_indent=enum_comments_indent,
width=100)
enum_entries_string.append(' /**')
enum_entries_string.append('\n'.join(
comments_line_wrapper.wrap(enum_comments)))
enum_entries_string.append(' */')
enum_entries_string.append(enum_template.substitute(values))
enum_names.append(enum_definition.class_name + '.' + enum_name)
enum_entries_string = '\n'.join(enum_entries_string)
Expand All @@ -419,7 +399,7 @@ def GenerateOutput(source_path, enum_definition):
'ENUM_ENTRIES': enum_entries_string,
'PACKAGE': enum_definition.enum_package,
'INT_DEF': enum_names_string,
'SCRIPT_NAME': GetScriptName(),
'SCRIPT_NAME': java_cpp_utils.GetScriptName(),
'SOURCE_PATH': source_path,
'YEAR': str(date.today().year)
}
Expand Down
1 change: 1 addition & 0 deletions build/android/gyp/java_cpp_enum.pydeps
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
java_cpp_enum.py
util/__init__.py
util/build_utils.py
util/java_cpp_utils.py
util/md5_check.py
9 changes: 5 additions & 4 deletions build/android/gyp/java_cpp_enum_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@

"""Tests for enum_preprocess.py.
This test suite containss various tests for the C++ -> Java enum generator.
This test suite contains various tests for the C++ -> Java enum generator.
"""

import collections
from datetime import date
import unittest

import java_cpp_enum
from java_cpp_enum import EnumDefinition, GenerateOutput, GetScriptName
from java_cpp_enum import EnumDefinition, GenerateOutput
from java_cpp_enum import HeaderParser
from util import java_cpp_utils


class TestPreprocess(unittest.TestCase):
Expand Down Expand Up @@ -65,8 +66,8 @@ def testOutput(self):
long_comment = ('This is a multiple line comment that is really long. '
'This is a multiple line comment that is')
self.assertEqual(
expected % (date.today().year, GetScriptName(), long_comment),
output)
expected % (date.today().year, java_cpp_utils.GetScriptName(),
long_comment), output)

def testParseSimpleEnum(self):
test_data = """
Expand Down
212 changes: 212 additions & 0 deletions build/android/gyp/java_cpp_strings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#!/user/bin/env python
#
# Copyright 2019 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 os
import re
import sys
import zipfile

from util import build_utils
from util import java_cpp_utils


def _ToUpper(match):
return match.group(1).upper()


def _GetClassName(source_path):
name = os.path.basename(os.path.abspath(source_path))
(name, _) = os.path.splitext(name)
name = re.sub(r'_([a-z])', _ToUpper, name)
name = re.sub(r'^(.)', _ToUpper, name)
return name


class _String(object):

def __init__(self, name, value, comments):
self.name = java_cpp_utils.KCamelToShouty(name)
self.value = value
self.comments = '\n'.join(' ' + x for x in comments)

def Format(self):
return '%s\n public static final String %s = %s;' % (
self.comments, self.name, self.value)


def ParseTemplateFile(lines):
package_re = re.compile(r'^package (.*);')
class_re = re.compile(r'.*class (.*) {')
package = ''
class_name = ''
for line in lines:
package_line = package_re.match(line)
if package_line:
package = package_line.groups()[0]
class_line = class_re.match(line)
if class_line:
class_name = class_line.groups()[0]
break
return package, class_name


# TODO(crbug.com/937282): It should be possible to parse a file for more than
# string constants. However, this currently only handles extracting string
# constants from a file (and all string constants from that file). Work will
# be needed if we want to annotate specific constants or non string constants
# in the file to be parsed.
class StringFileParser(object):
SINGLE_LINE_COMMENT_RE = re.compile(r'\s*(// [^\n]*)')
STRING_RE = re.compile(r'\s*const char k(.*)\[\]\s*=\s*(?:(".*"))?')
VALUE_RE = re.compile(r'\s*("[^"]*")')

def __init__(self, lines, path=''):
self._lines = lines
self._path = path
self._in_string = False
self._in_comment = False
self._package = ''
self._current_comments = []
self._current_name = ''
self._current_value = ''
self._strings = []

def _Reset(self):
self._current_comments = []
self._current_name = ''
self._current_value = ''
self._in_string = False
self._in_comment = False

def _AppendString(self):
self._strings.append(
_String(self._current_name, self._current_value,
self._current_comments))
self._Reset()

def _ParseValue(self, line):
value_line = StringFileParser.VALUE_RE.match(line)
if value_line:
self._current_value = value_line.groups()[0]
self._AppendString()
else:
self._Reset()

def _ParseComment(self, line):
comment_line = StringFileParser.SINGLE_LINE_COMMENT_RE.match(line)
if comment_line:
self._current_comments.append(comment_line.groups()[0])
self._in_comment = True
self._in_string = True
return True
else:
self._in_comment = False
return False

def _ParseString(self, line):
string_line = StringFileParser.STRING_RE.match(line)
if string_line:
self._current_name = string_line.groups()[0]
if string_line.groups()[1]:
self._current_value = string_line.groups()[1]
self._AppendString()
return True
else:
self._in_string = False
return False

def _ParseLine(self, line):
if not self._in_string:
self._ParseComment(line)
return

if self._in_comment:
if self._ParseComment(line):
return
if not self._ParseString(line):
self._Reset()
return

if self._in_string:
self._ParseValue(line)

def Parse(self):
for line in self._lines:
self._ParseLine(line)
return self._strings


def _GenerateOutput(template, source_path, template_path, strings):
description_template = """
// This following string constants were inserted by
// {SCRIPT_NAME}
// From
// {SOURCE_PATH}
// Into
// {TEMPLATE_PATH}
"""
values = {
'SCRIPT_NAME': java_cpp_utils.GetScriptName(),
'SOURCE_PATH': source_path,
'TEMPLATE_PATH': template_path,
}
description = description_template.format(**values)
native_strings = '\n\n'.join(x.Format() for x in strings)

values = {
'NATIVE_STRINGS': description + native_strings,
}
return template.format(**values)


def _ParseStringFile(path):
with open(path) as f:
return StringFileParser(f.readlines(), path).Parse()


def _Generate(source_paths, template_path):
with open(template_path) as f:
lines = f.readlines()
template = ''.join(lines)
for source_path in source_paths:
strings = _ParseStringFile(source_path)
package, class_name = ParseTemplateFile(lines)
package_path = package.replace('.', os.path.sep)
file_name = class_name + '.java'
output_path = os.path.join(package_path, file_name)
output = _GenerateOutput(template, source_path, template_path, strings)
yield output, output_path


def _Main(argv):
parser = argparse.ArgumentParser()

parser.add_argument(
'--srcjar',
required=True,
help='When specified, a .srcjar at the given path is '
'created instead of individual .java files.')

parser.add_argument(
'--template',
required=True,
help='Can be used to provide a context into which the'
'new string constants will be inserted.')

parser.add_argument(
'inputs', nargs='+', help='Input file(s)', metavar='INPUTFILE')
args = parser.parse_args(argv)

with build_utils.AtomicOutput(args.srcjar) as f:
with zipfile.ZipFile(f, 'w', zipfile.ZIP_STORED) as srcjar:
for data, path in _Generate(args.inputs, args.template):
build_utils.AddToZipHermetic(srcjar, path, data=data)


if __name__ == '__main__':
_Main(sys.argv[1:])
8 changes: 8 additions & 0 deletions build/android/gyp/java_cpp_strings.pydeps
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Generated by running:
# build/print_python_deps.py --root build/android/gyp --output build/android/gyp/java_cpp_strings.pydeps build/android/gyp/java_cpp_strings.py
../../gn_helpers.py
java_cpp_strings.py
util/__init__.py
util/build_utils.py
util/java_cpp_utils.py
util/md5_check.py
Loading

0 comments on commit b99472e

Please sign in to comment.