Skip to content

Commit 38b76aa

Browse files
committed
Add loading of PluggableDiscoveries when loading a platform release
1 parent abf9ce6 commit 38b76aa

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

arduino/cores/packagemanager/loader.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,3 +588,96 @@ func (pm *PackageManager) LoadToolsFromBundleDirectory(toolsPath *paths.Path) er
588588
}
589589
return nil
590590
}
591+
592+
// LoadDiscoveries load all discoveries for all loaded platforms
593+
// Returns error if:
594+
// * A PluggableDiscovery instance can't be created
595+
// * Tools required by the PlatformRelease cannot be found
596+
// * Command line to start PluggableDiscovery has malformed or mismatched quotes
597+
func (pm *PackageManager) LoadDiscoveries() []*status.Status {
598+
statuses := []*status.Status{}
599+
for _, platform := range pm.InstalledPlatformReleases() {
600+
statuses = append(statuses, pm.loadDiscoveries(platform)...)
601+
}
602+
return statuses
603+
}
604+
605+
func (pm *PackageManager) loadDiscoveries(release *cores.PlatformRelease) []*status.Status {
606+
statuses := []*status.Status{}
607+
discoveryProperties := release.Properties.SubTree("discovery")
608+
609+
if discoveryProperties.Size() == 0 {
610+
return nil
611+
}
612+
613+
// Handles discovery properties formatted like so:
614+
//
615+
// Case 1:
616+
// "discovery.required": "PLATFORM:DISCOVERY_NAME",
617+
//
618+
// Case 2:
619+
// "discovery.required.0": "PLATFORM:DISCOVERY_ID_1",
620+
// "discovery.required.1": "PLATFORM:DISCOVERY_ID_2",
621+
//
622+
// If both indexed and unindexed properties are found the unindexed are ignored
623+
for _, id := range discoveryProperties.ExtractSubIndexLists("required") {
624+
tool := pm.GetTool(id)
625+
if tool == nil {
626+
statuses = append(statuses, status.Newf(codes.FailedPrecondition, "discovery not found: %s", id))
627+
continue
628+
}
629+
toolRelease := tool.GetLatestInstalled()
630+
discoveryPath := toolRelease.InstallDir.Join(tool.Name).String()
631+
d, err := discovery.New(id, discoveryPath)
632+
if err != nil {
633+
statuses = append(statuses, status.Newf(codes.FailedPrecondition, "creating discovery: %s", err))
634+
continue
635+
}
636+
pm.discoveryManager.Add(d)
637+
}
638+
639+
discoveryIDs := discoveryProperties.FirstLevelOf()
640+
delete(discoveryIDs, "required")
641+
// Get the list of tools only we if have there are discoveries that use Direct discovery integration.
642+
// See:
643+
// https://github.com/arduino/tooling-rfcs/blob/main/RFCs/0002-pluggable-discovery.md#direct-discovery-integration-not-recommended
644+
// We need the tools only in that case since we might need some tool's
645+
// runtime properties to expand the discovery pattern to run it correctly.
646+
var tools []*cores.ToolRelease
647+
if len(discoveryIDs) > 0 {
648+
var err error
649+
tools, err = pm.FindToolsRequiredFromPlatformRelease(release)
650+
if err != nil {
651+
statuses = append(statuses, status.New(codes.Internal, err.Error()))
652+
}
653+
}
654+
655+
// Handles discovery properties formatted like so:
656+
//
657+
// discovery.DISCOVERY_ID.pattern: "COMMAND_TO_EXECUTE"
658+
for discoveryID, props := range discoveryIDs {
659+
pattern, ok := props.GetOk("pattern")
660+
if !ok {
661+
statuses = append(statuses, status.Newf(codes.FailedPrecondition, "can't find pattern for discovery with id %s", discoveryID))
662+
continue
663+
}
664+
configuration := release.Properties.Clone()
665+
configuration.Merge(release.RuntimeProperties())
666+
configuration.Merge(props)
667+
668+
for _, tool := range tools {
669+
configuration.Merge(tool.RuntimeProperties())
670+
}
671+
672+
cmd := configuration.ExpandPropsInString(pattern)
673+
if cmdArgs, err := properties.SplitQuotedString(cmd, `"'`, true); err != nil {
674+
statuses = append(statuses, status.New(codes.Internal, err.Error()))
675+
} else if d, err := discovery.New(discoveryID, cmdArgs...); err != nil {
676+
statuses = append(statuses, status.New(codes.Internal, err.Error()))
677+
} else {
678+
pm.discoveryManager.Add(d)
679+
}
680+
}
681+
682+
return statuses
683+
}

arduino/cores/packagemanager/loader_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package packagemanager
1818
import (
1919
"testing"
2020

21+
"github.com/arduino/go-paths-helper"
2122
"github.com/arduino/go-properties-orderedmap"
2223
"github.com/stretchr/testify/require"
24+
semver "go.bug.st/relaxed-semver"
2325
)
2426

2527
func TestVidPidConvertionToPluggableDiscovery(t *testing.T) {
@@ -103,3 +105,73 @@ arduino_zero_native.pid.3=0x024d
103105
"upload_port.3.pid": "0x024d",
104106
}`, zero4.Dump())
105107
}
108+
109+
func TestLoadDiscoveries(t *testing.T) {
110+
// Create all the necessary data to load discoveries
111+
fakePath := paths.New("fake-path")
112+
packageManager := NewPackageManager(fakePath, fakePath, fakePath, fakePath)
113+
pack := packageManager.Packages.GetOrCreatePackage("arduino")
114+
// ble-discovery tool
115+
tool := pack.GetOrCreateTool("arduino:ble-discovery")
116+
toolRelease := tool.GetOrCreateRelease(semver.ParseRelaxed("1.0.0"))
117+
// We set this to fake the tool is installed
118+
toolRelease.InstallDir = fakePath
119+
tool.GetOrCreateRelease(semver.ParseRelaxed("0.1.0"))
120+
121+
// serial-discovery tool
122+
tool = pack.GetOrCreateTool("arduino:serial-discovery")
123+
tool.GetOrCreateRelease(semver.ParseRelaxed("1.0.0"))
124+
toolRelease = tool.GetOrCreateRelease(semver.ParseRelaxed("0.1.0"))
125+
// We set this to fake the tool is installed
126+
toolRelease.InstallDir = fakePath
127+
128+
platform := pack.GetOrCreatePlatform("avr")
129+
release := platform.GetOrCreateRelease(semver.MustParse("1.0.0"))
130+
131+
release.Properties = properties.NewFromHashmap(map[string]string{
132+
"discovery.required": "arduino:ble-discovery",
133+
})
134+
135+
discoveries, err := packageManager.LoadDiscoveries(release)
136+
require.Len(t, discoveries, 1)
137+
require.NoError(t, err)
138+
require.Equal(t, discoveries[0].GetID(), "arduino:ble-discovery")
139+
140+
release.Properties = properties.NewFromHashmap(map[string]string{
141+
"discovery.required.0": "arduino:ble-discovery",
142+
"discovery.required.1": "arduino:serial-discovery",
143+
})
144+
145+
discoveries, err = packageManager.LoadDiscoveries(release)
146+
require.Len(t, discoveries, 2)
147+
require.NoError(t, err)
148+
require.Equal(t, discoveries[0].GetID(), "arduino:ble-discovery")
149+
require.Equal(t, discoveries[1].GetID(), "arduino:serial-discovery")
150+
151+
release.Properties = properties.NewFromHashmap(map[string]string{
152+
"discovery.required.0": "arduino:ble-discovery",
153+
"discovery.required.1": "arduino:serial-discovery",
154+
"discovery.teensy.pattern": "\"{runtime.tools.teensy_ports.path}/hardware/tools/teensy_ports\" -J2",
155+
})
156+
157+
discoveries, err = packageManager.LoadDiscoveries(release)
158+
require.Len(t, discoveries, 3)
159+
require.NoError(t, err)
160+
require.Equal(t, discoveries[0].GetID(), "arduino:ble-discovery")
161+
require.Equal(t, discoveries[1].GetID(), "arduino:serial-discovery")
162+
require.Equal(t, discoveries[2].GetID(), "teensy")
163+
164+
release.Properties = properties.NewFromHashmap(map[string]string{
165+
"discovery.required": "arduino:some-discovery",
166+
"discovery.required.0": "arduino:ble-discovery",
167+
"discovery.required.1": "arduino:serial-discovery",
168+
"discovery.teensy.pattern": "\"{runtime.tools.teensy_ports.path}/hardware/tools/teensy_ports\" -J2",
169+
})
170+
171+
discoveries, err = packageManager.LoadDiscoveries(release)
172+
require.Len(t, discoveries, 3)
173+
require.NoError(t, err)
174+
require.Equal(t, discoveries[0].GetID(), "arduino:ble-discovery")
175+
require.Equal(t, discoveries[1].GetID(), "arduino:serial-discovery")
176+
require.Equal(t, discoveries[2].GetID(), "teensy")
177+
}

arduino/cores/packagemanager/package_manager.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,39 @@ func (pm *PackageManager) InstalledBoards() []*cores.Board {
439439
return boards
440440
}
441441

442+
func (pm *PackageManager) FindToolsRequiredFromPlatformRelease(platform *cores.PlatformRelease) ([]*cores.ToolRelease, error) {
443+
pm.Log.Infof("Searching tools required for platform %s", platform)
444+
445+
// maps "PACKAGER:TOOL" => ToolRelease
446+
foundTools := map[string]*cores.ToolRelease{}
447+
// A Platform may not specify required tools (because it's a platform that comes from a
448+
// user/hardware dir without a package_index.json) then add all available tools
449+
for _, targetPackage := range pm.Packages {
450+
for _, tool := range targetPackage.Tools {
451+
rel := tool.GetLatestInstalled()
452+
if rel != nil {
453+
foundTools[rel.Tool.Name] = rel
454+
}
455+
}
456+
}
457+
// replace the default tools above with the specific required by the current platform
458+
requiredTools := []*cores.ToolRelease{}
459+
platform.ToolDependencies.Sort()
460+
for _, toolDep := range platform.ToolDependencies {
461+
pm.Log.WithField("tool", toolDep).Infof("Required tool")
462+
tool := pm.FindToolDependency(toolDep)
463+
if tool == nil {
464+
return nil, fmt.Errorf("tool release not found: %s", toolDep)
465+
}
466+
requiredTools = append(requiredTools, tool)
467+
delete(foundTools, tool.Tool.Name)
468+
}
469+
for _, toolRel := range foundTools {
470+
requiredTools = append(requiredTools, toolRel)
471+
}
472+
return requiredTools, nil
473+
}
474+
442475
// GetTool searches for tool in all packages and platforms.
443476
func (pm *PackageManager) GetTool(toolID string) *cores.Tool {
444477
split := strings.Split(toolID, ":")

0 commit comments

Comments
 (0)