• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2016 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	"path/filepath"
19	"regexp"
20	"strings"
21
22	"android/soong/bazel"
23	"android/soong/bazel/cquery"
24
25	"github.com/google/blueprint"
26)
27
28func init() {
29	RegisterFilegroupBuildComponents(InitRegistrationContext)
30}
31
32var PrepareForTestWithFilegroup = FixtureRegisterWithContext(func(ctx RegistrationContext) {
33	RegisterFilegroupBuildComponents(ctx)
34})
35
36func RegisterFilegroupBuildComponents(ctx RegistrationContext) {
37	ctx.RegisterModuleType("filegroup", FileGroupFactory)
38	ctx.RegisterModuleType("filegroup_defaults", FileGroupDefaultsFactory)
39}
40
41var convertedProtoLibrarySuffix = "_bp2build_converted"
42
43// IsFilegroup checks that a module is a filegroup type
44func IsFilegroup(ctx bazel.OtherModuleContext, m blueprint.Module) bool {
45	return ctx.OtherModuleType(m) == "filegroup"
46}
47
48var (
49	// ignoring case, checks for proto or protos as an independent word in the name, whether at the
50	// beginning, end, or middle. e.g. "proto.foo", "bar-protos", "baz_proto_srcs" would all match
51	filegroupLikelyProtoPattern = regexp.MustCompile("(?i)(^|[^a-z])proto(s)?([^a-z]|$)")
52	filegroupLikelyAidlPattern  = regexp.MustCompile("(?i)(^|[^a-z])aidl([^a-z]|$)")
53
54	ProtoSrcLabelPartition = bazel.LabelPartition{
55		Extensions:  []string{".proto"},
56		LabelMapper: isFilegroupWithPattern(filegroupLikelyProtoPattern),
57	}
58	AidlSrcLabelPartition = bazel.LabelPartition{
59		Extensions:  []string{".aidl"},
60		LabelMapper: isFilegroupWithPattern(filegroupLikelyAidlPattern),
61	}
62)
63
64func isFilegroupWithPattern(pattern *regexp.Regexp) bazel.LabelMapper {
65	return func(ctx bazel.OtherModuleContext, label bazel.Label) (string, bool) {
66		m, exists := ctx.ModuleFromName(label.OriginalModuleName)
67		labelStr := label.Label
68		if !exists || !IsFilegroup(ctx, m) {
69			return labelStr, false
70		}
71		likelyMatched := pattern.MatchString(label.OriginalModuleName)
72		return labelStr, likelyMatched
73	}
74}
75
76// https://docs.bazel.build/versions/master/be/general.html#filegroup
77type bazelFilegroupAttributes struct {
78	Srcs                bazel.LabelListAttribute
79	Applicable_licenses bazel.LabelListAttribute
80}
81
82type bazelAidlLibraryAttributes struct {
83	Srcs                bazel.LabelListAttribute
84	Strip_import_prefix *string
85	Deps                bazel.LabelListAttribute
86}
87
88// api srcs can be contained in filegroups.
89// this should be generated in api_bp2build workspace as well.
90func (fg *fileGroup) ConvertWithApiBp2build(ctx TopDownMutatorContext) {
91	fg.ConvertWithBp2build(ctx)
92}
93
94// ConvertWithBp2build performs bp2build conversion of filegroup
95func (fg *fileGroup) ConvertWithBp2build(ctx TopDownMutatorContext) {
96	srcs := bazel.MakeLabelListAttribute(
97		BazelLabelForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs))
98
99	// For Bazel compatibility, don't generate the filegroup if there is only 1
100	// source file, and that the source file is named the same as the module
101	// itself. In Bazel, eponymous filegroups like this would be an error.
102	//
103	// Instead, dependents on this single-file filegroup can just depend
104	// on the file target, instead of rule target, directly.
105	//
106	// You may ask: what if a filegroup has multiple files, and one of them
107	// shares the name? The answer: we haven't seen that in the wild, and
108	// should lock Soong itself down to prevent the behavior. For now,
109	// we raise an error if bp2build sees this problem.
110	for _, f := range srcs.Value.Includes {
111		if f.Label == fg.Name() {
112			if len(srcs.Value.Includes) > 1 {
113				ctx.ModuleErrorf("filegroup '%s' cannot contain a file with the same name", fg.Name())
114			}
115			return
116		}
117	}
118
119	// Convert module that has only AIDL files to aidl_library
120	// If the module has a mixed bag of AIDL and non-AIDL files, split the filegroup manually
121	// and then convert
122	if fg.ShouldConvertToAidlLibrary(ctx) {
123		tags := []string{"apex_available=//apex_available:anyapex"}
124		deps := bazel.MakeLabelListAttribute(BazelLabelForModuleDeps(ctx, fg.properties.Aidl.Deps))
125
126		attrs := &bazelAidlLibraryAttributes{
127			Srcs:                srcs,
128			Strip_import_prefix: fg.properties.Path,
129			Deps:                deps,
130		}
131
132		props := bazel.BazelTargetModuleProperties{
133			Rule_class:        "aidl_library",
134			Bzl_load_location: "//build/bazel/rules/aidl:aidl_library.bzl",
135		}
136
137		ctx.CreateBazelTargetModule(
138			props,
139			CommonAttributes{
140				Name: fg.Name(),
141				Tags: bazel.MakeStringListAttribute(tags),
142			},
143			attrs)
144	} else {
145		if fg.ShouldConvertToProtoLibrary(ctx) {
146			attrs := &ProtoAttrs{
147				Srcs:                srcs,
148				Strip_import_prefix: fg.properties.Path,
149			}
150
151			tags := []string{
152				"apex_available=//apex_available:anyapex",
153				// TODO(b/246997908): we can remove this tag if we could figure out a solution for this bug.
154				"manual",
155			}
156			ctx.CreateBazelTargetModule(
157				bazel.BazelTargetModuleProperties{Rule_class: "proto_library"},
158				CommonAttributes{
159					Name: fg.Name() + convertedProtoLibrarySuffix,
160					Tags: bazel.MakeStringListAttribute(tags),
161				},
162				attrs)
163		}
164
165		// TODO(b/242847534): Still convert to a filegroup because other unconverted
166		// modules may depend on the filegroup
167		attrs := &bazelFilegroupAttributes{
168			Srcs: srcs,
169		}
170
171		props := bazel.BazelTargetModuleProperties{
172			Rule_class:        "filegroup",
173			Bzl_load_location: "//build/bazel/rules:filegroup.bzl",
174		}
175
176		ctx.CreateBazelTargetModule(props, CommonAttributes{Name: fg.Name()}, attrs)
177	}
178}
179
180type fileGroupProperties struct {
181	// srcs lists files that will be included in this filegroup
182	Srcs []string `android:"path"`
183
184	Exclude_srcs []string `android:"path"`
185
186	// The base path to the files.  May be used by other modules to determine which portion
187	// of the path to use.  For example, when a filegroup is used as data in a cc_test rule,
188	// the base path is stripped off the path and the remaining path is used as the
189	// installation directory.
190	Path *string
191
192	// Create a make variable with the specified name that contains the list of files in the
193	// filegroup, relative to the root of the source tree.
194	Export_to_make_var *string
195
196	// aidl is explicitly provided for implicit aidl dependencies
197	// TODO(b/278298615): aidl prop is a no-op in Soong and is an escape hatch
198	// to include implicit aidl dependencies for bazel migration compatibility
199	Aidl struct {
200		// List of aidl files or filegroup depended on by srcs
201		Deps []string `android:"path"`
202	}
203}
204
205type fileGroup struct {
206	ModuleBase
207	BazelModuleBase
208	DefaultableModuleBase
209	FileGroupAsLibrary
210	properties fileGroupProperties
211	srcs       Paths
212}
213
214var _ MixedBuildBuildable = (*fileGroup)(nil)
215var _ SourceFileProducer = (*fileGroup)(nil)
216var _ FileGroupAsLibrary = (*fileGroup)(nil)
217
218// filegroup contains a list of files that are referenced by other modules
219// properties (such as "srcs") using the syntax ":<name>". filegroup are
220// also be used to export files across package boundaries.
221func FileGroupFactory() Module {
222	module := &fileGroup{}
223	module.AddProperties(&module.properties)
224	InitAndroidModule(module)
225	InitBazelModule(module)
226	InitDefaultableModule(module)
227	return module
228}
229
230var _ blueprint.JSONActionSupplier = (*fileGroup)(nil)
231
232func (fg *fileGroup) JSONActions() []blueprint.JSONAction {
233	ins := make([]string, 0, len(fg.srcs))
234	outs := make([]string, 0, len(fg.srcs))
235	for _, p := range fg.srcs {
236		ins = append(ins, p.String())
237		outs = append(outs, p.Rel())
238	}
239	return []blueprint.JSONAction{
240		blueprint.JSONAction{
241			Inputs:  ins,
242			Outputs: outs,
243		},
244	}
245}
246
247func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
248	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
249	if fg.properties.Path != nil {
250		fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
251	}
252}
253
254func (fg *fileGroup) Srcs() Paths {
255	return append(Paths{}, fg.srcs...)
256}
257
258func (fg *fileGroup) MakeVars(ctx MakeVarsModuleContext) {
259	if makeVar := String(fg.properties.Export_to_make_var); makeVar != "" {
260		ctx.StrictRaw(makeVar, strings.Join(fg.srcs.Strings(), " "))
261	}
262}
263
264func (fg *fileGroup) QueueBazelCall(ctx BaseModuleContext) {
265	bazelCtx := ctx.Config().BazelContext
266
267	bazelCtx.QueueBazelRequest(
268		fg.GetBazelLabel(ctx, fg),
269		cquery.GetOutputFiles,
270		configKey{arch: Common.String(), osType: CommonOS})
271}
272
273func (fg *fileGroup) IsMixedBuildSupported(ctx BaseModuleContext) bool {
274	// TODO(b/247782695), TODO(b/242847534) Fix mixed builds for filegroups
275	return false
276}
277
278func (fg *fileGroup) ProcessBazelQueryResponse(ctx ModuleContext) {
279	bazelCtx := ctx.Config().BazelContext
280	// This is a short-term solution because we rely on info from Android.bp to handle
281	// a converted module. This will block when we want to remove Android.bp for all
282	// converted modules at some point.
283	// TODO(b/242847534): Implement a long-term solution in which we don't need to rely
284	// on info form Android.bp for modules that are already converted to Bazel
285	relativeRoot := ctx.ModuleDir()
286	if fg.properties.Path != nil {
287		relativeRoot = filepath.Join(relativeRoot, *fg.properties.Path)
288	}
289
290	filePaths, err := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), configKey{arch: Common.String(), osType: CommonOS})
291	if err != nil {
292		ctx.ModuleErrorf(err.Error())
293		return
294	}
295
296	bazelOuts := make(Paths, 0, len(filePaths))
297	for _, p := range filePaths {
298		bazelOuts = append(bazelOuts, PathForBazelOutRelative(ctx, relativeRoot, p))
299	}
300	fg.srcs = bazelOuts
301}
302
303func (fg *fileGroup) ShouldConvertToAidlLibrary(ctx BazelConversionPathContext) bool {
304	return fg.shouldConvertToLibrary(ctx, ".aidl")
305}
306
307func (fg *fileGroup) ShouldConvertToProtoLibrary(ctx BazelConversionPathContext) bool {
308	return fg.shouldConvertToLibrary(ctx, ".proto")
309}
310
311func (fg *fileGroup) shouldConvertToLibrary(ctx BazelConversionPathContext, suffix string) bool {
312	if len(fg.properties.Srcs) == 0 || !fg.ShouldConvertWithBp2build(ctx) {
313		return false
314	}
315	for _, src := range fg.properties.Srcs {
316		if !strings.HasSuffix(src, suffix) {
317			return false
318		}
319	}
320	return true
321}
322
323func (fg *fileGroup) GetAidlLibraryLabel(ctx BazelConversionPathContext) string {
324	return fg.getFileGroupAsLibraryLabel(ctx)
325}
326
327func (fg *fileGroup) GetProtoLibraryLabel(ctx BazelConversionPathContext) string {
328	return fg.getFileGroupAsLibraryLabel(ctx) + convertedProtoLibrarySuffix
329}
330
331func (fg *fileGroup) getFileGroupAsLibraryLabel(ctx BazelConversionPathContext) string {
332	if ctx.OtherModuleDir(fg.module) == ctx.ModuleDir() {
333		return ":" + fg.Name()
334	} else {
335		return fg.GetBazelLabel(ctx, fg)
336	}
337}
338
339// Given a name in srcs prop, check to see if the name references a filegroup
340// and the filegroup is converted to aidl_library
341func IsConvertedToAidlLibrary(ctx BazelConversionPathContext, name string) bool {
342	if fg, ok := ToFileGroupAsLibrary(ctx, name); ok {
343		return fg.ShouldConvertToAidlLibrary(ctx)
344	}
345	return false
346}
347
348func ToFileGroupAsLibrary(ctx BazelConversionPathContext, name string) (FileGroupAsLibrary, bool) {
349	if module, ok := ctx.ModuleFromName(name); ok {
350		if IsFilegroup(ctx, module) {
351			if fg, ok := module.(FileGroupAsLibrary); ok {
352				return fg, true
353			}
354		}
355	}
356	return nil, false
357}
358
359// Defaults
360type FileGroupDefaults struct {
361	ModuleBase
362	DefaultsModuleBase
363}
364
365func FileGroupDefaultsFactory() Module {
366	module := &FileGroupDefaults{}
367	module.AddProperties(&fileGroupProperties{})
368	InitDefaultsModule(module)
369
370	return module
371}
372