1 // Copyright 2018 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 15 package cc 16 17 import ( 18 "encoding/json" 19 "log" 20 "os" 21 "path/filepath" 22 "strings" 23 24 "android/soong/android" 25 ) 26 27 // This singleton generates a compile_commands.json file. It does so for each 28 // blueprint Android.bp resulting in a cc.Module when either make, mm, mma, mmm 29 // or mmma is called. It will only create a single compile_commands.json file 30 // at out/development/ide/compdb/compile_commands.json. It will also symlink it 31 // to ${SOONG_LINK_COMPDB_TO} if set. In general this should be created by running 32 // make SOONG_GEN_COMPDB=1 nothing to get all targets. 33 34 func init() { 35 android.RegisterSingletonType("compdb_generator", compDBGeneratorSingleton) 36 } 37 38 func compDBGeneratorSingleton() android.Singleton { 39 return &compdbGeneratorSingleton{} 40 } 41 42 type compdbGeneratorSingleton struct{} 43 44 const ( 45 compdbFilename = "compile_commands.json" 46 compdbOutputProjectsDirectory = "out/development/ide/compdb" 47 48 // Environment variables used to modify behavior of this singleton. 49 envVariableGenerateCompdb = "SOONG_GEN_COMPDB" 50 envVariableGenerateCompdbDebugInfo = "SOONG_GEN_COMPDB_DEBUG" 51 envVariableCompdbLink = "SOONG_LINK_COMPDB_TO" 52 ) 53 54 // A compdb entry. The compile_commands.json file is a list of these. 55 type compDbEntry struct { 56 Directory string `json:"directory"` 57 Arguments []string `json:"arguments"` 58 File string `json:"file"` 59 Output string `json:"output,omitempty"` 60 } 61 62 func (c *compdbGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) { 63 if !ctx.Config().IsEnvTrue(envVariableGenerateCompdb) { 64 return 65 } 66 67 // Instruct the generator to indent the json file for easier debugging. 68 outputCompdbDebugInfo := ctx.Config().IsEnvTrue(envVariableGenerateCompdbDebugInfo) 69 70 // We only want one entry per file. We don't care what module/isa it's from 71 m := make(map[string]compDbEntry) 72 ctx.VisitAllModules(func(module android.Module) { 73 if ccModule, ok := module.(*Module); ok { 74 if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok { 75 generateCompdbProject(compiledModule, ctx, ccModule, m) 76 } 77 } 78 }) 79 80 // Create the output file. 81 dir := filepath.Join(getCompdbAndroidSrcRootDirectory(ctx), compdbOutputProjectsDirectory) 82 os.MkdirAll(dir, 0777) 83 compDBFile := filepath.Join(dir, compdbFilename) 84 f, err := os.Create(compdbFilename) 85 if err != nil { 86 log.Fatalf("Could not create file %s: %s", filepath.Join(dir, compdbFilename), err) 87 } 88 defer f.Close() 89 90 v := make([]compDbEntry, 0, len(m)) 91 92 for _, value := range m { 93 v = append(v, value) 94 } 95 var dat []byte 96 if outputCompdbDebugInfo { 97 dat, err = json.MarshalIndent(v, "", " ") 98 } else { 99 dat, err = json.Marshal(v) 100 } 101 if err != nil { 102 log.Fatalf("Failed to marshal: %s", err) 103 } 104 f.Write(dat) 105 106 finalLinkPath := filepath.Join(ctx.Config().Getenv(envVariableCompdbLink), compdbFilename) 107 if finalLinkPath != "" { 108 os.Remove(finalLinkPath) 109 if err := os.Symlink(compDBFile, finalLinkPath); err != nil { 110 log.Fatalf("Unable to symlink %s to %s: %s", compDBFile, finalLinkPath, err) 111 } 112 } 113 } 114 115 func expandAllVars(ctx android.SingletonContext, args []string) []string { 116 var out []string 117 for _, arg := range args { 118 if arg != "" { 119 if val, err := evalAndSplitVariable(ctx, arg); err == nil { 120 out = append(out, val...) 121 } else { 122 out = append(out, arg) 123 } 124 } 125 } 126 return out 127 } 128 129 func getArguments(src android.Path, ctx android.SingletonContext, ccModule *Module, ccPath string, cxxPath string) []string { 130 var args []string 131 isCpp := false 132 isAsm := false 133 // TODO It would be better to ask soong for the types here. 134 var clangPath string 135 switch src.Ext() { 136 case ".S", ".s", ".asm": 137 isAsm = true 138 isCpp = false 139 clangPath = ccPath 140 case ".c": 141 isAsm = false 142 isCpp = false 143 clangPath = ccPath 144 case ".cpp", ".cc", ".mm": 145 isAsm = false 146 isCpp = true 147 clangPath = cxxPath 148 default: 149 log.Print("Unknown file extension " + src.Ext() + " on file " + src.String()) 150 isAsm = true 151 isCpp = false 152 clangPath = ccPath 153 } 154 args = append(args, clangPath) 155 args = append(args, expandAllVars(ctx, ccModule.flags.GlobalFlags)...) 156 args = append(args, expandAllVars(ctx, ccModule.flags.CFlags)...) 157 if isCpp { 158 args = append(args, expandAllVars(ctx, ccModule.flags.CppFlags)...) 159 } else if !isAsm { 160 args = append(args, expandAllVars(ctx, ccModule.flags.ConlyFlags)...) 161 } 162 args = append(args, expandAllVars(ctx, ccModule.flags.SystemIncludeFlags)...) 163 args = append(args, src.String()) 164 return args 165 } 166 167 func generateCompdbProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module, builds map[string]compDbEntry) { 168 srcs := compiledModule.Srcs() 169 if len(srcs) == 0 { 170 return 171 } 172 173 rootDir := getCompdbAndroidSrcRootDirectory(ctx) 174 pathToCC, err := ctx.Eval(pctx, rootDir+"/${config.ClangBin}/") 175 ccPath := "/bin/false" 176 cxxPath := "/bin/false" 177 if err == nil { 178 ccPath = pathToCC + "clang" 179 cxxPath = pathToCC + "clang++" 180 } 181 for _, src := range srcs { 182 if _, ok := builds[src.String()]; !ok { 183 builds[src.String()] = compDbEntry{ 184 Directory: rootDir, 185 Arguments: getArguments(src, ctx, ccModule, ccPath, cxxPath), 186 File: src.String(), 187 } 188 } 189 } 190 } 191 192 func evalAndSplitVariable(ctx android.SingletonContext, str string) ([]string, error) { 193 evaluated, err := ctx.Eval(pctx, str) 194 if err == nil { 195 return strings.Fields(evaluated), nil 196 } 197 return []string{""}, err 198 } 199 200 func getCompdbAndroidSrcRootDirectory(ctx android.SingletonContext) string { 201 srcPath, _ := filepath.Abs(android.PathForSource(ctx).String()) 202 return srcPath 203 } 204