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