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