423 lines
13 KiB
Go
423 lines
13 KiB
Go
// Copyright 2017 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package cc
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"android/soong/android"
|
|
"android/soong/cc/config"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// This singleton generates CMakeLists.txt files. It does so for each blueprint Android.bp resulting in a cc.Module
|
|
// when either make, mm, mma, mmm or mmma is called. CMakeLists.txt files are generated in a separate folder
|
|
// structure (see variable CLionOutputProjectsDirectory for root).
|
|
|
|
func init() {
|
|
android.RegisterSingletonType("cmakelists_generator", cMakeListsGeneratorSingleton)
|
|
}
|
|
|
|
func cMakeListsGeneratorSingleton() android.Singleton {
|
|
return &cmakelistsGeneratorSingleton{}
|
|
}
|
|
|
|
type cmakelistsGeneratorSingleton struct{}
|
|
|
|
const (
|
|
cMakeListsFilename = "CMakeLists.txt"
|
|
cLionAggregateProjectsDirectory = "development" + string(os.PathSeparator) + "ide" + string(os.PathSeparator) + "clion"
|
|
cLionOutputProjectsDirectory = "out" + string(os.PathSeparator) + cLionAggregateProjectsDirectory
|
|
minimumCMakeVersionSupported = "3.5"
|
|
|
|
// Environment variables used to modify behavior of this singleton.
|
|
envVariableGenerateCMakeLists = "SOONG_GEN_CMAKEFILES"
|
|
envVariableGenerateDebugInfo = "SOONG_GEN_CMAKEFILES_DEBUG"
|
|
envVariableTrue = "1"
|
|
)
|
|
|
|
// Instruct generator to trace how header include path and flags were generated.
|
|
// This is done to ease investigating bug reports.
|
|
var outputDebugInfo = false
|
|
|
|
func (c *cmakelistsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
|
|
if getEnvVariable(envVariableGenerateCMakeLists, ctx) != envVariableTrue {
|
|
return
|
|
}
|
|
|
|
outputDebugInfo = (getEnvVariable(envVariableGenerateDebugInfo, ctx) == envVariableTrue)
|
|
|
|
// Track which projects have already had CMakeLists.txt generated to keep the first
|
|
// variant for each project.
|
|
seenProjects := map[string]bool{}
|
|
|
|
ctx.VisitAllModules(func(module android.Module) {
|
|
if ccModule, ok := module.(*Module); ok {
|
|
if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
|
|
generateCLionProject(compiledModule, ctx, ccModule, seenProjects)
|
|
}
|
|
}
|
|
})
|
|
|
|
// Link all handmade CMakeLists.txt aggregate from
|
|
// BASE/development/ide/clion to
|
|
// BASE/out/development/ide/clion.
|
|
dir := filepath.Join(getAndroidSrcRootDirectory(ctx), cLionAggregateProjectsDirectory)
|
|
filepath.Walk(dir, linkAggregateCMakeListsFiles)
|
|
|
|
return
|
|
}
|
|
|
|
func getEnvVariable(name string, ctx android.SingletonContext) string {
|
|
// Using android.Config.Getenv instead of os.getEnv to guarantee soong will
|
|
// re-run in case this environment variable changes.
|
|
return ctx.Config().Getenv(name)
|
|
}
|
|
|
|
func exists(path string) bool {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return true
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func linkAggregateCMakeListsFiles(path string, info os.FileInfo, err error) error {
|
|
|
|
if info == nil {
|
|
return nil
|
|
}
|
|
|
|
dst := strings.Replace(path, cLionAggregateProjectsDirectory, cLionOutputProjectsDirectory, 1)
|
|
if info.IsDir() {
|
|
// This is a directory to create
|
|
os.MkdirAll(dst, os.ModePerm)
|
|
} else {
|
|
// This is a file to link
|
|
os.Remove(dst)
|
|
os.Symlink(path, dst)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func generateCLionProject(compiledModule CompiledInterface, ctx android.SingletonContext, ccModule *Module,
|
|
seenProjects map[string]bool) {
|
|
srcs := compiledModule.Srcs()
|
|
if len(srcs) == 0 {
|
|
return
|
|
}
|
|
|
|
// Only write CMakeLists.txt for the first variant of each architecture of each module
|
|
clionproject_location := getCMakeListsForModule(ccModule, ctx)
|
|
if seenProjects[clionproject_location] {
|
|
return
|
|
}
|
|
|
|
seenProjects[clionproject_location] = true
|
|
|
|
// Ensure the directory hosting the cmakelists.txt exists
|
|
projectDir := path.Dir(clionproject_location)
|
|
os.MkdirAll(projectDir, os.ModePerm)
|
|
|
|
// Create cmakelists.txt
|
|
f, _ := os.Create(filepath.Join(projectDir, cMakeListsFilename))
|
|
defer f.Close()
|
|
|
|
// Header.
|
|
f.WriteString("# THIS FILE WAS AUTOMATICALY GENERATED!\n")
|
|
f.WriteString("# ANY MODIFICATION WILL BE OVERWRITTEN!\n\n")
|
|
f.WriteString("# To improve project view in Clion :\n")
|
|
f.WriteString("# Tools > CMake > Change Project Root \n\n")
|
|
f.WriteString(fmt.Sprintf("cmake_minimum_required(VERSION %s)\n", minimumCMakeVersionSupported))
|
|
f.WriteString(fmt.Sprintf("project(%s)\n", ccModule.ModuleBase.Name()))
|
|
f.WriteString(fmt.Sprintf("set(ANDROID_ROOT %s)\n\n", getAndroidSrcRootDirectory(ctx)))
|
|
|
|
if ccModule.flags.Clang {
|
|
pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
|
|
f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang"))
|
|
f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang++"))
|
|
} else {
|
|
toolchain := config.FindToolchain(ccModule.Os(), ccModule.Arch())
|
|
root, _ := evalVariable(ctx, toolchain.GccRoot())
|
|
triple, _ := evalVariable(ctx, toolchain.GccTriple())
|
|
pathToCC := filepath.Join(root, "bin", triple+"-")
|
|
f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "gcc"))
|
|
f.WriteString(fmt.Sprintf("set(CMAKE_CXX_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "g++"))
|
|
}
|
|
// Add all sources to the project.
|
|
f.WriteString("list(APPEND\n")
|
|
f.WriteString(" SOURCE_FILES\n")
|
|
for _, src := range srcs {
|
|
f.WriteString(fmt.Sprintf(" ${ANDROID_ROOT}/%s\n", src.String()))
|
|
}
|
|
f.WriteString(")\n")
|
|
|
|
// Add all header search path and compiler parameters (-D, -W, -f, -XXXX)
|
|
f.WriteString("\n# GLOBAL FLAGS:\n")
|
|
globalParameters := parseCompilerParameters(ccModule.flags.GlobalFlags, ctx, f)
|
|
translateToCMake(globalParameters, f, true, true)
|
|
|
|
f.WriteString("\n# CFLAGS:\n")
|
|
cParameters := parseCompilerParameters(ccModule.flags.CFlags, ctx, f)
|
|
translateToCMake(cParameters, f, true, true)
|
|
|
|
f.WriteString("\n# C ONLY FLAGS:\n")
|
|
cOnlyParameters := parseCompilerParameters(ccModule.flags.ConlyFlags, ctx, f)
|
|
translateToCMake(cOnlyParameters, f, true, false)
|
|
|
|
f.WriteString("\n# CPP FLAGS:\n")
|
|
cppParameters := parseCompilerParameters(ccModule.flags.CppFlags, ctx, f)
|
|
translateToCMake(cppParameters, f, false, true)
|
|
|
|
f.WriteString("\n# SYSTEM INCLUDE FLAGS:\n")
|
|
includeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f)
|
|
translateToCMake(includeParameters, f, true, true)
|
|
|
|
// Add project executable.
|
|
f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n",
|
|
cleanExecutableName(ccModule.ModuleBase.Name())))
|
|
}
|
|
|
|
func cleanExecutableName(s string) string {
|
|
return strings.Replace(s, "@", "-", -1)
|
|
}
|
|
|
|
func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) {
|
|
writeAllIncludeDirectories(c.systemHeaderSearchPath, f, true)
|
|
writeAllIncludeDirectories(c.headerSearchPath, f, false)
|
|
if cflags {
|
|
writeAllFlags(c.flags, f, "CMAKE_C_FLAGS")
|
|
}
|
|
|
|
if cppflags {
|
|
writeAllFlags(c.flags, f, "CMAKE_CXX_FLAGS")
|
|
}
|
|
if c.sysroot != "" {
|
|
f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(path.Join(c.sysroot, "usr", "include"))))
|
|
}
|
|
|
|
}
|
|
|
|
func buildCMakePath(p string) string {
|
|
if path.IsAbs(p) {
|
|
return p
|
|
}
|
|
return fmt.Sprintf("${ANDROID_ROOT}/%s", p)
|
|
}
|
|
|
|
func writeAllIncludeDirectories(includes []string, f *os.File, isSystem bool) {
|
|
if len(includes) == 0 {
|
|
return
|
|
}
|
|
|
|
system := ""
|
|
if isSystem {
|
|
system = "SYSTEM"
|
|
}
|
|
|
|
f.WriteString(fmt.Sprintf("include_directories(%s \n", system))
|
|
|
|
for _, include := range includes {
|
|
f.WriteString(fmt.Sprintf(" \"%s\"\n", buildCMakePath(include)))
|
|
}
|
|
f.WriteString(")\n\n")
|
|
|
|
// Also add all headers to source files.
|
|
f.WriteString("file (GLOB_RECURSE TMP_HEADERS\n")
|
|
for _, include := range includes {
|
|
f.WriteString(fmt.Sprintf(" \"%s/**/*.h\"\n", buildCMakePath(include)))
|
|
}
|
|
f.WriteString(")\n")
|
|
f.WriteString("list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n")
|
|
}
|
|
|
|
func writeAllFlags(flags []string, f *os.File, tag string) {
|
|
for _, flag := range flags {
|
|
f.WriteString(fmt.Sprintf("set(%s \"${%s} %s\")\n", tag, tag, flag))
|
|
}
|
|
}
|
|
|
|
type parameterType int
|
|
|
|
const (
|
|
headerSearchPath parameterType = iota
|
|
variable
|
|
systemHeaderSearchPath
|
|
flag
|
|
systemRoot
|
|
)
|
|
|
|
type compilerParameters struct {
|
|
headerSearchPath []string
|
|
systemHeaderSearchPath []string
|
|
flags []string
|
|
sysroot string
|
|
}
|
|
|
|
func makeCompilerParameters() compilerParameters {
|
|
return compilerParameters{
|
|
sysroot: "",
|
|
}
|
|
}
|
|
|
|
func categorizeParameter(parameter string) parameterType {
|
|
if strings.HasPrefix(parameter, "-I") {
|
|
return headerSearchPath
|
|
}
|
|
if strings.HasPrefix(parameter, "$") {
|
|
return variable
|
|
}
|
|
if strings.HasPrefix(parameter, "-isystem") {
|
|
return systemHeaderSearchPath
|
|
}
|
|
if strings.HasPrefix(parameter, "-isysroot") {
|
|
return systemRoot
|
|
}
|
|
if strings.HasPrefix(parameter, "--sysroot") {
|
|
return systemRoot
|
|
}
|
|
return flag
|
|
}
|
|
|
|
func parseCompilerParameters(params []string, ctx android.SingletonContext, f *os.File) compilerParameters {
|
|
var compilerParameters = makeCompilerParameters()
|
|
|
|
for i, str := range params {
|
|
f.WriteString(fmt.Sprintf("# Raw param [%d] = '%s'\n", i, str))
|
|
}
|
|
|
|
for i := 0; i < len(params); i++ {
|
|
param := params[i]
|
|
if param == "" {
|
|
continue
|
|
}
|
|
|
|
switch categorizeParameter(param) {
|
|
case headerSearchPath:
|
|
compilerParameters.headerSearchPath =
|
|
append(compilerParameters.headerSearchPath, strings.TrimPrefix(param, "-I"))
|
|
case variable:
|
|
if evaluated, error := evalVariable(ctx, param); error == nil {
|
|
if outputDebugInfo {
|
|
f.WriteString(fmt.Sprintf("# variable %s = '%s'\n", param, evaluated))
|
|
}
|
|
|
|
paramsFromVar := parseCompilerParameters(strings.Split(evaluated, " "), ctx, f)
|
|
concatenateParams(&compilerParameters, paramsFromVar)
|
|
|
|
} else {
|
|
if outputDebugInfo {
|
|
f.WriteString(fmt.Sprintf("# variable %s could NOT BE RESOLVED\n", param))
|
|
}
|
|
}
|
|
case systemHeaderSearchPath:
|
|
if i < len(params)-1 {
|
|
compilerParameters.systemHeaderSearchPath =
|
|
append(compilerParameters.systemHeaderSearchPath, params[i+1])
|
|
} else if outputDebugInfo {
|
|
f.WriteString("# Found a header search path marker with no path")
|
|
}
|
|
i = i + 1
|
|
case flag:
|
|
c := cleanupParameter(param)
|
|
f.WriteString(fmt.Sprintf("# FLAG '%s' became %s\n", param, c))
|
|
compilerParameters.flags = append(compilerParameters.flags, c)
|
|
case systemRoot:
|
|
if i < len(params)-1 {
|
|
compilerParameters.sysroot = params[i+1]
|
|
} else if outputDebugInfo {
|
|
f.WriteString("# Found a system root path marker with no path")
|
|
}
|
|
i = i + 1
|
|
}
|
|
}
|
|
return compilerParameters
|
|
}
|
|
|
|
func cleanupParameter(p string) string {
|
|
// In the blueprint, c flags can be passed as:
|
|
// cflags: [ "-DLOG_TAG=\"libEGL\"", ]
|
|
// which becomes:
|
|
// '-DLOG_TAG="libEGL"' in soong.
|
|
// In order to be injected in CMakelists.txt we need to:
|
|
// - Remove the wrapping ' character
|
|
// - Double escape all special \ and " characters.
|
|
// For a end result like:
|
|
// -DLOG_TAG=\\\"libEGL\\\"
|
|
if !strings.HasPrefix(p, "'") || !strings.HasSuffix(p, "'") || len(p) < 3 {
|
|
return p
|
|
}
|
|
|
|
// Reverse wrapper quotes and escaping that may have happened in NinjaAndShellEscape
|
|
// TODO: It is ok to reverse here for now but if NinjaAndShellEscape becomes more complex,
|
|
// we should create a method NinjaAndShellUnescape in escape.go and use that instead.
|
|
p = p[1 : len(p)-1]
|
|
p = strings.Replace(p, `'\''`, `'`, -1)
|
|
p = strings.Replace(p, `$$`, `$`, -1)
|
|
|
|
p = doubleEscape(p)
|
|
return p
|
|
}
|
|
|
|
func escape(s string) string {
|
|
s = strings.Replace(s, `\`, `\\`, -1)
|
|
s = strings.Replace(s, `"`, `\"`, -1)
|
|
return s
|
|
}
|
|
|
|
func doubleEscape(s string) string {
|
|
s = escape(s)
|
|
s = escape(s)
|
|
return s
|
|
}
|
|
|
|
func concatenateParams(c1 *compilerParameters, c2 compilerParameters) {
|
|
c1.headerSearchPath = append(c1.headerSearchPath, c2.headerSearchPath...)
|
|
c1.systemHeaderSearchPath = append(c1.systemHeaderSearchPath, c2.systemHeaderSearchPath...)
|
|
if c2.sysroot != "" {
|
|
c1.sysroot = c2.sysroot
|
|
}
|
|
c1.flags = append(c1.flags, c2.flags...)
|
|
}
|
|
|
|
func evalVariable(ctx android.SingletonContext, str string) (string, error) {
|
|
evaluated, err := ctx.Eval(pctx, str)
|
|
if err == nil {
|
|
return evaluated, nil
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
func getCMakeListsForModule(module *Module, ctx android.SingletonContext) string {
|
|
return filepath.Join(getAndroidSrcRootDirectory(ctx),
|
|
cLionOutputProjectsDirectory,
|
|
path.Dir(ctx.BlueprintFile(module)),
|
|
module.ModuleBase.Name()+"-"+
|
|
module.ModuleBase.Arch().ArchType.Name+"-"+
|
|
module.ModuleBase.Os().Name,
|
|
cMakeListsFilename)
|
|
}
|
|
|
|
func getAndroidSrcRootDirectory(ctx android.SingletonContext) string {
|
|
srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
|
|
return srcPath
|
|
}
|