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