Add build_flag_declarations module

And generate build-flags/build_flags.{pb,textproto} files

Bug: 328495189
Test: manual,TH
Change-Id: I97eb864e2d84bc606fef7e9077554894338edaa0
diff --git a/aconfig/build_flags/Android.bp b/aconfig/build_flags/Android.bp
new file mode 100644
index 0000000..69e4316
--- /dev/null
+++ b/aconfig/build_flags/Android.bp
@@ -0,0 +1,23 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-aconfig-build_flags",
+    pkgPath: "android/soong/aconfig/build_flags",
+    deps: [
+        "blueprint",
+        "blueprint-pathtools",
+        "sbox_proto",
+        "soong",
+        "soong-android",
+    ],
+    srcs: [
+        "all_build_flag_declarations.go",
+        "declarations.go",
+        "init.go",
+    ],
+    testSrcs: [
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/aconfig/build_flags/all_build_flag_declarations.go b/aconfig/build_flags/all_build_flag_declarations.go
new file mode 100644
index 0000000..282c9dc
--- /dev/null
+++ b/aconfig/build_flags/all_build_flag_declarations.go
@@ -0,0 +1,78 @@
+// Copyright 2023 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 build_flags
+
+import (
+	"android/soong/android"
+)
+
+// A singleton module that collects all of the build flags declared in the
+// tree into a single combined file for export to the external flag setting
+// server (inside Google it's Gantry).
+//
+// Note that this is ALL build_declarations modules present in the tree, not just
+// ones that are relevant to the product currently being built, so that that infra
+// doesn't need to pull from multiple builds and merge them.
+func AllBuildFlagDeclarationsFactory() android.Singleton {
+	return &allBuildFlagDeclarationsSingleton{}
+}
+
+type allBuildFlagDeclarationsSingleton struct {
+	intermediateBinaryProtoPath android.OutputPath
+	intermediateTextProtoPath   android.OutputPath
+}
+
+func (this *allBuildFlagDeclarationsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	// Find all of the build_flag_declarations modules
+	var intermediateFiles android.Paths
+	ctx.VisitAllModules(func(module android.Module) {
+		decl, ok := android.SingletonModuleProvider(ctx, module, BuildFlagDeclarationsProviderKey)
+		if !ok {
+			return
+		}
+		intermediateFiles = append(intermediateFiles, decl.IntermediateCacheOutputPath)
+	})
+
+	// Generate build action for build_flag (binary proto output)
+	this.intermediateBinaryProtoPath = android.PathForIntermediates(ctx, "all_build_flag_declarations.pb")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        allDeclarationsRule,
+		Inputs:      intermediateFiles,
+		Output:      this.intermediateBinaryProtoPath,
+		Description: "all_build_flag_declarations",
+		Args: map[string]string{
+			"intermediates": android.JoinPathsWithPrefix(intermediateFiles, "--intermediate "),
+		},
+	})
+	ctx.Phony("all_build_flag_declarations", this.intermediateBinaryProtoPath)
+
+	// Generate build action for build_flag (text proto output)
+	this.intermediateTextProtoPath = android.PathForIntermediates(ctx, "all_build_flag_declarations.textproto")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        allDeclarationsRuleTextProto,
+		Input:       this.intermediateBinaryProtoPath,
+		Output:      this.intermediateTextProtoPath,
+		Description: "all_build_flag_declarations_textproto",
+	})
+	ctx.Phony("all_build_flag_declarations_textproto", this.intermediateTextProtoPath)
+}
+
+func (this *allBuildFlagDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.DistForGoal("droid", this.intermediateBinaryProtoPath)
+	for _, goal := range []string{"docs", "droid", "sdk"} {
+		ctx.DistForGoalWithFilename(goal, this.intermediateBinaryProtoPath, "build_flags/all_flags.pb")
+		ctx.DistForGoalWithFilename(goal, this.intermediateTextProtoPath, "build_flags/all_flags.textproto")
+	}
+}
diff --git a/aconfig/build_flags/declarations.go b/aconfig/build_flags/declarations.go
new file mode 100644
index 0000000..f6a6ee1
--- /dev/null
+++ b/aconfig/build_flags/declarations.go
@@ -0,0 +1,118 @@
+// Copyright 2023 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 build_flags
+
+import (
+	"fmt"
+	"strings"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+type BuildFlagDeclarationsProviderData struct {
+	IntermediateCacheOutputPath android.WritablePath
+	IntermediateDumpOutputPath  android.WritablePath
+}
+
+var BuildFlagDeclarationsProviderKey = blueprint.NewProvider[BuildFlagDeclarationsProviderData]()
+
+type DeclarationsModule struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+
+	// Properties for "aconfig_declarations"
+	properties struct {
+		// aconfig files, relative to this Android.bp file
+		Srcs []string `android:"path"`
+	}
+
+	intermediatePath android.WritablePath
+}
+
+func DeclarationsFactory() android.Module {
+	module := &DeclarationsModule{}
+
+	android.InitAndroidModule(module)
+	android.InitDefaultableModule(module)
+	module.AddProperties(&module.properties)
+
+	return module
+}
+
+func (module *DeclarationsModule) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		// The default output of this module is the intermediates format, which is
+		// not installable and in a private format that no other rules can handle
+		// correctly.
+		return []android.Path{module.intermediatePath}, nil
+	default:
+		return nil, fmt.Errorf("unsupported build_flags_declarations module reference tag %q", tag)
+	}
+}
+
+func joinAndPrefix(prefix string, values []string) string {
+	var sb strings.Builder
+	for _, v := range values {
+		sb.WriteString(prefix)
+		sb.WriteString(v)
+	}
+	return sb.String()
+}
+
+func optionalVariable(prefix string, value string) string {
+	var sb strings.Builder
+	if value != "" {
+		sb.WriteString(prefix)
+		sb.WriteString(value)
+	}
+	return sb.String()
+}
+
+func (module *DeclarationsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Intermediate format
+	declarationFiles := android.PathsForModuleSrc(ctx, module.properties.Srcs)
+	intermediateCacheFilePath := android.PathForModuleOut(ctx, "build_flag_intermediate.pb")
+	inputFiles := make([]android.Path, len(declarationFiles))
+	copy(inputFiles, declarationFiles)
+
+	// TODO(lamont): generate the rc_proto.FlagArtifacts message for the sources.
+	args := map[string]string{
+		"release_version": ctx.Config().ReleaseVersion(),
+		"declarations":    android.JoinPathsWithPrefix(declarationFiles, "--decl "),
+	}
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        buildFlagRule,
+		Output:      intermediateCacheFilePath,
+		Inputs:      inputFiles,
+		Description: "build_flag_declarations",
+		Args:        args,
+	})
+
+	intermediateDumpFilePath := android.PathForModuleOut(ctx, "build_flag_intermediate.textproto")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        buildFlagTextRule,
+		Output:      intermediateDumpFilePath,
+		Input:       intermediateCacheFilePath,
+		Description: "build_flag_declarations_text",
+	})
+
+	android.SetProvider(ctx, BuildFlagDeclarationsProviderKey, BuildFlagDeclarationsProviderData{
+		IntermediateCacheOutputPath: intermediateCacheFilePath,
+		IntermediateDumpOutputPath:  intermediateDumpFilePath,
+	})
+}
diff --git a/aconfig/build_flags/init.go b/aconfig/build_flags/init.go
new file mode 100644
index 0000000..5907f4e
--- /dev/null
+++ b/aconfig/build_flags/init.go
@@ -0,0 +1,78 @@
+// Copyright 2023 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 build_flags
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	pctx = android.NewPackageContext("android/soong/aconfig/build_flags")
+
+	// For build_flag_declarations: Generate cache file
+	buildFlagRule = pctx.AndroidStaticRule("build-flag-declarations",
+		blueprint.RuleParams{
+			Command: `${buildFlagDeclarations} ` +
+				` ${declarations}` +
+				` --format pb` +
+				` --output ${out}.tmp` +
+				` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
+			CommandDeps: []string{
+				"${buildFlagDeclarations}",
+			},
+			Restat: true,
+		}, "release_version", "declarations")
+
+	buildFlagTextRule = pctx.AndroidStaticRule("build-flag-declarations-text",
+		blueprint.RuleParams{
+			Command: `${buildFlagDeclarations} --format=textproto` +
+				` --intermediate ${in}` +
+				` --format textproto` +
+				` --output ${out}.tmp` +
+				` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
+			CommandDeps: []string{
+				"${buildFlagDeclarations}",
+			},
+			Restat: true,
+		})
+
+	allDeclarationsRule = pctx.AndroidStaticRule("all-build-flag-declarations-dump",
+		blueprint.RuleParams{
+			Command: `${buildFlagDeclarations} ${intermediates} --format pb --output ${out}`,
+			CommandDeps: []string{
+				"${buildFlagDeclarations}",
+			},
+		}, "intermediates")
+
+	allDeclarationsRuleTextProto = pctx.AndroidStaticRule("All_build_flag_declarations_dump_textproto",
+		blueprint.RuleParams{
+			Command: `${buildFlagDeclarations} --intermediate ${in} --format textproto --output ${out}`,
+			CommandDeps: []string{
+				"${buildFlagDeclarations}",
+			},
+		})
+)
+
+func init() {
+	RegisterBuildComponents(android.InitRegistrationContext)
+	pctx.HostBinToolVariable("buildFlagDeclarations", "build-flag-declarations")
+}
+
+func RegisterBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("build_flag_declarations", DeclarationsFactory)
+	ctx.RegisterParallelSingletonType("all_build_flag_declarations", AllBuildFlagDeclarationsFactory)
+}