Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit feec25b0 authored by Colin Cross's avatar Colin Cross
Browse files

Move dexpreopt.Script to android.RuleBuilder

Move dexpreopt.Script to android.RuleBuilder so that the builder
style can be used in more places.  Also add tests for it.

Test: rule_builder_test.go
Change-Id: I92a963bd112bf033b08899e930094b908acfcdfd
parent a55b12be
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ bootstrap_go_package {
        "android/prebuilt_etc.go",
        "android/proto.go",
        "android/register.go",
        "android/rule_builder.go",
        "android/sh_binary.go",
        "android/singleton.go",
        "android/testing.go",
@@ -80,6 +81,7 @@ bootstrap_go_package {
        "android/paths_test.go",
        "android/prebuilt_test.go",
        "android/prebuilt_etc_test.go",
        "android/rule_builder_test.go",
        "android/util_test.go",
        "android/variable_test.go",
    ],
+230 −0
Original line number Diff line number Diff line
@@ -12,34 +12,44 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package dexpreopt
package android

import (
	"fmt"
	"path/filepath"
	"sort"
	"strings"

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

type Install struct {
type RuleBuilderInstall struct {
	From, To string
}

type Rule struct {
	commands []*Command
	installs []Install
type RuleBuilder struct {
	commands []*RuleBuilderCommand
	installs []RuleBuilderInstall
	restat   bool
}

func (r *RuleBuilder) Restat() *RuleBuilder {
	r.restat = true
	return r
}

func (r *Rule) Install(from, to string) {
	r.installs = append(r.installs, Install{from, to})
func (r *RuleBuilder) Install(from, to string) {
	r.installs = append(r.installs, RuleBuilderInstall{from, to})
}

func (r *Rule) Command() *Command {
	command := &Command{}
func (r *RuleBuilder) Command() *RuleBuilderCommand {
	command := &RuleBuilderCommand{}
	r.commands = append(r.commands, command)
	return command
}

func (r *Rule) Inputs() []string {
func (r *RuleBuilder) Inputs() []string {
	outputs := r.outputSet()

	inputs := make(map[string]bool)
@@ -60,7 +70,7 @@ func (r *Rule) Inputs() []string {
	return inputList
}

func (r *Rule) outputSet() map[string]bool {
func (r *RuleBuilder) outputSet() map[string]bool {
	outputs := make(map[string]bool)
	for _, c := range r.commands {
		for _, output := range c.outputs {
@@ -70,7 +80,7 @@ func (r *Rule) outputSet() map[string]bool {
	return outputs
}

func (r *Rule) Outputs() []string {
func (r *RuleBuilder) Outputs() []string {
	outputs := r.outputSet()

	var outputList []string
@@ -81,11 +91,11 @@ func (r *Rule) Outputs() []string {
	return outputList
}

func (r *Rule) Installs() []Install {
	return append([]Install(nil), r.installs...)
func (r *RuleBuilder) Installs() []RuleBuilderInstall {
	return append([]RuleBuilderInstall(nil), r.installs...)
}

func (r *Rule) Tools() []string {
func (r *RuleBuilder) Tools() []string {
	var tools []string
	for _, c := range r.commands {
		tools = append(tools, c.tools...)
@@ -93,7 +103,7 @@ func (r *Rule) Tools() []string {
	return tools
}

func (r *Rule) Commands() []string {
func (r *RuleBuilder) Commands() []string {
	var commands []string
	for _, c := range r.commands {
		commands = append(commands, string(c.buf))
@@ -101,14 +111,45 @@ func (r *Rule) Commands() []string {
	return commands
}

type Command struct {
func (r *RuleBuilder) Build(pctx PackageContext, ctx ModuleContext, name string, desc string) {
	var inputs Paths
	for _, input := range r.Inputs() {
		rel, isRel := MaybeRel(ctx, PathForModuleOut(ctx).String(), input)
		if isRel {
			inputs = append(inputs, PathForModuleOut(ctx, rel))
		} else {
			// TODO: use PathForOutput once boot image is moved to where PathForOutput can find it.
			inputs = append(inputs, &unknownRulePath{input})
		}
	}

	var outputs WritablePaths
	for _, output := range r.Outputs() {
		rel := Rel(ctx, PathForModuleOut(ctx).String(), output)
		outputs = append(outputs, PathForModuleOut(ctx, rel))
	}

	if len(r.Commands()) > 0 {
		ctx.Build(pctx, BuildParams{
			Rule: ctx.Rule(pctx, name, blueprint.RuleParams{
				Command:     strings.Join(proptools.NinjaEscape(r.Commands()), " && "),
				CommandDeps: r.Tools(),
			}),
			Implicits:   inputs,
			Outputs:     outputs,
			Description: desc,
		})
	}
}

type RuleBuilderCommand struct {
	buf     []byte
	inputs  []string
	outputs []string
	tools   []string
}

func (c *Command) Text(text string) *Command {
func (c *RuleBuilderCommand) Text(text string) *RuleBuilderCommand {
	if len(c.buf) > 0 {
		c.buf = append(c.buf, ' ')
	}
@@ -116,63 +157,74 @@ func (c *Command) Text(text string) *Command {
	return c
}

func (c *Command) Textf(format string, a ...interface{}) *Command {
func (c *RuleBuilderCommand) Textf(format string, a ...interface{}) *RuleBuilderCommand {
	return c.Text(fmt.Sprintf(format, a...))
}

func (c *Command) Flag(flag string) *Command {
func (c *RuleBuilderCommand) Flag(flag string) *RuleBuilderCommand {
	return c.Text(flag)
}

func (c *Command) FlagWithArg(flag, arg string) *Command {
func (c *RuleBuilderCommand) FlagWithArg(flag, arg string) *RuleBuilderCommand {
	return c.Text(flag + arg)
}

func (c *Command) FlagWithList(flag string, list []string, sep string) *Command {
func (c *RuleBuilderCommand) FlagWithList(flag string, list []string, sep string) *RuleBuilderCommand {
	return c.Text(flag + strings.Join(list, sep))
}

func (c *Command) Tool(path string) *Command {
func (c *RuleBuilderCommand) Tool(path string) *RuleBuilderCommand {
	c.tools = append(c.tools, path)
	return c.Text(path)
}

func (c *Command) Input(path string) *Command {
func (c *RuleBuilderCommand) Input(path string) *RuleBuilderCommand {
	c.inputs = append(c.inputs, path)
	return c.Text(path)
}

func (c *Command) Implicit(path string) *Command {
func (c *RuleBuilderCommand) Implicit(path string) *RuleBuilderCommand {
	c.inputs = append(c.inputs, path)
	return c
}

func (c *Command) Implicits(paths []string) *Command {
func (c *RuleBuilderCommand) Implicits(paths []string) *RuleBuilderCommand {
	c.inputs = append(c.inputs, paths...)
	return c
}

func (c *Command) Output(path string) *Command {
func (c *RuleBuilderCommand) Output(path string) *RuleBuilderCommand {
	c.outputs = append(c.outputs, path)
	return c.Text(path)
}

func (c *Command) ImplicitOutput(path string) *Command {
func (c *RuleBuilderCommand) ImplicitOutput(path string) *RuleBuilderCommand {
	c.outputs = append(c.outputs, path)
	return c
}

func (c *Command) FlagWithInput(flag, path string) *Command {
func (c *RuleBuilderCommand) FlagWithInput(flag, path string) *RuleBuilderCommand {
	c.inputs = append(c.inputs, path)
	return c.Text(flag + path)
}

func (c *Command) FlagWithInputList(flag string, paths []string, sep string) *Command {
func (c *RuleBuilderCommand) FlagWithInputList(flag string, paths []string, sep string) *RuleBuilderCommand {
	c.inputs = append(c.inputs, paths...)
	return c.FlagWithList(flag, paths, sep)
}

func (c *Command) FlagWithOutput(flag, path string) *Command {
func (c *RuleBuilderCommand) FlagWithOutput(flag, path string) *RuleBuilderCommand {
	c.outputs = append(c.outputs, path)
	return c.Text(flag + path)
}

type unknownRulePath struct {
	path string
}

var _ Path = (*unknownRulePath)(nil)

func (p *unknownRulePath) String() string { return p.path }
func (p *unknownRulePath) Ext() string    { return filepath.Ext(p.path) }
func (p *unknownRulePath) Base() string   { return filepath.Base(p.path) }
func (p *unknownRulePath) Rel() string    { return p.path }
+148 −0
Original line number Diff line number Diff line
// Copyright 2019 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 android

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

func TestRuleBuilder(t *testing.T) {
	rule := RuleBuilder{}

	cmd := rule.Command().
		Flag("Flag").
		FlagWithArg("FlagWithArg=", "arg").
		FlagWithInput("FlagWithInput=", "input").
		FlagWithOutput("FlagWithOutput=", "output").
		Implicit("Implicit").
		ImplicitOutput("ImplicitOutput").
		Input("Input").
		Output("Output").
		Text("Text").
		Tool("Tool")

	rule.Command().
		Text("command2").
		Input("input2").
		Output("output2").
		Tool("tool2")

	// Test updates to the first command after the second command has been started
	cmd.Text("after command2")
	// Test updating a command when the previous update did not replace the cmd variable
	cmd.Text("old cmd")

	// Test a command that uses the output of a previous command as an input
	rule.Command().
		Text("command3").
		Input("input3").
		Input("output2").
		Output("output3")

	wantCommands := []string{
		"Flag FlagWithArg=arg FlagWithInput=input FlagWithOutput=output Input Output Text Tool after command2 old cmd",
		"command2 input2 output2 tool2",
		"command3 input3 output2 output3",
	}
	wantInputs := []string{"Implicit", "Input", "input", "input2", "input3"}
	wantOutputs := []string{"ImplicitOutput", "Output", "output", "output2", "output3"}
	wantTools := []string{"Tool", "tool2"}

	if !reflect.DeepEqual(rule.Commands(), wantCommands) {
		t.Errorf("\nwant rule.Commands() = %#v\n                   got %#v", wantCommands, rule.Commands())
	}
	if !reflect.DeepEqual(rule.Inputs(), wantInputs) {
		t.Errorf("\nwant rule.Inputs() = %#v\n                 got %#v", wantInputs, rule.Inputs())
	}
	if !reflect.DeepEqual(rule.Outputs(), wantOutputs) {
		t.Errorf("\nwant rule.Outputs() = %#v\n                  got %#v", wantOutputs, rule.Outputs())
	}
	if !reflect.DeepEqual(rule.Tools(), wantTools) {
		t.Errorf("\nwant rule.Tools() = %#v\n                got %#v", wantTools, rule.Tools())
	}
}

func testRuleBuilderFactory() Module {
	module := &testRuleBuilderModule{}
	module.AddProperties(&module.properties)
	InitAndroidModule(module)
	return module
}

type testRuleBuilderModule struct {
	ModuleBase
	properties struct {
		Src string
	}
}

func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
	rule := RuleBuilder{}

	in := PathForSource(ctx, t.properties.Src)
	out := PathForModuleOut(ctx, ctx.ModuleName())

	rule.Command().Tool("cp").Input(in.String()).Output(out.String())

	rule.Build(pctx, ctx, "rule", "desc")
}

func TestRuleBuilder_Build(t *testing.T) {
	buildDir, err := ioutil.TempDir("", "soong_test_rule_builder")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(buildDir)

	bp := `
		rule_builder_test {
			name: "foo",
			src: "bar",
		}
	`

	config := TestConfig(buildDir, nil)
	ctx := NewTestContext()
	ctx.MockFileSystem(map[string][]byte{
		"Android.bp": []byte(bp),
		"bar":        nil,
		"cp":         nil,
	})
	ctx.RegisterModuleType("rule_builder_test", ModuleFactoryAdaptor(testRuleBuilderFactory))
	ctx.Register()

	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
	FailIfErrored(t, errs)
	_, errs = ctx.PrepareBuildActions(config)
	FailIfErrored(t, errs)

	foo := ctx.ModuleForTests("foo", "").Rule("rule")

	// TODO: make RuleParams accessible to tests and verify rule.Command().Tools() ends up in CommandDeps

	if len(foo.Implicits) != 1 || foo.Implicits[0].String() != "bar" {
		t.Errorf("want foo.Implicits = [%q], got %q", "bar", foo.Implicits.Strings())
	}

	wantOutput := filepath.Join(buildDir, ".intermediates", "foo", "foo")
	if len(foo.Outputs) != 1 || foo.Outputs[0].String() != wantOutput {
		t.Errorf("want foo.Outputs = [%q], got %q", wantOutput, foo.Outputs.Strings())
	}

}
+2 −2
Original line number Diff line number Diff line
@@ -4,12 +4,12 @@ bootstrap_go_package {
    srcs: [
        "config.go",
        "dexpreopt.go",
        "script.go",
    ],
    testSrcs: [
        "dexpreopt_test.go",
    ],
    deps: [
        "blueprint-pathtools",
        "soong-android",
    ],
}
+9 −7
Original line number Diff line number Diff line
@@ -39,6 +39,8 @@ import (
	"path/filepath"
	"strings"

	"android/soong/android"

	"github.com/google/blueprint/pathtools"
)

@@ -47,7 +49,7 @@ const SystemOtherPartition = "/system_other/"

// GenerateStripRule generates a set of commands that will take an APK or JAR as an input and strip the dex files if
// they are no longer necessary after preopting.
func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *Rule, err error) {
func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
	defer func() {
		if r := recover(); r != nil {
			if e, ok := r.(error); ok {
@@ -61,7 +63,7 @@ func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *Rule, er

	tools := global.Tools

	rule = &Rule{}
	rule = &android.RuleBuilder{}

	strip := shouldStripDex(module, global)

@@ -81,7 +83,7 @@ func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *Rule, er

// GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a
// ModuleConfig.  The produced files and their install locations will be available through rule.Installs().
func GenerateDexpreoptRule(global GlobalConfig, module ModuleConfig) (rule *Rule, err error) {
func GenerateDexpreoptRule(global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
	defer func() {
		if r := recover(); r != nil {
			if e, ok := r.(error); ok {
@@ -93,7 +95,7 @@ func GenerateDexpreoptRule(global GlobalConfig, module ModuleConfig) (rule *Rule
		}
	}()

	rule = &Rule{}
	rule = &android.RuleBuilder{}

	generateProfile := module.ProfileClassListing != "" && !global.DisableGenerateProfile

@@ -141,7 +143,7 @@ func dexpreoptDisabled(global GlobalConfig, module ModuleConfig) bool {
	return false
}

func profileCommand(global GlobalConfig, module ModuleConfig, rule *Rule) string {
func profileCommand(global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder) string {
	profilePath := filepath.Join(filepath.Dir(module.BuildPath), "profile.prof")
	profileInstalledPath := module.DexLocation + ".prof"

@@ -178,8 +180,8 @@ func profileCommand(global GlobalConfig, module ModuleConfig, rule *Rule) string
	return profilePath
}

func dexpreoptCommand(global GlobalConfig, module ModuleConfig, rule *Rule, profile, arch, bootImageLocation string,
	appImage, generateDM bool) {
func dexpreoptCommand(global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder,
	profile, arch, bootImageLocation string, appImage, generateDM bool) {

	// HACK: make soname in Soong-generated .odex files match Make.
	base := filepath.Base(module.DexLocation)
Loading