blob: 699f5c01753ea6813a7c21d044d37a02a53636c9 [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 Crossdfe47522018-06-07 14:16:27 -070065 // Track which projects have already had CMakeLists.txt generated to keep the first
66 // variant for each project.
67 seenProjects := map[string]bool{}
68
Colin Cross0875c522017-11-28 17:34:01 -080069 ctx.VisitAllModules(func(module android.Module) {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080070 if ccModule, ok := module.(*Module); ok {
71 if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
Colin Crossdfe47522018-06-07 14:16:27 -070072 generateCLionProject(compiledModule, ctx, ccModule, seenProjects)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080073 }
74 }
75 })
76
77 // Link all handmade CMakeLists.txt aggregate from
78 // BASE/development/ide/clion to
79 // BASE/out/development/ide/clion.
80 dir := filepath.Join(getAndroidSrcRootDirectory(ctx), cLionAggregateProjectsDirectory)
81 filepath.Walk(dir, linkAggregateCMakeListsFiles)
82
83 return
84}
85
Colin Cross0875c522017-11-28 17:34:01 -080086func getEnvVariable(name string, ctx android.SingletonContext) string {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080087 // Using android.Config.Getenv instead of os.getEnv to guarantee soong will
88 // re-run in case this environment variable changes.
Colin Crossaabf6792017-11-29 00:27:14 -080089 return ctx.Config().Getenv(name)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -080090}
91
92func exists(path string) bool {
93 _, err := os.Stat(path)
94 if err == nil {
95 return true
96 }
97 if os.IsNotExist(err) {
98 return false
99 }
100 return true
101}
102
103func linkAggregateCMakeListsFiles(path string, info os.FileInfo, err error) error {
104
105 if info == nil {
106 return nil
107 }
108
109 dst := strings.Replace(path, cLionAggregateProjectsDirectory, cLionOutputProjectsDirectory, 1)
110 if info.IsDir() {
111 // This is a directory to create
112 os.MkdirAll(dst, os.ModePerm)
113 } else {
114 // This is a file to link
115 os.Remove(dst)
116 os.Symlink(path, dst)
117 }
118 return nil
119}
120
Colin Crossdfe47522018-06-07 14:16:27 -0700121func generateCLionProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module,
122 seenProjects map[string]bool) {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800123 srcs := compiledModule.Srcs()
124 if len(srcs) == 0 {
125 return
126 }
127
Colin Crossdfe47522018-06-07 14:16:27 -0700128 // Only write CMakeLists.txt for the first variant of each architecture of each module
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800129 clionproject_location := getCMakeListsForModule(ccModule, ctx)
Colin Crossdfe47522018-06-07 14:16:27 -0700130 if seenProjects[clionproject_location] {
131 return
132 }
133
134 seenProjects[clionproject_location] = true
135
136 // Ensure the directory hosting the cmakelists.txt exists
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800137 projectDir := path.Dir(clionproject_location)
138 os.MkdirAll(projectDir, os.ModePerm)
139
140 // Create cmakelists.txt
141 f, _ := os.Create(filepath.Join(projectDir, cMakeListsFilename))
142 defer f.Close()
143
144 // Header.
145 f.WriteString("# THIS FILE WAS AUTOMATICALY GENERATED!\n")
146 f.WriteString("# ANY MODIFICATION WILL BE OVERWRITTEN!\n\n")
147 f.WriteString("# To improve project view in Clion :\n")
148 f.WriteString("# Tools > CMake > Change Project Root \n\n")
149 f.WriteString(fmt.Sprintf("cmake_minimum_required(VERSION %s)\n", minimumCMakeVersionSupported))
150 f.WriteString(fmt.Sprintf("project(%s)\n", ccModule.ModuleBase.Name()))
151 f.WriteString(fmt.Sprintf("set(ANDROID_ROOT %s)\n\n", getAndroidSrcRootDirectory(ctx)))
152
153 if ccModule.flags.Clang {
154 pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
155 f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang"))
156 f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang++"))
157 } else {
158 toolchain := config.FindToolchain(ccModule.Os(), ccModule.Arch())
159 root, _ := evalVariable(ctx, toolchain.GccRoot())
160 triple, _ := evalVariable(ctx, toolchain.GccTriple())
161 pathToCC := filepath.Join(root, "bin", triple+"-")
162 f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "gcc"))
163 f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "g++"))
164 }
165 // Add all sources to the project.
166 f.WriteString("list(APPEND\n")
167 f.WriteString(" SOURCE_FILES\n")
168 for _, src := range srcs {
169 f.WriteString(fmt.Sprintf(" ${ANDROID_ROOT}/%s\n", src.String()))
170 }
171 f.WriteString(")\n")
172
173 // Add all header search path and compiler parameters (-D, -W, -f, -XXXX)
174 f.WriteString("\n# GLOBAL FLAGS:\n")
175 globalParameters := parseCompilerParameters(ccModule.flags.GlobalFlags, ctx, f)
176 translateToCMake(globalParameters, f, true, true)
177
178 f.WriteString("\n# CFLAGS:\n")
179 cParameters := parseCompilerParameters(ccModule.flags.CFlags, ctx, f)
180 translateToCMake(cParameters, f, true, true)
181
182 f.WriteString("\n# C ONLY FLAGS:\n")
183 cOnlyParameters := parseCompilerParameters(ccModule.flags.ConlyFlags, ctx, f)
184 translateToCMake(cOnlyParameters, f, true, false)
185
186 f.WriteString("\n# CPP FLAGS:\n")
187 cppParameters := parseCompilerParameters(ccModule.flags.CppFlags, ctx, f)
188 translateToCMake(cppParameters, f, false, true)
189
Colin Crossc3199482017-03-30 15:03:04 -0700190 f.WriteString("\n# SYSTEM INCLUDE FLAGS:\n")
191 includeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f)
192 translateToCMake(includeParameters, f, true, true)
193
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800194 // Add project executable.
Fabien Sanglard7da49262017-03-23 17:49:09 -0700195 f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n",
196 cleanExecutableName(ccModule.ModuleBase.Name())))
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800197}
198
Fabien Sanglard7da49262017-03-23 17:49:09 -0700199func cleanExecutableName(s string) string {
200 return strings.Replace(s, "@", "-", -1)
201}
202
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800203func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) {
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700204 writeAllIncludeDirectories(c.systemHeaderSearchPath, f, true)
205 writeAllIncludeDirectories(c.headerSearchPath, f, false)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800206 if cflags {
Jack He4a10b652018-08-14 17:27:15 -0700207 writeAllRelativeFilePathFlags(c.relativeFilePathFlags, f, "CMAKE_C_FLAGS")
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800208 writeAllFlags(c.flags, f, "CMAKE_C_FLAGS")
209 }
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800210 if cppflags {
Jack He4a10b652018-08-14 17:27:15 -0700211 writeAllRelativeFilePathFlags(c.relativeFilePathFlags, f, "CMAKE_CXX_FLAGS")
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800212 writeAllFlags(c.flags, f, "CMAKE_CXX_FLAGS")
213 }
214 if c.sysroot != "" {
215 f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(path.Join(c.sysroot, "usr", "include"))))
216 }
217
218}
219
220func buildCMakePath(p string) string {
221 if path.IsAbs(p) {
222 return p
223 }
224 return fmt.Sprintf("${ANDROID_ROOT}/%s", p)
225}
226
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700227func writeAllIncludeDirectories(includes []string, f *os.File, isSystem bool) {
Fabien Sanglard67472412017-03-21 10:19:19 -0700228 if len(includes) == 0 {
229 return
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800230 }
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800231
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700232 system := ""
Colin Cross51d4ab22017-05-09 13:44:49 -0700233 if isSystem {
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700234 system = "SYSTEM"
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800235 }
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700236
237 f.WriteString(fmt.Sprintf("include_directories(%s \n", system))
238
Fabien Sanglard67472412017-03-21 10:19:19 -0700239 for _, include := range includes {
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700240 f.WriteString(fmt.Sprintf(" \"%s\"\n", buildCMakePath(include)))
241 }
242 f.WriteString(")\n\n")
243
244 // Also add all headers to source files.
Colin Cross51d4ab22017-05-09 13:44:49 -0700245 f.WriteString("file (GLOB_RECURSE TMP_HEADERS\n")
Fabien Sanglardd9233f12017-03-24 12:02:50 -0700246 for _, include := range includes {
247 f.WriteString(fmt.Sprintf(" \"%s/**/*.h\"\n", buildCMakePath(include)))
Fabien Sanglard67472412017-03-21 10:19:19 -0700248 }
249 f.WriteString(")\n")
Colin Cross51d4ab22017-05-09 13:44:49 -0700250 f.WriteString("list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n")
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800251}
252
Jack He4a10b652018-08-14 17:27:15 -0700253type relativeFilePathFlagType struct {
254 flag string
255 relativeFilePath string
256}
257
258func writeAllRelativeFilePathFlags(relativeFilePathFlags []relativeFilePathFlagType, f *os.File, tag string) {
259 for _, flag := range relativeFilePathFlags {
260 f.WriteString(fmt.Sprintf("set(%s \"${%s} %s=%s\")\n", tag, tag, flag.flag, buildCMakePath(flag.relativeFilePath)))
261 }
262}
263
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800264func writeAllFlags(flags []string, f *os.File, tag string) {
265 for _, flag := range flags {
266 f.WriteString(fmt.Sprintf("set(%s \"${%s} %s\")\n", tag, tag, flag))
267 }
268}
269
270type parameterType int
271
272const (
273 headerSearchPath parameterType = iota
274 variable
275 systemHeaderSearchPath
276 flag
277 systemRoot
Jack He4a10b652018-08-14 17:27:15 -0700278 relativeFilePathFlag
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800279)
280
281type compilerParameters struct {
Fabien Sanglard67472412017-03-21 10:19:19 -0700282 headerSearchPath []string
283 systemHeaderSearchPath []string
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800284 flags []string
285 sysroot string
Jack He4a10b652018-08-14 17:27:15 -0700286 // Must be in a=b/c/d format and can be split into "a" and "b/c/d"
287 relativeFilePathFlags []relativeFilePathFlagType
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800288}
289
290func makeCompilerParameters() compilerParameters {
291 return compilerParameters{
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800292 sysroot: "",
293 }
294}
295
296func categorizeParameter(parameter string) parameterType {
297 if strings.HasPrefix(parameter, "-I") {
298 return headerSearchPath
299 }
300 if strings.HasPrefix(parameter, "$") {
301 return variable
302 }
303 if strings.HasPrefix(parameter, "-isystem") {
304 return systemHeaderSearchPath
305 }
306 if strings.HasPrefix(parameter, "-isysroot") {
307 return systemRoot
308 }
309 if strings.HasPrefix(parameter, "--sysroot") {
310 return systemRoot
311 }
Jack He4a10b652018-08-14 17:27:15 -0700312 if strings.HasPrefix(parameter, "-fsanitize-blacklist") {
313 return relativeFilePathFlag
314 }
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800315 return flag
316}
317
Colin Cross0875c522017-11-28 17:34:01 -0800318func parseCompilerParameters(params []string, ctx android.SingletonContext, f *os.File) compilerParameters {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800319 var compilerParameters = makeCompilerParameters()
320
321 for i, str := range params {
322 f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str))
323 }
324
325 for i := 0; i < len(params); i++ {
326 param := params[i]
327 if param == "" {
328 continue
329 }
330
331 switch categorizeParameter(param) {
332 case headerSearchPath:
Fabien Sanglard67472412017-03-21 10:19:19 -0700333 compilerParameters.headerSearchPath =
334 append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I"))
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800335 case variable:
336 if evaluated, error := evalVariable(ctx, param); error == nil {
337 if outputDebugInfo {
338 f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated))
339 }
340
341 paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f)
342 concatenateParams(&compilerParameters, paramsFromVar)
343
344 } else {
345 if outputDebugInfo {
346 f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param))
347 }
348 }
349 case systemHeaderSearchPath:
350 if i < len(params)-1 {
Fabien Sanglard67472412017-03-21 10:19:19 -0700351 compilerParameters.systemHeaderSearchPath =
352 append(compilerParameters.systemHeaderSearchPath, params[i+1])
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800353 } else if outputDebugInfo {
354 f.WriteString("# Found a header search path marker with no path")
355 }
356 i = i + 1
357 case flag:
Fabien Sanglard5cb35192017-02-06 09:49:58 -0800358 c := cleanupParameter(param)
359 f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c))
360 compilerParameters.flags = append(compilerParameters.flags, c)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800361 case systemRoot:
362 if i < len(params)-1 {
363 compilerParameters.sysroot = params[i+1]
364 } else if outputDebugInfo {
365 f.WriteString("# Found a system root path marker with no path")
366 }
367 i = i + 1
Jack He4a10b652018-08-14 17:27:15 -0700368 case relativeFilePathFlag:
369 flagComponents := strings.Split(param, "=")
370 if len(flagComponents) == 2 {
371 flagStruct := relativeFilePathFlagType{flag: flagComponents[0], relativeFilePath: flagComponents[1]}
372 compilerParameters.relativeFilePathFlags = append(compilerParameters.relativeFilePathFlags, flagStruct)
373 } else {
374 if outputDebugInfo {
375 f.WriteString(fmt.Sprintf("# Relative File Path Flag [%s] is not formatted as a=b/c/d \n", param))
376 }
377 }
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800378 }
379 }
380 return compilerParameters
381}
382
Fabien Sanglard5cb35192017-02-06 09:49:58 -0800383func cleanupParameter(p string) string {
384 // In the blueprint, c flags can be passed as:
385 // cflags: [ "-DLOG_TAG=\"libEGL\"", ]
386 // which becomes:
387 // '-DLOG_TAG="libEGL"' in soong.
388 // In order to be injected in CMakelists.txt we need to:
389 // - Remove the wrapping ' character
390 // - Double escape all special \ and " characters.
391 // For a end result like:
392 // -DLOG_TAG=\\\"libEGL\\\"
393 if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 {
394 return p
395 }
396
397 // Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape
398 // TODO: It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex,
399 // we should create a method NinjaAndShellUnescape in escape.go and use that instead.
400 p = p[1 : len(p)-1]
401 p = strings.Replace(p, `'\''`, `'`, -1)
402 p = strings.Replace(p, `$$`, `$`, -1)
403
404 p = doubleEscape(p)
405 return p
406}
407
408func escape(s string) string {
409 s = strings.Replace(s, `\`, `\\`, -1)
410 s = strings.Replace(s, `"`, `\"`, -1)
411 return s
412}
413
414func doubleEscape(s string) string {
415 s = escape(s)
416 s = escape(s)
417 return s
418}
419
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800420func concatenateParams(c1 *compilerParameters, c2 compilerParameters) {
Fabien Sanglard67472412017-03-21 10:19:19 -0700421 c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...)
422 c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...)
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800423 if c2.sysroot != "" {
424 c1.sysroot = c2.sysroot
425 }
426 c1.flags = append(c1.flags, c2.flags...)
427}
428
Colin Cross0875c522017-11-28 17:34:01 -0800429func evalVariable(ctx android.SingletonContext, str string) (string, error) {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800430 evaluated, err := ctx.Eval(pctx, str)
431 if err == nil {
432 return evaluated, nil
433 }
434 return "", err
435}
436
Colin Cross0875c522017-11-28 17:34:01 -0800437func getCMakeListsForModule(module *Module, ctx android.SingletonContext) string {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800438 return filepath.Join(getAndroidSrcRootDirectory(ctx),
439 cLionOutputProjectsDirectory,
440 path.Dir(ctx.BlueprintFile(module)),
441 module.ModuleBase.Name()+"-"+
442 module.ModuleBase.Arch().ArchType.Name+"-"+
443 module.ModuleBase.Os().Name,
444 cMakeListsFilename)
445}
446
Colin Cross0875c522017-11-28 17:34:01 -0800447func getAndroidSrcRootDirectory(ctx android.SingletonContext) string {
Fabien Sanglardd61f1f42017-01-10 16:21:22 -0800448 srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
449 return srcPath
450}