123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- // 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"
- "github.com/google/blueprint"
- )
- // 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() blueprint.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 blueprint.SingletonContext) {
- if getEnvVariable(envVariableGenerateCMakeLists, ctx) != envVariableTrue {
- return
- }
- outputDebugInfo = (getEnvVariable(envVariableGenerateDebugInfo, ctx) == envVariableTrue)
- ctx.VisitAllModules(func(module blueprint.Module) {
- if ccModule, ok := module.(*Module); ok {
- if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
- generateCLionProject(compiledModule, ctx, ccModule)
- }
- }
- })
- // 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 blueprint.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().(android.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 blueprint.SingletonContext, ccModule *Module) {
- srcs := compiledModule.Srcs()
- if len(srcs) == 0 {
- return
- }
- // Ensure the directory hosting the cmakelists.txt exists
- clionproject_location := getCMakeListsForModule(ccModule, ctx)
- 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 blueprint.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 blueprint.SingletonContext, str string) (string, error) {
- evaluated, err := ctx.Eval(pctx, str)
- if err == nil {
- return evaluated, nil
- }
- return "", err
- }
- func getCMakeListsForModule(module *Module, ctx blueprint.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 blueprint.SingletonContext) string {
- srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
- return srcPath
- }
|