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