xref: /aosp_15_r20/build/soong/genrule/genrule.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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	"path/filepath"
25	"strconv"
26	"strings"
27
28	"github.com/google/blueprint"
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)
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
115func (t hostToolDependencyTag) AllowDisabledModuleDependencyProxy(
116	ctx android.OtherModuleProviderContext, target android.ModuleProxy) bool {
117	return android.OtherModuleProviderOrDefault(
118		ctx, target, android.CommonModuleInfoKey).ReplacedByPrebuilt
119}
120
121var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil)
122
123type generatorProperties struct {
124	// The command to run on one or more input files. Cmd supports substitution of a few variables.
125	//
126	// Available variables for substitution:
127	//
128	//  $(location): the path to the first entry in tools or tool_files.
129	//  $(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.
130	//  $(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.
131	//  $(in): one or more input files.
132	//  $(out): a single output file.
133	//  $(genDir): the sandbox directory for this tool; contains $(out).
134	//  $$: a literal $
135	Cmd proptools.Configurable[string] `android:"replace_instead_of_append"`
136
137	// name of the modules (if any) that produces the host executable.   Leave empty for
138	// prebuilts or scripts that do not need a module to build them.
139	Tools []string
140
141	// Local files that are used by the tool
142	Tool_files []string `android:"path"`
143
144	// List of directories to export generated headers from
145	Export_include_dirs []string
146
147	// list of input files
148	Srcs proptools.Configurable[[]string] `android:"path,arch_variant"`
149
150	// Same as srcs, but will add dependencies on modules via a device os variation and the device's
151	// first supported arch's variation. Can be used to add a dependency from a host genrule to
152	// a device module.
153	Device_first_srcs proptools.Configurable[[]string] `android:"path_device_first"`
154
155	// Same as srcs, but will add dependencies on modules via a device os variation and the common
156	// arch variation. Can be used to add a dependency from a host genrule to a device module.
157	Device_common_srcs proptools.Configurable[[]string] `android:"path_device_common"`
158
159	// Same as srcs, but will add dependencies on modules via a common_os os variation.
160	Common_os_srcs proptools.Configurable[[]string] `android:"path_common_os"`
161
162	// input files to exclude
163	Exclude_srcs []string `android:"path,arch_variant"`
164
165	// Enable restat to update the output only if the output is changed
166	Write_if_changed *bool
167
168	// When set to true, an additional $(build_number_file) label will be available
169	// to use in the cmd. This will be the location of a text file containing the
170	// build number. The dependency on this file will be "order-only", meaning that
171	// the genrule will not rerun when only this file changes, to avoid rerunning
172	// the genrule every build, because the build number changes every build.
173	// This also means that you should not attempt to consume the build number from
174	// the result of this genrule in another build rule. If you do, the build number
175	// in the second build rule will be stale when the second build rule rebuilds
176	// but this genrule does not. Only certain allowlisted modules are allowed to
177	// use this property, usages of the build number should be kept to the absolute
178	// minimum. Particularly no modules on the system image may include the build
179	// number. Prefer using libbuildversion via the use_version_lib property on
180	// cc modules.
181	Uses_order_only_build_number_file *bool
182}
183
184type Module struct {
185	android.ModuleBase
186	android.DefaultableModuleBase
187	android.ApexModuleBase
188
189	// For other packages to make their own genrules with extra
190	// properties
191	Extra interface{}
192
193	// CmdModifier can be set by wrappers around genrule to modify the command, for example to
194	// prefix environment variables to it.
195	CmdModifier func(ctx android.ModuleContext, cmd string) string
196
197	android.ImageInterface
198
199	properties generatorProperties
200
201	// For the different tasks that genrule and gensrc generate. genrule will
202	// generate 1 task, and gensrc will generate 1 or more tasks based on the
203	// number of shards the input files are sharded into.
204	taskGenerator taskFunc
205
206	rule        blueprint.Rule
207	rawCommands []string
208
209	exportedIncludeDirs android.Paths
210
211	outputFiles android.Paths
212	outputDeps  android.Paths
213
214	subName string
215	subDir  string
216}
217
218type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
219
220type generateTask struct {
221	in          android.Paths
222	out         android.WritablePaths
223	copyTo      android.WritablePaths // For gensrcs to set on gensrcsMerge rule.
224	genDir      android.WritablePath
225	extraInputs map[string][]string
226
227	cmd string
228	// For gensrsc sharding.
229	shard  int
230	shards int
231
232	// For nsjail tasks
233	useNsjail  bool
234	dirSrcs    android.DirectoryPaths
235	keepGendir bool
236}
237
238func (g *Module) GeneratedSourceFiles() android.Paths {
239	return g.outputFiles
240}
241
242func (g *Module) Srcs() android.Paths {
243	return append(android.Paths{}, g.outputFiles...)
244}
245
246func (g *Module) GeneratedHeaderDirs() android.Paths {
247	return g.exportedIncludeDirs
248}
249
250func (g *Module) GeneratedDeps() android.Paths {
251	return g.outputDeps
252}
253
254var _ android.SourceFileProducer = (*Module)(nil)
255
256func toolDepsMutator(ctx android.BottomUpMutatorContext) {
257	if g, ok := ctx.Module().(*Module); ok {
258		for _, tool := range g.properties.Tools {
259			tag := hostToolDependencyTag{label: tool}
260			if m := android.SrcIsModule(tool); m != "" {
261				tool = m
262			}
263			ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), tag, tool)
264		}
265	}
266}
267
268var buildNumberAllowlistKey = android.NewOnceKey("genruleBuildNumberAllowlistKey")
269
270// This allowlist should be kept to the bare minimum, it's
271// intended for things that existed before the build number
272// was tightly controlled. Prefer using libbuildversion
273// via the use_version_lib property of cc modules.
274// This is a function instead of a global map so that
275// soong plugins cannot add entries to the allowlist
276func isModuleInBuildNumberAllowlist(ctx android.ModuleContext) bool {
277	allowlist := ctx.Config().Once(buildNumberAllowlistKey, func() interface{} {
278		// Define the allowlist as a list and then copy it into a map so that
279		// gofmt doesn't change unnecessary lines trying to align the values of the map.
280		allowlist := []string{
281			// go/keep-sorted start
282			"build/soong/tests:gen",
283			"hardware/google/camera/common/hal/aidl_service:aidl_camera_build_version",
284			"tools/tradefederation/core:tradefed_zip",
285			"vendor/google/services/LyricCameraHAL/src/apex:com.google.pixel.camera.hal.manifest",
286			// go/keep-sorted end
287		}
288		allowlistMap := make(map[string]bool, len(allowlist))
289		for _, a := range allowlist {
290			allowlistMap[a] = true
291		}
292		return allowlistMap
293	}).(map[string]bool)
294
295	_, ok := allowlist[ctx.ModuleDir()+":"+ctx.ModuleName()]
296	return ok
297}
298
299// generateCommonBuildActions contains build action generation logic
300// common to both the mixed build case and the legacy case of genrule processing.
301// To fully support genrule in mixed builds, the contents of this function should
302// approach zero; there should be no genrule action registration done directly
303// by Soong logic in the mixed-build case.
304func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) {
305	// Add the variant as a suffix to the make modules to create, so that the make modules
306	// don't conflict because make doesn't know about variants. However, this causes issues with
307	// tracking required dependencies as the required property in soong is passed straight to make
308	// without accounting for these suffixes. To make it a little easier to work with, don't use
309	// a suffix for android_common variants so that java_genrules look like regular 1-variant
310	// genrules to make.
311	if ctx.ModuleSubDir() != "android_common" {
312		g.subName = ctx.ModuleSubDir()
313	}
314
315	if len(g.properties.Export_include_dirs) > 0 {
316		for _, dir := range g.properties.Export_include_dirs {
317			g.exportedIncludeDirs = append(g.exportedIncludeDirs,
318				android.PathForModuleGen(ctx, g.subDir, ctx.ModuleDir(), dir))
319			// Also export without ModuleDir for consistency with Export_include_dirs not being set
320			g.exportedIncludeDirs = append(g.exportedIncludeDirs,
321				android.PathForModuleGen(ctx, g.subDir, dir))
322		}
323	} else {
324		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, g.subDir))
325	}
326
327	locationLabels := map[string]location{}
328	firstLabel := ""
329
330	addLocationLabel := func(label string, loc location) {
331		if firstLabel == "" {
332			firstLabel = label
333		}
334		if _, exists := locationLabels[label]; !exists {
335			locationLabels[label] = loc
336		} else {
337			ctx.ModuleErrorf("multiple locations for label %q: %q and %q (do you have duplicate srcs entries?)",
338				label, locationLabels[label], loc)
339		}
340	}
341
342	var tools android.Paths
343	var packagedTools []android.PackagingSpec
344	if len(g.properties.Tools) > 0 {
345		seenTools := make(map[string]bool)
346		ctx.VisitDirectDepsProxyAllowDisabled(func(proxy android.ModuleProxy) {
347			switch tag := ctx.OtherModuleDependencyTag(proxy).(type) {
348			case hostToolDependencyTag:
349				// Necessary to retrieve any prebuilt replacement for the tool, since
350				// toolDepsMutator runs too late for the prebuilt mutators to have
351				// replaced the dependency.
352				module := android.PrebuiltGetPreferred(ctx, proxy)
353				tool := ctx.OtherModuleName(module)
354				if h, ok := android.OtherModuleProvider(ctx, module, android.HostToolProviderKey); ok {
355					// A HostToolProvider provides the path to a tool, which will be copied
356					// into the sandbox.
357					if !android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey).Enabled {
358						if ctx.Config().AllowMissingDependencies() {
359							ctx.AddMissingDependencies([]string{tool})
360						} else {
361							ctx.ModuleErrorf("depends on disabled module %q", tool)
362						}
363						return
364					}
365					path := h.HostToolPath
366					if !path.Valid() {
367						ctx.ModuleErrorf("host tool %q missing output file", tool)
368						return
369					}
370					if specs := android.OtherModuleProviderOrDefault(
371						ctx, module, android.InstallFilesProvider).TransitivePackagingSpecs.ToList(); specs != nil {
372						// If the HostToolProvider has PackgingSpecs, which are definitions of the
373						// required relative locations of the tool and its dependencies, use those
374						// instead.  They will be copied to those relative locations in the sbox
375						// sandbox.
376						// Care must be taken since TransitivePackagingSpec may return device-side
377						// paths via the required property. Filter them out.
378						for i, ps := range specs {
379							if ps.Partition() != "" {
380								if i == 0 {
381									panic("first PackagingSpec is assumed to be the host-side tool")
382								}
383								continue
384							}
385							packagedTools = append(packagedTools, ps)
386						}
387						// Assume that the first PackagingSpec of the module is the tool.
388						addLocationLabel(tag.label, packagedToolLocation{specs[0]})
389					} else {
390						tools = append(tools, path.Path())
391						addLocationLabel(tag.label, toolLocation{android.Paths{path.Path()}})
392					}
393				} else {
394					ctx.ModuleErrorf("%q is not a host tool provider", tool)
395					return
396				}
397
398				seenTools[tag.label] = true
399			}
400		})
401
402		// If AllowMissingDependencies is enabled, the build will not have stopped when
403		// AddFarVariationDependencies was called on a missing tool, which will result in nonsensical
404		// "cmd: unknown location label ..." errors later.  Add a placeholder file to the local label.
405		// The command that uses this placeholder file will never be executed because the rule will be
406		// replaced with an android.Error rule reporting the missing dependencies.
407		if ctx.Config().AllowMissingDependencies() {
408			for _, tool := range g.properties.Tools {
409				if !seenTools[tool] {
410					addLocationLabel(tool, errorLocation{"***missing tool " + tool + "***"})
411				}
412			}
413		}
414	}
415
416	if ctx.Failed() {
417		return
418	}
419
420	for _, toolFile := range g.properties.Tool_files {
421		paths := android.PathsForModuleSrc(ctx, []string{toolFile})
422		tools = append(tools, paths...)
423		addLocationLabel(toolFile, toolLocation{paths})
424	}
425
426	addLabelsForInputs := func(propName string, include, exclude []string) android.Paths {
427		includeDirInPaths := ctx.DeviceConfig().BuildBrokenInputDir(g.Name())
428		var srcFiles android.Paths
429		for _, in := range include {
430			paths, missingDeps := android.PathsAndMissingDepsRelativeToModuleSourceDir(android.SourceInput{
431				Context: ctx, Paths: []string{in}, ExcludePaths: exclude, IncludeDirs: includeDirInPaths,
432			})
433			if len(missingDeps) > 0 {
434				if !ctx.Config().AllowMissingDependencies() {
435					panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator",
436						missingDeps))
437				}
438
439				// If AllowMissingDependencies is enabled, the build will not have stopped when
440				// the dependency was added on a missing SourceFileProducer module, which will result in nonsensical
441				// "cmd: label ":..." has no files" errors later.  Add a placeholder file to the local label.
442				// The command that uses this placeholder file will never be executed because the rule will be
443				// replaced with an android.Error rule reporting the missing dependencies.
444				ctx.AddMissingDependencies(missingDeps)
445				addLocationLabel(in, errorLocation{"***missing " + propName + " " + in + "***"})
446			} else {
447				srcFiles = append(srcFiles, paths...)
448				addLocationLabel(in, inputLocation{paths})
449			}
450		}
451		return srcFiles
452	}
453	srcs := g.properties.Srcs.GetOrDefault(ctx, nil)
454	srcFiles := addLabelsForInputs("srcs", srcs, g.properties.Exclude_srcs)
455	srcFiles = append(srcFiles, addLabelsForInputs("device_first_srcs", g.properties.Device_first_srcs.GetOrDefault(ctx, nil), nil)...)
456	srcFiles = append(srcFiles, addLabelsForInputs("device_common_srcs", g.properties.Device_common_srcs.GetOrDefault(ctx, nil), nil)...)
457	srcFiles = append(srcFiles, addLabelsForInputs("common_os_srcs", g.properties.Common_os_srcs.GetOrDefault(ctx, nil), nil)...)
458	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcFiles.Strings()})
459
460	var copyFrom android.Paths
461	var outputFiles android.WritablePaths
462	var zipArgs strings.Builder
463
464	cmd := g.properties.Cmd.GetOrDefault(ctx, "")
465	if g.CmdModifier != nil {
466		cmd = g.CmdModifier(ctx, cmd)
467	}
468
469	var extraInputs android.Paths
470	// Generate tasks, either from genrule or gensrcs.
471	for i, task := range g.taskGenerator(ctx, cmd, srcFiles) {
472		if len(task.out) == 0 {
473			ctx.ModuleErrorf("must have at least one output file")
474			return
475		}
476
477		// Only handle extra inputs once as these currently are the same across all tasks
478		if i == 0 {
479			for name, values := range task.extraInputs {
480				extraInputs = append(extraInputs, addLabelsForInputs(name, values, []string{})...)
481			}
482		}
483
484		// Pick a unique path outside the task.genDir for the sbox manifest textproto,
485		// a unique rule name, and the user-visible description.
486		var rule *android.RuleBuilder
487		desc := "generate"
488		name := "generator"
489		if task.useNsjail {
490			rule = android.NewRuleBuilder(pctx, ctx).Nsjail(task.genDir, android.PathForModuleOut(ctx, "nsjail_build_sandbox"))
491			if task.keepGendir {
492				rule.NsjailKeepGendir()
493			}
494		} else {
495			manifestName := "genrule.sbox.textproto"
496			if task.shards > 0 {
497				manifestName = "genrule_" + strconv.Itoa(task.shard) + ".sbox.textproto"
498				desc += " " + strconv.Itoa(task.shard)
499				name += strconv.Itoa(task.shard)
500			} else if len(task.out) == 1 {
501				desc += " " + task.out[0].Base()
502			}
503
504			manifestPath := android.PathForModuleOut(ctx, manifestName)
505
506			// Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
507			rule = getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath))
508		}
509		if Bool(g.properties.Write_if_changed) {
510			rule.Restat()
511		}
512		cmd := rule.Command()
513
514		for _, out := range task.out {
515			addLocationLabel(out.Rel(), outputLocation{out})
516		}
517
518		rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
519			// report the error directly without returning an error to android.Expand to catch multiple errors in a
520			// single run
521			reportError := func(fmt string, args ...interface{}) (string, error) {
522				ctx.PropertyErrorf("cmd", fmt, args...)
523				return "SOONG_ERROR", nil
524			}
525
526			// Apply shell escape to each cases to prevent source file paths containing $ from being evaluated in shell
527			switch name {
528			case "location":
529				if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
530					return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
531				}
532				loc := locationLabels[firstLabel]
533				paths := loc.Paths(cmd)
534				if len(paths) == 0 {
535					return reportError("default label %q has no files", firstLabel)
536				} else if len(paths) > 1 {
537					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
538						firstLabel, firstLabel)
539				}
540				return proptools.ShellEscape(paths[0]), nil
541			case "in":
542				return strings.Join(proptools.ShellEscapeList(cmd.PathsForInputs(srcFiles)), " "), nil
543			case "out":
544				var sandboxOuts []string
545				for _, out := range task.out {
546					sandboxOuts = append(sandboxOuts, cmd.PathForOutput(out))
547				}
548				return strings.Join(proptools.ShellEscapeList(sandboxOuts), " "), nil
549			case "genDir":
550				return proptools.ShellEscape(cmd.PathForOutput(task.genDir)), nil
551			case "build_number_file":
552				if !proptools.Bool(g.properties.Uses_order_only_build_number_file) {
553					return reportError("to use the $(build_number_file) label, you must set uses_order_only_build_number_file: true")
554				}
555				return proptools.ShellEscape(cmd.PathForInput(ctx.Config().BuildNumberFile(ctx))), nil
556			default:
557				if strings.HasPrefix(name, "location ") {
558					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
559					if loc, ok := locationLabels[label]; ok {
560						paths := loc.Paths(cmd)
561						if len(paths) == 0 {
562							return reportError("label %q has no files", label)
563						} else if len(paths) > 1 {
564							return reportError("label %q has multiple files, use $(locations %s) to reference it",
565								label, label)
566						}
567						return proptools.ShellEscape(paths[0]), nil
568					} else {
569						return reportError("unknown location label %q is not in srcs, out, tools or tool_files.", label)
570					}
571				} else if strings.HasPrefix(name, "locations ") {
572					label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
573					if loc, ok := locationLabels[label]; ok {
574						paths := loc.Paths(cmd)
575						if len(paths) == 0 {
576							return reportError("label %q has no files", label)
577						}
578						return strings.Join(proptools.ShellEscapeList(paths), " "), nil
579					} else {
580						return reportError("unknown locations label %q is not in srcs, out, tools or tool_files.", label)
581					}
582				} else {
583					return reportError("unknown variable '$(%s)'", name)
584				}
585			}
586		})
587
588		if err != nil {
589			ctx.PropertyErrorf("cmd", "%s", err.Error())
590			return
591		}
592
593		g.rawCommands = append(g.rawCommands, rawCommand)
594
595		cmd.Text(rawCommand)
596		cmd.Implicits(srcFiles) // need to be able to reference other srcs
597		cmd.Implicits(extraInputs)
598		cmd.ImplicitOutputs(task.out)
599		cmd.Implicits(task.in)
600		cmd.ImplicitTools(tools)
601		cmd.ImplicitPackagedTools(packagedTools)
602		if proptools.Bool(g.properties.Uses_order_only_build_number_file) {
603			if !isModuleInBuildNumberAllowlist(ctx) {
604				ctx.ModuleErrorf("Only allowlisted modules may use uses_order_only_build_number_file: true")
605			}
606			cmd.OrderOnly(ctx.Config().BuildNumberFile(ctx))
607		}
608
609		if task.useNsjail {
610			for _, input := range task.dirSrcs {
611				cmd.ImplicitDirectory(input)
612				// TODO(b/375551969): remove glob
613				if paths, err := ctx.GlobWithDeps(filepath.Join(input.String(), "**/*"), nil); err == nil {
614					rule.NsjailImplicits(android.PathsForSource(ctx, paths))
615				} else {
616					ctx.PropertyErrorf("dir_srcs", "can't glob %q", input.String())
617				}
618			}
619		}
620
621		// Create the rule to run the genrule command inside sbox.
622		rule.Build(name, desc)
623
624		if len(task.copyTo) > 0 {
625			// If copyTo is set, multiple shards need to be copied into a single directory.
626			// task.out contains the per-shard paths, and copyTo contains the corresponding
627			// final path.  The files need to be copied into the final directory by a
628			// single rule so it can remove the directory before it starts to ensure no
629			// old files remain.  zipsync already does this, so build up zipArgs that
630			// zip all the per-shard directories into a single zip.
631			outputFiles = append(outputFiles, task.copyTo...)
632			copyFrom = append(copyFrom, task.out.Paths()...)
633			zipArgs.WriteString(" -C " + task.genDir.String())
634			zipArgs.WriteString(android.JoinWithPrefix(task.out.Strings(), " -f "))
635		} else {
636			outputFiles = append(outputFiles, task.out...)
637		}
638	}
639
640	if len(copyFrom) > 0 {
641		// Create a rule that zips all the per-shard directories into a single zip and then
642		// uses zipsync to unzip it into the final directory.
643		ctx.Build(pctx, android.BuildParams{
644			Rule:        gensrcsMerge,
645			Implicits:   copyFrom,
646			Outputs:     outputFiles,
647			Description: "merge shards",
648			Args: map[string]string{
649				"zipArgs": zipArgs.String(),
650				"tmpZip":  android.PathForModuleGen(ctx, g.subDir+".zip").String(),
651				"genDir":  android.PathForModuleGen(ctx, g.subDir).String(),
652			},
653		})
654	}
655
656	g.outputFiles = outputFiles.Paths()
657}
658
659func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
660	g.generateCommonBuildActions(ctx)
661
662	// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
663	// the genrules on AOSP. That will make things simpler to look at the graph in the common
664	// case. For larger sets of outputs, inject a phony target in between to limit ninja file
665	// growth.
666	if len(g.outputFiles) <= 6 {
667		g.outputDeps = g.outputFiles
668	} else {
669		phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
670		ctx.Build(pctx, android.BuildParams{
671			Rule:   blueprint.Phony,
672			Output: phonyFile,
673			Inputs: g.outputFiles,
674		})
675		g.outputDeps = android.Paths{phonyFile}
676	}
677
678	g.setOutputFiles(ctx)
679
680	if ctx.Os() == android.Windows {
681		// Make doesn't support windows:
682		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/module_arch_supported.mk;l=66;drc=f264690860bb6ee7762784d6b7201aae057ba6f2
683		g.HideFromMake()
684	}
685}
686
687func (g *Module) setOutputFiles(ctx android.ModuleContext) {
688	if len(g.outputFiles) == 0 {
689		return
690	}
691	ctx.SetOutputFiles(g.outputFiles, "")
692	// non-empty-string-tag should match one of the outputs
693	for _, files := range g.outputFiles {
694		ctx.SetOutputFiles(android.Paths{files}, files.Rel())
695	}
696}
697
698// Collect information for opening IDE project files in java/jdeps.go.
699func (g *Module) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
700	dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
701	for _, src := range g.properties.Srcs.GetOrDefault(ctx, nil) {
702		if strings.HasPrefix(src, ":") {
703			src = strings.Trim(src, ":")
704			dpInfo.Deps = append(dpInfo.Deps, src)
705		}
706	}
707}
708
709func (g *Module) AndroidMk() android.AndroidMkData {
710	return android.AndroidMkData{
711		Class:      "ETC",
712		OutputFile: android.OptionalPathForPath(g.outputFiles[0]),
713		SubName:    g.subName,
714		Extra: []android.AndroidMkExtraFunc{
715			func(w io.Writer, outputFile android.Path) {
716				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
717			},
718		},
719		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
720			android.WriteAndroidMkData(w, data)
721			if data.SubName != "" {
722				fmt.Fprintln(w, ".PHONY:", name)
723				fmt.Fprintln(w, name, ":", name+g.subName)
724			}
725		},
726	}
727}
728
729var _ android.ApexModule = (*Module)(nil)
730
731// Implements android.ApexModule
732func (g *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
733	sdkVersion android.ApiLevel) error {
734	// Because generated outputs are checked by client modules(e.g. cc_library, ...)
735	// we can safely ignore the check here.
736	return nil
737}
738
739func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module {
740	module := &Module{
741		taskGenerator: taskGenerator,
742	}
743
744	module.AddProperties(props...)
745	module.AddProperties(&module.properties)
746
747	module.ImageInterface = noopImageInterface{}
748
749	return module
750}
751
752type noopImageInterface struct{}
753
754func (x noopImageInterface) ImageMutatorBegin(android.ImageInterfaceContext)         {}
755func (x noopImageInterface) VendorVariantNeeded(android.ImageInterfaceContext) bool  { return false }
756func (x noopImageInterface) ProductVariantNeeded(android.ImageInterfaceContext) bool { return false }
757func (x noopImageInterface) CoreVariantNeeded(android.ImageInterfaceContext) bool    { return false }
758func (x noopImageInterface) RamdiskVariantNeeded(android.ImageInterfaceContext) bool { return false }
759func (x noopImageInterface) VendorRamdiskVariantNeeded(android.ImageInterfaceContext) bool {
760	return false
761}
762func (x noopImageInterface) DebugRamdiskVariantNeeded(android.ImageInterfaceContext) bool {
763	return false
764}
765func (x noopImageInterface) RecoveryVariantNeeded(android.ImageInterfaceContext) bool { return false }
766func (x noopImageInterface) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
767	return nil
768}
769func (x noopImageInterface) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
770}
771
772func NewGenSrcs() *Module {
773	properties := &genSrcsProperties{}
774
775	// finalSubDir is the name of the subdirectory that output files will be generated into.
776	// It is used so that per-shard directories can be placed alongside it an then finally
777	// merged into it.
778	const finalSubDir = "gensrcs"
779
780	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
781		shardSize := defaultShardSize
782		if s := properties.Shard_size; s != nil {
783			shardSize = int(*s)
784		}
785
786		// gensrcs rules can easily hit command line limits by repeating the command for
787		// every input file.  Shard the input files into groups.
788		shards := android.ShardPaths(srcFiles, shardSize)
789		var generateTasks []generateTask
790
791		for i, shard := range shards {
792			var commands []string
793			var outFiles android.WritablePaths
794			var copyTo android.WritablePaths
795
796			// When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
797			// shard will be write to their own directories and then be merged together
798			// into finalSubDir.  If sharding is not enabled (i.e. len(shards) == 1),
799			// the sbox rule will write directly to finalSubDir.
800			genSubDir := finalSubDir
801			if len(shards) > 1 {
802				genSubDir = strconv.Itoa(i)
803			}
804
805			genDir := android.PathForModuleGen(ctx, genSubDir)
806			// TODO(ccross): this RuleBuilder is a hack to be able to call
807			// rule.Command().PathForOutput.  Replace this with passing the rule into the
808			// generator.
809			rule := getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(genDir, nil))
810
811			for _, in := range shard {
812				outFile := android.GenPathWithExtAndTrimExt(ctx, finalSubDir, in, String(properties.Output_extension), String(properties.Trim_extension))
813
814				// If sharding is enabled, then outFile is the path to the output file in
815				// the shard directory, and copyTo is the path to the output file in the
816				// final directory.
817				if len(shards) > 1 {
818					shardFile := android.GenPathWithExtAndTrimExt(ctx, genSubDir, in, String(properties.Output_extension), String(properties.Trim_extension))
819					copyTo = append(copyTo, outFile)
820					outFile = shardFile
821				}
822
823				outFiles = append(outFiles, outFile)
824
825				// pre-expand the command line to replace $in and $out with references to
826				// a single input and output file.
827				command, err := android.Expand(rawCommand, func(name string) (string, error) {
828					switch name {
829					case "in":
830						return in.String(), nil
831					case "out":
832						return rule.Command().PathForOutput(outFile), nil
833					default:
834						return "$(" + name + ")", nil
835					}
836				})
837				if err != nil {
838					ctx.PropertyErrorf("cmd", err.Error())
839				}
840
841				// escape the command in case for example it contains '#', an odd number of '"', etc
842				command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command))
843				commands = append(commands, command)
844			}
845			fullCommand := strings.Join(commands, " && ")
846
847			generateTasks = append(generateTasks, generateTask{
848				in:     shard,
849				out:    outFiles,
850				copyTo: copyTo,
851				genDir: genDir,
852				cmd:    fullCommand,
853				shard:  i,
854				shards: len(shards),
855				extraInputs: map[string][]string{
856					"data": properties.Data,
857				},
858			})
859		}
860
861		return generateTasks
862	}
863
864	g := generatorFactory(taskGenerator, properties)
865	g.subDir = finalSubDir
866	return g
867}
868
869func GenSrcsFactory() android.Module {
870	m := NewGenSrcs()
871	android.InitAndroidModule(m)
872	android.InitDefaultableModule(m)
873	return m
874}
875
876type genSrcsProperties struct {
877	// extension that will be substituted for each output file
878	Output_extension *string
879
880	// maximum number of files that will be passed on a single command line.
881	Shard_size *int64
882
883	// Additional files needed for build that are not tooling related.
884	Data []string `android:"path"`
885
886	// Trim the matched extension for each input file, and it should start with ".".
887	Trim_extension *string
888}
889
890const defaultShardSize = 50
891
892func NewGenRule() *Module {
893	properties := &genRuleProperties{}
894
895	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
896		useNsjail := Bool(properties.Use_nsjail)
897
898		dirSrcs := android.DirectoryPathsForModuleSrc(ctx, properties.Dir_srcs)
899		if len(dirSrcs) > 0 && !useNsjail {
900			ctx.PropertyErrorf("dir_srcs", "can't use dir_srcs if use_nsjail is false")
901			return nil
902		}
903
904		keepGendir := Bool(properties.Keep_gendir)
905		if keepGendir && !useNsjail {
906			ctx.PropertyErrorf("keep_gendir", "can't use keep_gendir if use_nsjail is false")
907			return nil
908		}
909
910		outs := make(android.WritablePaths, len(properties.Out))
911		for i, out := range properties.Out {
912			outs[i] = android.PathForModuleGen(ctx, out)
913		}
914		return []generateTask{{
915			in:         srcFiles,
916			out:        outs,
917			genDir:     android.PathForModuleGen(ctx),
918			cmd:        rawCommand,
919			useNsjail:  useNsjail,
920			dirSrcs:    dirSrcs,
921			keepGendir: keepGendir,
922		}}
923	}
924
925	return generatorFactory(taskGenerator, properties)
926}
927
928func GenRuleFactory() android.Module {
929	m := NewGenRule()
930	android.InitAndroidModule(m)
931	android.InitDefaultableModule(m)
932	return m
933}
934
935type genRuleProperties struct {
936	Use_nsjail *bool
937
938	// List of input directories. Can be set only when use_nsjail is true. Currently, usage of
939	// dir_srcs is limited only to Trusty build.
940	Dir_srcs []string `android:"path"`
941
942	// If set to true, $(genDir) is not truncated. Useful when this genrule can be incrementally
943	// built. Can be set only when use_nsjail is true.
944	Keep_gendir *bool
945
946	// names of the output files that will be generated
947	Out []string `android:"arch_variant"`
948}
949
950var Bool = proptools.Bool
951var String = proptools.String
952
953// Defaults
954type Defaults struct {
955	android.ModuleBase
956	android.DefaultsModuleBase
957}
958
959func defaultsFactory() android.Module {
960	return DefaultsFactory()
961}
962
963func DefaultsFactory(props ...interface{}) android.Module {
964	module := &Defaults{}
965
966	module.AddProperties(props...)
967	module.AddProperties(
968		&generatorProperties{},
969		&genRuleProperties{},
970	)
971
972	android.InitDefaultsModule(module)
973
974	return module
975}
976
977var sandboxingAllowlistKey = android.NewOnceKey("genruleSandboxingAllowlistKey")
978
979type sandboxingAllowlistSets struct {
980	sandboxingDenyModuleSet map[string]bool
981}
982
983func getSandboxingAllowlistSets(ctx android.PathContext) *sandboxingAllowlistSets {
984	return ctx.Config().Once(sandboxingAllowlistKey, func() interface{} {
985		sandboxingDenyModuleSet := map[string]bool{}
986
987		android.AddToStringSet(sandboxingDenyModuleSet, SandboxingDenyModuleList)
988		return &sandboxingAllowlistSets{
989			sandboxingDenyModuleSet: sandboxingDenyModuleSet,
990		}
991	}).(*sandboxingAllowlistSets)
992}
993
994func getSandboxedRuleBuilder(ctx android.ModuleContext, r *android.RuleBuilder) *android.RuleBuilder {
995	if !ctx.DeviceConfig().GenruleSandboxing() {
996		return r.SandboxTools()
997	}
998	sandboxingAllowlistSets := getSandboxingAllowlistSets(ctx)
999	if sandboxingAllowlistSets.sandboxingDenyModuleSet[ctx.ModuleName()] {
1000		return r.SandboxTools()
1001	}
1002	return r.SandboxInputs()
1003}
1004