blob: ea124438e3075490b7d72b2c87c2096c48607c6f [file] [log] [blame]
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cc
import (
"encoding/json"
"log"
"os"
"path/filepath"
"strings"
"android/soong/android"
)
// This singleton generates a compile_commands.json file. It does so for each
// blueprint Android.bp resulting in a cc.Module when either make, mm, mma, mmm
// or mmma is called. It will only create a single compile_commands.json file
// at ${OUT_DIR}/soong/development/ide/compdb/compile_commands.json. It will also symlink it
// to ${SOONG_LINK_COMPDB_TO} if set. In general this should be created by running
// make SOONG_GEN_COMPDB=1 nothing to get all targets.
func init() {
android.RegisterSingletonType("compdb_generator", compDBGeneratorSingleton)
}
func compDBGeneratorSingleton() android.Singleton {
return &compdbGeneratorSingleton{}
}
type compdbGeneratorSingleton struct{}
const (
compdbFilename = "compile_commands.json"
compdbOutputProjectsDirectory = "development/ide/compdb"
// Environment variables used to modify behavior of this singleton.
envVariableGenerateCompdb = "SOONG_GEN_COMPDB"
envVariableGenerateCompdbDebugInfo = "SOONG_GEN_COMPDB_DEBUG"
envVariableCompdbLink = "SOONG_LINK_COMPDB_TO"
)
// A compdb entry. The compile_commands.json file is a list of these.
type compDbEntry struct {
Directory string `json:"directory"`
Arguments []string `json:"arguments"`
File string `json:"file"`
Output string `json:"output,omitempty"`
}
func (c *compdbGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
if !ctx.Config().IsEnvTrue(envVariableGenerateCompdb) {
return
}
// Instruct the generator to indent the json file for easier debugging.
outputCompdbDebugInfo := ctx.Config().IsEnvTrue(envVariableGenerateCompdbDebugInfo)
// We only want one entry per file. We don't care what module/isa it's from
m := make(map[string]compDbEntry)
ctx.VisitAllModules(func(module android.Module) {
if ccModule, ok := module.(*Module); ok {
if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
generateCompdbProject(compiledModule, ctx, ccModule, m)
}
}
})
// Create the output file.
dir := android.PathForOutput(ctx, compdbOutputProjectsDirectory)
os.MkdirAll(filepath.Join(android.AbsSrcDirForExistingUseCases(), dir.String()), 0777)
compDBFile := dir.Join(ctx, compdbFilename)
f, err := os.Create(filepath.Join(android.AbsSrcDirForExistingUseCases(), compDBFile.String()))
if err != nil {
log.Fatalf("Could not create file %s: %s", compDBFile, err)
}
defer f.Close()
v := make([]compDbEntry, 0, len(m))
for _, value := range m {
v = append(v, value)
}
var dat []byte
if outputCompdbDebugInfo {
dat, err = json.MarshalIndent(v, "", " ")
} else {
dat, err = json.Marshal(v)
}
if err != nil {
log.Fatalf("Failed to marshal: %s", err)
}
f.Write(dat)
if finalLinkDir := ctx.Config().Getenv(envVariableCompdbLink); finalLinkDir != "" {
finalLinkPath := filepath.Join(finalLinkDir, compdbFilename)
os.Remove(finalLinkPath)
if err := os.Symlink(compDBFile.String(), finalLinkPath); err != nil {
log.Fatalf("Unable to symlink %s to %s: %s", compDBFile, finalLinkPath, err)
}
}
}
func expandAllVars(ctx android.SingletonContext, args []string) []string {
var out []string
for _, arg := range args {
if arg != "" {
if val, err := evalAndSplitVariable(ctx, arg); err == nil {
out = append(out, val...)
} else {
out = append(out, arg)
}
}
}
return out
}
func getArguments(src android.Path, ctx android.SingletonContext, ccModule *Module, ccPath string, cxxPath string) []string {
var args []string
isCpp := false
isAsm := false
// TODO It would be better to ask soong for the types here.
var clangPath string
switch src.Ext() {
case ".S", ".s", ".asm":
isAsm = true
isCpp = false
clangPath = ccPath
case ".c":
isAsm = false
isCpp = false
clangPath = ccPath
case ".cpp", ".cc", ".cxx", ".mm":
isAsm = false
isCpp = true
clangPath = cxxPath
default:
log.Print("Unknown file extension " + src.Ext() + " on file " + src.String())
isAsm = true
isCpp = false
clangPath = ccPath
}
args = append(args, clangPath)
args = append(args, expandAllVars(ctx, ccModule.flags.Global.CommonFlags)...)
args = append(args, expandAllVars(ctx, ccModule.flags.Local.CommonFlags)...)
args = append(args, expandAllVars(ctx, ccModule.flags.Global.CFlags)...)
args = append(args, expandAllVars(ctx, ccModule.flags.Local.CFlags)...)
if isCpp {
args = append(args, expandAllVars(ctx, ccModule.flags.Global.CppFlags)...)
args = append(args, expandAllVars(ctx, ccModule.flags.Local.CppFlags)...)
} else if !isAsm {
args = append(args, expandAllVars(ctx, ccModule.flags.Global.ConlyFlags)...)
args = append(args, expandAllVars(ctx, ccModule.flags.Local.ConlyFlags)...)
}
args = append(args, expandAllVars(ctx, ccModule.flags.SystemIncludeFlags)...)
args = append(args, src.String())
return args
}
func generateCompdbProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module, builds map[string]compDbEntry) {
srcs := compiledModule.Srcs()
if len(srcs) == 0 {
return
}
pathToCC, err := ctx.Eval(pctx, "${config.ClangBin}")
ccPath := "/bin/false"
cxxPath := "/bin/false"
if err == nil {
ccPath = filepath.Join(pathToCC, "clang")
cxxPath = filepath.Join(pathToCC, "clang++")
}
for _, src := range srcs {
if _, ok := builds[src.String()]; !ok {
builds[src.String()] = compDbEntry{
Directory: android.AbsSrcDirForExistingUseCases(),
Arguments: getArguments(src, ctx, ccModule, ccPath, cxxPath),
File: src.String(),
}
}
}
}
func evalAndSplitVariable(ctx android.SingletonContext, str string) ([]string, error) {
evaluated, err := ctx.Eval(pctx, str)
if err == nil {
return strings.Fields(evaluated), nil
}
return []string{""}, err
}