1// Copyright 2015 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package genrule 16 17import ( 18 "fmt" 19 "io" 20 "strings" 21 22 "github.com/google/blueprint" 23 "github.com/google/blueprint/bootstrap" 24 "github.com/google/blueprint/proptools" 25 26 "android/soong/android" 27 "android/soong/shared" 28 "path/filepath" 29) 30 31func init() { 32 android.RegisterModuleType("genrule_defaults", defaultsFactory) 33 34 android.RegisterModuleType("gensrcs", GenSrcsFactory) 35 android.RegisterModuleType("genrule", GenRuleFactory) 36} 37 38var ( 39 pctx = android.NewPackageContext("android/soong/genrule") 40) 41 42func init() { 43 pctx.HostBinToolVariable("sboxCmd", "sbox") 44} 45 46type SourceFileGenerator interface { 47 GeneratedSourceFiles() android.Paths 48 GeneratedHeaderDirs() android.Paths 49 GeneratedDeps() android.Paths 50} 51 52// Alias for android.HostToolProvider 53// Deprecated: use android.HostToolProvider instead. 54type HostToolProvider interface { 55 android.HostToolProvider 56} 57 58type hostToolDependencyTag struct { 59 blueprint.BaseDependencyTag 60 label string 61} 62 63type generatorProperties struct { 64 // The command to run on one or more input files. Cmd supports substitution of a few variables 65 // (the actual substitution is implemented in GenerateAndroidBuildActions below) 66 // 67 // Available variables for substitution: 68 // 69 // $(location): the path to the first entry in tools or tool_files 70 // $(location <label>): the path to the tool, tool_file, input or output with name <label> 71 // $(in): one or more input files 72 // $(out): a single output file 73 // $(depfile): a file to which dependencies will be written, if the depfile property is set to true 74 // $(genDir): the sandbox directory for this tool; contains $(out) 75 // $$: a literal $ 76 // 77 // All files used must be declared as inputs (to ensure proper up-to-date checks). 78 // Use "$(in)" directly in Cmd to ensure that all inputs used are declared. 79 Cmd *string 80 81 // Enable reading a file containing dependencies in gcc format after the command completes 82 Depfile *bool 83 84 // name of the modules (if any) that produces the host executable. Leave empty for 85 // prebuilts or scripts that do not need a module to build them. 86 Tools []string 87 88 // Local file that is used as the tool 89 Tool_files []string `android:"path"` 90 91 // List of directories to export generated headers from 92 Export_include_dirs []string 93 94 // list of input files 95 Srcs []string `android:"path,arch_variant"` 96 97 // input files to exclude 98 Exclude_srcs []string `android:"path,arch_variant"` 99} 100 101type Module struct { 102 android.ModuleBase 103 android.DefaultableModuleBase 104 105 // For other packages to make their own genrules with extra 106 // properties 107 Extra interface{} 108 109 properties generatorProperties 110 111 taskGenerator taskFunc 112 113 deps android.Paths 114 rule blueprint.Rule 115 rawCommand string 116 117 exportedIncludeDirs android.Paths 118 119 outputFiles android.Paths 120 outputDeps android.Paths 121 122 subName string 123} 124 125type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask 126 127type generateTask struct { 128 in android.Paths 129 out android.WritablePaths 130 sandboxOuts []string 131 cmd string 132} 133 134func (g *Module) GeneratedSourceFiles() android.Paths { 135 return g.outputFiles 136} 137 138func (g *Module) Srcs() android.Paths { 139 return append(android.Paths{}, g.outputFiles...) 140} 141 142func (g *Module) GeneratedHeaderDirs() android.Paths { 143 return g.exportedIncludeDirs 144} 145 146func (g *Module) GeneratedDeps() android.Paths { 147 return g.outputDeps 148} 149 150func (g *Module) DepsMutator(ctx android.BottomUpMutatorContext) { 151 if g, ok := ctx.Module().(*Module); ok { 152 for _, tool := range g.properties.Tools { 153 tag := hostToolDependencyTag{label: tool} 154 if m := android.SrcIsModule(tool); m != "" { 155 tool = m 156 } 157 ctx.AddFarVariationDependencies([]blueprint.Variation{ 158 {Mutator: "arch", Variation: ctx.Config().BuildOsVariant}, 159 }, tag, tool) 160 } 161 } 162} 163 164func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) { 165 g.subName = ctx.ModuleSubDir() 166 167 if len(g.properties.Export_include_dirs) > 0 { 168 for _, dir := range g.properties.Export_include_dirs { 169 g.exportedIncludeDirs = append(g.exportedIncludeDirs, 170 android.PathForModuleGen(ctx, ctx.ModuleDir(), dir)) 171 } 172 } else { 173 g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, "")) 174 } 175 176 locationLabels := map[string][]string{} 177 firstLabel := "" 178 179 addLocationLabel := func(label string, paths []string) { 180 if firstLabel == "" { 181 firstLabel = label 182 } 183 if _, exists := locationLabels[label]; !exists { 184 locationLabels[label] = paths 185 } else { 186 ctx.ModuleErrorf("multiple labels for %q, %q and %q", 187 label, strings.Join(locationLabels[label], " "), strings.Join(paths, " ")) 188 } 189 } 190 191 if len(g.properties.Tools) > 0 { 192 seenTools := make(map[string]bool) 193 194 ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) { 195 switch tag := ctx.OtherModuleDependencyTag(module).(type) { 196 case hostToolDependencyTag: 197 tool := ctx.OtherModuleName(module) 198 var path android.OptionalPath 199 200 if t, ok := module.(android.HostToolProvider); ok { 201 if !t.(android.Module).Enabled() { 202 if ctx.Config().AllowMissingDependencies() { 203 ctx.AddMissingDependencies([]string{tool}) 204 } else { 205 ctx.ModuleErrorf("depends on disabled module %q", tool) 206 } 207 break 208 } 209 path = t.HostToolPath() 210 } else if t, ok := module.(bootstrap.GoBinaryTool); ok { 211 if s, err := filepath.Rel(android.PathForOutput(ctx).String(), t.InstallPath()); err == nil { 212 path = android.OptionalPathForPath(android.PathForOutput(ctx, s)) 213 } else { 214 ctx.ModuleErrorf("cannot find path for %q: %v", tool, err) 215 break 216 } 217 } else { 218 ctx.ModuleErrorf("%q is not a host tool provider", tool) 219 break 220 } 221 222 if path.Valid() { 223 g.deps = append(g.deps, path.Path()) 224 addLocationLabel(tag.label, []string{path.Path().String()}) 225 seenTools[tag.label] = true 226 } else { 227 ctx.ModuleErrorf("host tool %q missing output file", tool) 228 } 229 } 230 }) 231 232 // If AllowMissingDependencies is enabled, the build will not have stopped when 233 // AddFarVariationDependencies was called on a missing tool, which will result in nonsensical 234 // "cmd: unknown location label ..." errors later. Add a dummy file to the local label. The 235 // command that uses this dummy file will never be executed because the rule will be replaced with 236 // an android.Error rule reporting the missing dependencies. 237 if ctx.Config().AllowMissingDependencies() { 238 for _, tool := range g.properties.Tools { 239 if !seenTools[tool] { 240 addLocationLabel(tool, []string{"***missing tool " + tool + "***"}) 241 } 242 } 243 } 244 } 245 246 if ctx.Failed() { 247 return 248 } 249 250 for _, toolFile := range g.properties.Tool_files { 251 paths := android.PathsForModuleSrc(ctx, []string{toolFile}) 252 g.deps = append(g.deps, paths...) 253 addLocationLabel(toolFile, paths.Strings()) 254 } 255 256 var srcFiles android.Paths 257 for _, in := range g.properties.Srcs { 258 paths, missingDeps := android.PathsAndMissingDepsForModuleSrcExcludes(ctx, []string{in}, g.properties.Exclude_srcs) 259 if len(missingDeps) > 0 { 260 if !ctx.Config().AllowMissingDependencies() { 261 panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator", 262 missingDeps)) 263 } 264 265 // If AllowMissingDependencies is enabled, the build will not have stopped when 266 // the dependency was added on a missing SourceFileProducer module, which will result in nonsensical 267 // "cmd: label ":..." has no files" errors later. Add a dummy file to the local label. The 268 // command that uses this dummy file will never be executed because the rule will be replaced with 269 // an android.Error rule reporting the missing dependencies. 270 ctx.AddMissingDependencies(missingDeps) 271 addLocationLabel(in, []string{"***missing srcs " + in + "***"}) 272 } else { 273 srcFiles = append(srcFiles, paths...) 274 addLocationLabel(in, paths.Strings()) 275 } 276 } 277 278 task := g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) 279 280 for _, out := range task.out { 281 addLocationLabel(out.Rel(), []string{filepath.Join("__SBOX_OUT_DIR__", out.Rel())}) 282 } 283 284 referencedDepfile := false 285 286 rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) { 287 // report the error directly without returning an error to android.Expand to catch multiple errors in a 288 // single run 289 reportError := func(fmt string, args ...interface{}) (string, error) { 290 ctx.PropertyErrorf("cmd", fmt, args...) 291 return "SOONG_ERROR", nil 292 } 293 294 switch name { 295 case "location": 296 if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 { 297 return reportError("at least one `tools` or `tool_files` is required if $(location) is used") 298 } 299 paths := locationLabels[firstLabel] 300 if len(paths) == 0 { 301 return reportError("default label %q has no files", firstLabel) 302 } else if len(paths) > 1 { 303 return reportError("default label %q has multiple files, use $(locations %s) to reference it", 304 firstLabel, firstLabel) 305 } 306 return locationLabels[firstLabel][0], nil 307 case "in": 308 return "${in}", nil 309 case "out": 310 return "__SBOX_OUT_FILES__", nil 311 case "depfile": 312 referencedDepfile = true 313 if !Bool(g.properties.Depfile) { 314 return reportError("$(depfile) used without depfile property") 315 } 316 return "__SBOX_DEPFILE__", nil 317 case "genDir": 318 return "__SBOX_OUT_DIR__", nil 319 default: 320 if strings.HasPrefix(name, "location ") { 321 label := strings.TrimSpace(strings.TrimPrefix(name, "location ")) 322 if paths, ok := locationLabels[label]; ok { 323 if len(paths) == 0 { 324 return reportError("label %q has no files", label) 325 } else if len(paths) > 1 { 326 return reportError("label %q has multiple files, use $(locations %s) to reference it", 327 label, label) 328 } 329 return paths[0], nil 330 } else { 331 return reportError("unknown location label %q", label) 332 } 333 } else if strings.HasPrefix(name, "locations ") { 334 label := strings.TrimSpace(strings.TrimPrefix(name, "locations ")) 335 if paths, ok := locationLabels[label]; ok { 336 if len(paths) == 0 { 337 return reportError("label %q has no files", label) 338 } 339 return strings.Join(paths, " "), nil 340 } else { 341 return reportError("unknown locations label %q", label) 342 } 343 } else { 344 return reportError("unknown variable '$(%s)'", name) 345 } 346 } 347 }) 348 349 if err != nil { 350 ctx.PropertyErrorf("cmd", "%s", err.Error()) 351 return 352 } 353 354 if Bool(g.properties.Depfile) && !referencedDepfile { 355 ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd") 356 } 357 358 // tell the sbox command which directory to use as its sandbox root 359 buildDir := android.PathForOutput(ctx).String() 360 sandboxPath := shared.TempDirForOutDir(buildDir) 361 362 // recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written, 363 // to be replaced later by ninja_strings.go 364 depfilePlaceholder := "" 365 if Bool(g.properties.Depfile) { 366 depfilePlaceholder = "$depfileArgs" 367 } 368 369 genDir := android.PathForModuleGen(ctx) 370 // Escape the command for the shell 371 rawCommand = "'" + strings.Replace(rawCommand, "'", `'\''`, -1) + "'" 372 g.rawCommand = rawCommand 373 sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %s %s $allouts", 374 sandboxPath, genDir, rawCommand, depfilePlaceholder) 375 376 ruleParams := blueprint.RuleParams{ 377 Command: sandboxCommand, 378 CommandDeps: []string{"$sboxCmd"}, 379 } 380 args := []string{"allouts"} 381 if Bool(g.properties.Depfile) { 382 ruleParams.Deps = blueprint.DepsGCC 383 args = append(args, "depfileArgs") 384 } 385 g.rule = ctx.Rule(pctx, "generator", ruleParams, args...) 386 387 g.generateSourceFile(ctx, task) 388 389} 390 391func (g *Module) generateSourceFile(ctx android.ModuleContext, task generateTask) { 392 desc := "generate" 393 if len(task.out) == 0 { 394 ctx.ModuleErrorf("must have at least one output file") 395 return 396 } 397 if len(task.out) == 1 { 398 desc += " " + task.out[0].Base() 399 } 400 401 var depFile android.ModuleGenPath 402 if Bool(g.properties.Depfile) { 403 depFile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d") 404 } 405 406 params := android.BuildParams{ 407 Rule: g.rule, 408 Description: "generate", 409 Output: task.out[0], 410 ImplicitOutputs: task.out[1:], 411 Inputs: task.in, 412 Implicits: g.deps, 413 Args: map[string]string{ 414 "allouts": strings.Join(task.sandboxOuts, " "), 415 }, 416 } 417 if Bool(g.properties.Depfile) { 418 params.Depfile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d") 419 params.Args["depfileArgs"] = "--depfile-out " + depFile.String() 420 } 421 422 ctx.Build(pctx, params) 423 424 for _, outputFile := range task.out { 425 g.outputFiles = append(g.outputFiles, outputFile) 426 } 427 g.outputDeps = append(g.outputDeps, task.out[0]) 428} 429 430// Collect information for opening IDE project files in java/jdeps.go. 431func (g *Module) IDEInfo(dpInfo *android.IdeInfo) { 432 dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...) 433 for _, src := range g.properties.Srcs { 434 if strings.HasPrefix(src, ":") { 435 src = strings.Trim(src, ":") 436 dpInfo.Deps = append(dpInfo.Deps, src) 437 } 438 } 439} 440 441func (g *Module) AndroidMk() android.AndroidMkData { 442 return android.AndroidMkData{ 443 Include: "$(BUILD_PHONY_PACKAGE)", 444 Class: "FAKE", 445 OutputFile: android.OptionalPathForPath(g.outputFiles[0]), 446 SubName: g.subName, 447 Extra: []android.AndroidMkExtraFunc{ 448 func(w io.Writer, outputFile android.Path) { 449 fmt.Fprintln(w, "LOCAL_ADDITIONAL_DEPENDENCIES :=", strings.Join(g.outputFiles.Strings(), " ")) 450 }, 451 }, 452 Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { 453 android.WriteAndroidMkData(w, data) 454 if data.SubName != "" { 455 fmt.Fprintln(w, ".PHONY:", name) 456 fmt.Fprintln(w, name, ":", name+g.subName) 457 } 458 }, 459 } 460} 461 462func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module { 463 module := &Module{ 464 taskGenerator: taskGenerator, 465 } 466 467 module.AddProperties(props...) 468 module.AddProperties(&module.properties) 469 470 return module 471} 472 473// replace "out" with "__SBOX_OUT_DIR__/<the value of ${out}>" 474func pathToSandboxOut(path android.Path, genDir android.Path) string { 475 relOut, err := filepath.Rel(genDir.String(), path.String()) 476 if err != nil { 477 panic(fmt.Sprintf("Could not make ${out} relative: %v", err)) 478 } 479 return filepath.Join("__SBOX_OUT_DIR__", relOut) 480 481} 482 483func NewGenSrcs() *Module { 484 properties := &genSrcsProperties{} 485 486 taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask { 487 commands := []string{} 488 outFiles := android.WritablePaths{} 489 genDir := android.PathForModuleGen(ctx) 490 sandboxOuts := []string{} 491 for _, in := range srcFiles { 492 outFile := android.GenPathWithExt(ctx, "", in, String(properties.Output_extension)) 493 outFiles = append(outFiles, outFile) 494 495 sandboxOutfile := pathToSandboxOut(outFile, genDir) 496 sandboxOuts = append(sandboxOuts, sandboxOutfile) 497 498 command, err := android.Expand(rawCommand, func(name string) (string, error) { 499 switch name { 500 case "in": 501 return in.String(), nil 502 case "out": 503 return sandboxOutfile, nil 504 default: 505 return "$(" + name + ")", nil 506 } 507 }) 508 if err != nil { 509 ctx.PropertyErrorf("cmd", err.Error()) 510 } 511 512 // escape the command in case for example it contains '#', an odd number of '"', etc 513 command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command)) 514 commands = append(commands, command) 515 } 516 fullCommand := strings.Join(commands, " && ") 517 518 return generateTask{ 519 in: srcFiles, 520 out: outFiles, 521 sandboxOuts: sandboxOuts, 522 cmd: fullCommand, 523 } 524 } 525 526 return generatorFactory(taskGenerator, properties) 527} 528 529func GenSrcsFactory() android.Module { 530 m := NewGenSrcs() 531 android.InitAndroidModule(m) 532 return m 533} 534 535type genSrcsProperties struct { 536 // extension that will be substituted for each output file 537 Output_extension *string 538} 539 540func NewGenRule() *Module { 541 properties := &genRuleProperties{} 542 543 taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask { 544 outs := make(android.WritablePaths, len(properties.Out)) 545 sandboxOuts := make([]string, len(properties.Out)) 546 genDir := android.PathForModuleGen(ctx) 547 for i, out := range properties.Out { 548 outs[i] = android.PathForModuleGen(ctx, out) 549 sandboxOuts[i] = pathToSandboxOut(outs[i], genDir) 550 } 551 return generateTask{ 552 in: srcFiles, 553 out: outs, 554 sandboxOuts: sandboxOuts, 555 cmd: rawCommand, 556 } 557 } 558 559 return generatorFactory(taskGenerator, properties) 560} 561 562func GenRuleFactory() android.Module { 563 m := NewGenRule() 564 android.InitAndroidModule(m) 565 android.InitDefaultableModule(m) 566 return m 567} 568 569type genRuleProperties struct { 570 // names of the output files that will be generated 571 Out []string `android:"arch_variant"` 572} 573 574var Bool = proptools.Bool 575var String = proptools.String 576 577// 578// Defaults 579// 580type Defaults struct { 581 android.ModuleBase 582 android.DefaultsModuleBase 583} 584 585func (*Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) { 586} 587 588func defaultsFactory() android.Module { 589 return DefaultsFactory() 590} 591 592func DefaultsFactory(props ...interface{}) android.Module { 593 module := &Defaults{} 594 595 module.AddProperties(props...) 596 module.AddProperties( 597 &generatorProperties{}, 598 &genRuleProperties{}, 599 ) 600 601 android.InitDefaultsModule(module) 602 603 return module 604} 605