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

Commit 8321c1a8 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Use hashed subdir for soong_config modules"

parents 979dee31 81b00a8d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -565,6 +565,12 @@ modules (`cc_defaults`, `java_defaults`, etc.), which can then be referenced
by all of the vendor's other modules using the normal namespace and visibility
rules.

`soongConfigTraceMutator` enables modules affected by soong config variables to
write outputs into a hashed directory path. It does this by recording accesses
to soong config variables on each module, and then accumulating records of each
module's all dependencies. `m soong_config_trace` builds information about
hashes to `$OUT_DIR/soong/soong_config_trace.json`.

## Build logic

The build logic is written in Go using the
+89 −0
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@
package android

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/url"
	"os"
@@ -714,6 +717,31 @@ func SortedUniqueNamedPaths(l NamedPaths) NamedPaths {
	return l[:k+1]
}

// soongConfigTrace holds all references to VendorVars. Uses []string for blueprint:"mutated"
type soongConfigTrace struct {
	Bools   []string `json:",omitempty"`
	Strings []string `json:",omitempty"`
	IsSets  []string `json:",omitempty"`
}

func (c *soongConfigTrace) isEmpty() bool {
	return len(c.Bools) == 0 && len(c.Strings) == 0 && len(c.IsSets) == 0
}

// Returns hash of serialized trace records (empty string if there's no trace recorded)
func (c *soongConfigTrace) hash() string {
	// Use MD5 for speed. We don't care collision or preimage attack
	if c.isEmpty() {
		return ""
	}
	j, err := json.Marshal(c)
	if err != nil {
		panic(fmt.Errorf("json marshal of %#v failed: %#v", *c, err))
	}
	hash := md5.Sum(j)
	return hex.EncodeToString(hash[:])
}

type nameProperties struct {
	// The name of the module.  Must be unique across all modules.
	Name *string
@@ -958,6 +986,10 @@ type commonProperties struct {

	// Bazel conversion status
	BazelConversionStatus BazelConversionStatus `blueprint:"mutated"`

	// SoongConfigTrace records accesses to VendorVars (soong_config)
	SoongConfigTrace     soongConfigTrace `blueprint:"mutated"`
	SoongConfigTraceHash string           `blueprint:"mutated"`
}

// CommonAttributes represents the common Bazel attributes from which properties
@@ -3160,6 +3192,10 @@ func (m *moduleContext) ModuleSubDir() string {
	return m.bp.ModuleSubDir()
}

func (m *moduleContext) ModuleSoongConfigHash() string {
	return m.module.base().commonProperties.SoongConfigTraceHash
}

func (b *baseModuleContext) Target() Target {
	return b.target
}
@@ -3744,6 +3780,8 @@ func (m *moduleContext) TargetRequiredModuleNames() []string {

func init() {
	RegisterParallelSingletonType("buildtarget", BuildTargetSingleton)
	RegisterParallelSingletonType("soongconfigtrace", soongConfigTraceSingletonFunc)
	FinalDepsMutators(registerSoongConfigTraceMutator)
}

func BuildTargetSingleton() Singleton {
@@ -3925,3 +3963,54 @@ func (d *installPathsDepSet) ToList() InstallPaths {
	}
	return d.depSet.ToList().(InstallPaths)
}

func registerSoongConfigTraceMutator(ctx RegisterMutatorsContext) {
	ctx.BottomUp("soongconfigtrace", soongConfigTraceMutator).Parallel()
}

// soongConfigTraceMutator accumulates recorded soong_config trace from children. Also it normalizes
// SoongConfigTrace to make it consistent.
func soongConfigTraceMutator(ctx BottomUpMutatorContext) {
	trace := &ctx.Module().base().commonProperties.SoongConfigTrace
	ctx.VisitDirectDeps(func(m Module) {
		childTrace := &m.base().commonProperties.SoongConfigTrace
		trace.Bools = append(trace.Bools, childTrace.Bools...)
		trace.Strings = append(trace.Strings, childTrace.Strings...)
		trace.IsSets = append(trace.IsSets, childTrace.IsSets...)
	})
	trace.Bools = SortedUniqueStrings(trace.Bools)
	trace.Strings = SortedUniqueStrings(trace.Strings)
	trace.IsSets = SortedUniqueStrings(trace.IsSets)

	ctx.Module().base().commonProperties.SoongConfigTraceHash = trace.hash()
}

// soongConfigTraceSingleton writes a map from each module's config hash value to trace data.
func soongConfigTraceSingletonFunc() Singleton {
	return &soongConfigTraceSingleton{}
}

type soongConfigTraceSingleton struct {
}

func (s *soongConfigTraceSingleton) GenerateBuildActions(ctx SingletonContext) {
	outFile := PathForOutput(ctx, "soong_config_trace.json")

	traces := make(map[string]*soongConfigTrace)
	ctx.VisitAllModules(func(module Module) {
		trace := &module.base().commonProperties.SoongConfigTrace
		if !trace.isEmpty() {
			hash := module.base().commonProperties.SoongConfigTraceHash
			traces[hash] = trace
		}
	})

	j, err := json.Marshal(traces)
	if err != nil {
		ctx.Errorf("json marshal to %q failed: %#v", outFile, err)
		return
	}

	WriteFileRule(ctx, outFile, string(j))
	ctx.Phony("soong_config_trace", outFile)
}
+5 −1
Original line number Diff line number Diff line
@@ -1475,7 +1475,11 @@ type ModuleOutPathContext interface {
}

func pathForModuleOut(ctx ModuleOutPathContext) OutputPath {
	return PathForOutput(ctx, ".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir())
	soongConfigHash := ""
	if i, ok := ctx.(interface{ ModuleSoongConfigHash() string }); ok {
		soongConfigHash = i.ModuleSoongConfigHash()
	}
	return PathForOutput(ctx, ".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir(), soongConfigHash)
}

// PathForModuleOut returns a Path representing the paths... under the module's
+55 −2
Original line number Diff line number Diff line
@@ -421,6 +421,57 @@ func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[s
	}).(map[string]blueprint.ModuleFactory)
}

// tracingConfig is a wrapper to soongconfig.SoongConfig which records all accesses to SoongConfig.
type tracingConfig struct {
	config    soongconfig.SoongConfig
	boolSet   map[string]bool
	stringSet map[string]string
	isSetSet  map[string]bool
}

func (c *tracingConfig) Bool(name string) bool {
	c.boolSet[name] = c.config.Bool(name)
	return c.boolSet[name]
}

func (c *tracingConfig) String(name string) string {
	c.stringSet[name] = c.config.String(name)
	return c.stringSet[name]
}

func (c *tracingConfig) IsSet(name string) bool {
	c.isSetSet[name] = c.config.IsSet(name)
	return c.isSetSet[name]
}

func (c *tracingConfig) getTrace() soongConfigTrace {
	ret := soongConfigTrace{}

	for k, v := range c.boolSet {
		ret.Bools = append(ret.Bools, fmt.Sprintf("%q:%t", k, v))
	}
	for k, v := range c.stringSet {
		ret.Strings = append(ret.Strings, fmt.Sprintf("%q:%q", k, v))
	}
	for k, v := range c.isSetSet {
		ret.IsSets = append(ret.IsSets, fmt.Sprintf("%q:%t", k, v))
	}

	return ret
}

func newTracingConfig(config soongconfig.SoongConfig) *tracingConfig {
	c := tracingConfig{
		config:    config,
		boolSet:   make(map[string]bool),
		stringSet: make(map[string]string),
		isSetSet:  make(map[string]bool),
	}
	return &c
}

var _ soongconfig.SoongConfig = (*tracingConfig)(nil)

// configModuleFactory takes an existing soongConfigModuleFactory and a
// ModuleType to create a new ModuleFactory that uses a custom loadhook.
func configModuleFactory(factory blueprint.ModuleFactory, moduleType *soongconfig.ModuleType, bp2build bool) blueprint.ModuleFactory {
@@ -485,8 +536,8 @@ func configModuleFactory(factory blueprint.ModuleFactory, moduleType *soongconfi
			// conditional on Soong config variables by reading the product
			// config variables from Make.
			AddLoadHook(module, func(ctx LoadHookContext) {
				config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
				newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
				tracingConfig := newTracingConfig(ctx.Config().VendorConfig(moduleType.ConfigNamespace))
				newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, tracingConfig)
				if err != nil {
					ctx.ModuleErrorf("%s", err)
					return
@@ -494,6 +545,8 @@ func configModuleFactory(factory blueprint.ModuleFactory, moduleType *soongconfi
				for _, ps := range newProps {
					ctx.AppendProperties(ps)
				}

				module.(Module).base().commonProperties.SoongConfigTrace = tracingConfig.getTrace()
			})
		}
		return module, props
+200 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ package android

import (
	"fmt"
	"path/filepath"
	"testing"
)

@@ -35,6 +36,7 @@ type soongConfigTestModule struct {
	ModuleBase
	DefaultableModuleBase
	props      soongConfigTestModuleProperties
	outputPath ModuleOutPath
}

type soongConfigTestModuleProperties struct {
@@ -49,7 +51,9 @@ func soongConfigTestModuleFactory() Module {
	return m
}

func (t soongConfigTestModule) GenerateAndroidBuildActions(ModuleContext) {}
func (t *soongConfigTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
	t.outputPath = PathForModuleOut(ctx, "test")
}

var prepareForSoongConfigTestModule = FixtureRegisterWithContext(func(ctx RegistrationContext) {
	ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
@@ -503,3 +507,197 @@ func TestSoongConfigModuleSingletonModule(t *testing.T) {
		})
	}
}

func TestSoongConfigModuleTrace(t *testing.T) {
	bp := `
		soong_config_module_type {
			name: "acme_test",
			module_type: "test",
			config_namespace: "acme",
			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
			bool_variables: ["feature2", "unused_feature", "always_true"],
			value_variables: ["size", "unused_size"],
			properties: ["cflags", "srcs", "defaults"],
		}

		soong_config_module_type {
			name: "acme_test_defaults",
			module_type: "test_defaults",
			config_namespace: "acme",
			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
			bool_variables: ["feature2", "unused_feature", "always_true"],
			value_variables: ["size", "unused_size"],
			properties: ["cflags", "srcs", "defaults"],
		}

		soong_config_string_variable {
			name: "board",
			values: ["soc_a", "soc_b", "soc_c"],
		}

		soong_config_string_variable {
			name: "unused_string_var",
			values: ["a", "b"],
		}

		soong_config_bool_variable {
			name: "feature1",
		}

		soong_config_bool_variable {
			name: "FEATURE3",
		}

		test_defaults {
			name: "test_defaults",
			cflags: ["DEFAULT"],
		}

		test {
			name: "normal",
			defaults: ["test_defaults"],
		}

		acme_test {
			name: "board_1",
			defaults: ["test_defaults"],
			soong_config_variables: {
				board: {
					soc_a: {
						cflags: ["-DSOC_A"],
					},
				},
			},
		}

		acme_test {
			name: "board_2",
			defaults: ["test_defaults"],
			soong_config_variables: {
				board: {
					soc_a: {
						cflags: ["-DSOC_A"],
					},
				},
			},
		}

		acme_test {
			name: "size",
			defaults: ["test_defaults"],
			soong_config_variables: {
				size: {
					cflags: ["-DSIZE=%s"],
				},
			},
		}

		acme_test {
			name: "board_and_size",
			defaults: ["test_defaults"],
			soong_config_variables: {
				board: {
					soc_a: {
						cflags: ["-DSOC_A"],
					},
				},
				size: {
					cflags: ["-DSIZE=%s"],
				},
			},
		}

		acme_test_defaults {
			name: "board_defaults",
			soong_config_variables: {
				board: {
					soc_a: {
						cflags: ["-DSOC_A"],
					},
				},
			},
		}

		acme_test_defaults {
			name: "size_defaults",
			soong_config_variables: {
				size: {
					cflags: ["-DSIZE=%s"],
				},
			},
		}

		test {
			name: "board_and_size_with_defaults",
			defaults: ["board_defaults", "size_defaults"],
		}
    `

	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
			variables.VendorVars = vars
		})
	}

	preparer := fixtureForVendorVars(map[string]map[string]string{
		"acme": {
			"board":    "soc_a",
			"size":     "42",
			"feature1": "true",
			"feature2": "false",
			// FEATURE3 unset
			"unused_feature":    "true", // unused
			"unused_size":       "1",    // unused
			"unused_string_var": "a",    // unused
			"always_true":       "true",
		},
	})

	t.Run("soong config trace hash", func(t *testing.T) {
		result := GroupFixturePreparers(
			preparer,
			PrepareForTestWithDefaults,
			PrepareForTestWithSoongConfigModuleBuildComponents,
			prepareForSoongConfigTestModule,
			FixtureRegisterWithContext(func(ctx RegistrationContext) {
				ctx.FinalDepsMutators(registerSoongConfigTraceMutator)
			}),
			FixtureWithRootAndroidBp(bp),
		).RunTest(t)

		// Hashes of modules not using soong config should be empty
		normal := result.ModuleForTests("normal", "").Module().(*soongConfigTestModule)
		AssertDeepEquals(t, "normal hash", normal.base().commonProperties.SoongConfigTraceHash, "")
		AssertDeepEquals(t, "normal hash out", normal.outputPath.RelativeToTop().String(), "out/soong/.intermediates/normal/test")

		board1 := result.ModuleForTests("board_1", "").Module().(*soongConfigTestModule)
		board2 := result.ModuleForTests("board_2", "").Module().(*soongConfigTestModule)
		size := result.ModuleForTests("size", "").Module().(*soongConfigTestModule)

		// Trace mutator sets soong config trace hash correctly
		board1Hash := board1.base().commonProperties.SoongConfigTrace.hash()
		board1Output := board1.outputPath.RelativeToTop().String()
		AssertDeepEquals(t, "board hash calc", board1Hash, board1.base().commonProperties.SoongConfigTraceHash)
		AssertDeepEquals(t, "board hash path", board1Output, filepath.Join("out/soong/.intermediates/board_1", board1Hash, "test"))

		sizeHash := size.base().commonProperties.SoongConfigTrace.hash()
		sizeOutput := size.outputPath.RelativeToTop().String()
		AssertDeepEquals(t, "size hash calc", sizeHash, size.base().commonProperties.SoongConfigTraceHash)
		AssertDeepEquals(t, "size hash path", sizeOutput, filepath.Join("out/soong/.intermediates/size", sizeHash, "test"))

		// Trace should be identical for modules using the same set of variables
		AssertDeepEquals(t, "board trace", board1.base().commonProperties.SoongConfigTrace, board2.base().commonProperties.SoongConfigTrace)
		AssertDeepEquals(t, "board hash", board1.base().commonProperties.SoongConfigTraceHash, board2.base().commonProperties.SoongConfigTraceHash)

		// Trace hash should be different for different sets of soong variables
		AssertBoolEquals(t, "board hash not equal to size hash", board1.base().commonProperties.SoongConfigTraceHash == size.commonProperties.SoongConfigTraceHash, false)

		boardSize := result.ModuleForTests("board_and_size", "").Module().(*soongConfigTestModule)
		boardSizeDefaults := result.ModuleForTests("board_and_size_with_defaults", "").Module()

		// Trace should propagate
		AssertDeepEquals(t, "board_size hash calc", boardSize.base().commonProperties.SoongConfigTrace.hash(), boardSize.base().commonProperties.SoongConfigTraceHash)
		AssertDeepEquals(t, "board_size trace", boardSize.base().commonProperties.SoongConfigTrace, boardSizeDefaults.base().commonProperties.SoongConfigTrace)
		AssertDeepEquals(t, "board_size hash", boardSize.base().commonProperties.SoongConfigTraceHash, boardSizeDefaults.base().commonProperties.SoongConfigTraceHash)
	})
}