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