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