Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d61f1f45 authored by Fabien Sanglard's avatar Fabien Sanglard
Browse files

Add support for CMakefile generation

Test: Manually generated CMakeLists.txt for gui/ui/aapt2.

Change-Id: I7dedc300c1e50b8e39bc58091b650c0bbe2c62da
parent 6d34b308
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -129,6 +129,7 @@ bootstrap_go_package {
        "cc/tidy.go",
        "cc/util.go",

        "cc/cmakelists.go",
        "cc/compiler.go",
        "cc/installer.go",
        "cc/linker.go",
+0 −4
Original line number Diff line number Diff line
@@ -27,10 +27,6 @@ import (
// compare the contents of the environment variables, rewriting the file if necessary to cause
// a manifest regeneration.

func init() {
	RegisterSingletonType("env", EnvSingleton)
}

func EnvSingleton() blueprint.Singleton {
	return &envSingleton{}
}
+1 −0
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ func NewContext() *blueprint.Context {
			handle.Parallel()
		}
	}
	ctx.RegisterSingletonType("env", EnvSingleton)

	return ctx
}
+10 −7
Original line number Diff line number Diff line
@@ -269,6 +269,9 @@ type Module struct {
	cachedToolchain config.Toolchain

	subAndroidMkOnce map[subAndroidMkProvider]bool

	// Flags used to compile this module
	flags Flags
}

func (c *Module) Init() (blueprint.Module, []interface{}) {
@@ -462,6 +465,13 @@ func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
	flags.CppFlags, _ = filterList(flags.CppFlags, config.IllegalFlags)
	flags.ConlyFlags, _ = filterList(flags.ConlyFlags, config.IllegalFlags)

	deps := c.depsToPaths(ctx)
	if ctx.Failed() {
		return
	}
	flags.GlobalFlags = append(flags.GlobalFlags, deps.Flags...)
	c.flags = flags

	// Optimization to reduce size of build.ninja
	// Replace the long list of flags for each file with a module-local variable
	ctx.Variable(pctx, "cflags", strings.Join(flags.CFlags, " "))
@@ -471,13 +481,6 @@ func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
	flags.CppFlags = []string{"$cppflags"}
	flags.AsFlags = []string{"$asflags"}

	deps := c.depsToPaths(ctx)
	if ctx.Failed() {
		return
	}

	flags.GlobalFlags = append(flags.GlobalFlags, deps.Flags...)

	var objs Objects
	if c.compiler != nil {
		objs = c.compiler.compile(ctx, flags, deps)

cc/cmakelists.go

0 → 100644
+343 −0
Original line number Diff line number Diff line
package cc

import (
	"fmt"

	"android/soong/android"
	"android/soong/cc/config"
	"github.com/google/blueprint"
	"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() 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)

	// Add project executable.
	f.WriteString(fmt.Sprintf("\nadd_executable(%s ${SOURCE_FILES})\n", ccModule.ModuleBase.Name()))
}

func translateToCMake(c compilerParameters, f *os.File, cflags bool, cppflags bool) {
	writeAllSystemDirectories(c.systemHeaderSearchPath, f)
	writeAllIncludeDirectories(c.headerSearchPath, f)
	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 map[string]bool, f *os.File) {
	for include := range includes {
		f.WriteString(fmt.Sprintf("include_directories(\"%s\")\n", buildCMakePath(include)))
	}
}

func writeAllSystemDirectories(includes map[string]bool, f *os.File) {
	for include := range includes {
		f.WriteString(fmt.Sprintf("include_directories(SYSTEM \"%s\")\n", buildCMakePath(include)))
	}
}

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       map[string]bool
	systemHeaderSearchPath map[string]bool
	flags                  []string
	sysroot                string
}

func makeCompilerParameters() compilerParameters {
	return compilerParameters{
		headerSearchPath:       make(map[string]bool),
		systemHeaderSearchPath: make(map[string]bool),
		flags:   make([]string, 0),
		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[strings.TrimPrefix(param, "-I")] = true
		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[params[i+1]] = true
			} else if outputDebugInfo {
				f.WriteString("# Found a header search path marker with no path")
			}
			i = i + 1
		case flag:
			compilerParameters.flags = append(compilerParameters.flags, param)
		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 concatenateParams(c1 *compilerParameters, c2 compilerParameters) {
	concatenateMaps(c1.headerSearchPath, c2.headerSearchPath)
	concatenateMaps(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
}

// Concatenate two maps into one. Results are stored in first operand.
func concatenateMaps(map1 map[string]bool, map2 map[string]bool) {
	for key, value := range map2 {
		map1[key] = value
	}
}

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
}
Loading