Skip to content

Commit

Permalink
feat: add ability to find required ncs paths (#44)
Browse files Browse the repository at this point in the history
* feat: add ability to find required ncs paths

User can provide a base ncs path, and application will go through config file
and fetch necessary version.

Also adds option to skip env setup, if current one is already prepared by using NO_SETUP_ENV env variable.

* fix: update pin in example config
  • Loading branch information
ffenix113 authored Apr 7, 2024
1 parent b819148 commit eceecf6
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 22 deletions.
31 changes: 23 additions & 8 deletions cli/config/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"fmt"
"log"
"os"
"slices"
"strings"
Expand All @@ -23,6 +24,7 @@ type Device struct {

type General struct {
NCSToolChainBase string `yaml:"ncs_toolchain_base"`
NCSVersion string `yaml:"ncs_version"`
ZephyrBase string `yaml:"zephyr_base"`

Manufacturer string
Expand Down Expand Up @@ -50,12 +52,9 @@ type Board struct {
func ParseFromFile(configPath string) (*Device, error) {
cfg := &Device{
General: General{
RunEvery: time.Minute,
// Path for v2.5.0
// TODO: there is nice config file in ~/ncs/toolchains/toolchains.json
// which can be used to determine location of necessary toolchain.
NCSToolChainBase: "~/ncs/toolchains/7795df4459/",
ZephyrBase: "~/ncs/zephyr/",
RunEvery: time.Minute,
NCSToolChainBase: "~/ncs",
NCSVersion: "v2.5.0",
},
}

Expand Down Expand Up @@ -100,14 +99,30 @@ func (d *Device) PrependCommonClusters() {
func (g General) GetToochainsPath() (string, string) {
// If env variables are defined - they have higher priority.
ncsToolchainPath := os.Getenv("NCS_TOOLCHAIN_BASE")
ncsVersion := os.Getenv("NCS_VERSION")
zephyrPath := os.Getenv("ZEPHYR_BASE")

if ncsVersion == "" {
ncsVersion = g.NCSVersion
}

var locations NCSLocation
if ncsToolchainPath == "" || zephyrPath == "" {
var err error
locations, err = FindNCSLocation(g.NCSToolChainBase, ncsVersion)
if err != nil {
log.Panicf("find ncs location: %s", err.Error())
}

log.Printf("found toolchain version %q, requested version %q", locations.Version, ncsVersion)
}

if ncsToolchainPath == "" {
ncsToolchainPath = g.NCSToolChainBase
ncsToolchainPath = locations.NCS
}

if zephyrPath == "" {
zephyrPath = g.ZephyrBase
zephyrPath = locations.Zephyr
}

return ncsToolchainPath, zephyrPath
Expand Down
114 changes: 114 additions & 0 deletions cli/config/ncs_finder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package config

import (
"encoding/json"
"fmt"
"log"
"os"
"path"
"slices"

"golang.org/x/exp/maps"
)

type NCSLocation struct {
Version string
NCS string
Zephyr string
}

type toolchainItem struct {
Identifier struct {
BundleID string `json:"bundle_id"`
} `json:"identifier"`
NCSVersions []string `json:"ncs_versions"`
}

type toolchainTopLevelItem struct {
DefaultToolchain map[string]string `json:"default_toolchain"`
Toolchains []toolchainItem `json:"toolchains"`
}

// FindNCSLocation will return paths for NCS and Zephyr toolchains.
//
// If toolchain of required version was not found - it will try to
// use default version from toolchain file, and if it is not present -
// latest available in list of toolchains.
//
// As such this function can return different toolchain version that
// was requested, and caller can check it by comparing to version
// returned in NCSLocation.
func FindNCSLocation(ncsBase, version string) (NCSLocation, error) {
toolchainsJson := toolchainConfigPath(ncsBase)

configFile, err := os.Open(toolchainsJson)
if err != nil {
return NCSLocation{}, fmt.Errorf("open toolchains.json file at %q: %w", toolchainsJson, err)
}

var toolchainFile []toolchainTopLevelItem

err = json.NewDecoder(configFile).Decode(&toolchainFile)
if err != nil {
return NCSLocation{}, fmt.Errorf("decode toolchain file: %w", err)
}

if len(toolchainFile) == 0 {
return NCSLocation{}, fmt.Errorf("toolchain file does not contain definitions")
}

first := toolchainFile[0]

return providePaths(ncsBase, version, first)
}

func providePaths(ncsBase, version string, toolchainItem toolchainTopLevelItem) (NCSLocation, error) {
versionToIdentifier := mapVersions(toolchainItem)

availableVersions := maps.Keys(versionToIdentifier)
if len(availableVersions) == 0 {
return NCSLocation{}, fmt.Errorf("no toolchain versions found in toolchain configuration path %q", toolchainConfigPath(ncsBase))
}

log.Printf("available toolchain versions: %v", availableVersions)

bundleID := versionToIdentifier[version]

if bundleID == "" {
version = toolchainItem.DefaultToolchain["ncs_version"]
bundleID = versionToIdentifier[version]
}

if bundleID == "" {
log.Println("toolchain config does not provide default version, and required version was not found, falling back to latest version in config")

slices.Sort(availableVersions)
version = availableVersions[len(availableVersions)-1]

bundleID = versionToIdentifier[version]
}

return constructPaths(ncsBase, version, bundleID), nil
}

func mapVersions(toolchainItem toolchainTopLevelItem) map[string]string {
mapped := make(map[string]string, len(toolchainItem.Toolchains))

for _, toolchain := range toolchainItem.Toolchains {
mapped[toolchain.NCSVersions[0]] = toolchain.Identifier.BundleID
}

return mapped
}

func constructPaths(ncsBase, version, bundleID string) NCSLocation {
return NCSLocation{
Version: version,
NCS: path.Join(ncsBase, "toolchains", bundleID),
Zephyr: path.Join(ncsBase, version, "zephyr"),
}
}

func toolchainConfigPath(ncsBase string) string {
return path.Join(ncsBase, "toolchains", "toolchains.json")
}
24 changes: 15 additions & 9 deletions cli/runner/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package runner
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"path"
Expand Down Expand Up @@ -77,7 +78,13 @@ func WithWorkDir(workDir string) CmdOpt {
// WithToolchainPath updates environment of command
// to inlcude necessary variables for building firmware.
func WithToolchainPath(ncsToolchainBase, zephyrBase string) CmdOpt {
if ncsToolchainBase == "" || zephyrBase == "" {
// For now check that we don't want to setup env here,
// and move it to CLI ASAP.
// This could be useful if run inside environment that
// is already set up properly.
if noSetupEnv() || ncsToolchainBase == "" || zephyrBase == "" {
log.Println("environment will not be prepared because either one of the paths is empty, or requested not to")

return func(c *exec.Cmd) {}
}

Expand Down Expand Up @@ -107,14 +114,9 @@ func extendEnv(ncsToolchainPath string, zephyrPath string) []string {
envPath := os.Getenv("PATH")
combinedPath := ncsCombinedPath
if envPath != "" {
combinedPath += ":" + envPath
combinedPath += string(os.PathListSeparator) + envPath
}

pythonPath := generateEnvArray(ncsToolchainPath, []string{
"/usr/local/lib/python3.8",
"/usr/local/lib/python3.8/site-packages",
})

ldLibraryPath := generateEnvArray(ncsToolchainPath, []string{
"/usr/lib",
"/usr/lib/x86_64-linux-gnu",
Expand All @@ -126,8 +128,6 @@ func extendEnv(ncsToolchainPath string, zephyrPath string) []string {
"ZEPHYR_BASE=" + zephyrPath,
"ZEPHYR_SDK_INSTALL_DIR=" + path.Join(ncsToolchainPath, "/opt/zephyr-sdk"),
"ZEPHYR_TOOLCHAIN_VARIANT=zephyr",
"PYTHOHOME=" + path.Join(ncsCombinedPath, "/usr/local"),
"PYTHONPATH=" + pythonPath,
"LD_LIBRARY_PATH=" + ldLibraryPath,
}
}
Expand Down Expand Up @@ -160,3 +160,9 @@ func updateCurrentPath(envs []string) {
os.Setenv("PATH", envPath)
}
}

func noSetupEnv() bool {
_, ok := os.LookupEnv("NO_SETUP_ENV")

return ok
}
9 changes: 4 additions & 5 deletions cli/zigbee.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,10 @@ board:
rx: 1.10
leds:
- id: led_green
# The `pin` section is optional, if led is already present in board definition.
pin:
port: 0
pin: 6
inverted: true
# The pin definition is optional, if led is already present in board definition.
port: 0
pin: 6
inverted: true

sensors:
# - type: bme680
Expand Down

0 comments on commit eceecf6

Please sign in to comment.