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