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