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