123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- // Copyright 2015 Google Inc. All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package genrule
- import (
- "fmt"
- "io"
- "strings"
- "github.com/google/blueprint"
- "github.com/google/blueprint/bootstrap"
- "github.com/google/blueprint/proptools"
- "android/soong/android"
- "android/soong/shared"
- "path/filepath"
- )
- func init() {
- android.RegisterModuleType("genrule_defaults", defaultsFactory)
- android.RegisterModuleType("gensrcs", GenSrcsFactory)
- android.RegisterModuleType("genrule", GenRuleFactory)
- }
- var (
- pctx = android.NewPackageContext("android/soong/genrule")
- )
- func init() {
- pctx.HostBinToolVariable("sboxCmd", "sbox")
- }
- type SourceFileGenerator interface {
- GeneratedSourceFiles() android.Paths
- GeneratedHeaderDirs() android.Paths
- GeneratedDeps() android.Paths
- }
- // Alias for android.HostToolProvider
- // Deprecated: use android.HostToolProvider instead.
- type HostToolProvider interface {
- android.HostToolProvider
- }
- type hostToolDependencyTag struct {
- blueprint.BaseDependencyTag
- label string
- }
- type generatorProperties struct {
- // The command to run on one or more input files. Cmd supports substitution of a few variables
- // (the actual substitution is implemented in GenerateAndroidBuildActions below)
- //
- // Available variables for substitution:
- //
- // $(location): the path to the first entry in tools or tool_files
- // $(location <label>): the path to the tool, tool_file, input or output with name <label>
- // $(in): one or more input files
- // $(out): a single output file
- // $(depfile): a file to which dependencies will be written, if the depfile property is set to true
- // $(genDir): the sandbox directory for this tool; contains $(out)
- // $$: a literal $
- //
- // All files used must be declared as inputs (to ensure proper up-to-date checks).
- // Use "$(in)" directly in Cmd to ensure that all inputs used are declared.
- Cmd *string
- // Enable reading a file containing dependencies in gcc format after the command completes
- Depfile *bool
- // name of the modules (if any) that produces the host executable. Leave empty for
- // prebuilts or scripts that do not need a module to build them.
- Tools []string
- // Local file that is used as the tool
- Tool_files []string `android:"path"`
- // List of directories to export generated headers from
- Export_include_dirs []string
- // list of input files
- Srcs []string `android:"path,arch_variant"`
- // input files to exclude
- Exclude_srcs []string `android:"path,arch_variant"`
- }
- type Module struct {
- android.ModuleBase
- android.DefaultableModuleBase
- android.ApexModuleBase
- // For other packages to make their own genrules with extra
- // properties
- Extra interface{}
- properties generatorProperties
- taskGenerator taskFunc
- deps android.Paths
- rule blueprint.Rule
- rawCommand string
- exportedIncludeDirs android.Paths
- outputFiles android.Paths
- outputDeps android.Paths
- subName string
- }
- type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask
- type generateTask struct {
- in android.Paths
- out android.WritablePaths
- sandboxOuts []string
- cmd string
- }
- func (g *Module) GeneratedSourceFiles() android.Paths {
- return g.outputFiles
- }
- func (g *Module) Srcs() android.Paths {
- return append(android.Paths{}, g.outputFiles...)
- }
- func (g *Module) GeneratedHeaderDirs() android.Paths {
- return g.exportedIncludeDirs
- }
- func (g *Module) GeneratedDeps() android.Paths {
- return g.outputDeps
- }
- func (g *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
- if g, ok := ctx.Module().(*Module); ok {
- for _, tool := range g.properties.Tools {
- tag := hostToolDependencyTag{label: tool}
- if m := android.SrcIsModule(tool); m != "" {
- tool = m
- }
- ctx.AddFarVariationDependencies([]blueprint.Variation{
- {Mutator: "arch", Variation: ctx.Config().BuildOsVariant},
- }, tag, tool)
- }
- }
- }
- func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- g.subName = ctx.ModuleSubDir()
- if len(g.properties.Export_include_dirs) > 0 {
- for _, dir := range g.properties.Export_include_dirs {
- g.exportedIncludeDirs = append(g.exportedIncludeDirs,
- android.PathForModuleGen(ctx, ctx.ModuleDir(), dir))
- }
- } else {
- g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, ""))
- }
- locationLabels := map[string][]string{}
- firstLabel := ""
- addLocationLabel := func(label string, paths []string) {
- if firstLabel == "" {
- firstLabel = label
- }
- if _, exists := locationLabels[label]; !exists {
- locationLabels[label] = paths
- } else {
- ctx.ModuleErrorf("multiple labels for %q, %q and %q",
- label, strings.Join(locationLabels[label], " "), strings.Join(paths, " "))
- }
- }
- if len(g.properties.Tools) > 0 {
- seenTools := make(map[string]bool)
- ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
- switch tag := ctx.OtherModuleDependencyTag(module).(type) {
- case hostToolDependencyTag:
- tool := ctx.OtherModuleName(module)
- var path android.OptionalPath
- if t, ok := module.(android.HostToolProvider); ok {
- if !t.(android.Module).Enabled() {
- if ctx.Config().AllowMissingDependencies() {
- ctx.AddMissingDependencies([]string{tool})
- } else {
- ctx.ModuleErrorf("depends on disabled module %q", tool)
- }
- break
- }
- path = t.HostToolPath()
- } else if t, ok := module.(bootstrap.GoBinaryTool); ok {
- if s, err := filepath.Rel(android.PathForOutput(ctx).String(), t.InstallPath()); err == nil {
- path = android.OptionalPathForPath(android.PathForOutput(ctx, s))
- } else {
- ctx.ModuleErrorf("cannot find path for %q: %v", tool, err)
- break
- }
- } else {
- ctx.ModuleErrorf("%q is not a host tool provider", tool)
- break
- }
- if path.Valid() {
- g.deps = append(g.deps, path.Path())
- addLocationLabel(tag.label, []string{path.Path().String()})
- seenTools[tag.label] = true
- } else {
- ctx.ModuleErrorf("host tool %q missing output file", tool)
- }
- }
- })
- // If AllowMissingDependencies is enabled, the build will not have stopped when
- // AddFarVariationDependencies was called on a missing tool, which will result in nonsensical
- // "cmd: unknown location label ..." errors later. Add a dummy file to the local label. The
- // command that uses this dummy file will never be executed because the rule will be replaced with
- // an android.Error rule reporting the missing dependencies.
- if ctx.Config().AllowMissingDependencies() {
- for _, tool := range g.properties.Tools {
- if !seenTools[tool] {
- addLocationLabel(tool, []string{"***missing tool " + tool + "***"})
- }
- }
- }
- }
- if ctx.Failed() {
- return
- }
- for _, toolFile := range g.properties.Tool_files {
- paths := android.PathsForModuleSrc(ctx, []string{toolFile})
- g.deps = append(g.deps, paths...)
- addLocationLabel(toolFile, paths.Strings())
- }
- var srcFiles android.Paths
- for _, in := range g.properties.Srcs {
- paths, missingDeps := android.PathsAndMissingDepsForModuleSrcExcludes(ctx, []string{in}, g.properties.Exclude_srcs)
- if len(missingDeps) > 0 {
- if !ctx.Config().AllowMissingDependencies() {
- panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator",
- missingDeps))
- }
- // If AllowMissingDependencies is enabled, the build will not have stopped when
- // the dependency was added on a missing SourceFileProducer module, which will result in nonsensical
- // "cmd: label ":..." has no files" errors later. Add a dummy file to the local label. The
- // command that uses this dummy file will never be executed because the rule will be replaced with
- // an android.Error rule reporting the missing dependencies.
- ctx.AddMissingDependencies(missingDeps)
- addLocationLabel(in, []string{"***missing srcs " + in + "***"})
- } else {
- srcFiles = append(srcFiles, paths...)
- addLocationLabel(in, paths.Strings())
- }
- }
- task := g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles)
- for _, out := range task.out {
- addLocationLabel(out.Rel(), []string{filepath.Join("__SBOX_OUT_DIR__", out.Rel())})
- }
- referencedDepfile := false
- rawCommand, err := android.ExpandNinjaEscaped(task.cmd, func(name string) (string, bool, error) {
- // report the error directly without returning an error to android.Expand to catch multiple errors in a
- // single run
- reportError := func(fmt string, args ...interface{}) (string, bool, error) {
- ctx.PropertyErrorf("cmd", fmt, args...)
- return "SOONG_ERROR", false, nil
- }
- switch name {
- case "location":
- if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
- return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
- }
- paths := locationLabels[firstLabel]
- if len(paths) == 0 {
- return reportError("default label %q has no files", firstLabel)
- } else if len(paths) > 1 {
- return reportError("default label %q has multiple files, use $(locations %s) to reference it",
- firstLabel, firstLabel)
- }
- return locationLabels[firstLabel][0], false, nil
- case "in":
- return "${in}", true, nil
- case "out":
- return "__SBOX_OUT_FILES__", false, nil
- case "depfile":
- referencedDepfile = true
- if !Bool(g.properties.Depfile) {
- return reportError("$(depfile) used without depfile property")
- }
- return "__SBOX_DEPFILE__", false, nil
- case "genDir":
- return "__SBOX_OUT_DIR__", false, nil
- default:
- if strings.HasPrefix(name, "location ") {
- label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
- if paths, ok := locationLabels[label]; ok {
- if len(paths) == 0 {
- return reportError("label %q has no files", label)
- } else if len(paths) > 1 {
- return reportError("label %q has multiple files, use $(locations %s) to reference it",
- label, label)
- }
- return paths[0], false, nil
- } else {
- return reportError("unknown location label %q", label)
- }
- } else if strings.HasPrefix(name, "locations ") {
- label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
- if paths, ok := locationLabels[label]; ok {
- if len(paths) == 0 {
- return reportError("label %q has no files", label)
- }
- return strings.Join(paths, " "), false, nil
- } else {
- return reportError("unknown locations label %q", label)
- }
- } else {
- return reportError("unknown variable '$(%s)'", name)
- }
- }
- })
- if err != nil {
- ctx.PropertyErrorf("cmd", "%s", err.Error())
- return
- }
- if Bool(g.properties.Depfile) && !referencedDepfile {
- ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
- }
- // tell the sbox command which directory to use as its sandbox root
- buildDir := android.PathForOutput(ctx).String()
- sandboxPath := shared.TempDirForOutDir(buildDir)
- // recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written,
- // to be replaced later by ninja_strings.go
- depfilePlaceholder := ""
- if Bool(g.properties.Depfile) {
- depfilePlaceholder = "$depfileArgs"
- }
- genDir := android.PathForModuleGen(ctx)
- // Escape the command for the shell
- rawCommand = "'" + strings.Replace(rawCommand, "'", `'\''`, -1) + "'"
- g.rawCommand = rawCommand
- sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %s %s $allouts",
- sandboxPath, genDir, rawCommand, depfilePlaceholder)
- ruleParams := blueprint.RuleParams{
- Command: sandboxCommand,
- CommandDeps: []string{"$sboxCmd"},
- }
- args := []string{"allouts"}
- if Bool(g.properties.Depfile) {
- ruleParams.Deps = blueprint.DepsGCC
- args = append(args, "depfileArgs")
- }
- g.rule = ctx.Rule(pctx, "generator", ruleParams, args...)
- g.generateSourceFile(ctx, task)
- }
- func (g *Module) generateSourceFile(ctx android.ModuleContext, task generateTask) {
- desc := "generate"
- if len(task.out) == 0 {
- ctx.ModuleErrorf("must have at least one output file")
- return
- }
- if len(task.out) == 1 {
- desc += " " + task.out[0].Base()
- }
- var depFile android.ModuleGenPath
- if Bool(g.properties.Depfile) {
- depFile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
- }
- params := android.BuildParams{
- Rule: g.rule,
- Description: "generate",
- Output: task.out[0],
- ImplicitOutputs: task.out[1:],
- Inputs: task.in,
- Implicits: g.deps,
- Args: map[string]string{
- "allouts": strings.Join(task.sandboxOuts, " "),
- },
- }
- if Bool(g.properties.Depfile) {
- params.Depfile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
- params.Args["depfileArgs"] = "--depfile-out " + depFile.String()
- }
- ctx.Build(pctx, params)
- for _, outputFile := range task.out {
- g.outputFiles = append(g.outputFiles, outputFile)
- }
- g.outputDeps = append(g.outputDeps, task.out[0])
- }
- // Collect information for opening IDE project files in java/jdeps.go.
- func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
- dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
- for _, src := range g.properties.Srcs {
- if strings.HasPrefix(src, ":") {
- src = strings.Trim(src, ":")
- dpInfo.Deps = append(dpInfo.Deps, src)
- }
- }
- }
- func (g *Module) AndroidMk() android.AndroidMkData {
- return android.AndroidMkData{
- Include: "$(BUILD_PHONY_PACKAGE)",
- Class: "FAKE",
- OutputFile: android.OptionalPathForPath(g.outputFiles[0]),
- SubName: g.subName,
- Extra: []android.AndroidMkExtraFunc{
- func(w io.Writer, outputFile android.Path) {
- fmt.Fprintln(w, "LOCAL_ADDITIONAL_DEPENDENCIES :=", strings.Join(g.outputFiles.Strings(), " "))
- },
- },
- Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
- android.WriteAndroidMkData(w, data)
- if data.SubName != "" {
- fmt.Fprintln(w, ".PHONY:", name)
- fmt.Fprintln(w, name, ":", name+g.subName)
- }
- },
- }
- }
- func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module {
- module := &Module{
- taskGenerator: taskGenerator,
- }
- module.AddProperties(props...)
- module.AddProperties(&module.properties)
- return module
- }
- // replace "out" with "__SBOX_OUT_DIR__/<the value of ${out}>"
- func pathToSandboxOut(path android.Path, genDir android.Path) string {
- relOut, err := filepath.Rel(genDir.String(), path.String())
- if err != nil {
- panic(fmt.Sprintf("Could not make ${out} relative: %v", err))
- }
- return filepath.Join("__SBOX_OUT_DIR__", relOut)
- }
- func NewGenSrcs() *Module {
- properties := &genSrcsProperties{}
- taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask {
- commands := []string{}
- outFiles := android.WritablePaths{}
- genDir := android.PathForModuleGen(ctx)
- sandboxOuts := []string{}
- for _, in := range srcFiles {
- outFile := android.GenPathWithExt(ctx, "", in, String(properties.Output_extension))
- outFiles = append(outFiles, outFile)
- sandboxOutfile := pathToSandboxOut(outFile, genDir)
- sandboxOuts = append(sandboxOuts, sandboxOutfile)
- command, err := android.Expand(rawCommand, func(name string) (string, error) {
- switch name {
- case "in":
- return in.String(), nil
- case "out":
- return sandboxOutfile, nil
- default:
- return "$(" + name + ")", nil
- }
- })
- if err != nil {
- ctx.PropertyErrorf("cmd", err.Error())
- }
- // escape the command in case for example it contains '#', an odd number of '"', etc
- command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command))
- commands = append(commands, command)
- }
- fullCommand := strings.Join(commands, " && ")
- return generateTask{
- in: srcFiles,
- out: outFiles,
- sandboxOuts: sandboxOuts,
- cmd: fullCommand,
- }
- }
- return generatorFactory(taskGenerator, properties)
- }
- func GenSrcsFactory() android.Module {
- m := NewGenSrcs()
- android.InitAndroidModule(m)
- return m
- }
- type genSrcsProperties struct {
- // extension that will be substituted for each output file
- Output_extension *string
- }
- func NewGenRule() *Module {
- properties := &genRuleProperties{}
- taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask {
- outs := make(android.WritablePaths, len(properties.Out))
- sandboxOuts := make([]string, len(properties.Out))
- genDir := android.PathForModuleGen(ctx)
- for i, out := range properties.Out {
- outs[i] = android.PathForModuleGen(ctx, out)
- sandboxOuts[i] = pathToSandboxOut(outs[i], genDir)
- }
- return generateTask{
- in: srcFiles,
- out: outs,
- sandboxOuts: sandboxOuts,
- cmd: rawCommand,
- }
- }
- return generatorFactory(taskGenerator, properties)
- }
- func GenRuleFactory() android.Module {
- m := NewGenRule()
- android.InitAndroidModule(m)
- android.InitDefaultableModule(m)
- return m
- }
- type genRuleProperties struct {
- // names of the output files that will be generated
- Out []string `android:"arch_variant"`
- }
- var Bool = proptools.Bool
- var String = proptools.String
- //
- // Defaults
- //
- type Defaults struct {
- android.ModuleBase
- android.DefaultsModuleBase
- }
- func defaultsFactory() android.Module {
- return DefaultsFactory()
- }
- func DefaultsFactory(props ...interface{}) android.Module {
- module := &Defaults{}
- module.AddProperties(props...)
- module.AddProperties(
- &generatorProperties{},
- &genRuleProperties{},
- )
- android.InitDefaultsModule(module)
- return module
- }
|