github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/soong/cc/cmakelists.go (about)

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