Open-CMSIS-Pack/cbuild2cmake

View on GitHub
pkg/maker/contextlists.go

Summary

Maintainability
A
0 mins
Test Coverage
C
78%
/*
 * Copyright (c) 2024 Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

package maker

import (
    "path"
    "path/filepath"
    "strings"

    "github.com/Open-CMSIS-Pack/cbuild2cmake/pkg/utils"
    "golang.org/x/exp/maps"
)

func (m *Maker) CreateContextCMakeLists(index int) error {
    cbuild := &m.Cbuilds[index]
    outputByProducts, outputFile, outputType, customCommands := OutputFiles(cbuild.BuildDescType.Output)
    outputExt := path.Ext(outputFile)
    outputName := strings.TrimSuffix(outputFile, outputExt)
    cbuild.ContextRoot, _ = filepath.Rel(m.CbuildIndex.BaseDir, cbuild.BaseDir)
    cbuild.ContextRoot = filepath.ToSlash(cbuild.ContextRoot)
    cbuild.Toolchain = m.RegisteredToolchains[m.SelectedToolchainVersion[index]].Name
    outDir := AddRootPrefix(cbuild.ContextRoot, cbuild.BuildDescType.OutputDirs.Outdir)
    contextDir := path.Join(m.SolutionTmpDir, cbuild.BuildDescType.Context)
    cbuild.IncludeGlobal = make(LanguageMap)
    cbuild.UserIncGlobal = make(LanguageMap)

    var cmakeTargetType, outputDirType, linkerVars, linkerOptions string
    if outputType == "elf" {
        cmakeTargetType = "add_executable"
        outputDirType = "RUNTIME_OUTPUT_DIRECTORY"
    } else if outputType == "lib" {
        cmakeTargetType = "add_library"
        outputDirType = "ARCHIVE_OUTPUT_DIRECTORY"
    }

    // Create toolchain.cmake
    err := m.CMakeCreateToolchain(index, contextDir)
    if err != nil {
        return err
    }

    // Create groups.cmake
    err = cbuild.CMakeCreateGroups(contextDir)
    if err != nil {
        return err
    }

    // Create components.cmake
    err = cbuild.CMakeCreateComponents(contextDir)
    if err != nil {
        return err
    }

    // Libraries
    var libraries []string
    libraries = append(libraries, cbuild.ListGroupsAndComponents()...)
    libraries = append(libraries, cbuild.GetLinkLibraries()...)

    // Linker options
    if outputType == "elf" {
        linkerVars, linkerOptions = cbuild.LinkerOptions()
    }

    // Make system includes explicit for compilation database completeness
    var systemIncludes string
    for _, language := range cbuild.Languages {
        switch language {
        case "C", "CXX":
            systemIncludes += "\nset(CMAKE_" + language + "_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_" + language + "_IMPLICIT_INCLUDE_DIRECTORIES})"
        }
    }

    // Constructed files: collect headers and global pre-includes
    constructedFiles := cbuild.ClassifyFiles(cbuild.BuildDescType.ConstructedFiles)

    // Global classified includes
    includeGlobal := make(ScopeMap)
    includeGlobal["PUBLIC"] = AppendGlobalIncludes(make(LanguageMap), constructedFiles.Include)

    // Merge common add-path and add-path-asm
    if len(cbuild.BuildDescType.AddPath) > 0 {
        includeGlobal["PUBLIC"]["C,CXX"] = utils.AppendUniquely(includeGlobal["PUBLIC"]["C,CXX"], AddRootPrefixes(cbuild.ContextRoot, cbuild.BuildDescType.AddPath)...)
    }
    if len(cbuild.BuildDescType.AddPathAsm) > 0 {
        includeGlobal["PUBLIC"]["ASM"] = utils.AppendUniquely(includeGlobal["PUBLIC"]["ASM"], AddRootPrefixes(cbuild.ContextRoot, cbuild.BuildDescType.AddPathAsm)...)
    }
    includeGlobal["PUBLIC"] = MergeLanguageCommonIncludes(includeGlobal["PUBLIC"])

    // Global component includes
    for language, paths := range cbuild.IncludeGlobal {
        includeGlobal["PUBLIC"][language] = utils.AppendUniquely(includeGlobal["PUBLIC"][language], paths...)
    }

    // Global user includes
    for language, paths := range cbuild.UserIncGlobal {
        includeGlobal["PUBLIC"][language] = utils.AppendUniquely(includeGlobal["PUBLIC"][language], paths...)
    }

    // Global compile options abstractions
    abstractions := CompilerAbstractions{cbuild.BuildDescType.Debug, cbuild.BuildDescType.Optimize, cbuild.BuildDescType.Warnings, cbuild.BuildDescType.LanguageC, cbuild.BuildDescType.LanguageCpp}
    var globalCompilerAbstractions string
    if !AreAbstractionsEmpty(abstractions, cbuild.Languages) {
        globalCompilerAbstractions = "\n\n# Compile Options Abstractions" + cbuild.CMakeTargetCompileOptionsAbstractions("${CONTEXT}", abstractions, cbuild.Languages)
    }

    // Create CMakeLists content
    content := `cmake_minimum_required(VERSION 3.27)

# Roots
include("../roots.cmake")

set(CONTEXT ` + strings.ReplaceAll(cbuild.BuildDescType.Context, " ", "_") + `)
set(TARGET ${CONTEXT})
set(OUT_DIR "` + outDir + `")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)` + outputByProducts + linkerVars + `

# Processor Options` + cbuild.ProcessorOptions() + `

# Toolchain config map
include("toolchain.cmake")

# Setup project
project(${CONTEXT} LANGUAGES ` + strings.Join(cbuild.Languages, " ") + `)

# Compilation database
add_custom_target(database
  COMMAND ${CMAKE_COMMAND} -E make_directory "${OUT_DIR}"
  COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json" "${OUT_DIR}"
)` + systemIncludes + `

# Setup context
` + cmakeTargetType + `(${CONTEXT})
set_target_properties(${CONTEXT} PROPERTIES PREFIX "" SUFFIX "` + outputExt + `" OUTPUT_NAME "` + outputName + `")
set_target_properties(${CONTEXT} PROPERTIES ` + outputDirType + ` ${OUT_DIR})
add_library(${CONTEXT}_GLOBAL INTERFACE)

# Includes` + CMakeTargetIncludeDirectories("${CONTEXT}", includeGlobal) + `

# Defines` + CMakeTargetCompileDefinitions("${CONTEXT}", "", "PUBLIC", cbuild.BuildDescType.Define, []string{}) + `

# Compile options` + cbuild.CMakeTargetCompileOptionsGlobal("${CONTEXT}", "PUBLIC") + globalCompilerAbstractions + `

# Add groups and components
include("groups.cmake")
include("components.cmake")
` + cbuild.CMakeTargetLinkLibraries("${CONTEXT}", "PUBLIC", libraries...) + `
` + linkerOptions + customCommands + `
`
    // Update CMakeLists.txt
    contextCMakeLists := path.Join(contextDir, "CMakeLists.txt")
    err = utils.UpdateFile(contextCMakeLists, content)
    if err != nil {
        return err
    }
    return err
}

func (m *Maker) CMakeCreateToolchain(index int, contextDir string) error {
    toolchainConfig, _ := filepath.Rel(m.EnvVars.CompilerRoot, m.SelectedToolchainConfig[index])
    toolchainConfig = "${CMSIS_COMPILER_ROOT}/" + filepath.ToSlash(toolchainConfig)
    content := `# toolchain.cmake

set(REGISTERED_TOOLCHAIN_ROOT "` + m.RegisteredToolchains[m.SelectedToolchainVersion[index]].Path + `")
set(REGISTERED_TOOLCHAIN_VERSION "` + m.SelectedToolchainVersion[index].String() + `")
include("` + toolchainConfig + `")
`
    filename := path.Join(contextDir, "toolchain.cmake")
    err := utils.UpdateFile(filename, content)
    if err != nil {
        return err
    }
    return err
}

func (c *Cbuild) CMakeCreateGroups(contextDir string) error {
    content := "# groups.cmake\n"
    abstractions := CompilerAbstractions{c.BuildDescType.Debug, c.BuildDescType.Optimize, c.BuildDescType.Warnings, c.BuildDescType.LanguageC, c.BuildDescType.LanguageCpp}
    content += c.CMakeCreateGroupRecursively("", c.BuildDescType.Groups, abstractions, c.BuildDescType.DefineAsm, c.BuildDescType.Misc.ASM)
    filename := path.Join(contextDir, "groups.cmake")
    err := utils.UpdateFile(filename, content)
    if err != nil {
        return err
    }

    return err
}

func (c *Cbuild) CMakeCreateGroupRecursively(parent string, groups []Groups,
    parentAbstractions CompilerAbstractions, parentDefineAsm []interface{}, parentMiscAsm []string) string {
    var content string
    for _, group := range groups {
        miscAsm := utils.AppendUniquely(parentMiscAsm, group.Misc.ASM...)
        buildFiles := c.ClassifyFiles(group.Files)
        hasChildren := len(group.Groups) > 0
        if !hasChildren && len(buildFiles.Source) == 0 && len(buildFiles.Custom) == 0 && len(buildFiles.Include) == 0 &&
            len(buildFiles.Library) == 0 && len(buildFiles.Object) == 0 {
            continue
        }
        firstLevelGroup := len(parent) == 0
        name := parent + "_" + ReplaceDelimiters(group.Group)
        parentName := parent
        if firstLevelGroup {
            name = "Group" + name
            parentName = "${CONTEXT}"
        }
        // default scope
        scope := "PUBLIC"
        if buildFiles.Interface {
            scope = "INTERFACE"
        }
        // add_library
        content += "\n# group " + group.Group
        content += CMakeAddLibrary(name, buildFiles)
        // target_include_directories
        if len(buildFiles.Include) > 0 {
            c.UserIncGlobal = AppendGlobalIncludes(c.UserIncGlobal, buildFiles.Include)
        }
        content += CMakeTargetIncludeDirectories(name, c.MergeIncludes(buildFiles.Include, scope, parentName, group.AddPath, group.AddPathAsm, group.DelPath))
        // target_compile_definitions
        content += CMakeTargetCompileDefinitions(name, parentName, scope, group.Define, group.Undefine)
        group.DefineAsm = utils.AppendDefines(group.DefineAsm, parentDefineAsm)
        // compiler abstractions
        hasFileAbstractions := HasFileAbstractions(group.Files)
        groupAbstractions := CompilerAbstractions{group.Debug, group.Optimize, group.Warnings, group.LanguageC, group.LanguageCpp}
        languages := utils.AppendUniquely(maps.Keys(buildFiles.Source), maps.Keys(buildFiles.Custom)...)
        var abstractions CompilerAbstractions
        if !AreAbstractionsEmpty(groupAbstractions, c.Languages) {
            abstractions = InheritCompilerAbstractions(parentAbstractions, groupAbstractions)
            if !hasFileAbstractions {
                content += c.CMakeTargetCompileOptionsAbstractions(name, abstractions, languages)
            }
        }
        var libraries []string
        if !buildFiles.Interface && !hasFileAbstractions {
            if !AreAbstractionsEmpty(groupAbstractions, languages) {
                libraries = append(libraries, name+"_ABSTRACTIONS")
            } else if !AreAbstractionsEmpty(parentAbstractions, languages) {
                libraries = append(libraries, parentName+"_ABSTRACTIONS")
            }
        }
        // target_compile_options
        if hasChildren || len(buildFiles.Source) > 0 || len(buildFiles.Custom) > 0 {
            content += c.CMakeTargetCompileOptions(name, scope, group.Misc, buildFiles.PreIncludeLocal, parentName)
        }
        // target_link_libraries
        libraries = append(libraries, buildFiles.Library...)
        c.LibraryGlobal = append(c.LibraryGlobal, buildFiles.Library...)
        libraries = append(libraries, buildFiles.Object...)
        if len(libraries) > 0 {
            content += c.CMakeTargetLinkLibraries(name, scope, libraries...)
        }
        // file level handling
        for _, file := range group.Files {
            if strings.Contains(file.Category, "source") {
                if HasFileCustomOptions(file) {
                    // custom file target
                    fileTargetName := name + "_" + ReplaceDelimiters(file.File)
                    c.BuildGroups = append(c.BuildGroups, fileTargetName)
                    content += "\n\n# file " + file.File
                    content += c.CMakeAddLibraryCustomFile(fileTargetName, file)
                    // target_include_directories
                    content += CMakeTargetIncludeDirectories(fileTargetName, c.MergeIncludes(ScopeMap{}, "PUBLIC", name, file.AddPath, file.AddPathAsm, file.DelPath))
                    // target_compile_definitions (except asm)
                    if GetLanguage(file) != "ASM" {
                        content += CMakeTargetCompileDefinitions(fileTargetName, name, "PUBLIC", file.Define, file.Undefine)
                    }
                    // target_compile_options
                    content += c.CMakeTargetCompileOptions(fileTargetName, "PUBLIC", Misc{}, []string{}, name)
                }
                // asm defines are set in file properties
                if GetLanguage(file) == "ASM" {
                    file.DefineAsm = utils.AppendDefines(file.DefineAsm, group.DefineAsm)
                    file.DefineAsm = utils.AppendDefines(file.Define, file.DefineAsm)
                    content += c.SetFileAsmDefines(file, miscAsm)
                }
                // file compile options and abstractions
                fileAbstractions := CompilerAbstractions{file.Debug, file.Optimize, file.Warnings, file.LanguageC, file.LanguageCpp}
                if hasFileAbstractions {
                    fileAbstractions = InheritCompilerAbstractions(abstractions, fileAbstractions)
                }
                content += c.CMakeSetFileProperties(file, fileAbstractions)
            }
        }
        content += "\n"

        // create children groups recursively
        if hasChildren {
            content += c.CMakeCreateGroupRecursively(name, group.Groups, abstractions, group.DefineAsm, miscAsm)
        }
        c.BuildGroups = append(c.BuildGroups, name)
    }
    return content
}

func (c *Cbuild) CMakeCreateComponents(contextDir string) error {
    content := "# components.cmake\n"
    for _, component := range c.BuildDescType.Components {
        buildFiles := c.ClassifyFiles(component.Files)
        name := ReplaceDelimiters(component.Component)
        // default scope
        scope := "PUBLIC"
        if buildFiles.Interface {
            scope = "INTERFACE"
        }
        // add_library
        content += "\n# component " + component.Component
        content += CMakeAddLibrary(name, buildFiles)
        // target_include_directories
        if len(buildFiles.Include) > 0 {
            c.IncludeGlobal = AppendGlobalIncludes(c.IncludeGlobal, buildFiles.Include)
        }
        content += CMakeTargetIncludeDirectories(name, c.MergeIncludes(buildFiles.Include, scope, "${CONTEXT}", component.AddPath, component.AddPathAsm, component.DelPath))
        // target_compile_definitions
        content += CMakeTargetCompileDefinitions(name, "${CONTEXT}", scope, component.Define, component.Undefine)
        component.DefineAsm = utils.AppendDefines(component.DefineAsm, c.BuildDescType.DefineAsm)
        // compiler abstractions
        var libraries []string
        componentAbstractions := CompilerAbstractions{component.Debug, component.Optimize, component.Warnings, component.LanguageC, component.LanguageCpp}
        globalAbstractions := CompilerAbstractions{c.BuildDescType.Debug, c.BuildDescType.Optimize, c.BuildDescType.Warnings, c.BuildDescType.LanguageC, c.BuildDescType.LanguageCpp}
        languages := maps.Keys(buildFiles.Source)
        if !AreAbstractionsEmpty(componentAbstractions, languages) {
            abstractions := InheritCompilerAbstractions(globalAbstractions, componentAbstractions)
            content += c.CMakeTargetCompileOptionsAbstractions(name, abstractions, languages)
            libraries = append(libraries, name+"_ABSTRACTIONS")
        } else if !AreAbstractionsEmpty(globalAbstractions, languages) {
            libraries = append(libraries, "${CONTEXT}_ABSTRACTIONS")
        }
        // target_compile_options
        if len(buildFiles.Source) > 0 || len(buildFiles.Custom) > 0 {
            content += c.CMakeTargetCompileOptions(name, scope, component.Misc, buildFiles.PreIncludeLocal, "${CONTEXT}")
        }
        // target_link_libraries
        libraries = append(libraries, buildFiles.Library...)
        c.LibraryGlobal = append(c.LibraryGlobal, buildFiles.Library...)
        libraries = append(libraries, buildFiles.Object...)
        if len(libraries) > 0 {
            content += c.CMakeTargetLinkLibraries(name, scope, libraries...)
        }
        // asm defines are set in file properties
        for _, file := range component.Files {
            if strings.Contains(file.Category, "source") && GetLanguage(file) == "ASM" {
                file.DefineAsm = utils.AppendDefines(file.DefineAsm, component.DefineAsm)
                content += c.SetFileAsmDefines(file, utils.AppendUniquely(c.BuildDescType.Misc.ASM, component.Misc.ASM...))
            }
        }
        content += "\n"
    }

    filename := path.Join(contextDir, "components.cmake")
    err := utils.UpdateFile(filename, content)
    if err != nil {
        return err
    }

    return err
}

func (c *Cbuild) GetLinkLibraries() (libraries []string) {
    libraries = c.BuildDescType.Misc.Library
    // rescan libraries: special handling for GCC
    if c.Toolchain == "GCC" && (len(c.BuildDescType.Misc.Library)+len(c.LibraryGlobal)) > 1 {
        libraries = append(libraries, c.LibraryGlobal...)
        libraries = append([]string{"-Wl,--start-group"}, libraries...)
        libraries = append(libraries, "-Wl,--end-group")
    }
    return
}