blob: ccbc156bc81a08401dbdb219e710b147f9240a08 [file] [log] [blame]
Liz Kammer620dea62021-04-14 17:36:10 -04001// 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)
Chris Parsons5a34ffb2021-07-21 14:34:58 -040076 ModuleFromName(name string) (blueprint.Module, bool)
Liz Kammer620dea62021-04-14 17:36:10 -040077 Module() Module
Liz Kammer6eff3232021-08-26 08:37:59 -040078 OtherModuleType(m blueprint.Module) string
Liz Kammer620dea62021-04-14 17:36:10 -040079 OtherModuleName(m blueprint.Module) string
80 OtherModuleDir(m blueprint.Module) string
Liz Kammer6eff3232021-08-26 08:37:59 -040081 AddUnconvertedBp2buildDep(string)
Liz Kammer620dea62021-04-14 17:36:10 -040082}
83
84// BazelLabelForModuleDeps expects a list of reference to other modules, ("<module>"
85// or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the
86// module within the given ctx.
87func BazelLabelForModuleDeps(ctx BazelConversionPathContext, modules []string) bazel.LabelList {
Liz Kammer2d7bbe32021-06-10 18:20:06 -040088 return bazelLabelForModuleDeps(ctx, modules, false)
89}
90
91// BazelLabelForModuleWholeDeps expects a list of references to other modules, ("<module>"
92// or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the
93// module within the given ctx, where prebuilt dependencies will be appended with _alwayslink so
94// they can be handled as whole static libraries.
95func BazelLabelForModuleWholeDeps(ctx BazelConversionPathContext, modules []string) bazel.LabelList {
96 return bazelLabelForModuleDeps(ctx, modules, true)
97}
98
99// BazelLabelForModuleDepsExcludes expects two lists: modules (containing modules to include in the
100// list), and excludes (modules to exclude from the list). Both of these should contain references
101// to other modules, ("<module>" or ":<module>"). It returns a Bazel-compatible label list which
102// corresponds to dependencies on the module within the given ctx, and the excluded dependencies.
103func BazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
104 return bazelLabelForModuleDepsExcludes(ctx, modules, excludes, false)
105}
106
107// BazelLabelForModuleWholeDepsExcludes expects two lists: modules (containing modules to include in
108// the list), and excludes (modules to exclude from the list). Both of these should contain
109// references to other modules, ("<module>" or ":<module>"). It returns a Bazel-compatible label
110// list which corresponds to dependencies on the module within the given ctx, and the excluded
111// dependencies. Prebuilt dependencies will be appended with _alwayslink so they can be handled as
112// whole static libraries.
113func BazelLabelForModuleWholeDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
114 return bazelLabelForModuleDepsExcludes(ctx, modules, excludes, true)
115}
116
117func bazelLabelForModuleDeps(ctx BazelConversionPathContext, modules []string, isWholeLibs bool) bazel.LabelList {
Liz Kammer620dea62021-04-14 17:36:10 -0400118 var labels bazel.LabelList
Chris Parsons51f8c392021-08-03 21:01:05 -0400119 // In some cases, a nil string list is different than an explicitly empty list.
120 if len(modules) == 0 && modules != nil {
121 labels.Includes = []bazel.Label{}
122 return labels
123 }
Liz Kammer620dea62021-04-14 17:36:10 -0400124 for _, module := range modules {
125 bpText := module
126 if m := SrcIsModule(module); m == "" {
127 module = ":" + module
128 }
129 if m, t := SrcIsModuleWithTag(module); m != "" {
Liz Kammer2d7bbe32021-06-10 18:20:06 -0400130 l := getOtherModuleLabel(ctx, m, t, isWholeLibs)
Jingwen Chen38e62642021-04-19 05:00:15 +0000131 l.OriginalModuleName = bpText
Liz Kammer620dea62021-04-14 17:36:10 -0400132 labels.Includes = append(labels.Includes, l)
133 } else {
134 ctx.ModuleErrorf("%q, is not a module reference", module)
135 }
136 }
137 return labels
138}
139
Liz Kammer2d7bbe32021-06-10 18:20:06 -0400140func bazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string, isWholeLibs bool) bazel.LabelList {
141 moduleLabels := bazelLabelForModuleDeps(ctx, RemoveListFromList(modules, excludes), isWholeLibs)
Liz Kammer47535c52021-06-02 16:02:22 -0400142 if len(excludes) == 0 {
143 return moduleLabels
144 }
Liz Kammer2d7bbe32021-06-10 18:20:06 -0400145 excludeLabels := bazelLabelForModuleDeps(ctx, excludes, isWholeLibs)
Liz Kammer47535c52021-06-02 16:02:22 -0400146 return bazel.LabelList{
147 Includes: moduleLabels.Includes,
148 Excludes: excludeLabels.Includes,
149 }
150}
151
Lukacs T. Berki1353e592021-04-30 15:35:09 +0200152func BazelLabelForModuleSrcSingle(ctx BazelConversionPathContext, path string) bazel.Label {
153 return BazelLabelForModuleSrcExcludes(ctx, []string{path}, []string(nil)).Includes[0]
154}
155
Rupert Shuttleworth6e4950a2021-07-27 01:34:59 -0400156func BazelLabelForModuleDepSingle(ctx BazelConversionPathContext, path string) bazel.Label {
157 return BazelLabelForModuleDepsExcludes(ctx, []string{path}, []string(nil)).Includes[0]
158}
159
Liz Kammer620dea62021-04-14 17:36:10 -0400160// BazelLabelForModuleSrc expects a list of path (relative to local module directory) and module
161// references (":<module>") and returns a bazel.LabelList{} containing the resolved references in
162// paths, relative to the local module, or Bazel-labels (absolute if in a different package or
163// relative if within the same package).
164// Properties must have been annotated with struct tag `android:"path"` so that dependencies modules
165// will have already been handled by the path_deps mutator.
166func BazelLabelForModuleSrc(ctx BazelConversionPathContext, paths []string) bazel.LabelList {
167 return BazelLabelForModuleSrcExcludes(ctx, paths, []string(nil))
168}
169
170// BazelLabelForModuleSrc expects lists of path and excludes (relative to local module directory)
171// and module references (":<module>") and returns a bazel.LabelList{} containing the resolved
172// references in paths, minus those in excludes, relative to the local module, or Bazel-labels
173// (absolute if in a different package or relative if within the same package).
174// Properties must have been annotated with struct tag `android:"path"` so that dependencies modules
175// will have already been handled by the path_deps mutator.
176func BazelLabelForModuleSrcExcludes(ctx BazelConversionPathContext, paths, excludes []string) bazel.LabelList {
177 excludeLabels := expandSrcsForBazel(ctx, excludes, []string(nil))
178 excluded := make([]string, 0, len(excludeLabels.Includes))
179 for _, e := range excludeLabels.Includes {
180 excluded = append(excluded, e.Label)
181 }
182 labels := expandSrcsForBazel(ctx, paths, excluded)
183 labels.Excludes = excludeLabels.Includes
184 labels = transformSubpackagePaths(ctx, labels)
185 return labels
186}
187
188// Returns true if a prefix + components[:i] + /Android.bp exists
189// TODO(b/185358476) Could check for BUILD file instead of checking for Android.bp file, or ensure BUILD is always generated?
190func directoryHasBlueprint(fs pathtools.FileSystem, prefix string, components []string, componentIndex int) bool {
191 blueprintPath := prefix
192 if blueprintPath != "" {
193 blueprintPath = blueprintPath + "/"
194 }
195 blueprintPath = blueprintPath + strings.Join(components[:componentIndex+1], "/")
196 blueprintPath = blueprintPath + "/Android.bp"
197 if exists, _, _ := fs.Exists(blueprintPath); exists {
198 return true
199 } else {
200 return false
201 }
202}
203
204// Transform a path (if necessary) to acknowledge package boundaries
205//
206// e.g. something like
207// async_safe/include/async_safe/CHECK.h
208// might become
209// //bionic/libc/async_safe:include/async_safe/CHECK.h
210// if the "async_safe" directory is actually a package and not just a directory.
211//
212// In particular, paths that extend into packages are transformed into absolute labels beginning with //.
213func transformSubpackagePath(ctx BazelConversionPathContext, path bazel.Label) bazel.Label {
214 var newPath bazel.Label
215
Jingwen Chen38e62642021-04-19 05:00:15 +0000216 // Don't transform OriginalModuleName
217 newPath.OriginalModuleName = path.OriginalModuleName
Liz Kammer620dea62021-04-14 17:36:10 -0400218
219 if strings.HasPrefix(path.Label, "//") {
220 // Assume absolute labels are already correct (e.g. //path/to/some/package:foo.h)
221 newPath.Label = path.Label
222 return newPath
223 }
224
225 newLabel := ""
226 pathComponents := strings.Split(path.Label, "/")
227 foundBlueprint := false
228 // Check the deepest subdirectory first and work upwards
229 for i := len(pathComponents) - 1; i >= 0; i-- {
230 pathComponent := pathComponents[i]
231 var sep string
232 if !foundBlueprint && directoryHasBlueprint(ctx.Config().fs, ctx.ModuleDir(), pathComponents, i) {
233 sep = ":"
234 foundBlueprint = true
235 } else {
236 sep = "/"
237 }
238 if newLabel == "" {
239 newLabel = pathComponent
240 } else {
241 newLabel = pathComponent + sep + newLabel
242 }
243 }
244 if foundBlueprint {
245 // Ensure paths end up looking like //bionic/... instead of //./bionic/...
246 moduleDir := ctx.ModuleDir()
247 if strings.HasPrefix(moduleDir, ".") {
248 moduleDir = moduleDir[1:]
249 }
250 // Make the path into an absolute label (e.g. //bionic/libc/foo:bar.h instead of just foo:bar.h)
251 if moduleDir == "" {
252 newLabel = "//" + newLabel
253 } else {
254 newLabel = "//" + moduleDir + "/" + newLabel
255 }
256 }
257 newPath.Label = newLabel
258
259 return newPath
260}
261
262// Transform paths to acknowledge package boundaries
263// See transformSubpackagePath() for more information
264func transformSubpackagePaths(ctx BazelConversionPathContext, paths bazel.LabelList) bazel.LabelList {
265 var newPaths bazel.LabelList
266 for _, include := range paths.Includes {
267 newPaths.Includes = append(newPaths.Includes, transformSubpackagePath(ctx, include))
268 }
269 for _, exclude := range paths.Excludes {
270 newPaths.Excludes = append(newPaths.Excludes, transformSubpackagePath(ctx, exclude))
271 }
272 return newPaths
273}
274
275// expandSrcsForBazel returns bazel.LabelList with paths rooted from the module's local source
276// directory and Bazel target labels, excluding those included in the excludes argument (which
277// should already be expanded to resolve references to Soong-modules). Valid elements of paths
278// include:
279// * filepath, relative to local module directory, resolves as a filepath relative to the local
280// source directory
281// * glob, relative to the local module directory, resolves as filepath(s), relative to the local
282// module directory. Because Soong does not have a concept of crossing package boundaries, the
283// glob as computed by Soong may contain paths that cross package-boundaries that would be
284// unknowingly omitted if the glob were handled by Bazel. To allow identification and detect
285// (within Bazel) use of paths that cross package boundaries, we expand globs within Soong rather
286// than converting Soong glob syntax to Bazel glob syntax. **Invalid for excludes.**
287// * other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
288// or OutputFileProducer. These resolve as a Bazel label for a target. If the Bazel target is in
289// the local module directory, it will be returned relative to the current package (e.g.
290// ":<target>"). Otherwise, it will be returned as an absolute Bazel label (e.g.
291// "//path/to/dir:<target>"). If the reference to another module cannot be resolved,the function
292// will panic.
293// Properties passed as the paths or excludes argument must have been annotated with struct tag
294// `android:"path"` so that dependencies on other modules will have already been handled by the
295// path_deps mutator.
296func expandSrcsForBazel(ctx BazelConversionPathContext, paths, expandedExcludes []string) bazel.LabelList {
297 if paths == nil {
298 return bazel.LabelList{}
299 }
300 labels := bazel.LabelList{
301 Includes: []bazel.Label{},
302 }
Jingwen Chen4ecc67d2021-04-27 09:47:02 +0000303
304 // expandedExcludes contain module-dir relative paths, but root-relative paths
305 // are needed for GlobFiles later.
306 var rootRelativeExpandedExcludes []string
307 for _, e := range expandedExcludes {
308 rootRelativeExpandedExcludes = append(rootRelativeExpandedExcludes, filepath.Join(ctx.ModuleDir(), e))
309 }
310
Liz Kammer620dea62021-04-14 17:36:10 -0400311 for _, p := range paths {
312 if m, tag := SrcIsModuleWithTag(p); m != "" {
Liz Kammer2d7bbe32021-06-10 18:20:06 -0400313 l := getOtherModuleLabel(ctx, m, tag, false)
Liz Kammer620dea62021-04-14 17:36:10 -0400314 if !InList(l.Label, expandedExcludes) {
Jingwen Chen38e62642021-04-19 05:00:15 +0000315 l.OriginalModuleName = fmt.Sprintf(":%s", m)
Liz Kammer620dea62021-04-14 17:36:10 -0400316 labels.Includes = append(labels.Includes, l)
317 }
318 } else {
319 var expandedPaths []bazel.Label
320 if pathtools.IsGlob(p) {
Jingwen Chen4ecc67d2021-04-27 09:47:02 +0000321 // e.g. turn "math/*.c" in
322 // external/arm-optimized-routines to external/arm-optimized-routines/math/*.c
323 rootRelativeGlobPath := pathForModuleSrc(ctx, p).String()
324 globbedPaths := GlobFiles(ctx, rootRelativeGlobPath, rootRelativeExpandedExcludes)
Liz Kammer620dea62021-04-14 17:36:10 -0400325 globbedPaths = PathsWithModuleSrcSubDir(ctx, globbedPaths, "")
326 for _, path := range globbedPaths {
327 s := path.Rel()
328 expandedPaths = append(expandedPaths, bazel.Label{Label: s})
329 }
330 } else {
331 if !InList(p, expandedExcludes) {
332 expandedPaths = append(expandedPaths, bazel.Label{Label: p})
333 }
334 }
335 labels.Includes = append(labels.Includes, expandedPaths...)
336 }
337 }
338 return labels
339}
340
341// getOtherModuleLabel returns a bazel.Label for the given dependency/tag combination for the
342// module. The label will be relative to the current directory if appropriate. The dependency must
343// already be resolved by either deps mutator or path deps mutator.
Liz Kammer2d7bbe32021-06-10 18:20:06 -0400344func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string, isWholeLibs bool) bazel.Label {
Chris Parsons5a34ffb2021-07-21 14:34:58 -0400345 m, _ := ctx.ModuleFromName(dep)
Liz Kammer620dea62021-04-14 17:36:10 -0400346 if m == nil {
Chris Parsons5a34ffb2021-07-21 14:34:58 -0400347 panic(fmt.Errorf("No module named %q found, but was a direct dep of %q", dep, ctx.Module().Name()))
Liz Kammer620dea62021-04-14 17:36:10 -0400348 }
Liz Kammer6eff3232021-08-26 08:37:59 -0400349 if !convertedToBazel(ctx, m) {
350 ctx.AddUnconvertedBp2buildDep(dep)
351 }
Liz Kammer620dea62021-04-14 17:36:10 -0400352 otherLabel := bazelModuleLabel(ctx, m, tag)
353 label := bazelModuleLabel(ctx, ctx.Module(), "")
Liz Kammer2d7bbe32021-06-10 18:20:06 -0400354 if isWholeLibs {
355 if m, ok := m.(Module); ok && IsModulePrebuilt(m) {
356 otherLabel += "_alwayslink"
357 }
358 }
Liz Kammer620dea62021-04-14 17:36:10 -0400359 if samePackage(label, otherLabel) {
360 otherLabel = bazelShortLabel(otherLabel)
361 }
362
363 return bazel.Label{
364 Label: otherLabel,
365 }
366}
367
368func bazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module, tag string) string {
369 // TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
Liz Kammer6eff3232021-08-26 08:37:59 -0400370 if !convertedToBazel(ctx, module) {
Liz Kammer620dea62021-04-14 17:36:10 -0400371 return bp2buildModuleLabel(ctx, module)
372 }
Liz Kammer6eff3232021-08-26 08:37:59 -0400373 b, _ := module.(Bazelable)
Liz Kammer620dea62021-04-14 17:36:10 -0400374 return b.GetBazelLabel(ctx, module)
375}
376
377func bazelShortLabel(label string) string {
378 i := strings.Index(label, ":")
379 return label[i:]
380}
381
382func bazelPackage(label string) string {
383 i := strings.Index(label, ":")
384 return label[0:i]
385}
386
387func samePackage(label1, label2 string) bool {
388 return bazelPackage(label1) == bazelPackage(label2)
389}
390
391func bp2buildModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
392 moduleName := ctx.OtherModuleName(module)
393 moduleDir := ctx.OtherModuleDir(module)
394 return fmt.Sprintf("//%s:%s", moduleDir, moduleName)
395}
396
397// BazelOutPath is a Bazel output path compatible to be used for mixed builds within Soong/Ninja.
398type BazelOutPath struct {
399 OutputPath
400}
401
402var _ Path = BazelOutPath{}
403var _ objPathProvider = BazelOutPath{}
404
405func (p BazelOutPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
406 return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
407}
408
409// PathForBazelOut returns a Path representing the paths... under an output directory dedicated to
410// bazel-owned outputs.
411func PathForBazelOut(ctx PathContext, paths ...string) BazelOutPath {
412 execRootPathComponents := append([]string{"execroot", "__main__"}, paths...)
413 execRootPath := filepath.Join(execRootPathComponents...)
414 validatedExecRootPath, err := validatePath(execRootPath)
415 if err != nil {
416 reportPathError(ctx, err)
417 }
418
419 outputPath := OutputPath{basePath{"", ""},
Lukacs T. Berki9f6c24a2021-08-26 15:07:24 +0200420 ctx.Config().soongOutDir,
Liz Kammer620dea62021-04-14 17:36:10 -0400421 ctx.Config().BazelContext.OutputBase()}
422
423 return BazelOutPath{
424 OutputPath: outputPath.withRel(validatedExecRootPath),
425 }
426}
Liz Kammerb6a55bf2021-04-12 15:42:51 -0400427
428// PathsForBazelOut returns a list of paths representing the paths under an output directory
429// dedicated to Bazel-owned outputs.
430func PathsForBazelOut(ctx PathContext, paths []string) Paths {
431 outs := make(Paths, 0, len(paths))
432 for _, p := range paths {
433 outs = append(outs, PathForBazelOut(ctx, p))
434 }
435 return outs
436}