diff --git a/cmd/cbuild/commands/list/list.go b/cmd/cbuild/commands/list/list.go index 46c1017e..71c4b2cb 100644 --- a/cmd/cbuild/commands/list/list.go +++ b/cmd/cbuild/commands/list/list.go @@ -30,5 +30,10 @@ func init() { _ = command.Flags().MarkHidden("toolchain") command.Parent().HelpFunc()(command, strings) }) - ListCmd.AddCommand(ListConfigurationsCmd, ListContextsCmd, ListToolchainsCmd) + ListEnvironmentCmd.SetHelpFunc(func(command *cobra.Command, strings []string) { + _ = command.Flags().MarkHidden("schema") + _ = command.Flags().MarkHidden("toolchain") + command.Parent().HelpFunc()(command, strings) + }) + ListCmd.AddCommand(ListConfigurationsCmd, ListContextsCmd, ListToolchainsCmd, ListEnvironmentCmd) } diff --git a/cmd/cbuild/commands/list/list_environment.go b/cmd/cbuild/commands/list/list_environment.go new file mode 100644 index 00000000..1174ad97 --- /dev/null +++ b/cmd/cbuild/commands/list/list_environment.go @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package list + +import ( + "cbuild/pkg/builder" + "cbuild/pkg/builder/csolution" + "cbuild/pkg/utils" + + "github.com/spf13/cobra" +) + +var ListEnvironmentCmd = &cobra.Command{ + Use: "environment", + Short: "Print list of environment configurations", + Args: cobra.MaximumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + configs, err := utils.GetInstallConfigs() + if err != nil { + return err + } + + p := csolution.CSolutionBuilder{ + BuilderParams: builder.BuilderParams{ + Runner: utils.Runner{}, + InstallConfigs: configs, + }, + } + return p.ListEnvironment() + }, +} diff --git a/cmd/cbuild/commands/list/list_environment_test.go b/cmd/cbuild/commands/list/list_environment_test.go new file mode 100644 index 00000000..428283cc --- /dev/null +++ b/cmd/cbuild/commands/list/list_environment_test.go @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package list_test + +import ( + "cbuild/cmd/cbuild/commands" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestListEnvironmentCommand(t *testing.T) { + assert := assert.New(t) + + t.Run("invalid args", func(t *testing.T) { + cmd := commands.NewRootCmd() + cmd.SetArgs([]string{"list", "environment", "--invalid"}) + err := cmd.Execute() + assert.Error(err) + }) + + t.Run("test list environment", func(t *testing.T) { + cmd := commands.NewRootCmd() + cmd.SetArgs([]string{"list", "environment"}) + err := cmd.Execute() + assert.Error(err) + }) + + t.Run("test help", func(t *testing.T) { + cmd := commands.NewRootCmd() + cmd.SetArgs([]string{"list", "environment", "-h"}) + err := cmd.Execute() + assert.Nil(err) + }) +} diff --git a/pkg/builder/csolution/builder.go b/pkg/builder/csolution/builder.go index e7df790b..038a98f6 100644 --- a/pkg/builder/csolution/builder.go +++ b/pkg/builder/csolution/builder.go @@ -14,6 +14,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "sort" "strconv" "strings" @@ -293,6 +294,59 @@ func (b CSolutionBuilder) listToolchains(quiet bool) (toolchains []string, err e return toolchains, nil } +func (b CSolutionBuilder) listEnvironment(quiet bool) (envConfigs []string, err error) { + // get installed exe path and version number + getInstalledExeInfo := func(name string) string { + path, err := utils.GetInstalledExePath(name) + if err != nil || path == "" { + return "" + } + + // run "exe --version" command + versionStr, err := b.Runner.ExecuteCommand(path, true, "--version") + if err != nil { + versionStr = "" + } + + // get version + var version string + if name == "cmake" { + regex := "version\\s(.*?)\\s" + re, err := regexp.Compile(regex) + if err == nil { + match := re.FindAllStringSubmatch(versionStr, 1) + for index := range match { + version = match[index][1] + break + } + } + } else { + version = versionStr + } + info := path + if version != "" { + info += ", version " + version + } + return info + } + + // step1: call csolution list environment + args := []string{"list", "environment"} + output, err := b.runCSolution(args, quiet) + if err != nil { + return + } + if output != "" { + envConfigs = strings.Split(strings.ReplaceAll(strings.TrimSpace(output), "\r\n", "\n"), "\n") + } + + // step2: add other environment info + envConfigs = append(envConfigs, "cmake="+getInstalledExeInfo("cmake")) + envConfigs = append(envConfigs, "ninja="+getInstalledExeInfo("ninja")) + + return envConfigs, nil +} + func (b CSolutionBuilder) ListConfigurations() error { configurations, err := b.listConfigurations() if err != nil { @@ -312,6 +366,17 @@ func (b CSolutionBuilder) ListToolchains() error { return err } +func (b CSolutionBuilder) ListEnvironment() error { + envConfigs, err := b.listEnvironment(true) + if err != nil { + return err + } + for _, config := range envConfigs { + fmt.Println(config) + } + return nil +} + func (b CSolutionBuilder) Build() (err error) { _ = utils.UpdateEnvVars(b.InstallConfigs.BinPath, b.InstallConfigs.EtcPath) diff --git a/pkg/builder/csolution/builder_test.go b/pkg/builder/csolution/builder_test.go index ecebc1fb..1ea21bd2 100644 --- a/pkg/builder/csolution/builder_test.go +++ b/pkg/builder/csolution/builder_test.go @@ -36,6 +36,8 @@ func (r RunnerMock) ExecuteCommand(program string, quiet bool, args ...string) ( return "AC5@5.6.7\nAC6@6.18.0\nGCC@11.2.1\nIAR@8.50.6\n", nil } else if args[1] == "packs" { return "ARM::test:0.0.1\r\nARM::test2:0.0.2", nil + } else if args[1] == "environment" { + return "CMSIS_PACK_ROOT=C:/Path/Packs\nCMSIS_COMPILER_ROOT=C:/Test/etc\n", nil } } else if args[0] == "convert" { return "", nil @@ -271,6 +273,45 @@ func TestListToolchians(t *testing.T) { }) } +func TestListEnvironment(t *testing.T) { + assert := assert.New(t) + os.Setenv("CMSIS_BUILD_ROOT", testRoot+"/run/bin") + configs, err := utils.GetInstallConfigs() + assert.Nil(err) + b := CSolutionBuilder{ + BuilderParams: builder.BuilderParams{ + Runner: RunnerMock{}, + InstallConfigs: configs, + }, + } + + t.Run("test list environment", func(t *testing.T) { + envConfigs, err := b.listEnvironment(true) + assert.Nil(err) + assert.Equal(len(envConfigs), 4) + assert.Equal("CMSIS_PACK_ROOT=C:/Path/Packs", envConfigs[0]) + assert.Equal("CMSIS_COMPILER_ROOT=C:/Test/etc", envConfigs[1]) + assert.Regexp(`^cmake=([^\s]+)`, envConfigs[2]) + assert.Regexp(`^ninja=([^\s]+)`, envConfigs[3]) + }) + + t.Run("test list environment fails to detect", func(t *testing.T) { + // set empty install config, when cbuild is run standalone (without installation env) + b.InstallConfigs = utils.Configurations{} + envConfigs, err := b.listEnvironment(true) + assert.Error(err) + assert.Equal(len(envConfigs), 0) + // restore install config + b.InstallConfigs = configs + }) + + t.Run("test list environment", func(t *testing.T) { + err := b.ListEnvironment() + assert.Nil(err) + }) + +} + func TestBuild(t *testing.T) { assert := assert.New(t) os.Setenv("CMSIS_BUILD_ROOT", testRoot+"/run/bin") diff --git a/pkg/utils/configs.go b/pkg/utils/configs.go index 3fefd2d4..124ddc80 100644 --- a/pkg/utils/configs.go +++ b/pkg/utils/configs.go @@ -7,11 +7,10 @@ package utils import ( + "errors" "os" "path/filepath" "runtime" - - log "github.com/sirupsen/logrus" ) type Configurations struct { @@ -28,8 +27,7 @@ func GetInstallConfigs() (configs Configurations, err error) { if binPath == "" { binPath, err = GetExecutablePath() if err != nil { - log.Error("executable path was not found") - return configs, err + return Configurations{}, err } } if binPath != "" { @@ -38,9 +36,9 @@ func GetInstallConfigs() (configs Configurations, err error) { configs.BinPath = binPath etcPath := filepath.Clean(binPath + "/../etc") - if _, err := os.Stat(etcPath); os.IsNotExist(err) { - log.Error("etc directory was not found") - return configs, err + if _, err = os.Stat(etcPath); os.IsNotExist(err) { + err = errors.New(etcPath + " path was not found") + return Configurations{}, err } if etcPath != "" { etcPath, _ = filepath.Abs(etcPath) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index d876279e..da11af7d 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -9,6 +9,7 @@ package utils import ( "errors" "os" + "os/exec" "path/filepath" "runtime" "strings" @@ -336,3 +337,11 @@ func Contains[T comparable](slice []T, elem T) bool { } return false } + +func GetInstalledExePath(exeName string) (path string, err error) { + path, err = exec.LookPath(exeName) + if strings.Contains(path, "\\") { + path = strings.ReplaceAll(path, "\\", "/") + } + return +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 6134e5bb..0f840143 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -302,3 +302,12 @@ func TestContains(t *testing.T) { assert.Equal(output, test.expectedResult) } } + +func TestGetInstalledExePath(t *testing.T) { + assert := assert.New(t) + t.Run("test to get invalid executable path", func(t *testing.T) { + path, err := GetInstalledExePath("testunknown") + assert.Equal(path, "") + assert.Error(err) + }) +}