Skip to content

Commit

Permalink
Pluggable discovery: board identification can now identify also custo…
Browse files Browse the repository at this point in the history
…m board options (#1674)

* Added test for os-specific config options

* Build board config options structures only once and cache them

* Board's build options properties are now calculated only once and cached

* Added tests for config options ordering

It required insertion of test data with the properties.Set method to
preserve ordering.

* Renamed some variables to improve code readability

* Added board config identification subroutines

* Added board config detection in 'commands.identify' function

* Updated docs

* Apply suggestions from code review

Co-authored-by: per1234 <[email protected]>

* Fixed comment

Co-authored-by: per1234 <[email protected]>
  • Loading branch information
cmaglie and per1234 committed Oct 18, 2022
1 parent 6f14510 commit 0d8ce36
Show file tree
Hide file tree
Showing 6 changed files with 565 additions and 279 deletions.
127 changes: 88 additions & 39 deletions arduino/cores/board.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type Board struct {
BoardID string
Properties *properties.Map `json:"-"`
PlatformRelease *PlatformRelease `json:"-"`
configOptions *properties.Map
configOptionValues map[string]*properties.Map
configOptionProperties map[string]*properties.Map
defaultConfig *properties.Map
identificationProperties []*properties.Map
}

Expand Down Expand Up @@ -64,66 +68,74 @@ func (b *Board) String() string {
return b.FQBN()
}

func (b *Board) buildConfigOptionsStructures() {
if b.configOptions != nil {
return
}

b.configOptions = properties.NewMap()
allConfigs := b.Properties.SubTree("menu")
for _, option := range allConfigs.FirstLevelKeys() {
b.configOptions.Set(option, b.PlatformRelease.Menus.Get(option))
}

b.configOptionValues = map[string]*properties.Map{}
b.configOptionProperties = map[string]*properties.Map{}
b.defaultConfig = properties.NewMap()
for option, optionProps := range allConfigs.FirstLevelOf() {
b.configOptionValues[option] = properties.NewMap()
values := optionProps.FirstLevelKeys()
b.defaultConfig.Set(option, values[0])
for _, value := range values {
if label, ok := optionProps.GetOk(value); ok {
b.configOptionValues[option].Set(value, label)
b.configOptionProperties[option+"="+value] = optionProps.SubTree(value)
}
}
}
}

// GetConfigOptions returns an OrderedMap of configuration options for this board.
// The returned map will have key and value as option id and option name, respectively.
func (b *Board) GetConfigOptions() *properties.Map {
res := properties.NewMap()
menu := b.Properties.SubTree("menu")
for _, option := range menu.FirstLevelKeys() {
res.Set(option, b.PlatformRelease.Menus.Get(option))
}
return res
b.buildConfigOptionsStructures()
return b.configOptions
}

// GetConfigOptionValues returns an OrderedMap of possible values for a specific configuratio options
// for this board. The returned map will have key and value as option value and option value name,
// respectively.
func (b *Board) GetConfigOptionValues(option string) *properties.Map {
res := properties.NewMap()
menu := b.Properties.SubTree("menu").SubTree(option)
for _, value := range menu.FirstLevelKeys() {
if label, ok := menu.GetOk(value); ok {
res.Set(value, label)
}
}
return res
b.buildConfigOptionsStructures()
return b.configOptionValues[option]
}

// GetBuildProperties returns the build properties and the build
// platform for the Board with the configuration passed as parameter.
func (b *Board) GetBuildProperties(userConfigs *properties.Map) (*properties.Map, error) {
// Clone user configs because they are destroyed during iteration
userConfigs = userConfigs.Clone()
b.buildConfigOptionsStructures()

// Override default configs with user configs
config := b.defaultConfig.Clone()
config.Merge(userConfigs)

// Start with board's base properties
buildProperties := b.Properties.Clone()

// Add all sub-configurations one by one (a config is: option=value)
menu := b.Properties.SubTree("menu")
for _, option := range menu.FirstLevelKeys() {
optionMenu := menu.SubTree(option)
userValue, haveUserValue := userConfigs.GetOk(option)
if haveUserValue {
userConfigs.Remove(option)
if !optionMenu.ContainsKey(userValue) {
return nil, fmt.Errorf(tr("invalid value '%[1]s' for option '%[2]s'"), userValue, option)
}
} else {
// apply default
userValue = optionMenu.FirstLevelKeys()[0]
}

optionsConf := optionMenu.SubTree(userValue)
buildProperties.Merge(optionsConf)
}

// Check for residual invalid options...
if invalidKeys := userConfigs.Keys(); len(invalidKeys) > 0 {
invalidOption := invalidKeys[0]
if invalidOption == "" {
for option, value := range config.AsMap() {
if option == "" {
return nil, fmt.Errorf(tr("invalid empty option found"))
}
return nil, fmt.Errorf(tr("invalid option '%s'"), invalidOption)
if _, ok := b.configOptions.GetOk(option); !ok {
return nil, fmt.Errorf(tr("invalid option '%s'"), option)
}
optionsConf, ok := b.configOptionProperties[option+"="+value]
if !ok {
return nil, fmt.Errorf(tr("invalid value '%[1]s' for option '%[2]s'"), value, option)
}
buildProperties.Merge(optionsConf)
}

return buildProperties, nil
Expand Down Expand Up @@ -153,7 +165,7 @@ func (b *Board) GetIdentificationProperties() []*properties.Map {
}

// IsBoardMatchingIDProperties returns true if the board match the given
// identification properties
// upload port identification properties
func (b *Board) IsBoardMatchingIDProperties(query *properties.Map) bool {
// check checks if the given set of properties p match the "query"
check := func(p *properties.Map) bool {
Expand All @@ -179,3 +191,40 @@ func (b *Board) IsBoardMatchingIDProperties(query *properties.Map) bool {
func GetMonitorSettings(protocol string, boardProperties *properties.Map) *properties.Map {
return boardProperties.SubTree("monitor_port." + protocol)
}

// IdentifyBoardConfiguration returns the configuration of the board that can be
// deduced from the given upload port identification properties
func (b *Board) IdentifyBoardConfiguration(query *properties.Map) *properties.Map {
// check checks if the given set of properties p match the "query"
check := func(p *properties.Map) bool {
for k, v := range p.AsMap() {
if !strings.EqualFold(query.Get(k), v) {
return false
}
}
return true
}
checkAll := func(allP []*properties.Map) bool {
for _, p := range allP {
if check(p) {
return true
}
}
return false
}

res := properties.NewMap()
for _, option := range b.GetConfigOptions().Keys() {
values := b.GetConfigOptionValues(option).Keys()

for _, value := range values {
config := option + "=" + value
configProps := b.configOptionProperties[config]

if checkAll(configProps.ExtractSubIndexSets("upload_port")) {
res.Set(option, value)
}
}
}
return res
}
Loading

0 comments on commit 0d8ce36

Please sign in to comment.