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