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