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