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