xref: /aosp_15_r20/build/soong/cc/compdb.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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