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 15package cc 16 17import ( 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_DIR}/soong/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 34func init() { 35 android.RegisterParallelSingletonType("compdb_generator", compDBGeneratorSingleton) 36} 37 38func compDBGeneratorSingleton() android.Singleton { 39 return &compdbGeneratorSingleton{} 40} 41 42type compdbGeneratorSingleton struct{} 43 44const ( 45 compdbFilename = "compile_commands.json" 46 compdbOutputProjectsDirectory = "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. 55type 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 62func (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 := android.PathForOutput(ctx, compdbOutputProjectsDirectory) 82 os.MkdirAll(filepath.Join(android.AbsSrcDirForExistingUseCases(), dir.String()), 0777) 83 compDBFile := dir.Join(ctx, compdbFilename) 84 f, err := os.Create(filepath.Join(android.AbsSrcDirForExistingUseCases(), compDBFile.String())) 85 if err != nil { 86 log.Fatalf("Could not create file %s: %s", compDBFile, err) 87 } 88 defer func() { 89 if err := f.Close(); err != nil { 90 log.Fatalf("Could not close file %s: %s", compDBFile, err) 91 } 92 }() 93 94 v := make([]compDbEntry, 0, len(m)) 95 for _, value := range m { 96 v = append(v, value) 97 } 98 99 w := json.NewEncoder(f) 100 if outputCompdbDebugInfo { 101 w.SetIndent("", " ") 102 } 103 if err := w.Encode(v); err != nil { 104 log.Fatalf("Failed to encode: %s", err) 105 } 106 107 if finalLinkDir := ctx.Config().Getenv(envVariableCompdbLink); finalLinkDir != "" { 108 finalLinkPath := filepath.Join(finalLinkDir, compdbFilename) 109 os.Remove(finalLinkPath) 110 if err := os.Symlink(compDBFile.String(), finalLinkPath); err != nil { 111 log.Fatalf("Unable to symlink %s to %s: %s", compDBFile, finalLinkPath, err) 112 } 113 } 114} 115 116func expandAllVars(ctx android.SingletonContext, args []string) []string { 117 var out []string 118 for _, arg := range args { 119 if arg != "" { 120 if val, err := evalAndSplitVariable(ctx, arg); err == nil { 121 out = append(out, val...) 122 } else { 123 out = append(out, arg) 124 } 125 } 126 } 127 return out 128} 129 130func getArguments(src android.Path, ctx android.SingletonContext, ccModule *Module, ccPath string, cxxPath string) []string { 131 var args []string 132 isCpp := false 133 isAsm := false 134 // TODO It would be better to ask soong for the types here. 135 var clangPath string 136 switch src.Ext() { 137 case ".S", ".s", ".asm": 138 isAsm = true 139 isCpp = false 140 clangPath = ccPath 141 case ".c": 142 isAsm = false 143 isCpp = false 144 clangPath = ccPath 145 case ".cpp", ".cc", ".cxx", ".mm": 146 isAsm = false 147 isCpp = true 148 clangPath = cxxPath 149 case ".o": 150 return nil 151 default: 152 log.Print("Unknown file extension " + src.Ext() + " on file " + src.String()) 153 isAsm = true 154 isCpp = false 155 clangPath = ccPath 156 } 157 args = append(args, clangPath) 158 args = append(args, expandAllVars(ctx, ccModule.flags.Global.CommonFlags)...) 159 args = append(args, expandAllVars(ctx, ccModule.flags.Local.CommonFlags)...) 160 args = append(args, expandAllVars(ctx, ccModule.flags.Global.CFlags)...) 161 args = append(args, expandAllVars(ctx, ccModule.flags.Local.CFlags)...) 162 if isCpp { 163 args = append(args, expandAllVars(ctx, ccModule.flags.Global.CppFlags)...) 164 args = append(args, expandAllVars(ctx, ccModule.flags.Local.CppFlags)...) 165 } else if !isAsm { 166 args = append(args, expandAllVars(ctx, ccModule.flags.Global.ConlyFlags)...) 167 args = append(args, expandAllVars(ctx, ccModule.flags.Local.ConlyFlags)...) 168 } 169 args = append(args, expandAllVars(ctx, ccModule.flags.SystemIncludeFlags)...) 170 args = append(args, expandAllVars(ctx, ccModule.flags.NoOverrideFlags)...) 171 args = append(args, src.String()) 172 return args 173} 174 175func generateCompdbProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module, builds map[string]compDbEntry) { 176 srcs := compiledModule.Srcs() 177 if len(srcs) == 0 { 178 return 179 } 180 181 pathToCC, err := ctx.Eval(pctx, "${config.ClangBin}") 182 ccPath := "/bin/false" 183 cxxPath := "/bin/false" 184 if err == nil { 185 ccPath = filepath.Join(pathToCC, "clang") 186 cxxPath = filepath.Join(pathToCC, "clang++") 187 } 188 for _, src := range srcs { 189 if _, ok := builds[src.String()]; !ok { 190 args := getArguments(src, ctx, ccModule, ccPath, cxxPath) 191 if args == nil { 192 continue 193 } 194 builds[src.String()] = compDbEntry{ 195 Directory: android.AbsSrcDirForExistingUseCases(), 196 Arguments: getArguments(src, ctx, ccModule, ccPath, cxxPath), 197 File: src.String(), 198 } 199 } 200 } 201} 202 203func evalAndSplitVariable(ctx android.SingletonContext, str string) ([]string, error) { 204 evaluated, err := ctx.Eval(pctx, str) 205 if err == nil { 206 return strings.Fields(evaluated), nil 207 } 208 return []string{""}, err 209} 210