1// Copyright 2021 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://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, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bp2build 16 17/* 18For shareable/common bp2build testing functionality and dumping ground for 19specific-but-shared functionality among tests in package 20*/ 21 22import ( 23 "fmt" 24 "strings" 25 "testing" 26 27 "android/soong/android" 28 "android/soong/android/allowlists" 29 "android/soong/bazel" 30) 31 32var ( 33 // A default configuration for tests to not have to specify bp2build_available on top level targets. 34 bp2buildConfig = android.NewBp2BuildAllowlist().SetDefaultConfig( 35 allowlists.Bp2BuildConfig{ 36 android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively, 37 }, 38 ) 39 40 buildDir string 41) 42 43func checkError(t *testing.T, errs []error, expectedErr error) bool { 44 t.Helper() 45 46 if len(errs) != 1 { 47 return false 48 } 49 if strings.Contains(errs[0].Error(), expectedErr.Error()) { 50 return true 51 } 52 53 return false 54} 55 56func errored(t *testing.T, tc bp2buildTestCase, errs []error) bool { 57 t.Helper() 58 if tc.expectedErr != nil { 59 // Rely on checkErrors, as this test case is expected to have an error. 60 return false 61 } 62 63 if len(errs) > 0 { 64 for _, err := range errs { 65 t.Errorf("%s: %s", tc.description, err) 66 } 67 return true 68 } 69 70 // All good, continue execution. 71 return false 72} 73 74func runBp2BuildTestCaseSimple(t *testing.T, tc bp2buildTestCase) { 75 t.Helper() 76 runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc) 77} 78 79type bp2buildTestCase struct { 80 description string 81 moduleTypeUnderTest string 82 moduleTypeUnderTestFactory android.ModuleFactory 83 blueprint string 84 expectedBazelTargets []string 85 filesystem map[string]string 86 dir string 87 // An error with a string contained within the string of the expected error 88 expectedErr error 89 unconvertedDepsMode unconvertedDepsMode 90} 91 92func runBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc bp2buildTestCase) { 93 t.Helper() 94 dir := "." 95 filesystem := make(map[string][]byte) 96 toParse := []string{ 97 "Android.bp", 98 } 99 for f, content := range tc.filesystem { 100 if strings.HasSuffix(f, "Android.bp") { 101 toParse = append(toParse, f) 102 } 103 filesystem[f] = []byte(content) 104 } 105 config := android.TestConfig(buildDir, nil, tc.blueprint, filesystem) 106 ctx := android.NewTestContext(config) 107 108 registerModuleTypes(ctx) 109 ctx.RegisterModuleType(tc.moduleTypeUnderTest, tc.moduleTypeUnderTestFactory) 110 ctx.RegisterBp2BuildConfig(bp2buildConfig) 111 ctx.RegisterForBazelConversion() 112 113 _, parseErrs := ctx.ParseFileList(dir, toParse) 114 if errored(t, tc, parseErrs) { 115 return 116 } 117 _, resolveDepsErrs := ctx.ResolveDependencies(config) 118 if errored(t, tc, resolveDepsErrs) { 119 return 120 } 121 122 errs := append(parseErrs, resolveDepsErrs...) 123 if tc.expectedErr != nil && checkError(t, errs, tc.expectedErr) { 124 return 125 } 126 127 checkDir := dir 128 if tc.dir != "" { 129 checkDir = tc.dir 130 } 131 codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build) 132 codegenCtx.unconvertedDepMode = tc.unconvertedDepsMode 133 bazelTargets, errs := generateBazelTargetsForDir(codegenCtx, checkDir) 134 if tc.expectedErr != nil { 135 if checkError(t, errs, tc.expectedErr) { 136 return 137 } else { 138 t.Errorf("Expected error: %q, got: %q", tc.expectedErr, errs) 139 } 140 } else { 141 android.FailIfErrored(t, errs) 142 } 143 if actualCount, expectedCount := len(bazelTargets), len(tc.expectedBazelTargets); actualCount != expectedCount { 144 t.Errorf("%s: Expected %d bazel target (%s), got `%d`` (%s)", 145 tc.description, expectedCount, tc.expectedBazelTargets, actualCount, bazelTargets) 146 } else { 147 for i, target := range bazelTargets { 148 if w, g := tc.expectedBazelTargets[i], target.content; w != g { 149 t.Errorf( 150 "%s: Expected generated Bazel target to be `%s`, got `%s`", 151 tc.description, w, g) 152 } 153 } 154 } 155} 156 157type nestedProps struct { 158 Nested_prop *string 159} 160 161type EmbeddedProps struct { 162 Embedded_prop *string 163} 164 165type OtherEmbeddedProps struct { 166 Other_embedded_prop *string 167} 168 169type customProps struct { 170 EmbeddedProps 171 *OtherEmbeddedProps 172 173 Bool_prop bool 174 Bool_ptr_prop *bool 175 // Ensure that properties tagged `blueprint:mutated` are omitted 176 Int_prop int `blueprint:"mutated"` 177 Int64_ptr_prop *int64 178 String_prop string 179 String_ptr_prop *string 180 String_list_prop []string 181 182 Nested_props nestedProps 183 Nested_props_ptr *nestedProps 184 185 Arch_paths []string `android:"path,arch_variant"` 186 Arch_paths_exclude []string `android:"path,arch_variant"` 187 188 // Prop used to indicate this conversion should be 1 module -> multiple targets 189 One_to_many_prop *bool 190} 191 192type customModule struct { 193 android.ModuleBase 194 android.BazelModuleBase 195 196 props customProps 197} 198 199// OutputFiles is needed because some instances of this module use dist with a 200// tag property which requires the module implements OutputFileProducer. 201func (m *customModule) OutputFiles(tag string) (android.Paths, error) { 202 return android.PathsForTesting("path" + tag), nil 203} 204 205func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { 206 // nothing for now. 207} 208 209func customModuleFactoryBase() android.Module { 210 module := &customModule{} 211 module.AddProperties(&module.props) 212 android.InitBazelModule(module) 213 return module 214} 215 216func customModuleFactory() android.Module { 217 m := customModuleFactoryBase() 218 android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibBoth) 219 return m 220} 221 222type testProps struct { 223 Test_prop struct { 224 Test_string_prop string 225 } 226} 227 228type customTestModule struct { 229 android.ModuleBase 230 231 props customProps 232 test_props testProps 233} 234 235func (m *customTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { 236 // nothing for now. 237} 238 239func customTestModuleFactoryBase() android.Module { 240 m := &customTestModule{} 241 m.AddProperties(&m.props) 242 m.AddProperties(&m.test_props) 243 return m 244} 245 246func customTestModuleFactory() android.Module { 247 m := customTestModuleFactoryBase() 248 android.InitAndroidModule(m) 249 return m 250} 251 252type customDefaultsModule struct { 253 android.ModuleBase 254 android.DefaultsModuleBase 255} 256 257func customDefaultsModuleFactoryBase() android.DefaultsModule { 258 module := &customDefaultsModule{} 259 module.AddProperties(&customProps{}) 260 return module 261} 262 263func customDefaultsModuleFactoryBasic() android.Module { 264 return customDefaultsModuleFactoryBase() 265} 266 267func customDefaultsModuleFactory() android.Module { 268 m := customDefaultsModuleFactoryBase() 269 android.InitDefaultsModule(m) 270 return m 271} 272 273type EmbeddedAttr struct { 274 Embedded_attr *string 275} 276 277type OtherEmbeddedAttr struct { 278 Other_embedded_attr *string 279} 280 281type customBazelModuleAttributes struct { 282 EmbeddedAttr 283 *OtherEmbeddedAttr 284 String_ptr_prop *string 285 String_list_prop []string 286 Arch_paths bazel.LabelListAttribute 287} 288 289func (m *customModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) { 290 paths := bazel.LabelListAttribute{} 291 292 if p := m.props.One_to_many_prop; p != nil && *p { 293 customBp2buildOneToMany(ctx, m) 294 return 295 } 296 297 for axis, configToProps := range m.GetArchVariantProperties(ctx, &customProps{}) { 298 for config, props := range configToProps { 299 if archProps, ok := props.(*customProps); ok && archProps.Arch_paths != nil { 300 paths.SetSelectValue(axis, config, android.BazelLabelForModuleSrcExcludes(ctx, archProps.Arch_paths, archProps.Arch_paths_exclude)) 301 } 302 } 303 } 304 305 paths.ResolveExcludes() 306 307 attrs := &customBazelModuleAttributes{ 308 String_ptr_prop: m.props.String_ptr_prop, 309 String_list_prop: m.props.String_list_prop, 310 Arch_paths: paths, 311 } 312 attrs.Embedded_attr = m.props.Embedded_prop 313 if m.props.OtherEmbeddedProps != nil { 314 attrs.OtherEmbeddedAttr = &OtherEmbeddedAttr{Other_embedded_attr: m.props.OtherEmbeddedProps.Other_embedded_prop} 315 } 316 317 props := bazel.BazelTargetModuleProperties{ 318 Rule_class: "custom", 319 } 320 321 ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs) 322} 323 324// A bp2build mutator that uses load statements and creates a 1:M mapping from 325// module to target. 326func customBp2buildOneToMany(ctx android.TopDownMutatorContext, m *customModule) { 327 328 baseName := m.Name() 329 attrs := &customBazelModuleAttributes{} 330 331 myLibraryProps := bazel.BazelTargetModuleProperties{ 332 Rule_class: "my_library", 333 Bzl_load_location: "//build/bazel/rules:rules.bzl", 334 } 335 ctx.CreateBazelTargetModule(myLibraryProps, android.CommonAttributes{Name: baseName}, attrs) 336 337 protoLibraryProps := bazel.BazelTargetModuleProperties{ 338 Rule_class: "proto_library", 339 Bzl_load_location: "//build/bazel/rules:proto.bzl", 340 } 341 ctx.CreateBazelTargetModule(protoLibraryProps, android.CommonAttributes{Name: baseName + "_proto_library_deps"}, attrs) 342 343 myProtoLibraryProps := bazel.BazelTargetModuleProperties{ 344 Rule_class: "my_proto_library", 345 Bzl_load_location: "//build/bazel/rules:proto.bzl", 346 } 347 ctx.CreateBazelTargetModule(myProtoLibraryProps, android.CommonAttributes{Name: baseName + "_my_proto_library_deps"}, attrs) 348} 349 350// Helper method for tests to easily access the targets in a dir. 351func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) (BazelTargets, []error) { 352 // TODO: Set generateFilegroups to true and/or remove the generateFilegroups argument completely 353 res, err := GenerateBazelTargets(codegenCtx, false) 354 return res.buildFileToTargets[dir], err 355} 356 357func registerCustomModuleForBp2buildConversion(ctx *android.TestContext) { 358 ctx.RegisterModuleType("custom", customModuleFactory) 359 ctx.RegisterForBazelConversion() 360} 361 362func simpleModuleDoNotConvertBp2build(typ, name string) string { 363 return fmt.Sprintf(` 364%s { 365 name: "%s", 366 bazel_module: { bp2build_available: false }, 367}`, typ, name) 368} 369 370type attrNameToString map[string]string 371 372func makeBazelTarget(typ, name string, attrs attrNameToString) string { 373 attrStrings := make([]string, 0, len(attrs)+1) 374 attrStrings = append(attrStrings, fmt.Sprintf(` name = "%s",`, name)) 375 for _, k := range android.SortedStringKeys(attrs) { 376 attrStrings = append(attrStrings, fmt.Sprintf(" %s = %s,", k, attrs[k])) 377 } 378 return fmt.Sprintf(`%s( 379%s 380)`, typ, strings.Join(attrStrings, "\n")) 381} 382