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 15// A genrule module takes a list of source files ("srcs" property), an optional 16// list of tools ("tools" property), and a command line ("cmd" property), to 17// generate output files ("out" property). 18 19package genrule 20 21import ( 22 "fmt" 23 "io" 24 "strconv" 25 "strings" 26 27 "github.com/google/blueprint" 28 "github.com/google/blueprint/bootstrap" 29 "github.com/google/blueprint/proptools" 30 31 "android/soong/android" 32) 33 34func init() { 35 RegisterGenruleBuildComponents(android.InitRegistrationContext) 36} 37 38// Test fixture preparer that will register most genrule build components. 39// 40// Singletons and mutators should only be added here if they are needed for a majority of genrule 41// module types, otherwise they should be added under a separate preparer to allow them to be 42// selected only when needed to reduce test execution time. 43// 44// Module types do not have much of an overhead unless they are used so this should include as many 45// module types as possible. The exceptions are those module types that require mutators and/or 46// singletons in order to function in which case they should be kept together in a separate 47// preparer. 48var PrepareForTestWithGenRuleBuildComponents = android.GroupFixturePreparers( 49 android.FixtureRegisterWithContext(RegisterGenruleBuildComponents), 50) 51 52// Prepare a fixture to use all genrule module types, mutators and singletons fully. 53// 54// This should only be used by tests that want to run with as much of the build enabled as possible. 55var PrepareForIntegrationTestWithGenrule = android.GroupFixturePreparers( 56 PrepareForTestWithGenRuleBuildComponents, 57) 58 59func RegisterGenruleBuildComponents(ctx android.RegistrationContext) { 60 ctx.RegisterModuleType("genrule_defaults", defaultsFactory) 61 62 ctx.RegisterModuleType("gensrcs", GenSrcsFactory) 63 ctx.RegisterModuleType("genrule", GenRuleFactory) 64 65 ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) { 66 ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel() 67 }) 68} 69 70var ( 71 pctx = android.NewPackageContext("android/soong/genrule") 72 73 // Used by gensrcs when there is more than 1 shard to merge the outputs 74 // of each shard into a zip file. 75 gensrcsMerge = pctx.AndroidStaticRule("gensrcsMerge", blueprint.RuleParams{ 76 Command: "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}", 77 CommandDeps: []string{"${soongZip}", "${zipSync}"}, 78 Rspfile: "${tmpZip}.rsp", 79 RspfileContent: "${zipArgs}", 80 }, "tmpZip", "genDir", "zipArgs") 81) 82 83func init() { 84 pctx.Import("android/soong/android") 85 86 pctx.HostBinToolVariable("soongZip", "soong_zip") 87 pctx.HostBinToolVariable("zipSync", "zipsync") 88} 89 90type SourceFileGenerator interface { 91 GeneratedSourceFiles() android.Paths 92 GeneratedHeaderDirs() android.Paths 93 GeneratedDeps() android.Paths 94} 95 96// Alias for android.HostToolProvider 97// Deprecated: use android.HostToolProvider instead. 98type HostToolProvider interface { 99 android.HostToolProvider 100} 101 102type hostToolDependencyTag struct { 103 blueprint.BaseDependencyTag 104 android.LicenseAnnotationToolchainDependencyTag 105 label string 106} 107 108func (t hostToolDependencyTag) AllowDisabledModuleDependency(target android.Module) bool { 109 // Allow depending on a disabled module if it's replaced by a prebuilt 110 // counterpart. We get the prebuilt through android.PrebuiltGetPreferred in 111 // GenerateAndroidBuildActions. 112 return target.IsReplacedByPrebuilt() 113} 114 115var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil) 116 117type generatorProperties struct { 118 // The command to run on one or more input files. Cmd supports substitution of a few variables. 119 // 120 // Available variables for substitution: 121 // 122 // $(location): the path to the first entry in tools or tool_files. 123 // $(location <label>): the path to the tool, tool_file, input or output with name <label>. Use $(location) if <label> refers to a rule that outputs exactly one file. 124 // $(locations <label>): the paths to the tools, tool_files, inputs or outputs with name <label>. Use $(locations) if <label> refers to a rule that outputs two or more files. 125 // $(in): one or more input files. 126 // $(out): a single output file. 127 // $(genDir): the sandbox directory for this tool; contains $(out). 128 // $$: a literal $ 129 Cmd proptools.Configurable[string] `android:"replace_instead_of_append"` 130 131 // name of the modules (if any) that produces the host executable. Leave empty for 132 // prebuilts or scripts that do not need a module to build them. 133 Tools []string 134 135 // Local files that are used by the tool 136 Tool_files []string `android:"path"` 137 138 // List of directories to export generated headers from 139 Export_include_dirs []string 140 141 // list of input files 142 Srcs []string `android:"path,arch_variant"` 143 144 // input files to exclude 145 Exclude_srcs []string `android:"path,arch_variant"` 146 147 // Enable restat to update the output only if the output is changed 148 Write_if_changed *bool 149} 150 151type Module struct { 152 android.ModuleBase 153 android.DefaultableModuleBase 154 android.ApexModuleBase 155 156 // For other packages to make their own genrules with extra 157 // properties 158 Extra interface{} 159 160 // CmdModifier can be set by wrappers around genrule to modify the command, for example to 161 // prefix environment variables to it. 162 CmdModifier func(ctx android.ModuleContext, cmd string) string 163 164 android.ImageInterface 165 166 properties generatorProperties 167 168 // For the different tasks that genrule and gensrc generate. genrule will 169 // generate 1 task, and gensrc will generate 1 or more tasks based on the 170 // number of shards the input files are sharded into. 171 taskGenerator taskFunc 172 173 rule blueprint.Rule 174 rawCommands []string 175 176 exportedIncludeDirs android.Paths 177 178 outputFiles android.Paths 179 outputDeps android.Paths 180 181 subName string 182 subDir string 183} 184 185type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask 186 187type generateTask struct { 188 in android.Paths 189 out android.WritablePaths 190 copyTo android.WritablePaths // For gensrcs to set on gensrcsMerge rule. 191 genDir android.WritablePath 192 extraInputs map[string][]string 193 194 cmd string 195 // For gensrsc sharding. 196 shard int 197 shards int 198} 199 200func (g *Module) GeneratedSourceFiles() android.Paths { 201 return g.outputFiles 202} 203 204func (g *Module) Srcs() android.Paths { 205 return append(android.Paths{}, g.outputFiles...) 206} 207 208func (g *Module) GeneratedHeaderDirs() android.Paths { 209 return g.exportedIncludeDirs 210} 211 212func (g *Module) GeneratedDeps() android.Paths { 213 return g.outputDeps 214} 215 216func (g *Module) OutputFiles(tag string) (android.Paths, error) { 217 if tag == "" { 218 return append(android.Paths{}, g.outputFiles...), nil 219 } 220 // otherwise, tag should match one of outputs 221 for _, outputFile := range g.outputFiles { 222 if outputFile.Rel() == tag { 223 return android.Paths{outputFile}, nil 224 } 225 } 226 return nil, fmt.Errorf("unsupported module reference tag %q", tag) 227} 228 229var _ android.SourceFileProducer = (*Module)(nil) 230var _ android.OutputFileProducer = (*Module)(nil) 231 232func toolDepsMutator(ctx android.BottomUpMutatorContext) { 233 if g, ok := ctx.Module().(*Module); ok { 234 for _, tool := range g.properties.Tools { 235 tag := hostToolDependencyTag{label: tool} 236 if m := android.SrcIsModule(tool); m != "" { 237 tool = m 238 } 239 ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), tag, tool) 240 } 241 } 242} 243 244// generateCommonBuildActions contains build action generation logic 245// common to both the mixed build case and the legacy case of genrule processing. 246// To fully support genrule in mixed builds, the contents of this function should 247// approach zero; there should be no genrule action registration done directly 248// by Soong logic in the mixed-build case. 249func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) { 250 g.subName = ctx.ModuleSubDir() 251 252 if len(g.properties.Export_include_dirs) > 0 { 253 for _, dir := range g.properties.Export_include_dirs { 254 g.exportedIncludeDirs = append(g.exportedIncludeDirs, 255 android.PathForModuleGen(ctx, g.subDir, ctx.ModuleDir(), dir)) 256 // Also export without ModuleDir for consistency with Export_include_dirs not being set 257 g.exportedIncludeDirs = append(g.exportedIncludeDirs, 258 android.PathForModuleGen(ctx, g.subDir, dir)) 259 } 260 } else { 261 g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, g.subDir)) 262 } 263 264 locationLabels := map[string]location{} 265 firstLabel := "" 266 267 addLocationLabel := func(label string, loc location) { 268 if firstLabel == "" { 269 firstLabel = label 270 } 271 if _, exists := locationLabels[label]; !exists { 272 locationLabels[label] = loc 273 } else { 274 ctx.ModuleErrorf("multiple locations for label %q: %q and %q (do you have duplicate srcs entries?)", 275 label, locationLabels[label], loc) 276 } 277 } 278 279 var tools android.Paths 280 var packagedTools []android.PackagingSpec 281 if len(g.properties.Tools) > 0 { 282 seenTools := make(map[string]bool) 283 284 ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) { 285 switch tag := ctx.OtherModuleDependencyTag(module).(type) { 286 case hostToolDependencyTag: 287 tool := ctx.OtherModuleName(module) 288 if m, ok := module.(android.Module); ok { 289 // Necessary to retrieve any prebuilt replacement for the tool, since 290 // toolDepsMutator runs too late for the prebuilt mutators to have 291 // replaced the dependency. 292 module = android.PrebuiltGetPreferred(ctx, m) 293 } 294 295 switch t := module.(type) { 296 case android.HostToolProvider: 297 // A HostToolProvider provides the path to a tool, which will be copied 298 // into the sandbox. 299 if !t.(android.Module).Enabled(ctx) { 300 if ctx.Config().AllowMissingDependencies() { 301 ctx.AddMissingDependencies([]string{tool}) 302 } else { 303 ctx.ModuleErrorf("depends on disabled module %q", tool) 304 } 305 return 306 } 307 path := t.HostToolPath() 308 if !path.Valid() { 309 ctx.ModuleErrorf("host tool %q missing output file", tool) 310 return 311 } 312 if specs := t.TransitivePackagingSpecs(); specs != nil { 313 // If the HostToolProvider has PackgingSpecs, which are definitions of the 314 // required relative locations of the tool and its dependencies, use those 315 // instead. They will be copied to those relative locations in the sbox 316 // sandbox. 317 // Care must be taken since TransitivePackagingSpec may return device-side 318 // paths via the required property. Filter them out. 319 for i, ps := range specs { 320 if ps.Partition() != "" { 321 if i == 0 { 322 panic("first PackagingSpec is assumed to be the host-side tool") 323 } 324 continue 325 } 326 packagedTools = append(packagedTools, ps) 327 } 328 // Assume that the first PackagingSpec of the module is the tool. 329 addLocationLabel(tag.label, packagedToolLocation{specs[0]}) 330 } else { 331 tools = append(tools, path.Path()) 332 addLocationLabel(tag.label, toolLocation{android.Paths{path.Path()}}) 333 } 334 case bootstrap.GoBinaryTool: 335 // A GoBinaryTool provides the install path to a tool, which will be copied. 336 p := android.PathForGoBinary(ctx, t) 337 tools = append(tools, p) 338 addLocationLabel(tag.label, toolLocation{android.Paths{p}}) 339 default: 340 ctx.ModuleErrorf("%q is not a host tool provider", tool) 341 return 342 } 343 344 seenTools[tag.label] = true 345 } 346 }) 347 348 // If AllowMissingDependencies is enabled, the build will not have stopped when 349 // AddFarVariationDependencies was called on a missing tool, which will result in nonsensical 350 // "cmd: unknown location label ..." errors later. Add a placeholder file to the local label. 351 // The command that uses this placeholder file will never be executed because the rule will be 352 // replaced with an android.Error rule reporting the missing dependencies. 353 if ctx.Config().AllowMissingDependencies() { 354 for _, tool := range g.properties.Tools { 355 if !seenTools[tool] { 356 addLocationLabel(tool, errorLocation{"***missing tool " + tool + "***"}) 357 } 358 } 359 } 360 } 361 362 if ctx.Failed() { 363 return 364 } 365 366 for _, toolFile := range g.properties.Tool_files { 367 paths := android.PathsForModuleSrc(ctx, []string{toolFile}) 368 tools = append(tools, paths...) 369 addLocationLabel(toolFile, toolLocation{paths}) 370 } 371 372 addLabelsForInputs := func(propName string, include, exclude []string) android.Paths { 373 includeDirInPaths := ctx.DeviceConfig().BuildBrokenInputDir(g.Name()) 374 var srcFiles android.Paths 375 for _, in := range include { 376 paths, missingDeps := android.PathsAndMissingDepsRelativeToModuleSourceDir(android.SourceInput{ 377 Context: ctx, Paths: []string{in}, ExcludePaths: exclude, IncludeDirs: includeDirInPaths, 378 }) 379 if len(missingDeps) > 0 { 380 if !ctx.Config().AllowMissingDependencies() { 381 panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator", 382 missingDeps)) 383 } 384 385 // If AllowMissingDependencies is enabled, the build will not have stopped when 386 // the dependency was added on a missing SourceFileProducer module, which will result in nonsensical 387 // "cmd: label ":..." has no files" errors later. Add a placeholder file to the local label. 388 // The command that uses this placeholder file will never be executed because the rule will be 389 // replaced with an android.Error rule reporting the missing dependencies. 390 ctx.AddMissingDependencies(missingDeps) 391 addLocationLabel(in, errorLocation{"***missing " + propName + " " + in + "***"}) 392 } else { 393 srcFiles = append(srcFiles, paths...) 394 addLocationLabel(in, inputLocation{paths}) 395 } 396 } 397 return srcFiles 398 } 399 srcFiles := addLabelsForInputs("srcs", g.properties.Srcs, g.properties.Exclude_srcs) 400 android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcFiles.Strings()}) 401 402 var copyFrom android.Paths 403 var outputFiles android.WritablePaths 404 var zipArgs strings.Builder 405 406 cmd := g.properties.Cmd.GetOrDefault(ctx, "") 407 if g.CmdModifier != nil { 408 cmd = g.CmdModifier(ctx, cmd) 409 } 410 411 var extraInputs android.Paths 412 // Generate tasks, either from genrule or gensrcs. 413 for i, task := range g.taskGenerator(ctx, cmd, srcFiles) { 414 if len(task.out) == 0 { 415 ctx.ModuleErrorf("must have at least one output file") 416 return 417 } 418 419 // Only handle extra inputs once as these currently are the same across all tasks 420 if i == 0 { 421 for name, values := range task.extraInputs { 422 extraInputs = append(extraInputs, addLabelsForInputs(name, values, []string{})...) 423 } 424 } 425 426 // Pick a unique path outside the task.genDir for the sbox manifest textproto, 427 // a unique rule name, and the user-visible description. 428 manifestName := "genrule.sbox.textproto" 429 desc := "generate" 430 name := "generator" 431 if task.shards > 0 { 432 manifestName = "genrule_" + strconv.Itoa(task.shard) + ".sbox.textproto" 433 desc += " " + strconv.Itoa(task.shard) 434 name += strconv.Itoa(task.shard) 435 } else if len(task.out) == 1 { 436 desc += " " + task.out[0].Base() 437 } 438 439 manifestPath := android.PathForModuleOut(ctx, manifestName) 440 441 // Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox. 442 rule := getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath)) 443 if Bool(g.properties.Write_if_changed) { 444 rule.Restat() 445 } 446 cmd := rule.Command() 447 448 for _, out := range task.out { 449 addLocationLabel(out.Rel(), outputLocation{out}) 450 } 451 452 rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) { 453 // report the error directly without returning an error to android.Expand to catch multiple errors in a 454 // single run 455 reportError := func(fmt string, args ...interface{}) (string, error) { 456 ctx.PropertyErrorf("cmd", fmt, args...) 457 return "SOONG_ERROR", nil 458 } 459 460 // Apply shell escape to each cases to prevent source file paths containing $ from being evaluated in shell 461 switch name { 462 case "location": 463 if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 { 464 return reportError("at least one `tools` or `tool_files` is required if $(location) is used") 465 } 466 loc := locationLabels[firstLabel] 467 paths := loc.Paths(cmd) 468 if len(paths) == 0 { 469 return reportError("default label %q has no files", firstLabel) 470 } else if len(paths) > 1 { 471 return reportError("default label %q has multiple files, use $(locations %s) to reference it", 472 firstLabel, firstLabel) 473 } 474 return proptools.ShellEscape(paths[0]), nil 475 case "in": 476 return strings.Join(proptools.ShellEscapeList(cmd.PathsForInputs(srcFiles)), " "), nil 477 case "out": 478 var sandboxOuts []string 479 for _, out := range task.out { 480 sandboxOuts = append(sandboxOuts, cmd.PathForOutput(out)) 481 } 482 return strings.Join(proptools.ShellEscapeList(sandboxOuts), " "), nil 483 case "genDir": 484 return proptools.ShellEscape(cmd.PathForOutput(task.genDir)), nil 485 default: 486 if strings.HasPrefix(name, "location ") { 487 label := strings.TrimSpace(strings.TrimPrefix(name, "location ")) 488 if loc, ok := locationLabels[label]; ok { 489 paths := loc.Paths(cmd) 490 if len(paths) == 0 { 491 return reportError("label %q has no files", label) 492 } else if len(paths) > 1 { 493 return reportError("label %q has multiple files, use $(locations %s) to reference it", 494 label, label) 495 } 496 return proptools.ShellEscape(paths[0]), nil 497 } else { 498 return reportError("unknown location label %q is not in srcs, out, tools or tool_files.", label) 499 } 500 } else if strings.HasPrefix(name, "locations ") { 501 label := strings.TrimSpace(strings.TrimPrefix(name, "locations ")) 502 if loc, ok := locationLabels[label]; ok { 503 paths := loc.Paths(cmd) 504 if len(paths) == 0 { 505 return reportError("label %q has no files", label) 506 } 507 return strings.Join(proptools.ShellEscapeList(paths), " "), nil 508 } else { 509 return reportError("unknown locations label %q is not in srcs, out, tools or tool_files.", label) 510 } 511 } else { 512 return reportError("unknown variable '$(%s)'", name) 513 } 514 } 515 }) 516 517 if err != nil { 518 ctx.PropertyErrorf("cmd", "%s", err.Error()) 519 return 520 } 521 522 g.rawCommands = append(g.rawCommands, rawCommand) 523 524 cmd.Text(rawCommand) 525 cmd.Implicits(srcFiles) // need to be able to reference other srcs 526 cmd.Implicits(extraInputs) 527 cmd.ImplicitOutputs(task.out) 528 cmd.Implicits(task.in) 529 cmd.ImplicitTools(tools) 530 cmd.ImplicitPackagedTools(packagedTools) 531 532 // Create the rule to run the genrule command inside sbox. 533 rule.Build(name, desc) 534 535 if len(task.copyTo) > 0 { 536 // If copyTo is set, multiple shards need to be copied into a single directory. 537 // task.out contains the per-shard paths, and copyTo contains the corresponding 538 // final path. The files need to be copied into the final directory by a 539 // single rule so it can remove the directory before it starts to ensure no 540 // old files remain. zipsync already does this, so build up zipArgs that 541 // zip all the per-shard directories into a single zip. 542 outputFiles = append(outputFiles, task.copyTo...) 543 copyFrom = append(copyFrom, task.out.Paths()...) 544 zipArgs.WriteString(" -C " + task.genDir.String()) 545 zipArgs.WriteString(android.JoinWithPrefix(task.out.Strings(), " -f ")) 546 } else { 547 outputFiles = append(outputFiles, task.out...) 548 } 549 } 550 551 if len(copyFrom) > 0 { 552 // Create a rule that zips all the per-shard directories into a single zip and then 553 // uses zipsync to unzip it into the final directory. 554 ctx.Build(pctx, android.BuildParams{ 555 Rule: gensrcsMerge, 556 Implicits: copyFrom, 557 Outputs: outputFiles, 558 Description: "merge shards", 559 Args: map[string]string{ 560 "zipArgs": zipArgs.String(), 561 "tmpZip": android.PathForModuleGen(ctx, g.subDir+".zip").String(), 562 "genDir": android.PathForModuleGen(ctx, g.subDir).String(), 563 }, 564 }) 565 } 566 567 g.outputFiles = outputFiles.Paths() 568} 569 570func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) { 571 g.generateCommonBuildActions(ctx) 572 573 // For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of 574 // the genrules on AOSP. That will make things simpler to look at the graph in the common 575 // case. For larger sets of outputs, inject a phony target in between to limit ninja file 576 // growth. 577 if len(g.outputFiles) <= 6 { 578 g.outputDeps = g.outputFiles 579 } else { 580 phonyFile := android.PathForModuleGen(ctx, "genrule-phony") 581 ctx.Build(pctx, android.BuildParams{ 582 Rule: blueprint.Phony, 583 Output: phonyFile, 584 Inputs: g.outputFiles, 585 }) 586 g.outputDeps = android.Paths{phonyFile} 587 } 588} 589 590// Collect information for opening IDE project files in java/jdeps.go. 591func (g *Module) IDEInfo(dpInfo *android.IdeInfo) { 592 dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...) 593 for _, src := range g.properties.Srcs { 594 if strings.HasPrefix(src, ":") { 595 src = strings.Trim(src, ":") 596 dpInfo.Deps = append(dpInfo.Deps, src) 597 } 598 } 599} 600 601func (g *Module) AndroidMk() android.AndroidMkData { 602 return android.AndroidMkData{ 603 Class: "ETC", 604 OutputFile: android.OptionalPathForPath(g.outputFiles[0]), 605 SubName: g.subName, 606 Extra: []android.AndroidMkExtraFunc{ 607 func(w io.Writer, outputFile android.Path) { 608 fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true") 609 }, 610 }, 611 Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { 612 android.WriteAndroidMkData(w, data) 613 if data.SubName != "" { 614 fmt.Fprintln(w, ".PHONY:", name) 615 fmt.Fprintln(w, name, ":", name+g.subName) 616 } 617 }, 618 } 619} 620 621var _ android.ApexModule = (*Module)(nil) 622 623// Implements android.ApexModule 624func (g *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, 625 sdkVersion android.ApiLevel) error { 626 // Because generated outputs are checked by client modules(e.g. cc_library, ...) 627 // we can safely ignore the check here. 628 return nil 629} 630 631func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module { 632 module := &Module{ 633 taskGenerator: taskGenerator, 634 } 635 636 module.AddProperties(props...) 637 module.AddProperties(&module.properties) 638 639 module.ImageInterface = noopImageInterface{} 640 641 return module 642} 643 644type noopImageInterface struct{} 645 646func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext) {} 647func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool { return false } 648func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool { return false } 649func (x noopImageInterface) VendorRamdiskVariantNeeded(android.BaseModuleContext) bool { return false } 650func (x noopImageInterface) DebugRamdiskVariantNeeded(android.BaseModuleContext) bool { return false } 651func (x noopImageInterface) RecoveryVariantNeeded(android.BaseModuleContext) bool { return false } 652func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil } 653func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string) { 654} 655 656func NewGenSrcs() *Module { 657 properties := &genSrcsProperties{} 658 659 // finalSubDir is the name of the subdirectory that output files will be generated into. 660 // It is used so that per-shard directories can be placed alongside it an then finally 661 // merged into it. 662 const finalSubDir = "gensrcs" 663 664 taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask { 665 shardSize := defaultShardSize 666 if s := properties.Shard_size; s != nil { 667 shardSize = int(*s) 668 } 669 670 // gensrcs rules can easily hit command line limits by repeating the command for 671 // every input file. Shard the input files into groups. 672 shards := android.ShardPaths(srcFiles, shardSize) 673 var generateTasks []generateTask 674 675 for i, shard := range shards { 676 var commands []string 677 var outFiles android.WritablePaths 678 var copyTo android.WritablePaths 679 680 // When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each 681 // shard will be write to their own directories and then be merged together 682 // into finalSubDir. If sharding is not enabled (i.e. len(shards) == 1), 683 // the sbox rule will write directly to finalSubDir. 684 genSubDir := finalSubDir 685 if len(shards) > 1 { 686 genSubDir = strconv.Itoa(i) 687 } 688 689 genDir := android.PathForModuleGen(ctx, genSubDir) 690 // TODO(ccross): this RuleBuilder is a hack to be able to call 691 // rule.Command().PathForOutput. Replace this with passing the rule into the 692 // generator. 693 rule := getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(genDir, nil)) 694 695 for _, in := range shard { 696 outFile := android.GenPathWithExtAndTrimExt(ctx, finalSubDir, in, String(properties.Output_extension), String(properties.Trim_extension)) 697 698 // If sharding is enabled, then outFile is the path to the output file in 699 // the shard directory, and copyTo is the path to the output file in the 700 // final directory. 701 if len(shards) > 1 { 702 shardFile := android.GenPathWithExtAndTrimExt(ctx, genSubDir, in, String(properties.Output_extension), String(properties.Trim_extension)) 703 copyTo = append(copyTo, outFile) 704 outFile = shardFile 705 } 706 707 outFiles = append(outFiles, outFile) 708 709 // pre-expand the command line to replace $in and $out with references to 710 // a single input and output file. 711 command, err := android.Expand(rawCommand, func(name string) (string, error) { 712 switch name { 713 case "in": 714 return in.String(), nil 715 case "out": 716 return rule.Command().PathForOutput(outFile), nil 717 default: 718 return "$(" + name + ")", nil 719 } 720 }) 721 if err != nil { 722 ctx.PropertyErrorf("cmd", err.Error()) 723 } 724 725 // escape the command in case for example it contains '#', an odd number of '"', etc 726 command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command)) 727 commands = append(commands, command) 728 } 729 fullCommand := strings.Join(commands, " && ") 730 731 generateTasks = append(generateTasks, generateTask{ 732 in: shard, 733 out: outFiles, 734 copyTo: copyTo, 735 genDir: genDir, 736 cmd: fullCommand, 737 shard: i, 738 shards: len(shards), 739 extraInputs: map[string][]string{ 740 "data": properties.Data, 741 }, 742 }) 743 } 744 745 return generateTasks 746 } 747 748 g := generatorFactory(taskGenerator, properties) 749 g.subDir = finalSubDir 750 return g 751} 752 753func GenSrcsFactory() android.Module { 754 m := NewGenSrcs() 755 android.InitAndroidModule(m) 756 return m 757} 758 759type genSrcsProperties struct { 760 // extension that will be substituted for each output file 761 Output_extension *string 762 763 // maximum number of files that will be passed on a single command line. 764 Shard_size *int64 765 766 // Additional files needed for build that are not tooling related. 767 Data []string `android:"path"` 768 769 // Trim the matched extension for each input file, and it should start with ".". 770 Trim_extension *string 771} 772 773const defaultShardSize = 50 774 775func NewGenRule() *Module { 776 properties := &genRuleProperties{} 777 778 taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask { 779 outs := make(android.WritablePaths, len(properties.Out)) 780 for i, out := range properties.Out { 781 outs[i] = android.PathForModuleGen(ctx, out) 782 } 783 return []generateTask{{ 784 in: srcFiles, 785 out: outs, 786 genDir: android.PathForModuleGen(ctx), 787 cmd: rawCommand, 788 }} 789 } 790 791 return generatorFactory(taskGenerator, properties) 792} 793 794func GenRuleFactory() android.Module { 795 m := NewGenRule() 796 android.InitAndroidModule(m) 797 android.InitDefaultableModule(m) 798 return m 799} 800 801type genRuleProperties struct { 802 // names of the output files that will be generated 803 Out []string `android:"arch_variant"` 804} 805 806var Bool = proptools.Bool 807var String = proptools.String 808 809// Defaults 810type Defaults struct { 811 android.ModuleBase 812 android.DefaultsModuleBase 813} 814 815func defaultsFactory() android.Module { 816 return DefaultsFactory() 817} 818 819func DefaultsFactory(props ...interface{}) android.Module { 820 module := &Defaults{} 821 822 module.AddProperties(props...) 823 module.AddProperties( 824 &generatorProperties{}, 825 &genRuleProperties{}, 826 ) 827 828 android.InitDefaultsModule(module) 829 830 return module 831} 832 833var sandboxingAllowlistKey = android.NewOnceKey("genruleSandboxingAllowlistKey") 834 835type sandboxingAllowlistSets struct { 836 sandboxingDenyModuleSet map[string]bool 837} 838 839func getSandboxingAllowlistSets(ctx android.PathContext) *sandboxingAllowlistSets { 840 return ctx.Config().Once(sandboxingAllowlistKey, func() interface{} { 841 sandboxingDenyModuleSet := map[string]bool{} 842 843 android.AddToStringSet(sandboxingDenyModuleSet, SandboxingDenyModuleList) 844 return &sandboxingAllowlistSets{ 845 sandboxingDenyModuleSet: sandboxingDenyModuleSet, 846 } 847 }).(*sandboxingAllowlistSets) 848} 849 850func getSandboxedRuleBuilder(ctx android.ModuleContext, r *android.RuleBuilder) *android.RuleBuilder { 851 if !ctx.DeviceConfig().GenruleSandboxing() { 852 return r.SandboxTools() 853 } 854 sandboxingAllowlistSets := getSandboxingAllowlistSets(ctx) 855 if sandboxingAllowlistSets.sandboxingDenyModuleSet[ctx.ModuleName()] { 856 return r.SandboxTools() 857 } 858 return r.SandboxInputs() 859} 860