Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 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. |
Paul Duffin | 220263a | 2021-07-09 23:13:42 +0100 | [diff] [blame] | 89 | Srcs []string `android:"path"` |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 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 | |
Dan Willemsen | c70fdb6 | 2018-02-21 19:12:21 -0800 | [diff] [blame] | 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 | |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 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) { |
Paul Duffin | 220263a | 2021-07-09 23:13:42 +0100 | [diff] [blame] | 159 | // This implementation duplicates the one from genrule.go, where gensrcs is |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 160 | // defined. |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 161 | if g, ok := ctx.Module().(*waylandGenModule); ok { |
| 162 | if len(g.properties.Tools) > 0 { |
Colin Cross | b19afd9 | 2019-10-16 11:11:00 -0700 | [diff] [blame] | 163 | ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), |
| 164 | hostToolDepTag, g.properties.Tools...) |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 165 | } |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | // GenerateAndroidBuildActions implements the android.Module |
| 170 | // GenerateAndroidBuildActions method, which generates all the rules and builds |
| 171 | // commands used by this module instance. |
| 172 | func (g *waylandGenModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { |
| 173 | if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 { |
| 174 | ctx.ModuleErrorf("at least one `tools` or `tool_files` is required") |
| 175 | return |
| 176 | } |
| 177 | |
| 178 | // Prepare the list of tools that were defined for codegen purposes. |
| 179 | tools, implicitDeps := g.prepareTools(ctx) |
| 180 | |
| 181 | if ctx.Failed() { |
| 182 | return |
| 183 | } |
| 184 | |
| 185 | // Emit the rule for generating for processing each source file |
| 186 | g.emitRule(ctx, tools) |
| 187 | |
| 188 | if ctx.Failed() { |
| 189 | return |
| 190 | } |
| 191 | |
| 192 | generatedFilenamePrefix := proptools.String(g.properties.Prefix) |
| 193 | generatedFilenameSuffix := proptools.String(g.properties.Suffix) |
Paul Duffin | 220263a | 2021-07-09 23:13:42 +0100 | [diff] [blame] | 194 | for _, src := range android.PathsForModuleSrc(ctx, g.properties.Srcs) { |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 195 | out := g.generateOutputPath(ctx, src, generatedFilenamePrefix, generatedFilenameSuffix) |
| 196 | if out == nil { |
| 197 | continue |
| 198 | } |
| 199 | |
| 200 | g.emitBuild(ctx, src, out, implicitDeps) |
| 201 | g.outputFiles = append(g.outputFiles, out) |
| 202 | } |
| 203 | |
| 204 | g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx)) |
| 205 | } |
| 206 | |
| 207 | // genrateOutputPath takes an source path, a prefix, and a suffix, and use them |
| 208 | // to generate and return corresponding an output file path. |
| 209 | func (g *waylandGenModule) generateOutputPath(ctx android.ModuleContext, src android.Path, prefix string, suffix string) android.WritablePath { |
| 210 | // Construct a new filename by adding the requested prefix and suffix for this |
| 211 | // code generator instance. If the input file name is "wayland.xml", and the |
| 212 | // properties specify a prefix of "test-" and a suffix of "-client.cpp", we |
| 213 | // will end up with a fulename of "test-wayland-client.cpp" |
| 214 | protocolFilename, protocolExt := splitExt(src.Base()) |
| 215 | if protocolExt != ".xml" { |
| 216 | ctx.ModuleErrorf("Source file %q does not end with .xml", src) |
| 217 | return nil |
| 218 | } |
| 219 | return android.PathForModuleGen(ctx, prefix+protocolFilename+suffix) |
| 220 | } |
| 221 | |
| 222 | // emitRule is an internal function to emit each Ninja rule. |
| 223 | func (g *waylandGenModule) emitRule(ctx android.ModuleContext, tools map[string]android.Path) { |
| 224 | // Get the command to run to process each protocol file. Since everything |
| 225 | // should be templated, we generate a Ninja rule that uses the command, |
| 226 | // and invoke it from each Ninja build command we emit. |
| 227 | g.rule = ctx.Rule(pctx, "generator", blueprint.RuleParams{ |
| 228 | Command: g.expandCmd(ctx, tools), |
| 229 | }) |
| 230 | } |
| 231 | |
| 232 | // emitBuild is an internal function to emit each Build command. |
| 233 | func (g *waylandGenModule) emitBuild(ctx android.ModuleContext, src android.Path, out android.WritablePath, implicitDeps android.Paths) android.Path { |
| 234 | ctx.Build(pctx, android.BuildParams{ |
| 235 | Rule: g.rule, |
| 236 | Description: "generate " + out.Base(), |
| 237 | Output: out, |
| 238 | Inputs: android.Paths{src}, |
| 239 | Implicits: implicitDeps, |
| 240 | }) |
| 241 | |
| 242 | return out |
| 243 | } |
| 244 | |
| 245 | // prepareTools is an internal function to prepare a list of tools. |
| 246 | func (g *waylandGenModule) prepareTools(ctx android.ModuleContext) (tools map[string]android.Path, implicitDeps android.Paths) { |
| 247 | tools = map[string]android.Path{} |
| 248 | |
| 249 | // This was extracted and slightly simplifed from equivalent code in |
| 250 | // genrule.go. |
| 251 | |
| 252 | // For each entry in "tool", walk the dependency graph to get more |
| 253 | // information about it. |
| 254 | if len(g.properties.Tools) > 0 { |
| 255 | ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) { |
Paul Duffin | 220263a | 2021-07-09 23:13:42 +0100 | [diff] [blame] | 256 | tag := ctx.OtherModuleDependencyTag(module) |
| 257 | if android.IsSourceDepTagWithOutputTag(tag, "") { |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 258 | // Nothing to do |
Paul Duffin | 220263a | 2021-07-09 23:13:42 +0100 | [diff] [blame] | 259 | return |
| 260 | } |
| 261 | switch tag { |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 262 | case hostToolDepTag: |
| 263 | tool := ctx.OtherModuleName(module) |
| 264 | var path android.OptionalPath |
| 265 | |
| 266 | if t, ok := module.(genrule.HostToolProvider); ok { |
| 267 | if !t.(android.Module).Enabled() { |
Colin Cross | b19afd9 | 2019-10-16 11:11:00 -0700 | [diff] [blame] | 268 | if ctx.Config().AllowMissingDependencies() { |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 269 | ctx.AddMissingDependencies([]string{tool}) |
| 270 | } else { |
| 271 | ctx.ModuleErrorf("depends on disabled module %q", tool) |
| 272 | } |
| 273 | break |
| 274 | } |
| 275 | path = t.HostToolPath() |
| 276 | } else { |
| 277 | ctx.ModuleErrorf("%q is not a host tool provider", tool) |
| 278 | break |
| 279 | } |
| 280 | |
| 281 | if path.Valid() { |
| 282 | implicitDeps = append(implicitDeps, path.Path()) |
| 283 | if _, exists := tools[tool]; !exists { |
| 284 | tools[tool] = path.Path() |
| 285 | } else { |
| 286 | ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], path.Path().String()) |
| 287 | } |
| 288 | } else { |
| 289 | ctx.ModuleErrorf("host tool %q missing output file", tool) |
| 290 | } |
Lloyd Pique | 016680d | 2017-11-29 17:54:29 -0800 | [diff] [blame] | 291 | } |
| 292 | }) |
| 293 | } |
| 294 | |
| 295 | // Get more information about each entry in "tool_files". |
| 296 | for _, tool := range g.properties.Tool_files { |
| 297 | toolPath := android.PathForModuleSrc(ctx, tool) |
| 298 | implicitDeps = append(implicitDeps, toolPath) |
| 299 | if _, exists := tools[tool]; !exists { |
| 300 | tools[tool] = toolPath |
| 301 | } else { |
| 302 | ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], toolPath.String()) |
| 303 | } |
| 304 | } |
| 305 | return |
| 306 | } |
| 307 | |
| 308 | // expandCmd is an internal function to do some expansion and any additional |
| 309 | // wrapping of the generator command line. Returns the command line to use and |
| 310 | // an error value. |
| 311 | func (g *waylandGenModule) expandCmd(ctx android.ModuleContext, tools map[string]android.Path) (cmd string) { |
| 312 | cmd, err := android.Expand(proptools.String(g.properties.Cmd), func(name string) (string, error) { |
| 313 | switch name { |
| 314 | case "in": |
| 315 | return "$in", nil |
| 316 | case "out": |
| 317 | // We need to use the sandbox out path instead |
| 318 | //return "$sandboxOut", nil |
| 319 | return "$out", nil |
| 320 | case "location": |
| 321 | if len(g.properties.Tools) > 0 { |
| 322 | return tools[g.properties.Tools[0]].String(), nil |
| 323 | } else { |
| 324 | return tools[g.properties.Tool_files[0]].String(), nil |
| 325 | } |
| 326 | default: |
| 327 | if strings.HasPrefix(name, "location ") { |
| 328 | label := strings.TrimSpace(strings.TrimPrefix(name, "location ")) |
| 329 | if tool, ok := tools[label]; ok { |
| 330 | return tool.String(), nil |
| 331 | } else { |
| 332 | return "", fmt.Errorf("unknown location label %q", label) |
| 333 | } |
| 334 | } |
| 335 | return "", fmt.Errorf("unknown variable '$(%s)'", name) |
| 336 | } |
| 337 | }) |
| 338 | if err != nil { |
| 339 | ctx.PropertyErrorf("cmd", "%s", err.Error()) |
| 340 | } |
| 341 | return |
| 342 | } |
| 343 | |
| 344 | // waylandCodegenModuleFactory creates an extension module instance. |
| 345 | func waylandCodegenModuleFactory() android.Module { |
| 346 | m := &waylandGenModule{} |
| 347 | m.AddProperties(&m.properties) |
| 348 | android.InitAndroidModule(m) |
| 349 | return m |
| 350 | } |
| 351 | |
| 352 | // splitExt splits a base filename into (filename, ext) components, such that |
| 353 | // input == filename + ext |
| 354 | func splitExt(input string) (filename string, ext string) { |
| 355 | // There is no filepath.SplitExt() or equivalent. |
| 356 | dot := strings.LastIndex(input, ".") |
| 357 | if dot != -1 { |
| 358 | ext = input[dot:] |
| 359 | filename = input[:dot] |
| 360 | } else { |
| 361 | ext = "" |
| 362 | filename = input |
| 363 | } |
| 364 | return |
| 365 | } |