• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
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	"android/soong/bazel/cquery"
29
30	"github.com/google/blueprint"
31	"github.com/google/blueprint/bootstrap"
32	"github.com/google/blueprint/proptools"
33
34	"android/soong/android"
35	"android/soong/bazel"
36)
37
38func init() {
39	RegisterGenruleBuildComponents(android.InitRegistrationContext)
40}
41
42// Test fixture preparer that will register most genrule build components.
43//
44// Singletons and mutators should only be added here if they are needed for a majority of genrule
45// module types, otherwise they should be added under a separate preparer to allow them to be
46// selected only when needed to reduce test execution time.
47//
48// Module types do not have much of an overhead unless they are used so this should include as many
49// module types as possible. The exceptions are those module types that require mutators and/or
50// singletons in order to function in which case they should be kept together in a separate
51// preparer.
52var PrepareForTestWithGenRuleBuildComponents = android.GroupFixturePreparers(
53	android.FixtureRegisterWithContext(RegisterGenruleBuildComponents),
54)
55
56// Prepare a fixture to use all genrule module types, mutators and singletons fully.
57//
58// This should only be used by tests that want to run with as much of the build enabled as possible.
59var PrepareForIntegrationTestWithGenrule = android.GroupFixturePreparers(
60	PrepareForTestWithGenRuleBuildComponents,
61)
62
63func RegisterGenruleBuildComponents(ctx android.RegistrationContext) {
64	ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
65
66	ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
67	ctx.RegisterModuleType("genrule", GenRuleFactory)
68
69	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
70		ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel()
71	})
72}
73
74var (
75	pctx = android.NewPackageContext("android/soong/genrule")
76
77	// Used by gensrcs when there is more than 1 shard to merge the outputs
78	// of each shard into a zip file.
79	gensrcsMerge = pctx.AndroidStaticRule("gensrcsMerge", blueprint.RuleParams{
80		Command:        "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
81		CommandDeps:    []string{"${soongZip}", "${zipSync}"},
82		Rspfile:        "${tmpZip}.rsp",
83		RspfileContent: "${zipArgs}",
84	}, "tmpZip", "genDir", "zipArgs")
85)
86
87func init() {
88	pctx.Import("android/soong/android")
89
90	pctx.HostBinToolVariable("soongZip", "soong_zip")
91	pctx.HostBinToolVariable("zipSync", "zipsync")
92}
93
94type SourceFileGenerator interface {
95	GeneratedSourceFiles() android.Paths
96	GeneratedHeaderDirs() android.Paths
97	GeneratedDeps() android.Paths
98}
99
100// Alias for android.HostToolProvider
101// Deprecated: use android.HostToolProvider instead.
102type HostToolProvider interface {
103	android.HostToolProvider
104}
105
106type hostToolDependencyTag struct {
107	blueprint.BaseDependencyTag
108	android.LicenseAnnotationToolchainDependencyTag
109	label string
110}
111
112func (t hostToolDependencyTag) AllowDisabledModuleDependency(target android.Module) bool {
113	// Allow depending on a disabled module if it's replaced by a prebuilt
114	// counterpart. We get the prebuilt through android.PrebuiltGetPreferred in
115	// GenerateAndroidBuildActions.
116	return target.IsReplacedByPrebuilt()
117}
118
119var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil)
120
121type generatorProperties struct {
122	// The command to run on one or more input files. Cmd supports substitution of a few variables.
123	//
124	// Available variables for substitution:
125	//
126	//  $(location): the path to the first entry in tools or tool_files.
127	//  $(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.
128	//  $(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.
129	//  $(in): one or more input files.
130	//  $(out): a single output file.
131	//  $(depfile): a file to which dependencies will be written, if the depfile property is set to true.
132	//  $(genDir): the sandbox directory for this tool; contains $(out).
133	//  $$: a literal $
134	Cmd *string
135
136	// Enable reading a file containing dependencies in gcc format after the command completes
137	Depfile *bool
138
139	// name of the modules (if any) that produces the host executable.   Leave empty for
140	// prebuilts or scripts that do not need a module to build them.
141	Tools []string
142
143	// Local file that is used as the tool
144	Tool_files []string `android:"path"`
145
146	// List of directories to export generated headers from
147	Export_include_dirs []string
148
149	// list of input files
150	Srcs []string `android:"path,arch_variant"`
151
152	// input files to exclude
153	Exclude_srcs []string `android:"path,arch_variant"`
154}
155
156type Module struct {
157	android.ModuleBase
158	android.DefaultableModuleBase
159	android.BazelModuleBase
160	android.ApexModuleBase
161
162	// For other packages to make their own genrules with extra
163	// properties
164	Extra interface{}
165
166	// CmdModifier can be set by wrappers around genrule to modify the command, for example to
167	// prefix environment variables to it.
168	CmdModifier func(ctx android.ModuleContext, cmd string) string
169
170	android.ImageInterface
171
172	properties generatorProperties
173
174	// For the different tasks that genrule and gensrc generate. genrule will
175	// generate 1 task, and gensrc will generate 1 or more tasks based on the
176	// number of shards the input files are sharded into.
177	taskGenerator taskFunc
178
179	rule        blueprint.Rule
180	rawCommands []string
181
182	exportedIncludeDirs android.Paths
183
184	outputFiles android.Paths
185	outputDeps  android.Paths
186
187	subName string
188	subDir  string
189
190	// Collect the module directory for IDE info in java/jdeps.go.
191	modulePaths []string
192}
193
194var _ android.MixedBuildBuildable = (*Module)(nil)
195
196type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
197
198type generateTask struct {
199	in         android.Paths
200	out        android.WritablePaths
201	depFile    android.WritablePath
202	copyTo     android.WritablePaths // For gensrcs to set on gensrcsMerge rule.
203	genDir     android.WritablePath
204	extraTools android.Paths // dependencies on tools used by the generator
205
206	cmd string
207	// For gensrsc sharding.
208	shard  int
209	shards int
210}
211
212func (g *Module) GeneratedSourceFiles() android.Paths {
213	return g.outputFiles
214}
215
216func (g *Module) Srcs() android.Paths {
217	return append(android.Paths{}, g.outputFiles...)
218}
219
220func (g *Module) GeneratedHeaderDirs() android.Paths {
221	return g.exportedIncludeDirs
222}
223
224func (g *Module) GeneratedDeps() android.Paths {
225	return g.outputDeps
226}
227
228func (g *Module) OutputFiles(tag string) (android.Paths, error) {
229	if tag == "" {
230		return append(android.Paths{}, g.outputFiles...), nil
231	}
232	// otherwise, tag should match one of outputs
233	for _, outputFile := range g.outputFiles {
234		if outputFile.Rel() == tag {
235			return android.Paths{outputFile}, nil
236		}
237	}
238	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
239}
240
241var _ android.SourceFileProducer = (*Module)(nil)
242var _ android.OutputFileProducer = (*Module)(nil)
243
244func toolDepsMutator(ctx android.BottomUpMutatorContext) {
245	if g, ok := ctx.Module().(*Module); ok {
246		for _, tool := range g.properties.Tools {
247			tag := hostToolDependencyTag{label: tool}
248			if m := android.SrcIsModule(tool); m != "" {
249				tool = m
250			}
251			ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), tag, tool)
252		}
253	}
254}
255
256func (g *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
257	g.generateCommonBuildActions(ctx)
258
259	label := g.GetBazelLabel(ctx, g)
260	bazelCtx := ctx.Config().BazelContext
261	filePaths, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
262	if err != nil {
263		ctx.ModuleErrorf(err.Error())
264		return
265	}
266
267	var bazelOutputFiles android.Paths
268	exportIncludeDirs := map[string]bool{}
269	for _, bazelOutputFile := range filePaths {
270		bazelOutputFiles = append(bazelOutputFiles, android.PathForBazelOutRelative(ctx, ctx.ModuleDir(), bazelOutputFile))
271		exportIncludeDirs[filepath.Dir(bazelOutputFile)] = true
272	}
273	g.outputFiles = bazelOutputFiles
274	g.outputDeps = bazelOutputFiles
275	for includePath, _ := range exportIncludeDirs {
276		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForBazelOut(ctx, includePath))
277	}
278}
279
280// generateCommonBuildActions contains build action generation logic
281// common to both the mixed build case and the legacy case of genrule processing.
282// To fully support genrule in mixed builds, the contents of this function should
283// approach zero; there should be no genrule action registration done directly
284// by Soong logic in the mixed-build case.
285func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) {
286	g.subName = ctx.ModuleSubDir()
287
288	// Collect the module directory for IDE info in java/jdeps.go.
289	g.modulePaths = append(g.modulePaths, ctx.ModuleDir())
290
291	if len(g.properties.Export_include_dirs) > 0 {
292		for _, dir := range g.properties.Export_include_dirs {
293			g.exportedIncludeDirs = append(g.exportedIncludeDirs,
294				android.PathForModuleGen(ctx, g.subDir, ctx.ModuleDir(), dir))
295		}
296	} else {
297		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, g.subDir))
298	}
299
300	locationLabels := map[string]location{}
301	firstLabel := ""
302
303	addLocationLabel := func(label string, loc location) {
304		if firstLabel == "" {
305			firstLabel = label
306		}
307		if _, exists := locationLabels[label]; !exists {
308			locationLabels[label] = loc
309		} else {
310			ctx.ModuleErrorf("multiple locations for label %q: %q and %q (do you have duplicate srcs entries?)",
311				label, locationLabels[label], loc)
312		}
313	}
314
315	var tools android.Paths
316	var packagedTools []android.PackagingSpec
317	if len(g.properties.Tools) > 0 {
318		seenTools := make(map[string]bool)
319
320		ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
321			switch tag := ctx.OtherModuleDependencyTag(module).(type) {
322			case hostToolDependencyTag:
323				tool := ctx.OtherModuleName(module)
324				if m, ok := module.(android.Module); ok {
325					// Necessary to retrieve any prebuilt replacement for the tool, since
326					// toolDepsMutator runs too late for the prebuilt mutators to have
327					// replaced the dependency.
328					module = android.PrebuiltGetPreferred(ctx, m)
329				}
330
331				switch t := module.(type) {
332				case android.HostToolProvider:
333					// A HostToolProvider provides the path to a tool, which will be copied
334					// into the sandbox.
335					if !t.(android.Module).Enabled() {
336						if ctx.Config().AllowMissingDependencies() {
337							ctx.AddMissingDependencies([]string{tool})
338						} else {
339							ctx.ModuleErrorf("depends on disabled module %q", tool)
340						}
341						return
342					}
343					path := t.HostToolPath()
344					if !path.Valid() {
345						ctx.ModuleErrorf("host tool %q missing output file", tool)
346						return
347					}
348					if specs := t.TransitivePackagingSpecs(); specs != nil {
349						// If the HostToolProvider has PackgingSpecs, which are definitions of the
350						// required relative locations of the tool and its dependencies, use those
351						// instead.  They will be copied to those relative locations in the sbox
352						// sandbox.
353						packagedTools = append(packagedTools, specs...)
354						// Assume that the first PackagingSpec of the module is the tool.
355						addLocationLabel(tag.label, packagedToolLocation{specs[0]})
356					} else {
357						tools = append(tools, path.Path())
358						addLocationLabel(tag.label, toolLocation{android.Paths{path.Path()}})
359					}
360				case bootstrap.GoBinaryTool:
361					// A GoBinaryTool provides the install path to a tool, which will be copied.
362					p := android.PathForGoBinary(ctx, t)
363					tools = append(tools, p)
364					addLocationLabel(tag.label, toolLocation{android.Paths{p}})
365				default:
366					ctx.ModuleErrorf("%q is not a host tool provider", tool)
367					return
368				}
369
370				seenTools[tag.label] = true
371			}
372		})
373
374		// If AllowMissingDependencies is enabled, the build will not have stopped when
375		// AddFarVariationDependencies was called on a missing tool, which will result in nonsensical
376		// "cmd: unknown location label ..." errors later.  Add a placeholder file to the local label.
377		// The command that uses this placeholder file will never be executed because the rule will be
378		// replaced with an android.Error rule reporting the missing dependencies.
379		if ctx.Config().AllowMissingDependencies() {
380			for _, tool := range g.properties.Tools {
381				if !seenTools[tool] {
382					addLocationLabel(tool, errorLocation{"***missing tool " + tool + "***"})
383				}
384			}
385		}
386	}
387
388	if ctx.Failed() {
389		return
390	}
391
392	for _, toolFile := range g.properties.Tool_files {
393		paths := android.PathsForModuleSrc(ctx, []string{toolFile})
394		tools = append(tools, paths...)
395		addLocationLabel(toolFile, toolLocation{paths})
396	}
397
398	includeDirInPaths := ctx.DeviceConfig().BuildBrokenInputDir(g.Name())
399	var srcFiles android.Paths
400	for _, in := range g.properties.Srcs {
401		paths, missingDeps := android.PathsAndMissingDepsRelativeToModuleSourceDir(android.SourceInput{
402			Context: ctx, Paths: []string{in}, ExcludePaths: g.properties.Exclude_srcs, IncludeDirs: includeDirInPaths,
403		})
404		if len(missingDeps) > 0 {
405			if !ctx.Config().AllowMissingDependencies() {
406				panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator",
407					missingDeps))
408			}
409
410			// If AllowMissingDependencies is enabled, the build will not have stopped when
411			// the dependency was added on a missing SourceFileProducer module, which will result in nonsensical
412			// "cmd: label ":..." has no files" errors later.  Add a placeholder file to the local label.
413			// The command that uses this placeholder file will never be executed because the rule will be
414			// replaced with an android.Error rule reporting the missing dependencies.
415			ctx.AddMissingDependencies(missingDeps)
416			addLocationLabel(in, errorLocation{"***missing srcs " + in + "***"})
417		} else {
418			srcFiles = append(srcFiles, paths...)
419			addLocationLabel(in, inputLocation{paths})
420		}
421	}
422
423	var copyFrom android.Paths
424	var outputFiles android.WritablePaths
425	var zipArgs strings.Builder
426
427	cmd := String(g.properties.Cmd)
428	if g.CmdModifier != nil {
429		cmd = g.CmdModifier(ctx, cmd)
430	}
431
432	// Generate tasks, either from genrule or gensrcs.
433	for _, task := range g.taskGenerator(ctx, cmd, srcFiles) {
434		if len(task.out) == 0 {
435			ctx.ModuleErrorf("must have at least one output file")
436			return
437		}
438
439		// Pick a unique path outside the task.genDir for the sbox manifest textproto,
440		// a unique rule name, and the user-visible description.
441		manifestName := "genrule.sbox.textproto"
442		desc := "generate"
443		name := "generator"
444		if task.shards > 0 {
445			manifestName = "genrule_" + strconv.Itoa(task.shard) + ".sbox.textproto"
446			desc += " " + strconv.Itoa(task.shard)
447			name += strconv.Itoa(task.shard)
448		} else if len(task.out) == 1 {
449			desc += " " + task.out[0].Base()
450		}
451
452		manifestPath := android.PathForModuleOut(ctx, manifestName)
453
454		// Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
455		rule := android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath).SandboxTools()
456		cmd := rule.Command()
457
458		for _, out := range task.out {
459			addLocationLabel(out.Rel(), outputLocation{out})
460		}
461
462		referencedDepfile := false
463
464		rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
465			// report the error directly without returning an error to android.Expand to catch multiple errors in a
466			// single run
467			reportError := func(fmt string, args ...interface{}) (string, error) {
468				ctx.PropertyErrorf("cmd", fmt, args...)
469				return "SOONG_ERROR", nil
470			}
471
472			// Apply shell escape to each cases to prevent source file paths containing $ from being evaluated in shell
473			switch name {
474			case "location":
475				if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
476					return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
477				}
478				loc := locationLabels[firstLabel]
479				paths := loc.Paths(cmd)
480				if len(paths) == 0 {
481					return reportError("default label %q has no files", firstLabel)
482				} else if len(paths) > 1 {
483					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
484						firstLabel, firstLabel)
485				}
486				return proptools.ShellEscape(paths[0]), nil
487			case "in":
488				return strings.Join(proptools.ShellEscapeList(cmd.PathsForInputs(srcFiles)), " "), nil
489			case "out":
490				var sandboxOuts []string
491				for _, out := range task.out {
492					sandboxOuts = append(sandboxOuts, cmd.PathForOutput(out))
493				}
494				return strings.Join(proptools.ShellEscapeList(sandboxOuts), " "), nil
495			case "depfile":
496				referencedDepfile = true
497				if !Bool(g.properties.Depfile) {
498					return reportError("$(depfile) used without depfile property")
499				}
500				return "__SBOX_DEPFILE__", nil
501			case "genDir":
502				return proptools.ShellEscape(cmd.PathForOutput(task.genDir)), nil
503			default:
504				if strings.HasPrefix(name, "location ") {
505					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
506					if loc, ok := locationLabels[label]; ok {
507						paths := loc.Paths(cmd)
508						if len(paths) == 0 {
509							return reportError("label %q has no files", label)
510						} else if len(paths) > 1 {
511							return reportError("label %q has multiple files, use $(locations %s) to reference it",
512								label, label)
513						}
514						return proptools.ShellEscape(paths[0]), nil
515					} else {
516						return reportError("unknown location label %q is not in srcs, out, tools or tool_files.", label)
517					}
518				} else if strings.HasPrefix(name, "locations ") {
519					label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
520					if loc, ok := locationLabels[label]; ok {
521						paths := loc.Paths(cmd)
522						if len(paths) == 0 {
523							return reportError("label %q has no files", label)
524						}
525						return proptools.ShellEscape(strings.Join(paths, " ")), nil
526					} else {
527						return reportError("unknown locations label %q is not in srcs, out, tools or tool_files.", label)
528					}
529				} else {
530					return reportError("unknown variable '$(%s)'", name)
531				}
532			}
533		})
534
535		if err != nil {
536			ctx.PropertyErrorf("cmd", "%s", err.Error())
537			return
538		}
539
540		if Bool(g.properties.Depfile) && !referencedDepfile {
541			ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
542			return
543		}
544		g.rawCommands = append(g.rawCommands, rawCommand)
545
546		cmd.Text(rawCommand)
547		cmd.ImplicitOutputs(task.out)
548		cmd.Implicits(task.in)
549		cmd.ImplicitTools(tools)
550		cmd.ImplicitTools(task.extraTools)
551		cmd.ImplicitPackagedTools(packagedTools)
552		if Bool(g.properties.Depfile) {
553			cmd.ImplicitDepFile(task.depFile)
554		}
555
556		// Create the rule to run the genrule command inside sbox.
557		rule.Build(name, desc)
558
559		if len(task.copyTo) > 0 {
560			// If copyTo is set, multiple shards need to be copied into a single directory.
561			// task.out contains the per-shard paths, and copyTo contains the corresponding
562			// final path.  The files need to be copied into the final directory by a
563			// single rule so it can remove the directory before it starts to ensure no
564			// old files remain.  zipsync already does this, so build up zipArgs that
565			// zip all the per-shard directories into a single zip.
566			outputFiles = append(outputFiles, task.copyTo...)
567			copyFrom = append(copyFrom, task.out.Paths()...)
568			zipArgs.WriteString(" -C " + task.genDir.String())
569			zipArgs.WriteString(android.JoinWithPrefix(task.out.Strings(), " -f "))
570		} else {
571			outputFiles = append(outputFiles, task.out...)
572		}
573	}
574
575	if len(copyFrom) > 0 {
576		// Create a rule that zips all the per-shard directories into a single zip and then
577		// uses zipsync to unzip it into the final directory.
578		ctx.Build(pctx, android.BuildParams{
579			Rule:        gensrcsMerge,
580			Implicits:   copyFrom,
581			Outputs:     outputFiles,
582			Description: "merge shards",
583			Args: map[string]string{
584				"zipArgs": zipArgs.String(),
585				"tmpZip":  android.PathForModuleGen(ctx, g.subDir+".zip").String(),
586				"genDir":  android.PathForModuleGen(ctx, g.subDir).String(),
587			},
588		})
589	}
590
591	g.outputFiles = outputFiles.Paths()
592}
593
594func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
595	// Allowlist genrule to use depfile until we have a solution to remove it.
596	// TODO(b/235582219): Remove allowlist for genrule
597	if ctx.ModuleType() == "gensrcs" &&
598		!ctx.DeviceConfig().BuildBrokenDepfile() &&
599		Bool(g.properties.Depfile) {
600		ctx.PropertyErrorf(
601			"depfile",
602			"Deprecated to ensure the module type is convertible to Bazel. "+
603				"Try specifying the dependencies explicitly so that there is no need to use depfile. "+
604				"If not possible, the escape hatch is to use BUILD_BROKEN_DEPFILE to bypass the error.")
605	}
606
607	g.generateCommonBuildActions(ctx)
608
609	// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
610	// the genrules on AOSP. That will make things simpler to look at the graph in the common
611	// case. For larger sets of outputs, inject a phony target in between to limit ninja file
612	// growth.
613	if len(g.outputFiles) <= 6 {
614		g.outputDeps = g.outputFiles
615	} else {
616		phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
617		ctx.Build(pctx, android.BuildParams{
618			Rule:   blueprint.Phony,
619			Output: phonyFile,
620			Inputs: g.outputFiles,
621		})
622		g.outputDeps = android.Paths{phonyFile}
623	}
624}
625
626func (g *Module) QueueBazelCall(ctx android.BaseModuleContext) {
627	bazelCtx := ctx.Config().BazelContext
628	bazelCtx.QueueBazelRequest(g.GetBazelLabel(ctx, g), cquery.GetOutputFiles, android.GetConfigKey(ctx))
629}
630
631func (g *Module) IsMixedBuildSupported(ctx android.BaseModuleContext) bool {
632	return true
633}
634
635// Collect information for opening IDE project files in java/jdeps.go.
636func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
637	dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
638	for _, src := range g.properties.Srcs {
639		if strings.HasPrefix(src, ":") {
640			src = strings.Trim(src, ":")
641			dpInfo.Deps = append(dpInfo.Deps, src)
642		}
643	}
644	dpInfo.Paths = append(dpInfo.Paths, g.modulePaths...)
645}
646
647func (g *Module) AndroidMk() android.AndroidMkData {
648	return android.AndroidMkData{
649		Class:      "ETC",
650		OutputFile: android.OptionalPathForPath(g.outputFiles[0]),
651		SubName:    g.subName,
652		Extra: []android.AndroidMkExtraFunc{
653			func(w io.Writer, outputFile android.Path) {
654				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
655			},
656		},
657		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
658			android.WriteAndroidMkData(w, data)
659			if data.SubName != "" {
660				fmt.Fprintln(w, ".PHONY:", name)
661				fmt.Fprintln(w, name, ":", name+g.subName)
662			}
663		},
664	}
665}
666
667var _ android.ApexModule = (*Module)(nil)
668
669// Implements android.ApexModule
670func (g *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
671	sdkVersion android.ApiLevel) error {
672	// Because generated outputs are checked by client modules(e.g. cc_library, ...)
673	// we can safely ignore the check here.
674	return nil
675}
676
677func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module {
678	module := &Module{
679		taskGenerator: taskGenerator,
680	}
681
682	module.AddProperties(props...)
683	module.AddProperties(&module.properties)
684
685	module.ImageInterface = noopImageInterface{}
686
687	return module
688}
689
690type noopImageInterface struct{}
691
692func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext)                 {}
693func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool            { return false }
694func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool         { return false }
695func (x noopImageInterface) VendorRamdiskVariantNeeded(android.BaseModuleContext) bool   { return false }
696func (x noopImageInterface) DebugRamdiskVariantNeeded(android.BaseModuleContext) bool    { return false }
697func (x noopImageInterface) RecoveryVariantNeeded(android.BaseModuleContext) bool        { return false }
698func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil }
699func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
700}
701
702func NewGenSrcs() *Module {
703	properties := &genSrcsProperties{}
704
705	// finalSubDir is the name of the subdirectory that output files will be generated into.
706	// It is used so that per-shard directories can be placed alongside it an then finally
707	// merged into it.
708	const finalSubDir = "gensrcs"
709
710	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
711		shardSize := defaultShardSize
712		if s := properties.Shard_size; s != nil {
713			shardSize = int(*s)
714		}
715
716		// gensrcs rules can easily hit command line limits by repeating the command for
717		// every input file.  Shard the input files into groups.
718		shards := android.ShardPaths(srcFiles, shardSize)
719		var generateTasks []generateTask
720
721		for i, shard := range shards {
722			var commands []string
723			var outFiles android.WritablePaths
724			var commandDepFiles []string
725			var copyTo android.WritablePaths
726
727			// When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
728			// shard will be write to their own directories and then be merged together
729			// into finalSubDir.  If sharding is not enabled (i.e. len(shards) == 1),
730			// the sbox rule will write directly to finalSubDir.
731			genSubDir := finalSubDir
732			if len(shards) > 1 {
733				genSubDir = strconv.Itoa(i)
734			}
735
736			genDir := android.PathForModuleGen(ctx, genSubDir)
737			// TODO(ccross): this RuleBuilder is a hack to be able to call
738			// rule.Command().PathForOutput.  Replace this with passing the rule into the
739			// generator.
740			rule := android.NewRuleBuilder(pctx, ctx).Sbox(genDir, nil).SandboxTools()
741
742			for _, in := range shard {
743				outFile := android.GenPathWithExt(ctx, finalSubDir, in, String(properties.Output_extension))
744
745				// If sharding is enabled, then outFile is the path to the output file in
746				// the shard directory, and copyTo is the path to the output file in the
747				// final directory.
748				if len(shards) > 1 {
749					shardFile := android.GenPathWithExt(ctx, genSubDir, in, String(properties.Output_extension))
750					copyTo = append(copyTo, outFile)
751					outFile = shardFile
752				}
753
754				outFiles = append(outFiles, outFile)
755
756				// pre-expand the command line to replace $in and $out with references to
757				// a single input and output file.
758				command, err := android.Expand(rawCommand, func(name string) (string, error) {
759					switch name {
760					case "in":
761						return in.String(), nil
762					case "out":
763						return rule.Command().PathForOutput(outFile), nil
764					case "depfile":
765						// Generate a depfile for each output file.  Store the list for
766						// later in order to combine them all into a single depfile.
767						depFile := rule.Command().PathForOutput(outFile.ReplaceExtension(ctx, "d"))
768						commandDepFiles = append(commandDepFiles, depFile)
769						return depFile, nil
770					default:
771						return "$(" + name + ")", nil
772					}
773				})
774				if err != nil {
775					ctx.PropertyErrorf("cmd", err.Error())
776				}
777
778				// escape the command in case for example it contains '#', an odd number of '"', etc
779				command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command))
780				commands = append(commands, command)
781			}
782			fullCommand := strings.Join(commands, " && ")
783
784			var outputDepfile android.WritablePath
785			var extraTools android.Paths
786			if len(commandDepFiles) > 0 {
787				// Each command wrote to a depfile, but ninja can only handle one
788				// depfile per rule.  Use the dep_fixer tool at the end of the
789				// command to combine all the depfiles into a single output depfile.
790				outputDepfile = android.PathForModuleGen(ctx, genSubDir, "gensrcs.d")
791				depFixerTool := ctx.Config().HostToolPath(ctx, "dep_fixer")
792				fullCommand += fmt.Sprintf(" && %s -o $(depfile) %s",
793					rule.Command().PathForTool(depFixerTool),
794					strings.Join(commandDepFiles, " "))
795				extraTools = append(extraTools, depFixerTool)
796			}
797
798			generateTasks = append(generateTasks, generateTask{
799				in:         shard,
800				out:        outFiles,
801				depFile:    outputDepfile,
802				copyTo:     copyTo,
803				genDir:     genDir,
804				cmd:        fullCommand,
805				shard:      i,
806				shards:     len(shards),
807				extraTools: extraTools,
808			})
809		}
810
811		return generateTasks
812	}
813
814	g := generatorFactory(taskGenerator, properties)
815	g.subDir = finalSubDir
816	return g
817}
818
819func GenSrcsFactory() android.Module {
820	m := NewGenSrcs()
821	android.InitAndroidModule(m)
822	android.InitBazelModule(m)
823	return m
824}
825
826type genSrcsProperties struct {
827	// extension that will be substituted for each output file
828	Output_extension *string
829
830	// maximum number of files that will be passed on a single command line.
831	Shard_size *int64
832}
833
834type bazelGensrcsAttributes struct {
835	Srcs             bazel.LabelListAttribute
836	Output_extension *string
837	Tools            bazel.LabelListAttribute
838	Cmd              string
839}
840
841const defaultShardSize = 50
842
843func NewGenRule() *Module {
844	properties := &genRuleProperties{}
845
846	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
847		outs := make(android.WritablePaths, len(properties.Out))
848		var depFile android.WritablePath
849		for i, out := range properties.Out {
850			outPath := android.PathForModuleGen(ctx, out)
851			if i == 0 {
852				depFile = outPath.ReplaceExtension(ctx, "d")
853			}
854			outs[i] = outPath
855		}
856		return []generateTask{{
857			in:      srcFiles,
858			out:     outs,
859			depFile: depFile,
860			genDir:  android.PathForModuleGen(ctx),
861			cmd:     rawCommand,
862		}}
863	}
864
865	return generatorFactory(taskGenerator, properties)
866}
867
868func GenRuleFactory() android.Module {
869	m := NewGenRule()
870	android.InitAndroidModule(m)
871	android.InitDefaultableModule(m)
872	android.InitBazelModule(m)
873	return m
874}
875
876type genRuleProperties struct {
877	// names of the output files that will be generated
878	Out []string
879}
880
881type bazelGenruleAttributes struct {
882	Srcs  bazel.LabelListAttribute
883	Outs  []string
884	Tools bazel.LabelListAttribute
885	Cmd   string
886}
887
888// ConvertWithBp2build converts a Soong module -> Bazel target.
889func (m *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
890	// Bazel only has the "tools" attribute.
891	tools_prop := android.BazelLabelForModuleDeps(ctx, m.properties.Tools)
892	tool_files_prop := android.BazelLabelForModuleSrc(ctx, m.properties.Tool_files)
893	tools_prop.Append(tool_files_prop)
894
895	tools := bazel.MakeLabelListAttribute(tools_prop)
896	srcs := bazel.LabelListAttribute{}
897	srcs_labels := bazel.LabelList{}
898	// Only cc_genrule is arch specific
899	if ctx.ModuleType() == "cc_genrule" {
900		for axis, configToProps := range m.GetArchVariantProperties(ctx, &generatorProperties{}) {
901			for config, props := range configToProps {
902				if props, ok := props.(*generatorProperties); ok {
903					labels := android.BazelLabelForModuleSrcExcludes(ctx, props.Srcs, props.Exclude_srcs)
904					srcs_labels.Append(labels)
905					srcs.SetSelectValue(axis, config, labels)
906				}
907			}
908		}
909	} else {
910		srcs_labels = android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs)
911		srcs = bazel.MakeLabelListAttribute(srcs_labels)
912	}
913
914	var allReplacements bazel.LabelList
915	allReplacements.Append(tools.Value)
916	allReplacements.Append(bazel.FirstUniqueBazelLabelList(srcs_labels))
917
918	// Replace in and out variables with $< and $@
919	var cmd string
920	if m.properties.Cmd != nil {
921		if ctx.ModuleType() == "gensrcs" {
922			cmd = strings.ReplaceAll(*m.properties.Cmd, "$(in)", "$(SRC)")
923			cmd = strings.ReplaceAll(cmd, "$(out)", "$(OUT)")
924		} else {
925			cmd = strings.Replace(*m.properties.Cmd, "$(in)", "$(SRCS)", -1)
926			cmd = strings.Replace(cmd, "$(out)", "$(OUTS)", -1)
927		}
928		cmd = strings.Replace(cmd, "$(genDir)", "$(RULEDIR)", -1)
929		if len(tools.Value.Includes) > 0 {
930			cmd = strings.Replace(cmd, "$(location)", fmt.Sprintf("$(location %s)", tools.Value.Includes[0].Label), -1)
931			cmd = strings.Replace(cmd, "$(locations)", fmt.Sprintf("$(locations %s)", tools.Value.Includes[0].Label), -1)
932		}
933		for _, l := range allReplacements.Includes {
934			bpLoc := fmt.Sprintf("$(location %s)", l.OriginalModuleName)
935			bpLocs := fmt.Sprintf("$(locations %s)", l.OriginalModuleName)
936			bazelLoc := fmt.Sprintf("$(location %s)", l.Label)
937			bazelLocs := fmt.Sprintf("$(locations %s)", l.Label)
938			cmd = strings.Replace(cmd, bpLoc, bazelLoc, -1)
939			cmd = strings.Replace(cmd, bpLocs, bazelLocs, -1)
940		}
941	}
942
943	tags := android.ApexAvailableTags(m)
944
945	if ctx.ModuleType() == "gensrcs" {
946		// The Output_extension prop is not in an immediately accessible field
947		// in the Module struct, so use GetProperties and cast it
948		// to the known struct prop.
949		var outputExtension *string
950		for _, propIntf := range m.GetProperties() {
951			if props, ok := propIntf.(*genSrcsProperties); ok {
952				outputExtension = props.Output_extension
953				break
954			}
955		}
956		props := bazel.BazelTargetModuleProperties{
957			Rule_class:        "gensrcs",
958			Bzl_load_location: "//build/bazel/rules:gensrcs.bzl",
959		}
960		attrs := &bazelGensrcsAttributes{
961			Srcs:             srcs,
962			Output_extension: outputExtension,
963			Cmd:              cmd,
964			Tools:            tools,
965		}
966		ctx.CreateBazelTargetModule(props, android.CommonAttributes{
967			Name: m.Name(),
968			Tags: tags,
969		}, attrs)
970	} else {
971		// The Out prop is not in an immediately accessible field
972		// in the Module struct, so use GetProperties and cast it
973		// to the known struct prop.
974		var outs []string
975		for _, propIntf := range m.GetProperties() {
976			if props, ok := propIntf.(*genRuleProperties); ok {
977				outs = props.Out
978				break
979			}
980		}
981		attrs := &bazelGenruleAttributes{
982			Srcs:  srcs,
983			Outs:  outs,
984			Cmd:   cmd,
985			Tools: tools,
986		}
987		props := bazel.BazelTargetModuleProperties{
988			Rule_class: "genrule",
989		}
990		ctx.CreateBazelTargetModule(props, android.CommonAttributes{
991			Name: m.Name(),
992			Tags: tags,
993		}, attrs)
994	}
995}
996
997var Bool = proptools.Bool
998var String = proptools.String
999
1000// Defaults
1001type Defaults struct {
1002	android.ModuleBase
1003	android.DefaultsModuleBase
1004}
1005
1006func defaultsFactory() android.Module {
1007	return DefaultsFactory()
1008}
1009
1010func DefaultsFactory(props ...interface{}) android.Module {
1011	module := &Defaults{}
1012
1013	module.AddProperties(props...)
1014	module.AddProperties(
1015		&generatorProperties{},
1016		&genRuleProperties{},
1017	)
1018
1019	android.InitDefaultsModule(module)
1020
1021	return module
1022}
1023