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	"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
30func init() {
31	android.RegisterModuleType("gensrcs", GenSrcsFactory)
32	android.RegisterModuleType("genrule", GenRuleFactory)
33}
34
35var (
36	pctx = android.NewPackageContext("android/soong/genrule")
37)
38
39func init() {
40	pctx.HostBinToolVariable("sboxCmd", "sbox")
41}
42
43type SourceFileGenerator interface {
44	GeneratedSourceFiles() android.Paths
45	GeneratedHeaderDirs() android.Paths
46	GeneratedDeps() android.Paths
47}
48
49type HostToolProvider interface {
50	HostToolPath() android.OptionalPath
51}
52
53type hostToolDependencyTag struct {
54	blueprint.BaseDependencyTag
55}
56
57var hostToolDepTag hostToolDependencyTag
58
59type 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
94type 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
114type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) generateTask
115
116type generateTask struct {
117	in          android.Paths
118	out         android.WritablePaths
119	sandboxOuts []string
120	cmd         string
121}
122
123func (g *Module) GeneratedSourceFiles() android.Paths {
124	return g.outputFiles
125}
126
127func (g *Module) Srcs() android.Paths {
128	return append(android.Paths{}, g.outputFiles...)
129}
130
131func (g *Module) GeneratedHeaderDirs() android.Paths {
132	return g.exportedIncludeDirs
133}
134
135func (g *Module) GeneratedDeps() android.Paths {
136	return g.outputDeps
137}
138
139func (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
151func (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
307func (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
346func 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}>"
358func 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
367func 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
413func GenSrcsFactory() android.Module {
414	m := NewGenSrcs()
415	android.InitAndroidModule(m)
416	return m
417}
418
419type genSrcsProperties struct {
420	// extension that will be substituted for each output file
421	Output_extension *string
422}
423
424func 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
446func GenRuleFactory() android.Module {
447	m := NewGenRule()
448	android.InitAndroidModule(m)
449	return m
450}
451
452type genRuleProperties struct {
453	// names of the output files that will be generated
454	Out []string
455}
456
457var Bool = proptools.Bool
458var String = proptools.String
459