Open-CMSIS-Pack/cbuild

View on GitHub
pkg/builder/cproject/builder.go

Summary

Maintainability
A
1 hr
Test Coverage
C
76%
/*
 * Copyright (c) 2022-2024 Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

package cproject

import (
    "errors"
    "fmt"
    "os"
    "path/filepath"
    "strings"

    builder "github.com/Open-CMSIS-Pack/cbuild/v2/pkg/builder"
    "github.com/Open-CMSIS-Pack/cbuild/v2/pkg/errutils"
    log "github.com/Open-CMSIS-Pack/cbuild/v2/pkg/logger"
    utils "github.com/Open-CMSIS-Pack/cbuild/v2/pkg/utils"
)

type CprjBuilder struct {
    builder.BuilderParams
}

func (b CprjBuilder) checkCprj() error {
    fileExtension := filepath.Ext(b.InputFile)
    expectedExtension := ".cprj"
    if fileExtension != expectedExtension {
        return errutils.New(errutils.ErrInvalidFileExtension, fileExtension, expectedExtension)
    } else {
        if _, err := os.Stat(b.InputFile); os.IsNotExist(err) {
            return err
        }
    }
    return nil
}

func (b CprjBuilder) clean(dirs builder.BuildDirs, vars builder.InternalVars) (err error) {
    if _, err := os.Stat(dirs.IntDir); !os.IsNotExist(err) {
        _, err = b.Runner.ExecuteCommand(vars.CbuildgenBin, false, "rmdir", dirs.IntDir)
        if err != nil {
            return err
        }
    }
    if _, err := os.Stat(dirs.OutDir); !os.IsNotExist(err) {
        _, err = b.Runner.ExecuteCommand(vars.CbuildgenBin, false, "rmdir", dirs.OutDir)
        if err != nil {
            return err
        }
    }
    log.Info("clean finished successfully!")
    return nil
}

func (b CprjBuilder) getDirs() (dirs builder.BuildDirs, err error) {
    if b.Options.IntDir != "" {
        dirs.IntDir = b.Options.IntDir
    }
    if b.Options.OutDir != "" {
        dirs.OutDir = b.Options.OutDir
    }

    if b.Options.Output != "" {
        dirs.IntDir = ""
        dirs.OutDir = ""
    }

    intDir, outDir, err := GetCprjDirs(b.InputFile)
    if err != nil {
        return dirs, err
    }
    if dirs.IntDir == "" {
        dirs.IntDir = intDir
        if dirs.IntDir == "" {
            dirs.IntDir = "IntDir"
        }
        if !filepath.IsAbs(dirs.IntDir) {
            dirs.IntDir = filepath.Join(filepath.Dir(b.InputFile), dirs.IntDir)
        }
    }
    if dirs.OutDir == "" {
        dirs.OutDir = outDir
        if dirs.OutDir == "" {
            dirs.OutDir = "OutDir"
        }
        if !filepath.IsAbs(dirs.OutDir) {
            dirs.OutDir = filepath.Join(filepath.Dir(b.InputFile), dirs.OutDir)
        }
    }

    dirs.IntDir, _ = filepath.Abs(dirs.IntDir)
    dirs.OutDir, _ = filepath.Abs(dirs.OutDir)

    log.Debug("dirs.IntDir: " + dirs.IntDir)
    log.Debug("dirs.OutDir: " + dirs.OutDir)

    return dirs, err
}

func (b CprjBuilder) build() error {
    b.InputFile, _ = filepath.Abs(b.InputFile)
    b.InputFile = utils.NormalizePath(b.InputFile)

    err := b.checkCprj()
    if err != nil {
        return err
    }

    dirs, err := b.getDirs()
    if err != nil {
        return err
    }

    vars, err := b.GetInternalVars()
    if err != nil {
        return err
    }

    _ = utils.UpdateEnvVars(vars.BinPath, vars.EtcPath)

    if b.Options.Rebuild {
        err = b.clean(dirs, vars)
        if err != nil {
            return err
        }
    } else if b.Options.Clean {
        err = b.clean(dirs, vars)
        return err
    }

    if b.Options.Schema {
        if vars.XmllintBin == "" {
            log.Warn("xmllint was not found, proceed without xml validation")
        } else {
            _, err = b.Runner.ExecuteCommand(vars.XmllintBin, b.Options.Quiet, "--schema", filepath.Join(vars.EtcPath, "CPRJ.xsd"), b.InputFile, "--noout")
            if err != nil {
                return err
            }
        }
    }

    cprjFilename := filepath.Base(b.InputFile)
    cprjFilename = strings.TrimSuffix(cprjFilename, filepath.Ext(cprjFilename))
    packlistFile := filepath.Join(dirs.IntDir, cprjFilename+".cpinstall")
    log.Debug("vars.packlistFile: " + packlistFile)
    _ = os.Remove(packlistFile)
    _ = os.MkdirAll(dirs.IntDir, 0755)

    var args []string
    args = []string{"packlist", b.InputFile, "--outdir=" + dirs.OutDir, "--intdir=" + dirs.IntDir}
    if b.Options.Quiet {
        args = append(args, "--quiet")
    }
    if b.Options.UpdateRte {
        args = append(args, "--update-rte")
    }
    _, err = b.Runner.ExecuteCommand(vars.CbuildgenBin, false, args...)
    if err != nil {
        return err
    }

    if _, err := os.Stat(packlistFile); !os.IsNotExist(err) {
        if b.Options.Packs {
            if vars.CpackgetBin == "" {
                err = errutils.New(errutils.ErrBinaryNotFound,
                    "cpackget", "in "+vars.BinPath+". Missing packs cannot be installed")
                return err
            }
            args = []string{"add", "--agree-embedded-license", "--no-dependencies", "--packs-list-filename", packlistFile}
            if b.Options.Debug {
                args = append(args, "--verbose")
            } else if b.Options.Quiet {
                args = append(args, "--quiet")
            }
            _, err = b.Runner.ExecuteCommand(vars.CpackgetBin, b.Options.Quiet, args...)
            if err != nil {
                return err
            }
        } else {
            err = errutils.New(errutils.ErrMissingPacks)
            return err
        }
    }

    // no CMake orchestration needed
    if b.Options.NoDatabase {
        log.Info("setup finished successfully!")
        return nil
    }

    args = []string{"cmake", b.InputFile, "--outdir=" + dirs.OutDir, "--intdir=" + dirs.IntDir}
    if b.Options.Quiet {
        args = append(args, "--quiet")
    }
    if b.Options.LockFile != "" {
        lockFile, _ := filepath.Abs(b.Options.LockFile)
        args = append(args, "--update="+lockFile)
    }

    if b.Options.Debug {
        log.Debug("cbuildgen command: " + vars.CbuildgenBin + " " + strings.Join(args, " "))
    }

    _, err = b.Runner.ExecuteCommand(vars.CbuildgenBin, false, args...)
    if err != nil {
        return err
    }

    if _, err := os.Stat(dirs.IntDir + "/CMakeLists.txt"); errors.Is(err, os.ErrNotExist) {
        return err
    }

    if vars.CmakeBin == "" {
        err = errutils.New(errutils.ErrBinaryNotFound, "cmake", "")
        return err
    }

    if b.Options.Generator == "" {
        b.Options.Generator = "Ninja"
        if vars.NinjaBin == "" {
            err = errutils.New(errutils.ErrBinaryNotFound, "ninja", "")
            return err
        }
    }

    args = []string{"-G", b.Options.Generator, "-S", dirs.IntDir, "-B", dirs.IntDir}
    if b.Options.Debug {
        args = append(args, "-Wdev")
    } else {
        args = append(args, "-Wno-dev")
    }

    if b.Options.Debug {
        log.Debug("cmake configuration command: " + vars.CmakeBin + " " + strings.Join(args, " "))
    }

    _, err = b.Runner.ExecuteCommand(vars.CmakeBin, b.Options.Quiet, args...)
    if err != nil {
        return err
    }

    args = []string{"--build", dirs.IntDir, "-j", fmt.Sprintf("%d", b.Options.Jobs)}

    if b.Setup {
        args = append(args, "--target", "database")
    }

    if b.Options.Target != "" {
        args = append(args, "--target", b.Options.Target)
    }
    if b.Options.Debug || b.Options.Verbose {
        args = append(args, "--verbose")
    }

    if b.Options.Debug {
        log.Debug("cmake build command: " + vars.CmakeBin + " " + strings.Join(args, " "))
    }

    _, err = b.Runner.ExecuteCommand(vars.CmakeBin, false, args...)
    if err != nil {
        return err
    }

    operation := "build"
    if b.Setup {
        operation = "setup"
    }
    log.Info(operation + " finished successfully!")
    return nil
}

func (b CprjBuilder) Build() (err error) {
    if err = b.build(); err != nil {
        log.Error(err)
    }
    return err
}