Skip to content

Commit 810ad0d

Browse files
committed
Add prioritization to tool selection in absence of explicit dependencies
1 parent 7663b78 commit 810ad0d

File tree

7 files changed

+113
-15
lines changed

7 files changed

+113
-15
lines changed

Diff for: arduino/cores/packagemanager/package_manager.go

+44-15
Original file line numberDiff line numberDiff line change
@@ -618,25 +618,50 @@ func (pme *Explorer) GetTool(toolID string) *cores.Tool {
618618
func (pme *Explorer) FindToolsRequiredForBoard(board *cores.Board) ([]*cores.ToolRelease, error) {
619619
pme.log.Infof("Searching tools required for board %s", board)
620620

621-
// core := board.Properties["build.core"]
622621
platform := board.PlatformRelease
623622

624-
// maps "PACKAGER:TOOL" => ToolRelease
625-
foundTools := map[string]*cores.ToolRelease{}
626-
627-
// a Platform may not specify required tools (because it's a platform that comes from a
628-
// user/hardware dir without a package_index.json) then add all available tools
629-
for _, targetPackage := range pme.packages {
630-
for _, tool := range targetPackage.Tools {
631-
rel := tool.GetLatestInstalled()
632-
if rel != nil {
633-
foundTools[rel.Tool.Name] = rel
623+
// maps tool name => all available ToolRelease
624+
allToolsAlternatives := map[string][]*cores.ToolRelease{}
625+
for _, tool := range pme.GetAllInstalledToolsReleases() {
626+
alternatives := allToolsAlternatives[tool.Tool.Name]
627+
alternatives = append(alternatives, tool)
628+
allToolsAlternatives[tool.Tool.Name] = alternatives
629+
}
630+
631+
// selectBest select the tool with best matching, applying the following rules
632+
// in order of priority:
633+
// - the tool comes from the requested packager
634+
// - the tool has the greatest version
635+
// - the tool packager comes first in alphabetic order
636+
selectBest := func(tools []*cores.ToolRelease, packager string) *cores.ToolRelease {
637+
selected := tools[0]
638+
for _, tool := range tools[1:] {
639+
if tool.Tool.Package.Name != selected.Tool.Package.Name {
640+
if tool.Tool.Package.Name == packager {
641+
selected = tool
642+
}
643+
continue
644+
}
645+
if !tool.Version.Equal(selected.Version) {
646+
if tool.Version.GreaterThan(selected.Version) {
647+
selected = tool
648+
}
649+
continue
650+
}
651+
if tool.Tool.Package.Name < selected.Tool.Package.Name {
652+
selected = tool
634653
}
635654
}
655+
return selected
636656
}
637657

638-
// replace the default tools above with the specific required by the current platform
658+
// First select the specific tools required by the current platform
639659
requiredTools := []*cores.ToolRelease{}
660+
// The Sorting of the tool dependencies is required because some platforms may depends
661+
// on more than one version of the same tool. For example adafruit:samd has both
662+
// [email protected] and [email protected]. To allow the runtime property
663+
// {runtime.tools.bossac.path} to be correctly set to the 1.8.0 version we must ensure
664+
// taht the returned array is sorted by version.
640665
platform.ToolDependencies.Sort()
641666
for _, toolDep := range platform.ToolDependencies {
642667
pme.log.WithField("tool", toolDep).Infof("Required tool")
@@ -645,11 +670,15 @@ func (pme *Explorer) FindToolsRequiredForBoard(board *cores.Board) ([]*cores.Too
645670
return nil, fmt.Errorf(tr("tool release not found: %s"), toolDep)
646671
}
647672
requiredTools = append(requiredTools, tool)
648-
delete(foundTools, tool.Tool.Name)
673+
delete(allToolsAlternatives, tool.Tool.Name)
649674
}
650675

651-
for _, toolRel := range foundTools {
652-
requiredTools = append(requiredTools, toolRel)
676+
// Since a Platform may not specify the required tools (because it's a platform that comes
677+
// from a user/hardware dir without a package_index.json) then add all available tools giving
678+
// priority to tools coming from the same packager
679+
for _, tools := range allToolsAlternatives {
680+
tool := selectBest(tools, platform.Platform.Package.Name)
681+
requiredTools = append(requiredTools, tool)
653682
}
654683
return requiredTools, nil
655684
}

Diff for: internal/integrationtest/compile/compile_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2022 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package compile_test
17+
18+
import (
19+
"testing"
20+
21+
"github.com/arduino/arduino-cli/internal/integrationtest"
22+
"github.com/arduino/go-paths-helper"
23+
"github.com/stretchr/testify/require"
24+
)
25+
26+
func TestRuntimeToolPropertiesGeneration(t *testing.T) {
27+
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
28+
defer env.CleanUp()
29+
30+
// Run update-index with our test index
31+
_, _, err := cli.Run("core", "install", "arduino:[email protected]")
32+
require.NoError(t, err)
33+
34+
// Install test data into datadir
35+
testdata := paths.New("testdata", "platforms_with_conflicting_tools")
36+
hardwareDir := cli.DataDir().Join("packages")
37+
err = testdata.Join("alice").CopyDirTo(hardwareDir.Join("alice"))
38+
require.NoError(t, err)
39+
err = testdata.Join("bob").CopyDirTo(hardwareDir.Join("bob"))
40+
require.NoError(t, err)
41+
42+
sketch, err := paths.New("testdata", "bare_minimum").Abs()
43+
require.NoError(t, err)
44+
45+
// Multiple runs must always produce the same result
46+
for i := 0; i < 3; i++ {
47+
stdout, _, err := cli.Run("compile", "-b", "alice:avr:alice", "--show-properties", sketch.String())
48+
require.NoError(t, err)
49+
// the tools coming from the same packager are selected
50+
require.Contains(t, string(stdout), "runtime.tools.avr-gcc.path="+hardwareDir.String()+"/alice/tools/avr-gcc/50.0.0")
51+
require.Contains(t, string(stdout), "runtime.tools.avrdude.path="+hardwareDir.String()+"/alice/tools/avrdude/1.0.0")
52+
53+
stdout, _, err = cli.Run("compile", "-b", "bob:avr:bob", "--show-properties", sketch.String())
54+
require.NoError(t, err)
55+
// the latest version available are selected
56+
require.Contains(t, string(stdout), "runtime.tools.avr-gcc.path="+hardwareDir.String()+"/alice/tools/avr-gcc/50.0.0")
57+
require.Contains(t, string(stdout), "runtime.tools.avrdude.path="+hardwareDir.String()+"/arduino/tools/avrdude/6.3.0-arduino17")
58+
59+
stdout, _, err = cli.Run("compile", "-b", "arduino:avr:uno", "--show-properties", sketch.String())
60+
require.NoError(t, err)
61+
// the selected tools are listed as platform dependencies from the index.json
62+
require.Contains(t, string(stdout), "runtime.tools.avr-gcc.path="+hardwareDir.String()+"/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7")
63+
require.Contains(t, string(stdout), "runtime.tools.avrdude.path="+hardwareDir.String()+"/arduino/tools/avrdude/6.3.0-arduino17")
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
void setup() {}
2+
void loop() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
alice.name=MyBoard

Diff for: internal/integrationtest/compile/testdata/platforms_with_conflicting_tools/alice/tools/avr-gcc/50.0.0/.keep

Whitespace-only changes.

Diff for: internal/integrationtest/compile/testdata/platforms_with_conflicting_tools/alice/tools/avrdude/1.0.0/.keep

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bob.name=Alice's Board

0 commit comments

Comments
 (0)