github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/soong/genrule/genrule.go (about) 1 // Copyright 2015 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package genrule 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/google/blueprint" 22 "github.com/google/blueprint/bootstrap" 23 "github.com/google/blueprint/proptools" 24 25 "android/soong/android" 26 "android/soong/shared" 27 "path/filepath" 28 ) 29 30 func init() { 31 android.RegisterModuleType("gensrcs", GenSrcsFactory) 32 android.RegisterModuleType("genrule", GenRuleFactory) 33 } 34 35 var ( 36 pctx = android.NewPackageContext("android/soong/genrule") 37 ) 38 39 func init() { 40 pctx.HostBinToolVariable("sboxCmd", "sbox") 41 } 42 43 type SourceFileGenerator interface { 44 GeneratedSourceFiles() android.Paths 45 GeneratedHeaderDirs() android.Paths 46 GeneratedDeps() android.Paths 47 } 48 49 type HostToolProvider interface { 50 HostToolPath() android.OptionalPath 51 } 52 53 type hostToolDependencyTag struct { 54 blueprint.BaseDependencyTag 55 } 56 57 var hostToolDepTag hostToolDependencyTag 58 59 type generatorProperties struct { 60 // The command to run on one or more input files. Cmd supports substitution of a few variables 61 // (the actual substitution is implemented in GenerateAndroidBuildActions below) 62 // 63 // Available variables for substitution: 64 // 65 // $(location): the path to the first entry in tools or tool_files 66 // $(location <label>): the path to the tool or tool_file with name <label> 67 // $(in): one or more input files 68 // $(out): a single output file 69 // $(depfile): a file to which dependencies will be written, if the depfile property is set to true 70 // $(genDir): the sandbox directory for this tool; contains $(out) 71 // $$: a literal $ 72 // 73 // All files used must be declared as inputs (to ensure proper up-to-date checks). 74 // Use "$(in)" directly in Cmd to ensure that all inputs used are declared. 75 Cmd *string 76 77 // Enable reading a file containing dependencies in gcc format after the command completes 78 Depfile *bool 79 80 // name of the modules (if any) that produces the host executable. Leave empty for 81 // prebuilts or scripts that do not need a module to build them. 82 Tools []string 83 84 // Local file that is used as the tool 85 Tool_files []string 86 87 // List of directories to export generated headers from 88 Export_include_dirs []string 89 90 // list of input files 91 Srcs []string 92 } 93 94 type Module struct { 95 android.ModuleBase 96 97 // For other packages to make their own genrules with extra 98 // properties 99 Extra interface{} 100 101 properties generatorProperties 102 103 taskGenerator taskFunc 104 105 deps android.Paths 106 rule blueprint.Rule 107 108 exportedIncludeDirs android.Paths 109 110 outputFiles android.Paths 111 outputDeps android.Paths 112 } 113 114 type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask 115 116 type generateTask struct { 117 in android.Paths 118 out android.WritablePaths 119 sandboxOuts []string 120 cmd string 121 } 122 123 func (g *Module) GeneratedSourceFiles() android.Paths { 124 return g.outputFiles 125 } 126 127 func (g *Module) Srcs() android.Paths { 128 return append(android.Paths{}, g.outputFiles...) 129 } 130 131 func (g *Module) GeneratedHeaderDirs() android.Paths { 132 return g.exportedIncludeDirs 133 } 134 135 func (g *Module) GeneratedDeps() android.Paths { 136 return g.outputDeps 137 } 138 139 func (g *Module) DepsMutator(ctx android.BottomUpMutatorContext) { 140 android.ExtractSourcesDeps(ctx, g.properties.Srcs) 141 android.ExtractSourcesDeps(ctx, g.properties.Tool_files) 142 if g, ok := ctx.Module().(*Module); ok { 143 if len(g.properties.Tools) > 0 { 144 ctx.AddFarVariationDependencies([]blueprint.Variation{ 145 {"arch", ctx.Config().BuildOsVariant}, 146 }, hostToolDepTag, g.properties.Tools...) 147 } 148 } 149 } 150 151 func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) { 152 if len(g.properties.Export_include_dirs) > 0 { 153 for _, dir := range g.properties.Export_include_dirs { 154 g.exportedIncludeDirs = append(g.exportedIncludeDirs, 155 android.PathForModuleGen(ctx, ctx.ModuleDir(), dir)) 156 } 157 } else { 158 g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, "")) 159 } 160 161 tools := map[string]android.Path{} 162 163 if len(g.properties.Tools) > 0 { 164 ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) { 165 switch ctx.OtherModuleDependencyTag(module) { 166 case android.SourceDepTag: 167 // Nothing to do 168 case hostToolDepTag: 169 tool := ctx.OtherModuleName(module) 170 var path android.OptionalPath 171 172 if t, ok := module.(HostToolProvider); ok { 173 if !t.(android.Module).Enabled() { 174 if ctx.Config().AllowMissingDependencies() { 175 ctx.AddMissingDependencies([]string{tool}) 176 } else { 177 ctx.ModuleErrorf("depends on disabled module %q", tool) 178 } 179 break 180 } 181 path = t.HostToolPath() 182 } else if t, ok := module.(bootstrap.GoBinaryTool); ok { 183 if s, err := filepath.Rel(android.PathForOutput(ctx).String(), t.InstallPath()); err == nil { 184 path = android.OptionalPathForPath(android.PathForOutput(ctx, s)) 185 } else { 186 ctx.ModuleErrorf("cannot find path for %q: %v", tool, err) 187 break 188 } 189 } else { 190 ctx.ModuleErrorf("%q is not a host tool provider", tool) 191 break 192 } 193 194 if path.Valid() { 195 g.deps = append(g.deps, path.Path()) 196 if _, exists := tools[tool]; !exists { 197 tools[tool] = path.Path() 198 } else { 199 ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], path.Path().String()) 200 } 201 } else { 202 ctx.ModuleErrorf("host tool %q missing output file", tool) 203 } 204 default: 205 ctx.ModuleErrorf("unknown dependency on %q", ctx.OtherModuleName(module)) 206 } 207 }) 208 } 209 210 if ctx.Failed() { 211 return 212 } 213 214 toolFiles := ctx.ExpandSources(g.properties.Tool_files, nil) 215 for _, tool := range toolFiles { 216 g.deps = append(g.deps, tool) 217 if _, exists := tools[tool.Rel()]; !exists { 218 tools[tool.Rel()] = tool 219 } else { 220 ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool.Rel()], tool.Rel()) 221 } 222 } 223 224 referencedDepfile := false 225 226 srcFiles := ctx.ExpandSources(g.properties.Srcs, nil) 227 task := g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) 228 229 rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) { 230 switch name { 231 case "location": 232 if len(g.properties.Tools) == 0 && len(toolFiles) == 0 { 233 return "", fmt.Errorf("at least one `tools` or `tool_files` is required if $(location) is used") 234 } 235 236 if len(g.properties.Tools) > 0 { 237 return tools[g.properties.Tools[0]].String(), nil 238 } else { 239 return tools[toolFiles[0].Rel()].String(), nil 240 } 241 case "in": 242 return "${in}", nil 243 case "out": 244 return "__SBOX_OUT_FILES__", nil 245 case "depfile": 246 referencedDepfile = true 247 if !Bool(g.properties.Depfile) { 248 return "", fmt.Errorf("$(depfile) used without depfile property") 249 } 250 return "__SBOX_DEPFILE__", nil 251 case "genDir": 252 return "__SBOX_OUT_DIR__", nil 253 default: 254 if strings.HasPrefix(name, "location ") { 255 label := strings.TrimSpace(strings.TrimPrefix(name, "location ")) 256 if tool, ok := tools[label]; ok { 257 return tool.String(), nil 258 } else { 259 return "", fmt.Errorf("unknown location label %q", label) 260 } 261 } 262 return "", fmt.Errorf("unknown variable '$(%s)'", name) 263 } 264 }) 265 266 if Bool(g.properties.Depfile) && !referencedDepfile { 267 ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd") 268 } 269 270 if err != nil { 271 ctx.PropertyErrorf("cmd", "%s", err.Error()) 272 return 273 } 274 275 // tell the sbox command which directory to use as its sandbox root 276 buildDir := android.PathForOutput(ctx).String() 277 sandboxPath := shared.TempDirForOutDir(buildDir) 278 279 // recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written, 280 // to be replaced later by ninja_strings.go 281 depfilePlaceholder := "" 282 if Bool(g.properties.Depfile) { 283 depfilePlaceholder = "$depfileArgs" 284 } 285 286 genDir := android.PathForModuleGen(ctx) 287 // Escape the command for the shell 288 rawCommand = "'" + strings.Replace(rawCommand, "'", `'\''`, -1) + "'" 289 sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %s %s $allouts", 290 sandboxPath, genDir, rawCommand, depfilePlaceholder) 291 292 ruleParams := blueprint.RuleParams{ 293 Command: sandboxCommand, 294 CommandDeps: []string{"$sboxCmd"}, 295 } 296 args := []string{"allouts"} 297 if Bool(g.properties.Depfile) { 298 ruleParams.Deps = blueprint.DepsGCC 299 args = append(args, "depfileArgs") 300 } 301 g.rule = ctx.Rule(pctx, "generator", ruleParams, args...) 302 303 g.generateSourceFile(ctx, task) 304 305 } 306 307 func (g *Module) generateSourceFile(ctx android.ModuleContext, task generateTask) { 308 desc := "generate" 309 if len(task.out) == 0 { 310 ctx.ModuleErrorf("must have at least one output file") 311 return 312 } 313 if len(task.out) == 1 { 314 desc += " " + task.out[0].Base() 315 } 316 317 var depFile android.ModuleGenPath 318 if Bool(g.properties.Depfile) { 319 depFile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d") 320 } 321 322 params := android.BuildParams{ 323 Rule: g.rule, 324 Description: "generate", 325 Output: task.out[0], 326 ImplicitOutputs: task.out[1:], 327 Inputs: task.in, 328 Implicits: g.deps, 329 Args: map[string]string{ 330 "allouts": strings.Join(task.sandboxOuts, " "), 331 }, 332 } 333 if Bool(g.properties.Depfile) { 334 params.Depfile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d") 335 params.Args["depfileArgs"] = "--depfile-out " + depFile.String() 336 } 337 338 ctx.Build(pctx, params) 339 340 for _, outputFile := range task.out { 341 g.outputFiles = append(g.outputFiles, outputFile) 342 } 343 g.outputDeps = append(g.outputDeps, task.out[0]) 344 } 345 346 func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module { 347 module := &Module{ 348 taskGenerator: taskGenerator, 349 } 350 351 module.AddProperties(props...) 352 module.AddProperties(&module.properties) 353 354 return module 355 } 356 357 // replace "out" with "__SBOX_OUT_DIR__/<the value of ${out}>" 358 func pathToSandboxOut(path android.Path, genDir android.Path) string { 359 relOut, err := filepath.Rel(genDir.String(), path.String()) 360 if err != nil { 361 panic(fmt.Sprintf("Could not make ${out} relative: %v", err)) 362 } 363 return filepath.Join("__SBOX_OUT_DIR__", relOut) 364 365 } 366 367 func NewGenSrcs() *Module { 368 properties := &genSrcsProperties{} 369 370 taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask { 371 commands := []string{} 372 outFiles := android.WritablePaths{} 373 genDir := android.PathForModuleGen(ctx) 374 sandboxOuts := []string{} 375 for _, in := range srcFiles { 376 outFile := android.GenPathWithExt(ctx, "", in, String(properties.Output_extension)) 377 outFiles = append(outFiles, outFile) 378 379 sandboxOutfile := pathToSandboxOut(outFile, genDir) 380 sandboxOuts = append(sandboxOuts, sandboxOutfile) 381 382 command, err := android.Expand(rawCommand, func(name string) (string, error) { 383 switch name { 384 case "in": 385 return in.String(), nil 386 case "out": 387 return sandboxOutfile, nil 388 default: 389 return "$(" + name + ")", nil 390 } 391 }) 392 if err != nil { 393 ctx.PropertyErrorf("cmd", err.Error()) 394 } 395 396 // escape the command in case for example it contains '#', an odd number of '"', etc 397 command = fmt.Sprintf("bash -c %v", proptools.ShellEscape([]string{command})[0]) 398 commands = append(commands, command) 399 } 400 fullCommand := strings.Join(commands, " && ") 401 402 return generateTask{ 403 in: srcFiles, 404 out: outFiles, 405 sandboxOuts: sandboxOuts, 406 cmd: fullCommand, 407 } 408 } 409 410 return generatorFactory(taskGenerator, properties) 411 } 412 413 func GenSrcsFactory() android.Module { 414 m := NewGenSrcs() 415 android.InitAndroidModule(m) 416 return m 417 } 418 419 type genSrcsProperties struct { 420 // extension that will be substituted for each output file 421 Output_extension *string 422 } 423 424 func NewGenRule() *Module { 425 properties := &genRuleProperties{} 426 427 taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask { 428 outs := make(android.WritablePaths, len(properties.Out)) 429 sandboxOuts := make([]string, len(properties.Out)) 430 genDir := android.PathForModuleGen(ctx) 431 for i, out := range properties.Out { 432 outs[i] = android.PathForModuleGen(ctx, out) 433 sandboxOuts[i] = pathToSandboxOut(outs[i], genDir) 434 } 435 return generateTask{ 436 in: srcFiles, 437 out: outs, 438 sandboxOuts: sandboxOuts, 439 cmd: rawCommand, 440 } 441 } 442 443 return generatorFactory(taskGenerator, properties) 444 } 445 446 func GenRuleFactory() android.Module { 447 m := NewGenRule() 448 android.InitAndroidModule(m) 449 return m 450 } 451 452 type genRuleProperties struct { 453 // names of the output files that will be generated 454 Out []string 455 } 456 457 var Bool = proptools.Bool 458 var String = proptools.String