1// Copyright (C) 2021 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package aidl
16
17import (
18	"android/soong/android"
19
20	"fmt"
21	"strings"
22
23	"github.com/google/blueprint"
24	"github.com/google/blueprint/proptools"
25)
26
27var (
28	aidlMetadataRule = pctx.StaticRule("aidlMetadataRule", blueprint.RuleParams{
29		Command: `rm -f ${out} && { ` +
30			`echo '{' && ` +
31			`echo "\"name\": \"${name}\"," && ` +
32			`echo "\"stability\": \"${stability}\"," && ` +
33			`echo "\"types\": [${types}]," && ` +
34			`echo "\"hashes\": [${hashes}]" && ` +
35			`echo '}' ` +
36			`;} >> ${out}`,
37		Description: "AIDL metadata: ${out}",
38	}, "name", "stability", "types", "hashes")
39
40	joinJsonObjectsToArrayRule = pctx.StaticRule("joinJsonObjectsToArrayRule", blueprint.RuleParams{
41		Rspfile:        "$out.rsp",
42		RspfileContent: "$files",
43		Command: "rm -rf ${out} && " +
44			// Start the output array with an opening bracket.
45			"echo '[' >> ${out} && " +
46			// Append each input file and a comma to the output.
47			"for file in $$(cat ${out}.rsp); do " +
48			"cat $$file >> ${out}; echo ',' >> ${out}; " +
49			"done && " +
50			// Remove the last comma, replacing it with the closing bracket.
51			"sed -i '$$d' ${out} && echo ']' >> ${out}",
52		Description: "Joining JSON objects into array ${out}",
53	}, "files")
54)
55
56func init() {
57	android.RegisterModuleType("aidl_interfaces_metadata", aidlInterfacesMetadataSingletonFactory)
58}
59
60func aidlInterfacesMetadataSingletonFactory() android.Module {
61	i := &aidlInterfacesMetadataSingleton{}
62	android.InitAndroidModule(i)
63	return i
64}
65
66type aidlInterfacesMetadataSingleton struct {
67	android.ModuleBase
68
69	metadataPath android.WritablePath
70}
71
72var _ android.OutputFileProducer = (*aidlInterfacesMetadataSingleton)(nil)
73
74func (m *aidlInterfacesMetadataSingleton) GenerateAndroidBuildActions(ctx android.ModuleContext) {
75	if m.Name() != aidlMetadataSingletonName {
76		ctx.PropertyErrorf("name", "must be %s", aidlMetadataSingletonName)
77		return
78	}
79
80	type ModuleInfo struct {
81		Stability     string
82		ComputedTypes []string
83		HashFiles     []string
84	}
85
86	// name -> ModuleInfo
87	moduleInfos := map[string]ModuleInfo{}
88	ctx.VisitDirectDeps(func(m android.Module) {
89		if !m.ExportedToMake() {
90			return
91		}
92
93		switch t := m.(type) {
94		case *aidlInterface:
95			info := moduleInfos[t.ModuleBase.Name()]
96			info.Stability = proptools.StringDefault(t.properties.Stability, "")
97			info.ComputedTypes = t.computedTypes
98			moduleInfos[t.ModuleBase.Name()] = info
99		case *aidlGenRule:
100			info := moduleInfos[t.properties.BaseName]
101			if t.hashFile != nil {
102				info.HashFiles = append(info.HashFiles, t.hashFile.String())
103			}
104			moduleInfos[t.properties.BaseName] = info
105		}
106
107	})
108
109	var metadataOutputs android.Paths
110	for _, name := range android.SortedStringKeys(moduleInfos) {
111		info := moduleInfos[name]
112		metadataPath := android.PathForModuleOut(ctx, "metadata_"+name)
113		metadataOutputs = append(metadataOutputs, metadataPath)
114
115		// There is one aidlGenRule per-version per-backend. If we had
116		// objects per version and sub-objects per backend, we could
117		// avoid needing to filter out duplicates.
118		info.HashFiles = android.FirstUniqueStrings(info.HashFiles)
119
120		implicits := android.PathsForSource(ctx, info.HashFiles)
121
122		ctx.Build(pctx, android.BuildParams{
123			Rule:      aidlMetadataRule,
124			Implicits: implicits,
125			Output:    metadataPath,
126			Args: map[string]string{
127				"name":      name,
128				"stability": info.Stability,
129				"types":     strings.Join(wrap(`\"`, info.ComputedTypes, `\"`), ", "),
130				"hashes": strings.Join(
131					wrap(`\"$$(read -r < `,
132						info.HashFiles,
133						` hash extra; printf '%s' $$hash)\"`), ", "),
134			},
135		})
136	}
137
138	m.metadataPath = android.PathForModuleOut(ctx, "aidl_metadata.json")
139
140	ctx.Build(pctx, android.BuildParams{
141		Rule:   joinJsonObjectsToArrayRule,
142		Inputs: metadataOutputs,
143		Output: m.metadataPath,
144		Args: map[string]string{
145			"files": strings.Join(metadataOutputs.Strings(), " "),
146		},
147	})
148}
149
150func (m *aidlInterfacesMetadataSingleton) OutputFiles(tag string) (android.Paths, error) {
151	if tag != "" {
152		return nil, fmt.Errorf("unsupported tag %q", tag)
153	}
154
155	return android.Paths{m.metadataPath}, nil
156}
157