AIDEGen: collect cc_srcs and cc related flags in module_bp_cc_deps.json

Define a field CCSrcs and other cc related flags in a type ccIdeInfo
struct and write them into out/soong/module_bp_cc_deps.json. AIDEGen
can use these data to generate CMakeLists.txt by Python for multiple
native projects in CLion IDE.

Bug: 141512319

Test: 1. export SOONG_COLLECT_JAVA_DEPS=false SOONG_COLLECT_CC_DEPS=true;m nothing
         check
	 1). File out/soong/module_bp_cc_deps.json is generated.
	 2). In "JniInvocation_test" module: "path", "srcs",
	     "global_common_flags", "local_common_flags",
	     "global_c_flags", "local_c_flags", "global_c_conly_flags",
	     "local_c_conly_flags", "global_cpp_flags",
	     "local_cpp_flags" and "system_include_flags"
	     have been created.

Change-Id: I9292cc6373157ba68f013998a7364f84a70d5593
diff --git a/Android.bp b/Android.bp
index 7576102..64fca80 100644
--- a/Android.bp
+++ b/Android.bp
@@ -155,6 +155,7 @@
         "cc/androidmk.go",
         "cc/builder.go",
         "cc/cc.go",
+        "cc/ccdeps.go",
         "cc/check.go",
         "cc/coverage.go",
         "cc/gen.go",
diff --git a/cc/cc.go b/cc/cc.go
index f16bb12..0c32225 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -2463,14 +2463,6 @@
 	return c.installer != nil && !c.Properties.PreventInstall && c.IsForPlatform() && c.outputFile.Valid()
 }
 
-func (c *Module) IDEInfo(dpInfo *android.IdeInfo) {
-	outputFiles, err := c.OutputFiles("")
-	if err != nil {
-		panic(err)
-	}
-	dpInfo.Srcs = append(dpInfo.Srcs, outputFiles.Strings()...)
-}
-
 func (c *Module) AndroidMkWriteAdditionalDependenciesForSourceAbiDiff(w io.Writer) {
 	if c.linker != nil {
 		if library, ok := c.linker.(*libraryDecorator); ok {
diff --git a/cc/ccdeps.go b/cc/ccdeps.go
new file mode 100644
index 0000000..9b89110
--- /dev/null
+++ b/cc/ccdeps.go
@@ -0,0 +1,252 @@
+// Copyright 2019 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 cc
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"path"
+	"sort"
+	"strings"
+
+	"android/soong/android"
+)
+
+// This singleton collects cc modules' source and flags into to a json file.
+// It does so for generating CMakeLists.txt project files needed data when
+// either make, mm, mma, mmm or mmma is called.
+// The info file is generated in $OUT/module_bp_cc_depend.json.
+
+func init() {
+	android.RegisterSingletonType("ccdeps_generator", ccDepsGeneratorSingleton)
+}
+
+func ccDepsGeneratorSingleton() android.Singleton {
+	return &ccdepsGeneratorSingleton{}
+}
+
+type ccdepsGeneratorSingleton struct {
+}
+
+const (
+	// Environment variables used to control the behavior of this singleton.
+	envVariableCollectCCDeps = "SOONG_COLLECT_CC_DEPS"
+	ccdepsJsonFileName       = "module_bp_cc_deps.json"
+	cClang                   = "clang"
+	cppClang                 = "clang++"
+)
+
+type ccIdeInfo struct {
+	Path                 []string     `json:"path,omitempty"`
+	Srcs                 []string     `json:"srcs,omitempty"`
+	Global_Common_Flags  ccParameters `json:"global_common_flags,omitempty"`
+	Local_Common_Flags   ccParameters `json:"local_common_flags,omitempty"`
+	Global_C_flags       ccParameters `json:"global_c_flags,omitempty"`
+	Local_C_flags        ccParameters `json:"local_c_flags,omitempty"`
+	Global_C_only_flags  ccParameters `json:"global_c_only_flags,omitempty"`
+	Local_C_only_flags   ccParameters `json:"local_c_only_flags,omitempty"`
+	Global_Cpp_flags     ccParameters `json:"global_cpp_flags,omitempty"`
+	Local_Cpp_flags      ccParameters `json:"local_cpp_flags,omitempty"`
+	System_include_flags ccParameters `json:"system_include_flags,omitempty"`
+	Module_name          string       `json:"module_name,omitempty"`
+}
+
+type ccParameters struct {
+	HeaderSearchPath       []string          `json:"header_search_path,omitempty"`
+	SystemHeaderSearchPath []string          `json:"system_search_path,omitempty"`
+	FlagParameters         []string          `json:"flag,omitempty"`
+	SysRoot                string            `json:"system_root,omitempty"`
+	RelativeFilePathFlags  map[string]string `json:"relative_file_path,omitempty"`
+}
+
+type ccMapIdeInfos map[string]ccIdeInfo
+
+type ccDeps struct {
+	C_clang   string        `json:"clang,omitempty"`
+	Cpp_clang string        `json:"clang++,omitempty"`
+	Modules   ccMapIdeInfos `json:"modules,omitempty"`
+}
+
+func (c *ccdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	if !ctx.Config().IsEnvTrue(envVariableCollectCCDeps) {
+		return
+	}
+
+	moduleDeps := ccDeps{}
+	moduleInfos := map[string]ccIdeInfo{}
+
+	// Track which projects have already had CMakeLists.txt generated to keep the first
+	// variant for each project.
+	seenProjects := map[string]bool{}
+
+	pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
+	moduleDeps.C_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cClang)
+	moduleDeps.Cpp_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cppClang)
+
+	ctx.VisitAllModules(func(module android.Module) {
+		if ccModule, ok := module.(*Module); ok {
+			if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
+				generateCLionProjectData(ctx, compiledModule, ccModule, seenProjects, moduleInfos)
+			}
+		}
+	})
+
+	moduleDeps.Modules = moduleInfos
+
+	ccfpath := android.PathForOutput(ctx, ccdepsJsonFileName).String()
+	err := createJsonFile(moduleDeps, ccfpath)
+	if err != nil {
+		ctx.Errorf(err.Error())
+	}
+}
+
+func parseCompilerCCParameters(ctx android.SingletonContext, params []string) ccParameters {
+	compilerParams := ccParameters{}
+
+	cparams := []string{}
+	for _, param := range params {
+		param, _ = evalVariable(ctx, param)
+		cparams = append(cparams, param)
+	}
+
+	// Soong does not guarantee that each flag will be in an individual string. e.g: The
+	// input received could be:
+	// params = {"-isystem", "path/to/system"}
+	// or it could be
+	// params = {"-isystem path/to/system"}
+	// To normalize the input, we split all strings with the "space" character and consolidate
+	// all tokens into a flattened parameters list
+	cparams = normalizeParameters(cparams)
+
+	for i := 0; i < len(cparams); i++ {
+		param := cparams[i]
+		if param == "" {
+			continue
+		}
+
+		switch categorizeParameter(param) {
+		case headerSearchPath:
+			compilerParams.HeaderSearchPath =
+				append(compilerParams.HeaderSearchPath, strings.TrimPrefix(param, "-I"))
+		case systemHeaderSearchPath:
+			if i < len(params)-1 {
+				compilerParams.SystemHeaderSearchPath = append(compilerParams.SystemHeaderSearchPath, cparams[i+1])
+			}
+			i = i + 1
+		case flag:
+			c := cleanupParameter(param)
+			compilerParams.FlagParameters = append(compilerParams.FlagParameters, c)
+		case systemRoot:
+			if i < len(cparams)-1 {
+				compilerParams.SysRoot = cparams[i+1]
+			}
+			i = i + 1
+		case relativeFilePathFlag:
+			flagComponents := strings.Split(param, "=")
+			if len(flagComponents) == 2 {
+				if compilerParams.RelativeFilePathFlags == nil {
+					compilerParams.RelativeFilePathFlags = map[string]string{}
+				}
+				compilerParams.RelativeFilePathFlags[flagComponents[0]] = flagComponents[1]
+			}
+		}
+	}
+	return compilerParams
+}
+
+func generateCLionProjectData(ctx android.SingletonContext, compiledModule CompiledInterface,
+	ccModule *Module, seenProjects map[string]bool, moduleInfos map[string]ccIdeInfo) {
+	srcs := compiledModule.Srcs()
+	if len(srcs) == 0 {
+		return
+	}
+
+	// Only keep the DeviceArch variant module.
+	if ctx.DeviceConfig().DeviceArch() != ccModule.ModuleBase.Arch().ArchType.Name {
+		return
+	}
+
+	clionProjectLocation := getCMakeListsForModule(ccModule, ctx)
+	if seenProjects[clionProjectLocation] {
+		return
+	}
+
+	seenProjects[clionProjectLocation] = true
+
+	name := ccModule.ModuleBase.Name()
+	dpInfo := moduleInfos[name]
+
+	dpInfo.Path = append(dpInfo.Path, path.Dir(ctx.BlueprintFile(ccModule)))
+	dpInfo.Srcs = append(dpInfo.Srcs, srcs.Strings()...)
+	dpInfo.Path = android.FirstUniqueStrings(dpInfo.Path)
+	dpInfo.Srcs = android.FirstUniqueStrings(dpInfo.Srcs)
+
+	dpInfo.Global_Common_Flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CommonFlags)
+	dpInfo.Local_Common_Flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CommonFlags)
+	dpInfo.Global_C_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CFlags)
+	dpInfo.Local_C_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CFlags)
+	dpInfo.Global_C_only_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.ConlyFlags)
+	dpInfo.Local_C_only_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.ConlyFlags)
+	dpInfo.Global_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Global.CppFlags)
+	dpInfo.Local_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CppFlags)
+	dpInfo.System_include_flags = parseCompilerCCParameters(ctx, ccModule.flags.SystemIncludeFlags)
+
+	dpInfo.Module_name = name
+
+	moduleInfos[name] = dpInfo
+}
+
+type Deal struct {
+	Name    string
+	ideInfo ccIdeInfo
+}
+
+type Deals []Deal
+
+// Ensure it satisfies sort.Interface
+func (d Deals) Len() int           { return len(d) }
+func (d Deals) Less(i, j int) bool { return d[i].Name < d[j].Name }
+func (d Deals) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
+
+func sortMap(moduleInfos map[string]ccIdeInfo) map[string]ccIdeInfo {
+	var deals Deals
+	for k, v := range moduleInfos {
+		deals = append(deals, Deal{k, v})
+	}
+
+	sort.Sort(deals)
+
+	m := map[string]ccIdeInfo{}
+	for _, d := range deals {
+		m[d.Name] = d.ideInfo
+	}
+	return m
+}
+
+func createJsonFile(moduleDeps ccDeps, ccfpath string) error {
+	file, err := os.Create(ccfpath)
+	if err != nil {
+		return fmt.Errorf("Failed to create file: %s, relative: %v", ccdepsJsonFileName, err)
+	}
+	defer file.Close()
+	moduleDeps.Modules = sortMap(moduleDeps.Modules)
+	buf, err := json.MarshalIndent(moduleDeps, "", "\t")
+	if err != nil {
+		return fmt.Errorf("Write file failed: %s, relative: %v", ccdepsJsonFileName, err)
+	}
+	fmt.Fprintf(file, string(buf))
+	return nil
+}