1"""Copyright (C) 2022 The Android Open Source Project 2 3Licensed under the Apache License, Version 2.0 (the "License"); 4you may not use this file except in compliance with the License. 5You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9Unless required by applicable law or agreed to in writing, software 10distributed under the License is distributed on an "AS IS" BASIS, 11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12See the License for the specific language governing permissions and 13limitations under the License. 14""" 15 16load("@bazel_skylib//lib:paths.bzl", "paths") 17load("@bazel_skylib//lib:sets.bzl", "sets") 18load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") 19load(":cc_proto.bzl", "PROTO_GEN_NAME_SUFFIX", "cc_proto_library") 20 21PROTO_GEN = "external/protobuf/bin/aprotoc/aprotoc" 22VIRTUAL_IMPORT = "_virtual_imports" 23RUNFILES = "_middlemen/external_Sprotobuf_Sbin_Saprotoc_Saprotoc-runfiles" 24 25GEN_SUFFIX = [ 26 ".pb.h", 27 ".pb.cc", 28] 29 30def _get_search_paths(action): 31 cmd = action.argv 32 search_paths = sets.make() 33 cmd_len = len(cmd) 34 for i in range(cmd_len): 35 if cmd[i].startswith("-I"): 36 sets.insert(search_paths, cmd[i].lstrip("- I")) 37 38 return search_paths 39 40def _proto_code_gen_test_impl(ctx): 41 env = analysistest.begin(ctx) 42 target_under_test = analysistest.target_under_test(env) 43 actions = analysistest.target_actions(env) 44 package_root = ctx.label.package 45 local_file_output_path = paths.join( 46 package_root, 47 target_under_test.label.name, 48 package_root, 49 ) 50 51 input_files = [ 52 ctx.attr.local_file_path, 53 ctx.attr.external_file_path, 54 ctx.attr.deps_file_path, 55 ] 56 57 output_files = [ 58 ctx.attr.local_file_path, 59 ctx.attr.external_file_path, 60 ] 61 62 asserts.true( 63 env, 64 len(actions) == 1, 65 "Proto gen action not found: %s" % actions, 66 ) 67 68 action = actions[0] 69 70 asserts.set_equals( 71 env, 72 expected = sets.make( 73 [paths.join(package_root, file) for file in input_files] + [ 74 PROTO_GEN, 75 RUNFILES, 76 ], 77 ), 78 actual = sets.make([ 79 file.short_path 80 for file in action.inputs.to_list() 81 ]), 82 ) 83 84 asserts.set_equals( 85 env, 86 expected = sets.make( 87 [ 88 paths.join( 89 local_file_output_path, 90 paths.replace_extension(file, ext), 91 ) 92 for ext in GEN_SUFFIX 93 for file in output_files 94 ], 95 ), 96 actual = sets.make([ 97 file.short_path 98 for file in action.outputs.to_list() 99 ]), 100 ) 101 102 search_paths = _get_search_paths(action) 103 104 asserts.equals( 105 env, 106 expected = sets.make( 107 ["."] + 108 [paths.join(package_root, f) + "=" + paths.join(package_root, f) for f in input_files], 109 ), 110 actual = search_paths, 111 ) 112 113 return analysistest.end(env) 114 115proto_code_gen_test = analysistest.make( 116 _proto_code_gen_test_impl, 117 attrs = { 118 "local_file_path": attr.string(), 119 "deps_file_path": attr.string(), 120 "external_file_path": attr.string(), 121 }, 122) 123 124def _test_proto_code_gen(): 125 test_name = "proto_code_gen_test" 126 local_file_path = "local/proto_local.proto" 127 external_file_path = "external/proto_external.proto" 128 deps_file_path = "deps/proto_deps.proto" 129 external_proto_name = test_name + "_external_proto" 130 deps_proto_name = test_name + "_deps_proto" 131 local_proto_name = test_name + "_proto" 132 cc_name = test_name + "_cc_proto" 133 134 native.proto_library( 135 name = external_proto_name, 136 srcs = [external_file_path], 137 tags = ["manual"], 138 ) 139 140 native.proto_library( 141 name = deps_proto_name, 142 srcs = [deps_file_path], 143 tags = ["manual"], 144 ) 145 146 native.proto_library( 147 name = local_proto_name, 148 srcs = [local_file_path], 149 deps = [":" + deps_proto_name], 150 tags = ["manual"], 151 ) 152 153 cc_proto_library( 154 name = cc_name, 155 deps = [ 156 ":" + local_proto_name, 157 ":" + external_proto_name, 158 ], 159 tags = ["manual"], 160 ) 161 162 proto_code_gen_test( 163 name = test_name, 164 target_under_test = cc_name + PROTO_GEN_NAME_SUFFIX, 165 local_file_path = local_file_path, 166 deps_file_path = deps_file_path, 167 external_file_path = external_file_path, 168 ) 169 170 return test_name 171 172def _proto_strip_import_prefix_test_impl(ctx): 173 env = analysistest.begin(ctx) 174 actions = analysistest.target_actions(env) 175 package_root = ctx.label.package 176 177 # strip the proto file path, src/stripped/stripped.proto -> stripped/stripped.proto 178 stripped_file_name = paths.relativize(ctx.attr.stripped_file_name, ctx.attr.strip_import_prefix) 179 stripped_file_input_path = paths.join( 180 package_root, 181 VIRTUAL_IMPORT, 182 ctx.attr.stripped_proto_name, 183 ) 184 stripped_file_input_full_path = paths.join( 185 stripped_file_input_path, 186 stripped_file_name, 187 ) 188 189 asserts.true( 190 env, 191 len(actions) == 1, 192 "Proto gen action not found: %s" % actions, 193 ) 194 195 action = actions[0] 196 197 asserts.set_equals( 198 env, 199 expected = sets.make( 200 [ 201 paths.join(package_root, ctx.attr.unstripped_file_name), 202 stripped_file_input_full_path, 203 PROTO_GEN, 204 RUNFILES, 205 ], 206 ), 207 actual = sets.make([ 208 file.short_path 209 for file in action.inputs.to_list() 210 ]), 211 ) 212 213 asserts.set_equals( 214 env, 215 expected = sets.make(ctx.attr.expected_outputs), 216 actual = sets.make([ 217 file.short_path 218 for file in action.outputs.to_list() 219 ]), 220 ) 221 222 search_paths = _get_search_paths(action) 223 224 asserts.equals( 225 env, 226 expected = sets.make([ 227 ".", 228 paths.join(package_root, ctx.attr.unstripped_file_name) + "=" + paths.join(package_root, ctx.attr.unstripped_file_name), 229 stripped_file_input_full_path + "=" + 230 paths.join( 231 ctx.genfiles_dir.path, 232 stripped_file_input_full_path, 233 ), 234 paths.join( 235 ctx.genfiles_dir.path, 236 stripped_file_input_path, 237 ), 238 ]), 239 actual = search_paths, 240 ) 241 242 return analysistest.end(env) 243 244proto_strip_import_prefix_test = analysistest.make( 245 _proto_strip_import_prefix_test_impl, 246 attrs = { 247 "stripped_proto_name": attr.string(), 248 "stripped_file_name": attr.string(), 249 "unstripped_file_name": attr.string(), 250 "strip_import_prefix": attr.string(), 251 "expected_outputs": attr.string_list(), 252 }, 253) 254 255def _test_proto_strip_import_prefix(): 256 test_name = "proto_strip_import_prefix_test" 257 unstripped_proto_name = test_name + "_unstripped_proto" 258 stripped_proto_name = test_name + "_stripped_proto" 259 unstripped_file_name = "unstripped/unstripped.proto" 260 stripped_file_name = "src/stripped/stripped.proto" 261 cc_name = test_name + "_cc_proto" 262 strip_import_prefix = "src" 263 264 native.proto_library( 265 name = unstripped_proto_name, 266 srcs = [unstripped_file_name], 267 tags = ["manual"], 268 ) 269 270 native.proto_library( 271 name = stripped_proto_name, 272 srcs = [stripped_file_name], 273 strip_import_prefix = strip_import_prefix, 274 tags = ["manual"], 275 ) 276 277 cc_proto_library( 278 name = cc_name, 279 deps = [ 280 ":" + stripped_proto_name, 281 ":" + unstripped_proto_name, 282 ], 283 tags = ["manual"], 284 ) 285 286 expected_outputs = [ 287 # unstripped, the default behavior 288 # bazel package is added to the path 289 "build/bazel/rules/cc/proto_strip_import_prefix_test_cc_proto_proto_gen/build/bazel/rules/cc/unstripped/unstripped.pb.cc", 290 "build/bazel/rules/cc/proto_strip_import_prefix_test_cc_proto_proto_gen/build/bazel/rules/cc/unstripped/unstripped.pb.h", 291 # stripped - src/stripped/stripped.proto --> stripped/stripped.pb.cc 292 # since strip_import_prefix is not nil, the bazel package is not added to the path 293 "build/bazel/rules/cc/proto_strip_import_prefix_test_cc_proto_proto_gen/stripped/stripped.pb.cc", 294 "build/bazel/rules/cc/proto_strip_import_prefix_test_cc_proto_proto_gen/stripped/stripped.pb.h", 295 ] 296 297 proto_strip_import_prefix_test( 298 name = test_name, 299 target_under_test = cc_name + PROTO_GEN_NAME_SUFFIX, 300 stripped_proto_name = stripped_proto_name, 301 stripped_file_name = stripped_file_name, 302 unstripped_file_name = unstripped_file_name, 303 strip_import_prefix = strip_import_prefix, 304 expected_outputs = expected_outputs, 305 ) 306 307 return test_name 308 309def _proto_with_external_packages_test_impl(ctx): 310 env = analysistest.begin(ctx) 311 target_under_test = analysistest.target_under_test(env) 312 actions = analysistest.target_actions(env) 313 package_root = ctx.label.package 314 deps_file_path = ctx.attr.deps_file_path 315 external_file_path = ctx.attr.external_file_path 316 local_file_path = ctx.attr.local_file_path 317 318 asserts.true( 319 env, 320 len(actions) == 1, 321 "Proto gen action not found: %s" % actions, 322 ) 323 324 action = actions[0] 325 326 asserts.set_equals( 327 env, 328 expected = sets.make( 329 [ 330 paths.join(package_root, local_file_path), 331 deps_file_path, 332 external_file_path, 333 PROTO_GEN, 334 RUNFILES, 335 ], 336 ), 337 actual = sets.make([ 338 file.short_path 339 for file in action.inputs.to_list() 340 ]), 341 ) 342 343 asserts.set_equals( 344 env, 345 expected = sets.make( 346 [ 347 paths.join( 348 package_root, 349 target_under_test.label.name, 350 package_root, 351 paths.replace_extension(local_file_path, ext), 352 ) 353 for ext in GEN_SUFFIX 354 ] + 355 [ 356 paths.join( 357 package_root, 358 target_under_test.label.name, 359 paths.replace_extension(external_file_path, ext), 360 ) 361 for ext in GEN_SUFFIX 362 ], 363 ), 364 actual = sets.make([ 365 file.short_path 366 for file in action.outputs.to_list() 367 ]), 368 ) 369 370 search_paths = _get_search_paths(action) 371 372 asserts.equals( 373 env, 374 expected = sets.make([ 375 ".", 376 paths.join(package_root, local_file_path) + "=" + paths.join(package_root, local_file_path), 377 deps_file_path + "=" + deps_file_path, 378 external_file_path + "=" + external_file_path, 379 ]), 380 actual = search_paths, 381 ) 382 383 return analysistest.end(env) 384 385proto_with_external_packages_test = analysistest.make( 386 _proto_with_external_packages_test_impl, 387 attrs = { 388 "local_file_path": attr.string(), 389 "deps_file_path": attr.string(), 390 "external_file_path": attr.string(), 391 }, 392) 393 394def _test_proto_with_external_packages(): 395 test_name = "proto_with_external_packages_test" 396 proto_name = test_name + "_proto" 397 cc_name = test_name + "_cc_proto" 398 local_file_path = "local/proto_local.proto" 399 deps_file_path = "build/bazel/examples/cc/proto/deps/src/enums/proto_deps.proto" 400 external_file_path = "build/bazel/examples/cc/proto/external/src/enums/proto_external.proto" 401 402 native.proto_library( 403 name = proto_name, 404 srcs = [local_file_path], 405 deps = ["//build/bazel/examples/cc/proto/deps:deps_proto"], 406 tags = ["manual"], 407 ) 408 409 cc_proto_library( 410 name = cc_name, 411 deps = [ 412 ":" + proto_name, 413 "//build/bazel/examples/cc/proto/external:external_proto", 414 ], 415 tags = ["manual"], 416 ) 417 418 proto_with_external_packages_test( 419 name = test_name, 420 target_under_test = cc_name + PROTO_GEN_NAME_SUFFIX, 421 local_file_path = local_file_path, 422 deps_file_path = deps_file_path, 423 external_file_path = external_file_path, 424 ) 425 426 return test_name 427 428def cc_proto_test_suite(name): 429 native.test_suite( 430 name = name, 431 tests = [ 432 _test_proto_code_gen(), 433 _test_proto_strip_import_prefix(), 434 _test_proto_with_external_packages(), 435 ], 436 ) 437