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