1// Copyright 2019 Google Inc. All rights reserved.
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 cc
16
17import (
18	"encoding/json"
19	"fmt"
20	"path"
21	"sort"
22	"strings"
23
24	"android/soong/android"
25)
26
27// This singleton collects cc modules' source and flags into to a json file.
28// It does so for generating CMakeLists.txt project files needed data when
29// either make, mm, mma, mmm or mmma is called.
30// The info file is generated in $OUT/module_bp_cc_depend.json.
31
32func init() {
33	android.RegisterSingletonType("ccdeps_generator", ccDepsGeneratorSingleton)
34}
35
36func ccDepsGeneratorSingleton() android.Singleton {
37	return &ccdepsGeneratorSingleton{}
38}
39
40type ccdepsGeneratorSingleton struct {
41	outputPath android.Path
42}
43
44var _ android.SingletonMakeVarsProvider = (*ccdepsGeneratorSingleton)(nil)
45
46const (
47	// Environment variables used to control the behavior of this singleton.
48	envVariableCollectCCDeps = "SOONG_COLLECT_CC_DEPS"
49	ccdepsJsonFileName       = "module_bp_cc_deps.json"
50	cClang                   = "clang"
51	cppClang                 = "clang++"
52)
53
54type ccIdeInfo struct {
55	Path                 []string     `json:"path,omitempty"`
56	Srcs                 []string     `json:"srcs,omitempty"`
57	Global_Common_Flags  ccParameters `json:"global_common_flags,omitempty"`
58	Local_Common_Flags   ccParameters `json:"local_common_flags,omitempty"`
59	Global_C_flags       ccParameters `json:"global_c_flags,omitempty"`
60	Local_C_flags        ccParameters `json:"local_c_flags,omitempty"`
61	Global_C_only_flags  ccParameters `json:"global_c_only_flags,omitempty"`
62	Local_C_only_flags   ccParameters `json:"local_c_only_flags,omitempty"`
63	Global_Cpp_flags     ccParameters `json:"global_cpp_flags,omitempty"`
64	Local_Cpp_flags      ccParameters `json:"local_cpp_flags,omitempty"`
65	System_include_flags ccParameters `json:"system_include_flags,omitempty"`
66	Module_name          string       `json:"module_name,omitempty"`
67}
68
69type ccParameters struct {
70	HeaderSearchPath       []string          `json:"header_search_path,omitempty"`
71	SystemHeaderSearchPath []string          `json:"system_search_path,omitempty"`
72	FlagParameters         []string          `json:"flag,omitempty"`
73	SysRoot                string            `json:"system_root,omitempty"`
74	RelativeFilePathFlags  map[string]string `json:"relative_file_path,omitempty"`
75}
76
77type ccMapIdeInfos map[string]ccIdeInfo
78
79type ccDeps struct {
80	C_clang   string        `json:"clang,omitempty"`
81	Cpp_clang string        `json:"clang++,omitempty"`
82	Modules   ccMapIdeInfos `json:"modules,omitempty"`
83}
84
85func (c *ccdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
86	if !ctx.Config().IsEnvTrue(envVariableCollectCCDeps) {
87		return
88	}
89
90	moduleDeps := ccDeps{}
91	moduleInfos := map[string]ccIdeInfo{}
92
93	// Track which projects have already had CMakeLists.txt generated to keep the first
94	// variant for each project.
95	seenProjects := map[string]bool{}
96
97	pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
98	moduleDeps.C_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cClang)
99	moduleDeps.Cpp_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cppClang)
100
101	ctx.VisitAllModules(func(module android.Module) {
102		if ccModule, ok := module.(*Module); ok {
103			if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
104				generateCLionProjectData(ctx, compiledModule, ccModule, seenProjects, moduleInfos)
105			}
106		}
107	})
108
109	moduleDeps.Modules = moduleInfos
110
111	ccfpath := android.PathForOutput(ctx, ccdepsJsonFileName)
112	err := createJsonFile(moduleDeps, ccfpath)
113	if err != nil {
114		ctx.Errorf(err.Error())
115	}
116	c.outputPath = ccfpath
117
118	// This is necessary to satisfy the dangling rules check as this file is written by Soong rather than a rule.
119	ctx.Build(pctx, android.BuildParams{
120		Rule:   android.Touch,
121		Output: ccfpath,
122	})
123}
124
125func (c *ccdepsGeneratorSingleton) MakeVars(ctx android.MakeVarsContext) {
126	if c.outputPath == nil {
127		return
128	}
129
130	ctx.DistForGoal("general-tests", c.outputPath)
131}
132
133func parseCompilerCCParameters(ctx android.SingletonContext, params []string) ccParameters {
134	compilerParams := ccParameters{}
135
136	cparams := []string{}
137	for _, param := range params {
138		param, _ = evalVariable(ctx, param)
139		cparams = append(cparams, param)
140	}
141
142	// Soong does not guarantee that each flag will be in an individual string. e.g: The
143	// input received could be:
144	// params = {"-isystem", "path/to/system"}
145	// or it could be
146	// params = {"-isystem path/to/system"}
147	// To normalize the input, we split all strings with the "space" character and consolidate
148	// all tokens into a flattened parameters list
149	cparams = normalizeParameters(cparams)
150
151	for i := 0; i < len(cparams); i++ {
152		param := cparams[i]
153		if param == "" {
154			continue
155		}
156
157		switch categorizeParameter(param) {
158		case headerSearchPath:
159			compilerParams.HeaderSearchPath =
160				append(compilerParams.HeaderSearchPath, strings.TrimPrefix(param, "-I"))
161		case systemHeaderSearchPath:
162			if i < len(cparams)-1 {
163				compilerParams.SystemHeaderSearchPath = append(compilerParams.SystemHeaderSearchPath, cparams[i+1])
164			}
165			i = i + 1
166		case flag:
167			c := cleanupParameter(param)
168			compilerParams.FlagParameters = append(compilerParams.FlagParameters, c)
169		case systemRoot:
170			if i < len(cparams)-1 {
171				compilerParams.SysRoot = cparams[i+1]
172			}
173			i = i + 1
174		case relativeFilePathFlag:
175			flagComponents := strings.Split(param, "=")
176			if len(flagComponents) == 2 {
177				if compilerParams.RelativeFilePathFlags == nil {
178					compilerParams.RelativeFilePathFlags = map[string]string{}
179				}
180				compilerParams.RelativeFilePathFlags[flagComponents[0]] = flagComponents[1]
181			}
182		}
183	}
184	return compilerParams
185}
186
187func generateCLionProjectData(ctx android.SingletonContext, compiledModule CompiledInterface,
188	ccModule *Module, seenProjects map[string]bool, moduleInfos map[string]ccIdeInfo) {
189	srcs := compiledModule.Srcs()
190	if len(srcs) == 0 {
191		return
192	}
193
194	// Only keep the DeviceArch variant module.
195	if ctx.DeviceConfig().DeviceArch() != ccModule.ModuleBase.Arch().ArchType.Name {
196		return
197	}
198
199	clionProjectLocation := getCMakeListsForModule(ccModule, ctx)
200	if seenProjects[clionProjectLocation] {
201		return
202	}
203
204	seenProjects[clionProjectLocation] = true
205
206	name := ccModule.ModuleBase.Name()
207	dpInfo := moduleInfos[name]
208
209	dpInfo.Path = append(dpInfo.Path, path.Dir(ctx.BlueprintFile(ccModule)))
210	dpInfo.Srcs = append(dpInfo.Srcs, srcs.Strings()...)
211	dpInfo.Path = android.FirstUniqueStrings(dpInfo.Path)
212	dpInfo.Srcs = android.FirstUniqueStrings(dpInfo.Srcs)
213
214	dpInfo.Global_Common_Flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CommonFlags)
215	dpInfo.Local_Common_Flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CommonFlags)
216	dpInfo.Global_C_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CFlags)
217	dpInfo.Local_C_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CFlags)
218	dpInfo.Global_C_only_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.ConlyFlags)
219	dpInfo.Local_C_only_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.ConlyFlags)
220	dpInfo.Global_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CppFlags)
221	dpInfo.Local_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CppFlags)
222	dpInfo.System_include_flags = parseCompilerCCParameters(ctx, ccModule.flags.SystemIncludeFlags)
223
224	dpInfo.Module_name = name
225
226	moduleInfos[name] = dpInfo
227}
228
229type Deal struct {
230	Name    string
231	ideInfo ccIdeInfo
232}
233
234type Deals []Deal
235
236// Ensure it satisfies sort.Interface
237func (d Deals) Len() int           { return len(d) }
238func (d Deals) Less(i, j int) bool { return d[i].Name < d[j].Name }
239func (d Deals) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
240
241func sortMap(moduleInfos map[string]ccIdeInfo) map[string]ccIdeInfo {
242	var deals Deals
243	for k, v := range moduleInfos {
244		deals = append(deals, Deal{k, v})
245	}
246
247	sort.Sort(deals)
248
249	m := map[string]ccIdeInfo{}
250	for _, d := range deals {
251		m[d.Name] = d.ideInfo
252	}
253	return m
254}
255
256func createJsonFile(moduleDeps ccDeps, ccfpath android.WritablePath) error {
257	buf, err := json.MarshalIndent(moduleDeps, "", "\t")
258	if err != nil {
259		return fmt.Errorf("JSON marshal of cc deps failed: %s", err)
260	}
261	err = android.WriteFileToOutputDir(ccfpath, buf, 0666)
262	if err != nil {
263		return fmt.Errorf("Writing cc deps to %s failed: %s", ccfpath.String(), err)
264	}
265	return nil
266}
267