build.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. #! /usr/bin/env python3
  2. # Copyright 2021 The gRPC Authors
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Builds the content of xds-protos package"""
  16. import os
  17. from grpc_tools import protoc
  18. import pkg_resources
  19. # We might not want to compile all the protos
  20. EXCLUDE_PROTO_PACKAGES_LIST = [
  21. # Requires extra dependency to Prometheus protos
  22. 'envoy/service/metrics/v2',
  23. 'envoy/service/metrics/v3',
  24. 'envoy/service/metrics/v4alpha',
  25. ]
  26. # Compute the pathes
  27. WORK_DIR = os.path.dirname(os.path.abspath(__file__))
  28. GRPC_ROOT = os.path.abspath(os.path.join(WORK_DIR, '..', '..', '..', '..'))
  29. XDS_PROTO_ROOT = os.path.join(GRPC_ROOT, 'third_party', 'envoy-api')
  30. UDPA_PROTO_ROOT = os.path.join(GRPC_ROOT, 'third_party', 'udpa')
  31. GOOGLEAPIS_ROOT = os.path.join(GRPC_ROOT, 'third_party', 'googleapis')
  32. VALIDATE_ROOT = os.path.join(GRPC_ROOT, 'third_party', 'protoc-gen-validate')
  33. OPENCENSUS_PROTO_ROOT = os.path.join(GRPC_ROOT, 'third_party',
  34. 'opencensus-proto', 'src')
  35. OPENTELEMETRY_PROTO_ROOT = os.path.join(GRPC_ROOT, 'third_party',
  36. 'opentelemetry')
  37. WELL_KNOWN_PROTOS_INCLUDE = pkg_resources.resource_filename(
  38. 'grpc_tools', '_proto')
  39. OUTPUT_PATH = WORK_DIR
  40. # Prepare the test file generation
  41. TEST_FILE_NAME = 'generated_file_import_test.py'
  42. TEST_IMPORTS = []
  43. # The pkgutil-style namespace packaging __init__.py
  44. PKGUTIL_STYLE_INIT = "__path__ = __import__('pkgutil').extend_path(__path__, __name__)\n"
  45. NAMESPACE_PACKAGES = ["google"]
  46. def add_test_import(proto_package_path: str,
  47. file_name: str,
  48. service: bool = False):
  49. TEST_IMPORTS.append("from %s import %s\n" % (proto_package_path.replace(
  50. '/', '.'), file_name.replace('.proto', '_pb2')))
  51. if service:
  52. TEST_IMPORTS.append("from %s import %s\n" % (proto_package_path.replace(
  53. '/', '.'), file_name.replace('.proto', '_pb2_grpc')))
  54. # Prepare Protoc command
  55. COMPILE_PROTO_ONLY = [
  56. 'grpc_tools.protoc',
  57. '--proto_path={}'.format(XDS_PROTO_ROOT),
  58. '--proto_path={}'.format(UDPA_PROTO_ROOT),
  59. '--proto_path={}'.format(GOOGLEAPIS_ROOT),
  60. '--proto_path={}'.format(VALIDATE_ROOT),
  61. '--proto_path={}'.format(WELL_KNOWN_PROTOS_INCLUDE),
  62. '--proto_path={}'.format(OPENCENSUS_PROTO_ROOT),
  63. '--proto_path={}'.format(OPENTELEMETRY_PROTO_ROOT),
  64. '--python_out={}'.format(OUTPUT_PATH),
  65. ]
  66. COMPILE_BOTH = COMPILE_PROTO_ONLY + ['--grpc_python_out={}'.format(OUTPUT_PATH)]
  67. def has_grpc_service(proto_package_path: str) -> bool:
  68. return proto_package_path.startswith('envoy/service')
  69. def compile_protos(proto_root: str, sub_dir: str = '.') -> None:
  70. for root, _, files in os.walk(os.path.join(proto_root, sub_dir)):
  71. proto_package_path = os.path.relpath(root, proto_root)
  72. if proto_package_path in EXCLUDE_PROTO_PACKAGES_LIST:
  73. print(f'Skipping package {proto_package_path}')
  74. continue
  75. for file_name in files:
  76. if file_name.endswith('.proto'):
  77. # Compile proto
  78. if has_grpc_service(proto_package_path):
  79. return_code = protoc.main(COMPILE_BOTH +
  80. [os.path.join(root, file_name)])
  81. add_test_import(proto_package_path, file_name, service=True)
  82. else:
  83. return_code = protoc.main(COMPILE_PROTO_ONLY +
  84. [os.path.join(root, file_name)])
  85. add_test_import(proto_package_path,
  86. file_name,
  87. service=False)
  88. if return_code != 0:
  89. raise Exception('error: {} failed'.format(COMPILE_BOTH))
  90. def create_init_file(path: str, package_path: str = "") -> None:
  91. with open(os.path.join(path, "__init__.py"), 'w') as f:
  92. # Apply the pkgutil-style namespace packaging, which is compatible for 2
  93. # and 3. Here is the full table of namespace compatibility:
  94. # https://github.com/pypa/sample-namespace-packages/blob/master/table.md
  95. if package_path in NAMESPACE_PACKAGES:
  96. f.write(PKGUTIL_STYLE_INIT)
  97. def main():
  98. # Compile xDS protos
  99. compile_protos(XDS_PROTO_ROOT)
  100. compile_protos(UDPA_PROTO_ROOT)
  101. # We don't want to compile the entire GCP surface API, just the essential ones
  102. compile_protos(GOOGLEAPIS_ROOT, os.path.join('google', 'api'))
  103. compile_protos(GOOGLEAPIS_ROOT, os.path.join('google', 'rpc'))
  104. compile_protos(GOOGLEAPIS_ROOT, os.path.join('google', 'longrunning'))
  105. compile_protos(GOOGLEAPIS_ROOT, os.path.join('google', 'logging'))
  106. compile_protos(GOOGLEAPIS_ROOT, os.path.join('google', 'type'))
  107. compile_protos(VALIDATE_ROOT, 'validate')
  108. compile_protos(OPENCENSUS_PROTO_ROOT)
  109. compile_protos(OPENTELEMETRY_PROTO_ROOT)
  110. # Generate __init__.py files for all modules
  111. create_init_file(WORK_DIR)
  112. for proto_root_module in [
  113. 'envoy', 'google', 'opencensus', 'udpa', 'validate', 'xds',
  114. 'opentelemetry'
  115. ]:
  116. for root, _, _ in os.walk(os.path.join(WORK_DIR, proto_root_module)):
  117. package_path = os.path.relpath(root, WORK_DIR)
  118. create_init_file(root, package_path)
  119. # Generate test file
  120. with open(os.path.join(WORK_DIR, TEST_FILE_NAME), 'w') as f:
  121. f.writelines(TEST_IMPORTS)
  122. if __name__ == "__main__":
  123. main()