• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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