blob: 1c47ec98c6eb0585f8dffe7f6b2ffbdabdf3d57b [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
192func writeAllIncludeDirectories(includes map[string]bool, f *os.File) {
193 for include := range includes {
194 f.WriteString(fmt.Sprintf("include_directories(\"%s\")\n", buildCMakePath(include)))
195 }
196}
197
198func writeAllSystemDirectories(includes map[string]bool, f *os.File) {
199 for include := range includes {
200 f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(include)))
201 }
202}
203
204func writeAllFlags(flags []string, f *os.File, tag string) {
205 for _, flag := range flags {
206 f.WriteString(fmt.Sprintf("set(%s \"${%s} %s\")\n", tag, tag, flag))
207 }
208}
209
210type parameterType int
211
212const (
213 headerSearchPath parameterType = iota
214 variable
215 systemHeaderSearchPath
216 flag
217 systemRoot
218)
219
220type compilerParameters struct {
221 headerSearchPath map[string]bool
222 systemHeaderSearchPath map[string]bool
223 flags []string
224 sysroot string
225}
226
227func makeCompilerParameters() compilerParameters {
228 return compilerParameters{
229 headerSearchPath: make(map[string]bool),
230 systemHeaderSearchPath: make(map[string]bool),
231 flags: make([]string, 0),
232 sysroot: "",
233 }
234}
235
236func categorizeParameter(parameter string) parameterType {
237 if strings.HasPrefix(parameter, "-I") {
238 return headerSearchPath
239 }
240 if strings.HasPrefix(parameter, "$") {
241 return variable
242 }
243 if strings.HasPrefix(parameter, "-isystem") {
244 return systemHeaderSearchPath
245 }
246 if strings.HasPrefix(parameter, "-isysroot") {
247 return systemRoot
248 }
249 if strings.HasPrefix(parameter, "--sysroot") {
250 return systemRoot
251 }
252 return flag
253}
254
255func parseCompilerParameters(params []string, ctx blueprint.SingletonContext, f *os.File) compilerParameters {
256 var compilerParameters = makeCompilerParameters()
257
258 for i, str := range params {
259 f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str))
260 }
261
262 for i := 0; i < len(params); i++ {
263 param := params[i]
264 if param == "" {
265 continue
266 }
267
268 switch categorizeParameter(param) {
269 case headerSearchPath:
270 compilerParameters.headerSearchPath[strings.TrimPrefix(param, "-I")] = true
271 case variable:
272 if evaluated, error := evalVariable(ctx, param); error == nil {
273 if outputDebugInfo {
274 f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated))
275 }
276
277 paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f)
278 concatenateParams(&compilerParameters, paramsFromVar)
279
280 } else {
281 if outputDebugInfo {
282 f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param))
283 }
284 }
285 case systemHeaderSearchPath:
286 if i < len(params)-1 {
287 compilerParameters.systemHeaderSearchPath[params[i+1]] = true
288 } else if outputDebugInfo {
289 f.WriteString("# Found a header search path marker with no path")
290 }
291 i = i + 1
292 case flag:
Fabien Sanglard5cb35192017-02-06 09:49:58 -0800293 c := cleanupParameter(param)
294 f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c))
295 compilerParameters.flags = append(compilerParameters.flags, c)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800296 case systemRoot:
297 if i < len(params)-1 {
298 compilerParameters.sysroot = params[i+1]
299 } else if outputDebugInfo {
300 f.WriteString("# Found a system root path marker with no path")
301 }
302 i = i + 1
303 }
304 }
305 return compilerParameters
306}
307
Fabien Sanglard5cb35192017-02-06 09:49:58 -0800308func cleanupParameter(p string) string {
309 // In the blueprint, c flags can be passed as:
310 // cflags: [ "-DLOG_TAG=\"libEGL\"", ]
311 // which becomes:
312 // '-DLOG_TAG="libEGL"' in soong.
313 // In order to be injected in CMakelists.txt we need to:
314 // - Remove the wrapping ' character
315 // - Double escape all special \ and " characters.
316 // For a end result like:
317 // -DLOG_TAG=\\\"libEGL\\\"
318 if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 {
319 return p
320 }
321
322 // Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape
323 // TODO: It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex,
324 // we should create a method NinjaAndShellUnescape in escape.go and use that instead.
325 p = p[1 : len(p)-1]
326 p = strings.Replace(p, `'\''`, `'`, -1)
327 p = strings.Replace(p, `$$`, `$`, -1)
328
329 p = doubleEscape(p)
330 return p
331}
332
333func escape(s string) string {
334 s = strings.Replace(s, `\`, `\\`, -1)
335 s = strings.Replace(s, `"`, `\"`, -1)
336 return s
337}
338
339func doubleEscape(s string) string {
340 s = escape(s)
341 s = escape(s)
342 return s
343}
344
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800345func concatenateParams(c1 *compilerParameters, c2 compilerParameters) {
346 concatenateMaps(c1.headerSearchPath, c2.headerSearchPath)
347 concatenateMaps(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath)
348 if c2.sysroot != "" {
349 c1.sysroot = c2.sysroot
350 }
351 c1.flags = append(c1.flags, c2.flags...)
352}
353
354func evalVariable(ctx blueprint.SingletonContext, str string) (string, error) {
355 evaluated, err := ctx.Eval(pctx, str)
356 if err == nil {
357 return evaluated, nil
358 }
359 return "", err
360}
361
362// Concatenate two maps into one. Results are stored in first operand.
363func concatenateMaps(map1 map[string]bool, map2 map[string]bool) {
364 for key, value := range map2 {
365 map1[key] = value
366 }
367}
368
369func getCMakeListsForModule(module *Module, ctx blueprint.SingletonContext) string {
370 return filepath.Join(getAndroidSrcRootDirectory(ctx),
371 cLionOutputProjectsDirectory,
372 path.Dir(ctx.BlueprintFile(module)),
373 module.ModuleBase.Name()+"-"+
374 module.ModuleBase.Arch().ArchType.Name+"-"+
375 module.ModuleBase.Os().Name,
376 cMakeListsFilename)
377}
378
379func getAndroidSrcRootDirectory(ctx blueprint.SingletonContext) string {
380 srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
381 return srcPath
382}