xref: /aosp_15_r20/external/grpc-grpc/src/python/grpcio_observability/make_grpcio_observability.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1#!/usr/bin/env python3
2
3# Copyright 2023 gRPC authors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import errno
18import os
19import os.path
20import pprint
21import shutil
22import subprocess
23import sys
24import traceback
25
26# the template for the content of observability_lib_deps.py
27DEPS_FILE_CONTENT = """
28# Copyright 2023 gRPC authors.
29#
30# Licensed under the Apache License, Version 2.0 (the "License");
31# you may not use this file except in compliance with the License.
32# You may obtain a copy of the License at
33#
34#     http://www.apache.org/licenses/LICENSE-2.0
35#
36# Unless required by applicable law or agreed to in writing, software
37# distributed under the License is distributed on an "AS IS" BASIS,
38# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
39# See the License for the specific language governing permissions and
40# limitations under the License.
41
42# AUTO-GENERATED BY make_grpcio_observability.py!
43CC_FILES={cc_files}
44
45CC_INCLUDES={cc_includes}
46"""
47
48# maps bazel reference to actual path
49BAZEL_REFERENCE_LINK = [
50    ("@com_google_absl//", "third_party/abseil-cpp/"),
51    ("//src", "grpc_root/src"),
52]
53
54ABSL_INCLUDE = (os.path.join("third_party", "abseil-cpp"),)
55
56# will be added to include path when building grpcio_observability
57EXTENSION_INCLUDE_DIRECTORIES = ABSL_INCLUDE
58
59CC_INCLUDES = list(EXTENSION_INCLUDE_DIRECTORIES)
60
61# the target directory is relative to the grpcio_observability package root.
62GRPCIO_OBSERVABILITY_ROOT_PREFIX = "src/python/grpcio_observability/"
63
64# Pairs of (source, target) directories to copy
65# from the grpc repo root to the grpcio_observability build root.
66COPY_FILES_SOURCE_TARGET_PAIRS = [
67    ("include", "grpc_root/include"),
68    ("third_party/abseil-cpp/absl", "third_party/abseil-cpp/absl"),
69    ("src/core", "grpc_root/src/core"),
70]
71
72# grpc repo root
73GRPC_ROOT = os.path.abspath(
74    os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..")
75)
76
77
78# the file to generate
79GRPC_PYTHON_OBSERVABILITY_LIB_DEPS = os.path.join(
80    GRPC_ROOT,
81    "src",
82    "python",
83    "grpcio_observability",
84    "observability_lib_deps.py",
85)
86
87# the script to run for getting dependencies
88BAZEL_DEPS = os.path.join(
89    GRPC_ROOT, "tools", "distrib", "python", "bazel_deps.sh"
90)
91
92# the bazel target to scrape to get list of sources for the build
93BAZEL_DEPS_QUERIES = [
94    "//src/core:slice",
95]
96
97
98def _bazel_query(query):
99    """Runs 'bazel query' to collect source file info."""
100    print('Running "bazel query %s"' % query)
101    output = subprocess.check_output([BAZEL_DEPS, query])
102    return output.decode("ascii").splitlines()
103
104
105def _pretty_print_list(items):
106    """Pretty print python list"""
107    formatted = pprint.pformat(items, indent=4)
108    # add newline after opening bracket (and fix indent of the next line)
109    if formatted.startswith("["):
110        formatted = formatted[0] + "\n " + formatted[1:]
111    # add newline before closing bracket
112    if formatted.endswith("]"):
113        formatted = formatted[:-1] + "\n" + formatted[-1]
114    return formatted
115
116
117def _bazel_name_to_file_path(name):
118    """Transform bazel reference to source file name."""
119    for link in BAZEL_REFERENCE_LINK:
120        if name.startswith(link[0]):
121            filepath = link[1] + name[len(link[0]) :].replace(":", "/")
122            return filepath
123    return None
124
125
126def _generate_deps_file_content():
127    """Returns the data structure with dependencies of protoc as python code."""
128    cc_files_output = []
129    for query in BAZEL_DEPS_QUERIES:
130        cc_files_output += _bazel_query(query)
131
132    # Collect .cc files (that will be later included in the native extension build)
133    cc_files = set()
134    for name in cc_files_output:
135        if name.endswith(".cc"):
136            filepath = _bazel_name_to_file_path(name)
137            if filepath:
138                cc_files.add(filepath)
139
140    deps_file_content = DEPS_FILE_CONTENT.format(
141        cc_files=_pretty_print_list(sorted(list(cc_files))),
142        cc_includes=_pretty_print_list(CC_INCLUDES),
143    )
144    return deps_file_content
145
146
147def _copy_source_tree(source, target):
148    """Copies source directory to a given target directory."""
149    print("Copying contents of %s to %s" % (source, target))
150    for source_dir, _, files in os.walk(source):
151        target_dir = os.path.abspath(
152            os.path.join(target, os.path.relpath(source_dir, source))
153        )
154        try:
155            os.makedirs(target_dir)
156        except OSError as error:
157            if error.errno != errno.EEXIST:
158                raise
159        for relative_file in files:
160            source_file = os.path.abspath(
161                os.path.join(source_dir, relative_file)
162            )
163            target_file = os.path.abspath(
164                os.path.join(target_dir, relative_file)
165            )
166            shutil.copyfile(source_file, target_file)
167
168
169def main():
170    os.chdir(GRPC_ROOT)
171
172    # Step 1:
173    # In order to be able to build the grpcio_observability package, we need the source
174    # code for the plugins and its dependencies to be available under the build root of
175    # the grpcio_observability package.
176    # So we simply copy all the necessary files where the build will expect them to be.
177    for source, target in COPY_FILES_SOURCE_TARGET_PAIRS:
178        # convert the slashes in the relative path to platform-specific path dividers.
179        # All paths are relative to GRPC_ROOT
180        source_abs = os.path.join(GRPC_ROOT, os.path.join(*source.split("/")))
181        # for targets, add grpcio_observability root prefix
182        target = GRPCIO_OBSERVABILITY_ROOT_PREFIX + target
183        target_abs = os.path.join(GRPC_ROOT, os.path.join(*target.split("/")))
184        _copy_source_tree(source_abs, target_abs)
185    print(
186        "The necessary source files were copied under the grpcio_observability package root."
187    )
188
189    # Step 2:
190    # Extract build metadata from bazel build (by running "bazel query")
191    # and populate the observability_lib_deps.py file with python-readable data structure
192    # that will be used by grpcio_observability's setup.py.
193    try:
194        print('Invoking "bazel query" to gather the dependencies.')
195        observability_lib_deps_content = _generate_deps_file_content()
196    except Exception as error:
197        # We allow this script to succeed even if we couldn't get the dependencies,
198        # as then we can assume that even without a successful bazel run the
199        # dependencies currently in source control are 'good enough'.
200        sys.stderr.write("Got non-fatal error:\n")
201        traceback.print_exc(file=sys.stderr)
202        return
203    # If we successfully got the dependencies, truncate and rewrite the deps file.
204    with open(GRPC_PYTHON_OBSERVABILITY_LIB_DEPS, "w") as deps_file:
205        deps_file.write(observability_lib_deps_content)
206    print('File "%s" updated.' % GRPC_PYTHON_OBSERVABILITY_LIB_DEPS)
207    print("Done.")
208
209
210if __name__ == "__main__":
211    main()
212