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