blob: 83b6d69c8d3aee5c409ad9a36eef81e06c2bd4c3 [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.
Fabien Sanglard7da49262017-03-23 17:49:09 -0700166 f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n",
167 cleanExecutableName(ccModule.ModuleBase.Name())))
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800168}
169
Fabien Sanglard7da49262017-03-23 17:49:09 -0700170func cleanExecutableName(s string) string {
171 return strings.Replace(s, "@", "-", -1)
172}
173
174
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800175func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) {
176 writeAllSystemDirectories(c.systemHeaderSearchPath, f)
177 writeAllIncludeDirectories(c.headerSearchPath, f)
178 if cflags {
179 writeAllFlags(c.flags, f, "CMAKE_C_FLAGS")
180 }
181
182 if cppflags {
183 writeAllFlags(c.flags, f, "CMAKE_CXX_FLAGS")
184 }
185 if c.sysroot != "" {
186 f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(path.Join(c.sysroot, "usr", "include"))))
187 }
188
189}
190
191func buildCMakePath(p string) string {
192 if path.IsAbs(p) {
193 return p
194 }
195 return fmt.Sprintf("${ANDROID_ROOT}/%s", p)
196}
197
Fabien Sanglard67472412017-03-21 10:19:19 -0700198func writeAllIncludeDirectories(includes []string, f *os.File) {
199 if len(includes) == 0 {
200 return
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800201 }
Fabien Sanglard67472412017-03-21 10:19:19 -0700202 f.WriteString("include_directories(\n")
203 for _, include := range includes {
204 f.WriteString(fmt.Sprintf(" \"%s\"\n", buildCMakePath(include)))
205 }
206 f.WriteString(")\n")
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800207}
208
Fabien Sanglard67472412017-03-21 10:19:19 -0700209func writeAllSystemDirectories(includes []string, f *os.File) {
210 if len(includes) == 0 {
211 return
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800212 }
Fabien Sanglard67472412017-03-21 10:19:19 -0700213 f.WriteString("include_directories(SYSTEM \n")
214 for _, include := range includes {
215 f.WriteString(fmt.Sprintf(" \"%s\"\n", buildCMakePath(include)))
216 }
217 f.WriteString(")\n")
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800218}
219
220func 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
226type parameterType int
227
228const (
229 headerSearchPath parameterType = iota
230 variable
231 systemHeaderSearchPath
232 flag
233 systemRoot
234)
235
236type compilerParameters struct {
Fabien Sanglard67472412017-03-21 10:19:19 -0700237 headerSearchPath []string
238 systemHeaderSearchPath []string
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800239 flags []string
240 sysroot string
241}
242
243func makeCompilerParameters() compilerParameters {
244 return compilerParameters{
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800245 sysroot: "",
246 }
247}
248
249func categorizeParameter(parameter string) parameterType {
250 if strings.HasPrefix(parameter, "-I") {
251 return headerSearchPath
252 }
253 if strings.HasPrefix(parameter, "$") {
254 return variable
255 }
256 if strings.HasPrefix(parameter, "-isystem") {
257 return systemHeaderSearchPath
258 }
259 if strings.HasPrefix(parameter, "-isysroot") {
260 return systemRoot
261 }
262 if strings.HasPrefix(parameter, "--sysroot") {
263 return systemRoot
264 }
265 return flag
266}
267
268func parseCompilerParameters(params []string, ctx blueprint.SingletonContext, f *os.File) compilerParameters {
269 var compilerParameters = makeCompilerParameters()
270
271 for i, str := range params {
272 f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str))
273 }
274
275 for i := 0; i < len(params); i++ {
276 param := params[i]
277 if param == "" {
278 continue
279 }
280
281 switch categorizeParameter(param) {
282 case headerSearchPath:
Fabien Sanglard67472412017-03-21 10:19:19 -0700283 compilerParameters.headerSearchPath =
284 append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I"))
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800285 case variable:
286 if evaluated, error := evalVariable(ctx, param); error == nil {
287 if outputDebugInfo {
288 f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated))
289 }
290
291 paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f)
292 concatenateParams(&compilerParameters, paramsFromVar)
293
294 } else {
295 if outputDebugInfo {
296 f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param))
297 }
298 }
299 case systemHeaderSearchPath:
300 if i < len(params)-1 {
Fabien Sanglard67472412017-03-21 10:19:19 -0700301 compilerParameters.systemHeaderSearchPath =
302 append(compilerParameters.systemHeaderSearchPath, params[i+1])
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800303 } else if outputDebugInfo {
304 f.WriteString("# Found a header search path marker with no path")
305 }
306 i = i + 1
307 case flag:
Fabien Sanglard5cb35192017-02-06 09:49:58 -0800308 c := cleanupParameter(param)
309 f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c))
310 compilerParameters.flags = append(compilerParameters.flags, c)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800311 case systemRoot:
312 if i < len(params)-1 {
313 compilerParameters.sysroot = params[i+1]
314 } else if outputDebugInfo {
315 f.WriteString("# Found a system root path marker with no path")
316 }
317 i = i + 1
318 }
319 }
320 return compilerParameters
321}
322
Fabien Sanglard5cb35192017-02-06 09:49:58 -0800323func cleanupParameter(p string) string {
324 // In the blueprint, c flags can be passed as:
325 // cflags: [ "-DLOG_TAG=\"libEGL\"", ]
326 // which becomes:
327 // '-DLOG_TAG="libEGL"' in soong.
328 // In order to be injected in CMakelists.txt we need to:
329 // - Remove the wrapping ' character
330 // - Double escape all special \ and " characters.
331 // For a end result like:
332 // -DLOG_TAG=\\\"libEGL\\\"
333 if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 {
334 return p
335 }
336
337 // Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape
338 // TODO: It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex,
339 // we should create a method NinjaAndShellUnescape in escape.go and use that instead.
340 p = p[1 : len(p)-1]
341 p = strings.Replace(p, `'\''`, `'`, -1)
342 p = strings.Replace(p, `$$`, `$`, -1)
343
344 p = doubleEscape(p)
345 return p
346}
347
348func escape(s string) string {
349 s = strings.Replace(s, `\`, `\\`, -1)
350 s = strings.Replace(s, `"`, `\"`, -1)
351 return s
352}
353
354func doubleEscape(s string) string {
355 s = escape(s)
356 s = escape(s)
357 return s
358}
359
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800360func concatenateParams(c1 *compilerParameters, c2 compilerParameters) {
Fabien Sanglard67472412017-03-21 10:19:19 -0700361 c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...)
362 c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800363 if c2.sysroot != "" {
364 c1.sysroot = c2.sysroot
365 }
366 c1.flags = append(c1.flags, c2.flags...)
367}
368
369func evalVariable(ctx blueprint.SingletonContext, str string) (string, error) {
370 evaluated, err := ctx.Eval(pctx, str)
371 if err == nil {
372 return evaluated, nil
373 }
374 return "", err
375}
376
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800377func getCMakeListsForModule(module *Module, ctx blueprint.SingletonContext) string {
378 return filepath.Join(getAndroidSrcRootDirectory(ctx),
379 cLionOutputProjectsDirectory,
380 path.Dir(ctx.BlueprintFile(module)),
381 module.ModuleBase.Name()+"-"+
382 module.ModuleBase.Arch().ArchType.Name+"-"+
383 module.ModuleBase.Os().Name,
384 cMakeListsFilename)
385}
386
387func getAndroidSrcRootDirectory(ctx blueprint.SingletonContext) string {
388 srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
389 return srcPath
390}