genrule.go 18 KB

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