• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2017 The Android Open Source Project
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/*
16Package wayland_protocol defines an plugin module for the Soong build system,
17which makes it easier to generate code from a list of Wayland protocol files.
18
19The primary build module is "wayland_protocol_codegen", which takes a list of
20protocol files, and runs a configurable code-generation tool to generate
21source code for each one. There is also a "wayland_protocol_codegen_defaults"
22for setting common properties.
23
24This package is substantially similar to the base "android/soong/genrule"
25package, which was originally used for inspiration for this one, and has
26been recently restructured so that it can be kept in sync with a tool like
27"vimdiff" to keep things in sync as needed.
28
29Notable differences:
30
31  - This package implements a more powerful template mechanism for specifying
32    what output path/filename should be used for each source filename. The
33    genrule package only allows the extension on each source filename to be
34    replaced.
35
36  - This package drops support for depfiles, after observing comments that
37    they are problematic in the genrule package sources.
38
39  - This package drops "Extra" and "CmdModifier" from the public Module
40    structure, as this module is not expected to be extended.
41
42  - This package drops "rule" from the public Module structure, as it was
43    unused but present in genrule.
44
45# Usage
46
47	wayland_protocol_codegen {
48		// A standard target name.
49		name: "wayland_extension_protocol_sources",
50
51		// A simple template for generating output filenames.
52		output: "$(in).c"
53
54		// The command line template. See "Cmd".
55		cmd: "$(location wayland_scanner) code < $(in) > $(out)",
56
57		// Protocol source files for the expansion.
58		srcs: [":wayland_extension_protocols"],
59
60		// Any buildable binaries to use as tools
61		tools: ["wayland_scanner"],
62
63		// Any source files to be used  (scripts, template files)
64		tools_files: [],
65	}
66*/
67package soong_wayland_protocol_codegen
68
69import (
70	"fmt"
71	"strconv"
72	"strings"
73
74	"github.com/google/blueprint"
75	"github.com/google/blueprint/bootstrap"
76	"github.com/google/blueprint/proptools"
77
78	"android/soong/android"
79	"android/soong/genrule"
80)
81
82func init() {
83	registerCodeGenBuildComponents(android.InitRegistrationContext)
84}
85
86func registerCodeGenBuildComponents(ctx android.RegistrationContext) {
87	ctx.RegisterModuleType("wayland_protocol_codegen_defaults", defaultsFactory)
88
89	ctx.RegisterModuleType("wayland_protocol_codegen", codegenFactory)
90
91	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
92		ctx.BottomUp("wayland_protocol_codegen_tool_deps", toolDepsMutator).Parallel()
93	})
94}
95
96var (
97	pctx = android.NewPackageContext("android/soong/external/wayland_protocol_codegen")
98
99	// Used by wayland_protocol_codegen when there is more than 1 shard to merge the outputs
100	// of each shard into a zip file.
101	gensrcsMerge = pctx.AndroidStaticRule("wayland_protocol_codegenMerge", blueprint.RuleParams{
102		Command:        "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
103		CommandDeps:    []string{"${soongZip}", "${zipSync}"},
104		Rspfile:        "${tmpZip}.rsp",
105		RspfileContent: "${zipArgs}",
106	}, "tmpZip", "genDir", "zipArgs")
107)
108
109func init() {
110	pctx.Import("android/soong/android")
111
112	pctx.HostBinToolVariable("soongZip", "soong_zip")
113	pctx.HostBinToolVariable("zipSync", "zipsync")
114}
115
116type hostToolDependencyTag struct {
117	blueprint.BaseDependencyTag
118	android.LicenseAnnotationToolchainDependencyTag
119	label string
120}
121
122func (t hostToolDependencyTag) AllowDisabledModuleDependency(target android.Module) bool {
123	// Allow depending on a disabled module if it's replaced by a prebuilt
124	// counterpart. We get the prebuilt through android.PrebuiltGetPreferred in
125	// GenerateAndroidBuildActions.
126	return target.IsReplacedByPrebuilt()
127}
128
129var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil)
130
131type generatorProperties struct {
132	// The command to run on one or more input files. Cmd supports
133	// substitution of a few variables (the actual substitution is implemented
134	// in GenerateAndroidBuildActions below)
135	//
136	// Available variables for substitution:
137	//
138	//	- $(location)
139	//		the path to the first entry in tools or tool_files
140	//	- $(location <label>)
141	//		the path to the tool, tool_file, input or output with name <label>. Use
142	//		$(location) if <label> refers to a rule that outputs exactly one file.
143	//	- $(locations <label>)
144	//		the paths to the tools, tool_files, inputs or outputs with name
145	//		<label>. Use $(locations) if <label> refers to a rule that outputs two
146	//		or more files.
147	//	- $(in)
148	//		one or more input files
149	//	- $(out)
150	//		a single output file
151	//	- $(genDir)
152	//		the sandbox directory for this tool; contains $(out)
153	//	- $$
154	//		a literal '$'
155	//
156	// All files used must be declared as inputs (to ensure proper up-to-date
157	// checks). Use "$(in)" directly in Cmd to ensure that all inputs used are
158	// declared.
159	Cmd *string
160
161	// name of the modules (if any) that produces the host executable. Leave
162	// empty for prebuilts or scripts that do not need a module to build them.
163	Tools []string
164
165	// Local source files that are used as scripts or other input files needed
166	// by a tool.
167	Tool_files []string `android:"path"`
168
169	// List of directories to export generated headers from.
170	Export_include_dirs []string
171
172	// List of input files.
173	Srcs []string `android:"path,arch_variant"`
174
175	// Input files to exclude.
176	Exclude_srcs []string `android:"path,arch_variant"`
177}
178
179type Module struct {
180	android.ModuleBase
181	android.DefaultableModuleBase
182	android.ApexModuleBase
183
184	android.ImageInterface
185
186	properties generatorProperties
187
188	taskGenerator taskFunc
189
190	rawCommands []string
191
192	exportedIncludeDirs android.Paths
193
194	outputFiles android.Paths
195	outputDeps  android.Paths
196
197	subName string
198	subDir  string
199
200	// Collect the module directory for IDE info in java/jdeps.go.
201	modulePaths []string
202}
203
204type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
205
206type generateTask struct {
207	in     android.Paths
208	out    android.WritablePaths
209	copyTo android.WritablePaths
210	genDir android.WritablePath
211	cmd    string
212
213	shard  int
214	shards int
215}
216
217// Part of genrule.SourceFileGenerator.
218// Returns the list of generated source files.
219func (g *Module) GeneratedSourceFiles() android.Paths {
220	return g.outputFiles
221}
222
223// Part of genrule.SourceFileGenerator.
224// Returns the list of input source files.
225func (g *Module) Srcs() android.Paths {
226	return append(android.Paths{}, g.outputFiles...)
227}
228
229// Part of genrule.SourceFileGenerator.
230// Returns the list of the list of exported include paths.
231func (g *Module) GeneratedHeaderDirs() android.Paths {
232	return g.exportedIncludeDirs
233}
234
235// Part of genrule.SourceFileGenerator.
236// Returns the list of files to be used as dependencies when using
237// GeneratedHeaderDirs
238func (g *Module) GeneratedDeps() android.Paths {
239	return g.outputDeps
240}
241
242// Ensure Module implements the genrule.SourceFileGenerator interface.
243var _ genrule.SourceFileGenerator = (*Module)(nil)
244
245// Ensure Module implements the android.SourceFileProducer interface.
246var _ android.SourceFileProducer = (*Module)(nil)
247
248func toolDepsMutator(ctx android.BottomUpMutatorContext) {
249	if g, ok := ctx.Module().(*Module); ok {
250		for _, tool := range g.properties.Tools {
251			tag := hostToolDependencyTag{label: tool}
252			if m := android.SrcIsModule(tool); m != "" {
253				tool = m
254			}
255			ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), tag, tool)
256		}
257	}
258}
259
260// Part of android.Module.
261// Generates all the rules and builds commands used by this module instance.
262func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) {
263	g.subName = ctx.ModuleSubDir()
264
265	// Collect the module directory for IDE info in java/jdeps.go.
266	g.modulePaths = append(g.modulePaths, ctx.ModuleDir())
267
268	if len(g.properties.Export_include_dirs) > 0 {
269		for _, dir := range g.properties.Export_include_dirs {
270			g.exportedIncludeDirs = append(g.exportedIncludeDirs,
271				android.PathForModuleGen(ctx, g.subDir, ctx.ModuleDir(), dir))
272		}
273	} else {
274		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, g.subDir))
275	}
276
277	locationLabels := map[string]location{}
278	firstLabel := ""
279
280	addLocationLabel := func(label string, loc location) {
281		if firstLabel == "" {
282			firstLabel = label
283		}
284		if _, exists := locationLabels[label]; !exists {
285			locationLabels[label] = loc
286		} else {
287			ctx.ModuleErrorf("multiple locations for label %q: %q and %q (do you have duplicate srcs entries?)",
288				label, locationLabels[label], loc)
289		}
290	}
291
292	var tools android.Paths
293	var packagedTools []android.PackagingSpec
294	if len(g.properties.Tools) > 0 {
295		seenTools := make(map[string]bool)
296
297		ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
298			switch tag := ctx.OtherModuleDependencyTag(module).(type) {
299			case hostToolDependencyTag:
300				tool := ctx.OtherModuleName(module)
301				if m, ok := module.(android.Module); ok {
302					// Necessary to retrieve any prebuilt replacement for the tool, since
303					// toolDepsMutator runs too late for the prebuilt mutators to have
304					// replaced the dependency.
305					module = android.PrebuiltGetPreferred(ctx, m)
306				}
307
308				switch t := module.(type) {
309				case android.HostToolProvider:
310					// A HostToolProvider provides the path to a tool, which will be copied
311					// into the sandbox.
312					if !t.(android.Module).Enabled(ctx) {
313						if ctx.Config().AllowMissingDependencies() {
314							ctx.AddMissingDependencies([]string{tool})
315						} else {
316							ctx.ModuleErrorf("depends on disabled module %q", tool)
317						}
318						return
319					}
320					path := t.HostToolPath()
321					if !path.Valid() {
322						ctx.ModuleErrorf("host tool %q missing output file", tool)
323						return
324					}
325					if specs := t.TransitivePackagingSpecs(); specs != nil {
326						// If the HostToolProvider has PackgingSpecs, which are definitions of the
327						// required relative locations of the tool and its dependencies, use those
328						// instead.  They will be copied to those relative locations in the sbox
329						// sandbox.
330						packagedTools = append(packagedTools, specs...)
331						// Assume that the first PackagingSpec of the module is the tool.
332						addLocationLabel(tag.label, packagedToolLocation{specs[0]})
333					} else {
334						tools = append(tools, path.Path())
335						addLocationLabel(tag.label, toolLocation{android.Paths{path.Path()}})
336					}
337				case bootstrap.GoBinaryTool:
338					// A GoBinaryTool provides the install path to a tool, which will be copied.
339					p := android.PathForGoBinary(ctx, t)
340					tools = append(tools, p)
341					addLocationLabel(tag.label, toolLocation{android.Paths{p}})
342				default:
343					ctx.ModuleErrorf("%q is not a host tool provider", tool)
344					return
345				}
346
347				seenTools[tag.label] = true
348			}
349		})
350
351		// If AllowMissingDependencies is enabled, the build will not have stopped when
352		// AddFarVariationDependencies was called on a missing tool, which will result in nonsensical
353		// "cmd: unknown location label ..." errors later.  Add a placeholder file to the local label.
354		// The command that uses this placeholder file will never be executed because the rule will be
355		// replaced with an android.Error rule reporting the missing dependencies.
356		if ctx.Config().AllowMissingDependencies() {
357			for _, tool := range g.properties.Tools {
358				if !seenTools[tool] {
359					addLocationLabel(tool, errorLocation{"***missing tool " + tool + "***"})
360				}
361			}
362		}
363	}
364
365	if ctx.Failed() {
366		return
367	}
368
369	for _, toolFile := range g.properties.Tool_files {
370		paths := android.PathsForModuleSrc(ctx, []string{toolFile})
371		tools = append(tools, paths...)
372		addLocationLabel(toolFile, toolLocation{paths})
373	}
374
375	includeDirInPaths := ctx.DeviceConfig().BuildBrokenInputDir(g.Name())
376	var srcFiles android.Paths
377	for _, in := range g.properties.Srcs {
378		paths, missingDeps := android.PathsAndMissingDepsRelativeToModuleSourceDir(android.SourceInput{
379			Context: ctx, Paths: []string{in}, ExcludePaths: g.properties.Exclude_srcs, IncludeDirs: includeDirInPaths,
380		})
381		if len(missingDeps) > 0 {
382			if !ctx.Config().AllowMissingDependencies() {
383				panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator",
384					missingDeps))
385			}
386
387			// If AllowMissingDependencies is enabled, the build will not have stopped when
388			// the dependency was added on a missing SourceFileProducer module, which will result in nonsensical
389			// "cmd: label ":..." has no files" errors later.  Add a placeholder file to the local label.
390			// The command that uses this placeholder file will never be executed because the rule will be
391			// replaced with an android.Error rule reporting the missing dependencies.
392			ctx.AddMissingDependencies(missingDeps)
393			addLocationLabel(in, errorLocation{"***missing srcs " + in + "***"})
394		} else {
395			srcFiles = append(srcFiles, paths...)
396			addLocationLabel(in, inputLocation{paths})
397		}
398	}
399
400	var copyFrom android.Paths
401	var outputFiles android.WritablePaths
402	var zipArgs strings.Builder
403
404	cmd := proptools.String(g.properties.Cmd)
405
406	tasks := g.taskGenerator(ctx, cmd, srcFiles)
407	if ctx.Failed() {
408		return
409	}
410
411	for _, task := range tasks {
412		if len(task.out) == 0 {
413			ctx.ModuleErrorf("must have at least one output file")
414			return
415		}
416
417		// Pick a unique path outside the task.genDir for the sbox manifest textproto,
418		// a unique rule name, and the user-visible description.
419		manifestName := "wayland_protocol_codegen.sbox.textproto"
420		desc := "generate"
421		name := "generator"
422		if task.shards > 0 {
423			manifestName = "wayland_protocol_codegen_" + strconv.Itoa(task.shard) + ".sbox.textproto"
424			desc += " " + strconv.Itoa(task.shard)
425			name += strconv.Itoa(task.shard)
426		} else if len(task.out) == 1 {
427			desc += " " + task.out[0].Base()
428		}
429
430		manifestPath := android.PathForModuleOut(ctx, manifestName)
431
432		// Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
433		rule := android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath).SandboxTools()
434		cmd := rule.Command()
435
436		for _, out := range task.out {
437			addLocationLabel(out.Rel(), outputLocation{out})
438		}
439
440		rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
441			// Report the error directly without returning an error to android.Expand to catch multiple errors in a
442			// single run
443			reportError := func(fmt string, args ...interface{}) (string, error) {
444				ctx.PropertyErrorf("cmd", fmt, args...)
445				return "SOONG_ERROR", nil
446			}
447
448			// Apply shell escape to each cases to prevent source file paths containing $ from being evaluated in shell
449			switch name {
450			case "location":
451				if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
452					return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
453				}
454				loc := locationLabels[firstLabel]
455				paths := loc.Paths(cmd)
456				if len(paths) == 0 {
457					return reportError("default label %q has no files", firstLabel)
458				} else if len(paths) > 1 {
459					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
460						firstLabel, firstLabel)
461				}
462				return proptools.ShellEscape(paths[0]), nil
463			case "in":
464				return strings.Join(proptools.ShellEscapeList(cmd.PathsForInputs(srcFiles)), " "), nil
465			case "out":
466				var sandboxOuts []string
467				for _, out := range task.out {
468					sandboxOuts = append(sandboxOuts, cmd.PathForOutput(out))
469				}
470				return strings.Join(proptools.ShellEscapeList(sandboxOuts), " "), nil
471			case "genDir":
472				return proptools.ShellEscape(cmd.PathForOutput(task.genDir)), nil
473			default:
474				if strings.HasPrefix(name, "location ") {
475					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
476					if loc, ok := locationLabels[label]; ok {
477						paths := loc.Paths(cmd)
478						if len(paths) == 0 {
479							return reportError("label %q has no files", label)
480						} else if len(paths) > 1 {
481							return reportError("label %q has multiple files, use $(locations %s) to reference it",
482								label, label)
483						}
484						return proptools.ShellEscape(paths[0]), nil
485					} else {
486						return reportError("unknown location label %q is not in srcs, out, tools or tool_files.", label)
487					}
488				} else if strings.HasPrefix(name, "locations ") {
489					label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
490					if loc, ok := locationLabels[label]; ok {
491						paths := loc.Paths(cmd)
492						if len(paths) == 0 {
493							return reportError("label %q has no files", label)
494						}
495						return proptools.ShellEscape(strings.Join(paths, " ")), nil
496					} else {
497						return reportError("unknown locations label %q is not in srcs, out, tools or tool_files.", label)
498					}
499				} else {
500					return reportError("unknown variable '$(%s)'", name)
501				}
502			}
503		})
504
505		if err != nil {
506			ctx.PropertyErrorf("cmd", "%s", err.Error())
507			return
508		}
509
510		g.rawCommands = append(g.rawCommands, rawCommand)
511
512		cmd.Text(rawCommand)
513		cmd.ImplicitOutputs(task.out)
514		cmd.Implicits(task.in)
515		cmd.ImplicitTools(tools)
516		cmd.ImplicitPackagedTools(packagedTools)
517
518		// Create the rule to run the genrule command inside sbox.
519		rule.Build(name, desc)
520
521		if len(task.copyTo) > 0 {
522			// If copyTo is set, multiple shards need to be copied into a single directory.
523			// task.out contains the per-shard paths, and copyTo contains the corresponding
524			// final path.  The files need to be copied into the final directory by a
525			// single rule so it can remove the directory before it starts to ensure no
526			// old files remain.  zipsync already does this, so build up zipArgs that
527			// zip all the per-shard directories into a single zip.
528			outputFiles = append(outputFiles, task.copyTo...)
529			copyFrom = append(copyFrom, task.out.Paths()...)
530			zipArgs.WriteString(" -C " + task.genDir.String())
531			zipArgs.WriteString(android.JoinWithPrefix(task.out.Strings(), " -f "))
532		} else {
533			outputFiles = append(outputFiles, task.out...)
534		}
535	}
536
537	if len(copyFrom) > 0 {
538		// Create a rule that zips all the per-shard directories into a single zip and then
539		// uses zipsync to unzip it into the final directory.
540		ctx.Build(pctx, android.BuildParams{
541			Rule:        gensrcsMerge,
542			Implicits:   copyFrom,
543			Outputs:     outputFiles,
544			Description: "merge shards",
545			Args: map[string]string{
546				"zipArgs": zipArgs.String(),
547				"tmpZip":  android.PathForModuleGen(ctx, g.subDir+".zip").String(),
548				"genDir":  android.PathForModuleGen(ctx, g.subDir).String(),
549			},
550		})
551	}
552
553	g.outputFiles = outputFiles.Paths()
554}
555
556func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
557	g.generateCommonBuildActions(ctx)
558
559	// When there are less than six outputs, we directly give those as the
560	// output dependency for this module. However, if there are more outputs,
561	// we inject a phony target. This potentially saves space in the generated
562	// ninja file, as well as simplifying any visualizations of the dependency
563	// graph.
564	if len(g.outputFiles) <= 6 {
565		g.outputDeps = g.outputFiles
566	} else {
567		phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
568		ctx.Build(pctx, android.BuildParams{
569			Rule:   blueprint.Phony,
570			Output: phonyFile,
571			Inputs: g.outputFiles,
572		})
573		g.outputDeps = android.Paths{phonyFile}
574	}
575
576	g.setOutputFiles(ctx)
577}
578
579func (g *Module) setOutputFiles(ctx android.ModuleContext) {
580	if len(g.outputFiles) == 0 {
581		return
582	}
583	ctx.SetOutputFiles(g.outputFiles, "")
584	// non-empty-string-tag should match one of the outputs
585	for _, files := range g.outputFiles {
586		ctx.SetOutputFiles(android.Paths{files}, files.Rel())
587	}
588}
589
590// Part of android.IDEInfo.
591// Collect information for opening IDE project files in java/jdeps.go.
592func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
593	dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
594	for _, src := range g.properties.Srcs {
595		if strings.HasPrefix(src, ":") {
596			src = strings.Trim(src, ":")
597			dpInfo.Deps = append(dpInfo.Deps, src)
598		}
599	}
600	dpInfo.Paths = append(dpInfo.Paths, g.modulePaths...)
601}
602
603// Ensure Module implements android.ApexModule
604// Note: gensrcs implements it but it's possible we do not actually need to.
605var _ android.ApexModule = (*Module)(nil)
606
607// Part of android.ApexModule.
608func (g *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
609	sdkVersion android.ApiLevel) error {
610	// Because generated outputs are checked by client modules(e.g. cc_library, ...)
611	// we can safely ignore the check here.
612	return nil
613}
614
615func generatorFactory(taskGenerator taskFunc, props ...interface{}) *Module {
616	module := &Module{
617		taskGenerator: taskGenerator,
618	}
619
620	module.AddProperties(props...)
621	module.AddProperties(&module.properties)
622
623	module.ImageInterface = noopImageInterface{}
624
625	return module
626}
627
628type noopImageInterface struct{}
629
630func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext)                 {}
631func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool            { return false }
632func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool         { return false }
633func (x noopImageInterface) VendorRamdiskVariantNeeded(android.BaseModuleContext) bool   { return false }
634func (x noopImageInterface) DebugRamdiskVariantNeeded(android.BaseModuleContext) bool    { return false }
635func (x noopImageInterface) RecoveryVariantNeeded(android.BaseModuleContext) bool        { return false }
636func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil }
637func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string) {
638}
639
640// Constructs a Module for handling the code generation.
641func newCodegen() *Module {
642	properties := &codegenProperties{}
643
644	// finalSubDir is the name of the subdirectory that output files will be generated into.
645	// It is used so that per-shard directories can be placed alongside it an then finally
646	// merged into it.
647	const finalSubDir = "wayland_protocol_codegen"
648
649	// Code generation commands are sharded so that up to this many files
650	// are generated as part of one sandbox process.
651	const defaultShardSize = 100
652
653	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
654		shardSize := defaultShardSize
655
656		if len(srcFiles) == 0 {
657			ctx.ModuleErrorf("must have at least one source file")
658			return []generateTask{}
659		}
660
661		// wayland_protocol_codegen rules can easily hit command line limits by
662		// repeating the command for every input file.  Shard the input files into
663		// groups.
664		shards := android.ShardPaths(srcFiles, shardSize)
665		var generateTasks []generateTask
666
667		distinctOutputs := make(map[string]android.Path)
668
669		for i, shard := range shards {
670			var commands []string
671			var outFiles android.WritablePaths
672			var copyTo android.WritablePaths
673
674			// When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
675			// shard will be write to their own directories and then be merged together
676			// into finalSubDir.  If sharding is not enabled (i.e. len(shards) == 1),
677			// the sbox rule will write directly to finalSubDir.
678			genSubDir := finalSubDir
679			if len(shards) > 1 {
680				genSubDir = strconv.Itoa(i)
681			}
682
683			genDir := android.PathForModuleGen(ctx, genSubDir)
684			// NOTE: This TODO is copied from gensrcs, as applies here too.
685			// TODO(ccross): this RuleBuilder is a hack to be able to call
686			// rule.Command().PathForOutput.  Replace this with passing the rule into the
687			// generator.
688			rule := android.NewRuleBuilder(pctx, ctx).Sbox(genDir, nil).SandboxTools()
689
690			for _, in := range shard {
691				outFileRaw := expandOutputPath(ctx, *properties, in)
692
693				if conflictWith, hasKey := distinctOutputs[outFileRaw]; hasKey {
694					ctx.ModuleErrorf("generation conflict: both '%v' and '%v' generate '%v'",
695						conflictWith.String(), in.String(), outFileRaw)
696				}
697
698				distinctOutputs[outFileRaw] = in
699
700				outFile := android.PathForModuleGen(ctx, finalSubDir, outFileRaw)
701
702				// If sharding is enabled, then outFile is the path to the output file in
703				// the shard directory, and copyTo is the path to the output file in the
704				// final directory.
705				if len(shards) > 1 {
706					shardFile := android.PathForModuleGen(ctx, genSubDir, outFileRaw)
707					copyTo = append(copyTo, outFile)
708					outFile = shardFile
709				}
710
711				outFiles = append(outFiles, outFile)
712
713				// pre-expand the command line to replace $in and $out with references to
714				// a single input and output file.
715				command, err := android.Expand(rawCommand, func(name string) (string, error) {
716					switch name {
717					case "in":
718						return in.String(), nil
719					case "out":
720						return rule.Command().PathForOutput(outFile), nil
721					default:
722						return "$(" + name + ")", nil
723					}
724				})
725				if err != nil {
726					ctx.PropertyErrorf("cmd", err.Error())
727				}
728
729				// escape the command in case for example it contains '#', an odd number of '"', etc
730				command = fmt.Sprintf("bash -c %v", proptools.ShellEscape(command))
731				commands = append(commands, command)
732			}
733			fullCommand := strings.Join(commands, " && ")
734
735			generateTasks = append(generateTasks, generateTask{
736				in:     shard,
737				out:    outFiles,
738				copyTo: copyTo,
739				genDir: genDir,
740				cmd:    fullCommand,
741				shard:  i,
742				shards: len(shards),
743			})
744		}
745
746		return generateTasks
747	}
748
749	g := generatorFactory(taskGenerator, properties)
750	g.subDir = finalSubDir
751	return g
752}
753
754// Factory for code generation modules
755func codegenFactory() android.Module {
756	m := newCodegen()
757	android.InitAndroidModule(m)
758	android.InitDefaultableModule(m)
759	return m
760}
761
762// The custom properties specific to this code generation module.
763type codegenProperties struct {
764	// The string to prepend to every protocol filename to generate the
765	// corresponding output filename. The empty string by default.
766	// Deprecated. Prefer "Output" instead.
767	Prefix *string
768
769	// The suffix to append to every protocol filename to generate the
770	// corresponding output filename. The empty string by default.
771	// Deprecated. Prefer "Output" instead.
772	Suffix *string
773
774	// The output filename template.
775	//
776	// This template string allows the output file name to be generated for
777	// each source file, using some limited properties of the source file.
778	//
779	//	$(in:base): The base filename, no path or extension
780	//	$(in:base.ext): The filename, no path
781	//	$(in:path/base): The filename with path but no extension
782	//	$(in:path/base.ext): The full source filename
783	//	$(in): An alias for $(in:base) for the base filename, no extension
784	//
785	// Note that the path that is maintained is the relative path used when
786	// including the source in an Android.bp file.
787	//
788	// The template allows arbitrary prefixes and suffixes to be added to the
789	// output filename. For example, "a_$(in).d" would take an source filename
790	// of "b.c" and turn it into "a_b.d".
791	//
792	// The output template does not have to generate a unique filename,
793	// however the implementation will raise an error if the same output file
794	// is generated by more than one source file.
795	Output *string
796}
797
798// Expands the output path pattern to form the output path for the given
799// input path.
800func expandOutputPath(ctx android.ModuleContext, properties codegenProperties, in android.Path) string {
801	template := proptools.String(properties.Output)
802	if len(template) == 0 {
803		prefix := proptools.String(properties.Prefix)
804		suffix := proptools.String(properties.Suffix)
805		return prefix + removeExtension(in.Base()) + suffix
806	}
807
808	outPath, _ := android.Expand(template, func(name string) (string, error) {
809		// Report the error directly without returning an error to
810		// android.Expand to catch multiple errors in a single run.
811		reportError := func(fmt string, args ...interface{}) (string, error) {
812			ctx.PropertyErrorf("output", fmt, args...)
813			return "EXPANSION_ERROR", nil
814		}
815
816		switch name {
817		case "in":
818			return removeExtension(in.Base()), nil
819		case "in:base":
820			return removeExtension(in.Base()), nil
821		case "in:base.ext":
822			return in.Base(), nil
823		case "in:path/base":
824			return removeExtension(in.Rel()), nil
825		case "in:path/base.ext":
826			return in.Rel(), nil
827		default:
828			return reportError("unknown variable '$(%s)'", name)
829		}
830	})
831
832	return outPath
833}
834
835// Removes any extension from the final component of a path.
836func removeExtension(path string) string {
837	// Note: This implementation does not handle files like ".bashrc" correctly.
838	if dot := strings.LastIndex(path, "."); dot != -1 {
839		return path[:dot]
840	}
841	return path
842}
843
844// Defaults module.
845type Defaults struct {
846	android.ModuleBase
847	android.DefaultsModuleBase
848}
849
850func defaultsFactory() android.Module {
851	return DefaultsFactory()
852}
853
854func DefaultsFactory(props ...interface{}) android.Module {
855	module := &Defaults{}
856
857	module.AddProperties(props...)
858	module.AddProperties(
859		&generatorProperties{},
860		&codegenProperties{},
861	)
862
863	android.InitDefaultsModule(module)
864
865	return module
866}
867