blob: 63e2c50ed0fba4e7f5d0eb7700d799b66db980a4 [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)
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)
Jingwen Chen38e62642021-04-19 05:00:15 +000094 l.OriginalModuleName = bpText
Liz Kammer620dea62021-04-14 17:36:10 -040095 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
103// BazelLabelForModuleSrc expects a list of path (relative to local module directory) and module
104// references (":<module>") and returns a bazel.LabelList{} containing the resolved references in
105// paths, relative to the local module, or Bazel-labels (absolute if in a different package or
106// relative if within the same package).
107// Properties must have been annotated with struct tag `android:"path"` so that dependencies modules
108// will have already been handled by the path_deps mutator.
109func BazelLabelForModuleSrc(ctx BazelConversionPathContext, paths []string) bazel.LabelList {
110 return BazelLabelForModuleSrcExcludes(ctx, paths, []string(nil))
111}
112
113// BazelLabelForModuleSrc expects lists of path and excludes (relative to local module directory)
114// and module references (":<module>") and returns a bazel.LabelList{} containing the resolved
115// references in paths, minus those in excludes, relative to the local module, or Bazel-labels
116// (absolute if in a different package or relative if within the same package).
117// Properties must have been annotated with struct tag `android:"path"` so that dependencies modules
118// will have already been handled by the path_deps mutator.
119func BazelLabelForModuleSrcExcludes(ctx BazelConversionPathContext, paths, excludes []string) bazel.LabelList {
120 excludeLabels := expandSrcsForBazel(ctx, excludes, []string(nil))
121 excluded := make([]string, 0, len(excludeLabels.Includes))
122 for _, e := range excludeLabels.Includes {
123 excluded = append(excluded, e.Label)
124 }
125 labels := expandSrcsForBazel(ctx, paths, excluded)
126 labels.Excludes = excludeLabels.Includes
127 labels = transformSubpackagePaths(ctx, labels)
128 return labels
129}
130
131// Returns true if a prefix + components[:i] + /Android.bp exists
132// TODO(b/185358476) Could check for BUILD file instead of checking for Android.bp file, or ensure BUILD is always generated?
133func directoryHasBlueprint(fs pathtools.FileSystem, prefix string, components []string, componentIndex int) bool {
134 blueprintPath := prefix
135 if blueprintPath != "" {
136 blueprintPath = blueprintPath + "/"
137 }
138 blueprintPath = blueprintPath + strings.Join(components[:componentIndex+1], "/")
139 blueprintPath = blueprintPath + "/Android.bp"
140 if exists, _, _ := fs.Exists(blueprintPath); exists {
141 return true
142 } else {
143 return false
144 }
145}
146
147// Transform a path (if necessary) to acknowledge package boundaries
148//
149// e.g. something like
150// async_safe/include/async_safe/CHECK.h
151// might become
152// //bionic/libc/async_safe:include/async_safe/CHECK.h
153// if the "async_safe" directory is actually a package and not just a directory.
154//
155// In particular, paths that extend into packages are transformed into absolute labels beginning with //.
156func transformSubpackagePath(ctx BazelConversionPathContext, path bazel.Label) bazel.Label {
157 var newPath bazel.Label
158
Jingwen Chen38e62642021-04-19 05:00:15 +0000159 // Don't transform OriginalModuleName
160 newPath.OriginalModuleName = path.OriginalModuleName
Liz Kammer620dea62021-04-14 17:36:10 -0400161
162 if strings.HasPrefix(path.Label, "//") {
163 // Assume absolute labels are already correct (e.g. //path/to/some/package:foo.h)
164 newPath.Label = path.Label
165 return newPath
166 }
167
168 newLabel := ""
169 pathComponents := strings.Split(path.Label, "/")
170 foundBlueprint := false
171 // Check the deepest subdirectory first and work upwards
172 for i := len(pathComponents) - 1; i >= 0; i-- {
173 pathComponent := pathComponents[i]
174 var sep string
175 if !foundBlueprint && directoryHasBlueprint(ctx.Config().fs, ctx.ModuleDir(), pathComponents, i) {
176 sep = ":"
177 foundBlueprint = true
178 } else {
179 sep = "/"
180 }
181 if newLabel == "" {
182 newLabel = pathComponent
183 } else {
184 newLabel = pathComponent + sep + newLabel
185 }
186 }
187 if foundBlueprint {
188 // Ensure paths end up looking like //bionic/... instead of //./bionic/...
189 moduleDir := ctx.ModuleDir()
190 if strings.HasPrefix(moduleDir, ".") {
191 moduleDir = moduleDir[1:]
192 }
193 // Make the path into an absolute label (e.g. //bionic/libc/foo:bar.h instead of just foo:bar.h)
194 if moduleDir == "" {
195 newLabel = "//" + newLabel
196 } else {
197 newLabel = "//" + moduleDir + "/" + newLabel
198 }
199 }
200 newPath.Label = newLabel
201
202 return newPath
203}
204
205// Transform paths to acknowledge package boundaries
206// See transformSubpackagePath() for more information
207func transformSubpackagePaths(ctx BazelConversionPathContext, paths bazel.LabelList) bazel.LabelList {
208 var newPaths bazel.LabelList
209 for _, include := range paths.Includes {
210 newPaths.Includes = append(newPaths.Includes, transformSubpackagePath(ctx, include))
211 }
212 for _, exclude := range paths.Excludes {
213 newPaths.Excludes = append(newPaths.Excludes, transformSubpackagePath(ctx, exclude))
214 }
215 return newPaths
216}
217
218// expandSrcsForBazel returns bazel.LabelList with paths rooted from the module's local source
219// directory and Bazel target labels, excluding those included in the excludes argument (which
220// should already be expanded to resolve references to Soong-modules). Valid elements of paths
221// include:
222// * filepath, relative to local module directory, resolves as a filepath relative to the local
223// source directory
224// * glob, relative to the local module directory, resolves as filepath(s), relative to the local
225// module directory. Because Soong does not have a concept of crossing package boundaries, the
226// glob as computed by Soong may contain paths that cross package-boundaries that would be
227// unknowingly omitted if the glob were handled by Bazel. To allow identification and detect
228// (within Bazel) use of paths that cross package boundaries, we expand globs within Soong rather
229// than converting Soong glob syntax to Bazel glob syntax. **Invalid for excludes.**
230// * other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
231// or OutputFileProducer. These resolve as a Bazel label for a target. If the Bazel target is in
232// the local module directory, it will be returned relative to the current package (e.g.
233// ":<target>"). Otherwise, it will be returned as an absolute Bazel label (e.g.
234// "//path/to/dir:<target>"). If the reference to another module cannot be resolved,the function
235// will panic.
236// Properties passed as the paths or excludes argument must have been annotated with struct tag
237// `android:"path"` so that dependencies on other modules will have already been handled by the
238// path_deps mutator.
239func expandSrcsForBazel(ctx BazelConversionPathContext, paths, expandedExcludes []string) bazel.LabelList {
240 if paths == nil {
241 return bazel.LabelList{}
242 }
243 labels := bazel.LabelList{
244 Includes: []bazel.Label{},
245 }
246 for _, p := range paths {
247 if m, tag := SrcIsModuleWithTag(p); m != "" {
248 l := getOtherModuleLabel(ctx, m, tag)
249 if !InList(l.Label, expandedExcludes) {
Jingwen Chen38e62642021-04-19 05:00:15 +0000250 l.OriginalModuleName = fmt.Sprintf(":%s", m)
Liz Kammer620dea62021-04-14 17:36:10 -0400251 labels.Includes = append(labels.Includes, l)
252 }
253 } else {
254 var expandedPaths []bazel.Label
255 if pathtools.IsGlob(p) {
256 globbedPaths := GlobFiles(ctx, pathForModuleSrc(ctx, p).String(), expandedExcludes)
257 globbedPaths = PathsWithModuleSrcSubDir(ctx, globbedPaths, "")
258 for _, path := range globbedPaths {
259 s := path.Rel()
260 expandedPaths = append(expandedPaths, bazel.Label{Label: s})
261 }
262 } else {
263 if !InList(p, expandedExcludes) {
264 expandedPaths = append(expandedPaths, bazel.Label{Label: p})
265 }
266 }
267 labels.Includes = append(labels.Includes, expandedPaths...)
268 }
269 }
270 return labels
271}
272
273// getOtherModuleLabel returns a bazel.Label for the given dependency/tag combination for the
274// module. The label will be relative to the current directory if appropriate. The dependency must
275// already be resolved by either deps mutator or path deps mutator.
276func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string) bazel.Label {
277 m, _ := ctx.GetDirectDep(dep)
278 if m == nil {
279 panic(fmt.Errorf(`Cannot get direct dep %q of %q.
280 This is likely because it was not added via AddDependency().
281 This may be due a mutator skipped during bp2build.`, dep, ctx.Module().Name()))
282 }
283 otherLabel := bazelModuleLabel(ctx, m, tag)
284 label := bazelModuleLabel(ctx, ctx.Module(), "")
285 if samePackage(label, otherLabel) {
286 otherLabel = bazelShortLabel(otherLabel)
287 }
288
289 return bazel.Label{
290 Label: otherLabel,
291 }
292}
293
294func bazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module, tag string) string {
295 // TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
296 b, ok := module.(Bazelable)
297 // TODO(b/181155349): perhaps return an error here if the module can't be/isn't being converted
298 if !ok || !b.ConvertedToBazel(ctx) {
299 return bp2buildModuleLabel(ctx, module)
300 }
301 return b.GetBazelLabel(ctx, module)
302}
303
304func bazelShortLabel(label string) string {
305 i := strings.Index(label, ":")
306 return label[i:]
307}
308
309func bazelPackage(label string) string {
310 i := strings.Index(label, ":")
311 return label[0:i]
312}
313
314func samePackage(label1, label2 string) bool {
315 return bazelPackage(label1) == bazelPackage(label2)
316}
317
318func bp2buildModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
319 moduleName := ctx.OtherModuleName(module)
320 moduleDir := ctx.OtherModuleDir(module)
321 return fmt.Sprintf("//%s:%s", moduleDir, moduleName)
322}
323
324// BazelOutPath is a Bazel output path compatible to be used for mixed builds within Soong/Ninja.
325type BazelOutPath struct {
326 OutputPath
327}
328
329var _ Path = BazelOutPath{}
330var _ objPathProvider = BazelOutPath{}
331
332func (p BazelOutPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
333 return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
334}
335
336// PathForBazelOut returns a Path representing the paths... under an output directory dedicated to
337// bazel-owned outputs.
338func PathForBazelOut(ctx PathContext, paths ...string) BazelOutPath {
339 execRootPathComponents := append([]string{"execroot", "__main__"}, paths...)
340 execRootPath := filepath.Join(execRootPathComponents...)
341 validatedExecRootPath, err := validatePath(execRootPath)
342 if err != nil {
343 reportPathError(ctx, err)
344 }
345
346 outputPath := OutputPath{basePath{"", ""},
347 ctx.Config().buildDir,
348 ctx.Config().BazelContext.OutputBase()}
349
350 return BazelOutPath{
351 OutputPath: outputPath.withRel(validatedExecRootPath),
352 }
353}
Liz Kammerb6a55bf2021-04-12 15:42:51 -0400354
355// PathsForBazelOut returns a list of paths representing the paths under an output directory
356// dedicated to Bazel-owned outputs.
357func PathsForBazelOut(ctx PathContext, paths []string) Paths {
358 outs := make(Paths, 0, len(paths))
359 for _, p := range paths {
360 outs = append(outs, PathForBazelOut(ctx, p))
361 }
362 return outs
363}