Skip to content

Commit

Permalink
Extended tool support for various OS / CPU (#1077)
Browse files Browse the repository at this point in the history
* tools-flavor-tests: Some renames and clean up

* tools-flavors: Prioritize OS exatch-match over compatible-match

* tools-flavors: unrolled freebsd regexps

* tools-flavors: Added Apple silicon support

* tools-flavors: Added support for Windows 64 bit

* tools-flavors: give priority to better matching OS
  • Loading branch information
cmaglie committed Dec 9, 2020
1 parent 78d1d11 commit 0ccf27f
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 68 deletions.
91 changes: 63 additions & 28 deletions arduino/cores/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,55 +141,90 @@ func (tr *ToolRelease) RuntimeProperties() *properties.Map {
}

var (
regexpArmLinux = regexp.MustCompile("arm.*-linux-gnueabihf")
regexpArm64Linux = regexp.MustCompile("(aarch64|arm64)-linux-gnu")
regexpAmd64 = regexp.MustCompile("x86_64-.*linux-gnu")
regexpi386 = regexp.MustCompile("i[3456]86-.*linux-gnu")
regexpWindows = regexp.MustCompile("i[3456]86-.*(mingw32|cygwin)")
regexpMac64Bit = regexp.MustCompile("(i[3456]86|x86_64)-apple-darwin.*")
regexpmac32Bit = regexp.MustCompile("i[3456]86-apple-darwin.*")
regexpArmBSD = regexp.MustCompile("arm.*-freebsd[0-9]*")
regexpLinuxArm = regexp.MustCompile("arm.*-linux-gnueabihf")
regexpLinuxArm64 = regexp.MustCompile("(aarch64|arm64)-linux-gnu")
regexpLinux64 = regexp.MustCompile("x86_64-.*linux-gnu")
regexpLinux32 = regexp.MustCompile("i[3456]86-.*linux-gnu")
regexpWindows32 = regexp.MustCompile("i[3456]86-.*(mingw32|cygwin)")
regexpWindows64 = regexp.MustCompile("(amd64|x86_64)-.*(mingw32|cygwin)")
regexpMac64 = regexp.MustCompile("x86_64-apple-darwin.*")
regexpMac32 = regexp.MustCompile("i[3456]86-apple-darwin.*")
regexpMacArm64 = regexp.MustCompile("arm64-apple-darwin.*")
regexpFreeBSDArm = regexp.MustCompile("arm.*-freebsd[0-9]*")
regexpFreeBSD32 = regexp.MustCompile("i?[3456]86-freebsd[0-9]*")
regexpFreeBSD64 = regexp.MustCompile("amd64-freebsd[0-9]*")
)

func (f *Flavor) isCompatibleWithCurrentMachine() bool {
return f.isCompatibleWith(runtime.GOOS, runtime.GOARCH)
}

func (f *Flavor) isCompatibleWith(osName, osArch string) bool {
func (f *Flavor) isExactMatchWith(osName, osArch string) bool {
if f.OS == "all" {
return true
}

switch osName + "," + osArch {
case "linux,arm", "linux,armbe":
return regexpArmLinux.MatchString(f.OS)
return regexpLinuxArm.MatchString(f.OS)
case "linux,arm64":
return regexpArm64Linux.MatchString(f.OS)
return regexpLinuxArm64.MatchString(f.OS)
case "linux,amd64":
return regexpAmd64.MatchString(f.OS)
return regexpLinux64.MatchString(f.OS)
case "linux,386":
return regexpi386.MatchString(f.OS)
case "windows,386", "windows,amd64":
return regexpWindows.MatchString(f.OS)
return regexpLinux32.MatchString(f.OS)
case "windows,386":
return regexpWindows32.MatchString(f.OS)
case "windows,amd64":
return regexpWindows64.MatchString(f.OS)
case "darwin,arm64":
return regexpMacArm64.MatchString(f.OS)
case "darwin,amd64":
return regexpmac32Bit.MatchString(f.OS) || regexpMac64Bit.MatchString(f.OS)
return regexpMac64.MatchString(f.OS)
case "darwin,386":
return regexpmac32Bit.MatchString(f.OS)
return regexpMac32.MatchString(f.OS)
case "freebsd,arm":
return regexpArmBSD.MatchString(f.OS)
case "freebsd,386", "freebsd,amd64":
genericFreeBSDexp := regexp.MustCompile(osArch + "%s-freebsd[0-9]*")
return genericFreeBSDexp.MatchString(f.OS)
return regexpFreeBSDArm.MatchString(f.OS)
case "freebsd,386":
return regexpFreeBSD32.MatchString(f.OS)
case "freebsd,amd64":
return regexpFreeBSD64.MatchString(f.OS)
}
return false
}

func (f *Flavor) isCompatibleWith(osName, osArch string) (bool, int) {
if f.isExactMatchWith(osName, osArch) {
return true, 1000
}

switch osName + "," + osArch {
case "windows,amd64":
return regexpWindows32.MatchString(f.OS), 10
case "darwin,amd64":
return regexpMac32.MatchString(f.OS), 10
case "darwin,arm64":
// Compatibility guaranteed through Rosetta emulation
if regexpMac64.MatchString(f.OS) {
// Prefer amd64 version if available
return true, 20
}
return regexpMac32.MatchString(f.OS), 10
}

return false, 0
}

// GetCompatibleFlavour returns the downloadable resource compatible with the running O.S.
func (tr *ToolRelease) GetCompatibleFlavour() *resources.DownloadResource {
return tr.GetFlavourCompatibleWith(runtime.GOOS, runtime.GOARCH)
}

// GetFlavourCompatibleWith returns the downloadable resource compatible with the specified O.S.
func (tr *ToolRelease) GetFlavourCompatibleWith(osName, osArch string) *resources.DownloadResource {
var resource *resources.DownloadResource
priority := -1
for _, flavour := range tr.Flavors {
if flavour.isCompatibleWithCurrentMachine() {
return flavour.Resource
if comp, p := flavour.isCompatibleWith(osName, osArch); comp && p > priority {
resource = flavour.Resource
priority = p
}
}
return nil
return resource
}
157 changes: 117 additions & 40 deletions arduino/cores/tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cores
import (
"testing"

"github.com/arduino/arduino-cli/arduino/resources"
"github.com/stretchr/testify/require"
)

Expand All @@ -26,71 +27,147 @@ func TestFlavorCompatibility(t *testing.T) {
Os string
Arch string
}
windowsi386 := &os{"windows", "386"}
windowsx8664 := &os{"windows", "amd64"}
linuxi386 := &os{"linux", "386"}
linuxamd64 := &os{"linux", "amd64"}
linuxarm := &os{"linux", "arm"}
linuxarmbe := &os{"linux", "armbe"}
linuxarm64 := &os{"linux", "arm64"}
darwini386 := &os{"darwin", "386"}
darwinamd64 := &os{"darwin", "amd64"}
freebsdi386 := &os{"freebsd", "386"}
freebsdamd64 := &os{"freebsd", "amd64"}
windows32 := &os{"windows", "386"}
windows64 := &os{"windows", "amd64"}
linux32 := &os{"linux", "386"}
linux64 := &os{"linux", "amd64"}
linuxArm := &os{"linux", "arm"}
linuxArmbe := &os{"linux", "armbe"}
linuxArm64 := &os{"linux", "arm64"}
darwin32 := &os{"darwin", "386"}
darwin64 := &os{"darwin", "amd64"}
darwinArm64 := &os{"darwin", "arm64"}
freebsd32 := &os{"freebsd", "386"}
freebsd64 := &os{"freebsd", "amd64"}
oses := []*os{
windowsi386,
windowsx8664,
linuxi386,
linuxamd64,
linuxarm,
linuxarmbe,
linuxarm64,
darwini386,
darwinamd64,
freebsdi386,
freebsdamd64,
windows32,
windows64,
linux32,
linux64,
linuxArm,
linuxArmbe,
linuxArm64,
darwin32,
darwin64,
darwinArm64,
freebsd32,
freebsd64,
}

type test struct {
Flavour *Flavor
Positives []*os
Flavour *Flavor
Compatibles []*os
ExactMatch []*os
}
tests := []*test{
{&Flavor{OS: "i686-mingw32"}, []*os{windowsi386, windowsx8664}},
{&Flavor{OS: "i386-apple-darwin11"}, []*os{darwini386, darwinamd64}},
{&Flavor{OS: "x86_64-apple-darwin"}, []*os{darwinamd64}},
{&Flavor{OS: "i686-mingw32"}, []*os{windows32, windows64}, []*os{windows32}},
{&Flavor{OS: "x86_64-mingw32"}, []*os{windows64}, []*os{windows64}},
{&Flavor{OS: "i386-apple-darwin11"}, []*os{darwin32, darwin64, darwinArm64}, []*os{darwin32}},
{&Flavor{OS: "x86_64-apple-darwin"}, []*os{darwin64, darwinArm64}, []*os{darwin64}},
{&Flavor{OS: "arm64-apple-darwin"}, []*os{darwinArm64}, []*os{darwinArm64}},

// Raspberry PI, BBB or other ARM based host
// PI: "arm-linux-gnueabihf"
// Raspbian on PI2: "arm-linux-gnueabihf"
// Ubuntu Mate on PI2: "arm-linux-gnueabihf"
// Debian 7.9 on BBB: "arm-linux-gnueabihf"
// Raspbian on PI Zero: "arm-linux-gnueabihf"
{&Flavor{OS: "arm-linux-gnueabihf"}, []*os{linuxarm, linuxarmbe}},
{&Flavor{OS: "arm-linux-gnueabihf"}, []*os{linuxArm, linuxArmbe}, []*os{linuxArm, linuxArmbe}},
// Arch-linux on PI2: "armv7l-unknown-linux-gnueabihf"
{&Flavor{OS: "armv7l-unknown-linux-gnueabihf"}, []*os{linuxarm, linuxarmbe}},
{&Flavor{OS: "armv7l-unknown-linux-gnueabihf"}, []*os{linuxArm, linuxArmbe}, []*os{linuxArm, linuxArmbe}},

{&Flavor{OS: "i686-linux-gnu"}, []*os{linuxi386}},
{&Flavor{OS: "i686-pc-linux-gnu"}, []*os{linuxi386}},
{&Flavor{OS: "x86_64-linux-gnu"}, []*os{linuxamd64}},
{&Flavor{OS: "x86_64-pc-linux-gnu"}, []*os{linuxamd64}},
{&Flavor{OS: "aarch64-linux-gnu"}, []*os{linuxarm64}},
{&Flavor{OS: "arm64-linux-gnu"}, []*os{linuxarm64}},
{&Flavor{OS: "i686-linux-gnu"}, []*os{linux32}, []*os{linux32}},
{&Flavor{OS: "i686-pc-linux-gnu"}, []*os{linux32}, []*os{linux32}},
{&Flavor{OS: "x86_64-linux-gnu"}, []*os{linux64}, []*os{linux64}},
{&Flavor{OS: "x86_64-pc-linux-gnu"}, []*os{linux64}, []*os{linux64}},
{&Flavor{OS: "aarch64-linux-gnu"}, []*os{linuxArm64}, []*os{linuxArm64}},
{&Flavor{OS: "arm64-linux-gnu"}, []*os{linuxArm64}, []*os{linuxArm64}},
}

check := func(test *test, os *os) {
for _, positiveOs := range test.Positives {
checkCompatible := func(test *test, os *os) {
// if the os is in the "positive" set iCompatibleWith must return true...
res, _ := test.Flavour.isCompatibleWith(os.Os, os.Arch)
for _, compatibleOs := range test.Compatibles {
if compatibleOs == os {
require.True(t, res, "'%s' tag compatible with '%s,%s' pair", test.Flavour.OS, os.Os, os.Arch)
return
}
}
// ...otherwise false
require.False(t, res, "'%s' tag compatible with '%s,%s' pair", test.Flavour.OS, os.Os, os.Arch)
}
checkExactMatch := func(test *test, os *os) {
// if the os is in the "positive" set iExactMatchWith must return true...
for _, positiveOs := range test.ExactMatch {
if positiveOs == os {
require.True(t, test.Flavour.isCompatibleWith(os.Os, os.Arch), "'%s' tag compatible with '%s,%s' pair", test.Flavour.OS, os.Os, os.Arch)
require.True(t, test.Flavour.isExactMatchWith(os.Os, os.Arch), "'%s' tag exact match with '%s,%s' pair", test.Flavour.OS, os.Os, os.Arch)
return
}
}
require.False(t, test.Flavour.isCompatibleWith(os.Os, os.Arch), "'%s' tag compatible with '%s,%s' pair", test.Flavour.OS, os.Os, os.Arch)
// ...otherwise false
require.False(t, test.Flavour.isExactMatchWith(os.Os, os.Arch), "'%s' tag exact match with '%s,%s' pair", test.Flavour.OS, os.Os, os.Arch)
}

for _, test := range tests {
for _, os := range oses {
check(test, os)
checkCompatible(test, os)
checkExactMatch(test, os)
}
}
}

func TestFlavorPrioritySelection(t *testing.T) {
res := (&ToolRelease{
Flavors: []*Flavor{
{OS: "i386-apple-darwin11", Resource: &resources.DownloadResource{ArchiveFileName: "1"}},
{OS: "x86_64-apple-darwin", Resource: &resources.DownloadResource{ArchiveFileName: "2"}},
{OS: "arm64-apple-darwin", Resource: &resources.DownloadResource{ArchiveFileName: "3"}},
},
}).GetFlavourCompatibleWith("darwin", "arm64")
require.NotNil(t, res)
require.Equal(t, "3", res.ArchiveFileName)

res = (&ToolRelease{
Flavors: []*Flavor{
{OS: "i386-apple-darwin11", Resource: &resources.DownloadResource{ArchiveFileName: "1"}},
{OS: "x86_64-apple-darwin", Resource: &resources.DownloadResource{ArchiveFileName: "2"}},
},
}).GetFlavourCompatibleWith("darwin", "arm64")
require.NotNil(t, res)
require.Equal(t, "2", res.ArchiveFileName)

res = (&ToolRelease{
Flavors: []*Flavor{
{OS: "x86_64-apple-darwin", Resource: &resources.DownloadResource{ArchiveFileName: "2"}},
{OS: "i386-apple-darwin11", Resource: &resources.DownloadResource{ArchiveFileName: "1"}},
},
}).GetFlavourCompatibleWith("darwin", "arm64")
require.NotNil(t, res)
require.Equal(t, "2", res.ArchiveFileName)

res = (&ToolRelease{
Flavors: []*Flavor{
{OS: "i386-apple-darwin11", Resource: &resources.DownloadResource{ArchiveFileName: "1"}},
},
}).GetFlavourCompatibleWith("darwin", "arm64")
require.NotNil(t, res)
require.Equal(t, "1", res.ArchiveFileName)

res = (&ToolRelease{
Flavors: []*Flavor{
{OS: "i686-mingw32", Resource: &resources.DownloadResource{ArchiveFileName: "1"}},
{OS: "x86_64-mingw32", Resource: &resources.DownloadResource{ArchiveFileName: "2"}},
},
}).GetFlavourCompatibleWith("windows", "amd64")
require.NotNil(t, res)
require.Equal(t, "2", res.ArchiveFileName)

res = (&ToolRelease{
Flavors: []*Flavor{
{OS: "x86_64-mingw32", Resource: &resources.DownloadResource{ArchiveFileName: "2"}},
{OS: "i686-mingw32", Resource: &resources.DownloadResource{ArchiveFileName: "1"}},
},
}).GetFlavourCompatibleWith("windows", "amd64")
require.NotNil(t, res)
require.Equal(t, "2", res.ArchiveFileName)
}

0 comments on commit 0ccf27f

Please sign in to comment.