Skip to content

Automatically download indexes, if missing, in gRPC Init call #2119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions arduino/resources/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ type IndexResource struct {
SignatureURL *url.URL
}

// IndexFileName returns the index file name as it is saved in data dir (package_xxx_index.json).
func (res *IndexResource) IndexFileName() string {
filename := path.Base(res.URL.Path) // == package_index.json[.gz] || packacge_index.tar.bz2
if i := strings.Index(filename, "."); i != -1 {
filename = filename[:i]
}
return filename + ".json"
}

// Download will download the index and possibly check the signature using the Arduino's public key.
// If the file is in .gz format it will be unpacked first.
func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadProgressCB) error {
Expand All @@ -53,18 +62,18 @@ func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadP
defer tmp.RemoveAll()

// Download index file
indexFileName := path.Base(res.URL.Path) // == package_index.json[.gz]
tmpIndexPath := tmp.Join(indexFileName)
if err := httpclient.DownloadFile(tmpIndexPath, res.URL.String(), "", tr("Downloading index: %s", indexFileName), downloadCB, nil, downloader.NoResume); err != nil {
downloadFileName := path.Base(res.URL.Path) // == package_index.json[.gz] || package_index.tar.bz2
indexFileName := res.IndexFileName() // == package_index.json
tmpIndexPath := tmp.Join(downloadFileName)
if err := httpclient.DownloadFile(tmpIndexPath, res.URL.String(), "", tr("Downloading index: %s", downloadFileName), downloadCB, nil, downloader.NoResume); err != nil {
return &arduino.FailedDownloadError{Message: tr("Error downloading index '%s'", res.URL), Cause: err}
}

var signaturePath, tmpSignaturePath *paths.Path
hasSignature := false

// Expand the index if it is compressed
if strings.HasSuffix(indexFileName, ".tar.bz2") {
indexFileName = strings.TrimSuffix(indexFileName, ".tar.bz2") + ".json" // == package_index.json
if strings.HasSuffix(downloadFileName, ".tar.bz2") {
signatureFileName := indexFileName + ".sig"
signaturePath = destDir.Join(signatureFileName)

Expand Down Expand Up @@ -95,8 +104,7 @@ func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadP
} else {
logrus.Infof("No signature %s found in package index archive %s", signatureFileName, tmpArchivePath.Base())
}
} else if strings.HasSuffix(indexFileName, ".gz") {
indexFileName = strings.TrimSuffix(indexFileName, ".gz") // == package_index.json
} else if strings.HasSuffix(downloadFileName, ".gz") {
tmpUnzippedIndexPath := tmp.Join(indexFileName)
if err := paths.GUnzip(tmpIndexPath, tmpUnzippedIndexPath); err != nil {
return &arduino.PermissionDeniedError{Message: tr("Error extracting %s", indexFileName), Cause: err}
Expand Down
6 changes: 3 additions & 3 deletions commands/core/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import (
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
)

// GetPlatforms returns a list of installed platforms, optionally filtered by
// PlatformList returns a list of installed platforms, optionally filtered by
// those requiring an update.
func GetPlatforms(req *rpc.PlatformListRequest) ([]*rpc.Platform, error) {
func PlatformList(req *rpc.PlatformListRequest) (*rpc.PlatformListResponse, error) {
pme, release := commands.GetPackageManagerExplorer(req)
if pme == nil {
return nil, &arduino.InvalidInstanceError{}
Expand Down Expand Up @@ -85,5 +85,5 @@ func GetPlatforms(req *rpc.PlatformListRequest) ([]*rpc.Platform, error) {
}
return false
})
return res, nil
return &rpc.PlatformListResponse{InstalledPlatforms: res}, nil
}
7 changes: 2 additions & 5 deletions commands/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,8 @@ func (s *ArduinoCoreServerImpl) PlatformSearch(ctx context.Context, req *rpc.Pla

// PlatformList FIXMEDOC
func (s *ArduinoCoreServerImpl) PlatformList(ctx context.Context, req *rpc.PlatformListRequest) (*rpc.PlatformListResponse, error) {
platforms, err := core.GetPlatforms(req)
if err != nil {
return nil, convertErrorToRPCStatus(err)
}
return &rpc.PlatformListResponse{InstalledPlatforms: platforms}, nil
platforms, err := core.PlatformList(req)
return platforms, convertErrorToRPCStatus(err)
}

// Upload FIXMEDOC
Expand Down
71 changes: 57 additions & 14 deletions commands/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,20 +262,11 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
})
}

{
// We need to rebuild the PackageManager currently in use by this instance
// in case this is not the first Init on this instances, that might happen
// after reinitializing an instance after installing or uninstalling a core.
// If this is not done the information of the uninstall core is kept in memory,
// even if it should not.
pmb, commitPackageManager := instance.pm.NewBuilder()

// Load packages index
urls := []string{globals.DefaultIndexURL}
if profile == nil {
urls = append(urls, configuration.Settings.GetStringSlice("board_manager.additional_urls")...)
}
for _, u := range urls {
// Perform first-update of indexes if needed
defaultIndexURL, _ := utils.URLParse(globals.DefaultIndexURL)
allPackageIndexUrls := []*url.URL{defaultIndexURL}
if profile == nil {
for _, u := range configuration.Settings.GetStringSlice("board_manager.additional_urls") {
URL, err := utils.URLParse(u)
if err != nil {
e := &arduino.InitFailedError{
Expand All @@ -286,7 +277,21 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
responseError(e.ToRPCStatus())
continue
}
allPackageIndexUrls = append(allPackageIndexUrls, URL)
}
}
firstUpdate(context.Background(), req.GetInstance(), downloadCallback, allPackageIndexUrls)

{
// We need to rebuild the PackageManager currently in use by this instance
// in case this is not the first Init on this instances, that might happen
// after reinitializing an instance after installing or uninstalling a core.
// If this is not done the information of the uninstall core is kept in memory,
// even if it should not.
pmb, commitPackageManager := instance.pm.NewBuilder()

// Load packages index
for _, URL := range allPackageIndexUrls {
if URL.Scheme == "file" {
_, err := pmb.LoadPackageIndexFromFile(paths.New(URL.Path))
if err != nil {
Expand Down Expand Up @@ -595,3 +600,41 @@ func LoadSketch(ctx context.Context, req *rpc.LoadSketchRequest) (*rpc.LoadSketc
RootFolderFiles: rootFolderFiles,
}, nil
}

// firstUpdate downloads libraries and packages indexes if they don't exist.
// This ideally is only executed the first time the CLI is run.
func firstUpdate(ctx context.Context, instance *rpc.Instance, downloadCb func(msg *rpc.DownloadProgress), externalPackageIndexes []*url.URL) error {
// Gets the data directory to verify if library_index.json and package_index.json exist
dataDir := configuration.DataDir(configuration.Settings)
libraryIndex := dataDir.Join("library_index.json")

if libraryIndex.NotExist() {
// The library_index.json file doesn't exists, that means the CLI is run for the first time
// so we proceed with the first update that downloads the file
req := &rpc.UpdateLibrariesIndexRequest{Instance: instance}
if err := UpdateLibrariesIndex(ctx, req, downloadCb); err != nil {
return err
}
}

for _, URL := range externalPackageIndexes {
if URL.Scheme == "file" {
continue
}
packageIndexFileName := (&resources.IndexResource{URL: URL}).IndexFileName()
packageIndexFile := dataDir.Join(packageIndexFileName)
if packageIndexFile.NotExist() {
// The index file doesn't exists, that means the CLI is run for the first time,
// or the 3rd party package index URL has just been added. Similarly to the
// library update we download that file and all the other package indexes from
// additional_urls
req := &rpc.UpdateIndexRequest{Instance: instance}
if err := UpdateIndex(ctx, req, downloadCb); err != nil {
return err
}
break
}
}

return nil
}
34 changes: 34 additions & 0 deletions docs/UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,40 @@ has been removed as well.

That method was outdated and must not be used.

### golang API: method `github.com/arduino/arduino-cli/commands/core/GetPlatforms` renamed

The following method in `github.com/arduino/arduino-cli/commands/core`:

```go
func GetPlatforms(req *rpc.PlatformListRequest) ([]*rpc.Platform, error) { ... }
```

has been changed to:

```go
func PlatformList(req *rpc.PlatformListRequest) (*rpc.PlatformListResponse, error) { ... }
```

now it better follows the gRPC API interface. Old code like the following:

```go
platforms, _ := core.GetPlatforms(&rpc.PlatformListRequest{Instance: inst})
for _, i := range platforms {
...
}
```

must be changed as follows:

```go
// Use PlatformList function instead of GetPlatforms
platforms, _ := core.PlatformList(&rpc.PlatformListRequest{Instance: inst})
// Access installed platforms through the .InstalledPlatforms field
for _, i := range platforms.InstalledPlatforms {
...
}
```

## 0.31.0

### Added `post_install` script support for tools
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/arguments/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,14 @@ func GetInstalledProgrammers() []string {
func GetUninstallableCores() []string {
inst := instance.CreateAndInit()

platforms, _ := core.GetPlatforms(&rpc.PlatformListRequest{
platforms, _ := core.PlatformList(&rpc.PlatformListRequest{
Instance: inst,
UpdatableOnly: false,
All: false,
})
var res []string
// transform the data structure for the completion
for _, i := range platforms {
for _, i := range platforms.InstalledPlatforms {
res = append(res, i.Id+"\t"+i.Name)
}
return res
Expand Down
6 changes: 3 additions & 3 deletions internal/cli/arguments/reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ func ParseReference(arg string) (*Reference, error) {
ret.Architecture = toks[1]

// Now that we have the required informations in `ret` we can
// try to use core.GetPlatforms to optimize what the user typed
// try to use core.PlatformList to optimize what the user typed
// (by replacing the PackageName and Architecture in ret with the content of core.GetPlatform())
platforms, _ := core.GetPlatforms(&rpc.PlatformListRequest{
platforms, _ := core.PlatformList(&rpc.PlatformListRequest{
Instance: instance.CreateAndInit(),
UpdatableOnly: false,
All: true, // this is true because we want also the installable platforms
})
foundPlatforms := []string{}
for _, platform := range platforms {
for _, platform := range platforms.InstalledPlatforms {
platformID := platform.GetId()
platformUser := ret.PackageName + ":" + ret.Architecture
// At first we check if the platform the user is searching for matches an available one,
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/core/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ func List(inst *rpc.Instance, all bool, updatableOnly bool) {

// GetList returns a list of installed platforms.
func GetList(inst *rpc.Instance, all bool, updatableOnly bool) []*rpc.Platform {
platforms, err := core.GetPlatforms(&rpc.PlatformListRequest{
platforms, err := core.PlatformList(&rpc.PlatformListRequest{
Instance: inst,
UpdatableOnly: updatableOnly,
All: all,
})
if err != nil {
feedback.Fatal(tr("Error listing platforms: %v", err), feedback.ErrGeneric)
}
return platforms
return platforms.InstalledPlatforms
}

// output from this command requires special formatting, let's create a dedicated
Expand Down
8 changes: 2 additions & 6 deletions internal/cli/core/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,16 @@ func initSearchCommand() *cobra.Command {
const indexUpdateInterval = "24h"

func runSearchCommand(cmd *cobra.Command, args []string) {
inst, status := instance.Create()
if status != nil {
feedback.Fatal(tr("Error creating instance: %v", status), feedback.ErrGeneric)
}
inst := instance.CreateAndInit()

if indexesNeedUpdating(indexUpdateInterval) {
err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, feedback.ProgressBar())
if err != nil {
feedback.FatalError(err, feedback.ErrGeneric)
}
instance.Init(inst)
}

instance.Init(inst)

arguments := strings.ToLower(strings.Join(args, " "))
logrus.Infof("Executing `arduino-cli core search` with args: '%s'", arguments)

Expand Down
2 changes: 1 addition & 1 deletion internal/cli/core/update_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func initUpdateIndexCommand() *cobra.Command {
}

func runUpdateIndexCommand(cmd *cobra.Command, args []string) {
inst := instance.CreateInstanceAndRunFirstUpdate()
inst := instance.CreateAndInit()
logrus.Info("Executing `arduino-cli core update-index`")
UpdateIndex(inst)
}
Expand Down
6 changes: 3 additions & 3 deletions internal/cli/core/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,20 @@ func runUpgradeCommand(args []string, skipPostInstall bool) {
func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool) {
// if no platform was passed, upgrade allthethings
if len(args) == 0 {
targets, err := core.GetPlatforms(&rpc.PlatformListRequest{
targets, err := core.PlatformList(&rpc.PlatformListRequest{
Instance: inst,
UpdatableOnly: true,
})
if err != nil {
feedback.Fatal(tr("Error retrieving core list: %v", err), feedback.ErrGeneric)
}

if len(targets) == 0 {
if len(targets.InstalledPlatforms) == 0 {
feedback.Print(tr("All the cores are already at the latest version"))
return
}

for _, t := range targets {
for _, t := range targets.InstalledPlatforms {
args = append(args, t.Id)
}
}
Expand Down
Loading