Golang程序  |  367行  |  12.55 KB

// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// ---------------------------------------------------------------------------

// Package wayland_protcool defines extension modules for the Soong build system
// to make it easier to generate code from a list of Wayland protocol files.
//
// The primary extension module is "wayland_protocol_codegen", which applies a
// code generation tool to a list of source protocol files.
//
// Note that the code generation done here is similar to what is done by the
// base Soong "gensrcs" module, but there are two functional differences:
//
//     1) The output filenames are computed from the input filenames, rather
//        than needing to be specified explicitly. An optional prefix as well
//        as a suffix can be added to the protocol filename (without extension).
//
//     2) Code generation is done for each file independently by emitting
//        multiple Ninja build commands, rather than one build command which
//        does it all.
package wayland_protocol

import (
	"fmt"
	"strings"

	"github.com/google/blueprint"
	"github.com/google/blueprint/proptools"

	"android/soong/android"
	"android/soong/genrule"
)

func init() {
	// Register out extension module type name with Soong.
	android.RegisterModuleType(
		"wayland_protocol_codegen", waylandCodegenModuleFactory)
}

var (
	// Create a context for build rule output from this package
	pctx = android.NewPackageContext("android/soong/external/wayland-protocol")
)

type hostToolDependencyTag struct {
	blueprint.BaseDependencyTag
}

var hostToolDepTag hostToolDependencyTag

// waylandCodegenProperties defines the properties that will be read in from the
// Android.bp file for each instantiation of the module.
type waylandCodegenProperties struct {
	// This string gives the command line template to run on each protocol file
	// to wayland_protocol_codegen.
	//
	// The string can contain one or more "$" prefixed variable names for
	// values that can vary. At a minimum you need to use ${location}, ${out}
	// and ${in}
	//
	//  $(location): the path to the first entry in tools or tool_files
	//  $(location <label>): the path to the tool or tool_file with name <label>
	//  $(in): A protocol file from srcs
	//  $(out): The constructed output filename from the protocol filename.
	//  $$: a literal $
	Cmd *string

	// The string to prepend to every protcol filename to generate the
	// corresponding output filename. The empty string by default.
	Prefix *string

	// The suffix to append to every protocol filename to generate the
	// corresponding output filename. The empty string by default.
	Suffix *string

	// The list of protocol files to process.
	Srcs []string

	// The names of any built host executables to use for code generation. Can
	// be left empty if a local script is used instead (specified in
	// tool_files).
	Tools []string

	// Local files that are used for code generation. Can be scripts to run, but
	// should also include any other files that the code generation step should
	// depend on that might be used by the code gen tool.
	Tool_files []string
}

// waylandGenModule defines the Soong module for each instance.
type waylandGenModule struct {
	android.ModuleBase

	// Store a copy of the parsed properties for easy reference.
	properties waylandCodegenProperties

	// Each module emits its own blueprint (Ninja) rule. Store a reference
	// to the one created for this instance.
	rule blueprint.Rule

	// Each module exports one or more include directories. Store the paths here
	// here for easy retrieval.
	exportedIncludeDirs android.Paths

	// Each module has a list of files it outputs, that can be used by other
	// modules. Store the list of paths here for easy reference.
	outputFiles android.Paths
}

// For the uninitiated, this is an idiom to check that a given object implements
// an interface. In this case we want to be sure that waylandGenModule
// implements genrule.SourceFileGenerator
var _ genrule.SourceFileGenerator = (*waylandGenModule)(nil)

// Check that we implement android.SourceFileProducer
var _ android.SourceFileProducer = (*waylandGenModule)(nil)

// GeneratedSourceFiles implements the genrule.SourceFileGenerator
// GeneratedSourceFiles method to return the list of generated source files.
func (g *waylandGenModule) GeneratedSourceFiles() android.Paths {
	return g.outputFiles
}

// GeneratedHeaderDirs implements the genrule.SourceFileGenerator
// GeneratedHeaderDirs method to return the list of exported include
// directories.
func (g *waylandGenModule) GeneratedHeaderDirs() android.Paths {
	return g.exportedIncludeDirs
}

// GeneratedDeps implements the genrule.SourceFileGenerator GeneratedDeps
// method to return the list of files to be used as dependencies when using
// GeneratedHeaderDirs.
func (g *waylandGenModule) GeneratedDeps() android.Paths {
	return g.outputFiles
}

// Srcs implements the android.SourceFileProducer Srcs method to return the list
// of source files.
func (g *waylandGenModule) Srcs() android.Paths {
	return g.outputFiles
}

// DepsMutator implements the android.Module DepsMutator method to apply a
// mutator context to the build graph.
func (g *waylandGenModule) DepsMutator(ctx android.BottomUpMutatorContext) {
	// This implementatoin duplicates the one from genrule.go, where gensrcs is
	// defined.
	android.ExtractSourcesDeps(ctx, g.properties.Srcs)
	if g, ok := ctx.Module().(*waylandGenModule); ok {
		if len(g.properties.Tools) > 0 {
			ctx.AddFarVariationDependencies([]blueprint.Variation{
				{"arch", ctx.AConfig().BuildOsVariant},
			}, hostToolDepTag, g.properties.Tools...)
		}
	}
}

// GenerateAndroidBuildActions implements the android.Module
// GenerateAndroidBuildActions method, which generates all the rules and builds
// commands used by this module instance.
func (g *waylandGenModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
		ctx.ModuleErrorf("at least one `tools` or `tool_files` is required")
		return
	}

	// Prepare the list of tools that were defined for codegen purposes.
	tools, implicitDeps := g.prepareTools(ctx)

	if ctx.Failed() {
		return
	}

	// Emit the rule for generating for processing each source file
	g.emitRule(ctx, tools)

	if ctx.Failed() {
		return
	}

	generatedFilenamePrefix := proptools.String(g.properties.Prefix)
	generatedFilenameSuffix := proptools.String(g.properties.Suffix)
	for _, src := range ctx.ExpandSources(g.properties.Srcs, nil) {
		out := g.generateOutputPath(ctx, src, generatedFilenamePrefix, generatedFilenameSuffix)
		if out == nil {
			continue
		}

		g.emitBuild(ctx, src, out, implicitDeps)
		g.outputFiles = append(g.outputFiles, out)
	}

	g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx))
}

// genrateOutputPath takes an source path, a prefix, and a suffix, and use them
// to generate and return corresponding an output file path.
func (g *waylandGenModule) generateOutputPath(ctx android.ModuleContext, src android.Path, prefix string, suffix string) android.WritablePath {
	// Construct a new filename by adding the requested prefix and suffix for this
	// code generator instance. If the input file name is "wayland.xml", and the
	// properties specify a prefix of "test-" and a suffix of "-client.cpp", we
	// will end up with a fulename of "test-wayland-client.cpp"
	protocolFilename, protocolExt := splitExt(src.Base())
	if protocolExt != ".xml" {
		ctx.ModuleErrorf("Source file %q does not end with .xml", src)
		return nil
	}
	return android.PathForModuleGen(ctx, prefix+protocolFilename+suffix)
}

// emitRule is an internal function to emit each Ninja rule.
func (g *waylandGenModule) emitRule(ctx android.ModuleContext, tools map[string]android.Path) {
	// Get the command to run to process each protocol file. Since everything
	// should be templated, we generate a Ninja rule that uses the command,
	// and invoke it from each Ninja build command we emit.
	g.rule = ctx.Rule(pctx, "generator", blueprint.RuleParams{
		Command: g.expandCmd(ctx, tools),
	})
}

// emitBuild is an internal function to emit each Build command.
func (g *waylandGenModule) emitBuild(ctx android.ModuleContext, src android.Path, out android.WritablePath, implicitDeps android.Paths) android.Path {
	ctx.Build(pctx, android.BuildParams{
		Rule:        g.rule,
		Description: "generate " + out.Base(),
		Output:      out,
		Inputs:      android.Paths{src},
		Implicits:   implicitDeps,
	})

	return out
}

// prepareTools is an internal function to prepare a list of tools.
func (g *waylandGenModule) prepareTools(ctx android.ModuleContext) (tools map[string]android.Path, implicitDeps android.Paths) {
	tools = map[string]android.Path{}

	// This was extracted and slightly simplifed from equivalent code in
	// genrule.go.

	// For each entry in "tool", walk the dependency graph to get more
	// information about it.
	if len(g.properties.Tools) > 0 {
		ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
			switch ctx.OtherModuleDependencyTag(module) {
			case android.SourceDepTag:
				// Nothing to do
			case hostToolDepTag:
				tool := ctx.OtherModuleName(module)
				var path android.OptionalPath

				if t, ok := module.(genrule.HostToolProvider); ok {
					if !t.(android.Module).Enabled() {
						if ctx.AConfig().AllowMissingDependencies() {
							ctx.AddMissingDependencies([]string{tool})
						} else {
							ctx.ModuleErrorf("depends on disabled module %q", tool)
						}
						break
					}
					path = t.HostToolPath()
				} else {
					ctx.ModuleErrorf("%q is not a host tool provider", tool)
					break
				}

				if path.Valid() {
					implicitDeps = append(implicitDeps, path.Path())
					if _, exists := tools[tool]; !exists {
						tools[tool] = path.Path()
					} else {
						ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], path.Path().String())
					}
				} else {
					ctx.ModuleErrorf("host tool %q missing output file", tool)
				}
			default:
				ctx.ModuleErrorf("unknown dependency on %q", ctx.OtherModuleName(module))
			}
		})
	}

	// Get more information about each entry in "tool_files".
	for _, tool := range g.properties.Tool_files {
		toolPath := android.PathForModuleSrc(ctx, tool)
		implicitDeps = append(implicitDeps, toolPath)
		if _, exists := tools[tool]; !exists {
			tools[tool] = toolPath
		} else {
			ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], toolPath.String())
		}
	}
	return
}

// expandCmd is an internal function to do some expansion and any additional
// wrapping of the generator command line. Returns the command line to use and
// an error value.
func (g *waylandGenModule) expandCmd(ctx android.ModuleContext, tools map[string]android.Path) (cmd string) {
	cmd, err := android.Expand(proptools.String(g.properties.Cmd), func(name string) (string, error) {
		switch name {
		case "in":
			return "$in", nil
		case "out":
			// We need to use the sandbox out path instead
			//return "$sandboxOut", nil
			return "$out", nil
		case "location":
			if len(g.properties.Tools) > 0 {
				return tools[g.properties.Tools[0]].String(), nil
			} else {
				return tools[g.properties.Tool_files[0]].String(), nil
			}
		default:
			if strings.HasPrefix(name, "location ") {
				label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
				if tool, ok := tools[label]; ok {
					return tool.String(), nil
				} else {
					return "", fmt.Errorf("unknown location label %q", label)
				}
			}
			return "", fmt.Errorf("unknown variable '$(%s)'", name)
		}
	})
	if err != nil {
		ctx.PropertyErrorf("cmd", "%s", err.Error())
	}
	return
}

// waylandCodegenModuleFactory creates an extension module instance.
func waylandCodegenModuleFactory() android.Module {
	m := &waylandGenModule{}
	m.AddProperties(&m.properties)
	android.InitAndroidModule(m)
	return m
}

// splitExt splits a base filename into (filename, ext) components, such that
// input == filename + ext
func splitExt(input string) (filename string, ext string) {
	// There is no filepath.SplitExt() or equivalent.
	dot := strings.LastIndex(input, ".")
	if dot != -1 {
		ext = input[dot:]
		filename = input[:dot]
	} else {
		ext = ""
		filename = input
	}
	return
}