blob: a9c5b5e9fd2b3e9ab9216249e421a133b437c506 [file] [log] [blame]
Alex Lightec868fc2018-04-17 16:50:48 -07001// 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/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 = "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.
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 := 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
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) []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 switch src.Ext() {
135 case ".S", ".s", ".asm":
136 isAsm = true
137 isCpp = false
138 case ".c":
139 isAsm = false
140 isCpp = false
141 case ".cpp", ".cc", ".mm":
142 isAsm = false
143 isCpp = true
144 default:
145 log.Print("Unknown file extension " + src.Ext() + " on file " + src.String())
146 isAsm = true
147 isCpp = false
148 }
149 // The executable for the compilation doesn't matter but we need something there.
150 args = append(args, "/bin/false")
151 args = append(args, expandAllVars(ctx, ccModule.flags.GlobalFlags)...)
152 args = append(args, expandAllVars(ctx, ccModule.flags.CFlags)...)
153 if isCpp {
154 args = append(args, expandAllVars(ctx, ccModule.flags.CppFlags)...)
155 } else if !isAsm {
156 args = append(args, expandAllVars(ctx, ccModule.flags.ConlyFlags)...)
157 }
158 args = append(args, expandAllVars(ctx, ccModule.flags.SystemIncludeFlags)...)
159 args = append(args, src.String())
160 return args
161}
162
163func generateCompdbProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module, builds map[string]compDbEntry) {
164 srcs := compiledModule.Srcs()
165 if len(srcs) == 0 {
166 return
167 }
168
169 rootDir := getCompdbAndroidSrcRootDirectory(ctx)
170 for _, src := range srcs {
171 if _, ok := builds[src.String()]; !ok {
172 builds[src.String()] = compDbEntry{
173 Directory: rootDir,
174 Arguments: getArguments(src, ctx, ccModule),
175 File: src.String(),
176 }
177 }
178 }
179}
180
181func evalAndSplitVariable(ctx android.SingletonContext, str string) ([]string, error) {
182 evaluated, err := ctx.Eval(pctx, str)
183 if err == nil {
184 return strings.Split(evaluated, " "), nil
185 }
186 return []string{""}, err
187}
188
189func getCompdbAndroidSrcRootDirectory(ctx android.SingletonContext) string {
190 srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
191 return srcPath
192}