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