perfetto_cmd: Teach perfetto_cmd to parse pbtxt configs
Adds the ability for perfetto_cmd to understand pbtxt configs
(this is controlled by the -t/--txt flag). This is somewhat complicated
by perfetto_cmd only having access to protobuf lite.
- Add a cut down version of descriptor.proto
- Use this to generate a (lite) descriptor.pb.h/descriptor.pb.cc
- Convert perfetto_config.proto into a FileSetDescriptor
(perfetto_config.descriptor)
- Encode perfetto_config.descriptor as a C++ constant named
kPerfettoConfigDescriptor (perfetto_config.descriptor.h)
- Use descriptor.pb.h to parse the kPerfettoConfigDescriptor
- Manually parse the incoming config based on the descriptor and
use protozero to write the proto encoding into a buffer which is then
parsed as normal.
Converting perfetto_config.proto to a descriptor happens at commit time
to avoid having to teach google3 and Android build systems how to
convert a proto descriptor into a .h file of the correct format.
This is not a stable API. Backwards and forwards compatible proto format changes
such as renaming a field or removing an optional field are breaking changes to
the proto text format (we can't distinguish between a typo'ed field and an
optional field) so this format is only really designed for local testing.
Several smaller clean ups:
- Move scattered_stream_memory_delegate.{h,cc} to be part of protozero
proper
- Remove the (now empty) src/protozero:test_support target
- Remove unused code in tools/gen_merged_protos
- Remove trailing whitespace in PRESUBMIT.py
Change-Id: Ic59b1338bd83421f68d4e12b4e01b9aaf5aa99be
diff --git a/tools/gen_binary_descriptors b/tools/gen_binary_descriptors
new file mode 100755
index 0000000..513ef95
--- /dev/null
+++ b/tools/gen_binary_descriptors
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+import os
+import re
+import sys
+import argparse
+import tempfile
+import subprocess
+import hashlib
+import textwrap
+
+SOURCE_TARGET = {
+ 'protos/perfetto/config/perfetto_config.proto':
+ 'src/perfetto_cmd/perfetto_config.descriptor.h',
+}
+
+ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+
+SCRIPT_PATH = 'tools/gen_binary_descriptors'
+
+def hash_path(path):
+ hash = hashlib.sha1()
+ with open(os.path.join(ROOT_DIR, path)) as f:
+ hash.update(f.read())
+ return hash.hexdigest()
+
+
+def check(source, target):
+ assert os.path.exists(os.path.join(ROOT_DIR, target))
+
+ with open(target, 'rb') as f:
+ s = f.read()
+
+ hashes = re.findall(r'// SHA1\((.*)\)\n// (.*)\n', s)
+ assert sorted([SCRIPT_PATH, source]) == sorted([key for key, _ in hashes])
+ for path, expected_sha1 in hashes:
+ actual_sha1 = hash_path(os.path.join(ROOT_DIR, path))
+ assert actual_sha1 == expected_sha1, \
+ 'Hash for path {} did not match'.format(path)
+
+
+def generate(source, target, protoc_path):
+ _, source_name = os.path.split(source)
+ _, target_name = os.path.split(target)
+ assert source_name.replace('.proto', '.descriptor.h') == target_name
+
+ with tempfile.NamedTemporaryFile() as fdescriptor:
+ subprocess.check_call([
+ protoc_path,
+ '--proto_path=protos',
+ '-o{}'.format(fdescriptor.name),
+ source,
+ ], cwd=ROOT_DIR)
+
+ s = fdescriptor.read()
+ constant_name = 'k' + target_name.title()[:-2].translate(None, '._')
+ binary = '{' + ', '.join('{0:#04x}'.format(ord(c)) for c in s) + '}'
+ binary = textwrap.fill(binary,
+ width=80,
+ initial_indent=' ',
+ subsequent_indent=' ')
+ include_guard = target.replace('/', '_').replace('.', '_').upper() + '_'
+
+ with open(os.path.join(ROOT_DIR, target), 'wb') as f:
+ f.write("""
+#ifndef {include_guard}
+#define {include_guard}
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <array>
+
+// This file was autogenerated by tools/gen_binary_descriptors. Do not edit.
+
+// SHA1({script_path})
+// {script_hash}
+// SHA1({source_path})
+// {source_hash}
+
+// This is the proto {{proto_name}} encoded as a ProtoFileDescriptor to allow
+// for reflection without libprotobuf full/non-lite protos.
+
+namespace perfetto {{
+
+constexpr std::array<uint8_t, {size}> {constant_name}{{
+{binary}}};
+
+}} // namespace perfetto
+
+#endif // {include_guard}
+""".format(**{
+ 'size': len(s),
+ 'constant_name': constant_name,
+ 'binary': binary,
+ 'include_guard': include_guard,
+ 'script_path': SCRIPT_PATH,
+ 'script_hash': hash_path(__file__),
+ 'source_path': source,
+ 'source_hash': hash_path(os.path.join(source)),
+ }))
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--check-only', action='store_true')
+ parser.add_argument('--protoc')
+ args = parser.parse_args()
+
+ for source, target in SOURCE_TARGET.iteritems():
+ if args.check_only:
+ check(source, target)
+ else:
+ protoc = args.protoc
+ assert os.path.exists(protoc)
+ generate(source, target, args.protoc)
+
+if __name__ == '__main__':
+ exit(main())