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