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	"strconv"
21	"strings"
22
23	"github.com/google/blueprint"
24	"github.com/google/blueprint/bootstrap"
25	"github.com/google/blueprint/proptools"
26
27	"android/soong/android"
28	"android/soong/shared"
29	"crypto/sha256"
30	"path/filepath"
31)
32
33func init() {
34	registerGenruleBuildComponents(android.InitRegistrationContext)
35}
36
37func registerGenruleBuildComponents(ctx android.RegistrationContext) {
38	ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
39
40	ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
41	ctx.RegisterModuleType("genrule", GenRuleFactory)
42
43	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
44		ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel()
45	})
46}
47
48var (
49	pctx = android.NewPackageContext("android/soong/genrule")
50
51	gensrcsMerge = pctx.AndroidStaticRule("gensrcsMerge", blueprint.RuleParams{
52		Command:        "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
53		CommandDeps:    []string{"${soongZip}", "${zipSync}"},
54		Rspfile:        "${tmpZip}.rsp",
55		RspfileContent: "${zipArgs}",
56	}, "tmpZip", "genDir", "zipArgs")
57)
58
59func init() {
60	pctx.Import("android/soong/android")
61	pctx.HostBinToolVariable("sboxCmd", "sbox")
62
63	pctx.HostBinToolVariable("soongZip", "soong_zip")
64	pctx.HostBinToolVariable("zipSync", "zipsync")
65}
66
67type SourceFileGenerator interface {
68	GeneratedSourceFiles() android.Paths
69	GeneratedHeaderDirs() android.Paths
70	GeneratedDeps() android.Paths
71}
72
73// Alias for android.HostToolProvider
74// Deprecated: use android.HostToolProvider instead.
75type HostToolProvider interface {
76	android.HostToolProvider
77}
78
79type hostToolDependencyTag struct {
80	blueprint.BaseDependencyTag
81	label string
82}
83
84type generatorProperties struct {
85	// The command to run on one or more input files. Cmd supports substitution of a few variables
86	//
87	// Available variables for substitution:
88	//
89	//  $(location): the path to the first entry in tools or tool_files
90	//  $(location <label>): the path to the tool, tool_file, input or output with name <label>
91	//  $(in): one or more input files
92	//  $(out): a single output file
93	//  $(depfile): a file to which dependencies will be written, if the depfile property is set to true
94	//  $(genDir): the sandbox directory for this tool; contains $(out)
95	//  $$: a literal $
96	Cmd *string
97
98	// Enable reading a file containing dependencies in gcc format after the command completes
99	Depfile *bool
100
101	// name of the modules (if any) that produces the host executable.   Leave empty for
102	// prebuilts or scripts that do not need a module to build them.
103	Tools []string
104
105	// Local file that is used as the tool
106	Tool_files []string `android:"path"`
107
108	// List of directories to export generated headers from
109	Export_include_dirs []string
110
111	// list of input files
112	Srcs []string `android:"path,arch_variant"`
113
114	// input files to exclude
115	Exclude_srcs []string `android:"path,arch_variant"`
116}
117
118type Module struct {
119	android.ModuleBase
120	android.DefaultableModuleBase
121	android.ApexModuleBase
122
123	// For other packages to make their own genrules with extra
124	// properties
125	Extra interface{}
126	android.ImageInterface
127
128	properties generatorProperties
129
130	taskGenerator taskFunc
131
132	deps        android.Paths
133	rule        blueprint.Rule
134	rawCommands []string
135
136	exportedIncludeDirs android.Paths
137
138	outputFiles android.Paths
139	outputDeps  android.Paths
140
141	subName string
142	subDir  string
143
144	// Collect the module directory for IDE info in java/jdeps.go.
145	modulePaths []string
146}
147
148type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
149
150type generateTask struct {
151	in          android.Paths
152	out         android.WritablePaths
153	copyTo      android.WritablePaths
154	genDir      android.WritablePath
155	sandboxOuts []string
156	cmd         string
157	shard       int
158	shards      int
159}
160
161func (g *Module) GeneratedSourceFiles() android.Paths {
162	return g.outputFiles
163}
164
165func (g *Module) Srcs() android.Paths {
166	return append(android.Paths{}, g.outputFiles...)
167}
168
169func (g *Module) GeneratedHeaderDirs() android.Paths {
170	return g.exportedIncludeDirs
171}
172
173func (g *Module) GeneratedDeps() android.Paths {
174	return g.outputDeps
175}
176
177func toolDepsMutator(ctx android.BottomUpMutatorContext) {
178	if g, ok := ctx.Module().(*Module); ok {
179		for _, tool := range g.properties.Tools {
180			tag := hostToolDependencyTag{label: tool}
181			if m := android.SrcIsModule(tool); m != "" {
182				tool = m
183			}
184			ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), tag, tool)
185		}
186	}
187}
188
189func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
190	g.subName = ctx.ModuleSubDir()
191
192	// Collect the module directory for IDE info in java/jdeps.go.
193	g.modulePaths = append(g.modulePaths, ctx.ModuleDir())
194
195	if len(g.properties.Export_include_dirs) > 0 {
196		for _, dir := range g.properties.Export_include_dirs {
197			g.exportedIncludeDirs = append(g.exportedIncludeDirs,
198				android.PathForModuleGen(ctx, g.subDir, ctx.ModuleDir(), dir))
199		}
200	} else {
201		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, g.subDir))
202	}
203
204	locationLabels := map[string][]string{}
205	firstLabel := ""
206
207	addLocationLabel := func(label string, paths []string) {
208		if firstLabel == "" {
209			firstLabel = label
210		}
211		if _, exists := locationLabels[label]; !exists {
212			locationLabels[label] = paths
213		} else {
214			ctx.ModuleErrorf("multiple labels for %q, %q and %q",
215				label, strings.Join(locationLabels[label], " "), strings.Join(paths, " "))
216		}
217	}
218
219	if len(g.properties.Tools) > 0 {
220		seenTools := make(map[string]bool)
221
222		ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
223			switch tag := ctx.OtherModuleDependencyTag(module).(type) {
224			case hostToolDependencyTag:
225				tool := ctx.OtherModuleName(module)
226				var path android.OptionalPath
227
228				if t, ok := module.(android.HostToolProvider); ok {
229					if !t.(android.Module).Enabled() {
230						if ctx.Config().AllowMissingDependencies() {
231							ctx.AddMissingDependencies([]string{tool})
232						} else {
233							ctx.ModuleErrorf("depends on disabled module %q", tool)
234						}
235						break
236					}
237					path = t.HostToolPath()
238				} else if t, ok := module.(bootstrap.GoBinaryTool); ok {
239					if s, err := filepath.Rel(android.PathForOutput(ctx).String(), t.InstallPath()); err == nil {
240						path = android.OptionalPathForPath(android.PathForOutput(ctx, s))
241					} else {
242						ctx.ModuleErrorf("cannot find path for %q: %v", tool, err)
243						break
244					}
245				} else {
246					ctx.ModuleErrorf("%q is not a host tool provider", tool)
247					break
248				}
249
250				if path.Valid() {
251					g.deps = append(g.deps, path.Path())
252					addLocationLabel(tag.label, []string{path.Path().String()})
253					seenTools[tag.label] = true
254				} else {
255					ctx.ModuleErrorf("host tool %q missing output file", tool)
256				}
257			}
258		})
259
260		// If AllowMissingDependencies is enabled, the build will not have stopped when
261		// AddFarVariationDependencies was called on a missing tool, which will result in nonsensical
262		// "cmd: unknown location label ..." errors later.  Add a placeholder file to the local label.
263		// The command that uses this placeholder file will never be executed because the rule will be
264		// replaced with an android.Error rule reporting the missing dependencies.
265		if ctx.Config().AllowMissingDependencies() {
266			for _, tool := range g.properties.Tools {
267				if !seenTools[tool] {
268					addLocationLabel(tool, []string{"***missing tool " + tool + "***"})
269				}
270			}
271		}
272	}
273
274	if ctx.Failed() {
275		return
276	}
277
278	for _, toolFile := range g.properties.Tool_files {
279		paths := android.PathsForModuleSrc(ctx, []string{toolFile})
280		g.deps = append(g.deps, paths...)
281		addLocationLabel(toolFile, paths.Strings())
282	}
283
284	var srcFiles android.Paths
285	for _, in := range g.properties.Srcs {
286		paths, missingDeps := android.PathsAndMissingDepsForModuleSrcExcludes(ctx, []string{in}, g.properties.Exclude_srcs)
287		if len(missingDeps) > 0 {
288			if !ctx.Config().AllowMissingDependencies() {
289				panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator",
290					missingDeps))
291			}
292
293			// If AllowMissingDependencies is enabled, the build will not have stopped when
294			// the dependency was added on a missing SourceFileProducer module, which will result in nonsensical
295			// "cmd: label ":..." has no files" errors later.  Add a placeholder file to the local label.
296			// The command that uses this placeholder file will never be executed because the rule will be
297			// replaced with an android.Error rule reporting the missing dependencies.
298			ctx.AddMissingDependencies(missingDeps)
299			addLocationLabel(in, []string{"***missing srcs " + in + "***"})
300		} else {
301			srcFiles = append(srcFiles, paths...)
302			addLocationLabel(in, paths.Strings())
303		}
304	}
305
306	var copyFrom android.Paths
307	var outputFiles android.WritablePaths
308	var zipArgs strings.Builder
309
310	for _, task := range g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) {
311		for _, out := range task.out {
312			addLocationLabel(out.Rel(), []string{filepath.Join("__SBOX_OUT_DIR__", out.Rel())})
313		}
314
315		referencedIn := false
316		referencedDepfile := false
317
318		rawCommand, err := android.ExpandNinjaEscaped(task.cmd, func(name string) (string, bool, error) {
319			// report the error directly without returning an error to android.Expand to catch multiple errors in a
320			// single run
321			reportError := func(fmt string, args ...interface{}) (string, bool, error) {
322				ctx.PropertyErrorf("cmd", fmt, args...)
323				return "SOONG_ERROR", false, nil
324			}
325
326			switch name {
327			case "location":
328				if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
329					return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
330				}
331				paths := locationLabels[firstLabel]
332				if len(paths) == 0 {
333					return reportError("default label %q has no files", firstLabel)
334				} else if len(paths) > 1 {
335					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
336						firstLabel, firstLabel)
337				}
338				return locationLabels[firstLabel][0], false, nil
339			case "in":
340				referencedIn = true
341				return "${in}", true, nil
342			case "out":
343				return "__SBOX_OUT_FILES__", false, nil
344			case "depfile":
345				referencedDepfile = true
346				if !Bool(g.properties.Depfile) {
347					return reportError("$(depfile) used without depfile property")
348				}
349				return "__SBOX_DEPFILE__", false, nil
350			case "genDir":
351				return "__SBOX_OUT_DIR__", false, nil
352			default:
353				if strings.HasPrefix(name, "location ") {
354					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
355					if paths, ok := locationLabels[label]; ok {
356						if len(paths) == 0 {
357							return reportError("label %q has no files", label)
358						} else if len(paths) > 1 {
359							return reportError("label %q has multiple files, use $(locations %s) to reference it",
360								label, label)
361						}
362						return paths[0], false, nil
363					} else {
364						return reportError("unknown location label %q", label)
365					}
366				} else if strings.HasPrefix(name, "locations ") {
367					label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
368					if paths, ok := locationLabels[label]; ok {
369						if len(paths) == 0 {
370							return reportError("label %q has no files", label)
371						}
372						return strings.Join(paths, " "), false, nil
373					} else {
374						return reportError("unknown locations label %q", label)
375					}
376				} else {
377					return reportError("unknown variable '$(%s)'", name)
378				}
379			}
380		})
381
382		if err != nil {
383			ctx.PropertyErrorf("cmd", "%s", err.Error())
384			return
385		}
386
387		if Bool(g.properties.Depfile) && !referencedDepfile {
388			ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
389			return
390		}
391
392		// tell the sbox command which directory to use as its sandbox root
393		buildDir := android.PathForOutput(ctx).String()
394		sandboxPath := shared.TempDirForOutDir(buildDir)
395
396		// recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written,
397		// to be replaced later by ninja_strings.go
398		depfilePlaceholder := ""
399		if Bool(g.properties.Depfile) {
400			depfilePlaceholder = "$depfileArgs"
401		}
402
403		// Escape the command for the shell
404		rawCommand = "'" + strings.Replace(rawCommand, "'", `'\''`, -1) + "'"
405		g.rawCommands = append(g.rawCommands, rawCommand)
406
407		sandboxCommand := fmt.Sprintf("rm -rf %s && $sboxCmd --sandbox-path %s --output-root %s",
408			task.genDir, sandboxPath, task.genDir)
409
410		if !referencedIn {
411			sandboxCommand = sandboxCommand + hashSrcFiles(srcFiles)
412		}
413
414		sandboxCommand = sandboxCommand + fmt.Sprintf(" -c %s %s $allouts",
415			rawCommand, depfilePlaceholder)
416
417		ruleParams := blueprint.RuleParams{
418			Command:     sandboxCommand,
419			CommandDeps: []string{"$sboxCmd"},
420		}
421		args := []string{"allouts"}
422		if Bool(g.properties.Depfile) {
423			ruleParams.Deps = blueprint.DepsGCC
424			args = append(args, "depfileArgs")
425		}
426		name := "generator"
427		if task.shards > 1 {
428			name += strconv.Itoa(task.shard)
429		}
430		rule := ctx.Rule(pctx, name, ruleParams, args...)
431
432		g.generateSourceFile(ctx, task, rule)
433
434		if len(task.copyTo) > 0 {
435			outputFiles = append(outputFiles, task.copyTo...)
436			copyFrom = append(copyFrom, task.out.Paths()...)
437			zipArgs.WriteString(" -C " + task.genDir.String())
438			zipArgs.WriteString(android.JoinWithPrefix(task.out.Strings(), " -f "))
439		} else {
440			outputFiles = append(outputFiles, task.out...)
441		}
442	}
443
444	if len(copyFrom) > 0 {
445		ctx.Build(pctx, android.BuildParams{
446			Rule:      gensrcsMerge,
447			Implicits: copyFrom,
448			Outputs:   outputFiles,
449			Args: map[string]string{
450				"zipArgs": zipArgs.String(),
451				"tmpZip":  android.PathForModuleGen(ctx, g.subDir+".zip").String(),
452				"genDir":  android.PathForModuleGen(ctx, g.subDir).String(),
453			},
454		})
455	}
456
457	g.outputFiles = outputFiles.Paths()
458
459	// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
460	// the genrules on AOSP. That will make things simpler to look at the graph in the common
461	// case. For larger sets of outputs, inject a phony target in between to limit ninja file
462	// growth.
463	if len(g.outputFiles) <= 6 {
464		g.outputDeps = g.outputFiles
465	} else {
466		phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
467
468		ctx.Build(pctx, android.BuildParams{
469			Rule:   blueprint.Phony,
470			Output: phonyFile,
471			Inputs: g.outputFiles,
472		})
473
474		g.outputDeps = android.Paths{phonyFile}
475	}
476
477}
478
479func hashSrcFiles(srcFiles android.Paths) string {
480	h := sha256.New()
481	for _, src := range srcFiles {
482		h.Write([]byte(src.String()))
483	}
484	return fmt.Sprintf(" --input-hash %x", h.Sum(nil))
485}
486
487func (g *Module) generateSourceFile(ctx android.ModuleContext, task generateTask, rule blueprint.Rule) {
488	desc := "generate"
489	if len(task.out) == 0 {
490		ctx.ModuleErrorf("must have at least one output file")
491		return
492	}
493	if len(task.out) == 1 {
494		desc += " " + task.out[0].Base()
495	}
496
497	var depFile android.ModuleGenPath
498	if Bool(g.properties.Depfile) {
499		depFile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
500	}
501
502	if task.shards > 1 {
503		desc += " " + strconv.Itoa(task.shard)
504	}
505
506	params := android.BuildParams{
507		Rule:            rule,
508		Description:     desc,
509		Output:          task.out[0],
510		ImplicitOutputs: task.out[1:],
511		Inputs:          task.in,
512		Implicits:       g.deps,
513		Args: map[string]string{
514			"allouts": strings.Join(task.sandboxOuts, " "),
515		},
516	}
517	if Bool(g.properties.Depfile) {
518		params.Depfile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
519		params.Args["depfileArgs"] = "--depfile-out " + depFile.String()
520	}
521
522	ctx.Build(pctx, params)
523}
524
525// Collect information for opening IDE project files in java/jdeps.go.
526func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
527	dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
528	for _, src := range g.properties.Srcs {
529		if strings.HasPrefix(src, ":") {
530			src = strings.Trim(src, ":")
531			dpInfo.Deps = append(dpInfo.Deps, src)
532		}
533	}
534	dpInfo.Paths = append(dpInfo.Paths, g.modulePaths...)
535}
536
537func (g *Module) AndroidMk() android.AndroidMkData {
538	return android.AndroidMkData{
539		Include:    "$(BUILD_PHONY_PACKAGE)",
540		Class:      "FAKE",
541		OutputFile: android.OptionalPathForPath(g.outputFiles[0]),
542		SubName:    g.subName,
543		Extra: []android.AndroidMkExtraFunc{
544			func(w io.Writer, outputFile android.Path) {
545				fmt.Fprintln(w, "LOCAL_ADDITIONAL_DEPENDENCIES :=", strings.Join(g.outputDeps.Strings(), " "))
546			},
547		},
548		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
549			android.WriteAndroidMkData(w, data)
550			if data.SubName != "" {
551				fmt.Fprintln(w, ".PHONY:", name)
552				fmt.Fprintln(w, name, ":", name+g.subName)
553			}
554		},
555	}
556}
557
558func (g *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion int) error {
559	// Because generated outputs are checked by client modules(e.g. cc_library, ...)
560	// we can safely ignore the check here.
561	return nil
562}
563
564func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module {
565	module := &Module{
566		taskGenerator: taskGenerator,
567	}
568
569	module.AddProperties(props...)
570	module.AddProperties(&module.properties)
571
572	module.ImageInterface = noopImageInterface{}
573
574	return module
575}
576
577type noopImageInterface struct{}
578
579func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext)                 {}
580func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool            { return false }
581func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool         { return false }
582func (x noopImageInterface) RecoveryVariantNeeded(android.BaseModuleContext) bool        { return false }
583func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil }
584func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
585}
586
587// replace "out" with "__SBOX_OUT_DIR__/<the value of ${out}>"
588func pathToSandboxOut(path android.Path, genDir android.Path) string {
589	relOut, err := filepath.Rel(genDir.String(), path.String())
590	if err != nil {
591		panic(fmt.Sprintf("Could not make ${out} relative: %v", err))
592	}
593	return filepath.Join("__SBOX_OUT_DIR__", relOut)
594
595}
596
597func NewGenSrcs() *Module {
598	properties := &genSrcsProperties{}
599
600	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
601		genDir := android.PathForModuleGen(ctx, "gensrcs")
602		shardSize := defaultShardSize
603		if s := properties.Shard_size; s != nil {
604			shardSize = int(*s)
605		}
606
607		shards := android.ShardPaths(srcFiles, shardSize)
608		var generateTasks []generateTask
609
610		for i, shard := range shards {
611			var commands []string
612			var outFiles android.WritablePaths
613			var copyTo android.WritablePaths
614			var shardDir android.WritablePath
615			var sandboxOuts []string
616
617			if len(shards) > 1 {
618				shardDir = android.PathForModuleGen(ctx, strconv.Itoa(i))
619			} else {
620				shardDir = genDir
621			}
622
623			for _, in := range shard {
624				outFile := android.GenPathWithExt(ctx, "gensrcs", in, String(properties.Output_extension))
625				sandboxOutfile := pathToSandboxOut(outFile, genDir)
626
627				if len(shards) > 1 {
628					shardFile := android.GenPathWithExt(ctx, strconv.Itoa(i), in, String(properties.Output_extension))
629					copyTo = append(copyTo, outFile)
630					outFile = shardFile
631				}
632
633				outFiles = append(outFiles, outFile)
634				sandboxOuts = append(sandboxOuts, sandboxOutfile)
635
636				command, err := android.Expand(rawCommand, func(name string) (string, error) {
637					switch name {
638					case "in":
639						return in.String(), nil
640					case "out":
641						return sandboxOutfile, nil
642					default:
643						return "$(" + name + ")", nil
644					}
645				})
646				if err != nil {
647					ctx.PropertyErrorf("cmd", err.Error())
648				}
649
650				// escape the command in case for example it contains '#', an odd number of '"', etc
651				command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command))
652				commands = append(commands, command)
653			}
654			fullCommand := strings.Join(commands, " && ")
655
656			generateTasks = append(generateTasks, generateTask{
657				in:          shard,
658				out:         outFiles,
659				copyTo:      copyTo,
660				genDir:      shardDir,
661				sandboxOuts: sandboxOuts,
662				cmd:         fullCommand,
663				shard:       i,
664				shards:      len(shards),
665			})
666		}
667
668		return generateTasks
669	}
670
671	g := generatorFactory(taskGenerator, properties)
672	g.subDir = "gensrcs"
673	return g
674}
675
676func GenSrcsFactory() android.Module {
677	m := NewGenSrcs()
678	android.InitAndroidModule(m)
679	return m
680}
681
682type genSrcsProperties struct {
683	// extension that will be substituted for each output file
684	Output_extension *string
685
686	// maximum number of files that will be passed on a single command line.
687	Shard_size *int64
688}
689
690const defaultShardSize = 100
691
692func NewGenRule() *Module {
693	properties := &genRuleProperties{}
694
695	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
696		outs := make(android.WritablePaths, len(properties.Out))
697		sandboxOuts := make([]string, len(properties.Out))
698		genDir := android.PathForModuleGen(ctx)
699		for i, out := range properties.Out {
700			outs[i] = android.PathForModuleGen(ctx, out)
701			sandboxOuts[i] = pathToSandboxOut(outs[i], genDir)
702		}
703		return []generateTask{{
704			in:          srcFiles,
705			out:         outs,
706			genDir:      android.PathForModuleGen(ctx),
707			sandboxOuts: sandboxOuts,
708			cmd:         rawCommand,
709		}}
710	}
711
712	return generatorFactory(taskGenerator, properties)
713}
714
715func GenRuleFactory() android.Module {
716	m := NewGenRule()
717	android.InitAndroidModule(m)
718	android.InitDefaultableModule(m)
719	return m
720}
721
722type genRuleProperties struct {
723	// names of the output files that will be generated
724	Out []string `android:"arch_variant"`
725}
726
727var Bool = proptools.Bool
728var String = proptools.String
729
730//
731// Defaults
732//
733type Defaults struct {
734	android.ModuleBase
735	android.DefaultsModuleBase
736}
737
738func defaultsFactory() android.Module {
739	return DefaultsFactory()
740}
741
742func DefaultsFactory(props ...interface{}) android.Module {
743	module := &Defaults{}
744
745	module.AddProperties(props...)
746	module.AddProperties(
747		&generatorProperties{},
748		&genRuleProperties{},
749	)
750
751	android.InitDefaultsModule(module)
752
753	return module
754}
755