| // 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" |
| "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 { |
| outputPath android.Path |
| } |
| |
| var _ android.SingletonMakeVarsProvider = (*ccdepsGeneratorSingleton)(nil) |
| |
| 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) |
| err := createJsonFile(moduleDeps, ccfpath) |
| if err != nil { |
| ctx.Errorf(err.Error()) |
| } |
| c.outputPath = ccfpath |
| |
| // This is necessary to satisfy the dangling rules check as this file is written by Soong rather than a rule. |
| ctx.Build(pctx, android.BuildParams{ |
| Rule: android.Touch, |
| Output: ccfpath, |
| }) |
| } |
| |
| func (c *ccdepsGeneratorSingleton) MakeVars(ctx android.MakeVarsContext) { |
| if c.outputPath == nil { |
| return |
| } |
| |
| ctx.DistForGoal("general-tests", c.outputPath) |
| } |
| |
| 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(cparams)-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 android.WritablePath) error { |
| buf, err := json.MarshalIndent(moduleDeps, "", "\t") |
| if err != nil { |
| return fmt.Errorf("JSON marshal of cc deps failed: %s", err) |
| } |
| err = android.WriteFileToOutputDir(ccfpath, buf, 0666) |
| if err != nil { |
| return fmt.Errorf("Writing cc deps to %s failed: %s", ccfpath.String(), err) |
| } |
| return nil |
| } |