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