Unverified Commit 03b77381 authored by Silvano Cerza's avatar Silvano Cerza Committed by GitHub

Enhances tracking of installed platforms (#998)

parent 44446539
...@@ -35,12 +35,18 @@ type Platform struct { ...@@ -35,12 +35,18 @@ type Platform struct {
Package *Package `json:"-"` Package *Package `json:"-"`
} }
// PlatformReleaseHelp represents the help URL for this Platform release
type PlatformReleaseHelp struct {
Online string `json:"-"`
}
// PlatformRelease represents a release of a plaform package. // PlatformRelease represents a release of a plaform package.
type PlatformRelease struct { type PlatformRelease struct {
Resource *resources.DownloadResource Resource *resources.DownloadResource
Version *semver.Version Version *semver.Version
BoardsManifest []*BoardManifest BoardsManifest []*BoardManifest
Dependencies ToolDependencies // The Dependency entries to load tools. Dependencies ToolDependencies // The Dependency entries to load tools.
Help PlatformReleaseHelp `json:"-"`
Platform *Platform `json:"-"` Platform *Platform `json:"-"`
Properties *properties.Map `json:"-"` Properties *properties.Map `json:"-"`
Boards map[string]*Board `json:"-"` Boards map[string]*Board `json:"-"`
......
...@@ -86,13 +86,15 @@ type indexToolReleaseFlavour struct { ...@@ -86,13 +86,15 @@ type indexToolReleaseFlavour struct {
// indexBoard represents a single Board as written in package_index.json file. // indexBoard represents a single Board as written in package_index.json file.
type indexBoard struct { type indexBoard struct {
Name string `json:"name"` Name string `json:"name"`
ID []indexBoardID `json:"id"` ID []indexBoardID `json:"id,omitempty"`
} }
// indexBoardID represents the ID of a single board. i.e. uno, yun, diecimila, micro and the likes
type indexBoardID struct { type indexBoardID struct {
USB string `json:"usb"` USB string `json:"usb"`
} }
// indexHelp represents the help URL
type indexHelp struct { type indexHelp struct {
Online string `json:"online,omitempty"` Online string `json:"online,omitempty"`
} }
...@@ -105,6 +107,82 @@ func (index Index) MergeIntoPackages(outPackages cores.Packages) { ...@@ -105,6 +107,82 @@ func (index Index) MergeIntoPackages(outPackages cores.Packages) {
} }
} }
// IndexFromPlatformRelease creates an Index that contains a single indexPackage
// which in turn contains a single indexPlatformRelease converted from the one
// passed as argument
func IndexFromPlatformRelease(pr *cores.PlatformRelease) Index {
boards := []indexBoard{}
for _, manifest := range pr.BoardsManifest {
board := indexBoard{
Name: manifest.Name,
}
for _, id := range manifest.ID {
if id.USB != "" {
board.ID = []indexBoardID{{USB: id.USB}}
}
}
boards = append(boards, board)
}
tools := []indexToolDependency{}
for _, t := range pr.Dependencies {
tools = append(tools, indexToolDependency{
Packager: t.ToolPackager,
Name: t.ToolName,
Version: t.ToolVersion,
})
}
packageTools := []*indexToolRelease{}
for name, tool := range pr.Platform.Package.Tools {
for _, toolRelease := range tool.Releases {
flavours := []indexToolReleaseFlavour{}
for _, flavour := range toolRelease.Flavors {
flavours = append(flavours, indexToolReleaseFlavour{
OS: flavour.OS,
URL: flavour.Resource.URL,
ArchiveFileName: flavour.Resource.ArchiveFileName,
Size: json.Number(fmt.Sprintf("%d", flavour.Resource.Size)),
Checksum: flavour.Resource.Checksum,
})
}
packageTools = append(packageTools, &indexToolRelease{
Name: name,
Version: toolRelease.Version,
Systems: flavours,
})
}
}
return Index{
IsTrusted: pr.IsTrusted,
Packages: []*indexPackage{
{
Name: pr.Platform.Package.Name,
Maintainer: pr.Platform.Package.Maintainer,
WebsiteURL: pr.Platform.Package.WebsiteURL,
URL: pr.Platform.Package.URL,
Email: pr.Platform.Package.Email,
Platforms: []*indexPlatformRelease{{
Name: pr.Platform.Name,
Architecture: pr.Platform.Architecture,
Version: pr.Version,
Category: pr.Platform.Category,
URL: pr.Resource.URL,
ArchiveFileName: pr.Resource.ArchiveFileName,
Checksum: pr.Resource.Checksum,
Size: json.Number(fmt.Sprintf("%d", pr.Resource.Size)),
Boards: boards,
Help: indexHelp{Online: pr.Help.Online},
ToolDependencies: tools,
}},
Tools: packageTools,
Help: indexHelp{Online: pr.Platform.Package.Help.Online},
},
},
}
}
func (inPackage indexPackage) extractPackageIn(outPackages cores.Packages, trusted bool) { func (inPackage indexPackage) extractPackageIn(outPackages cores.Packages, trusted bool) {
outPackage := outPackages.GetOrCreatePackage(inPackage.Name) outPackage := outPackages.GetOrCreatePackage(inPackage.Name)
outPackage.Maintainer = inPackage.Maintainer outPackage.Maintainer = inPackage.Maintainer
...@@ -144,6 +222,7 @@ func (inPlatformRelease indexPlatformRelease) extractPlatformIn(outPackage *core ...@@ -144,6 +222,7 @@ func (inPlatformRelease indexPlatformRelease) extractPlatformIn(outPackage *core
URL: inPlatformRelease.URL, URL: inPlatformRelease.URL,
CachePath: "packages", CachePath: "packages",
} }
outPlatformRelease.Help = cores.PlatformReleaseHelp{Online: inPlatformRelease.Help.Online}
outPlatformRelease.BoardsManifest = inPlatformRelease.extractBoardsManifest() outPlatformRelease.BoardsManifest = inPlatformRelease.extractBoardsManifest()
if deps, err := inPlatformRelease.extractDeps(); err == nil { if deps, err := inPlatformRelease.extractDeps(); err == nil {
outPlatformRelease.Dependencies = deps outPlatformRelease.Dependencies = deps
......
This diff is collapsed.
...@@ -16,10 +16,12 @@ ...@@ -16,10 +16,12 @@
package packagemanager package packagemanager
import ( import (
"encoding/json"
"fmt" "fmt"
"runtime" "runtime"
"github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/arduino/cores/packageindex"
"github.com/arduino/arduino-cli/executils" "github.com/arduino/arduino-cli/executils"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
...@@ -39,6 +41,20 @@ func (pm *PackageManager) InstallPlatform(platformRelease *cores.PlatformRelease ...@@ -39,6 +41,20 @@ func (pm *PackageManager) InstallPlatform(platformRelease *cores.PlatformRelease
} else { } else {
return err return err
} }
if err := pm.cacheInstalledJSON(platformRelease); err != nil {
return errors.Errorf("creating installed.json in %s: %s", platformRelease.InstallDir, err)
}
return nil
}
func (pm *PackageManager) cacheInstalledJSON(platformRelease *cores.PlatformRelease) error {
index := packageindex.IndexFromPlatformRelease(platformRelease)
platformJSON, err := json.MarshalIndent(index, "", " ")
if err != nil {
return err
}
installedJSON := platformRelease.InstallDir.Join("installed.json")
installedJSON.WriteFile(platformJSON)
return nil return nil
} }
......
...@@ -265,10 +265,20 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p ...@@ -265,10 +265,20 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
platform.InstallDir = path platform.InstallDir = path
// Some useful paths // Some useful paths
installedJSONPath := path.Join("installed.json")
platformTxtPath := path.Join("platform.txt") platformTxtPath := path.Join("platform.txt")
platformTxtLocalPath := path.Join("platform.local.txt") platformTxtLocalPath := path.Join("platform.local.txt")
programmersTxtPath := path.Join("programmers.txt") programmersTxtPath := path.Join("programmers.txt")
// If the installed.json file is found load it, this is done to handle the
// case in which the platform's index and its url have been deleted locally,
// if we don't load it some information about the platform is lost
if installedJSONPath.Exist() {
if _, err := pm.LoadPackageIndexFromFile(installedJSONPath); err != nil {
return fmt.Errorf("loading %s: %s", installedJSONPath, err)
}
}
// Create platform properties // Create platform properties
platform.Properties = platform.Properties.Clone() // TODO: why CLONE? platform.Properties = platform.Properties.Clone() // TODO: why CLONE?
if p, err := properties.SafeLoad(platformTxtPath.String()); err == nil { if p, err := properties.SafeLoad(platformTxtPath.String()); err == nil {
......
...@@ -131,7 +131,7 @@ def run_command(pytestconfig, data_dir, downloads_dir, working_dir): ...@@ -131,7 +131,7 @@ def run_command(pytestconfig, data_dir, downloads_dir, working_dir):
# It escapes spaces in the path using "\ " but it doesn't always work, # It escapes spaces in the path using "\ " but it doesn't always work,
# wrapping the path in quotation marks is the safest approach # wrapping the path in quotation marks is the safest approach
with run_context.prefix(f'{cd_command} "{custom_working_dir}"'): with run_context.prefix(f'{cd_command} "{custom_working_dir}"'):
return run_context.run(cli_full_line, echo=False, hide=True, warn=True, env=custom_env) return run_context.run(cli_full_line, echo=False, hide=True, warn=True, env=custom_env, encoding="utf-8")
return _run return _run
......
...@@ -12,11 +12,8 @@ ...@@ -12,11 +12,8 @@
# otherwise use the software for commercial activities involving the Arduino # otherwise use the software for commercial activities involving the Arduino
# software without disclosing the source code of your own applications. To purchase # software without disclosing the source code of your own applications. To purchase
# a commercial license, send an email to license@arduino.cc. # a commercial license, send an email to license@arduino.cc.
import pytest
import simplejson as json import simplejson as json
from .common import running_on_ci
gold_board = """ gold_board = """
{ {
...@@ -401,12 +398,40 @@ def test_board_list(run_command): ...@@ -401,12 +398,40 @@ def test_board_list(run_command):
assert "protocol_label" in port assert "protocol_label" in port
@pytest.mark.skipif(running_on_ci(), reason="VMs have no serial ports")
def test_board_listall(run_command): def test_board_listall(run_command):
run_command("core update-index") run_command("core update-index")
result = run_command("board listall") run_command("core install arduino:avr@1.8.3")
assert result.ok res = run_command("board listall")
assert ["Board", "Name", "FQBN"] == result.stdout.splitlines()[0].strip().split() assert res.ok
lines = [l.rsplit(maxsplit=1) for l in res.stdout.strip().splitlines()]
assert len(lines) == 27
assert ["Board Name", "FQBN"] in lines
assert ["Arduino Yún", "arduino:avr:yun"] in lines
assert ["Arduino Uno", "arduino:avr:uno"] in lines
assert ["Arduino Duemilanove or Diecimila", "arduino:avr:diecimila"] in lines
assert ["Arduino Nano", "arduino:avr:nano"] in lines
assert ["Arduino Mega or Mega 2560", "arduino:avr:mega"] in lines
assert ["Arduino Mega ADK", "arduino:avr:megaADK"] in lines
assert ["Arduino Leonardo", "arduino:avr:leonardo"] in lines
assert ["Arduino Leonardo ETH", "arduino:avr:leonardoeth"] in lines
assert ["Arduino Micro", "arduino:avr:micro"] in lines
assert ["Arduino Esplora", "arduino:avr:esplora"] in lines
assert ["Arduino Mini", "arduino:avr:mini"] in lines
assert ["Arduino Ethernet", "arduino:avr:ethernet"] in lines
assert ["Arduino Fio", "arduino:avr:fio"] in lines
assert ["Arduino BT", "arduino:avr:bt"] in lines
assert ["LilyPad Arduino USB", "arduino:avr:LilyPadUSB"] in lines
assert ["LilyPad Arduino", "arduino:avr:lilypad"] in lines
assert ["Arduino Pro or Pro Mini", "arduino:avr:pro"] in lines
assert ["Arduino NG or older", "arduino:avr:atmegang"] in lines
assert ["Arduino Robot Control", "arduino:avr:robotControl"] in lines
assert ["Arduino Robot Motor", "arduino:avr:robotMotor"] in lines
assert ["Arduino Gemma", "arduino:avr:gemma"] in lines
assert ["Adafruit Circuit Playground", "arduino:avr:circuitplay32u4cat"] in lines
assert ["Arduino Yún Mini", "arduino:avr:yunmini"] in lines
assert ["Arduino Industrial 101", "arduino:avr:chiwawa"] in lines
assert ["Linino One", "arduino:avr:one"] in lines
assert ["Arduino Uno WiFi", "arduino:avr:unowifi"] in lines
def test_board_details(run_command): def test_board_details(run_command):
......
...@@ -95,17 +95,24 @@ def test_core_search_no_args(run_command, httpserver): ...@@ -95,17 +95,24 @@ def test_core_search_no_args(run_command, httpserver):
result = run_command("core search") result = run_command("core search")
assert result.ok assert result.ok
num_platforms = 0 num_platforms = 0
found = False
for l in result.stdout.splitlines()[1:]: # ignore the header on first line for l in result.stdout.splitlines()[1:]: # ignore the header on first line
if l: # ignore empty lines if l: # ignore empty lines
assert not l.startswith("test:x86") if l.startswith("test:x86"):
found = True
num_platforms += 1 num_platforms += 1
# same thing in JSON format, also check the number of platforms found is the same # same thing in JSON format, also check the number of platforms found is the same
result = run_command("core search --format json") result = run_command("core search --format json")
assert result.ok assert result.ok
platforms = json.loads(result.stdout) platforms = json.loads(result.stdout)
found = False
platforms = json.loads(result.stdout)
for elem in platforms: for elem in platforms:
assert elem.get("Name") != "test_core" if elem.get("Name") == "test_core":
found = True
break
assert found
assert len(platforms) == num_platforms assert len(platforms) == num_platforms
# list all with additional urls, check the test core is there # list all with additional urls, check the test core is there
...@@ -270,3 +277,24 @@ def test_core_broken_install(run_command): ...@@ -270,3 +277,24 @@ def test_core_broken_install(run_command):
url = "https://raw.githubusercontent.com/arduino/arduino-cli/master/test/testdata/test_index.json" url = "https://raw.githubusercontent.com/arduino/arduino-cli/master/test/testdata/test_index.json"
assert run_command("core update-index --additional-urls={}".format(url)) assert run_command("core update-index --additional-urls={}".format(url))
assert not run_command("core install brokenchecksum:x86 --additional-urls={}".format(url)) assert not run_command("core install brokenchecksum:x86 --additional-urls={}".format(url))
def test_core_install_creates_installed_json(run_command, data_dir):
assert run_command("core update-index")
assert run_command("core install arduino:avr@1.6.23")
installed_json_file = Path(data_dir, "packages", "arduino", "hardware", "avr", "1.6.23", "installed.json")
assert installed_json_file.exists()
installed_json = json.load(installed_json_file.open("r"))
expected_installed_json = json.load((Path(__file__).parent / "testdata" / "installed.json").open("r"))
def ordered(obj):
if isinstance(obj, dict):
return sorted({k: ordered(v) for k, v in obj.items()})
if isinstance(obj, list):
return sorted(ordered(x) for x in obj)
else:
return obj
assert ordered(installed_json) == ordered(expected_installed_json)
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment