|  | // Copyright 2017 Google Inc. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | package cc | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  |  | 
|  | "android/soong/android" | 
|  | "os" | 
|  | "path" | 
|  | "path/filepath" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | // This singleton generates CMakeLists.txt files. It does so for each blueprint Android.bp resulting in a cc.Module | 
|  | // when either make, mm, mma, mmm or mmma is called. CMakeLists.txt files are generated in a separate folder | 
|  | // structure (see variable CLionOutputProjectsDirectory for root). | 
|  |  | 
|  | func init() { | 
|  | android.RegisterSingletonType("cmakelists_generator", cMakeListsGeneratorSingleton) | 
|  | } | 
|  |  | 
|  | func cMakeListsGeneratorSingleton() android.Singleton { | 
|  | return &cmakelistsGeneratorSingleton{} | 
|  | } | 
|  |  | 
|  | type cmakelistsGeneratorSingleton struct{} | 
|  |  | 
|  | const ( | 
|  | cMakeListsFilename              = "CMakeLists.txt" | 
|  | cLionAggregateProjectsDirectory = "development" + string(os.PathSeparator) + "ide" + string(os.PathSeparator) + "clion" | 
|  | cLionOutputProjectsDirectory    = "out" + string(os.PathSeparator) + cLionAggregateProjectsDirectory | 
|  | minimumCMakeVersionSupported    = "3.5" | 
|  |  | 
|  | // Environment variables used to modify behavior of this singleton. | 
|  | envVariableGenerateCMakeLists = "SOONG_GEN_CMAKEFILES" | 
|  | envVariableGenerateDebugInfo  = "SOONG_GEN_CMAKEFILES_DEBUG" | 
|  | envVariableTrue               = "1" | 
|  | ) | 
|  |  | 
|  | // Instruct generator to trace how header include path and flags were generated. | 
|  | // This is done to ease investigating bug reports. | 
|  | var outputDebugInfo = false | 
|  |  | 
|  | func (c *cmakelistsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) { | 
|  | if getEnvVariable(envVariableGenerateCMakeLists, ctx) != envVariableTrue { | 
|  | return | 
|  | } | 
|  |  | 
|  | outputDebugInfo = (getEnvVariable(envVariableGenerateDebugInfo, ctx) == envVariableTrue) | 
|  |  | 
|  | // Track which projects have already had CMakeLists.txt generated to keep the first | 
|  | // variant for each project. | 
|  | seenProjects := map[string]bool{} | 
|  |  | 
|  | ctx.VisitAllModules(func(module android.Module) { | 
|  | if ccModule, ok := module.(*Module); ok { | 
|  | if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok { | 
|  | generateCLionProject(compiledModule, ctx, ccModule, seenProjects) | 
|  | } | 
|  | } | 
|  | }) | 
|  |  | 
|  | // Link all handmade CMakeLists.txt aggregate from | 
|  | //     BASE/development/ide/clion to | 
|  | // BASE/out/development/ide/clion. | 
|  | dir := filepath.Join(getAndroidSrcRootDirectory(ctx), cLionAggregateProjectsDirectory) | 
|  | filepath.Walk(dir, linkAggregateCMakeListsFiles) | 
|  |  | 
|  | return | 
|  | } | 
|  |  | 
|  | func getEnvVariable(name string, ctx android.SingletonContext) string { | 
|  | // Using android.Config.Getenv instead of os.getEnv to guarantee soong will | 
|  | // re-run in case this environment variable changes. | 
|  | return ctx.Config().Getenv(name) | 
|  | } | 
|  |  | 
|  | func exists(path string) bool { | 
|  | _, err := os.Stat(path) | 
|  | if err == nil { | 
|  | return true | 
|  | } | 
|  | if os.IsNotExist(err) { | 
|  | return false | 
|  | } | 
|  | return true | 
|  | } | 
|  |  | 
|  | func linkAggregateCMakeListsFiles(path string, info os.FileInfo, err error) error { | 
|  |  | 
|  | if info == nil { | 
|  | return nil | 
|  | } | 
|  |  | 
|  | dst := strings.Replace(path, cLionAggregateProjectsDirectory, cLionOutputProjectsDirectory, 1) | 
|  | if info.IsDir() { | 
|  | // This is a directory to create | 
|  | os.MkdirAll(dst, os.ModePerm) | 
|  | } else { | 
|  | // This is a file to link | 
|  | os.Remove(dst) | 
|  | os.Symlink(path, dst) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func generateCLionProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module, | 
|  | seenProjects map[string]bool) { | 
|  | srcs := compiledModule.Srcs() | 
|  | if len(srcs) == 0 { | 
|  | return | 
|  | } | 
|  |  | 
|  | // Only write CMakeLists.txt for the first variant of each architecture of each module | 
|  | clionproject_location := getCMakeListsForModule(ccModule, ctx) | 
|  | if seenProjects[clionproject_location] { | 
|  | return | 
|  | } | 
|  |  | 
|  | seenProjects[clionproject_location] = true | 
|  |  | 
|  | // Ensure the directory hosting the cmakelists.txt exists | 
|  | projectDir := path.Dir(clionproject_location) | 
|  | os.MkdirAll(projectDir, os.ModePerm) | 
|  |  | 
|  | // Create cmakelists.txt | 
|  | f, _ := os.Create(filepath.Join(projectDir, cMakeListsFilename)) | 
|  | defer f.Close() | 
|  |  | 
|  | // Header. | 
|  | f.WriteString("# THIS FILE WAS AUTOMATICALY GENERATED!\n") | 
|  | f.WriteString("# ANY MODIFICATION WILL BE OVERWRITTEN!\n\n") | 
|  | f.WriteString("# To improve project view in Clion    :\n") | 
|  | f.WriteString("# Tools > CMake > Change Project Root  \n\n") | 
|  | f.WriteString(fmt.Sprintf("cmake_minimum_required(VERSION %s)\n", minimumCMakeVersionSupported)) | 
|  | f.WriteString(fmt.Sprintf("project(%s)\n", ccModule.ModuleBase.Name())) | 
|  | f.WriteString(fmt.Sprintf("set(ANDROID_ROOT %s)\n\n", getAndroidSrcRootDirectory(ctx))) | 
|  |  | 
|  | pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/") | 
|  | f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang")) | 
|  | f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang++")) | 
|  |  | 
|  | // Add all sources to the project. | 
|  | f.WriteString("list(APPEND\n") | 
|  | f.WriteString("     SOURCE_FILES\n") | 
|  | for _, src := range srcs { | 
|  | f.WriteString(fmt.Sprintf("    ${ANDROID_ROOT}/%s\n", src.String())) | 
|  | } | 
|  | f.WriteString(")\n") | 
|  |  | 
|  | // Add all header search path and compiler parameters (-D, -W, -f, -XXXX) | 
|  | f.WriteString("\n# GLOBAL FLAGS:\n") | 
|  | globalParameters := parseCompilerParameters(ccModule.flags.GlobalFlags, ctx, f) | 
|  | translateToCMake(globalParameters, f, true, true) | 
|  |  | 
|  | f.WriteString("\n# CFLAGS:\n") | 
|  | cParameters := parseCompilerParameters(ccModule.flags.CFlags, ctx, f) | 
|  | translateToCMake(cParameters, f, true, true) | 
|  |  | 
|  | f.WriteString("\n# C ONLY FLAGS:\n") | 
|  | cOnlyParameters := parseCompilerParameters(ccModule.flags.ConlyFlags, ctx, f) | 
|  | translateToCMake(cOnlyParameters, f, true, false) | 
|  |  | 
|  | f.WriteString("\n# CPP FLAGS:\n") | 
|  | cppParameters := parseCompilerParameters(ccModule.flags.CppFlags, ctx, f) | 
|  | translateToCMake(cppParameters, f, false, true) | 
|  |  | 
|  | f.WriteString("\n# SYSTEM INCLUDE FLAGS:\n") | 
|  | includeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f) | 
|  | translateToCMake(includeParameters, f, true, true) | 
|  |  | 
|  | // Add project executable. | 
|  | f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n", | 
|  | cleanExecutableName(ccModule.ModuleBase.Name()))) | 
|  | } | 
|  |  | 
|  | func cleanExecutableName(s string) string { | 
|  | return strings.Replace(s, "@", "-", -1) | 
|  | } | 
|  |  | 
|  | func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) { | 
|  | writeAllIncludeDirectories(c.systemHeaderSearchPath, f, true) | 
|  | writeAllIncludeDirectories(c.headerSearchPath, f, false) | 
|  | if cflags { | 
|  | writeAllRelativeFilePathFlags(c.relativeFilePathFlags, f, "CMAKE_C_FLAGS") | 
|  | writeAllFlags(c.flags, f, "CMAKE_C_FLAGS") | 
|  | } | 
|  | if cppflags { | 
|  | writeAllRelativeFilePathFlags(c.relativeFilePathFlags, f, "CMAKE_CXX_FLAGS") | 
|  | writeAllFlags(c.flags, f, "CMAKE_CXX_FLAGS") | 
|  | } | 
|  | if c.sysroot != "" { | 
|  | f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(path.Join(c.sysroot, "usr", "include")))) | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | func buildCMakePath(p string) string { | 
|  | if path.IsAbs(p) { | 
|  | return p | 
|  | } | 
|  | return fmt.Sprintf("${ANDROID_ROOT}/%s", p) | 
|  | } | 
|  |  | 
|  | func writeAllIncludeDirectories(includes []string, f *os.File, isSystem bool) { | 
|  | if len(includes) == 0 { | 
|  | return | 
|  | } | 
|  |  | 
|  | system := "" | 
|  | if isSystem { | 
|  | system = "SYSTEM" | 
|  | } | 
|  |  | 
|  | f.WriteString(fmt.Sprintf("include_directories(%s \n", system)) | 
|  |  | 
|  | for _, include := range includes { | 
|  | f.WriteString(fmt.Sprintf("    \"%s\"\n", buildCMakePath(include))) | 
|  | } | 
|  | f.WriteString(")\n\n") | 
|  |  | 
|  | // Also add all headers to source files. | 
|  | f.WriteString("file (GLOB_RECURSE TMP_HEADERS\n") | 
|  | for _, include := range includes { | 
|  | f.WriteString(fmt.Sprintf("    \"%s/**/*.h\"\n", buildCMakePath(include))) | 
|  | } | 
|  | f.WriteString(")\n") | 
|  | f.WriteString("list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n") | 
|  | } | 
|  |  | 
|  | type relativeFilePathFlagType struct { | 
|  | flag             string | 
|  | relativeFilePath string | 
|  | } | 
|  |  | 
|  | func writeAllRelativeFilePathFlags(relativeFilePathFlags []relativeFilePathFlagType, f *os.File, tag string) { | 
|  | for _, flag := range relativeFilePathFlags { | 
|  | f.WriteString(fmt.Sprintf("set(%s \"${%s} %s=%s\")\n", tag, tag, flag.flag, buildCMakePath(flag.relativeFilePath))) | 
|  | } | 
|  | } | 
|  |  | 
|  | func writeAllFlags(flags []string, f *os.File, tag string) { | 
|  | for _, flag := range flags { | 
|  | f.WriteString(fmt.Sprintf("set(%s \"${%s} %s\")\n", tag, tag, flag)) | 
|  | } | 
|  | } | 
|  |  | 
|  | type parameterType int | 
|  |  | 
|  | const ( | 
|  | headerSearchPath parameterType = iota | 
|  | variable | 
|  | systemHeaderSearchPath | 
|  | flag | 
|  | systemRoot | 
|  | relativeFilePathFlag | 
|  | ) | 
|  |  | 
|  | type compilerParameters struct { | 
|  | headerSearchPath       []string | 
|  | systemHeaderSearchPath []string | 
|  | flags                  []string | 
|  | sysroot                string | 
|  | // Must be in a=b/c/d format and can be split into "a" and "b/c/d" | 
|  | relativeFilePathFlags []relativeFilePathFlagType | 
|  | } | 
|  |  | 
|  | func makeCompilerParameters() compilerParameters { | 
|  | return compilerParameters{ | 
|  | sysroot: "", | 
|  | } | 
|  | } | 
|  |  | 
|  | func categorizeParameter(parameter string) parameterType { | 
|  | if strings.HasPrefix(parameter, "-I") { | 
|  | return headerSearchPath | 
|  | } | 
|  | if strings.HasPrefix(parameter, "$") { | 
|  | return variable | 
|  | } | 
|  | if strings.HasPrefix(parameter, "-isystem") { | 
|  | return systemHeaderSearchPath | 
|  | } | 
|  | if strings.HasPrefix(parameter, "-isysroot") { | 
|  | return systemRoot | 
|  | } | 
|  | if strings.HasPrefix(parameter, "--sysroot") { | 
|  | return systemRoot | 
|  | } | 
|  | if strings.HasPrefix(parameter, "-fsanitize-blacklist") { | 
|  | return relativeFilePathFlag | 
|  | } | 
|  | return flag | 
|  | } | 
|  |  | 
|  | func parseCompilerParameters(params []string, ctx android.SingletonContext, f *os.File) compilerParameters { | 
|  | var compilerParameters = makeCompilerParameters() | 
|  |  | 
|  | for i, str := range params { | 
|  | f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str)) | 
|  | } | 
|  |  | 
|  | for i := 0; i < len(params); i++ { | 
|  | param := params[i] | 
|  | if param == "" { | 
|  | continue | 
|  | } | 
|  |  | 
|  | switch categorizeParameter(param) { | 
|  | case headerSearchPath: | 
|  | compilerParameters.headerSearchPath = | 
|  | append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I")) | 
|  | case variable: | 
|  | if evaluated, error := evalVariable(ctx, param); error == nil { | 
|  | if outputDebugInfo { | 
|  | f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated)) | 
|  | } | 
|  |  | 
|  | paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f) | 
|  | concatenateParams(&compilerParameters, paramsFromVar) | 
|  |  | 
|  | } else { | 
|  | if outputDebugInfo { | 
|  | f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param)) | 
|  | } | 
|  | } | 
|  | case systemHeaderSearchPath: | 
|  | if i < len(params)-1 { | 
|  | compilerParameters.systemHeaderSearchPath = | 
|  | append(compilerParameters.systemHeaderSearchPath, params[i+1]) | 
|  | } else if outputDebugInfo { | 
|  | f.WriteString("# Found a header search path marker with no path") | 
|  | } | 
|  | i = i + 1 | 
|  | case flag: | 
|  | c := cleanupParameter(param) | 
|  | f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c)) | 
|  | compilerParameters.flags = append(compilerParameters.flags, c) | 
|  | case systemRoot: | 
|  | if i < len(params)-1 { | 
|  | compilerParameters.sysroot = params[i+1] | 
|  | } else if outputDebugInfo { | 
|  | f.WriteString("# Found a system root path marker with no path") | 
|  | } | 
|  | i = i + 1 | 
|  | case relativeFilePathFlag: | 
|  | flagComponents := strings.Split(param, "=") | 
|  | if len(flagComponents) == 2 { | 
|  | flagStruct := relativeFilePathFlagType{flag: flagComponents[0], relativeFilePath: flagComponents[1]} | 
|  | compilerParameters.relativeFilePathFlags = append(compilerParameters.relativeFilePathFlags, flagStruct) | 
|  | } else { | 
|  | if outputDebugInfo { | 
|  | f.WriteString(fmt.Sprintf("# Relative File Path Flag [%s] is not formatted as a=b/c/d \n", param)) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return compilerParameters | 
|  | } | 
|  |  | 
|  | func cleanupParameter(p string) string { | 
|  | // In the blueprint, c flags can be passed as: | 
|  | //  cflags: [ "-DLOG_TAG=\"libEGL\"", ] | 
|  | // which becomes: | 
|  | // '-DLOG_TAG="libEGL"' in soong. | 
|  | // In order to be injected in CMakelists.txt we need to: | 
|  | // - Remove the wrapping ' character | 
|  | // - Double escape all special \ and " characters. | 
|  | // For a end result like: | 
|  | // -DLOG_TAG=\\\"libEGL\\\" | 
|  | if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 { | 
|  | return p | 
|  | } | 
|  |  | 
|  | // Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape | 
|  | // TODO:  It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex, | 
|  | // we should create a method NinjaAndShellUnescape in escape.go and use that instead. | 
|  | p = p[1 : len(p)-1] | 
|  | p = strings.Replace(p, `'\''`, `'`, -1) | 
|  | p = strings.Replace(p, `$$`, `$`, -1) | 
|  |  | 
|  | p = doubleEscape(p) | 
|  | return p | 
|  | } | 
|  |  | 
|  | func escape(s string) string { | 
|  | s = strings.Replace(s, `\`, `\\`, -1) | 
|  | s = strings.Replace(s, `"`, `\"`, -1) | 
|  | return s | 
|  | } | 
|  |  | 
|  | func doubleEscape(s string) string { | 
|  | s = escape(s) | 
|  | s = escape(s) | 
|  | return s | 
|  | } | 
|  |  | 
|  | func concatenateParams(c1 *compilerParameters, c2 compilerParameters) { | 
|  | c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...) | 
|  | c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...) | 
|  | if c2.sysroot != "" { | 
|  | c1.sysroot = c2.sysroot | 
|  | } | 
|  | c1.flags = append(c1.flags, c2.flags...) | 
|  | } | 
|  |  | 
|  | func evalVariable(ctx android.SingletonContext, str string) (string, error) { | 
|  | evaluated, err := ctx.Eval(pctx, str) | 
|  | if err == nil { | 
|  | return evaluated, nil | 
|  | } | 
|  | return "", err | 
|  | } | 
|  |  | 
|  | func getCMakeListsForModule(module *Module, ctx android.SingletonContext) string { | 
|  | return filepath.Join(getAndroidSrcRootDirectory(ctx), | 
|  | cLionOutputProjectsDirectory, | 
|  | path.Dir(ctx.BlueprintFile(module)), | 
|  | module.ModuleBase.Name()+"-"+ | 
|  | module.ModuleBase.Arch().ArchType.Name+"-"+ | 
|  | module.ModuleBase.Os().Name, | 
|  | cMakeListsFilename) | 
|  | } | 
|  |  | 
|  | func getAndroidSrcRootDirectory(ctx android.SingletonContext) string { | 
|  | srcPath, _ := filepath.Abs(android.PathForSource(ctx).String()) | 
|  | return srcPath | 
|  | } |