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(android.AbsSrcDirForExistingUseCases(), 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", android.AbsSrcDirForExistingUseCases())) 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 ALL FLAGS:\n") 166 globalAllParameters := parseCompilerParameters(ccModule.flags.Global.CommonFlags, ctx, f) 167 translateToCMake(globalAllParameters, f, true, true) 168 169 f.WriteString("\n# LOCAL ALL FLAGS:\n") 170 localAllParameters := parseCompilerParameters(ccModule.flags.Local.CommonFlags, ctx, f) 171 translateToCMake(localAllParameters, f, true, true) 172 173 f.WriteString("\n# GLOBAL CFLAGS:\n") 174 globalCParameters := parseCompilerParameters(ccModule.flags.Global.CFlags, ctx, f) 175 translateToCMake(globalCParameters, f, true, true) 176 177 f.WriteString("\n# LOCAL CFLAGS:\n") 178 localCParameters := parseCompilerParameters(ccModule.flags.Local.CFlags, ctx, f) 179 translateToCMake(localCParameters, f, true, true) 180 181 f.WriteString("\n# GLOBAL C ONLY FLAGS:\n") 182 globalConlyParameters := parseCompilerParameters(ccModule.flags.Global.ConlyFlags, ctx, f) 183 translateToCMake(globalConlyParameters, f, true, false) 184 185 f.WriteString("\n# LOCAL C ONLY FLAGS:\n") 186 localConlyParameters := parseCompilerParameters(ccModule.flags.Local.ConlyFlags, ctx, f) 187 translateToCMake(localConlyParameters, f, true, false) 188 189 f.WriteString("\n# GLOBAL CPP FLAGS:\n") 190 globalCppParameters := parseCompilerParameters(ccModule.flags.Global.CppFlags, ctx, f) 191 translateToCMake(globalCppParameters, f, false, true) 192 193 f.WriteString("\n# LOCAL CPP FLAGS:\n") 194 localCppParameters := parseCompilerParameters(ccModule.flags.Local.CppFlags, ctx, f) 195 translateToCMake(localCppParameters, f, false, true) 196 197 f.WriteString("\n# GLOBAL SYSTEM INCLUDE FLAGS:\n") 198 globalIncludeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f) 199 translateToCMake(globalIncludeParameters, f, true, true) 200 201 // Add project executable. 202 f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n", 203 cleanExecutableName(ccModule.ModuleBase.Name()))) 204} 205 206func cleanExecutableName(s string) string { 207 return strings.Replace(s, "@", "-", -1) 208} 209 210func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) { 211 writeAllIncludeDirectories(c.systemHeaderSearchPath, f, true) 212 writeAllIncludeDirectories(c.headerSearchPath, f, false) 213 if cflags { 214 writeAllRelativeFilePathFlags(c.relativeFilePathFlags, f, "CMAKE_C_FLAGS") 215 writeAllFlags(c.flags, f, "CMAKE_C_FLAGS") 216 } 217 if cppflags { 218 writeAllRelativeFilePathFlags(c.relativeFilePathFlags, f, "CMAKE_CXX_FLAGS") 219 writeAllFlags(c.flags, f, "CMAKE_CXX_FLAGS") 220 } 221 if c.sysroot != "" { 222 f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(path.Join(c.sysroot, "usr", "include")))) 223 } 224 225} 226 227func buildCMakePath(p string) string { 228 if path.IsAbs(p) { 229 return p 230 } 231 return fmt.Sprintf("${ANDROID_ROOT}/%s", p) 232} 233 234func writeAllIncludeDirectories(includes []string, f *os.File, isSystem bool) { 235 if len(includes) == 0 { 236 return 237 } 238 239 system := "" 240 if isSystem { 241 system = "SYSTEM" 242 } 243 244 f.WriteString(fmt.Sprintf("include_directories(%s \n", system)) 245 246 for _, include := range includes { 247 f.WriteString(fmt.Sprintf(" \"%s\"\n", buildCMakePath(include))) 248 } 249 f.WriteString(")\n\n") 250 251 // Also add all headers to source files. 252 f.WriteString("file (GLOB_RECURSE TMP_HEADERS\n") 253 for _, include := range includes { 254 f.WriteString(fmt.Sprintf(" \"%s/**/*.h\"\n", buildCMakePath(include))) 255 } 256 f.WriteString(")\n") 257 f.WriteString("list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n") 258} 259 260type relativeFilePathFlagType struct { 261 flag string 262 relativeFilePath string 263} 264 265func writeAllRelativeFilePathFlags(relativeFilePathFlags []relativeFilePathFlagType, f *os.File, tag string) { 266 for _, flag := range relativeFilePathFlags { 267 f.WriteString(fmt.Sprintf("set(%s \"${%s} %s=%s\")\n", tag, tag, flag.flag, buildCMakePath(flag.relativeFilePath))) 268 } 269} 270 271func writeAllFlags(flags []string, f *os.File, tag string) { 272 for _, flag := range flags { 273 f.WriteString(fmt.Sprintf("set(%s \"${%s} %s\")\n", tag, tag, flag)) 274 } 275} 276 277type parameterType int 278 279const ( 280 headerSearchPath parameterType = iota 281 variable 282 systemHeaderSearchPath 283 flag 284 systemRoot 285 relativeFilePathFlag 286) 287 288type compilerParameters struct { 289 headerSearchPath []string 290 systemHeaderSearchPath []string 291 flags []string 292 sysroot string 293 // Must be in a=b/c/d format and can be split into "a" and "b/c/d" 294 relativeFilePathFlags []relativeFilePathFlagType 295} 296 297func makeCompilerParameters() compilerParameters { 298 return compilerParameters{ 299 sysroot: "", 300 } 301} 302 303func categorizeParameter(parameter string) parameterType { 304 if strings.HasPrefix(parameter, "-I") { 305 return headerSearchPath 306 } 307 if strings.HasPrefix(parameter, "$") { 308 return variable 309 } 310 if strings.HasPrefix(parameter, "-isystem") { 311 return systemHeaderSearchPath 312 } 313 if strings.HasPrefix(parameter, "-isysroot") { 314 return systemRoot 315 } 316 if strings.HasPrefix(parameter, "--sysroot") { 317 return systemRoot 318 } 319 if strings.HasPrefix(parameter, "-fsanitize-blacklist") { 320 return relativeFilePathFlag 321 } 322 return flag 323} 324 325// Flattens a list of strings potentially containing space characters into a list of string containing no 326// spaces. 327func normalizeParameters(params []string) []string { 328 var flatParams []string 329 for _, s := range params { 330 s = strings.Trim(s, " ") 331 if len(s) == 0 { 332 continue 333 } 334 flatParams = append(flatParams, strings.Split(s, " ")...) 335 } 336 return flatParams 337} 338 339func parseCompilerParameters(params []string, ctx android.SingletonContext, f *os.File) compilerParameters { 340 var compilerParameters = makeCompilerParameters() 341 342 for i, str := range params { 343 f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str)) 344 } 345 346 // Soong does not guarantee that each flag will be in an individual string. e.g: The 347 // input received could be: 348 // params = {"-isystem", "path/to/system"} 349 // or it could be 350 // params = {"-isystem path/to/system"} 351 // To normalize the input, we split all strings with the "space" character and consolidate 352 // all tokens into a flattened parameters list 353 params = normalizeParameters(params) 354 355 for i := 0; i < len(params); i++ { 356 param := params[i] 357 if param == "" { 358 continue 359 } 360 361 switch categorizeParameter(param) { 362 case headerSearchPath: 363 compilerParameters.headerSearchPath = 364 append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I")) 365 case variable: 366 if evaluated, error := evalVariable(ctx, param); error == nil { 367 if outputDebugInfo { 368 f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated)) 369 } 370 371 paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f) 372 concatenateParams(&compilerParameters, paramsFromVar) 373 374 } else { 375 if outputDebugInfo { 376 f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param)) 377 } 378 } 379 case systemHeaderSearchPath: 380 if i < len(params)-1 { 381 compilerParameters.systemHeaderSearchPath = 382 append(compilerParameters.systemHeaderSearchPath, params[i+1]) 383 } else if outputDebugInfo { 384 f.WriteString("# Found a header search path marker with no path") 385 } 386 i = i + 1 387 case flag: 388 c := cleanupParameter(param) 389 f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c)) 390 compilerParameters.flags = append(compilerParameters.flags, c) 391 case systemRoot: 392 if i < len(params)-1 { 393 compilerParameters.sysroot = params[i+1] 394 } else if outputDebugInfo { 395 f.WriteString("# Found a system root path marker with no path") 396 } 397 i = i + 1 398 case relativeFilePathFlag: 399 flagComponents := strings.Split(param, "=") 400 if len(flagComponents) == 2 { 401 flagStruct := relativeFilePathFlagType{flag: flagComponents[0], relativeFilePath: flagComponents[1]} 402 compilerParameters.relativeFilePathFlags = append(compilerParameters.relativeFilePathFlags, flagStruct) 403 } else { 404 if outputDebugInfo { 405 f.WriteString(fmt.Sprintf("# Relative File Path Flag [%s] is not formatted as a=b/c/d \n", param)) 406 } 407 } 408 } 409 } 410 return compilerParameters 411} 412 413func cleanupParameter(p string) string { 414 // In the blueprint, c flags can be passed as: 415 // cflags: [ "-DLOG_TAG=\"libEGL\"", ] 416 // which becomes: 417 // '-DLOG_TAG="libEGL"' in soong. 418 // In order to be injected in CMakelists.txt we need to: 419 // - Remove the wrapping ' character 420 // - Double escape all special \ and " characters. 421 // For a end result like: 422 // -DLOG_TAG=\\\"libEGL\\\" 423 if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 { 424 return p 425 } 426 427 // Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape 428 // TODO: It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex, 429 // we should create a method NinjaAndShellUnescape in escape.go and use that instead. 430 p = p[1 : len(p)-1] 431 p = strings.Replace(p, `'\''`, `'`, -1) 432 p = strings.Replace(p, `$$`, `$`, -1) 433 434 p = doubleEscape(p) 435 return p 436} 437 438func escape(s string) string { 439 s = strings.Replace(s, `\`, `\\`, -1) 440 s = strings.Replace(s, `"`, `\"`, -1) 441 return s 442} 443 444func doubleEscape(s string) string { 445 s = escape(s) 446 s = escape(s) 447 return s 448} 449 450func concatenateParams(c1 *compilerParameters, c2 compilerParameters) { 451 c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...) 452 c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...) 453 if c2.sysroot != "" { 454 c1.sysroot = c2.sysroot 455 } 456 c1.flags = append(c1.flags, c2.flags...) 457} 458 459func evalVariable(ctx android.SingletonContext, str string) (string, error) { 460 evaluated, err := ctx.Eval(pctx, str) 461 if err == nil { 462 return evaluated, nil 463 } 464 return "", err 465} 466 467func getCMakeListsForModule(module *Module, ctx android.SingletonContext) string { 468 return filepath.Join(android.AbsSrcDirForExistingUseCases(), 469 cLionOutputProjectsDirectory, 470 path.Dir(ctx.BlueprintFile(module)), 471 module.ModuleBase.Name()+"-"+ 472 module.ModuleBase.Arch().ArchType.Name+"-"+ 473 module.ModuleBase.Os().Name, 474 cMakeListsFilename) 475} 476