Home | History | Annotate | Download | only in cc
      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 
     15 package cc
     16 
     17 import (
     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 
     34 func init() {
     35 	android.RegisterSingletonType("compdb_generator", compDBGeneratorSingleton)
     36 }
     37 
     38 func compDBGeneratorSingleton() android.Singleton {
     39 	return &compdbGeneratorSingleton{}
     40 }
     41 
     42 type compdbGeneratorSingleton struct{}
     43 
     44 const (
     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.
     55 type 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 
     62 func (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 
    115 func 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 
    129 func 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", ".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.GlobalFlags)...)
    156 	args = append(args, expandAllVars(ctx, ccModule.flags.CFlags)...)
    157 	if isCpp {
    158 		args = append(args, expandAllVars(ctx, ccModule.flags.CppFlags)...)
    159 	} else if !isAsm {
    160 		args = append(args, expandAllVars(ctx, ccModule.flags.ConlyFlags)...)
    161 	}
    162 	args = append(args, expandAllVars(ctx, ccModule.flags.SystemIncludeFlags)...)
    163 	args = append(args, src.String())
    164 	return args
    165 }
    166 
    167 func generateCompdbProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module, builds map[string]compDbEntry) {
    168 	srcs := compiledModule.Srcs()
    169 	if len(srcs) == 0 {
    170 		return
    171 	}
    172 
    173 	rootDir := getCompdbAndroidSrcRootDirectory(ctx)
    174 	pathToCC, err := ctx.Eval(pctx, rootDir+"/${config.ClangBin}/")
    175 	ccPath := "/bin/false"
    176 	cxxPath := "/bin/false"
    177 	if err == nil {
    178 		ccPath = pathToCC + "clang"
    179 		cxxPath = pathToCC + "clang++"
    180 	}
    181 	for _, src := range srcs {
    182 		if _, ok := builds[src.String()]; !ok {
    183 			builds[src.String()] = compDbEntry{
    184 				Directory: rootDir,
    185 				Arguments: getArguments(src, ctx, ccModule, ccPath, cxxPath),
    186 				File:      src.String(),
    187 			}
    188 		}
    189 	}
    190 }
    191 
    192 func evalAndSplitVariable(ctx android.SingletonContext, str string) ([]string, error) {
    193 	evaluated, err := ctx.Eval(pctx, str)
    194 	if err == nil {
    195 		return strings.Fields(evaluated), nil
    196 	}
    197 	return []string{""}, err
    198 }
    199 
    200 func getCompdbAndroidSrcRootDirectory(ctx android.SingletonContext) string {
    201 	srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
    202 	return srcPath
    203 }
    204