diff --git a/Taskfile.yml b/Taskfile.yml
index 2bcf5ba05b8..ed04c7d9cb0 100755
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -30,7 +30,7 @@ tasks:
   test-legacy:
     desc: Run tests for the `legacy` package
     cmds:
-      - go test {{ default "-v" .GOFLAGS }} ./legacy/...
+      - go test {{ default "-v -failfast" .GOFLAGS }} ./legacy/...
 
   test-unit-race:
     desc: Run unit tests only with race condition detection
diff --git a/arduino/cores/cores.go b/arduino/cores/cores.go
index eb831be452d..071aa09c823 100644
--- a/arduino/cores/cores.go
+++ b/arduino/cores/cores.go
@@ -18,6 +18,7 @@
 package cores
 
 import (
+	"encoding/json"
 	"strings"
 
 	paths "github.com/arduino/go-paths-helper"
@@ -41,14 +42,13 @@ type PlatformRelease struct {
 	Resource       *resources.DownloadResource
 	Version        *semver.Version
 	BoardsManifest []*BoardManifest
-	Dependencies   ToolDependencies // The Dependency entries to load tools.
-	Platform       *Platform        `json:"-"`
-
-	Properties  *properties.Map            `json:"-"`
-	Boards      map[string]*Board          `json:"-"`
-	Programmers map[string]*properties.Map `json:"-"`
-	Menus       *properties.Map            `json:"-"`
-	InstallDir  *paths.Path                `json:"-"`
+	Dependencies   ToolDependencies           // The Dependency entries to load tools.
+	Platform       *Platform                  `json:"-"`
+	Properties     *properties.Map            `json:"-"`
+	Boards         map[string]*Board          `json:"-"`
+	Programmers    map[string]*properties.Map `json:"-"`
+	Menus          *properties.Map            `json:"-"`
+	InstallDir     *paths.Path                `json:"-"`
 }
 
 // BoardManifest contains information about a board. These metadata are usually
@@ -226,3 +226,25 @@ func (release *PlatformRelease) String() string {
 	}
 	return release.Platform.String() + "@" + version
 }
+
+// MarshalJSON provides a more user friendly serialization for
+// PlatformRelease objects.
+func (release *PlatformRelease) MarshalJSON() ([]byte, error) {
+	latestStr := ""
+	latest := release.Platform.GetLatestRelease()
+	if latest != nil {
+		latestStr = latest.Version.String()
+	}
+
+	return json.Marshal(&struct {
+		ID        string `json:"ID,omitempty"`
+		Installed string `json:"Installed,omitempty"`
+		Latest    string `json:"Latest,omitempty"`
+		Name      string `json:"Name,omitempty"`
+	}{
+		ID:        release.Platform.String(),
+		Installed: release.Version.String(),
+		Latest:    latestStr,
+		Name:      release.Platform.Name,
+	})
+}
diff --git a/arduino/cores/packageindex/index.go b/arduino/cores/packageindex/index.go
index 075dc0fda16..b66b4d1561c 100644
--- a/arduino/cores/packageindex/index.go
+++ b/arduino/cores/packageindex/index.go
@@ -24,7 +24,7 @@ import (
 	"github.com/arduino/arduino-cli/arduino/cores"
 	"github.com/arduino/arduino-cli/arduino/resources"
 	"github.com/arduino/go-paths-helper"
-	"go.bug.st/relaxed-semver"
+	semver "go.bug.st/relaxed-semver"
 )
 
 // Index represents Cores and Tools struct as seen from package_index.json file.
@@ -97,13 +97,13 @@ type indexHelp struct {
 
 // MergeIntoPackages converts the Index data into a cores.Packages and merge them
 // with the existing conents of the cores.Packages passed as parameter.
-func (index Index) MergeIntoPackages(outPackages *cores.Packages) {
+func (index Index) MergeIntoPackages(outPackages cores.Packages) {
 	for _, inPackage := range index.Packages {
 		inPackage.extractPackageIn(outPackages)
 	}
 }
 
-func (inPackage indexPackage) extractPackageIn(outPackages *cores.Packages) {
+func (inPackage indexPackage) extractPackageIn(outPackages cores.Packages) {
 	outPackage := outPackages.GetOrCreatePackage(inPackage.Name)
 	outPackage.Maintainer = inPackage.Maintainer
 	outPackage.WebsiteURL = inPackage.WebsiteURL
diff --git a/arduino/cores/packagemanager/download.go b/arduino/cores/packagemanager/download.go
index 15c4b58916a..8d3c097ff14 100644
--- a/arduino/cores/packagemanager/download.go
+++ b/arduino/cores/packagemanager/download.go
@@ -23,7 +23,7 @@ import (
 
 	"github.com/arduino/arduino-cli/arduino/cores"
 	"go.bug.st/downloader"
-	"go.bug.st/relaxed-semver"
+	semver "go.bug.st/relaxed-semver"
 )
 
 // PlatformReference represents a tuple to identify a Platform
@@ -44,7 +44,7 @@ func (platform *PlatformReference) String() string {
 // FindPlatform returns the Platform matching the PlatformReference or nil if not found.
 // The PlatformVersion field of the reference is ignored.
 func (pm *PackageManager) FindPlatform(ref *PlatformReference) *cores.Platform {
-	targetPackage, ok := pm.GetPackages().Packages[ref.Package]
+	targetPackage, ok := pm.Packages[ref.Package]
 	if !ok {
 		return nil
 	}
@@ -71,7 +71,7 @@ func (pm *PackageManager) FindPlatformRelease(ref *PlatformReference) *cores.Pla
 // FindPlatformReleaseDependencies takes a PlatformReference and returns a set of items to download and
 // a set of outputs for non existing platforms.
 func (pm *PackageManager) FindPlatformReleaseDependencies(item *PlatformReference) (*cores.PlatformRelease, []*cores.ToolRelease, error) {
-	targetPackage, exists := pm.packages.Packages[item.Package]
+	targetPackage, exists := pm.Packages[item.Package]
 	if !exists {
 		return nil, nil, fmt.Errorf("package %s not found", item.Package)
 	}
@@ -94,7 +94,7 @@ func (pm *PackageManager) FindPlatformReleaseDependencies(item *PlatformReferenc
 	}
 
 	// replaces "latest" with latest version too
-	toolDeps, err := pm.packages.GetDepsOfPlatformRelease(release)
+	toolDeps, err := pm.Packages.GetDepsOfPlatformRelease(release)
 	if err != nil {
 		return nil, nil, fmt.Errorf("getting tool dependencies for platform %s: %s", release.String(), err)
 	}
diff --git a/arduino/cores/packagemanager/install_uninstall.go b/arduino/cores/packagemanager/install_uninstall.go
index de17edc0cab..144cdb5fa0d 100644
--- a/arduino/cores/packagemanager/install_uninstall.go
+++ b/arduino/cores/packagemanager/install_uninstall.go
@@ -127,7 +127,7 @@ func (pm *PackageManager) UninstallTool(toolRelease *cores.ToolRelease) error {
 // passed as parameter
 func (pm *PackageManager) IsToolRequired(toolRelease *cores.ToolRelease) bool {
 	// Search in all installed platforms
-	for _, targetPackage := range pm.packages.Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, platform := range targetPackage.Platforms {
 			if platformRelease := pm.GetInstalledPlatformRelease(platform); platformRelease != nil {
 				if platformRelease.RequiresToolRelease(toolRelease) {
diff --git a/arduino/cores/packagemanager/loader.go b/arduino/cores/packagemanager/loader.go
index 0aa4ddb8185..8a542ef4a03 100644
--- a/arduino/cores/packagemanager/loader.go
+++ b/arduino/cores/packagemanager/loader.go
@@ -112,7 +112,7 @@ func (pm *PackageManager) LoadHardwareFromDirectory(path *paths.Path) error {
 			continue
 		}
 
-		targetPackage := pm.packages.GetOrCreatePackage(packager)
+		targetPackage := pm.Packages.GetOrCreatePackage(packager)
 		if err := pm.loadPlatforms(targetPackage, architectureParentPath); err != nil {
 			return fmt.Errorf("loading package %s: %s", packager, err)
 		}
@@ -419,7 +419,7 @@ func (pm *PackageManager) LoadToolsFromBundleDirectory(toolsPath *paths.Path) er
 		}
 
 		for packager, toolsData := range all.FirstLevelOf() {
-			targetPackage := pm.packages.GetOrCreatePackage(packager)
+			targetPackage := pm.Packages.GetOrCreatePackage(packager)
 
 			for toolName, toolVersion := range toolsData.AsMap() {
 				tool := targetPackage.GetOrCreateTool(toolName)
@@ -431,7 +431,7 @@ func (pm *PackageManager) LoadToolsFromBundleDirectory(toolsPath *paths.Path) er
 		}
 	} else {
 		// otherwise load the tools inside the unnamed package
-		unnamedPackage := pm.packages.GetOrCreatePackage("")
+		unnamedPackage := pm.Packages.GetOrCreatePackage("")
 		pm.loadToolsFromPackage(unnamedPackage, toolsPath)
 	}
 	return nil
diff --git a/arduino/cores/packagemanager/package_manager.go b/arduino/cores/packagemanager/package_manager.go
index 3654121d486..b9390110418 100644
--- a/arduino/cores/packagemanager/package_manager.go
+++ b/arduino/cores/packagemanager/package_manager.go
@@ -38,9 +38,8 @@ import (
 // The manager also keeps track of the status of the Packages (their Platform Releases, actually)
 // installed in the system.
 type PackageManager struct {
-	Log      logrus.FieldLogger
-	packages *cores.Packages
-
+	Log         logrus.FieldLogger
+	Packages    cores.Packages
 	IndexDir    *paths.Path
 	PackagesDir *paths.Path
 	DownloadDir *paths.Path
@@ -51,7 +50,7 @@ type PackageManager struct {
 func NewPackageManager(indexDir, packagesDir, downloadDir, tempDir *paths.Path) *PackageManager {
 	return &PackageManager{
 		Log:         logrus.StandardLogger(),
-		packages:    cores.NewPackages(),
+		Packages:    cores.NewPackages(),
 		IndexDir:    indexDir,
 		PackagesDir: packagesDir,
 		DownloadDir: downloadDir,
@@ -59,20 +58,10 @@ func NewPackageManager(indexDir, packagesDir, downloadDir, tempDir *paths.Path)
 	}
 }
 
-// Clear FIXMEDOC
-func (pm *PackageManager) Clear() {
-	pm.packages = cores.NewPackages()
-}
-
-// GetPackages FIXMEDOC
-func (pm *PackageManager) GetPackages() *cores.Packages {
-	return pm.packages
-}
-
 // FindPlatformReleaseProvidingBoardsWithVidPid FIXMEDOC
 func (pm *PackageManager) FindPlatformReleaseProvidingBoardsWithVidPid(vid, pid string) []*cores.PlatformRelease {
 	res := []*cores.PlatformRelease{}
-	for _, targetPackage := range pm.packages.Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, targetPlatform := range targetPackage.Platforms {
 			platformRelease := targetPlatform.GetLatestRelease()
 			if platformRelease == nil {
@@ -92,7 +81,7 @@ func (pm *PackageManager) FindPlatformReleaseProvidingBoardsWithVidPid(vid, pid
 // FindBoardsWithVidPid FIXMEDOC
 func (pm *PackageManager) FindBoardsWithVidPid(vid, pid string) []*cores.Board {
 	res := []*cores.Board{}
-	for _, targetPackage := range pm.packages.Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, targetPlatform := range targetPackage.Platforms {
 			if platform := pm.GetInstalledPlatformRelease(targetPlatform); platform != nil {
 				for _, board := range platform.Boards {
@@ -109,7 +98,7 @@ func (pm *PackageManager) FindBoardsWithVidPid(vid, pid string) []*cores.Board {
 // FindBoardsWithID FIXMEDOC
 func (pm *PackageManager) FindBoardsWithID(id string) []*cores.Board {
 	res := []*cores.Board{}
-	for _, targetPackage := range pm.packages.Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, targetPlatform := range targetPackage.Platforms {
 			if platform := pm.GetInstalledPlatformRelease(targetPlatform); platform != nil {
 				for _, board := range platform.Boards {
@@ -152,7 +141,7 @@ func (pm *PackageManager) ResolveFQBN(fqbn *cores.FQBN) (
 	*properties.Map, *cores.PlatformRelease, error) {
 
 	// Find package
-	targetPackage := pm.packages.Packages[fqbn.Package]
+	targetPackage := pm.Packages[fqbn.Package]
 	if targetPackage == nil {
 		return nil, nil, nil, nil, nil,
 			errors.New("unknown package " + fqbn.Package)
@@ -189,7 +178,7 @@ func (pm *PackageManager) ResolveFQBN(fqbn *cores.FQBN) (
 	coreParts := strings.Split(buildProperties.Get("build.core"), ":")
 	if len(coreParts) > 1 {
 		referredPackage := coreParts[1]
-		buildPackage := pm.packages.Packages[referredPackage]
+		buildPackage := pm.Packages[referredPackage]
 		if buildPackage == nil {
 			return targetPackage, platformRelease, board, buildProperties, nil,
 				fmt.Errorf("missing package %s:%s required for build", referredPackage, platform)
@@ -215,7 +204,7 @@ func (pm *PackageManager) LoadPackageIndexFromFile(indexPath *paths.Path) (*pack
 		return nil, fmt.Errorf("loading json index file %s: %s", indexPath, err)
 	}
 
-	index.MergeIntoPackages(pm.packages)
+	index.MergeIntoPackages(pm.Packages)
 	return index, nil
 }
 
@@ -224,7 +213,7 @@ func (pm *PackageManager) LoadPackageIndexFromFile(indexPath *paths.Path) (*pack
 func (pm *PackageManager) Package(name string) *PackageActions {
 	//TODO: perhaps these 2 structure should be merged? cores.Packages vs pkgmgr??
 	var err error
-	thePackage := pm.packages.Packages[name]
+	thePackage := pm.Packages[name]
 	if thePackage == nil {
 		err = fmt.Errorf("package '%s' not found", name)
 	}
@@ -350,7 +339,7 @@ func (pm *PackageManager) GetInstalledPlatformRelease(platform *cores.Platform)
 // GetAllInstalledToolsReleases FIXMEDOC
 func (pm *PackageManager) GetAllInstalledToolsReleases() []*cores.ToolRelease {
 	tools := []*cores.ToolRelease{}
-	for _, targetPackage := range pm.packages.Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, tool := range targetPackage.Tools {
 			for _, release := range tool.Releases {
 				if release.IsInstalled() {
@@ -366,7 +355,7 @@ func (pm *PackageManager) GetAllInstalledToolsReleases() []*cores.ToolRelease {
 // useful to range all PlatformReleases in for loops.
 func (pm *PackageManager) InstalledPlatformReleases() []*cores.PlatformRelease {
 	platforms := []*cores.PlatformRelease{}
-	for _, targetPackage := range pm.packages.Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, platform := range targetPackage.Platforms {
 			for _, release := range platform.GetAllInstalled() {
 				platforms = append(platforms, release)
@@ -380,7 +369,7 @@ func (pm *PackageManager) InstalledPlatformReleases() []*cores.PlatformRelease {
 // all Boards in for loops.
 func (pm *PackageManager) InstalledBoards() []*cores.Board {
 	boards := []*cores.Board{}
-	for _, targetPackage := range pm.packages.Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, platform := range targetPackage.Platforms {
 			for _, release := range platform.GetAllInstalled() {
 				for _, board := range release.Boards {
@@ -404,7 +393,7 @@ func (pm *PackageManager) FindToolsRequiredForBoard(board *cores.Board) ([]*core
 
 	// a Platform may not specify required tools (because it's a platform that comes from a
 	// sketchbook/hardware dir without a package_index.json) then add all available tools
-	for _, targetPackage := range pm.packages.Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, tool := range targetPackage.Tools {
 			rel := tool.GetLatestInstalled()
 			if rel != nil {
diff --git a/arduino/cores/status.go b/arduino/cores/status.go
index deea73229b9..305863e3c5b 100644
--- a/arduino/cores/status.go
+++ b/arduino/cores/status.go
@@ -25,15 +25,11 @@ import (
 )
 
 // Packages represents a set of Packages
-type Packages struct {
-	Packages map[string]*Package // Maps packager name to Package
-}
+type Packages map[string]*Package // Maps packager name to Package
 
 // NewPackages creates a new Packages object
-func NewPackages() *Packages {
-	return &Packages{
-		Packages: map[string]*Package{},
-	}
+func NewPackages() Packages {
+	return map[string]*Package{}
 }
 
 // Package represents a package in the system.
@@ -44,13 +40,13 @@ type Package struct {
 	Email      string               // Email of maintainer.
 	Platforms  map[string]*Platform // The platforms in the system.
 	Tools      map[string]*Tool     // The tools in the system.
-	Packages   *Packages            `json:"-"`
+	Packages   Packages             `json:"-"`
 }
 
 // GetOrCreatePackage returns the specified Package or create an empty one
 // filling all the cross-references
-func (packages *Packages) GetOrCreatePackage(packager string) *Package {
-	if targetPackage, ok := packages.Packages[packager]; ok {
+func (packages Packages) GetOrCreatePackage(packager string) *Package {
+	if targetPackage, ok := packages[packager]; ok {
 		return targetPackage
 	}
 	targetPackage := &Package{
@@ -59,15 +55,15 @@ func (packages *Packages) GetOrCreatePackage(packager string) *Package {
 		Tools:     map[string]*Tool{},
 		Packages:  packages,
 	}
-	packages.Packages[packager] = targetPackage
+	packages[packager] = targetPackage
 	return targetPackage
 }
 
 // Names returns the array containing the name of the packages.
-func (packages *Packages) Names() []string {
-	res := make([]string, len(packages.Packages))
+func (packages Packages) Names() []string {
+	res := make([]string, len(packages))
 	i := 0
-	for n := range packages.Packages {
+	for n := range packages {
 		res[i] = n
 		i++
 	}
@@ -75,6 +71,30 @@ func (packages *Packages) Names() []string {
 	return res
 }
 
+// GetDepsOfPlatformRelease returns the deps of a specified release of a core.
+func (packages Packages) GetDepsOfPlatformRelease(release *PlatformRelease) ([]*ToolRelease, error) {
+	if release == nil {
+		return nil, errors.New("release cannot be nil")
+	}
+	ret := []*ToolRelease{}
+	for _, dep := range release.Dependencies {
+		pkg, exists := packages[dep.ToolPackager]
+		if !exists {
+			return nil, fmt.Errorf("package %s not found", dep.ToolPackager)
+		}
+		tool, exists := pkg.Tools[dep.ToolName]
+		if !exists {
+			return nil, fmt.Errorf("tool %s not found", dep.ToolName)
+		}
+		toolRelease, exists := tool.Releases[dep.ToolVersion.String()]
+		if !exists {
+			return nil, fmt.Errorf("tool version %s not found", dep.ToolVersion)
+		}
+		ret = append(ret, toolRelease)
+	}
+	return ret, nil
+}
+
 // GetOrCreatePlatform returns the Platform object with the specified architecture
 // or creates a new one if not found
 func (targetPackage *Package) GetOrCreatePlatform(architecure string) *Platform {
@@ -110,7 +130,7 @@ func (targetPackage *Package) String() string {
 }
 
 func (tdep ToolDependency) extractTool(sc Packages) (*Tool, error) {
-	pkg, exists := sc.Packages[tdep.ToolPackager]
+	pkg, exists := sc[tdep.ToolPackager]
 	if !exists {
 		return nil, errors.New("package not found")
 	}
@@ -132,27 +152,3 @@ func (tdep ToolDependency) extractRelease(sc Packages) (*ToolRelease, error) {
 	}
 	return release, nil
 }
-
-// GetDepsOfPlatformRelease returns the deps of a specified release of a core.
-func (packages *Packages) GetDepsOfPlatformRelease(release *PlatformRelease) ([]*ToolRelease, error) {
-	if release == nil {
-		return nil, errors.New("release cannot be nil")
-	}
-	ret := []*ToolRelease{}
-	for _, dep := range release.Dependencies {
-		pkg, exists := packages.Packages[dep.ToolPackager]
-		if !exists {
-			return nil, fmt.Errorf("package %s not found", dep.ToolPackager)
-		}
-		tool, exists := pkg.Tools[dep.ToolName]
-		if !exists {
-			return nil, fmt.Errorf("tool %s not found", dep.ToolName)
-		}
-		toolRelease, exists := tool.Releases[dep.ToolVersion.String()]
-		if !exists {
-			return nil, fmt.Errorf("tool version %s not found", dep.ToolVersion)
-		}
-		ret = append(ret, toolRelease)
-	}
-	return ret, nil
-}
diff --git a/cli/core/list.go b/cli/core/list.go
index 3f38657ae9c..3620394f069 100644
--- a/cli/core/list.go
+++ b/cli/core/list.go
@@ -18,17 +18,16 @@
 package core
 
 import (
-	"context"
 	"fmt"
 	"os"
 	"sort"
 
+	"github.com/arduino/arduino-cli/arduino/cores"
 	"github.com/arduino/arduino-cli/cli/errorcodes"
 	"github.com/arduino/arduino-cli/cli/instance"
 	"github.com/arduino/arduino-cli/cli/output"
 	"github.com/arduino/arduino-cli/commands/core"
 	"github.com/arduino/arduino-cli/common/formatter"
-	rpc "github.com/arduino/arduino-cli/rpc/commands"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
@@ -54,30 +53,29 @@ func runListCommand(cmd *cobra.Command, args []string) {
 	instance := instance.CreateInstance()
 	logrus.Info("Executing `arduino core list`")
 
-	resp, err := core.PlatformList(context.Background(), &rpc.PlatformListReq{
-		Instance:      instance,
-		UpdatableOnly: listFlags.updatableOnly,
-	})
+	platforms, err := core.GetPlatforms(instance.Id, listFlags.updatableOnly)
 	if err != nil {
 		formatter.PrintError(err, "Error listing platforms")
 		os.Exit(errorcodes.ErrGeneric)
 	}
-	installed := resp.GetInstalledPlatform()
-	if installed != nil && len(installed) > 0 {
-		if output.JSONOrElse(installed) {
-			outputInstalledCores(installed)
-		}
+
+	if output.JSONOrElse(platforms) {
+		outputInstalledCores(platforms)
 	}
 }
 
-func outputInstalledCores(cores []*rpc.Platform) {
+func outputInstalledCores(platforms []*cores.PlatformRelease) {
+	if platforms == nil || len(platforms) == 0 {
+		return
+	}
+
 	table := output.NewTable()
 	table.AddRow("ID", "Installed", "Latest", "Name")
-	sort.Slice(cores, func(i, j int) bool {
-		return cores[i].ID < cores[j].ID
+	sort.Slice(platforms, func(i, j int) bool {
+		return platforms[i].Platform.String() < platforms[j].Platform.String()
 	})
-	for _, item := range cores {
-		table.AddRow(item.GetID(), item.GetInstalled(), item.GetLatest(), item.GetName())
+	for _, p := range platforms {
+		table.AddRow(p.Platform.String(), p.Version.String(), p.Platform.GetLatestRelease().Version.String(), p.Platform.Name)
 	}
 	fmt.Print(table.Render())
 }
diff --git a/cli/core/upgrade.go b/cli/core/upgrade.go
index ec951e7915a..47a3d7a56d9 100644
--- a/cli/core/upgrade.go
+++ b/cli/core/upgrade.go
@@ -19,6 +19,7 @@ package core
 
 import (
 	"context"
+	"fmt"
 	"os"
 
 	"github.com/arduino/arduino-cli/cli/errorcodes"
@@ -51,23 +52,50 @@ func runUpgradeCommand(cmd *cobra.Command, args []string) {
 	instance := instance.CreateInstance()
 	logrus.Info("Executing `arduino core upgrade`")
 
+	// if no platform was passed, upgrade allthethings
+	if len(args) == 0 {
+		targets, err := core.GetPlatforms(instance.Id, true)
+		if err != nil {
+			formatter.PrintError(err, "Error retrieving core list")
+			os.Exit(errorcodes.ErrGeneric)
+		}
+
+		if len(targets) == 0 {
+			formatter.PrintResult("All the cores are already at the latest version")
+			return
+		}
+
+		for _, t := range targets {
+			args = append(args, t.Platform.String())
+		}
+	}
+
+	// proceed upgrading, if anything is upgradable
+	exitErr := false
 	platformsRefs := parsePlatformReferenceArgs(args)
 	for i, platformRef := range platformsRefs {
 		if platformRef.Version != "" {
 			formatter.PrintErrorMessage(("Invalid item " + args[i]))
-			os.Exit(errorcodes.ErrBadArgument)
+			exitErr = true
+			continue
 		}
-	}
-	for _, platformRef := range platformsRefs {
-		_, err := core.PlatformUpgrade(context.Background(), &rpc.PlatformUpgradeReq{
+
+		r := &rpc.PlatformUpgradeReq{
 			Instance:        instance,
 			PlatformPackage: platformRef.Package,
 			Architecture:    platformRef.Architecture,
-		}, output.ProgressBar(), output.TaskProgress(),
-			globals.HTTPClientHeader)
-		if err != nil {
+		}
+
+		_, err := core.PlatformUpgrade(context.Background(), r, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader)
+		if err == core.ErrAlreadyLatest {
+			formatter.PrintResult(fmt.Sprintf("Platform %s is already at the latest version", platformRef))
+		} else if err != nil {
 			formatter.PrintError(err, "Error during upgrade")
 			os.Exit(errorcodes.ErrGeneric)
 		}
 	}
+
+	if exitErr {
+		os.Exit(errorcodes.ErrBadArgument)
+	}
 }
diff --git a/commands/board/listall.go b/commands/board/listall.go
index 7d2dcbc4f1b..6367984c7ec 100644
--- a/commands/board/listall.go
+++ b/commands/board/listall.go
@@ -48,7 +48,7 @@ func ListAll(ctx context.Context, req *rpc.BoardListAllReq) (*rpc.BoardListAllRe
 	}
 
 	list := &rpc.BoardListAllResp{Boards: []*rpc.BoardListItem{}}
-	for _, targetPackage := range pm.GetPackages().Packages {
+	for _, targetPackage := range pm.Packages {
 		for _, platform := range targetPackage.Platforms {
 			platformRelease := pm.GetInstalledPlatformRelease(platform)
 			if platformRelease == nil {
diff --git a/commands/bundled_tools_ctags.go b/commands/bundled_tools_ctags.go
index 643a6cacd48..2c975060be4 100644
--- a/commands/bundled_tools_ctags.go
+++ b/commands/bundled_tools_ctags.go
@@ -25,7 +25,7 @@ import (
 )
 
 func loadBuiltinCtagsMetadata(pm *packagemanager.PackageManager) {
-	builtinPackage := pm.GetPackages().GetOrCreatePackage("builtin")
+	builtinPackage := pm.Packages.GetOrCreatePackage("builtin")
 	ctagsTool := builtinPackage.GetOrCreateTool("ctags")
 	ctagsRel := ctagsTool.GetOrCreateRelease(semver.ParseRelaxed("5.8-arduino11"))
 	ctagsRel.Flavors = []*cores.Flavor{
diff --git a/commands/bundled_tools_serial_discovery.go b/commands/bundled_tools_serial_discovery.go
index 3700ff6cd1e..c481bc08a3d 100644
--- a/commands/bundled_tools_serial_discovery.go
+++ b/commands/bundled_tools_serial_discovery.go
@@ -30,7 +30,7 @@ import (
 var serialDiscoveryVersion = semver.ParseRelaxed("0.5.0")
 
 func loadBuiltinSerialDiscoveryMetadata(pm *packagemanager.PackageManager) {
-	builtinPackage := pm.GetPackages().GetOrCreatePackage("builtin")
+	builtinPackage := pm.Packages.GetOrCreatePackage("builtin")
 	ctagsTool := builtinPackage.GetOrCreateTool("serial-discovery")
 	ctagsRel := ctagsTool.GetOrCreateRelease(serialDiscoveryVersion)
 	ctagsRel.Flavors = []*cores.Flavor{
diff --git a/commands/core/core.go b/commands/core/core.go
index 86845ce9e00..77921555505 100644
--- a/commands/core/core.go
+++ b/commands/core/core.go
@@ -22,10 +22,10 @@ import (
 	rpc "github.com/arduino/arduino-cli/rpc/commands"
 )
 
-// platformReleaseToRPC converts our internal structure to the RPC structure.
+// PlatformReleaseToRPC converts our internal structure to the RPC structure.
 // Note: this function does not touch the "Installed" field of rpc.Platform as it's not always clear that the
 // platformRelease we're currently converting is actually installed.
-func platformReleaseToRPC(platformRelease *cores.PlatformRelease) *rpc.Platform {
+func PlatformReleaseToRPC(platformRelease *cores.PlatformRelease) *rpc.Platform {
 	boards := make([]*rpc.Board, len(platformRelease.Boards))
 	i := 0
 	for _, b := range platformRelease.Boards {
diff --git a/commands/core/list.go b/commands/core/list.go
index 38a79ff3e69..ac1cd234470 100644
--- a/commands/core/list.go
+++ b/commands/core/list.go
@@ -18,36 +18,38 @@
 package core
 
 import (
-	"context"
-	"errors"
-
+	"github.com/arduino/arduino-cli/arduino/cores"
 	"github.com/arduino/arduino-cli/commands"
-	rpc "github.com/arduino/arduino-cli/rpc/commands"
+	"github.com/pkg/errors"
 )
 
-// PlatformList FIXMEDOC
-func PlatformList(ctx context.Context, req *rpc.PlatformListReq) (*rpc.PlatformListResp, error) {
-	pm := commands.GetPackageManager(req)
-	if pm == nil {
+// GetPlatforms returns a list of installed platforms, optionally filtered by
+// those requiring an update.
+func GetPlatforms(instanceID int32, updatableOnly bool) ([]*cores.PlatformRelease, error) {
+	i := commands.GetInstance(instanceID)
+	if i == nil {
+		return nil, errors.Errorf("unable to find an instance with ID: %d", instanceID)
+	}
+
+	packageManager := i.PackageManager
+	if packageManager == nil {
 		return nil, errors.New("invalid instance")
 	}
 
-	installed := []*rpc.Platform{}
-	for _, targetPackage := range pm.GetPackages().Packages {
+	res := []*cores.PlatformRelease{}
+	for _, targetPackage := range packageManager.Packages {
 		for _, platform := range targetPackage.Platforms {
-			if platformRelease := pm.GetInstalledPlatformRelease(platform); platformRelease != nil {
-				if req.GetUpdatableOnly() {
+			if platformRelease := packageManager.GetInstalledPlatformRelease(platform); platformRelease != nil {
+				if updatableOnly {
 					if latest := platform.GetLatestRelease(); latest == nil || latest == platformRelease {
 						continue
 					}
 				}
 
-				p := platformReleaseToRPC(platformRelease)
-				p.Installed = platformRelease.Version.String()
-				installed = append(installed, p)
+				res = append(res, platformRelease)
 			}
 		}
 	}
 
-	return &rpc.PlatformListResp{InstalledPlatform: installed}, nil
+	return res, nil
 }
diff --git a/commands/core/search.go b/commands/core/search.go
index 70e03aa6973..2622d3c39a0 100644
--- a/commands/core/search.go
+++ b/commands/core/search.go
@@ -45,7 +45,7 @@ func PlatformSearch(ctx context.Context, req *rpc.PlatformSearchReq) (*rpc.Platf
 		match := func(line string) bool {
 			return strings.Contains(strings.ToLower(line), search)
 		}
-		for _, targetPackage := range pm.GetPackages().Packages {
+		for _, targetPackage := range pm.Packages {
 			for _, platform := range targetPackage.Platforms {
 				platformRelease := platform.GetLatestRelease()
 				if platformRelease == nil {
@@ -67,7 +67,7 @@ func PlatformSearch(ctx context.Context, req *rpc.PlatformSearchReq) (*rpc.Platf
 
 	out := make([]*rpc.Platform, len(res))
 	for i, platformRelease := range res {
-		out[i] = platformReleaseToRPC(platformRelease)
+		out[i] = PlatformReleaseToRPC(platformRelease)
 	}
 	return &rpc.PlatformSearchResp{SearchOutput: out}, nil
 }
diff --git a/commands/core/upgrade.go b/commands/core/upgrade.go
index 93d2a221383..35608e48697 100644
--- a/commands/core/upgrade.go
+++ b/commands/core/upgrade.go
@@ -28,6 +28,12 @@ import (
 	rpc "github.com/arduino/arduino-cli/rpc/commands"
 )
 
+var (
+	// ErrAlreadyLatest is returned when an upgrade is not possible because
+	// already at latest version.
+	ErrAlreadyLatest = errors.New("platform already at latest version")
+)
+
 // PlatformUpgrade FIXMEDOC
 func PlatformUpgrade(ctx context.Context, req *rpc.PlatformUpgradeReq,
 	downloadCB commands.DownloadProgressCB, taskCB commands.TaskProgressCB, downloaderHeaders http.Header) (*rpc.PlatformUpgradeResp, error) {
@@ -71,7 +77,7 @@ func upgradePlatform(pm *packagemanager.PackageManager, platformRef *packagemana
 	}
 	latest := platform.GetLatestRelease()
 	if !latest.Version.GreaterThan(installed.Version) {
-		return fmt.Errorf("platform %s is already at the latest version", platformRef)
+		return ErrAlreadyLatest
 	}
 	platformRef.PlatformVersion = latest.Version
 	toInstallRefs = append(toInstallRefs, platformRef)
diff --git a/commands/daemon/daemon.go b/commands/daemon/daemon.go
index 2413372ab4e..82e347fc0f1 100644
--- a/commands/daemon/daemon.go
+++ b/commands/daemon/daemon.go
@@ -194,7 +194,19 @@ func (s *ArduinoCoreServerImpl) PlatformSearch(ctx context.Context, req *rpc.Pla
 
 // PlatformList FIXMEDOC
 func (s *ArduinoCoreServerImpl) PlatformList(ctx context.Context, req *rpc.PlatformListReq) (*rpc.PlatformListResp, error) {
-	return core.PlatformList(ctx, req)
+	platforms, err := core.GetPlatforms(req.Instance.Id, req.UpdatableOnly)
+	if err != nil {
+		return nil, err
+	}
+
+	installed := []*rpc.Platform{}
+	for _, p := range platforms {
+		rpcPlatform := core.PlatformReleaseToRPC(p)
+		rpcPlatform.Installed = p.Version.String()
+		installed = append(installed, rpcPlatform)
+	}
+
+	return &rpc.PlatformListResp{InstalledPlatform: installed}, nil
 }
 
 // Upload FIXMEDOC
diff --git a/commands/instances.go b/commands/instances.go
index beffde1a9af..6aa67c79568 100644
--- a/commands/instances.go
+++ b/commands/instances.go
@@ -48,11 +48,11 @@ var instancesCount int32 = 1
 // instantiate as many as needed by providing a different configuration
 // for each one.
 type CoreInstance struct {
-	config      *configs.Configuration
-	pm          *packagemanager.PackageManager
-	lm          *librariesmanager.LibrariesManager
-	getLibOnly  bool
-	discoveries []*discovery.Discovery
+	config         *configs.Configuration
+	PackageManager *packagemanager.PackageManager
+	lm             *librariesmanager.LibrariesManager
+	getLibOnly     bool
+	discoveries    []*discovery.Discovery
 }
 
 // InstanceContainer FIXMEDOC
@@ -60,13 +60,19 @@ type InstanceContainer interface {
 	GetInstance() *rpc.Instance
 }
 
+// GetInstance returns a CoreInstance for the given ID, or nil if ID
+// doesn't exist
+func GetInstance(id int32) *CoreInstance {
+	return instances[id]
+}
+
 // GetPackageManager FIXMEDOC
 func GetPackageManager(req InstanceContainer) *packagemanager.PackageManager {
 	i, ok := instances[req.GetInstance().GetId()]
 	if !ok {
 		return nil
 	}
-	return i.pm
+	return i.PackageManager
 }
 
 // GetLibraryManager FIXMEDOC
@@ -93,11 +99,11 @@ func (instance *CoreInstance) installToolIfMissing(tool *cores.ToolRelease, down
 		return false, nil
 	}
 	taskCB(&rpc.TaskProgress{Name: "Downloading missing tool " + tool.String()})
-	if err := DownloadToolRelease(instance.pm, tool, downloadCB, downloaderHeaders); err != nil {
+	if err := DownloadToolRelease(instance.PackageManager, tool, downloadCB, downloaderHeaders); err != nil {
 		return false, fmt.Errorf("downloading %s tool: %s", tool, err)
 	}
 	taskCB(&rpc.TaskProgress{Completed: true})
-	if err := InstallToolRelease(instance.pm, tool, taskCB); err != nil {
+	if err := InstallToolRelease(instance.PackageManager, tool, taskCB); err != nil {
 		return false, fmt.Errorf("installing %s tool: %s", tool, err)
 	}
 	return true, nil
@@ -106,21 +112,21 @@ func (instance *CoreInstance) installToolIfMissing(tool *cores.ToolRelease, down
 func (instance *CoreInstance) checkForBuiltinTools(downloadCB DownloadProgressCB, taskCB TaskProgressCB,
 	downloaderHeaders http.Header) error {
 	// Check for ctags tool
-	ctags, _ := getBuiltinCtagsTool(instance.pm)
+	ctags, _ := getBuiltinCtagsTool(instance.PackageManager)
 	ctagsInstalled, err := instance.installToolIfMissing(ctags, downloadCB, taskCB, downloaderHeaders)
 	if err != nil {
 		return err
 	}
 
 	// Check for bultin serial-discovery tool
-	serialDiscoveryTool, _ := getBuiltinSerialDiscoveryTool(instance.pm)
+	serialDiscoveryTool, _ := getBuiltinSerialDiscoveryTool(instance.PackageManager)
 	serialDiscoveryInstalled, err := instance.installToolIfMissing(serialDiscoveryTool, downloadCB, taskCB, downloaderHeaders)
 	if err != nil {
 		return err
 	}
 
 	if ctagsInstalled || serialDiscoveryInstalled {
-		if err := instance.pm.LoadHardware(instance.config); err != nil {
+		if err := instance.PackageManager.LoadHardware(instance.config); err != nil {
 			return fmt.Errorf("could not load hardware packages: %s", err)
 		}
 	}
@@ -128,14 +134,14 @@ func (instance *CoreInstance) checkForBuiltinTools(downloadCB DownloadProgressCB
 }
 
 func (instance *CoreInstance) startDiscoveries() error {
-	serialDiscovery, err := newBuiltinSerialDiscovery(instance.pm)
+	serialDiscovery, err := newBuiltinSerialDiscovery(instance.PackageManager)
 	if err != nil {
 		return fmt.Errorf("starting serial discovery: %s", err)
 	}
 
 	discoveriesToStop := instance.discoveries
 	discoveriesToStart := append(
-		discovery.ExtractDiscoveriesFromPlatforms(instance.pm),
+		discovery.ExtractDiscoveriesFromPlatforms(instance.PackageManager),
 		serialDiscovery,
 	)
 
@@ -182,10 +188,10 @@ func Init(ctx context.Context, req *rpc.InitReq, downloadCB DownloadProgressCB,
 		return nil, fmt.Errorf("cannot initialize package manager: %s", err)
 	}
 	instance := &CoreInstance{
-		config:     config,
-		pm:         pm,
-		lm:         lm,
-		getLibOnly: req.GetLibraryManagerOnly()}
+		config:         config,
+		PackageManager: pm,
+		lm:             lm,
+		getLibOnly:     req.GetLibraryManagerOnly()}
 	handle := instancesCount
 	instancesCount++
 	instances[handle] = instance
@@ -305,7 +311,7 @@ func Rescan(ctx context.Context, req *rpc.RescanReq) (*rpc.RescanResp, error) {
 	if err != nil {
 		return nil, fmt.Errorf("rescanning filesystem: %s", err)
 	}
-	coreInstance.pm = pm
+	coreInstance.PackageManager = pm
 	coreInstance.lm = lm
 
 	coreInstance.startDiscoveries()
@@ -357,7 +363,7 @@ func createInstance(ctx context.Context, config *configs.Configuration, getLibOn
 
 	// Add libraries dirs from installed platforms
 	if pm != nil {
-		for _, targetPackage := range pm.GetPackages().Packages {
+		for _, targetPackage := range pm.Packages {
 			for _, platform := range targetPackage.Platforms {
 				if platformRelease := pm.GetInstalledPlatformRelease(platform); platformRelease != nil {
 					lm.AddPlatformReleaseLibrariesDir(platformRelease, libraries.PlatformBuiltIn)
diff --git a/commands/upload/upload.go b/commands/upload/upload.go
index e3dbe1929ad..3dcb1fef4c0 100644
--- a/commands/upload/upload.go
+++ b/commands/upload/upload.go
@@ -93,7 +93,7 @@ func Upload(ctx context.Context, req *rpc.UploadReq, outStream io.Writer, errStr
 		uploadToolPattern = split[1]
 		architecture := board.PlatformRelease.Platform.Architecture
 
-		if referencedPackage := pm.GetPackages().Packages[referencedPackageName]; referencedPackage == nil {
+		if referencedPackage := pm.Packages[referencedPackageName]; referencedPackage == nil {
 			return nil, fmt.Errorf("required platform %s:%s not installed", referencedPackageName, architecture)
 		} else if referencedPlatform := referencedPackage.Platforms[architecture]; referencedPlatform == nil {
 			return nil, fmt.Errorf("required platform %s:%s not installed", referencedPackageName, architecture)
diff --git a/legacy/builder/add_build_board_property_if_missing.go b/legacy/builder/add_build_board_property_if_missing.go
index daad26063c7..329eb9cd8ac 100644
--- a/legacy/builder/add_build_board_property_if_missing.go
+++ b/legacy/builder/add_build_board_property_if_missing.go
@@ -43,7 +43,7 @@ func (*AddBuildBoardPropertyIfMissing) Run(ctx *types.Context) error {
 	packages := ctx.Hardware
 	logger := ctx.GetLogger()
 
-	for _, aPackage := range packages.Packages {
+	for _, aPackage := range packages {
 		for _, platform := range aPackage.Platforms {
 			for _, platformRelease := range platform.Releases {
 				for _, board := range platformRelease.Boards {
diff --git a/legacy/builder/hardware_loader.go b/legacy/builder/hardware_loader.go
index d00a51edc0b..ddd0933fb25 100644
--- a/legacy/builder/hardware_loader.go
+++ b/legacy/builder/hardware_loader.go
@@ -45,6 +45,6 @@ func (s *HardwareLoader) Run(ctx *types.Context) error {
 		}
 		ctx.PackageManager = pm
 	}
-	ctx.Hardware = ctx.PackageManager.GetPackages()
+	ctx.Hardware = ctx.PackageManager.Packages
 	return nil
 }
diff --git a/legacy/builder/rewrite_hardware_keys.go b/legacy/builder/rewrite_hardware_keys.go
index f1e612fd4a9..8117658719b 100644
--- a/legacy/builder/rewrite_hardware_keys.go
+++ b/legacy/builder/rewrite_hardware_keys.go
@@ -46,7 +46,7 @@ func (s *RewriteHardwareKeys) Run(ctx *types.Context) error {
 	platformKeysRewrite := ctx.PlatformKeyRewrites
 	hardwareRewriteResults := ctx.HardwareRewriteResults
 
-	for _, aPackage := range packages.Packages {
+	for _, aPackage := range packages {
 		for _, platform := range aPackage.Platforms {
 			for _, platformRelease := range platform.Releases {
 				if platformRelease.Properties.Get(constants.REWRITING) != constants.REWRITING_DISABLED {
diff --git a/legacy/builder/setup_build_properties.go b/legacy/builder/setup_build_properties.go
index 086eff97495..7ae9a54a3fd 100644
--- a/legacy/builder/setup_build_properties.go
+++ b/legacy/builder/setup_build_properties.go
@@ -90,7 +90,7 @@ func (s *SetupBuildProperties) Run(ctx *types.Context) error {
 		var variantPlatformRelease *cores.PlatformRelease
 		variantParts := strings.Split(variant, ":")
 		if len(variantParts) > 1 {
-			variantPlatform := packages.Packages[variantParts[0]].Platforms[targetPlatform.Platform.Architecture]
+			variantPlatform := packages[variantParts[0]].Platforms[targetPlatform.Platform.Architecture]
 			variantPlatformRelease = ctx.PackageManager.GetInstalledPlatformRelease(variantPlatform)
 			variant = variantParts[1]
 		} else {
diff --git a/legacy/builder/test/hardware_loader_test.go b/legacy/builder/test/hardware_loader_test.go
index 97579da3a79..2ffb7debf1f 100644
--- a/legacy/builder/test/hardware_loader_test.go
+++ b/legacy/builder/test/hardware_loader_test.go
@@ -55,23 +55,23 @@ func TestLoadHardware(t *testing.T) {
 	}
 
 	packages := ctx.Hardware
-	require.Equal(t, 2, len(packages.Packages))
-	require.NotNil(t, packages.Packages["arduino"])
-	require.Equal(t, 2, len(packages.Packages["arduino"].Platforms))
+	require.Equal(t, 2, len(packages))
+	require.NotNil(t, packages["arduino"])
+	require.Equal(t, 2, len(packages["arduino"].Platforms))
 
-	require.Equal(t, "uno", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["uno"].BoardID)
-	require.Equal(t, "uno", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["uno"].Properties.Get("_id"))
+	require.Equal(t, "uno", packages["arduino"].Platforms["avr"].Releases[""].Boards["uno"].BoardID)
+	require.Equal(t, "uno", packages["arduino"].Platforms["avr"].Releases[""].Boards["uno"].Properties.Get("_id"))
 
-	require.Equal(t, "yun", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["yun"].BoardID)
-	require.Equal(t, "true", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["yun"].Properties.Get("upload.wait_for_upload_port"))
+	require.Equal(t, "yun", packages["arduino"].Platforms["avr"].Releases[""].Boards["yun"].BoardID)
+	require.Equal(t, "true", packages["arduino"].Platforms["avr"].Releases[""].Boards["yun"].Properties.Get("upload.wait_for_upload_port"))
 
-	require.Equal(t, "{build.usb_flags}", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["robotMotor"].Properties.Get("build.extra_flags"))
+	require.Equal(t, "{build.usb_flags}", packages["arduino"].Platforms["avr"].Releases[""].Boards["robotMotor"].Properties.Get("build.extra_flags"))
 
-	require.Equal(t, "arduino_due_x", packages.Packages["arduino"].Platforms["sam"].Releases[""].Boards["arduino_due_x"].BoardID)
+	require.Equal(t, "arduino_due_x", packages["arduino"].Platforms["sam"].Releases[""].Boards["arduino_due_x"].BoardID)
 
-	require.Equal(t, "ATmega123", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["diecimila"].Properties.Get("menu.cpu.atmega123"))
+	require.Equal(t, "ATmega123", packages["arduino"].Platforms["avr"].Releases[""].Boards["diecimila"].Properties.Get("menu.cpu.atmega123"))
 
-	avrPlatform := packages.Packages["arduino"].Platforms["avr"]
+	avrPlatform := packages["arduino"].Platforms["avr"]
 	require.Equal(t, "Arduino AVR Boards", avrPlatform.Releases[""].Properties.Get("name"))
 	require.Equal(t, "-v", avrPlatform.Releases[""].Properties.Get("tools.avrdude.bootloader.params.verbose"))
 	require.Equal(t, "/my/personal/avrdude", avrPlatform.Releases[""].Properties.Get("tools.avrdude.cmd.path"))
@@ -105,25 +105,25 @@ func TestLoadHardwareMixingUserHardwareFolder(t *testing.T) {
 
 	if runtime.GOOS == "windows" {
 		//a package is a symlink, and windows does not support them
-		require.Equal(t, 3, len(packages.Packages))
+		require.Equal(t, 3, len(packages))
 	} else {
-		require.Equal(t, 4, len(packages.Packages))
+		require.Equal(t, 4, len(packages))
 	}
 
-	require.NotNil(t, packages.Packages["arduino"])
-	require.Equal(t, 2, len(packages.Packages["arduino"].Platforms))
+	require.NotNil(t, packages["arduino"])
+	require.Equal(t, 2, len(packages["arduino"].Platforms))
 
-	require.Equal(t, "uno", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["uno"].BoardID)
-	require.Equal(t, "uno", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["uno"].Properties.Get("_id"))
+	require.Equal(t, "uno", packages["arduino"].Platforms["avr"].Releases[""].Boards["uno"].BoardID)
+	require.Equal(t, "uno", packages["arduino"].Platforms["avr"].Releases[""].Boards["uno"].Properties.Get("_id"))
 
-	require.Equal(t, "yun", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["yun"].BoardID)
-	require.Equal(t, "true", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["yun"].Properties.Get("upload.wait_for_upload_port"))
+	require.Equal(t, "yun", packages["arduino"].Platforms["avr"].Releases[""].Boards["yun"].BoardID)
+	require.Equal(t, "true", packages["arduino"].Platforms["avr"].Releases[""].Boards["yun"].Properties.Get("upload.wait_for_upload_port"))
 
-	require.Equal(t, "{build.usb_flags}", packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards["robotMotor"].Properties.Get("build.extra_flags"))
+	require.Equal(t, "{build.usb_flags}", packages["arduino"].Platforms["avr"].Releases[""].Boards["robotMotor"].Properties.Get("build.extra_flags"))
 
-	require.Equal(t, "arduino_due_x", packages.Packages["arduino"].Platforms["sam"].Releases[""].Boards["arduino_due_x"].BoardID)
+	require.Equal(t, "arduino_due_x", packages["arduino"].Platforms["sam"].Releases[""].Boards["arduino_due_x"].BoardID)
 
-	avrPlatform := packages.Packages["arduino"].Platforms["avr"].Releases[""]
+	avrPlatform := packages["arduino"].Platforms["avr"].Releases[""]
 	require.Equal(t, "Arduino AVR Boards", avrPlatform.Properties.Get("name"))
 	require.Equal(t, "-v", avrPlatform.Properties.Get("tools.avrdude.bootloader.params.verbose"))
 	require.Equal(t, "/my/personal/avrdude", avrPlatform.Properties.Get("tools.avrdude.cmd.path"))
@@ -135,8 +135,8 @@ func TestLoadHardwareMixingUserHardwareFolder(t *testing.T) {
 	require.Equal(t, "\"{compiler.path}{compiler.cpp.cmd}\" {compiler.cpp.flags} {preproc.includes.flags} -mmcu={build.mcu} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.cpp.extra_flags} {build.extra_flags} {includes} \"{source_file}\"", avrPlatform.Properties.Get("recipe.preproc.includes"))
 	require.False(t, avrPlatform.Properties.ContainsKey("preproc.macros.compatibility_flags"))
 
-	require.NotNil(t, packages.Packages["my_avr_platform"])
-	myAVRPlatform := packages.Packages["my_avr_platform"]
+	require.NotNil(t, packages["my_avr_platform"])
+	myAVRPlatform := packages["my_avr_platform"]
 	//require.Equal(t, "hello world", myAVRPlatform.Properties.Get("example"))
 	myAVRPlatformAvrArch := myAVRPlatform.Platforms["avr"].Releases[""]
 	require.Equal(t, "custom_yun", myAVRPlatformAvrArch.Boards["custom_yun"].BoardID)
@@ -149,8 +149,8 @@ func TestLoadHardwareMixingUserHardwareFolder(t *testing.T) {
 	//require.Equal(t, "-w -x c++ -E -CC", packages.Properties.Get("preproc.macros.flags"))
 
 	if runtime.GOOS != "windows" {
-		require.NotNil(t, packages.Packages["my_symlinked_avr_platform"])
-		require.NotNil(t, packages.Packages["my_symlinked_avr_platform"].Platforms["avr"])
+		require.NotNil(t, packages["my_symlinked_avr_platform"])
+		require.NotNil(t, packages["my_symlinked_avr_platform"].Platforms["avr"])
 	}
 }
 
@@ -169,15 +169,15 @@ func TestLoadHardwareWithBoardManagerFolderStructure(t *testing.T) {
 	}
 
 	packages := ctx.Hardware
-	require.Equal(t, 3, len(packages.Packages))
-	require.NotNil(t, packages.Packages["arduino"])
-	require.Equal(t, 1, len(packages.Packages["arduino"].Platforms))
-	require.NotNil(t, packages.Packages["RedBearLab"])
-	require.Equal(t, 1, len(packages.Packages["RedBearLab"].Platforms))
-	require.NotNil(t, packages.Packages["RFduino"])
-	require.Equal(t, 0, len(packages.Packages["RFduino"].Platforms))
-
-	samdPlatform := packages.Packages["arduino"].Platforms["samd"].Releases["1.6.5"]
+	require.Equal(t, 3, len(packages))
+	require.NotNil(t, packages["arduino"])
+	require.Equal(t, 1, len(packages["arduino"].Platforms))
+	require.NotNil(t, packages["RedBearLab"])
+	require.Equal(t, 1, len(packages["RedBearLab"].Platforms))
+	require.NotNil(t, packages["RFduino"])
+	require.Equal(t, 0, len(packages["RFduino"].Platforms))
+
+	samdPlatform := packages["arduino"].Platforms["samd"].Releases["1.6.5"]
 	require.Equal(t, 3, len(samdPlatform.Boards))
 
 	require.Equal(t, "arduino_zero_edbg", samdPlatform.Boards["arduino_zero_edbg"].BoardID)
@@ -194,7 +194,7 @@ func TestLoadHardwareWithBoardManagerFolderStructure(t *testing.T) {
 	require.Equal(t, "Atmel EDBG", samdPlatform.Programmers["edbg"].Get("name"))
 	require.Equal(t, "openocd", samdPlatform.Programmers["edbg"].Get("program.tool"))
 
-	avrRedBearPlatform := packages.Packages["RedBearLab"].Platforms["avr"].Releases["1.0.0"]
+	avrRedBearPlatform := packages["RedBearLab"].Platforms["avr"].Releases["1.0.0"]
 	require.Equal(t, 3, len(avrRedBearPlatform.Boards))
 
 	require.Equal(t, "blend", avrRedBearPlatform.Boards["blend"].BoardID)
@@ -220,24 +220,24 @@ func TestLoadLotsOfHardware(t *testing.T) {
 
 	if runtime.GOOS == "windows" {
 		//a package is a symlink, and windows does not support them
-		require.Equal(t, 5, len(packages.Packages))
+		require.Equal(t, 5, len(packages))
 	} else {
-		require.Equal(t, 6, len(packages.Packages))
+		require.Equal(t, 6, len(packages))
 	}
 
-	require.NotNil(t, packages.Packages["arduino"])
-	require.NotNil(t, packages.Packages["my_avr_platform"])
+	require.NotNil(t, packages["arduino"])
+	require.NotNil(t, packages["my_avr_platform"])
 
-	require.Equal(t, 3, len(packages.Packages["arduino"].Platforms))
-	require.Equal(t, 20, len(packages.Packages["arduino"].Platforms["avr"].Releases[""].Boards))
-	require.Equal(t, 2, len(packages.Packages["arduino"].Platforms["sam"].Releases[""].Boards))
-	require.Equal(t, 3, len(packages.Packages["arduino"].Platforms["samd"].Releases["1.6.5"].Boards))
+	require.Equal(t, 3, len(packages["arduino"].Platforms))
+	require.Equal(t, 20, len(packages["arduino"].Platforms["avr"].Releases[""].Boards))
+	require.Equal(t, 2, len(packages["arduino"].Platforms["sam"].Releases[""].Boards))
+	require.Equal(t, 3, len(packages["arduino"].Platforms["samd"].Releases["1.6.5"].Boards))
 
-	require.Equal(t, 1, len(packages.Packages["my_avr_platform"].Platforms))
-	require.Equal(t, 2, len(packages.Packages["my_avr_platform"].Platforms["avr"].Releases[""].Boards))
+	require.Equal(t, 1, len(packages["my_avr_platform"].Platforms))
+	require.Equal(t, 2, len(packages["my_avr_platform"].Platforms["avr"].Releases[""].Boards))
 
 	if runtime.GOOS != "windows" {
-		require.Equal(t, 1, len(packages.Packages["my_symlinked_avr_platform"].Platforms))
-		require.Equal(t, 2, len(packages.Packages["my_symlinked_avr_platform"].Platforms["avr"].Releases[""].Boards))
+		require.Equal(t, 1, len(packages["my_symlinked_avr_platform"].Platforms))
+		require.Equal(t, 2, len(packages["my_symlinked_avr_platform"].Platforms["avr"].Releases[""].Boards))
 	}
 }
diff --git a/legacy/builder/test/rewrite_hardware_keys_test.go b/legacy/builder/test/rewrite_hardware_keys_test.go
index 5af3faf88af..a10de59f16f 100644
--- a/legacy/builder/test/rewrite_hardware_keys_test.go
+++ b/legacy/builder/test/rewrite_hardware_keys_test.go
@@ -42,10 +42,9 @@ import (
 func TestRewriteHardwareKeys(t *testing.T) {
 	ctx := &types.Context{}
 
-	packages := &cores.Packages{}
-	packages.Packages = map[string]*cores.Package{}
+	packages := cores.Packages{}
 	aPackage := &cores.Package{Name: "dummy"}
-	packages.Packages["dummy"] = aPackage
+	packages["dummy"] = aPackage
 	aPackage.Platforms = map[string]*cores.Platform{}
 
 	platform := &cores.PlatformRelease{
@@ -83,10 +82,9 @@ func TestRewriteHardwareKeys(t *testing.T) {
 func TestRewriteHardwareKeysWithRewritingDisabled(t *testing.T) {
 	ctx := &types.Context{}
 
-	packages := &cores.Packages{}
-	packages.Packages = make(map[string]*cores.Package)
+	packages := cores.Packages{}
 	aPackage := &cores.Package{Name: "dummy"}
-	packages.Packages["dummy"] = aPackage
+	packages["dummy"] = aPackage
 	aPackage.Platforms = make(map[string]*cores.Platform)
 
 	platform := &cores.PlatformRelease{
diff --git a/legacy/builder/types/context.go b/legacy/builder/types/context.go
index c1cfadfbb31..e2ed7ff8706 100644
--- a/legacy/builder/types/context.go
+++ b/legacy/builder/types/context.go
@@ -39,7 +39,7 @@ type Context struct {
 	BuildOptionsJsonPrevious string
 
 	PackageManager *packagemanager.PackageManager
-	Hardware       *cores.Packages
+	Hardware       cores.Packages
 	AllTools       []*cores.ToolRelease
 	RequiredTools  []*cores.ToolRelease
 	TargetBoard    *cores.Board