Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 1 | package cc |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | |
| 6 | "android/soong/android" |
| 7 | "android/soong/cc/config" |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 8 | "os" |
| 9 | "path" |
| 10 | "path/filepath" |
| 11 | "strings" |
Colin Cross | c319948 | 2017-03-30 15:03:04 -0700 | [diff] [blame^] | 12 | |
| 13 | "github.com/google/blueprint" |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 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 | |
Colin Cross | c319948 | 2017-03-30 15:03:04 -0700 | [diff] [blame^] | 166 | f.WriteString("\n# SYSTEM INCLUDE FLAGS:\n") |
| 167 | includeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f) |
| 168 | translateToCMake(includeParameters, f, true, true) |
| 169 | |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 170 | // Add project executable. |
Fabien Sanglard | 7da4926 | 2017-03-23 17:49:09 -0700 | [diff] [blame] | 171 | f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n", |
| 172 | cleanExecutableName(ccModule.ModuleBase.Name()))) |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 173 | } |
| 174 | |
Fabien Sanglard | 7da4926 | 2017-03-23 17:49:09 -0700 | [diff] [blame] | 175 | func cleanExecutableName(s string) string { |
| 176 | return strings.Replace(s, "@", "-", -1) |
| 177 | } |
| 178 | |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 179 | func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) { |
Fabien Sanglard | d9233f1 | 2017-03-24 12:02:50 -0700 | [diff] [blame] | 180 | writeAllIncludeDirectories(c.systemHeaderSearchPath, f, true) |
| 181 | writeAllIncludeDirectories(c.headerSearchPath, f, false) |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 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 | |
Fabien Sanglard | d9233f1 | 2017-03-24 12:02:50 -0700 | [diff] [blame] | 202 | func writeAllIncludeDirectories(includes []string, f *os.File, isSystem bool) { |
Fabien Sanglard | 6747241 | 2017-03-21 10:19:19 -0700 | [diff] [blame] | 203 | if len(includes) == 0 { |
| 204 | return |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 205 | } |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 206 | |
Fabien Sanglard | d9233f1 | 2017-03-24 12:02:50 -0700 | [diff] [blame] | 207 | system := "" |
| 208 | if isSystem { |
| 209 | system = "SYSTEM" |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 210 | } |
Fabien Sanglard | d9233f1 | 2017-03-24 12:02:50 -0700 | [diff] [blame] | 211 | |
| 212 | f.WriteString(fmt.Sprintf("include_directories(%s \n", system)) |
| 213 | |
Fabien Sanglard | 6747241 | 2017-03-21 10:19:19 -0700 | [diff] [blame] | 214 | for _, include := range includes { |
Fabien Sanglard | d9233f1 | 2017-03-24 12:02:50 -0700 | [diff] [blame] | 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))) |
Fabien Sanglard | 6747241 | 2017-03-21 10:19:19 -0700 | [diff] [blame] | 223 | } |
| 224 | f.WriteString(")\n") |
Fabien Sanglard | d9233f1 | 2017-03-24 12:02:50 -0700 | [diff] [blame] | 225 | f.WriteString("list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n"); |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 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 { |
Fabien Sanglard | 6747241 | 2017-03-21 10:19:19 -0700 | [diff] [blame] | 245 | headerSearchPath []string |
| 246 | systemHeaderSearchPath []string |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 247 | flags []string |
| 248 | sysroot string |
| 249 | } |
| 250 | |
| 251 | func makeCompilerParameters() compilerParameters { |
| 252 | return compilerParameters{ |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 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: |
Fabien Sanglard | 6747241 | 2017-03-21 10:19:19 -0700 | [diff] [blame] | 291 | compilerParameters.headerSearchPath = |
| 292 | append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I")) |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 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 { |
Fabien Sanglard | 6747241 | 2017-03-21 10:19:19 -0700 | [diff] [blame] | 309 | compilerParameters.systemHeaderSearchPath = |
| 310 | append(compilerParameters.systemHeaderSearchPath, params[i+1]) |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 311 | } else if outputDebugInfo { |
| 312 | f.WriteString("# Found a header search path marker with no path") |
| 313 | } |
| 314 | i = i + 1 |
| 315 | case flag: |
Fabien Sanglard | 5cb3519 | 2017-02-06 09:49:58 -0800 | [diff] [blame] | 316 | c := cleanupParameter(param) |
| 317 | f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c)) |
| 318 | compilerParameters.flags = append(compilerParameters.flags, c) |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 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 | |
Fabien Sanglard | 5cb3519 | 2017-02-06 09:49:58 -0800 | [diff] [blame] | 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 | |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 368 | func concatenateParams(c1 *compilerParameters, c2 compilerParameters) { |
Fabien Sanglard | 6747241 | 2017-03-21 10:19:19 -0700 | [diff] [blame] | 369 | c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...) |
| 370 | c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...) |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 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 | |
Fabien Sanglard | d61f1f4 | 2017-01-10 16:21:22 -0800 | [diff] [blame] | 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 | } |