Home | History | Annotate | Download | only in wayland-protocols
      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 // ---------------------------------------------------------------------------
     16 
     17 // Package wayland_protcool defines extension modules for the Soong build system
     18 // to make it easier to generate code from a list of Wayland protocol files.
     19 //
     20 // The primary extension module is "wayland_protocol_codegen", which applies a
     21 // code generation tool to a list of source protocol files.
     22 //
     23 // Note that the code generation done here is similar to what is done by the
     24 // base Soong "gensrcs" module, but there are two functional differences:
     25 //
     26 //     1) The output filenames are computed from the input filenames, rather
     27 //        than needing to be specified explicitly. An optional prefix as well
     28 //        as a suffix can be added to the protocol filename (without extension).
     29 //
     30 //     2) Code generation is done for each file independently by emitting
     31 //        multiple Ninja build commands, rather than one build command which
     32 //        does it all.
     33 package wayland_protocol
     34 
     35 import (
     36 	"fmt"
     37 	"strings"
     38 
     39 	"github.com/google/blueprint"
     40 	"github.com/google/blueprint/proptools"
     41 
     42 	"android/soong/android"
     43 	"android/soong/genrule"
     44 )
     45 
     46 func init() {
     47 	// Register out extension module type name with Soong.
     48 	android.RegisterModuleType(
     49 		"wayland_protocol_codegen", waylandCodegenModuleFactory)
     50 }
     51 
     52 var (
     53 	// Create a context for build rule output from this package
     54 	pctx = android.NewPackageContext("android/soong/external/wayland-protocol")
     55 )
     56 
     57 type hostToolDependencyTag struct {
     58 	blueprint.BaseDependencyTag
     59 }
     60 
     61 var hostToolDepTag hostToolDependencyTag
     62 
     63 // waylandCodegenProperties defines the properties that will be read in from the
     64 // Android.bp file for each instantiation of the module.
     65 type waylandCodegenProperties struct {
     66 	// This string gives the command line template to run on each protocol file
     67 	// to wayland_protocol_codegen.
     68 	//
     69 	// The string can contain one or more "$" prefixed variable names for
     70 	// values that can vary. At a minimum you need to use ${location}, ${out}
     71 	// and ${in}
     72 	//
     73 	//  $(location): the path to the first entry in tools or tool_files
     74 	//  $(location <label>): the path to the tool or tool_file with name <label>
     75 	//  $(in): A protocol file from srcs
     76 	//  $(out): The constructed output filename from the protocol filename.
     77 	//  $$: a literal $
     78 	Cmd *string
     79 
     80 	// The string to prepend to every protcol filename to generate the
     81 	// corresponding output filename. The empty string by default.
     82 	Prefix *string
     83 
     84 	// The suffix to append to every protocol filename to generate the
     85 	// corresponding output filename. The empty string by default.
     86 	Suffix *string
     87 
     88 	// The list of protocol files to process.
     89 	Srcs []string
     90 
     91 	// The names of any built host executables to use for code generation. Can
     92 	// be left empty if a local script is used instead (specified in
     93 	// tool_files).
     94 	Tools []string
     95 
     96 	// Local files that are used for code generation. Can be scripts to run, but
     97 	// should also include any other files that the code generation step should
     98 	// depend on that might be used by the code gen tool.
     99 	Tool_files []string
    100 }
    101 
    102 // waylandGenModule defines the Soong module for each instance.
    103 type waylandGenModule struct {
    104 	android.ModuleBase
    105 
    106 	// Store a copy of the parsed properties for easy reference.
    107 	properties waylandCodegenProperties
    108 
    109 	// Each module emits its own blueprint (Ninja) rule. Store a reference
    110 	// to the one created for this instance.
    111 	rule blueprint.Rule
    112 
    113 	// Each module exports one or more include directories. Store the paths here
    114 	// here for easy retrieval.
    115 	exportedIncludeDirs android.Paths
    116 
    117 	// Each module has a list of files it outputs, that can be used by other
    118 	// modules. Store the list of paths here for easy reference.
    119 	outputFiles android.Paths
    120 }
    121 
    122 // For the uninitiated, this is an idiom to check that a given object implements
    123 // an interface. In this case we want to be sure that waylandGenModule
    124 // implements genrule.SourceFileGenerator
    125 var _ genrule.SourceFileGenerator = (*waylandGenModule)(nil)
    126 
    127 // Check that we implement android.SourceFileProducer
    128 var _ android.SourceFileProducer = (*waylandGenModule)(nil)
    129 
    130 // GeneratedSourceFiles implements the genrule.SourceFileGenerator
    131 // GeneratedSourceFiles method to return the list of generated source files.
    132 func (g *waylandGenModule) GeneratedSourceFiles() android.Paths {
    133 	return g.outputFiles
    134 }
    135 
    136 // GeneratedHeaderDirs implements the genrule.SourceFileGenerator
    137 // GeneratedHeaderDirs method to return the list of exported include
    138 // directories.
    139 func (g *waylandGenModule) GeneratedHeaderDirs() android.Paths {
    140 	return g.exportedIncludeDirs
    141 }
    142 
    143 // GeneratedDeps implements the genrule.SourceFileGenerator GeneratedDeps
    144 // method to return the list of files to be used as dependencies when using
    145 // GeneratedHeaderDirs.
    146 func (g *waylandGenModule) GeneratedDeps() android.Paths {
    147 	return g.outputFiles
    148 }
    149 
    150 // Srcs implements the android.SourceFileProducer Srcs method to return the list
    151 // of source files.
    152 func (g *waylandGenModule) Srcs() android.Paths {
    153 	return g.outputFiles
    154 }
    155 
    156 // DepsMutator implements the android.Module DepsMutator method to apply a
    157 // mutator context to the build graph.
    158 func (g *waylandGenModule) DepsMutator(ctx android.BottomUpMutatorContext) {
    159 	// This implementatoin duplicates the one from genrule.go, where gensrcs is
    160 	// defined.
    161 	android.ExtractSourcesDeps(ctx, g.properties.Srcs)
    162 	if g, ok := ctx.Module().(*waylandGenModule); ok {
    163 		if len(g.properties.Tools) > 0 {
    164 			ctx.AddFarVariationDependencies([]blueprint.Variation{
    165 				{"arch", ctx.AConfig().BuildOsVariant},
    166 			}, hostToolDepTag, g.properties.Tools...)
    167 		}
    168 	}
    169 }
    170 
    171 // GenerateAndroidBuildActions implements the android.Module
    172 // GenerateAndroidBuildActions method, which generates all the rules and builds
    173 // commands used by this module instance.
    174 func (g *waylandGenModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
    175 	if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
    176 		ctx.ModuleErrorf("at least one `tools` or `tool_files` is required")
    177 		return
    178 	}
    179 
    180 	// Prepare the list of tools that were defined for codegen purposes.
    181 	tools, implicitDeps := g.prepareTools(ctx)
    182 
    183 	if ctx.Failed() {
    184 		return
    185 	}
    186 
    187 	// Emit the rule for generating for processing each source file
    188 	g.emitRule(ctx, tools)
    189 
    190 	if ctx.Failed() {
    191 		return
    192 	}
    193 
    194 	generatedFilenamePrefix := proptools.String(g.properties.Prefix)
    195 	generatedFilenameSuffix := proptools.String(g.properties.Suffix)
    196 	for _, src := range ctx.ExpandSources(g.properties.Srcs, nil) {
    197 		out := g.generateOutputPath(ctx, src, generatedFilenamePrefix, generatedFilenameSuffix)
    198 		if out == nil {
    199 			continue
    200 		}
    201 
    202 		g.emitBuild(ctx, src, out, implicitDeps)
    203 		g.outputFiles = append(g.outputFiles, out)
    204 	}
    205 
    206 	g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx))
    207 }
    208 
    209 // genrateOutputPath takes an source path, a prefix, and a suffix, and use them
    210 // to generate and return corresponding an output file path.
    211 func (g *waylandGenModule) generateOutputPath(ctx android.ModuleContext, src android.Path, prefix string, suffix string) android.WritablePath {
    212 	// Construct a new filename by adding the requested prefix and suffix for this
    213 	// code generator instance. If the input file name is "wayland.xml", and the
    214 	// properties specify a prefix of "test-" and a suffix of "-client.cpp", we
    215 	// will end up with a fulename of "test-wayland-client.cpp"
    216 	protocolFilename, protocolExt := splitExt(src.Base())
    217 	if protocolExt != ".xml" {
    218 		ctx.ModuleErrorf("Source file %q does not end with .xml", src)
    219 		return nil
    220 	}
    221 	return android.PathForModuleGen(ctx, prefix+protocolFilename+suffix)
    222 }
    223 
    224 // emitRule is an internal function to emit each Ninja rule.
    225 func (g *waylandGenModule) emitRule(ctx android.ModuleContext, tools map[string]android.Path) {
    226 	// Get the command to run to process each protocol file. Since everything
    227 	// should be templated, we generate a Ninja rule that uses the command,
    228 	// and invoke it from each Ninja build command we emit.
    229 	g.rule = ctx.Rule(pctx, "generator", blueprint.RuleParams{
    230 		Command: g.expandCmd(ctx, tools),
    231 	})
    232 }
    233 
    234 // emitBuild is an internal function to emit each Build command.
    235 func (g *waylandGenModule) emitBuild(ctx android.ModuleContext, src android.Path, out android.WritablePath, implicitDeps android.Paths) android.Path {
    236 	ctx.Build(pctx, android.BuildParams{
    237 		Rule:        g.rule,
    238 		Description: "generate " + out.Base(),
    239 		Output:      out,
    240 		Inputs:      android.Paths{src},
    241 		Implicits:   implicitDeps,
    242 	})
    243 
    244 	return out
    245 }
    246 
    247 // prepareTools is an internal function to prepare a list of tools.
    248 func (g *waylandGenModule) prepareTools(ctx android.ModuleContext) (tools map[string]android.Path, implicitDeps android.Paths) {
    249 	tools = map[string]android.Path{}
    250 
    251 	// This was extracted and slightly simplifed from equivalent code in
    252 	// genrule.go.
    253 
    254 	// For each entry in "tool", walk the dependency graph to get more
    255 	// information about it.
    256 	if len(g.properties.Tools) > 0 {
    257 		ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
    258 			switch ctx.OtherModuleDependencyTag(module) {
    259 			case android.SourceDepTag:
    260 				// Nothing to do
    261 			case hostToolDepTag:
    262 				tool := ctx.OtherModuleName(module)
    263 				var path android.OptionalPath
    264 
    265 				if t, ok := module.(genrule.HostToolProvider); ok {
    266 					if !t.(android.Module).Enabled() {
    267 						if ctx.AConfig().AllowMissingDependencies() {
    268 							ctx.AddMissingDependencies([]string{tool})
    269 						} else {
    270 							ctx.ModuleErrorf("depends on disabled module %q", tool)
    271 						}
    272 						break
    273 					}
    274 					path = t.HostToolPath()
    275 				} else {
    276 					ctx.ModuleErrorf("%q is not a host tool provider", tool)
    277 					break
    278 				}
    279 
    280 				if path.Valid() {
    281 					implicitDeps = append(implicitDeps, path.Path())
    282 					if _, exists := tools[tool]; !exists {
    283 						tools[tool] = path.Path()
    284 					} else {
    285 						ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], path.Path().String())
    286 					}
    287 				} else {
    288 					ctx.ModuleErrorf("host tool %q missing output file", tool)
    289 				}
    290 			default:
    291 				ctx.ModuleErrorf("unknown dependency on %q", ctx.OtherModuleName(module))
    292 			}
    293 		})
    294 	}
    295 
    296 	// Get more information about each entry in "tool_files".
    297 	for _, tool := range g.properties.Tool_files {
    298 		toolPath := android.PathForModuleSrc(ctx, tool)
    299 		implicitDeps = append(implicitDeps, toolPath)
    300 		if _, exists := tools[tool]; !exists {
    301 			tools[tool] = toolPath
    302 		} else {
    303 			ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], toolPath.String())
    304 		}
    305 	}
    306 	return
    307 }
    308 
    309 // expandCmd is an internal function to do some expansion and any additional
    310 // wrapping of the generator command line. Returns the command line to use and
    311 // an error value.
    312 func (g *waylandGenModule) expandCmd(ctx android.ModuleContext, tools map[string]android.Path) (cmd string) {
    313 	cmd, err := android.Expand(proptools.String(g.properties.Cmd), func(name string) (string, error) {
    314 		switch name {
    315 		case "in":
    316 			return "$in", nil
    317 		case "out":
    318 			// We need to use the sandbox out path instead
    319 			//return "$sandboxOut", nil
    320 			return "$out", nil
    321 		case "location":
    322 			if len(g.properties.Tools) > 0 {
    323 				return tools[g.properties.Tools[0]].String(), nil
    324 			} else {
    325 				return tools[g.properties.Tool_files[0]].String(), nil
    326 			}
    327 		default:
    328 			if strings.HasPrefix(name, "location ") {
    329 				label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
    330 				if tool, ok := tools[label]; ok {
    331 					return tool.String(), nil
    332 				} else {
    333 					return "", fmt.Errorf("unknown location label %q", label)
    334 				}
    335 			}
    336 			return "", fmt.Errorf("unknown variable '$(%s)'", name)
    337 		}
    338 	})
    339 	if err != nil {
    340 		ctx.PropertyErrorf("cmd", "%s", err.Error())
    341 	}
    342 	return
    343 }
    344 
    345 // waylandCodegenModuleFactory creates an extension module instance.
    346 func waylandCodegenModuleFactory() android.Module {
    347 	m := &waylandGenModule{}
    348 	m.AddProperties(&m.properties)
    349 	android.InitAndroidModule(m)
    350 	return m
    351 }
    352 
    353 // splitExt splits a base filename into (filename, ext) components, such that
    354 // input == filename + ext
    355 func splitExt(input string) (filename string, ext string) {
    356 	// There is no filepath.SplitExt() or equivalent.
    357 	dot := strings.LastIndex(input, ".")
    358 	if dot != -1 {
    359 		ext = input[dot:]
    360 		filename = input[:dot]
    361 	} else {
    362 		ext = ""
    363 		filename = input
    364 	}
    365 	return
    366 }
    367