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