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// A genrule module takes a list of source files ("srcs" property), an optional
16// list of tools ("tools" property), and a command line ("cmd" property), to
17// generate output files ("out" property).
18
19package genrule
20
21import (
22	"fmt"
23	"io"
24	"strconv"
25	"strings"
26
27	"github.com/google/blueprint"
28	"github.com/google/blueprint/bootstrap"
29	"github.com/google/blueprint/proptools"
30
31	"android/soong/android"
32)
33
34func init() {
35	RegisterGenruleBuildComponents(android.InitRegistrationContext)
36}
37
38// Test fixture preparer that will register most genrule build components.
39//
40// Singletons and mutators should only be added here if they are needed for a majority of genrule
41// module types, otherwise they should be added under a separate preparer to allow them to be
42// selected only when needed to reduce test execution time.
43//
44// Module types do not have much of an overhead unless they are used so this should include as many
45// module types as possible. The exceptions are those module types that require mutators and/or
46// singletons in order to function in which case they should be kept together in a separate
47// preparer.
48var PrepareForTestWithGenRuleBuildComponents = android.GroupFixturePreparers(
49	android.FixtureRegisterWithContext(RegisterGenruleBuildComponents),
50)
51
52// Prepare a fixture to use all genrule module types, mutators and singletons fully.
53//
54// This should only be used by tests that want to run with as much of the build enabled as possible.
55var PrepareForIntegrationTestWithGenrule = android.GroupFixturePreparers(
56	PrepareForTestWithGenRuleBuildComponents,
57)
58
59func RegisterGenruleBuildComponents(ctx android.RegistrationContext) {
60	ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
61
62	ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
63	ctx.RegisterModuleType("genrule", GenRuleFactory)
64
65	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
66		ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel()
67	})
68}
69
70var (
71	pctx = android.NewPackageContext("android/soong/genrule")
72
73	// Used by gensrcs when there is more than 1 shard to merge the outputs
74	// of each shard into a zip file.
75	gensrcsMerge = pctx.AndroidStaticRule("gensrcsMerge", blueprint.RuleParams{
76		Command:        "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
77		CommandDeps:    []string{"${soongZip}", "${zipSync}"},
78		Rspfile:        "${tmpZip}.rsp",
79		RspfileContent: "${zipArgs}",
80	}, "tmpZip", "genDir", "zipArgs")
81)
82
83func init() {
84	pctx.Import("android/soong/android")
85
86	pctx.HostBinToolVariable("soongZip", "soong_zip")
87	pctx.HostBinToolVariable("zipSync", "zipsync")
88}
89
90type SourceFileGenerator interface {
91	GeneratedSourceFiles() android.Paths
92	GeneratedHeaderDirs() android.Paths
93	GeneratedDeps() android.Paths
94}
95
96// Alias for android.HostToolProvider
97// Deprecated: use android.HostToolProvider instead.
98type HostToolProvider interface {
99	android.HostToolProvider
100}
101
102type hostToolDependencyTag struct {
103	blueprint.BaseDependencyTag
104	android.LicenseAnnotationToolchainDependencyTag
105	label string
106}
107
108func (t hostToolDependencyTag) AllowDisabledModuleDependency(target android.Module) bool {
109	// Allow depending on a disabled module if it's replaced by a prebuilt
110	// counterpart. We get the prebuilt through android.PrebuiltGetPreferred in
111	// GenerateAndroidBuildActions.
112	return target.IsReplacedByPrebuilt()
113}
114
115var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil)
116
117type generatorProperties struct {
118	// The command to run on one or more input files. Cmd supports substitution of a few variables.
119	//
120	// Available variables for substitution:
121	//
122	//  $(location): the path to the first entry in tools or tool_files.
123	//  $(location <label>): the path to the tool, tool_file, input or output with name <label>. Use $(location) if <label> refers to a rule that outputs exactly one file.
124	//  $(locations <label>): the paths to the tools, tool_files, inputs or outputs with name <label>. Use $(locations) if <label> refers to a rule that outputs two or more files.
125	//  $(in): one or more input files.
126	//  $(out): a single output file.
127	//  $(genDir): the sandbox directory for this tool; contains $(out).
128	//  $$: a literal $
129	Cmd proptools.Configurable[string] `android:"replace_instead_of_append"`
130
131	// name of the modules (if any) that produces the host executable.   Leave empty for
132	// prebuilts or scripts that do not need a module to build them.
133	Tools []string
134
135	// Local files that are used by the tool
136	Tool_files []string `android:"path"`
137
138	// List of directories to export generated headers from
139	Export_include_dirs []string
140
141	// list of input files
142	Srcs []string `android:"path,arch_variant"`
143
144	// input files to exclude
145	Exclude_srcs []string `android:"path,arch_variant"`
146
147	// Enable restat to update the output only if the output is changed
148	Write_if_changed *bool
149}
150
151type Module struct {
152	android.ModuleBase
153	android.DefaultableModuleBase
154	android.ApexModuleBase
155
156	// For other packages to make their own genrules with extra
157	// properties
158	Extra interface{}
159
160	// CmdModifier can be set by wrappers around genrule to modify the command, for example to
161	// prefix environment variables to it.
162	CmdModifier func(ctx android.ModuleContext, cmd string) string
163
164	android.ImageInterface
165
166	properties generatorProperties
167
168	// For the different tasks that genrule and gensrc generate. genrule will
169	// generate 1 task, and gensrc will generate 1 or more tasks based on the
170	// number of shards the input files are sharded into.
171	taskGenerator taskFunc
172
173	rule        blueprint.Rule
174	rawCommands []string
175
176	exportedIncludeDirs android.Paths
177
178	outputFiles android.Paths
179	outputDeps  android.Paths
180
181	subName string
182	subDir  string
183}
184
185type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
186
187type generateTask struct {
188	in          android.Paths
189	out         android.WritablePaths
190	copyTo      android.WritablePaths // For gensrcs to set on gensrcsMerge rule.
191	genDir      android.WritablePath
192	extraInputs map[string][]string
193
194	cmd string
195	// For gensrsc sharding.
196	shard  int
197	shards int
198}
199
200func (g *Module) GeneratedSourceFiles() android.Paths {
201	return g.outputFiles
202}
203
204func (g *Module) Srcs() android.Paths {
205	return append(android.Paths{}, g.outputFiles...)
206}
207
208func (g *Module) GeneratedHeaderDirs() android.Paths {
209	return g.exportedIncludeDirs
210}
211
212func (g *Module) GeneratedDeps() android.Paths {
213	return g.outputDeps
214}
215
216func (g *Module) OutputFiles(tag string) (android.Paths, error) {
217	if tag == "" {
218		return append(android.Paths{}, g.outputFiles...), nil
219	}
220	// otherwise, tag should match one of outputs
221	for _, outputFile := range g.outputFiles {
222		if outputFile.Rel() == tag {
223			return android.Paths{outputFile}, nil
224		}
225	}
226	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
227}
228
229var _ android.SourceFileProducer = (*Module)(nil)
230var _ android.OutputFileProducer = (*Module)(nil)
231
232func toolDepsMutator(ctx android.BottomUpMutatorContext) {
233	if g, ok := ctx.Module().(*Module); ok {
234		for _, tool := range g.properties.Tools {
235			tag := hostToolDependencyTag{label: tool}
236			if m := android.SrcIsModule(tool); m != "" {
237				tool = m
238			}
239			ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), tag, tool)
240		}
241	}
242}
243
244// generateCommonBuildActions contains build action generation logic
245// common to both the mixed build case and the legacy case of genrule processing.
246// To fully support genrule in mixed builds, the contents of this function should
247// approach zero; there should be no genrule action registration done directly
248// by Soong logic in the mixed-build case.
249func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) {
250	g.subName = ctx.ModuleSubDir()
251
252	if len(g.properties.Export_include_dirs) > 0 {
253		for _, dir := range g.properties.Export_include_dirs {
254			g.exportedIncludeDirs = append(g.exportedIncludeDirs,
255				android.PathForModuleGen(ctx, g.subDir, ctx.ModuleDir(), dir))
256			// Also export without ModuleDir for consistency with Export_include_dirs not being set
257			g.exportedIncludeDirs = append(g.exportedIncludeDirs,
258				android.PathForModuleGen(ctx, g.subDir, dir))
259		}
260	} else {
261		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, g.subDir))
262	}
263
264	locationLabels := map[string]location{}
265	firstLabel := ""
266
267	addLocationLabel := func(label string, loc location) {
268		if firstLabel == "" {
269			firstLabel = label
270		}
271		if _, exists := locationLabels[label]; !exists {
272			locationLabels[label] = loc
273		} else {
274			ctx.ModuleErrorf("multiple locations for label %q: %q and %q (do you have duplicate srcs entries?)",
275				label, locationLabels[label], loc)
276		}
277	}
278
279	var tools android.Paths
280	var packagedTools []android.PackagingSpec
281	if len(g.properties.Tools) > 0 {
282		seenTools := make(map[string]bool)
283
284		ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
285			switch tag := ctx.OtherModuleDependencyTag(module).(type) {
286			case hostToolDependencyTag:
287				tool := ctx.OtherModuleName(module)
288				if m, ok := module.(android.Module); ok {
289					// Necessary to retrieve any prebuilt replacement for the tool, since
290					// toolDepsMutator runs too late for the prebuilt mutators to have
291					// replaced the dependency.
292					module = android.PrebuiltGetPreferred(ctx, m)
293				}
294
295				switch t := module.(type) {
296				case android.HostToolProvider:
297					// A HostToolProvider provides the path to a tool, which will be copied
298					// into the sandbox.
299					if !t.(android.Module).Enabled(ctx) {
300						if ctx.Config().AllowMissingDependencies() {
301							ctx.AddMissingDependencies([]string{tool})
302						} else {
303							ctx.ModuleErrorf("depends on disabled module %q", tool)
304						}
305						return
306					}
307					path := t.HostToolPath()
308					if !path.Valid() {
309						ctx.ModuleErrorf("host tool %q missing output file", tool)
310						return
311					}
312					if specs := t.TransitivePackagingSpecs(); specs != nil {
313						// If the HostToolProvider has PackgingSpecs, which are definitions of the
314						// required relative locations of the tool and its dependencies, use those
315						// instead.  They will be copied to those relative locations in the sbox
316						// sandbox.
317						// Care must be taken since TransitivePackagingSpec may return device-side
318						// paths via the required property. Filter them out.
319						for i, ps := range specs {
320							if ps.Partition() != "" {
321								if i == 0 {
322									panic("first PackagingSpec is assumed to be the host-side tool")
323								}
324								continue
325							}
326							packagedTools = append(packagedTools, ps)
327						}
328						// Assume that the first PackagingSpec of the module is the tool.
329						addLocationLabel(tag.label, packagedToolLocation{specs[0]})
330					} else {
331						tools = append(tools, path.Path())
332						addLocationLabel(tag.label, toolLocation{android.Paths{path.Path()}})
333					}
334				case bootstrap.GoBinaryTool:
335					// A GoBinaryTool provides the install path to a tool, which will be copied.
336					p := android.PathForGoBinary(ctx, t)
337					tools = append(tools, p)
338					addLocationLabel(tag.label, toolLocation{android.Paths{p}})
339				default:
340					ctx.ModuleErrorf("%q is not a host tool provider", tool)
341					return
342				}
343
344				seenTools[tag.label] = true
345			}
346		})
347
348		// If AllowMissingDependencies is enabled, the build will not have stopped when
349		// AddFarVariationDependencies was called on a missing tool, which will result in nonsensical
350		// "cmd: unknown location label ..." errors later.  Add a placeholder file to the local label.
351		// The command that uses this placeholder file will never be executed because the rule will be
352		// replaced with an android.Error rule reporting the missing dependencies.
353		if ctx.Config().AllowMissingDependencies() {
354			for _, tool := range g.properties.Tools {
355				if !seenTools[tool] {
356					addLocationLabel(tool, errorLocation{"***missing tool " + tool + "***"})
357				}
358			}
359		}
360	}
361
362	if ctx.Failed() {
363		return
364	}
365
366	for _, toolFile := range g.properties.Tool_files {
367		paths := android.PathsForModuleSrc(ctx, []string{toolFile})
368		tools = append(tools, paths...)
369		addLocationLabel(toolFile, toolLocation{paths})
370	}
371
372	addLabelsForInputs := func(propName string, include, exclude []string) android.Paths {
373		includeDirInPaths := ctx.DeviceConfig().BuildBrokenInputDir(g.Name())
374		var srcFiles android.Paths
375		for _, in := range include {
376			paths, missingDeps := android.PathsAndMissingDepsRelativeToModuleSourceDir(android.SourceInput{
377				Context: ctx, Paths: []string{in}, ExcludePaths: exclude, IncludeDirs: includeDirInPaths,
378			})
379			if len(missingDeps) > 0 {
380				if !ctx.Config().AllowMissingDependencies() {
381					panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator",
382						missingDeps))
383				}
384
385				// If AllowMissingDependencies is enabled, the build will not have stopped when
386				// the dependency was added on a missing SourceFileProducer module, which will result in nonsensical
387				// "cmd: label ":..." has no files" errors later.  Add a placeholder file to the local label.
388				// The command that uses this placeholder file will never be executed because the rule will be
389				// replaced with an android.Error rule reporting the missing dependencies.
390				ctx.AddMissingDependencies(missingDeps)
391				addLocationLabel(in, errorLocation{"***missing " + propName + " " + in + "***"})
392			} else {
393				srcFiles = append(srcFiles, paths...)
394				addLocationLabel(in, inputLocation{paths})
395			}
396		}
397		return srcFiles
398	}
399	srcFiles := addLabelsForInputs("srcs", g.properties.Srcs, g.properties.Exclude_srcs)
400	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcFiles.Strings()})
401
402	var copyFrom android.Paths
403	var outputFiles android.WritablePaths
404	var zipArgs strings.Builder
405
406	cmd := g.properties.Cmd.GetOrDefault(ctx, "")
407	if g.CmdModifier != nil {
408		cmd = g.CmdModifier(ctx, cmd)
409	}
410
411	var extraInputs android.Paths
412	// Generate tasks, either from genrule or gensrcs.
413	for i, task := range g.taskGenerator(ctx, cmd, srcFiles) {
414		if len(task.out) == 0 {
415			ctx.ModuleErrorf("must have at least one output file")
416			return
417		}
418
419		// Only handle extra inputs once as these currently are the same across all tasks
420		if i == 0 {
421			for name, values := range task.extraInputs {
422				extraInputs = append(extraInputs, addLabelsForInputs(name, values, []string{})...)
423			}
424		}
425
426		// Pick a unique path outside the task.genDir for the sbox manifest textproto,
427		// a unique rule name, and the user-visible description.
428		manifestName := "genrule.sbox.textproto"
429		desc := "generate"
430		name := "generator"
431		if task.shards > 0 {
432			manifestName = "genrule_" + strconv.Itoa(task.shard) + ".sbox.textproto"
433			desc += " " + strconv.Itoa(task.shard)
434			name += strconv.Itoa(task.shard)
435		} else if len(task.out) == 1 {
436			desc += " " + task.out[0].Base()
437		}
438
439		manifestPath := android.PathForModuleOut(ctx, manifestName)
440
441		// Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
442		rule := getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath))
443		if Bool(g.properties.Write_if_changed) {
444			rule.Restat()
445		}
446		cmd := rule.Command()
447
448		for _, out := range task.out {
449			addLocationLabel(out.Rel(), outputLocation{out})
450		}
451
452		rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
453			// report the error directly without returning an error to android.Expand to catch multiple errors in a
454			// single run
455			reportError := func(fmt string, args ...interface{}) (string, error) {
456				ctx.PropertyErrorf("cmd", fmt, args...)
457				return "SOONG_ERROR", nil
458			}
459
460			// Apply shell escape to each cases to prevent source file paths containing $ from being evaluated in shell
461			switch name {
462			case "location":
463				if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
464					return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
465				}
466				loc := locationLabels[firstLabel]
467				paths := loc.Paths(cmd)
468				if len(paths) == 0 {
469					return reportError("default label %q has no files", firstLabel)
470				} else if len(paths) > 1 {
471					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
472						firstLabel, firstLabel)
473				}
474				return proptools.ShellEscape(paths[0]), nil
475			case "in":
476				return strings.Join(proptools.ShellEscapeList(cmd.PathsForInputs(srcFiles)), " "), nil
477			case "out":
478				var sandboxOuts []string
479				for _, out := range task.out {
480					sandboxOuts = append(sandboxOuts, cmd.PathForOutput(out))
481				}
482				return strings.Join(proptools.ShellEscapeList(sandboxOuts), " "), nil
483			case "genDir":
484				return proptools.ShellEscape(cmd.PathForOutput(task.genDir)), nil
485			default:
486				if strings.HasPrefix(name, "location ") {
487					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
488					if loc, ok := locationLabels[label]; ok {
489						paths := loc.Paths(cmd)
490						if len(paths) == 0 {
491							return reportError("label %q has no files", label)
492						} else if len(paths) > 1 {
493							return reportError("label %q has multiple files, use $(locations %s) to reference it",
494								label, label)
495						}
496						return proptools.ShellEscape(paths[0]), nil
497					} else {
498						return reportError("unknown location label %q is not in srcs, out, tools or tool_files.", label)
499					}
500				} else if strings.HasPrefix(name, "locations ") {
501					label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
502					if loc, ok := locationLabels[label]; ok {
503						paths := loc.Paths(cmd)
504						if len(paths) == 0 {
505							return reportError("label %q has no files", label)
506						}
507						return strings.Join(proptools.ShellEscapeList(paths), " "), nil
508					} else {
509						return reportError("unknown locations label %q is not in srcs, out, tools or tool_files.", label)
510					}
511				} else {
512					return reportError("unknown variable '$(%s)'", name)
513				}
514			}
515		})
516
517		if err != nil {
518			ctx.PropertyErrorf("cmd", "%s", err.Error())
519			return
520		}
521
522		g.rawCommands = append(g.rawCommands, rawCommand)
523
524		cmd.Text(rawCommand)
525		cmd.Implicits(srcFiles) // need to be able to reference other srcs
526		cmd.Implicits(extraInputs)
527		cmd.ImplicitOutputs(task.out)
528		cmd.Implicits(task.in)
529		cmd.ImplicitTools(tools)
530		cmd.ImplicitPackagedTools(packagedTools)
531
532		// Create the rule to run the genrule command inside sbox.
533		rule.Build(name, desc)
534
535		if len(task.copyTo) > 0 {
536			// If copyTo is set, multiple shards need to be copied into a single directory.
537			// task.out contains the per-shard paths, and copyTo contains the corresponding
538			// final path.  The files need to be copied into the final directory by a
539			// single rule so it can remove the directory before it starts to ensure no
540			// old files remain.  zipsync already does this, so build up zipArgs that
541			// zip all the per-shard directories into a single zip.
542			outputFiles = append(outputFiles, task.copyTo...)
543			copyFrom = append(copyFrom, task.out.Paths()...)
544			zipArgs.WriteString(" -C " + task.genDir.String())
545			zipArgs.WriteString(android.JoinWithPrefix(task.out.Strings(), " -f "))
546		} else {
547			outputFiles = append(outputFiles, task.out...)
548		}
549	}
550
551	if len(copyFrom) > 0 {
552		// Create a rule that zips all the per-shard directories into a single zip and then
553		// uses zipsync to unzip it into the final directory.
554		ctx.Build(pctx, android.BuildParams{
555			Rule:        gensrcsMerge,
556			Implicits:   copyFrom,
557			Outputs:     outputFiles,
558			Description: "merge shards",
559			Args: map[string]string{
560				"zipArgs": zipArgs.String(),
561				"tmpZip":  android.PathForModuleGen(ctx, g.subDir+".zip").String(),
562				"genDir":  android.PathForModuleGen(ctx, g.subDir).String(),
563			},
564		})
565	}
566
567	g.outputFiles = outputFiles.Paths()
568}
569
570func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
571	g.generateCommonBuildActions(ctx)
572
573	// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
574	// the genrules on AOSP. That will make things simpler to look at the graph in the common
575	// case. For larger sets of outputs, inject a phony target in between to limit ninja file
576	// growth.
577	if len(g.outputFiles) <= 6 {
578		g.outputDeps = g.outputFiles
579	} else {
580		phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
581		ctx.Build(pctx, android.BuildParams{
582			Rule:   blueprint.Phony,
583			Output: phonyFile,
584			Inputs: g.outputFiles,
585		})
586		g.outputDeps = android.Paths{phonyFile}
587	}
588}
589
590// Collect information for opening IDE project files in java/jdeps.go.
591func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
592	dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
593	for _, src := range g.properties.Srcs {
594		if strings.HasPrefix(src, ":") {
595			src = strings.Trim(src, ":")
596			dpInfo.Deps = append(dpInfo.Deps, src)
597		}
598	}
599}
600
601func (g *Module) AndroidMk() android.AndroidMkData {
602	return android.AndroidMkData{
603		Class:      "ETC",
604		OutputFile: android.OptionalPathForPath(g.outputFiles[0]),
605		SubName:    g.subName,
606		Extra: []android.AndroidMkExtraFunc{
607			func(w io.Writer, outputFile android.Path) {
608				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
609			},
610		},
611		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
612			android.WriteAndroidMkData(w, data)
613			if data.SubName != "" {
614				fmt.Fprintln(w, ".PHONY:", name)
615				fmt.Fprintln(w, name, ":", name+g.subName)
616			}
617		},
618	}
619}
620
621var _ android.ApexModule = (*Module)(nil)
622
623// Implements android.ApexModule
624func (g *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
625	sdkVersion android.ApiLevel) error {
626	// Because generated outputs are checked by client modules(e.g. cc_library, ...)
627	// we can safely ignore the check here.
628	return nil
629}
630
631func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module {
632	module := &Module{
633		taskGenerator: taskGenerator,
634	}
635
636	module.AddProperties(props...)
637	module.AddProperties(&module.properties)
638
639	module.ImageInterface = noopImageInterface{}
640
641	return module
642}
643
644type noopImageInterface struct{}
645
646func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext)                 {}
647func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool            { return false }
648func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool         { return false }
649func (x noopImageInterface) VendorRamdiskVariantNeeded(android.BaseModuleContext) bool   { return false }
650func (x noopImageInterface) DebugRamdiskVariantNeeded(android.BaseModuleContext) bool    { return false }
651func (x noopImageInterface) RecoveryVariantNeeded(android.BaseModuleContext) bool        { return false }
652func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil }
653func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string) {
654}
655
656func NewGenSrcs() *Module {
657	properties := &genSrcsProperties{}
658
659	// finalSubDir is the name of the subdirectory that output files will be generated into.
660	// It is used so that per-shard directories can be placed alongside it an then finally
661	// merged into it.
662	const finalSubDir = "gensrcs"
663
664	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
665		shardSize := defaultShardSize
666		if s := properties.Shard_size; s != nil {
667			shardSize = int(*s)
668		}
669
670		// gensrcs rules can easily hit command line limits by repeating the command for
671		// every input file.  Shard the input files into groups.
672		shards := android.ShardPaths(srcFiles, shardSize)
673		var generateTasks []generateTask
674
675		for i, shard := range shards {
676			var commands []string
677			var outFiles android.WritablePaths
678			var copyTo android.WritablePaths
679
680			// When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
681			// shard will be write to their own directories and then be merged together
682			// into finalSubDir.  If sharding is not enabled (i.e. len(shards) == 1),
683			// the sbox rule will write directly to finalSubDir.
684			genSubDir := finalSubDir
685			if len(shards) > 1 {
686				genSubDir = strconv.Itoa(i)
687			}
688
689			genDir := android.PathForModuleGen(ctx, genSubDir)
690			// TODO(ccross): this RuleBuilder is a hack to be able to call
691			// rule.Command().PathForOutput.  Replace this with passing the rule into the
692			// generator.
693			rule := getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(genDir, nil))
694
695			for _, in := range shard {
696				outFile := android.GenPathWithExtAndTrimExt(ctx, finalSubDir, in, String(properties.Output_extension), String(properties.Trim_extension))
697
698				// If sharding is enabled, then outFile is the path to the output file in
699				// the shard directory, and copyTo is the path to the output file in the
700				// final directory.
701				if len(shards) > 1 {
702					shardFile := android.GenPathWithExtAndTrimExt(ctx, genSubDir, in, String(properties.Output_extension), String(properties.Trim_extension))
703					copyTo = append(copyTo, outFile)
704					outFile = shardFile
705				}
706
707				outFiles = append(outFiles, outFile)
708
709				// pre-expand the command line to replace $in and $out with references to
710				// a single input and output file.
711				command, err := android.Expand(rawCommand, func(name string) (string, error) {
712					switch name {
713					case "in":
714						return in.String(), nil
715					case "out":
716						return rule.Command().PathForOutput(outFile), nil
717					default:
718						return "$(" + name + ")", nil
719					}
720				})
721				if err != nil {
722					ctx.PropertyErrorf("cmd", err.Error())
723				}
724
725				// escape the command in case for example it contains '#', an odd number of '"', etc
726				command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command))
727				commands = append(commands, command)
728			}
729			fullCommand := strings.Join(commands, " && ")
730
731			generateTasks = append(generateTasks, generateTask{
732				in:     shard,
733				out:    outFiles,
734				copyTo: copyTo,
735				genDir: genDir,
736				cmd:    fullCommand,
737				shard:  i,
738				shards: len(shards),
739				extraInputs: map[string][]string{
740					"data": properties.Data,
741				},
742			})
743		}
744
745		return generateTasks
746	}
747
748	g := generatorFactory(taskGenerator, properties)
749	g.subDir = finalSubDir
750	return g
751}
752
753func GenSrcsFactory() android.Module {
754	m := NewGenSrcs()
755	android.InitAndroidModule(m)
756	return m
757}
758
759type genSrcsProperties struct {
760	// extension that will be substituted for each output file
761	Output_extension *string
762
763	// maximum number of files that will be passed on a single command line.
764	Shard_size *int64
765
766	// Additional files needed for build that are not tooling related.
767	Data []string `android:"path"`
768
769	// Trim the matched extension for each input file, and it should start with ".".
770	Trim_extension *string
771}
772
773const defaultShardSize = 50
774
775func NewGenRule() *Module {
776	properties := &genRuleProperties{}
777
778	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
779		outs := make(android.WritablePaths, len(properties.Out))
780		for i, out := range properties.Out {
781			outs[i] = android.PathForModuleGen(ctx, out)
782		}
783		return []generateTask{{
784			in:     srcFiles,
785			out:    outs,
786			genDir: android.PathForModuleGen(ctx),
787			cmd:    rawCommand,
788		}}
789	}
790
791	return generatorFactory(taskGenerator, properties)
792}
793
794func GenRuleFactory() android.Module {
795	m := NewGenRule()
796	android.InitAndroidModule(m)
797	android.InitDefaultableModule(m)
798	return m
799}
800
801type genRuleProperties struct {
802	// names of the output files that will be generated
803	Out []string `android:"arch_variant"`
804}
805
806var Bool = proptools.Bool
807var String = proptools.String
808
809// Defaults
810type Defaults struct {
811	android.ModuleBase
812	android.DefaultsModuleBase
813}
814
815func defaultsFactory() android.Module {
816	return DefaultsFactory()
817}
818
819func DefaultsFactory(props ...interface{}) android.Module {
820	module := &Defaults{}
821
822	module.AddProperties(props...)
823	module.AddProperties(
824		&generatorProperties{},
825		&genRuleProperties{},
826	)
827
828	android.InitDefaultsModule(module)
829
830	return module
831}
832
833var sandboxingAllowlistKey = android.NewOnceKey("genruleSandboxingAllowlistKey")
834
835type sandboxingAllowlistSets struct {
836	sandboxingDenyModuleSet map[string]bool
837}
838
839func getSandboxingAllowlistSets(ctx android.PathContext) *sandboxingAllowlistSets {
840	return ctx.Config().Once(sandboxingAllowlistKey, func() interface{} {
841		sandboxingDenyModuleSet := map[string]bool{}
842
843		android.AddToStringSet(sandboxingDenyModuleSet, SandboxingDenyModuleList)
844		return &sandboxingAllowlistSets{
845			sandboxingDenyModuleSet: sandboxingDenyModuleSet,
846		}
847	}).(*sandboxingAllowlistSets)
848}
849
850func getSandboxedRuleBuilder(ctx android.ModuleContext, r *android.RuleBuilder) *android.RuleBuilder {
851	if !ctx.DeviceConfig().GenruleSandboxing() {
852		return r.SandboxTools()
853	}
854	sandboxingAllowlistSets := getSandboxingAllowlistSets(ctx)
855	if sandboxingAllowlistSets.sandboxingDenyModuleSet[ctx.ModuleName()] {
856		return r.SandboxTools()
857	}
858	return r.SandboxInputs()
859}
860