Home | History | Annotate | Download | only in cc
      1 package cc
      2 
      3 import (
      4 	"fmt"
      5 
      6 	"android/soong/android"
      7 	"android/soong/cc/config"
      8 	"os"
      9 	"path"
     10 	"path/filepath"
     11 	"strings"
     12 
     13 	"github.com/google/blueprint"
     14 )
     15 
     16 // This singleton generates CMakeLists.txt files. It does so for each blueprint Android.bp resulting in a cc.Module
     17 // when either make, mm, mma, mmm or mmma is called. CMakeLists.txt files are generated in a separate folder
     18 // structure (see variable CLionOutputProjectsDirectory for root).
     19 
     20 func init() {
     21 	android.RegisterSingletonType("cmakelists_generator", cMakeListsGeneratorSingleton)
     22 }
     23 
     24 func cMakeListsGeneratorSingleton() blueprint.Singleton {
     25 	return &cmakelistsGeneratorSingleton{}
     26 }
     27 
     28 type cmakelistsGeneratorSingleton struct{}
     29 
     30 const (
     31 	cMakeListsFilename              = "CMakeLists.txt"
     32 	cLionAggregateProjectsDirectory = "development" + string(os.PathSeparator) + "ide" + string(os.PathSeparator) + "clion"
     33 	cLionOutputProjectsDirectory    = "out" + string(os.PathSeparator) + cLionAggregateProjectsDirectory
     34 	minimumCMakeVersionSupported    = "3.5"
     35 
     36 	// Environment variables used to modify behavior of this singleton.
     37 	envVariableGenerateCMakeLists = "SOONG_GEN_CMAKEFILES"
     38 	envVariableGenerateDebugInfo  = "SOONG_GEN_CMAKEFILES_DEBUG"
     39 	envVariableTrue               = "1"
     40 )
     41 
     42 // Instruct generator to trace how header include path and flags were generated.
     43 // This is done to ease investigating bug reports.
     44 var outputDebugInfo = false
     45 
     46 func (c *cmakelistsGeneratorSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
     47 	if getEnvVariable(envVariableGenerateCMakeLists, ctx) != envVariableTrue {
     48 		return
     49 	}
     50 
     51 	outputDebugInfo = (getEnvVariable(envVariableGenerateDebugInfo, ctx) == envVariableTrue)
     52 
     53 	ctx.VisitAllModules(func(module blueprint.Module) {
     54 		if ccModule, ok := module.(*Module); ok {
     55 			if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
     56 				generateCLionProject(compiledModule, ctx, ccModule)
     57 			}
     58 		}
     59 	})
     60 
     61 	// Link all handmade CMakeLists.txt aggregate from
     62 	//     BASE/development/ide/clion to
     63 	// BASE/out/development/ide/clion.
     64 	dir := filepath.Join(getAndroidSrcRootDirectory(ctx), cLionAggregateProjectsDirectory)
     65 	filepath.Walk(dir, linkAggregateCMakeListsFiles)
     66 
     67 	return
     68 }
     69 
     70 func getEnvVariable(name string, ctx blueprint.SingletonContext) string {
     71 	// Using android.Config.Getenv instead of os.getEnv to guarantee soong will
     72 	// re-run in case this environment variable changes.
     73 	return ctx.Config().(android.Config).Getenv(name)
     74 }
     75 
     76 func exists(path string) bool {
     77 	_, err := os.Stat(path)
     78 	if err == nil {
     79 		return true
     80 	}
     81 	if os.IsNotExist(err) {
     82 		return false
     83 	}
     84 	return true
     85 }
     86 
     87 func linkAggregateCMakeListsFiles(path string, info os.FileInfo, err error) error {
     88 
     89 	if info == nil {
     90 		return nil
     91 	}
     92 
     93 	dst := strings.Replace(path, cLionAggregateProjectsDirectory, cLionOutputProjectsDirectory, 1)
     94 	if info.IsDir() {
     95 		// This is a directory to create
     96 		os.MkdirAll(dst, os.ModePerm)
     97 	} else {
     98 		// This is a file to link
     99 		os.Remove(dst)
    100 		os.Symlink(path, dst)
    101 	}
    102 	return nil
    103 }
    104 
    105 func generateCLionProject(compiledModule CompiledInterface, ctx blueprint.SingletonContext, ccModule *Module) {
    106 	srcs := compiledModule.Srcs()
    107 	if len(srcs) == 0 {
    108 		return
    109 	}
    110 
    111 	// Ensure the directory hosting the cmakelists.txt exists
    112 	clionproject_location := getCMakeListsForModule(ccModule, ctx)
    113 	projectDir := path.Dir(clionproject_location)
    114 	os.MkdirAll(projectDir, os.ModePerm)
    115 
    116 	// Create cmakelists.txt
    117 	f, _ := os.Create(filepath.Join(projectDir, cMakeListsFilename))
    118 	defer f.Close()
    119 
    120 	// Header.
    121 	f.WriteString("# THIS FILE WAS AUTOMATICALY GENERATED!\n")
    122 	f.WriteString("# ANY MODIFICATION WILL BE OVERWRITTEN!\n\n")
    123 	f.WriteString("# To improve project view in Clion    :\n")
    124 	f.WriteString("# Tools > CMake > Change Project Root  \n\n")
    125 	f.WriteString(fmt.Sprintf("cmake_minimum_required(VERSION %s)\n", minimumCMakeVersionSupported))
    126 	f.WriteString(fmt.Sprintf("project(%s)\n", ccModule.ModuleBase.Name()))
    127 	f.WriteString(fmt.Sprintf("set(ANDROID_ROOT %s)\n\n", getAndroidSrcRootDirectory(ctx)))
    128 
    129 	if ccModule.flags.Clang {
    130 		pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
    131 		f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang"))
    132 		f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang++"))
    133 	} else {
    134 		toolchain := config.FindToolchain(ccModule.Os(), ccModule.Arch())
    135 		root, _ := evalVariable(ctx, toolchain.GccRoot())
    136 		triple, _ := evalVariable(ctx, toolchain.GccTriple())
    137 		pathToCC := filepath.Join(root, "bin", triple+"-")
    138 		f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "gcc"))
    139 		f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "g++"))
    140 	}
    141 	// Add all sources to the project.
    142 	f.WriteString("list(APPEND\n")
    143 	f.WriteString("     SOURCE_FILES\n")
    144 	for _, src := range srcs {
    145 		f.WriteString(fmt.Sprintf("    ${ANDROID_ROOT}/%s\n", src.String()))
    146 	}
    147 	f.WriteString(")\n")
    148 
    149 	// Add all header search path and compiler parameters (-D, -W, -f, -XXXX)
    150 	f.WriteString("\n# GLOBAL FLAGS:\n")
    151 	globalParameters := parseCompilerParameters(ccModule.flags.GlobalFlags, ctx, f)
    152 	translateToCMake(globalParameters, f, true, true)
    153 
    154 	f.WriteString("\n# CFLAGS:\n")
    155 	cParameters := parseCompilerParameters(ccModule.flags.CFlags, ctx, f)
    156 	translateToCMake(cParameters, f, true, true)
    157 
    158 	f.WriteString("\n# C ONLY FLAGS:\n")
    159 	cOnlyParameters := parseCompilerParameters(ccModule.flags.ConlyFlags, ctx, f)
    160 	translateToCMake(cOnlyParameters, f, true, false)
    161 
    162 	f.WriteString("\n# CPP FLAGS:\n")
    163 	cppParameters := parseCompilerParameters(ccModule.flags.CppFlags, ctx, f)
    164 	translateToCMake(cppParameters, f, false, true)
    165 
    166 	f.WriteString("\n# SYSTEM INCLUDE FLAGS:\n")
    167 	includeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f)
    168 	translateToCMake(includeParameters, f, true, true)
    169 
    170 	// Add project executable.
    171 	f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n",
    172 		cleanExecutableName(ccModule.ModuleBase.Name())))
    173 }
    174 
    175 func cleanExecutableName(s string) string {
    176 	return strings.Replace(s, "@", "-", -1)
    177 }
    178 
    179 func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) {
    180 	writeAllIncludeDirectories(c.systemHeaderSearchPath, f, true)
    181 	writeAllIncludeDirectories(c.headerSearchPath, f, false)
    182 	if cflags {
    183 		writeAllFlags(c.flags, f, "CMAKE_C_FLAGS")
    184 	}
    185 
    186 	if cppflags {
    187 		writeAllFlags(c.flags, f, "CMAKE_CXX_FLAGS")
    188 	}
    189 	if c.sysroot != "" {
    190 		f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(path.Join(c.sysroot, "usr", "include"))))
    191 	}
    192 
    193 }
    194 
    195 func buildCMakePath(p string) string {
    196 	if path.IsAbs(p) {
    197 		return p
    198 	}
    199 	return fmt.Sprintf("${ANDROID_ROOT}/%s", p)
    200 }
    201 
    202 func writeAllIncludeDirectories(includes []string, f *os.File, isSystem bool) {
    203 	if len(includes) == 0 {
    204 		return
    205 	}
    206 
    207 	system := ""
    208 	if isSystem {
    209 		system = "SYSTEM"
    210 	}
    211 
    212 	f.WriteString(fmt.Sprintf("include_directories(%s \n", system))
    213 
    214 	for _, include := range includes {
    215 		f.WriteString(fmt.Sprintf("    \"%s\"\n", buildCMakePath(include)))
    216 	}
    217 	f.WriteString(")\n\n")
    218 
    219 	// Also add all headers to source files.
    220 	f.WriteString("file (GLOB_RECURSE TMP_HEADERS\n")
    221 	for _, include := range includes {
    222 		f.WriteString(fmt.Sprintf("    \"%s/**/*.h\"\n", buildCMakePath(include)))
    223 	}
    224 	f.WriteString(")\n")
    225 	f.WriteString("list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n")
    226 }
    227 
    228 func writeAllFlags(flags []string, f *os.File, tag string) {
    229 	for _, flag := range flags {
    230 		f.WriteString(fmt.Sprintf("set(%s \"${%s} %s\")\n", tag, tag, flag))
    231 	}
    232 }
    233 
    234 type parameterType int
    235 
    236 const (
    237 	headerSearchPath parameterType = iota
    238 	variable
    239 	systemHeaderSearchPath
    240 	flag
    241 	systemRoot
    242 )
    243 
    244 type compilerParameters struct {
    245 	headerSearchPath       []string
    246 	systemHeaderSearchPath []string
    247 	flags                  []string
    248 	sysroot                string
    249 }
    250 
    251 func makeCompilerParameters() compilerParameters {
    252 	return compilerParameters{
    253 		sysroot: "",
    254 	}
    255 }
    256 
    257 func categorizeParameter(parameter string) parameterType {
    258 	if strings.HasPrefix(parameter, "-I") {
    259 		return headerSearchPath
    260 	}
    261 	if strings.HasPrefix(parameter, "$") {
    262 		return variable
    263 	}
    264 	if strings.HasPrefix(parameter, "-isystem") {
    265 		return systemHeaderSearchPath
    266 	}
    267 	if strings.HasPrefix(parameter, "-isysroot") {
    268 		return systemRoot
    269 	}
    270 	if strings.HasPrefix(parameter, "--sysroot") {
    271 		return systemRoot
    272 	}
    273 	return flag
    274 }
    275 
    276 func parseCompilerParameters(params []string, ctx blueprint.SingletonContext, f *os.File) compilerParameters {
    277 	var compilerParameters = makeCompilerParameters()
    278 
    279 	for i, str := range params {
    280 		f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str))
    281 	}
    282 
    283 	for i := 0; i < len(params); i++ {
    284 		param := params[i]
    285 		if param == "" {
    286 			continue
    287 		}
    288 
    289 		switch categorizeParameter(param) {
    290 		case headerSearchPath:
    291 			compilerParameters.headerSearchPath =
    292 				append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I"))
    293 		case variable:
    294 			if evaluated, error := evalVariable(ctx, param); error == nil {
    295 				if outputDebugInfo {
    296 					f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated))
    297 				}
    298 
    299 				paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f)
    300 				concatenateParams(&compilerParameters, paramsFromVar)
    301 
    302 			} else {
    303 				if outputDebugInfo {
    304 					f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param))
    305 				}
    306 			}
    307 		case systemHeaderSearchPath:
    308 			if i < len(params)-1 {
    309 				compilerParameters.systemHeaderSearchPath =
    310 					append(compilerParameters.systemHeaderSearchPath, params[i+1])
    311 			} else if outputDebugInfo {
    312 				f.WriteString("# Found a header search path marker with no path")
    313 			}
    314 			i = i + 1
    315 		case flag:
    316 			c := cleanupParameter(param)
    317 			f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c))
    318 			compilerParameters.flags = append(compilerParameters.flags, c)
    319 		case systemRoot:
    320 			if i < len(params)-1 {
    321 				compilerParameters.sysroot = params[i+1]
    322 			} else if outputDebugInfo {
    323 				f.WriteString("# Found a system root path marker with no path")
    324 			}
    325 			i = i + 1
    326 		}
    327 	}
    328 	return compilerParameters
    329 }
    330 
    331 func cleanupParameter(p string) string {
    332 	// In the blueprint, c flags can be passed as:
    333 	//  cflags: [ "-DLOG_TAG=\"libEGL\"", ]
    334 	// which becomes:
    335 	// '-DLOG_TAG="libEGL"' in soong.
    336 	// In order to be injected in CMakelists.txt we need to:
    337 	// - Remove the wrapping ' character
    338 	// - Double escape all special \ and " characters.
    339 	// For a end result like:
    340 	// -DLOG_TAG=\\\"libEGL\\\"
    341 	if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 {
    342 		return p
    343 	}
    344 
    345 	// Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape
    346 	// TODO:  It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex,
    347 	// we should create a method NinjaAndShellUnescape in escape.go and use that instead.
    348 	p = p[1 : len(p)-1]
    349 	p = strings.Replace(p, `'\''`, `'`, -1)
    350 	p = strings.Replace(p, `$$`, `$`, -1)
    351 
    352 	p = doubleEscape(p)
    353 	return p
    354 }
    355 
    356 func escape(s string) string {
    357 	s = strings.Replace(s, `\`, `\\`, -1)
    358 	s = strings.Replace(s, `"`, `\"`, -1)
    359 	return s
    360 }
    361 
    362 func doubleEscape(s string) string {
    363 	s = escape(s)
    364 	s = escape(s)
    365 	return s
    366 }
    367 
    368 func concatenateParams(c1 *compilerParameters, c2 compilerParameters) {
    369 	c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...)
    370 	c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...)
    371 	if c2.sysroot != "" {
    372 		c1.sysroot = c2.sysroot
    373 	}
    374 	c1.flags = append(c1.flags, c2.flags...)
    375 }
    376 
    377 func evalVariable(ctx blueprint.SingletonContext, str string) (string, error) {
    378 	evaluated, err := ctx.Eval(pctx, str)
    379 	if err == nil {
    380 		return evaluated, nil
    381 	}
    382 	return "", err
    383 }
    384 
    385 func getCMakeListsForModule(module *Module, ctx blueprint.SingletonContext) string {
    386 	return filepath.Join(getAndroidSrcRootDirectory(ctx),
    387 		cLionOutputProjectsDirectory,
    388 		path.Dir(ctx.BlueprintFile(module)),
    389 		module.ModuleBase.Name()+"-"+
    390 			module.ModuleBase.Arch().ArchType.Name+"-"+
    391 			module.ModuleBase.Os().Name,
    392 		cMakeListsFilename)
    393 }
    394 
    395 func getAndroidSrcRootDirectory(ctx blueprint.SingletonContext) string {
    396 	srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
    397 	return srcPath
    398 }
    399