cmakelists.go 13 KB

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