Golang程序  |  292行  |  7.79 KB

// Copyright 2015 Google Inc. All rights reserved.
//
// 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 genrule

import (
	"fmt"
	"strings"

	"github.com/google/blueprint"

	"android/soong/android"
)

func init() {
	android.RegisterModuleType("gensrcs", GenSrcsFactory)
	android.RegisterModuleType("genrule", GenRuleFactory)
}

var (
	pctx = android.NewPackageContext("android/soong/genrule")
)

type SourceFileGenerator interface {
	GeneratedSourceFiles() android.Paths
	GeneratedHeaderDirs() android.Paths
}

type HostToolProvider interface {
	HostToolPath() android.OptionalPath
}

type generatorProperties struct {
	// command to run on one or more input files.  Available variables for substitution:
	// $(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): one or more input files
	// $(out): a single output file
	// $(deps): a file to which dependencies will be written, if the depfile property is set to true
	// $(genDir): the sandbox directory for this tool; contains $(out)
	// $$: a literal $
	//
	// DO NOT directly reference paths to files in the source tree, or the
	// command will be missing proper dependencies to re-run if the files
	// change.
	Cmd string

	// Enable reading a file containing dependencies in gcc format after the command completes
	Depfile bool

	// name of the modules (if any) that produces the host executable.   Leave empty for
	// prebuilts or scripts that do not need a module to build them.
	Tools []string

	// Local file that is used as the tool
	Tool_files []string

	// List of directories to export generated headers from
	Export_include_dirs []string

	// list of input files
	Srcs []string
}

type generator struct {
	android.ModuleBase

	properties generatorProperties

	tasks taskFunc

	deps android.Paths
	rule blueprint.Rule

	exportedIncludeDirs android.Paths

	outputFiles android.Paths
}

type taskFunc func(ctx android.ModuleContext, srcFiles android.Paths) []generateTask

type generateTask struct {
	in  android.Paths
	out android.WritablePaths
}

func (g *generator) GeneratedSourceFiles() android.Paths {
	return g.outputFiles
}

func (g *generator) Srcs() android.Paths {
	return g.outputFiles
}

func (g *generator) GeneratedHeaderDirs() android.Paths {
	return g.exportedIncludeDirs
}

func (g *generator) DepsMutator(ctx android.BottomUpMutatorContext) {
	android.ExtractSourcesDeps(ctx, g.properties.Srcs)
	if g, ok := ctx.Module().(*generator); ok {
		if len(g.properties.Tools) > 0 {
			ctx.AddFarVariationDependencies([]blueprint.Variation{
				{"arch", ctx.AConfig().BuildOsVariant},
			}, nil, g.properties.Tools...)
		}
	}
}

func (g *generator) 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
	}

	if len(g.properties.Export_include_dirs) > 0 {
		for _, dir := range g.properties.Export_include_dirs {
			g.exportedIncludeDirs = append(g.exportedIncludeDirs,
				android.PathForModuleGen(ctx, ctx.ModuleDir(), dir))
		}
	} else {
		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, ""))
	}

	tools := map[string]android.Path{}

	if len(g.properties.Tools) > 0 {
		ctx.VisitDirectDeps(func(module blueprint.Module) {
			if t, ok := module.(HostToolProvider); ok {
				p := t.HostToolPath()
				if p.Valid() {
					g.deps = append(g.deps, p.Path())
					tool := ctx.OtherModuleName(module)
					if _, exists := tools[tool]; !exists {
						tools[tool] = p.Path()
					} else {
						ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], p.Path().String())
					}
				} else {
					ctx.ModuleErrorf("host tool %q missing output file", ctx.OtherModuleName(module))
				}
			}
		})
	}

	for _, tool := range g.properties.Tool_files {
		toolPath := android.PathForModuleSrc(ctx, tool)
		g.deps = append(g.deps, toolPath)
		if _, exists := tools[tool]; !exists {
			tools[tool] = toolPath
		} else {
			ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], toolPath.String())
		}
	}

	cmd, err := android.Expand(g.properties.Cmd, func(name string) (string, error) {
		switch name {
		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
			}
		case "in":
			return "${in}", nil
		case "out":
			return "${out}", nil
		case "depfile":
			if !g.properties.Depfile {
				return "", fmt.Errorf("$(depfile) used without depfile property")
			}
			return "${depfile}", nil
		case "genDir":
			return android.PathForModuleGen(ctx, "").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())
	}

	ruleParams := blueprint.RuleParams{
		Command: cmd,
	}
	var args []string
	if g.properties.Depfile {
		ruleParams.Deps = blueprint.DepsGCC
		args = append(args, "depfile")
	}
	g.rule = ctx.Rule(pctx, "generator", ruleParams, args...)

	srcFiles := ctx.ExpandSources(g.properties.Srcs, nil)
	for _, task := range g.tasks(ctx, srcFiles) {
		g.generateSourceFile(ctx, task)
	}
}

func (g *generator) generateSourceFile(ctx android.ModuleContext, task generateTask) {
	params := android.ModuleBuildParams{
		Rule:      g.rule,
		Outputs:   task.out,
		Inputs:    task.in,
		Implicits: g.deps,
	}
	if g.properties.Depfile {
		depfile := android.GenPathWithExt(ctx, "", task.out[0], task.out[0].Ext()+".d")
		params.Depfile = depfile
	}
	ctx.ModuleBuild(pctx, params)

	for _, outputFile := range task.out {
		g.outputFiles = append(g.outputFiles, outputFile)
	}
}

func generatorFactory(tasks taskFunc, props ...interface{}) (blueprint.Module, []interface{}) {
	module := &generator{
		tasks: tasks,
	}

	props = append(props, &module.properties)

	return android.InitAndroidModule(module, props...)
}

func GenSrcsFactory() (blueprint.Module, []interface{}) {
	properties := &genSrcsProperties{}

	tasks := func(ctx android.ModuleContext, srcFiles android.Paths) []generateTask {
		tasks := make([]generateTask, 0, len(srcFiles))
		for _, in := range srcFiles {
			tasks = append(tasks, generateTask{
				in:  android.Paths{in},
				out: android.WritablePaths{android.GenPathWithExt(ctx, "", in, properties.Output_extension)},
			})
		}
		return tasks
	}

	return generatorFactory(tasks, properties)
}

type genSrcsProperties struct {
	// extension that will be substituted for each output file
	Output_extension string
}

func GenRuleFactory() (blueprint.Module, []interface{}) {
	properties := &genRuleProperties{}

	tasks := func(ctx android.ModuleContext, srcFiles android.Paths) []generateTask {
		outs := make(android.WritablePaths, len(properties.Out))
		for i, out := range properties.Out {
			outs[i] = android.PathForModuleGen(ctx, out)
		}
		return []generateTask{
			{
				in:  srcFiles,
				out: outs,
			},
		}
	}

	return generatorFactory(tasks, properties)
}

type genRuleProperties struct {
	// names of the output files that will be generated
	Out []string
}