Use hashed subdir for soong_config modules

This is to differentiate soong intermediate directories for soong config
modules. This will help incremental build across different
devices.

Test result of building panther, building cheetah, and building panther
again:

Before this change
- build time: 02:57
- # of tasks: 31044

After this change
- build time: 01:48
- # of tasks: 1694

Build time includes build.ninja generating time (which is more than 1
minute), so the overriden artifacts become far fewer.

And "NINJA_ARGS='-n -d explain' m" only gave 4 "command line changed"
nodes.

Bug: 279362051
Test: see above
Change-Id: I4891cbe823ae21628465e5c6eb26a4837ccdd202
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index cab3e2d..79bdeb8 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path/filepath"
 	"testing"
 )
 
@@ -34,7 +35,8 @@
 type soongConfigTestModule struct {
 	ModuleBase
 	DefaultableModuleBase
-	props soongConfigTestModuleProperties
+	props      soongConfigTestModuleProperties
+	outputPath ModuleOutPath
 }
 
 type soongConfigTestModuleProperties struct {
@@ -49,7 +51,9 @@
 	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 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)
+	})
+}