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