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.RegisterSingletonType("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 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 if finalLinkDir := ctx.Config().Getenv(envVariableCompdbLink); finalLinkDir != "" { 107 finalLinkPath := filepath.Join(finalLinkDir, compdbFilename) 108 os.Remove(finalLinkPath) 109 if err := os.Symlink(compDBFile.String(), finalLinkPath); err != nil { 110 log.Fatalf("Unable to symlink %s to %s: %s", compDBFile, finalLinkPath, err) 111 } 112 } 113} 114 115func 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 129func 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", ".cxx", ".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.Global.CommonFlags)...) 156 args = append(args, expandAllVars(ctx, ccModule.flags.Local.CommonFlags)...) 157 args = append(args, expandAllVars(ctx, ccModule.flags.Global.CFlags)...) 158 args = append(args, expandAllVars(ctx, ccModule.flags.Local.CFlags)...) 159 if isCpp { 160 args = append(args, expandAllVars(ctx, ccModule.flags.Global.CppFlags)...) 161 args = append(args, expandAllVars(ctx, ccModule.flags.Local.CppFlags)...) 162 } else if !isAsm { 163 args = append(args, expandAllVars(ctx, ccModule.flags.Global.ConlyFlags)...) 164 args = append(args, expandAllVars(ctx, ccModule.flags.Local.ConlyFlags)...) 165 } 166 args = append(args, expandAllVars(ctx, ccModule.flags.SystemIncludeFlags)...) 167 args = append(args, src.String()) 168 return args 169} 170 171func generateCompdbProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module, builds map[string]compDbEntry) { 172 srcs := compiledModule.Srcs() 173 if len(srcs) == 0 { 174 return 175 } 176 177 pathToCC, err := ctx.Eval(pctx, "${config.ClangBin}") 178 ccPath := "/bin/false" 179 cxxPath := "/bin/false" 180 if err == nil { 181 ccPath = filepath.Join(pathToCC, "clang") 182 cxxPath = filepath.Join(pathToCC, "clang++") 183 } 184 for _, src := range srcs { 185 if _, ok := builds[src.String()]; !ok { 186 builds[src.String()] = compDbEntry{ 187 Directory: android.AbsSrcDirForExistingUseCases(), 188 Arguments: getArguments(src, ctx, ccModule, ccPath, cxxPath), 189 File: src.String(), 190 } 191 } 192 } 193} 194 195func evalAndSplitVariable(ctx android.SingletonContext, str string) ([]string, error) { 196 evaluated, err := ctx.Eval(pctx, str) 197 if err == nil { 198 return strings.Fields(evaluated), nil 199 } 200 return []string{""}, err 201} 202