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