diff --git a/Android.bp b/Android.bp index 75761027d..64fca8036 100644 --- a/Android.bp +++ b/Android.bp @@ -155,6 +155,7 @@ bootstrap_go_package { "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 f16bb12bc..0c3222536 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -2463,14 +2463,6 @@ func (c *Module) installable() bool { 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 000000000..9b89110b9 --- /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 +}