1// Copyright 2015 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 android 16 17import ( 18 "fmt" 19 "path/filepath" 20 "strings" 21 22 "android/soong/bazel" 23 24 "github.com/google/blueprint" 25 "github.com/google/blueprint/pathtools" 26) 27 28// bazel_paths contains methods to: 29// * resolve Soong path and module references into bazel.LabelList 30// * resolve Bazel path references into Soong-compatible paths 31// 32// There is often a similar method for Bazel as there is for Soong path handling and should be used 33// in similar circumstances 34// 35// Bazel Soong 36// 37// BazelLabelForModuleSrc PathForModuleSrc 38// BazelLabelForModuleSrcExcludes PathForModuleSrcExcludes 39// BazelLabelForModuleDeps n/a 40// tbd PathForSource 41// tbd ExistentPathsForSources 42// PathForBazelOut PathForModuleOut 43// 44// Use cases: 45// * Module contains a property (often tagged `android:"path"`) that expects paths *relative to the 46// module directory*: 47// * BazelLabelForModuleSrcExcludes, if the module also contains an excludes_<propname> property 48// * BazelLabelForModuleSrc, otherwise 49// * Converting references to other modules to Bazel Labels: 50// BazelLabelForModuleDeps 51// * Converting a path obtained from bazel_handler cquery results: 52// PathForBazelOut 53// 54// NOTE: all Soong globs are expanded within Soong rather than being converted to a Bazel glob 55// syntax. This occurs because Soong does not have a concept of crossing package boundaries, 56// so the glob as computed by Soong may contain paths that cross package-boundaries. These 57// would be unknowingly omitted if the glob were handled by Bazel. By expanding globs within 58// Soong, we support identification and detection (within Bazel) use of paths that cross 59// package boundaries. 60// 61// Path resolution: 62// * filepath/globs: resolves as itself or is converted to an absolute Bazel label (e.g. 63// //path/to/dir:<filepath>) if path exists in a separate package or subpackage. 64// * references to other modules (using the ":name{.tag}" syntax). These resolve as a Bazel label 65// for a target. If the Bazel target is in the local module directory, it will be returned 66// relative to the current package (e.g. ":<target>"). Otherwise, it will be returned as an 67// absolute Bazel label (e.g. "//path/to/dir:<target>"). If the reference to another module 68// cannot be resolved,the function will panic. This is often due to the dependency not being added 69// via an AddDependency* method. 70 71// A minimal context interface to check if a module should be converted by bp2build, 72// with functions containing information to match against allowlists and denylists. 73// If a module is deemed to be convertible by bp2build, then it should rely on a 74// BazelConversionPathContext for more functions for dep/path features. 75type BazelConversionContext interface { 76 Config() Config 77 78 Module() Module 79 OtherModuleType(m blueprint.Module) string 80 OtherModuleName(m blueprint.Module) string 81 OtherModuleDir(m blueprint.Module) string 82 ModuleErrorf(format string, args ...interface{}) 83} 84 85// A subset of the ModuleContext methods which are sufficient to resolve references to paths/deps in 86// order to form a Bazel-compatible label for conversion. 87type BazelConversionPathContext interface { 88 EarlyModulePathContext 89 BazelConversionContext 90 91 ModuleErrorf(fmt string, args ...interface{}) 92 PropertyErrorf(property, fmt string, args ...interface{}) 93 GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag) 94 ModuleFromName(name string) (blueprint.Module, bool) 95 AddUnconvertedBp2buildDep(string) 96 AddMissingBp2buildDep(dep string) 97} 98 99// BazelLabelForModuleDeps expects a list of reference to other modules, ("<module>" 100// or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the 101// module within the given ctx. 102func BazelLabelForModuleDeps(ctx BazelConversionPathContext, modules []string) bazel.LabelList { 103 return BazelLabelForModuleDepsWithFn(ctx, modules, BazelModuleLabel) 104} 105 106// BazelLabelForModuleWholeDepsExcludes expects two lists: modules (containing modules to include in 107// the list), and excludes (modules to exclude from the list). Both of these should contain 108// references to other modules, ("<module>" or ":<module>"). It returns a Bazel-compatible label 109// list which corresponds to dependencies on the module within the given ctx, and the excluded 110// dependencies. Prebuilt dependencies will be appended with _alwayslink so they can be handled as 111// whole static libraries. 112func BazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList { 113 return BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, BazelModuleLabel) 114} 115 116// BazelLabelForModuleDepsWithFn expects a list of reference to other modules, ("<module>" 117// or ":<module>") and applies moduleToLabelFn to determine and return a Bazel-compatible label 118// which corresponds to dependencies on the module within the given ctx. 119func BazelLabelForModuleDepsWithFn(ctx BazelConversionPathContext, modules []string, 120 moduleToLabelFn func(BazelConversionPathContext, blueprint.Module) string) bazel.LabelList { 121 var labels bazel.LabelList 122 // In some cases, a nil string list is different than an explicitly empty list. 123 if len(modules) == 0 && modules != nil { 124 labels.Includes = []bazel.Label{} 125 return labels 126 } 127 for _, module := range modules { 128 bpText := module 129 if m := SrcIsModule(module); m == "" { 130 module = ":" + module 131 } 132 if m, t := SrcIsModuleWithTag(module); m != "" { 133 l := getOtherModuleLabel(ctx, m, t, moduleToLabelFn) 134 if l != nil { 135 l.OriginalModuleName = bpText 136 labels.Includes = append(labels.Includes, *l) 137 } 138 } else { 139 ctx.ModuleErrorf("%q, is not a module reference", module) 140 } 141 } 142 return labels 143} 144 145// BazelLabelForModuleDepsExcludesWithFn expects two lists: modules (containing modules to include in the 146// list), and excludes (modules to exclude from the list). Both of these should contain references 147// to other modules, ("<module>" or ":<module>"). It applies moduleToLabelFn to determine and return a 148// Bazel-compatible label list which corresponds to dependencies on the module within the given ctx, and 149// the excluded dependencies. 150func BazelLabelForModuleDepsExcludesWithFn(ctx BazelConversionPathContext, modules, excludes []string, 151 moduleToLabelFn func(BazelConversionPathContext, blueprint.Module) string) bazel.LabelList { 152 moduleLabels := BazelLabelForModuleDepsWithFn(ctx, RemoveListFromList(modules, excludes), moduleToLabelFn) 153 if len(excludes) == 0 { 154 return moduleLabels 155 } 156 excludeLabels := BazelLabelForModuleDepsWithFn(ctx, excludes, moduleToLabelFn) 157 return bazel.LabelList{ 158 Includes: moduleLabels.Includes, 159 Excludes: excludeLabels.Includes, 160 } 161} 162 163func BazelLabelForModuleSrcSingle(ctx BazelConversionPathContext, path string) bazel.Label { 164 if srcs := BazelLabelForModuleSrcExcludes(ctx, []string{path}, []string(nil)).Includes; len(srcs) > 0 { 165 return srcs[0] 166 } 167 return bazel.Label{} 168} 169 170func BazelLabelForModuleDepSingle(ctx BazelConversionPathContext, path string) bazel.Label { 171 if srcs := BazelLabelForModuleDepsExcludes(ctx, []string{path}, []string(nil)).Includes; len(srcs) > 0 { 172 return srcs[0] 173 } 174 return bazel.Label{} 175} 176 177// BazelLabelForModuleSrc expects a list of path (relative to local module directory) and module 178// references (":<module>") and returns a bazel.LabelList{} containing the resolved references in 179// paths, relative to the local module, or Bazel-labels (absolute if in a different package or 180// relative if within the same package). 181// Properties must have been annotated with struct tag `android:"path"` so that dependencies modules 182// will have already been handled by the path_deps mutator. 183func BazelLabelForModuleSrc(ctx BazelConversionPathContext, paths []string) bazel.LabelList { 184 return BazelLabelForModuleSrcExcludes(ctx, paths, []string(nil)) 185} 186 187// BazelLabelForModuleSrc expects lists of path and excludes (relative to local module directory) 188// and module references (":<module>") and returns a bazel.LabelList{} containing the resolved 189// references in paths, minus those in excludes, relative to the local module, or Bazel-labels 190// (absolute if in a different package or relative if within the same package). 191// Properties must have been annotated with struct tag `android:"path"` so that dependencies modules 192// will have already been handled by the path_deps mutator. 193func BazelLabelForModuleSrcExcludes(ctx BazelConversionPathContext, paths, excludes []string) bazel.LabelList { 194 excludeLabels := expandSrcsForBazel(ctx, excludes, []string(nil)) 195 excluded := make([]string, 0, len(excludeLabels.Includes)) 196 for _, e := range excludeLabels.Includes { 197 excluded = append(excluded, e.Label) 198 } 199 labels := expandSrcsForBazel(ctx, paths, excluded) 200 labels.Excludes = excludeLabels.Includes 201 labels = transformSubpackagePaths(ctx, labels) 202 return labels 203} 204 205// Returns true if a prefix + components[:i] + /Android.bp exists 206// TODO(b/185358476) Could check for BUILD file instead of checking for Android.bp file, or ensure BUILD is always generated? 207func directoryHasBlueprint(fs pathtools.FileSystem, prefix string, components []string, componentIndex int) bool { 208 blueprintPath := prefix 209 if blueprintPath != "" { 210 blueprintPath = blueprintPath + "/" 211 } 212 blueprintPath = blueprintPath + strings.Join(components[:componentIndex+1], "/") 213 blueprintPath = blueprintPath + "/Android.bp" 214 if exists, _, _ := fs.Exists(blueprintPath); exists { 215 return true 216 } else { 217 return false 218 } 219} 220 221// Transform a path (if necessary) to acknowledge package boundaries 222// 223// e.g. something like 224// async_safe/include/async_safe/CHECK.h 225// might become 226// //bionic/libc/async_safe:include/async_safe/CHECK.h 227// if the "async_safe" directory is actually a package and not just a directory. 228// 229// In particular, paths that extend into packages are transformed into absolute labels beginning with //. 230func transformSubpackagePath(ctx BazelConversionPathContext, path bazel.Label) bazel.Label { 231 var newPath bazel.Label 232 233 // Don't transform OriginalModuleName 234 newPath.OriginalModuleName = path.OriginalModuleName 235 236 if strings.HasPrefix(path.Label, "//") { 237 // Assume absolute labels are already correct (e.g. //path/to/some/package:foo.h) 238 newPath.Label = path.Label 239 return newPath 240 } 241 242 newLabel := "" 243 pathComponents := strings.Split(path.Label, "/") 244 foundBlueprint := false 245 // Check the deepest subdirectory first and work upwards 246 for i := len(pathComponents) - 1; i >= 0; i-- { 247 pathComponent := pathComponents[i] 248 var sep string 249 if !foundBlueprint && directoryHasBlueprint(ctx.Config().fs, ctx.ModuleDir(), pathComponents, i) { 250 sep = ":" 251 foundBlueprint = true 252 } else { 253 sep = "/" 254 } 255 if newLabel == "" { 256 newLabel = pathComponent 257 } else { 258 newLabel = pathComponent + sep + newLabel 259 } 260 } 261 if foundBlueprint { 262 // Ensure paths end up looking like //bionic/... instead of //./bionic/... 263 moduleDir := ctx.ModuleDir() 264 if strings.HasPrefix(moduleDir, ".") { 265 moduleDir = moduleDir[1:] 266 } 267 // Make the path into an absolute label (e.g. //bionic/libc/foo:bar.h instead of just foo:bar.h) 268 if moduleDir == "" { 269 newLabel = "//" + newLabel 270 } else { 271 newLabel = "//" + moduleDir + "/" + newLabel 272 } 273 } 274 newPath.Label = newLabel 275 276 return newPath 277} 278 279// Transform paths to acknowledge package boundaries 280// See transformSubpackagePath() for more information 281func transformSubpackagePaths(ctx BazelConversionPathContext, paths bazel.LabelList) bazel.LabelList { 282 var newPaths bazel.LabelList 283 for _, include := range paths.Includes { 284 newPaths.Includes = append(newPaths.Includes, transformSubpackagePath(ctx, include)) 285 } 286 for _, exclude := range paths.Excludes { 287 newPaths.Excludes = append(newPaths.Excludes, transformSubpackagePath(ctx, exclude)) 288 } 289 return newPaths 290} 291 292// Converts root-relative Paths to a list of bazel.Label relative to the module in ctx. 293func RootToModuleRelativePaths(ctx BazelConversionPathContext, paths Paths) []bazel.Label { 294 var newPaths []bazel.Label 295 for _, path := range PathsWithModuleSrcSubDir(ctx, paths, "") { 296 s := path.Rel() 297 newPaths = append(newPaths, bazel.Label{Label: s}) 298 } 299 return newPaths 300} 301 302// expandSrcsForBazel returns bazel.LabelList with paths rooted from the module's local source 303// directory and Bazel target labels, excluding those included in the excludes argument (which 304// should already be expanded to resolve references to Soong-modules). Valid elements of paths 305// include: 306// * filepath, relative to local module directory, resolves as a filepath relative to the local 307// source directory 308// * glob, relative to the local module directory, resolves as filepath(s), relative to the local 309// module directory. Because Soong does not have a concept of crossing package boundaries, the 310// glob as computed by Soong may contain paths that cross package-boundaries that would be 311// unknowingly omitted if the glob were handled by Bazel. To allow identification and detect 312// (within Bazel) use of paths that cross package boundaries, we expand globs within Soong rather 313// than converting Soong glob syntax to Bazel glob syntax. **Invalid for excludes.** 314// * other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer 315// or OutputFileProducer. These resolve as a Bazel label for a target. If the Bazel target is in 316// the local module directory, it will be returned relative to the current package (e.g. 317// ":<target>"). Otherwise, it will be returned as an absolute Bazel label (e.g. 318// "//path/to/dir:<target>"). If the reference to another module cannot be resolved,the function 319// will panic. 320// Properties passed as the paths or excludes argument must have been annotated with struct tag 321// `android:"path"` so that dependencies on other modules will have already been handled by the 322// path_deps mutator. 323func expandSrcsForBazel(ctx BazelConversionPathContext, paths, expandedExcludes []string) bazel.LabelList { 324 if paths == nil { 325 return bazel.LabelList{} 326 } 327 labels := bazel.LabelList{ 328 Includes: []bazel.Label{}, 329 } 330 331 // expandedExcludes contain module-dir relative paths, but root-relative paths 332 // are needed for GlobFiles later. 333 var rootRelativeExpandedExcludes []string 334 for _, e := range expandedExcludes { 335 rootRelativeExpandedExcludes = append(rootRelativeExpandedExcludes, filepath.Join(ctx.ModuleDir(), e)) 336 } 337 338 for _, p := range paths { 339 if m, tag := SrcIsModuleWithTag(p); m != "" { 340 l := getOtherModuleLabel(ctx, m, tag, BazelModuleLabel) 341 if l != nil && !InList(l.Label, expandedExcludes) { 342 l.OriginalModuleName = fmt.Sprintf(":%s", m) 343 labels.Includes = append(labels.Includes, *l) 344 } 345 } else { 346 var expandedPaths []bazel.Label 347 if pathtools.IsGlob(p) { 348 // e.g. turn "math/*.c" in 349 // external/arm-optimized-routines to external/arm-optimized-routines/math/*.c 350 rootRelativeGlobPath := pathForModuleSrc(ctx, p).String() 351 expandedPaths = RootToModuleRelativePaths(ctx, GlobFiles(ctx, rootRelativeGlobPath, rootRelativeExpandedExcludes)) 352 } else { 353 if !InList(p, expandedExcludes) { 354 expandedPaths = append(expandedPaths, bazel.Label{Label: p}) 355 } 356 } 357 labels.Includes = append(labels.Includes, expandedPaths...) 358 } 359 } 360 return labels 361} 362 363// getOtherModuleLabel returns a bazel.Label for the given dependency/tag combination for the 364// module. The label will be relative to the current directory if appropriate. The dependency must 365// already be resolved by either deps mutator or path deps mutator. 366func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string, 367 labelFromModule func(BazelConversionPathContext, blueprint.Module) string) *bazel.Label { 368 m, _ := ctx.ModuleFromName(dep) 369 // The module was not found in an Android.bp file, this is often due to: 370 // * a limited manifest 371 // * a required module not being converted from Android.mk 372 if m == nil { 373 ctx.AddMissingBp2buildDep(dep) 374 return &bazel.Label{ 375 Label: ":" + dep + "__BP2BUILD__MISSING__DEP", 376 } 377 } 378 if !convertedToBazel(ctx, m) { 379 ctx.AddUnconvertedBp2buildDep(dep) 380 } 381 label := BazelModuleLabel(ctx, ctx.Module()) 382 otherLabel := labelFromModule(ctx, m) 383 384 // TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets. 385 386 if samePackage(label, otherLabel) { 387 otherLabel = bazelShortLabel(otherLabel) 388 } 389 390 return &bazel.Label{ 391 Label: otherLabel, 392 } 393} 394 395func BazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string { 396 // TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets. 397 if !convertedToBazel(ctx, module) { 398 return bp2buildModuleLabel(ctx, module) 399 } 400 b, _ := module.(Bazelable) 401 return b.GetBazelLabel(ctx, module) 402} 403 404func bazelShortLabel(label string) string { 405 i := strings.Index(label, ":") 406 if i == -1 { 407 panic(fmt.Errorf("Could not find the ':' character in '%s', expected a fully qualified label.", label)) 408 } 409 return label[i:] 410} 411 412func bazelPackage(label string) string { 413 i := strings.Index(label, ":") 414 if i == -1 { 415 panic(fmt.Errorf("Could not find the ':' character in '%s', expected a fully qualified label.", label)) 416 } 417 return label[0:i] 418} 419 420func samePackage(label1, label2 string) bool { 421 return bazelPackage(label1) == bazelPackage(label2) 422} 423 424func bp2buildModuleLabel(ctx BazelConversionContext, module blueprint.Module) string { 425 moduleName := ctx.OtherModuleName(module) 426 moduleDir := ctx.OtherModuleDir(module) 427 return fmt.Sprintf("//%s:%s", moduleDir, moduleName) 428} 429 430// BazelOutPath is a Bazel output path compatible to be used for mixed builds within Soong/Ninja. 431type BazelOutPath struct { 432 OutputPath 433} 434 435// ensure BazelOutPath implements Path 436var _ Path = BazelOutPath{} 437 438// ensure BazelOutPath implements genPathProvider 439var _ genPathProvider = BazelOutPath{} 440 441// ensure BazelOutPath implements objPathProvider 442var _ objPathProvider = BazelOutPath{} 443 444func (p BazelOutPath) genPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleGenPath { 445 return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext)) 446} 447 448func (p BazelOutPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath { 449 return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext)) 450} 451 452// PathForBazelOut returns a Path representing the paths... under an output directory dedicated to 453// bazel-owned outputs. 454func PathForBazelOut(ctx PathContext, paths ...string) BazelOutPath { 455 execRootPathComponents := append([]string{"execroot", "__main__"}, paths...) 456 execRootPath := filepath.Join(execRootPathComponents...) 457 validatedExecRootPath, err := validatePath(execRootPath) 458 if err != nil { 459 reportPathError(ctx, err) 460 } 461 462 outputPath := OutputPath{basePath{"", ""}, 463 ctx.Config().soongOutDir, 464 ctx.Config().BazelContext.OutputBase()} 465 466 return BazelOutPath{ 467 OutputPath: outputPath.withRel(validatedExecRootPath), 468 } 469} 470 471// PathsForBazelOut returns a list of paths representing the paths under an output directory 472// dedicated to Bazel-owned outputs. 473func PathsForBazelOut(ctx PathContext, paths []string) Paths { 474 outs := make(Paths, 0, len(paths)) 475 for _, p := range paths { 476 outs = append(outs, PathForBazelOut(ctx, p)) 477 } 478 return outs 479} 480