mirror of https://github.com/grpc/grpc.git
The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)
https://grpc.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
6.9 KiB
199 lines
6.9 KiB
#! /usr/bin/env python3 |
|
# Copyright 2021 The gRPC Authors |
|
# |
|
# 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. |
|
"""Builds the content of xds-protos package""" |
|
|
|
import os |
|
import sys |
|
|
|
from grpc_tools import protoc |
|
|
|
if sys.version_info >= (3, 9, 0): |
|
from importlib import resources |
|
else: |
|
import pkg_resources |
|
|
|
|
|
def localize_path(p): |
|
return os.path.join(*p.split("/")) |
|
|
|
|
|
def _get_resource_file_name( |
|
package_or_requirement: str, resource_name: str |
|
) -> str: |
|
"""Obtain the filename for a resource on the file system.""" |
|
file_name = None |
|
if sys.version_info >= (3, 9, 0): |
|
file_name = ( |
|
resources.files(package_or_requirement) / resource_name |
|
).resolve() |
|
else: |
|
file_name = pkg_resources.resource_filename( |
|
package_or_requirement, resource_name |
|
) |
|
return str(file_name) |
|
|
|
|
|
# We might not want to compile all the protos |
|
EXCLUDE_PROTO_PACKAGES_LIST = tuple( |
|
localize_path(p) |
|
for p in ( |
|
# Requires extra dependency to Prometheus protos |
|
"envoy/service/metrics/v2", |
|
"envoy/service/metrics/v3", |
|
"envoy/service/metrics/v4alpha", |
|
) |
|
) |
|
|
|
# Compute the pathes |
|
WORK_DIR = os.path.dirname(os.path.abspath(__file__)) |
|
GRPC_ROOT = os.path.abspath(os.path.join(WORK_DIR, "..", "..", "..", "..")) |
|
ENVOY_API_PROTO_ROOT = os.path.join(GRPC_ROOT, "third_party", "envoy-api") |
|
XDS_PROTO_ROOT = os.path.join(GRPC_ROOT, "third_party", "xds") |
|
GOOGLEAPIS_ROOT = os.path.join(GRPC_ROOT, "third_party", "googleapis") |
|
VALIDATE_ROOT = os.path.join(GRPC_ROOT, "third_party", "protoc-gen-validate") |
|
OPENCENSUS_PROTO_ROOT = os.path.join( |
|
GRPC_ROOT, "third_party", "opencensus-proto", "src" |
|
) |
|
OPENTELEMETRY_PROTO_ROOT = os.path.join(GRPC_ROOT, "third_party", "opentelemetry") |
|
WELL_KNOWN_PROTOS_INCLUDE = _get_resource_file_name("grpc_tools", "_proto") |
|
|
|
OUTPUT_PATH = WORK_DIR |
|
|
|
# Prepare the test file generation |
|
TEST_FILE_NAME = "generated_file_import_test.py" |
|
TEST_IMPORTS = [] |
|
|
|
# The pkgutil-style namespace packaging __init__.py |
|
PKGUTIL_STYLE_INIT = ( |
|
"__path__ = __import__('pkgutil').extend_path(__path__, __name__)\n" |
|
) |
|
NAMESPACE_PACKAGES = ["google"] |
|
|
|
|
|
def add_test_import(proto_package_path: str, file_name: str, service: bool = False): |
|
TEST_IMPORTS.append( |
|
"from %s import %s\n" |
|
% ( |
|
proto_package_path.replace("/", ".").replace("-", "_"), |
|
file_name.replace(".proto", "_pb2").replace("-", "_"), |
|
) |
|
) |
|
if service: |
|
TEST_IMPORTS.append( |
|
"from %s import %s\n" |
|
% ( |
|
proto_package_path.replace("/", ".").replace("-", "_"), |
|
file_name.replace(".proto", "_pb2_grpc").replace("-", "_"), |
|
) |
|
) |
|
|
|
|
|
# Prepare Protoc command |
|
COMPILE_PROTO_ONLY = [ |
|
"grpc_tools.protoc", |
|
"--proto_path={}".format(ENVOY_API_PROTO_ROOT), |
|
"--proto_path={}".format(XDS_PROTO_ROOT), |
|
"--proto_path={}".format(GOOGLEAPIS_ROOT), |
|
"--proto_path={}".format(VALIDATE_ROOT), |
|
"--proto_path={}".format(WELL_KNOWN_PROTOS_INCLUDE), |
|
"--proto_path={}".format(OPENCENSUS_PROTO_ROOT), |
|
"--proto_path={}".format(OPENTELEMETRY_PROTO_ROOT), |
|
"--python_out={}".format(OUTPUT_PATH), |
|
] |
|
COMPILE_BOTH = COMPILE_PROTO_ONLY + ["--grpc_python_out={}".format(OUTPUT_PATH)] |
|
|
|
|
|
def has_grpc_service(proto_package_path: str) -> bool: |
|
return proto_package_path.startswith(os.path.join("envoy", "service")) |
|
|
|
|
|
def compile_protos(proto_root: str, sub_dir: str = ".") -> None: |
|
compiled_any = False |
|
for root, _, files in os.walk(os.path.join(proto_root, sub_dir)): |
|
proto_package_path = os.path.relpath(root, proto_root) |
|
if proto_package_path in EXCLUDE_PROTO_PACKAGES_LIST: |
|
print(f"Skipping package {proto_package_path}") |
|
continue |
|
for file_name in files: |
|
if file_name.endswith(".proto"): |
|
# Compile proto |
|
compiled_any = True |
|
if has_grpc_service(proto_package_path): |
|
return_code = protoc.main( |
|
COMPILE_BOTH + [os.path.join(root, file_name)] |
|
) |
|
add_test_import(proto_package_path, file_name, service=True) |
|
else: |
|
return_code = protoc.main( |
|
COMPILE_PROTO_ONLY + [os.path.join(root, file_name)] |
|
) |
|
add_test_import(proto_package_path, file_name, service=False) |
|
if return_code != 0: |
|
raise Exception("error: {} failed".format(COMPILE_BOTH)) |
|
# Ensure a deterministic order. |
|
TEST_IMPORTS.sort() |
|
if not compiled_any: |
|
raise Exception( |
|
"No proto files found at {}. Did you update git submodules?".format( |
|
proto_root, sub_dir |
|
) |
|
) |
|
|
|
|
|
def create_init_file(path: str, package_path: str = "") -> None: |
|
with open(os.path.join(path, "__init__.py"), "w") as f: |
|
# Apply the pkgutil-style namespace packaging, which is compatible for 2 |
|
# and 3. Here is the full table of namespace compatibility: |
|
# https://github.com/pypa/sample-namespace-packages/blob/master/table.md |
|
if package_path in NAMESPACE_PACKAGES: |
|
f.write(PKGUTIL_STYLE_INIT) |
|
|
|
|
|
def main(): |
|
# Compile xDS protos |
|
compile_protos(ENVOY_API_PROTO_ROOT) |
|
compile_protos(XDS_PROTO_ROOT) |
|
# We don't want to compile the entire GCP surface API, just the essential ones |
|
compile_protos(GOOGLEAPIS_ROOT, os.path.join("google", "api")) |
|
compile_protos(GOOGLEAPIS_ROOT, os.path.join("google", "rpc")) |
|
compile_protos(GOOGLEAPIS_ROOT, os.path.join("google", "longrunning")) |
|
compile_protos(GOOGLEAPIS_ROOT, os.path.join("google", "logging")) |
|
compile_protos(GOOGLEAPIS_ROOT, os.path.join("google", "type")) |
|
compile_protos(VALIDATE_ROOT, "validate") |
|
compile_protos(OPENCENSUS_PROTO_ROOT) |
|
compile_protos(OPENTELEMETRY_PROTO_ROOT) |
|
|
|
# Generate __init__.py files for all modules |
|
create_init_file(WORK_DIR) |
|
for proto_root_module in [ |
|
"envoy", |
|
"google", |
|
"opencensus", |
|
"udpa", |
|
"validate", |
|
"xds", |
|
"opentelemetry", |
|
]: |
|
for root, _, _ in os.walk(os.path.join(WORK_DIR, proto_root_module)): |
|
package_path = os.path.relpath(root, WORK_DIR) |
|
create_init_file(root, package_path) |
|
|
|
# Generate test file |
|
with open(os.path.join(WORK_DIR, TEST_FILE_NAME), "w") as f: |
|
f.writelines(TEST_IMPORTS) |
|
|
|
|
|
if __name__ == "__main__": |
|
main()
|
|
|