1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
#!/usr/bin/env python
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Converts a given ASCII proto into a binary resource.
"""
from __future__ import print_function
import abc
from importlib import util as imp_util
import optparse
import os
import re
import subprocess
import sys
import traceback
class GoogleProtobufModuleImporter:
"""A custom module importer for importing google.protobuf.
See PEP #302 (https://www.python.org/dev/peps/pep-0302/) for full information
on the Importer Protocol.
"""
def __init__(self, paths):
"""Creates a loader that searches |paths| for google.protobuf modules."""
self._paths = paths
def _fullname_to_filepath(self, fullname):
"""Converts a full module name to a corresponding path to a .py file.
e.g. google.protobuf.text_format -> pyproto/google/protobuf/text_format.py
"""
for path in self._paths:
filepath = os.path.join(path, fullname.replace('.', os.sep) + '.py')
if os.path.isfile(filepath):
return filepath
return None
def _module_exists(self, fullname):
return self._fullname_to_filepath(fullname) is not None
def find_module(self, fullname, path=None):
"""Returns a loader module for the google.protobuf module in pyproto."""
if (fullname.startswith('google.protobuf.')
and self._module_exists(fullname)):
# Per PEP #302, this will result in self.load_module getting used
# to load |fullname|.
return self
# Per PEP #302, if the module cannot be loaded, then return None.
return None
def load_module(self, fullname):
"""Loads the module specified by |fullname| and returns the module."""
if fullname in sys.modules:
# Per PEP #302, if |fullname| is in sys.modules, it must be returned.
return sys.modules[fullname]
if (not fullname.startswith('google.protobuf.') or
not self._module_exists(fullname)):
# Per PEP #302, raise ImportError if the requested module/package
# cannot be loaded. This should never get reached for this simple loader,
# but is included for completeness.
raise ImportError(fullname)
filepath = self._fullname_to_filepath(fullname)
spec = imp_util.spec_from_file_location(fullname, filepath)
loaded = imp_util.module_from_spec(spec)
spec.loader.exec_module(loaded)
return loaded
class BinaryProtoGenerator:
# If the script is run in a virtualenv
# (https://virtualenv.pypa.io/en/stable/), then no google.protobuf library
# should be brought in from site-packages. Passing -S into the interpreter in
# a virtualenv actually destroys the ability to import standard library
# functions like optparse, so this script should not be wrapped if we're in a
# virtualenv.
def _IsInVirtualEnv(self):
# This is the way used by pip and other software to detect virtualenv.
return hasattr(sys, 'real_prefix')
def _ImportProtoModules(self, paths):
"""Import the protobuf modules we need. |paths| is list of import paths"""
for path in paths:
# Put the path to our proto libraries in front, so that we don't use
# system protobuf.
sys.path.insert(1, path)
if self._IsInVirtualEnv():
# Add a custom module loader. When run in a virtualenv that has
# google.protobuf installed, the site-package was getting searched first
# despite that pyproto/ is at the start of the sys.path. The module
# loaders in the meta_path precede all other imports (including even
# builtins), which allows the proper google.protobuf from pyproto to be
# found.
sys.meta_path.append(GoogleProtobufModuleImporter(paths))
import google.protobuf.text_format as text_format
globals()['text_format'] = text_format
self.ImportProtoModule()
def _GenerateBinaryProtos(self, opts):
""" Read the ASCII proto and generate one or more binary protos. """
# Read the ASCII
with open(opts.infile, 'r') as ifile:
ascii_pb_str = ifile.read()
# Parse it into a structured PB
full_pb = self.EmptyProtoInstance()
text_format.Merge(ascii_pb_str, full_pb)
self.ValidatePb(opts, full_pb);
self.ProcessPb(opts, full_pb)
@abc.abstractmethod
def ImportProtoModule(self):
""" Import the proto module to be used by the generator. """
pass
@abc.abstractmethod
def EmptyProtoInstance(self):
""" Returns an empty proto instance to be filled by the generator."""
pass
@abc.abstractmethod
def ValidatePb(self, opts, pb):
""" Validate the basic values of the protobuf. The
file_type_policies_unittest.cc will also validate it by platform,
but this will catch errors earlier.
"""
pass
@abc.abstractmethod
def ProcessPb(self, opts, pb):
""" Process the parsed prototobuf. """
pass
def AddCommandLineOptions(self, parser):
""" Allows subclasses to add any options the command line parser. """
pass
def AddExtraCommandLineArgsForVirtualEnvRun(self, opts, command):
""" Allows subclasses to add any extra command line arguments when running
this under a virtualenv."""
pass
def VerifyArgs(self, opts):
""" Allows subclasses to check command line parameters before running. """
return True
def Run(self):
parser = optparse.OptionParser()
# TODO(crbug.com/41255210): Remove this once the bug is fixed.
parser.add_option('-w', '--wrap', action="store_true", default=False,
help='Wrap this script in another python '
'execution to disable site-packages. This is a '
'fix for http://crbug.com/605592')
parser.add_option('-i', '--infile',
help='The ASCII proto file to read.')
parser.add_option('-d', '--outdir',
help='Directory underwhich binary file(s) will be ' +
'written')
parser.add_option('-o', '--outbasename',
help='Basename of the binary file to write to.')
parser.add_option('-p', '--path', action="append",
help='Repeat this as needed. Directory(s) containing ' +
'the your_proto_definition_pb2.py and ' +
'google.protobuf.text_format modules')
self.AddCommandLineOptions(parser)
(opts, args) = parser.parse_args()
if opts.infile is None or opts.outdir is None or opts.outbasename is None:
parser.print_help()
return 1
if opts.wrap and not self._IsInVirtualEnv():
# Run this script again with different args to the interpreter to suppress
# the inclusion of libraries, like google.protobuf, from site-packages,
# which is checked before sys.path when resolving imports. We want to
# specifically import the libraries injected into the sys.path in
# ImportProtoModules().
command = [sys.executable, '-S', '-s', sys.argv[0]]
command += ['-i', opts.infile]
command += ['-d', opts.outdir]
command += ['-o', opts.outbasename]
for path in opts.path:
command += ['-p', path]
self.AddExtraCommandLineArgsForVirtualEnvRun(opts, command);
sys.exit(subprocess.call(command))
self._ImportProtoModules(opts.path)
if not self.VerifyArgs(opts):
print("Wrong arguments")
return 1
try:
self._GenerateBinaryProtos(opts)
except Exception as e:
print("ERROR: Failed to render binary version of %s:\n %s\n%s" %
(opts.infile, str(e), traceback.format_exc()))
return 1
|