blob: a2f46cd405744a693723c6b4e865cea7b51e12e1 [file] [log] [blame]
Colin Crossd00350c2017-11-17 10:55:38 -08001// 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
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080015package cc
16
17import (
18 "fmt"
19
20 "android/soong/android"
21 "android/soong/cc/config"
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080022 "os"
23 "path"
24 "path/filepath"
25 "strings"
26)
27
28// This singleton generates CMakeLists.txt files. It does so for each blueprint Android.bp resulting in a cc.Module
29// when either make, mm, mma, mmm or mmma is called. CMakeLists.txt files are generated in a separate folder
30// structure (see variable CLionOutputProjectsDirectory for root).
31
32func init() {
33 android.RegisterSingletonType("cmakelists_generator", cMakeListsGeneratorSingleton)
34}
35
Colin Cross0875c522017-11-28 17:34:01 -080036func cMakeListsGeneratorSingleton() android.Singleton {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080037 return &cmakelistsGeneratorSingleton{}
38}
39
40type cmakelistsGeneratorSingleton struct{}
41
42const (
43 cMakeListsFilename = "CMakeLists.txt"
44 cLionAggregateProjectsDirectory = "development" + string(os.PathSeparator) + "ide" + string(os.PathSeparator) + "clion"
45 cLionOutputProjectsDirectory = "out" + string(os.PathSeparator) + cLionAggregateProjectsDirectory
46 minimumCMakeVersionSupported = "3.5"
47
48 // Environment variables used to modify behavior of this singleton.
49 envVariableGenerateCMakeLists = "SOONG_GEN_CMAKEFILES"
50 envVariableGenerateDebugInfo = "SOONG_GEN_CMAKEFILES_DEBUG"
51 envVariableTrue = "1"
52)
53
54// Instruct generator to trace how header include path and flags were generated.
55// This is done to ease investigating bug reports.
56var outputDebugInfo = false
57
Colin Cross0875c522017-11-28 17:34:01 -080058func (c *cmakelistsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080059 if getEnvVariable(envVariableGenerateCMakeLists, ctx) != envVariableTrue {
60 return
61 }
62
63 outputDebugInfo = (getEnvVariable(envVariableGenerateDebugInfo, ctx) == envVariableTrue)
64
Colin Crossdfe47522018-06-07 14:16:27 -070065 // Track which projects have already had CMakeLists.txt generated to keep the first
66 // variant for each project.
67 seenProjects := map[string]bool{}
68
Colin Cross0875c522017-11-28 17:34:01 -080069 ctx.VisitAllModules(func(module android.Module) {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080070 if ccModule, ok := module.(*Module); ok {
71 if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
Colin Crossdfe47522018-06-07 14:16:27 -070072 generateCLionProject(compiledModule, ctx, ccModule, seenProjects)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080073 }
74 }
75 })
76
77 // Link all handmade CMakeLists.txt aggregate from
78 // BASE/development/ide/clion to
79 // BASE/out/development/ide/clion.
80 dir := filepath.Join(getAndroidSrcRootDirectory(ctx), cLionAggregateProjectsDirectory)
81 filepath.Walk(dir, linkAggregateCMakeListsFiles)
82
83 return
84}
85
Colin Cross0875c522017-11-28 17:34:01 -080086func getEnvVariable(name string, ctx android.SingletonContext) string {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080087 // Using android.Config.Getenv instead of os.getEnv to guarantee soong will
88 // re-run in case this environment variable changes.
Colin Crossaabf6792017-11-29 00:27:14 -080089 return ctx.Config().Getenv(name)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080090}
91
92func exists(path string) bool {
93 _, err := os.Stat(path)
94 if err == nil {
95 return true
96 }
97 if os.IsNotExist(err) {
98 return false
99 }
100 return true
101}
102
103func linkAggregateCMakeListsFiles(path string, info os.FileInfo, err error) error {
104
105 if info == nil {
106 return nil
107 }
108
109 dst := strings.Replace(path, cLionAggregateProjectsDirectory, cLionOutputProjectsDirectory, 1)
110 if info.IsDir() {
111 // This is a directory to create
112 os.MkdirAll(dst, os.ModePerm)
113 } else {
114 // This is a file to link
115 os.Remove(dst)
116 os.Symlink(path, dst)
117 }
118 return nil
119}
120
Colin Crossdfe47522018-06-07 14:16:27 -0700121func generateCLionProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module,
122 seenProjects map[string]bool) {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800123 srcs := compiledModule.Srcs()
124 if len(srcs) == 0 {
125 return
126 }
127
Colin Crossdfe47522018-06-07 14:16:27 -0700128 // Only write CMakeLists.txt for the first variant of each architecture of each module
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800129 clionproject_location := getCMakeListsForModule(ccModule, ctx)
Colin Crossdfe47522018-06-07 14:16:27 -0700130 if seenProjects[clionproject_location] {
131 return
132 }
133
134 seenProjects[clionproject_location] = true
135
136 // Ensure the directory hosting the cmakelists.txt exists
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800137 projectDir := path.Dir(clionproject_location)
138 os.MkdirAll(projectDir, os.ModePerm)
139
140 // Create cmakelists.txt
141 f, _ := os.Create(filepath.Join(projectDir, cMakeListsFilename))
142 defer f.Close()
143
144 // Header.
145 f.WriteString("# THIS FILE WAS AUTOMATICALY GENERATED!\n")
146 f.WriteString("# ANY MODIFICATION WILL BE OVERWRITTEN!\n\n")
147 f.WriteString("# To improve project view in Clion :\n")
148 f.WriteString("# Tools > CMake > Change Project Root \n\n")
149 f.WriteString(fmt.Sprintf("cmake_minimum_required(VERSION %s)\n", minimumCMakeVersionSupported))
150 f.WriteString(fmt.Sprintf("project(%s)\n", ccModule.ModuleBase.Name()))
151 f.WriteString(fmt.Sprintf("set(ANDROID_ROOT %s)\n\n", getAndroidSrcRootDirectory(ctx)))
152
153 if ccModule.flags.Clang {
154 pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
155 f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang"))
156 f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang++"))
157 } else {
158 toolchain := config.FindToolchain(ccModule.Os(), ccModule.Arch())
159 root, _ := evalVariable(ctx, toolchain.GccRoot())
160 triple, _ := evalVariable(ctx, toolchain.GccTriple())
161 pathToCC := filepath.Join(root, "bin", triple+"-")
162 f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "gcc"))
163 f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "g++"))
164 }
165 // Add all sources to the project.
166 f.WriteString("list(APPEND\n")
167 f.WriteString(" SOURCE_FILES\n")
168 for _, src := range srcs {
169 f.WriteString(fmt.Sprintf(" ${ANDROID_ROOT}/%s\n", src.String()))
170 }
171 f.WriteString(")\n")
172
173 // Add all header search path and compiler parameters (-D, -W, -f, -XXXX)
174 f.WriteString("\n# GLOBAL FLAGS:\n")
175 globalParameters := parseCompilerParameters(ccModule.flags.GlobalFlags, ctx, f)
176 translateToCMake(globalParameters, f, true, true)
177
178 f.WriteString("\n# CFLAGS:\n")
179 cParameters := parseCompilerParameters(ccModule.flags.CFlags, ctx, f)
180 translateToCMake(cParameters, f, true, true)
181
182 f.WriteString("\n# C ONLY FLAGS:\n")
183 cOnlyParameters := parseCompilerParameters(ccModule.flags.ConlyFlags, ctx, f)
184 translateToCMake(cOnlyParameters, f, true, false)
185
186 f.WriteString("\n# CPP FLAGS:\n")
187 cppParameters := parseCompilerParameters(ccModule.flags.CppFlags, ctx, f)
188 translateToCMake(cppParameters, f, false, true)
189
Colin Crossc3199482017-03-30 15:03:04 -0700190 f.WriteString("\n# SYSTEM INCLUDE FLAGS:\n")
191 includeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f)
192 translateToCMake(includeParameters, f, true, true)
193
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800194 // Add project executable.
Fabien Sanglard7da49262017-03-23 17:49:09 -0700195 f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n",
196 cleanExecutableName(ccModule.ModuleBase.Name())))
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800197}
198
Fabien Sanglard7da49262017-03-23 17:49:09 -0700199func cleanExecutableName(s string) string {
200 return strings.Replace(s, "@", "-", -1)
201}
202
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800203func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) {
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700204 writeAllIncludeDirectories(c.systemHeaderSearchPath, f, true)
205 writeAllIncludeDirectories(c.headerSearchPath, f, false)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800206 if cflags {
207 writeAllFlags(c.flags, f, "CMAKE_C_FLAGS")
208 }
209
210 if cppflags {
211 writeAllFlags(c.flags, f, "CMAKE_CXX_FLAGS")
212 }
213 if c.sysroot != "" {
214 f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(path.Join(c.sysroot, "usr", "include"))))
215 }
216
217}
218
219func buildCMakePath(p string) string {
220 if path.IsAbs(p) {
221 return p
222 }
223 return fmt.Sprintf("${ANDROID_ROOT}/%s", p)
224}
225
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700226func writeAllIncludeDirectories(includes []string, f *os.File, isSystem bool) {
Fabien Sanglard67472412017-03-21 10:19:19 -0700227 if len(includes) == 0 {
228 return
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800229 }
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800230
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700231 system := ""
Colin Cross51d4ab22017-05-09 13:44:49 -0700232 if isSystem {
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700233 system = "SYSTEM"
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800234 }
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700235
236 f.WriteString(fmt.Sprintf("include_directories(%s \n", system))
237
Fabien Sanglard67472412017-03-21 10:19:19 -0700238 for _, include := range includes {
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700239 f.WriteString(fmt.Sprintf(" \"%s\"\n", buildCMakePath(include)))
240 }
241 f.WriteString(")\n\n")
242
243 // Also add all headers to source files.
Colin Cross51d4ab22017-05-09 13:44:49 -0700244 f.WriteString("file (GLOB_RECURSE TMP_HEADERS\n")
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700245 for _, include := range includes {
246 f.WriteString(fmt.Sprintf(" \"%s/**/*.h\"\n", buildCMakePath(include)))
Fabien Sanglard67472412017-03-21 10:19:19 -0700247 }
248 f.WriteString(")\n")
Colin Cross51d4ab22017-05-09 13:44:49 -0700249 f.WriteString("list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n")
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800250}
251
252func writeAllFlags(flags []string, f *os.File, tag string) {
253 for _, flag := range flags {
254 f.WriteString(fmt.Sprintf("set(%s \"${%s} %s\")\n", tag, tag, flag))
255 }
256}
257
258type parameterType int
259
260const (
261 headerSearchPath parameterType = iota
262 variable
263 systemHeaderSearchPath
264 flag
265 systemRoot
266)
267
268type compilerParameters struct {
Fabien Sanglard67472412017-03-21 10:19:19 -0700269 headerSearchPath []string
270 systemHeaderSearchPath []string
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800271 flags []string
272 sysroot string
273}
274
275func makeCompilerParameters() compilerParameters {
276 return compilerParameters{
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800277 sysroot: "",
278 }
279}
280
281func categorizeParameter(parameter string) parameterType {
282 if strings.HasPrefix(parameter, "-I") {
283 return headerSearchPath
284 }
285 if strings.HasPrefix(parameter, "$") {
286 return variable
287 }
288 if strings.HasPrefix(parameter, "-isystem") {
289 return systemHeaderSearchPath
290 }
291 if strings.HasPrefix(parameter, "-isysroot") {
292 return systemRoot
293 }
294 if strings.HasPrefix(parameter, "--sysroot") {
295 return systemRoot
296 }
297 return flag
298}
299
Colin Cross0875c522017-11-28 17:34:01 -0800300func parseCompilerParameters(params []string, ctx android.SingletonContext, f *os.File) compilerParameters {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800301 var compilerParameters = makeCompilerParameters()
302
303 for i, str := range params {
304 f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str))
305 }
306
307 for i := 0; i < len(params); i++ {
308 param := params[i]
309 if param == "" {
310 continue
311 }
312
313 switch categorizeParameter(param) {
314 case headerSearchPath:
Fabien Sanglard67472412017-03-21 10:19:19 -0700315 compilerParameters.headerSearchPath =
316 append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I"))
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800317 case variable:
318 if evaluated, error := evalVariable(ctx, param); error == nil {
319 if outputDebugInfo {
320 f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated))
321 }
322
323 paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f)
324 concatenateParams(&compilerParameters, paramsFromVar)
325
326 } else {
327 if outputDebugInfo {
328 f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param))
329 }
330 }
331 case systemHeaderSearchPath:
332 if i < len(params)-1 {
Fabien Sanglard67472412017-03-21 10:19:19 -0700333 compilerParameters.systemHeaderSearchPath =
334 append(compilerParameters.systemHeaderSearchPath, params[i+1])
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800335 } else if outputDebugInfo {
336 f.WriteString("# Found a header search path marker with no path")
337 }
338 i = i + 1
339 case flag:
Fabien Sanglard5cb35192017-02-06 09:49:58 -0800340 c := cleanupParameter(param)
341 f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c))
342 compilerParameters.flags = append(compilerParameters.flags, c)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800343 case systemRoot:
344 if i < len(params)-1 {
345 compilerParameters.sysroot = params[i+1]
346 } else if outputDebugInfo {
347 f.WriteString("# Found a system root path marker with no path")
348 }
349 i = i + 1
350 }
351 }
352 return compilerParameters
353}
354
Fabien Sanglard5cb35192017-02-06 09:49:58 -0800355func cleanupParameter(p string) string {
356 // In the blueprint, c flags can be passed as:
357 // cflags: [ "-DLOG_TAG=\"libEGL\"", ]
358 // which becomes:
359 // '-DLOG_TAG="libEGL"' in soong.
360 // In order to be injected in CMakelists.txt we need to:
361 // - Remove the wrapping ' character
362 // - Double escape all special \ and " characters.
363 // For a end result like:
364 // -DLOG_TAG=\\\"libEGL\\\"
365 if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 {
366 return p
367 }
368
369 // Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape
370 // TODO: It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex,
371 // we should create a method NinjaAndShellUnescape in escape.go and use that instead.
372 p = p[1 : len(p)-1]
373 p = strings.Replace(p, `'\''`, `'`, -1)
374 p = strings.Replace(p, `$$`, `$`, -1)
375
376 p = doubleEscape(p)
377 return p
378}
379
380func escape(s string) string {
381 s = strings.Replace(s, `\`, `\\`, -1)
382 s = strings.Replace(s, `"`, `\"`, -1)
383 return s
384}
385
386func doubleEscape(s string) string {
387 s = escape(s)
388 s = escape(s)
389 return s
390}
391
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800392func concatenateParams(c1 *compilerParameters, c2 compilerParameters) {
Fabien Sanglard67472412017-03-21 10:19:19 -0700393 c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...)
394 c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800395 if c2.sysroot != "" {
396 c1.sysroot = c2.sysroot
397 }
398 c1.flags = append(c1.flags, c2.flags...)
399}
400
Colin Cross0875c522017-11-28 17:34:01 -0800401func evalVariable(ctx android.SingletonContext, str string) (string, error) {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800402 evaluated, err := ctx.Eval(pctx, str)
403 if err == nil {
404 return evaluated, nil
405 }
406 return "", err
407}
408
Colin Cross0875c522017-11-28 17:34:01 -0800409func getCMakeListsForModule(module *Module, ctx android.SingletonContext) string {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800410 return filepath.Join(getAndroidSrcRootDirectory(ctx),
411 cLionOutputProjectsDirectory,
412 path.Dir(ctx.BlueprintFile(module)),
413 module.ModuleBase.Name()+"-"+
414 module.ModuleBase.Arch().ArchType.Name+"-"+
415 module.ModuleBase.Os().Name,
416 cMakeListsFilename)
417}
418
Colin Cross0875c522017-11-28 17:34:01 -0800419func getAndroidSrcRootDirectory(ctx android.SingletonContext) string {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800420 srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
421 return srcPath
422}