Open-CMSIS-Pack/generator-bridge

View on GitHub
internal/stm32CubeMX/stm32CubeMX.go

Summary

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

package stm32cubemx

import (
    "errors"
    "fmt"
    "io/fs"
    "os"
    "os/exec"
    "path"
    "path/filepath"
    "runtime"
    "strings"
    "syscall"
    "time"

    "github.com/fsnotify/fsnotify"
    "github.com/open-cmsis-pack/generator-bridge/internal/cbuild"
    "github.com/open-cmsis-pack/generator-bridge/internal/common"
    "github.com/open-cmsis-pack/generator-bridge/internal/generator"
    "github.com/open-cmsis-pack/generator-bridge/internal/utils"
    log "github.com/sirupsen/logrus"
)

type BridgeParamType struct {
    BoardName         string
    BoardVendor       string
    Device            string
    Output            string
    ProjectName       string
    ProjectType       string
    ForProjectPart    string
    PairedSecurePart  string
    Compiler          string
    GeneratorMap      string
    CgenName          string
    CubeContext       string
    CubeContextFolder string
}

var watcher *fsnotify.Watcher
var running bool // true if running in wait loop waiting for .ioc file

var LogFile *os.File

func procWait(proc *os.Process) {
    if proc != nil {
        if runtime.GOOS == "windows" {
            _, err := proc.Wait()
            if err != nil {
                log.Infof("Cannot wait for CubeMX to end, err %v", err)
                return
            }
        } else {
            for {
                err := proc.Signal(syscall.Signal(0))
                if err != nil {
                    log.Infoln("Cannot Signal to CubeMX, is not running")
                    break
                }
                time.Sleep(time.Millisecond * 200)
            }
        }
        log.Debugln("CubeMX ended")
        if watcher != nil {
            watcher.Close()
            log.Debugln("Watcher closed")
        }
        running = false // cubeMX ended, do not wait for .ioc file anymore
    }
}

func Process(cbuildGenIdxYmlPath, outPath, cubeMxPath string, runCubeMx bool, pid int) error {
    var projectFile string

    cRoot := os.Getenv("CMSIS_COMPILER_ROOT")
    if len(cRoot) == 0 {
        ex, err := os.Executable()
        if err != nil {
            return err
        }
        exPath := filepath.Dir(ex)
        exPath = filepath.ToSlash(exPath)
        cRoot = path.Dir(exPath)
    }
    var generatorFile string
    err := filepath.Walk(cRoot, func(path string, f fs.FileInfo, err error) error {
        if f.Mode().IsRegular() && strings.Contains(path, "global.generator.yml") {
            generatorFile = path
            return nil
        }
        return nil
    })
    if err != nil {
        return err
    }
    if len(generatorFile) == 0 {
        return errors.New("config file 'global.generator.yml' not found")
    }

    var gParms generator.ParamsType
    err = ReadGeneratorYmlFile(generatorFile, &gParms)
    if err != nil {
        return err
    }

    var parms cbuild.ParamsType
    err = ReadCbuildGenIdxYmlFile(cbuildGenIdxYmlPath, "CubeMX", &parms)
    if err != nil {
        return err
    }

    var bridgeParams []BridgeParamType
    err = GetBridgeInfo(&parms, &bridgeParams)
    if err != nil {
        return err
    }

    workDir := path.Dir(cbuildGenIdxYmlPath)
    if parms.Output != "" {
        if filepath.IsAbs(parms.Output) {
            workDir = parms.Output
        } else {
            workDir = path.Join(workDir, parms.Output)
        }
    } else {
        workDir = path.Join(workDir, outPath)
    }
    workDir = filepath.Clean(workDir)
    workDir = filepath.ToSlash(workDir)

    err = os.MkdirAll(workDir, os.ModePerm)
    if err != nil {
        return err
    }

    cubeIocPath := workDir
    if pid >= 0 {
        lastPath := filepath.Base(cubeIocPath)
        if lastPath != "STM32CubeMX" {
            cubeIocPath = path.Join(cubeIocPath, "STM32CubeMX")
        }
        iocprojectPath := path.Join(cubeIocPath, "STM32CubeMX.ioc")
        mxprojectPath := path.Join(cubeIocPath, ".mxproject")
        log.Debugf("pid of CubeMX in daemon: %d", pid)
        running = true
        first := true
        iocProjectWait := false
        for {
            proc, err := os.FindProcess(pid) // this only works for windows as it is now
            if err == nil {                  // cubeMX already runs
                if runtime.GOOS != "windows" {
                    err = proc.Signal(syscall.Signal(0))
                    if err != nil {
                        break // out of loop if CubeMX does not run anymore
                    }
                }
                if first { // only start wait thread once
                    go procWait(proc)
                    first = false
                }
                if !running {
                    break // out of loop if CubeMX does not run anymore
                }
                stIOC, err := os.Stat(iocprojectPath)
                if err != nil { // .ioc file not (yet) there
                    iocProjectWait = true
                    time.Sleep(time.Second)
                    continue // stay in loop waiting for CubeMX end or change of .mxproject
                }
                if iocProjectWait { // .ioc file was created new, there will not be multiple changes
                    log.Debugln("new project file:", iocprojectPath)
                    i := 1
                    for ; i < 100; i++ { // wait for .mxproject coming
                        _, err := os.Stat(mxprojectPath)
                        if err == nil {
                            break // .mxproject appeared
                        }
                        time.Sleep(time.Second)
                    }
                    if i < 100 {
                        mxproject, err := IniReader(mxprojectPath, bridgeParams)
                        if err != nil {
                            continue // stay in loop waiting for CubeMX end or change of .mxproject
                        }
                        err = ReadContexts(iocprojectPath, bridgeParams)
                        if err != nil {
                            continue // stay in loop waiting for CubeMX end or change of .mxproject
                        }
                        err = WriteCgenYml(workDir, mxproject, bridgeParams)
                        if err != nil {
                            continue // stay in loop waiting for CubeMX end or change of .mxproject
                        }
                        iocProjectWait = false // reset wait for iocProject flag
                    }
                } else {
                    st, err := os.Stat(mxprojectPath)
                    if err != nil {
                        continue // stay in loop waiting for CubeMX end or change of .mxproject
                    }
                    fIoc, err := os.Open(mxprojectPath)
                    if err != nil {
                        continue // stay in loop waiting for CubeMX end or change of .mxproject
                    }
                    mxprojectBuf := make([]byte, st.Size())
                    _, err = fIoc.Read(mxprojectBuf)
                    if err != nil {
                        log.Fatal(err)
                    }
                    fIoc.Close()
                    for { // wait for .mxproject change
                        if !running {
                            break // out of loop if CubeMX does not run anymore
                        }
                        time.Sleep(time.Second)
                        stIOC1, err := os.Stat(iocprojectPath)
                        if err != nil { // .ioc file not (yet) there
                            break // continue loop waiting for CubeMX end or change of .mxproject
                        }
                        st1, err := os.Stat(mxprojectPath)
                        if err != nil {
                            break // continue loop waiting for CubeMX end or change of .mxproject
                        }
                        if stIOC.ModTime() != stIOC1.ModTime() && st.ModTime() != st1.ModTime() { // time changed
                            // it seems to me that the compare is superfluous because there are only in rare cases changes but the time always changes
                            if st.Size() == st1.Size() { // no change in length, compare content
                                fIoc, err := os.Open(mxprojectPath)
                                if err != nil {
                                    break // continue loop waiting for CubeMX end or change of .mxproject
                                }
                                mxprojectBuf1 := make([]byte, st1.Size())
                                _, err = fIoc.Read(mxprojectBuf1)
                                if err != nil {
                                    log.Fatal(err)
                                }
                                fIoc.Close()
                                // if bytes.Equal(mxprojectBuf, mxprojectBuf1) {
                                //    continue // wait for .mxproject change
                                // }
                            }
                            mxproject, err := IniReader(mxprojectPath, bridgeParams)
                            if err != nil {
                                break // continue loop waiting for CubeMX end or change of .mxproject
                            }
                            err = ReadContexts(iocprojectPath, bridgeParams)
                            if err != nil {
                                break // continue loop waiting for CubeMX end or change of .mxproject
                            }
                            log.Debugln("Writing Cgen.yml file")
                            err = WriteCgenYml(workDir, mxproject, bridgeParams)
                            if err != nil {
                                break // continue loop waiting for CubeMX end or change of .mxproject
                            }
                            break // leave inner loop reload all
                        }
                    }
                }
            } else {
                break // CubeMX does not run anymore
            }
        }
        // should only come here if CubeMX does not run anymore
    }

    if runCubeMx {
        lastPath := filepath.Base(cubeIocPath)
        if lastPath != "STM32CubeMX" {
            cubeIocPath = path.Join(cubeIocPath, "STM32CubeMX")
        }
        cubeIocPath = path.Join(cubeIocPath, "STM32CubeMX.ioc")

        var err error
        var pid int
        if utils.FileExists(cubeIocPath) {
            pid, err = Launch(cubeIocPath, "")
            if err != nil {
                return errors.New("generator '" + gParms.ID + "' missing. Install from '" + gParms.DownloadURL + "'")
            }
        } else {
            projectFile, err = WriteProjectFile(workDir, bridgeParams[0])
            if err != nil {
                return err
            }
            log.Debugf("Generated file: %v", projectFile)

            pid, err = Launch("", projectFile)
            if err != nil {
                return errors.New("generator '" + gParms.ID + "' missing. Install from '" + gParms.DownloadURL + "'")
            }
        }
        // here cubeMX runs
        exe, err := os.Executable()
        if err != nil {
            return err
        }
        ownPath, err := filepath.EvalSymlinks((exe))
        if err != nil {
            return err
        }
        cmd := exec.Command(ownPath) //nolint
        cmd.Args = os.Args
        cmd.Args = append(cmd.Args, "-p", fmt.Sprint(pid)) // pid of cubeMX
        log.Debugf("cmd.Start as %v", cmd)
        if err := cmd.Start(); err != nil { // start myself as a daemon
            log.Fatal(err)
            return err
        }
    }
    return nil
}

func Launch(iocFile, projectFile string) (int, error) {
    const cubeEnvVar = "STM32CubeMX_PATH"
    cubeEnv := os.Getenv(cubeEnvVar)
    if cubeEnv == "" {
        return -1, errors.New("environment variable for CubeMX not set: " + cubeEnvVar)
    }

    if iocFile != "" {
        log.Infoln("Launching STM32CubeMX with ", iocFile)
    } else if projectFile != "" {
        log.Infoln("Launching STM32CubeMX with -s ", projectFile)
    } else {
        log.Infoln("Launching STM32CubeMX...")
    }

    var pathJava string
    var arg0 string
    var arg1 string
    var pathCubeMx string
    var cmd *exec.Cmd

    switch runtime.GOOS {
    case "windows":
        pathJava = path.Join(cubeEnv, "jre", "bin", "java.exe")
        pathCubeMx = path.Join(cubeEnv, "STM32CubeMX.exe")
    case "darwin":
        pathJava = path.Join(cubeEnv, "jre", "Contents", "Home", "bin", "java")
        arg0 = path.Join(cubeEnv, "stm32cubemx.icns")
        arg0 = "-Xdock:icon=" + arg0
        arg1 = "-Xdock:name=STM32CubeMX"
        pathCubeMx = path.Join(cubeEnv, "STM32CubeMX")
    default:
        pathJava = path.Join(cubeEnv, "jre", "bin", "java")
        pathCubeMx = path.Join(cubeEnv, "STM32CubeMX")
    }

    if runtime.GOOS == "darwin" {
        if iocFile != "" {
            cmd = exec.Command(pathJava, arg0, arg1, "-jar", pathCubeMx, iocFile)
        } else if projectFile != "" {
            cmd = exec.Command(pathJava, arg0, arg1, "-jar", pathCubeMx, "-s", projectFile)
        } else {
            cmd = exec.Command(pathJava, arg0, arg1, "-jar", pathCubeMx)
        }
    } else {
        if iocFile != "" {
            cmd = exec.Command(pathJava, "-jar", pathCubeMx, iocFile)
        } else if projectFile != "" {
            cmd = exec.Command(pathJava, "-jar", pathCubeMx, "-s", projectFile)
        } else {
            cmd = exec.Command(pathJava, "-jar", pathCubeMx)
        }
    }
    log.Debugf("Start CubeMX as %v", cmd)
    if err := cmd.Start(); err != nil {
        log.Fatal(err)
        return -1, err
    }

    return cmd.Process.Pid, nil
}

func WriteProjectFile(workDir string, params BridgeParamType) (string, error) {
    filePath := filepath.Join(workDir, "project.script")
    log.Debugf("Writing CubeMX project file %v", filePath)

    var text utils.TextBuilder
    if params.BoardName != "" && params.BoardVendor == "STMicroelectronics" {
        text.AddLine("loadboard", params.BoardName, "allmodes")
    } else {
        text.AddLine("load", params.Device)
    }
    text.AddLine("project name", "STM32CubeMX")

    toolchain, err := GetToolchain(params.Compiler)
    if err != nil {
        return "", err
    }
    text.AddLine("project toolchain", utils.AddQuotes(toolchain))

    cubeWorkDir := workDir
    if runtime.GOOS == "windows" {
        cubeWorkDir = filepath.FromSlash(cubeWorkDir)
    }
    text.AddLine("project path", utils.AddQuotes(cubeWorkDir))
    text.AddLine("SetCopyLibrary", utils.AddQuotes("copy only"))

    if utils.FileExists(filePath) {
        os.Remove(filePath)
    }

    err = os.WriteFile(filePath, []byte(text.GetLine()), 0600)
    if err != nil {
        log.Errorf("Error writing %v", err)
        return "", err
    }

    return filePath, nil
}

func ReadCbuildGenIdxYmlFile(path, generatorID string, parms *cbuild.ParamsType) error {
    log.Debugf("Reading cbuild-gen-idx.yml file: '%v'", path)
    err := cbuild.Read(path, generatorID, parms)
    if err != nil {
        return err
    }

    return nil
}

func ReadGeneratorYmlFile(path string, parms *generator.ParamsType) error {
    log.Debugf("Reading generator.yml file: '%v'", path)
    err := generator.Read(path, parms)
    return err
}

func GetBridgeInfo(parms *cbuild.ParamsType, bridgeParams *[]BridgeParamType) error {
    var boardName string
    var boardVendor string
    var device string
    var output string
    var projectType string

    split := strings.Split(parms.Board, "::")
    if len(split) == 2 {
        boardVendor = split[0]
        boardName = split[1]
    } else {
        boardVendor = ""
        boardName = parms.Board
    }
    split = strings.Split(boardName, ":")
    if len(split) == 2 {
        boardName = split[0]
    }

    device = parms.Device
    projectType = parms.ProjectType
    output = parms.Output

    for _, gen := range parms.CbuildGens {
        var bparm BridgeParamType

        bparm.BoardName = boardName
        bparm.BoardVendor = boardVendor
        bparm.Device = device
        bparm.Output = output
        bparm.ProjectName = gen.Project
        bparm.ProjectType = projectType
        bparm.ForProjectPart = gen.ForProjectPart
        bparm.GeneratorMap = gen.Map
        bparm.CgenName = gen.Name
        compiler := gen.CbuildGen.BuildGen.Compiler
        compiler = strings.Split(compiler, "@")[0]
        bparm.Compiler = compiler
        if gen.Map != "" {
            bparm.CubeContext = gen.Map
            bparm.CubeContextFolder = gen.Map
        } else {
            switch parms.ProjectType {
            case "single-core":
                bparm.CubeContext = ""
                bparm.CubeContextFolder = ""
            case "multi-core":
                core := gen.CbuildGen.BuildGen.Processor.Core
                bparm.CubeContext = strings.ReplaceAll(core, "-", "")
                bparm.CubeContextFolder = "C" + strings.Split(core, "-")[1]
            case "trustzone":
                core := gen.CbuildGen.BuildGen.Processor.Core
                context := strings.ReplaceAll(core, "-", "")
                if gen.ForProjectPart == "non-secure" {
                    bparm.CubeContext = context + "NS"
                    bparm.CubeContextFolder = "NonSecure"
                    for _, tmpGen := range parms.CbuildGens {
                        if tmpGen.ForProjectPart == "secure" {
                            bparm.PairedSecurePart = tmpGen.Project
                            break
                        }
                    }
                }
                if gen.ForProjectPart == "secure" {
                    bparm.CubeContext = context + "S"
                    bparm.CubeContextFolder = "Secure"
                }
            }
        }
        *bridgeParams = append(*bridgeParams, bparm)
    }
    return nil
}

var filterFiles = map[string]string{
    "system_":                            "system_ file (already added)",
    "Templates":                          "Templates file (mostly not present)",
    "/STM32CubeMX/Drivers/CMSIS/Include": "CMSIS include folder (delivered by ARM::CMSIS)",
}

func FilterFile(file string) bool {
    for key, value := range filterFiles {
        if strings.Contains(file, key) {
            log.Debugf("ignoring %v: %v", value, file)
            return true
        }
    }

    return false
}

func FindMxProject(context string, mxprojectAll MxprojectAllType) (MxprojectType, error) {
    if len(mxprojectAll.Mxproject) == 0 {
        return MxprojectType{}, errors.New("no .mxproject read")
    } else if len(mxprojectAll.Mxproject) == 1 {
        mxproject := mxprojectAll.Mxproject[0]
        return mxproject, nil
    }

    for _, mxproject := range mxprojectAll.Mxproject {
        if mxproject.Context == context {
            return mxproject, nil
        }
    }

    return MxprojectType{}, nil
}

func WriteCgenYml(outPath string, mxprojectAll MxprojectAllType, bridgeParams []BridgeParamType) error {
    for _, parm := range bridgeParams {
        mxproject, err := FindMxProject(parm.CubeContext, mxprojectAll)
        if err != nil {
            continue
        }
        err = WriteCgenYmlSub(outPath, mxproject, parm)
        if err != nil {
            return err
        }
    }

    return nil
}

func WriteCgenYmlSub(outPath string, mxproject MxprojectType, bridgeParam BridgeParamType) error {
    var cgen cbuild.CgenType

    relativePathAdd, err := GetRelativePathAdd(outPath, bridgeParam.Compiler)
    if err != nil {
        return err
    }

    cgen.GeneratorImport.ForBoard = bridgeParam.BoardName
    cgen.GeneratorImport.ForDevice = bridgeParam.Device
    cgen.GeneratorImport.Define = append(cgen.GeneratorImport.Define, mxproject.PreviousUsedFiles.CDefines...)

    for _, headerPath := range mxproject.PreviousUsedFiles.HeaderPath {
        headerPath, _ = utils.ConvertFilename(outPath, headerPath, relativePathAdd)
        if FilterFile(headerPath) {
            continue
        }
        cgen.GeneratorImport.AddPath = append(cgen.GeneratorImport.AddPath, headerPath)
    }

    cfgPath := "MX_Device"
    if bridgeParam.CubeContextFolder != "" {
        cfgPath = path.Join(cfgPath, bridgeParam.CubeContextFolder)
    }
    cfgPath, _ = utils.ConvertFilename(outPath, cfgPath, "")
    cgen.GeneratorImport.AddPath = append(cgen.GeneratorImport.AddPath, cfgPath)

    var groupSrc cbuild.CgenGroupsType
    var groupHalDriver cbuild.CgenGroupsType
    var groupTz cbuild.CgenGroupsType

    groupSrc.Group = "CubeMX"
    groupHalDriver.Group = "STM32 HAL Driver"
    groupHalFilter := "HAL_Driver"

    for _, file := range mxproject.PreviousUsedFiles.SourceFiles {
        if FilterFile(file) {
            continue
        }
        file, _ = utils.ConvertFilename(outPath, file, relativePathAdd)
        if strings.Contains(file, groupHalFilter) {
            var cgenFile cbuild.CgenFilesType
            cgenFile.File = file
            groupHalDriver.Files = append(groupHalDriver.Files, cgenFile)
        } else {
            var cgenFile cbuild.CgenFilesType
            cgenFile.File = file
            groupSrc.Files = append(groupSrc.Files, cgenFile)
        }
    }

    var cgenFile cbuild.CgenFilesType
    startupFile, err := GetStartupFile(outPath, bridgeParam)
    if err != nil {
        return err
    }
    startupFile, err = utils.ConvertFilenameRel(outPath, startupFile)
    if err != nil {
        return err
    }
    cgenFile.File = startupFile
    groupSrc.Files = append(groupSrc.Files, cgenFile)

    systemFile, err := GetSystemFile(outPath, bridgeParam)
    if err != nil {
        return err
    }
    systemFile, err = utils.ConvertFilenameRel(outPath, systemFile)
    if err != nil {
        return err
    }
    cgenFile.File = systemFile
    groupSrc.Files = append(groupSrc.Files, cgenFile)

    // linkerFiles, err := GetLinkerScripts(outPath, bridgeParam)
    // if err != nil {
    //     return err
    // }
    // for _, file := range linkerFiles {
    //     file, err = utils.ConvertFilenameRel(outPath, file)
    //     if err != nil {
    //         return err
    //     }
    //     var cgenFile cbuild.CgenFilesType
    //     cgenFile.File = file
    //     groupSrc.Files = append(groupSrc.Files, cgenFile)
    // }

    cgen.GeneratorImport.Groups = append(cgen.GeneratorImport.Groups, groupSrc)
    cgen.GeneratorImport.Groups = append(cgen.GeneratorImport.Groups, groupHalDriver)

    if bridgeParam.ForProjectPart == "non-secure" {
        groupTz.Group = "CMSE Library"
        var cgenFile cbuild.CgenFilesType
        cgenFile.File = "$cmse-lib("
        cgenFile.File += bridgeParam.PairedSecurePart
        cgenFile.File += ")$"
        groupTz.Files = append(groupTz.Files, cgenFile)
        cgen.GeneratorImport.Groups = append(cgen.GeneratorImport.Groups, groupTz)
    }

    return common.WriteYml(bridgeParam.CgenName, &cgen)
}

func GetToolchain(compiler string) (string, error) {
    var toolchainMapping = map[string]string{
        "AC6":   "MDK-ARM V5",
        "GCC":   "STM32CubeIDE",
        "IAR":   "EWARM",
        "CLANG": "STM32CubeIDE",
    }

    toolchain, ok := toolchainMapping[compiler]
    if !ok {
        return "", errors.New("unknown compiler '" + compiler + "'")
    }
    return toolchain, nil
}

func GetRelativePathAdd(outPath string, compiler string) (string, error) {
    var pathMapping = map[string]string{
        "AC6":   "MDK-ARM",
        "GCC":   "",
        "IAR":   "EWARM",
        "CLANG": "",
    }

    folder, ok := pathMapping[compiler]
    if !ok {
        return "", errors.New("unknown compiler '" + compiler + "'")
    }

    lastPath := filepath.Base(outPath)
    var relativePathAdd string
    if lastPath != "STM32CubeMX" {
        relativePathAdd = path.Join(relativePathAdd, "STM32CubeMX")
    }
    relativePathAdd = path.Join(relativePathAdd, folder)

    return relativePathAdd, nil
}

func GetToolchainFolderPath(outPath string, compiler string) (string, error) {
    var toolchainFolderMapping = map[string]string{
        "AC6":   "MDK-ARM",
        "GCC":   "STM32CubeIDE",
        "IAR":   "EWARM",
        "CLANG": "STM32CubeIDE",
    }

    toolchainFolder, ok := toolchainFolderMapping[compiler]
    if !ok {
        return "", errors.New("unknown compiler '" + compiler + "'")
    }

    lastPath := filepath.Base(outPath)
    toolchainFolderPath := outPath
    if lastPath != "STM32CubeMX" {
        toolchainFolderPath = path.Join(outPath, "STM32CubeMX")
    }
    toolchainFolderPath = path.Join(toolchainFolderPath, toolchainFolder)

    return toolchainFolderPath, nil
}

func GetStartupFile(outPath string, bridgeParams BridgeParamType) (string, error) {
    var startupFolder string
    var fileExtesion string
    var fileFilter string

    startupFolder, err := GetToolchainFolderPath(outPath, bridgeParams.Compiler)
    if err != nil {
        return "", err
    }

    fileExtesion = ".s"
    switch bridgeParams.Compiler {
    case "AC6", "IAR":
        if bridgeParams.ProjectType == "multi-core" {
            fileFilter = "_" + bridgeParams.ForProjectPart
        }

    case "GCC", "CLANG":
        switch bridgeParams.ProjectType {
        case "multi-core":
            startupFolder = path.Join(startupFolder, bridgeParams.ForProjectPart)
        case "trustzone":
            if bridgeParams.ForProjectPart == "secure" {
                startupFolder = path.Join(startupFolder, "Secure")
            }
            if bridgeParams.ForProjectPart == "non-secure" {
                startupFolder = path.Join(startupFolder, "NonSecure")
            }
        }
        startupFolder = path.Join(startupFolder, "Application")
        startupFolder = path.Join(startupFolder, "Startup")

    default:
        return "", errors.New("unknown compiler")
    }

    if !utils.DirExists(startupFolder) {
        errorString := "Directory not found: " + startupFolder
        log.Error(errorString)
        return "", errors.New(errorString)
    }

    var startupFile string
    err = filepath.Walk(startupFolder, func(path string, f fs.FileInfo, err error) error {
        if f.Mode().IsRegular() &&
            strings.HasSuffix(f.Name(), fileExtesion) &&
            strings.HasPrefix(f.Name(), "startup_") {
            if fileFilter != "" {
                if strings.Contains(f.Name(), fileFilter) {
                    startupFile = path
                }
            } else {
                startupFile = path
            }
        }
        return nil
    })

    if startupFile == "" {
        errorString := "startup file not found"
        log.Error(errorString)
        return "", errors.New(errorString)
    }

    return startupFile, err
}

func GetSystemFile(outPath string, bridgeParams BridgeParamType) (string, error) {
    var toolchainFolder string
    var systemFolder string

    toolchainFolder, err := GetToolchainFolderPath(outPath, bridgeParams.Compiler)
    if err != nil {
        return "", err
    }

    if bridgeParams.ProjectType == "multi-core" {
        systemFolder = filepath.Dir(toolchainFolder)
        systemFolder = path.Join(systemFolder, "Common")
        systemFolder = path.Join(systemFolder, "Src")
        if !utils.DirExists(toolchainFolder) {
            systemFolder = ""
        }
    }

    if systemFolder == "" {
        systemFolder = filepath.Dir(toolchainFolder)

        if bridgeParams.GeneratorMap != "" {
            systemFolder = path.Join(systemFolder, bridgeParams.GeneratorMap)
        }

        switch bridgeParams.ProjectType {
        case "multi-core":
            systemFolder = path.Join(systemFolder, bridgeParams.ForProjectPart)
        case "trustzone":
            if bridgeParams.ForProjectPart == "secure" {
                systemFolder = path.Join(systemFolder, "Secure")
            }
            if bridgeParams.ForProjectPart == "non-secure" {
                systemFolder = path.Join(systemFolder, "NonSecure")
            }
        }
        systemFolder = path.Join(systemFolder, "Src")
    }

    if !utils.DirExists(systemFolder) {
        errorString := "Directory not found: " + systemFolder
        log.Error(errorString)
        return "", errors.New(errorString)
    }

    var systemFile string
    err = filepath.Walk(systemFolder, func(path string, f fs.FileInfo, err error) error {
        if f.Mode().IsRegular() &&
            strings.HasPrefix(f.Name(), "system_stm32") &&
            strings.HasSuffix(f.Name(), ".c") {
            systemFile = path
        }
        return nil
    })

    if systemFile == "" {
        errorString := "system file not found"
        log.Error(errorString)
        return "", errors.New(errorString)
    }

    return systemFile, err
}

// func GetLinkerScripts(outPath string, bridgeParams BridgeParamType) ([]string, error) {
//     var linkerFolder string
//     var fileExtesion string
//     var fileFilter string

//     linkerFolder, err := GetToolchainFolderPath(outPath, bridgeParams.Compiler)
//     if err != nil {
//         return nil, err
//     }

//     switch bridgeParams.Compiler {
//     case "AC6":
//         fileExtesion = ".sct"
//     case "IAR":
//         fileExtesion = ".icf"
//     case "GCC", "CLANG":
//         fileExtesion = ".ld"
//     default:
//         return nil, errors.New("unknown compiler '" + bridgeParams.Compiler + "'")
//     }

//     if bridgeParams.GeneratorMap != "" {
//         linkerFolder = path.Join(linkerFolder, bridgeParams.GeneratorMap)
//     }

//     switch bridgeParams.Compiler {
//     case "AC6", "IAR":
//         switch bridgeParams.ProjectType {
//         case "single-core":
//             fileFilter = ""
//         case "multi-core":
//             fileFilter = "_" + bridgeParams.ForProjectPart
//         case "trustzone":
//             if bridgeParams.ForProjectPart == "secure" {
//                 fileFilter = "_s."
//             }
//             if bridgeParams.ForProjectPart == "non-secure" {
//                 fileFilter = "_ns."
//             }
//         }

//     case "GCC", "CLANG":
//         switch bridgeParams.ProjectType {
//         case "multi-core":
//             linkerFolder = path.Join(linkerFolder, bridgeParams.ForProjectPart)
//         case "trustzone":
//             if bridgeParams.ForProjectPart == "secure" {
//                 linkerFolder = path.Join(linkerFolder, "Secure")
//             }
//             if bridgeParams.ForProjectPart == "non-secure" {
//                 linkerFolder = path.Join(linkerFolder, "NonSecure")
//             }
//         }
//     default:
//         return nil, errors.New("unknown compiler '" + bridgeParams.Compiler + "'")
//     }

//     if !utils.DirExists(linkerFolder) {
//         errorString := "Directory not found: " + linkerFolder
//         log.Error(errorString)
//         return nil, errors.New(errorString)
//     }

//     var linkerScripts []string
//     err = filepath.Walk(linkerFolder, func(path string, f fs.FileInfo, err error) error {
//         if f.Mode().IsRegular() && strings.HasSuffix(f.Name(), fileExtesion) {
//             if fileFilter != "" {
//                 if strings.Contains(f.Name(), fileFilter) {
//                     linkerScripts = append(linkerScripts, path)
//                 }
//             } else {
//                 linkerScripts = append(linkerScripts, path)
//             }
//         }
//         return nil
//     })

//     return linkerScripts, err
// }