123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- // 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"
- "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.RegisterParallelSingletonType("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(android.AbsSrcDirForExistingUseCases(), 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
- clionprojectLocation := getCMakeListsForModule(ccModule, ctx)
- if seenProjects[clionprojectLocation] {
- return
- }
- seenProjects[clionprojectLocation] = true
- // Ensure the directory hosting the cmakelists.txt exists
- projectDir := path.Dir(clionprojectLocation)
- 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", android.AbsSrcDirForExistingUseCases()))
- 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++"))
- // 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 ALL FLAGS:\n")
- globalAllParameters := parseCompilerParameters(ccModule.flags.Global.CommonFlags, ctx, f)
- translateToCMake(globalAllParameters, f, true, true)
- f.WriteString("\n# LOCAL ALL FLAGS:\n")
- localAllParameters := parseCompilerParameters(ccModule.flags.Local.CommonFlags, ctx, f)
- translateToCMake(localAllParameters, f, true, true)
- f.WriteString("\n# GLOBAL CFLAGS:\n")
- globalCParameters := parseCompilerParameters(ccModule.flags.Global.CFlags, ctx, f)
- translateToCMake(globalCParameters, f, true, true)
- f.WriteString("\n# LOCAL CFLAGS:\n")
- localCParameters := parseCompilerParameters(ccModule.flags.Local.CFlags, ctx, f)
- translateToCMake(localCParameters, f, true, true)
- f.WriteString("\n# GLOBAL C ONLY FLAGS:\n")
- globalConlyParameters := parseCompilerParameters(ccModule.flags.Global.ConlyFlags, ctx, f)
- translateToCMake(globalConlyParameters, f, true, false)
- f.WriteString("\n# LOCAL C ONLY FLAGS:\n")
- localConlyParameters := parseCompilerParameters(ccModule.flags.Local.ConlyFlags, ctx, f)
- translateToCMake(localConlyParameters, f, true, false)
- f.WriteString("\n# GLOBAL CPP FLAGS:\n")
- globalCppParameters := parseCompilerParameters(ccModule.flags.Global.CppFlags, ctx, f)
- translateToCMake(globalCppParameters, f, false, true)
- f.WriteString("\n# LOCAL CPP FLAGS:\n")
- localCppParameters := parseCompilerParameters(ccModule.flags.Local.CppFlags, ctx, f)
- translateToCMake(localCppParameters, f, false, true)
- f.WriteString("\n# GLOBAL SYSTEM INCLUDE FLAGS:\n")
- globalIncludeParameters := parseCompilerParameters(ccModule.flags.SystemIncludeFlags, ctx, f)
- translateToCMake(globalIncludeParameters, 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 {
- writeAllRelativeFilePathFlags(c.relativeFilePathFlags, f, "CMAKE_C_FLAGS")
- writeAllFlags(c.flags, f, "CMAKE_C_FLAGS")
- }
- if cppflags {
- writeAllRelativeFilePathFlags(c.relativeFilePathFlags, f, "CMAKE_CXX_FLAGS")
- 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")
- }
- type relativeFilePathFlagType struct {
- flag string
- relativeFilePath string
- }
- func writeAllRelativeFilePathFlags(relativeFilePathFlags []relativeFilePathFlagType, f *os.File, tag string) {
- for _, flag := range relativeFilePathFlags {
- f.WriteString(fmt.Sprintf("set(%s \"${%s} %s=%s\")\n", tag, tag, flag.flag, buildCMakePath(flag.relativeFilePath)))
- }
- }
- 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
- relativeFilePathFlag
- )
- type compilerParameters struct {
- headerSearchPath []string
- systemHeaderSearchPath []string
- flags []string
- sysroot string
- // Must be in a=b/c/d format and can be split into "a" and "b/c/d"
- relativeFilePathFlags []relativeFilePathFlagType
- }
- 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
- }
- if strings.HasPrefix(parameter, "-fsanitize-ignorelist") {
- return relativeFilePathFlag
- }
- if strings.HasPrefix(parameter, "-fprofile-sample-use") {
- return relativeFilePathFlag
- }
- return flag
- }
- // Flattens a list of strings potentially containing space characters into a list of string containing no
- // spaces.
- func normalizeParameters(params []string) []string {
- var flatParams []string
- for _, s := range params {
- s = strings.Trim(s, " ")
- if len(s) == 0 {
- continue
- }
- flatParams = append(flatParams, strings.Split(s, " ")...)
- }
- return flatParams
- }
- 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))
- }
- // Soong does not guarantee that each flag will be in an individual string. e.g: The
- // input received could be:
- // params = {"-isystem", "path/to/system"}
- // or it could be
- // params = {"-isystem path/to/system"}
- // To normalize the input, we split all strings with the "space" character and consolidate
- // all tokens into a flattened parameters list
- params = normalizeParameters(params)
- 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
- case relativeFilePathFlag:
- flagComponents := strings.Split(param, "=")
- if len(flagComponents) == 2 {
- flagStruct := relativeFilePathFlagType{flag: flagComponents[0], relativeFilePath: flagComponents[1]}
- compilerParameters.relativeFilePathFlags = append(compilerParameters.relativeFilePathFlags, flagStruct)
- } else {
- if outputDebugInfo {
- f.WriteString(fmt.Sprintf("# Relative File Path Flag [%s] is not formatted as a=b/c/d \n", param))
- }
- }
- }
- }
- 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(android.AbsSrcDirForExistingUseCases(),
- cLionOutputProjectsDirectory,
- path.Dir(ctx.BlueprintFile(module)),
- module.ModuleBase.Name()+"-"+
- module.ModuleBase.Arch().ArchType.Name+"-"+
- module.ModuleBase.Os().Name,
- cMakeListsFilename)
- }
|