cmakelists.go 15 KB

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