1# Copyright 2024 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Bazel Tests.""" 15 16import pathlib 17import unittest 18import xml.etree.ElementTree 19 20from pw_build_mcuxpresso.bazel import ( 21 BazelVariable, 22 generate_bazel_targets, 23 headers_cc_library, 24 import_targets, 25 target_to_bazel, 26) 27from pw_build_mcuxpresso.common import BuildTarget 28from pw_build_mcuxpresso.components import Project 29 30# pylint: disable=missing-function-docstring 31# pylint: disable=line-too-long 32 33 34def setup_test_project() -> Project: 35 test_manifest_xml = ''' 36 <manifest> 37 <components> 38 <component id="test"> 39 <dependencies> 40 <component_dependency value="foo"/> 41 <component_dependency value="bar"/> 42 </dependencies> 43 </component> 44 <component id="foo" package_base_path="foo"> 45 <defines> 46 <define name="FOO"/> 47 </defines> 48 <source relative_path="include" type="c_include"> 49 <files mask="foo.h"/> 50 </source> 51 <source relative_path="src" type="src"> 52 <files mask="foo.cc"/> 53 </source> 54 <include_paths> 55 <include_path relative_path="include" type="c_include"/> 56 </include_paths> 57 </component> 58 <component id="bar" package_base_path="bar"> 59 <defines> 60 <define name="BAR"/> 61 </defines> 62 <source relative_path="include" type="c_include"> 63 <files mask="bar.h"/> 64 </source> 65 <source relative_path="src" type="src"> 66 <files mask="bar.cc"/> 67 </source> 68 <include_paths> 69 <include_path relative_path="include" type="c_include"/> 70 </include_paths> 71 </component> 72 <component id="frodo" package_base_path="frodo"> 73 <dependencies> 74 <component_dependency value="bilbo"/> 75 <component_dependency value="smeagol"/> 76 </dependencies> 77 <defines> 78 <define name="FRODO"/> 79 </defines> 80 <source relative_path="include" type="c_include"> 81 <files mask="frodo.h"/> 82 </source> 83 <source relative_path="src" type="src"> 84 <files mask="frodo.cc"/> 85 </source> 86 <source toolchain="armgcc" relative_path="./" type="lib"> 87 <files mask="libonering.a"/> 88 </source> 89 <include_paths> 90 <include_path relative_path="include" type="c_include"/> 91 </include_paths> 92 </component> 93 <component id="bilbo" package_base_path="bilbo"> 94 <defines> 95 <define name="BILBO"/> 96 </defines> 97 <source relative_path="include" type="c_include"> 98 <files mask="bilbo.h"/> 99 </source> 100 <source relative_path="src" type="src"> 101 <files mask="bilbo.cc"/> 102 </source> 103 <include_paths> 104 <include_path relative_path="include" type="c_include"/> 105 </include_paths> 106 </component> 107 <component id="smeagol" package_base_path="smeagol"> 108 <dependencies> 109 <component_dependency value="gollum"/> 110 </dependencies> 111 <source toolchain="armgcc" relative_path="./" type="lib"> 112 <files mask="libonering.a"/> 113 </source> 114 </component> 115 <component id="gollum" package_base_path="gollum"> 116 <dependencies> 117 <component_dependency value="smeagol"/> 118 </dependencies> 119 <source toolchain="armgcc" relative_path="./" type="lib"> 120 <files mask="libonering.a"/> 121 </source> 122 </component> 123 </components> 124 </manifest> 125 ''' 126 manifest = ( 127 xml.etree.ElementTree.fromstring(test_manifest_xml), 128 pathlib.Path.cwd() / "manifest.xml", 129 ) 130 return Project([manifest], pathlib.Path.cwd(), ["test", "frodo"]) 131 132 133class TargetToBazelTest(unittest.TestCase): 134 """target_to_bazel tests.""" 135 136 def test_str_attrib(self): 137 expected_format = r''' 138test_rule( 139 name = "foo", 140 bar = "baz", 141) 142'''.strip() 143 144 target = BuildTarget("test_rule", "foo", {"bar": "baz"}) 145 146 self.assertEqual(target_to_bazel(target), expected_format) 147 148 def test_num_attr(self): 149 expected_format = r''' 150test_rule( 151 name = "foo", 152 bar = 13, 153) 154'''.strip() 155 156 target = BuildTarget( 157 "test_rule", 158 "foo", 159 { 160 "bar": 13, 161 }, 162 ) 163 164 self.assertEqual(target_to_bazel(target), expected_format) 165 166 def test_bool_attr(self): 167 expected_format = r''' 168test_rule( 169 name = "foo", 170 bar = True, 171 baz = False, 172) 173'''.strip() 174 175 target = BuildTarget( 176 "test_rule", 177 "foo", 178 { 179 "bar": True, 180 "baz": False, 181 }, 182 ) 183 184 self.assertEqual(target_to_bazel(target), expected_format) 185 186 def test_target_attr(self): 187 expected_format = r''' 188test_rule( 189 name = "foo", 190 bar = ":baz", 191) 192'''.strip() 193 194 target = BuildTarget( 195 "test_rule", 196 "foo", 197 {"bar": BuildTarget("test_rule", "baz")}, 198 ) 199 200 self.assertEqual(target_to_bazel(target), expected_format) 201 202 def test_bazel_var_attr(self): 203 expected_format = r''' 204test_rule( 205 name = "foo", 206 bar = MY_VAR, 207) 208'''.strip() 209 210 target = BuildTarget( 211 "test_rule", 212 "foo", 213 {"bar": BazelVariable("MY_VAR", "")}, 214 ) 215 216 self.assertEqual(target_to_bazel(target), expected_format) 217 218 def test_list_attr(self): 219 expected_format = r''' 220test_rule( 221 name = "foo", 222 bar = ['baz', 13, True, ':quox'], 223) 224'''.strip() 225 226 target = BuildTarget( 227 "test_rule", 228 "foo", 229 { 230 "bar": [ 231 "baz", 232 13, 233 True, 234 BuildTarget("test_rule", "quox"), 235 ], 236 }, 237 ) 238 239 self.assertEqual(target_to_bazel(target), expected_format) 240 241 def test_full_rule(self): 242 expected_format = r''' 243lore( 244 name = "gollum", 245 age = 600, 246 is_hobbit = True, 247 needs = [':fish', ':ring'], 248 other_name = "smeagol", 249) 250'''.strip() 251 252 target = BuildTarget( 253 "lore", 254 "gollum", 255 { 256 "other_name": "smeagol", 257 "age": 600, 258 "is_hobbit": True, 259 "needs": [ 260 BuildTarget("test_rule", "fish"), 261 BuildTarget("test_rule", "ring"), 262 ], 263 }, 264 ) 265 266 self.assertEqual(target_to_bazel(target), expected_format) 267 268 def test_indent(self): 269 expected_format = r''' 270test_rule( 271 name = "foo", 272 indent = 2, 273) 274'''.strip() 275 276 target = BuildTarget("test_rule", "foo", {"indent": 2}) 277 278 self.assertEqual(target_to_bazel(target, 2), expected_format) 279 280 281class ImportTargetsTest(unittest.TestCase): 282 """import_targets tests.""" 283 284 def test_single_lib(self): 285 library = [pathlib.Path("testlib.a")] 286 287 targets = import_targets(library) 288 289 self.assertEqual(len(targets), 1) 290 self.assertListEqual( 291 targets, 292 [ 293 BuildTarget( 294 "cc_import", 295 "testlib.a", 296 { 297 "static_library": "testlib.a", 298 }, 299 ) 300 ], 301 ) 302 303 def test_nested_lib(self): 304 library = [pathlib.Path("some/path/to/testlib.a")] 305 306 targets = import_targets(library) 307 308 self.assertEqual(len(targets), 1) 309 self.assertListEqual( 310 targets, 311 [ 312 BuildTarget( 313 "cc_import", 314 "some.path.to.testlib.a", 315 { 316 "static_library": "some/path/to/testlib.a", 317 }, 318 ) 319 ], 320 ) 321 322 def test_multi_libs(self): 323 libraries = [ 324 pathlib.Path("some/path/to/testlib.a"), 325 pathlib.Path("testlib.a"), 326 pathlib.Path("libonering.a"), 327 ] 328 329 targets = import_targets(libraries) 330 331 self.assertEqual(len(targets), len(libraries)) 332 self.assertListEqual( 333 targets, 334 [ 335 BuildTarget( 336 "cc_import", 337 "libonering.a", 338 { 339 "static_library": "libonering.a", 340 }, 341 ), 342 BuildTarget( 343 "cc_import", 344 "some.path.to.testlib.a", 345 { 346 "static_library": "some/path/to/testlib.a", 347 }, 348 ), 349 BuildTarget( 350 "cc_import", 351 "testlib.a", 352 { 353 "static_library": "testlib.a", 354 }, 355 ), 356 ], 357 ) 358 359 360class HeadersCommonTest(unittest.TestCase): 361 """headers_cc_library tests.""" 362 363 def test_common_component(self): 364 expected_format = ''' 365cc_library( 366 name = "commons", 367 copts = COPTS, 368 defines = ['BAR', 'BILBO', 'FOO', 'FRODO'], 369 deps = [':user_config'], 370 hdrs = ['bar/include/bar.h', 'bilbo/include/bilbo.h', 'foo/include/foo.h', 'frodo/include/frodo.h'], 371 includes = ['bar/include', 'bilbo/include', 'foo/include', 'frodo/include'], 372) 373'''.strip() 374 375 project = setup_test_project() 376 377 commons = headers_cc_library(project) 378 379 self.assertIsNotNone(commons.attrs.get("defines")) 380 self.assertListEqual( 381 commons.attrs["defines"], ["BAR", "BILBO", "FOO", "FRODO"] 382 ) 383 384 self.assertIsNotNone(commons.attrs.get("hdrs")) 385 self.assertListEqual( 386 commons.attrs["hdrs"], 387 [ 388 'bar/include/bar.h', 389 'bilbo/include/bilbo.h', 390 'foo/include/foo.h', 391 'frodo/include/frodo.h', 392 ], 393 ) 394 395 self.assertIsNotNone(commons.attrs.get("includes")) 396 self.assertListEqual( 397 commons.attrs["includes"], 398 ['bar/include', 'bilbo/include', 'foo/include', 'frodo/include'], 399 ) 400 401 self.assertEqual(target_to_bazel(commons), expected_format) 402 403 404class ProjectTargetsTest(unittest.TestCase): 405 """generate_project_targets tests.""" 406 407 def test_project(self): 408 expected_format = ''' 409label_flag( 410 name = "user_config", 411 build_setting_default = "@pigweed//pw_build:empty_cc_library", 412) 413cc_library( 414 name = "commons", 415 copts = COPTS, 416 defines = ['BAR', 'BILBO', 'FOO', 'FRODO'], 417 deps = [':user_config'], 418 hdrs = ['bar/include/bar.h', 'bilbo/include/bilbo.h', 'foo/include/foo.h', 'frodo/include/frodo.h'], 419 includes = ['bar/include', 'bilbo/include', 'foo/include', 'frodo/include'], 420) 421cc_import( 422 name = "frodo.libonering.a", 423 static_library = "frodo/libonering.a", 424) 425cc_import( 426 name = "gollum.libonering.a", 427 static_library = "gollum/libonering.a", 428) 429cc_import( 430 name = "smeagol.libonering.a", 431 static_library = "smeagol/libonering.a", 432) 433cc_library( 434 name = "bar", 435 copts = COPTS, 436 deps = [':commons'], 437 srcs = ['bar/src/bar.cc'], 438) 439cc_library( 440 name = "bilbo", 441 copts = COPTS, 442 deps = [':commons'], 443 srcs = ['bilbo/src/bilbo.cc'], 444) 445cc_library( 446 name = "foo", 447 copts = COPTS, 448 deps = [':commons'], 449 srcs = ['foo/src/foo.cc'], 450) 451cc_library( 452 name = "frodo", 453 copts = COPTS, 454 deps = [':bilbo', ':commons', ':frodo.libonering.a', ':smeagol'], 455 srcs = ['frodo/src/frodo.cc'], 456) 457cc_library( 458 name = "gollum", 459 copts = COPTS, 460 deps = [':commons', ':gollum.libonering.a', ':smeagol.libonering.a'], 461) 462cc_library( 463 name = "smeagol", 464 copts = COPTS, 465 deps = [':commons', ':gollum.libonering.a', ':smeagol.libonering.a'], 466) 467cc_library( 468 name = "test", 469 copts = COPTS, 470 deps = [':bar', ':commons', ':foo'], 471) 472'''.strip() 473 474 project = setup_test_project() 475 476 targets = list(generate_bazel_targets(project)) 477 self.assertEqual(len(targets), 12) 478 479 targets = "\n".join(target_to_bazel(target) for target in targets) 480 self.assertEqual(targets, expected_format) 481 482 483if __name__ == '__main__': 484 unittest.main() 485