使用协议缓冲区的 Python 项目,部署问题



我有一个使用setup工具进行部署的Python项目,我主要遵循有关项目结构的指南。该项目使用 Google Protocol Buffers 来定义网络消息格式。我的主要问题是如何在安装过程中 setup.py 调用 protoc 编译器以将定义构建到 _pb2.py 文件中。

在这个问题中,建议只将生成的 _pb2.py 文件与项目一起分发。虽然这可能适用于非常相似的平台,但我发现有几种情况不起作用。例如,当我在使用 Anaconda Python 的 Mac 上进行开发并将生成的 _pb2.py 以及项目的其余部分复制到运行 Raspbian 的 Raspberry Pi 时,总是会出现来自 _pb2.py 模块的导入错误。但是,如果我在 Pi 上重新编译 .proto 文件,则项目将按预期工作。因此,分发已编译的文件似乎不是一种选择。

有点在这里寻找工作和最佳实践解决方案。可以假设 protoc 编译器安装在目标平台上。

编辑:

因为人们问失败的原因。在Mac上,protobuf版本是2.6.1。在 Pi 上它是 2.4.1。显然,生成的 protoc 编译器输出使用的内部 API 已更改。输出基本上是:

  File "[...]network_manager.py", line 8, in <module>
      import InstrumentControl.transports.serial_bridge_protocol_pb2 as protocol
  File "[...]serial_bridge_protocol_pb2.py", line 9, in <module>
      from google.protobuf import symbol_database as _symbol_database
  ImportError: cannot import name symbol_database

好的,我解决了这个问题,而无需用户安装特定的旧版本或在开发机器以外的其他平台上编译原型文件。它的灵感来自protobuf本身的这个 setup.py 脚本。

首先,需要找到 protoc,这可以使用

# Find the Protocol Compiler.
if 'PROTOC' in os.environ and os.path.exists(os.environ['PROTOC']):
  protoc = os.environ['PROTOC']
else:
  protoc = find_executable("protoc")

此函数将编译一个 .proto 文件并将 _pb2.py 放在同一个位置。但是,可以任意更改行为。

def generate_proto(source):
  """Invokes the Protocol Compiler to generate a _pb2.py from the given
  .proto file.  Does nothing if the output already exists and is newer than
  the input."""
  output = source.replace(".proto", "_pb2.py")
  if (not os.path.exists(output) or
      (os.path.exists(source) and
       os.path.getmtime(source) > os.path.getmtime(output))):
    print "Generating %s..." % output
    if not os.path.exists(source):
      sys.stderr.write("Can't find required file: %sn" % source)
      sys.exit(-1)
    if protoc == None:
      sys.stderr.write(
          "Protocol buffers compiler 'protoc' not installed or not found.n"
          )
      sys.exit(-1)
    protoc_command = [ protoc, "-I.", "--python_out=.", source ]
    if subprocess.call(protoc_command) != 0:
      sys.exit(-1)

接下来,派生类 _build_py 和 _clean 以添加生成和清理协议缓冲区。

# List of all .proto files
proto_src = ['file1.proto', 'path/to/file2.proto']
class build_py(_build_py):
  def run(self):
    for f in proto_src:
        generate_proto(f)
    _build_py.run(self)
class clean(_clean):
  def run(self):
    # Delete generated files in the code tree.
    for (dirpath, dirnames, filenames) in os.walk("."):
      for filename in filenames:
        filepath = os.path.join(dirpath, filename)
        if filepath.endswith("_pb2.py"):
          os.remove(filepath)
    # _clean is an old-style class, so super() doesn't work.
    _clean.run(self)

最后,参数

cmdclass = { 'clean': clean, 'build_py': build_py }   

需要添加到对设置的调用中,并且一切正常。仍然需要检查可能的怪癖,但到目前为止,它在 Mac 和 Pi 上完美运行。

我刚刚启动了protobuf-setuptools包来使用这段代码中最理智的部分。它仍然需要改进,所以欢迎任何反馈!

看看:https://pypi.python.org/pypi/protobuf-setuptools

另一种解决方案是将 protobuf 库与您的应用程序捆绑在一起,而不是使用目标计算机上安装的版本。这样,您就知道没有版本与生成的代码不匹配。

最新更新