Skip to content

[breaking] Downloaders/Installers refactor #1809

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 15 commits into from
Aug 8, 2022
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
16 changes: 12 additions & 4 deletions arduino/cores/packagemanager/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/arduino/arduino-cli/arduino"
"github.com/arduino/arduino-cli/arduino/cores"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/pkg/errors"
"go.bug.st/downloader/v2"
semver "go.bug.st/relaxed-semver"
)
Expand Down Expand Up @@ -118,13 +119,20 @@ func (pm *PackageManager) FindPlatformReleaseDependencies(item *PlatformReferenc
}

// DownloadToolRelease downloads a ToolRelease. If the tool is already downloaded a nil Downloader
// is returned.
func (pm *PackageManager) DownloadToolRelease(tool *cores.ToolRelease, config *downloader.Config, label string, progressCB rpc.DownloadProgressCB) error {
// is returned. Uses the given downloader configuration for download, or the default config if nil.
func (pm *PackageManager) DownloadToolRelease(tool *cores.ToolRelease, config *downloader.Config, progressCB rpc.DownloadProgressCB) error {
resource := tool.GetCompatibleFlavour()
if resource == nil {
return fmt.Errorf(tr("tool not available for your OS"))
return &arduino.FailedDownloadError{
Message: tr("Error downloading tool %s", tool),
Cause: errors.New(tr("no versions available for the current OS", tool))}
}
return resource.Download(pm.DownloadDir, config, label, progressCB)
if err := resource.Download(pm.DownloadDir, config, tool.String(), progressCB); err != nil {
return &arduino.FailedDownloadError{
Message: tr("Error downloading tool %s", tool),
Cause: err}
}
return nil
}

// DownloadPlatformRelease downloads a PlatformRelease. If the platform is already downloaded a
Expand Down
222 changes: 212 additions & 10 deletions arduino/cores/packagemanager/install_uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,170 @@ import (
"fmt"
"runtime"

"github.com/arduino/arduino-cli/arduino"
"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/arduino/cores/packageindex"
"github.com/arduino/arduino-cli/executils"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
"github.com/pkg/errors"
)

// DownloadAndInstallPlatformUpgrades runs a full installation process to upgrade the given platform.
// This method takes care of downloading missing archives, upgrading platforms and tools, and
// removing the previously installed platform/tools that are no longer needed after the upgrade.
func (pm *PackageManager) DownloadAndInstallPlatformUpgrades(
platformRef *PlatformReference,
downloadCB rpc.DownloadProgressCB,
taskCB rpc.TaskProgressCB,
skipPostInstall bool,
) error {
if platformRef.PlatformVersion != nil {
return &arduino.InvalidArgumentError{Message: tr("Upgrade doesn't accept parameters with version")}
}

// Search the latest version for all specified platforms
platform := pm.FindPlatform(platformRef)
if platform == nil {
return &arduino.PlatformNotFoundError{Platform: platformRef.String()}
}
installed := pm.GetInstalledPlatformRelease(platform)
if installed == nil {
return &arduino.PlatformNotFoundError{Platform: platformRef.String()}
}
latest := platform.GetLatestRelease()
if !latest.Version.GreaterThan(installed.Version) {
return &arduino.PlatformAlreadyAtTheLatestVersionError{}
}
platformRef.PlatformVersion = latest.Version

platformRelease, tools, err := pm.FindPlatformReleaseDependencies(platformRef)
if err != nil {
return &arduino.PlatformNotFoundError{Platform: platformRef.String()}
}
if err := pm.DownloadAndInstallPlatformAndTools(platformRelease, tools, downloadCB, taskCB, skipPostInstall); err != nil {
return err
}

return nil
}

// DownloadAndInstallPlatformAndTools runs a full installation process for the given platform and tools.
// This method takes care of downloading missing archives, installing/upgrading platforms and tools, and
// removing the previously installed platform/tools that are no longer needed after the upgrade.
func (pm *PackageManager) DownloadAndInstallPlatformAndTools(
platformRelease *cores.PlatformRelease, requiredTools []*cores.ToolRelease,
downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB,
skipPostInstall bool) error {
log := pm.log.WithField("platform", platformRelease)

// Prerequisite checks before install
toolsToInstall := []*cores.ToolRelease{}
for _, tool := range requiredTools {
if tool.IsInstalled() {
log.WithField("tool", tool).Warn("Tool already installed")
taskCB(&rpc.TaskProgress{Name: tr("Tool %s already installed", tool), Completed: true})
} else {
toolsToInstall = append(toolsToInstall, tool)
}
}

// Package download
taskCB(&rpc.TaskProgress{Name: tr("Downloading packages")})
for _, tool := range toolsToInstall {
if err := pm.DownloadToolRelease(tool, nil, downloadCB); err != nil {
return err
}
}
if err := pm.DownloadPlatformRelease(platformRelease, nil, downloadCB); err != nil {
return err
}
taskCB(&rpc.TaskProgress{Completed: true})

// Install tools first
for _, tool := range toolsToInstall {
if err := pm.InstallTool(tool, taskCB); err != nil {
return err
}
}

installed := pm.GetInstalledPlatformRelease(platformRelease.Platform)
installedTools := []*cores.ToolRelease{}
if installed == nil {
// No version of this platform is installed
log.Info("Installing platform")
taskCB(&rpc.TaskProgress{Name: tr("Installing platform %s", platformRelease)})
} else {
// A platform with a different version is already installed
log.Info("Replacing platform " + installed.String())
taskCB(&rpc.TaskProgress{Name: tr("Replacing platform %[1]s with %[2]s", installed, platformRelease)})
platformRef := &PlatformReference{
Package: platformRelease.Platform.Package.Name,
PlatformArchitecture: platformRelease.Platform.Architecture,
PlatformVersion: installed.Version,
}

// Get a list of tools used by the currently installed platform version.
// This must be done so tools used by the currently installed version are
// removed if not used also by the newly installed version.
var err error
_, installedTools, err = pm.FindPlatformReleaseDependencies(platformRef)
if err != nil {
return &arduino.NotFoundError{Message: tr("Can't find dependencies for platform %s", platformRef), Cause: err}
}
}

// Install
if err := pm.InstallPlatform(platformRelease); err != nil {
log.WithError(err).Error("Cannot install platform")
return &arduino.FailedInstallError{Message: tr("Cannot install platform"), Cause: err}
}

// If upgrading remove previous release
if installed != nil {
uninstallErr := pm.UninstallPlatform(installed, taskCB)

// In case of error try to rollback
if uninstallErr != nil {
log.WithError(uninstallErr).Error("Error upgrading platform.")
taskCB(&rpc.TaskProgress{Message: tr("Error upgrading platform: %s", uninstallErr)})

// Rollback
if err := pm.UninstallPlatform(platformRelease, taskCB); err != nil {
log.WithError(err).Error("Error rolling-back changes.")
taskCB(&rpc.TaskProgress{Message: tr("Error rolling-back changes: %s", err)})
}

return &arduino.FailedInstallError{Message: tr("Cannot upgrade platform"), Cause: uninstallErr}
}

// Uninstall unused tools
for _, tool := range installedTools {
taskCB(&rpc.TaskProgress{Name: tr("Uninstalling %s, tool is no more required", tool)})
if !pm.IsToolRequired(tool) {
pm.UninstallTool(tool, taskCB)
}
}

}

// Perform post install
if !skipPostInstall {
log.Info("Running post_install script")
taskCB(&rpc.TaskProgress{Message: tr("Configuring platform.")})
if err := pm.RunPostInstallScript(platformRelease); err != nil {
taskCB(&rpc.TaskProgress{Message: tr("WARNING cannot configure platform: %s", err)})
}
} else {
log.Info("Skipping platform configuration.")
taskCB(&rpc.TaskProgress{Message: tr("Skipping platform configuration.")})
}

log.Info("Platform installed")
taskCB(&rpc.TaskProgress{Message: tr("Platform %s installed", platformRelease), Completed: true})
return nil
}

// InstallPlatform installs a specific release of a platform.
func (pm *PackageManager) InstallPlatform(platformRelease *cores.PlatformRelease) error {
destDir := pm.PackagesDir.Join(
Expand All @@ -39,7 +196,7 @@ func (pm *PackageManager) InstallPlatform(platformRelease *cores.PlatformRelease

// InstallPlatformInDirectory installs a specific release of a platform in a specific directory.
func (pm *PackageManager) InstallPlatformInDirectory(platformRelease *cores.PlatformRelease, destDir *paths.Path) error {
if err := platformRelease.Resource.Install(pm.DownloadDir, pm.TempDir, destDir); err != nil {
if err := platformRelease.Resource.Install(pm.DownloadDir, pm.tempDir, destDir); err != nil {
return errors.Errorf(tr("installing platform %[1]s: %[2]s"), platformRelease, err)
}
if d, err := destDir.Abs(); err == nil {
Expand Down Expand Up @@ -109,25 +266,51 @@ func (pm *PackageManager) IsManagedPlatformRelease(platformRelease *cores.Platfo
}

// UninstallPlatform remove a PlatformRelease.
func (pm *PackageManager) UninstallPlatform(platformRelease *cores.PlatformRelease) error {
func (pm *PackageManager) UninstallPlatform(platformRelease *cores.PlatformRelease, taskCB rpc.TaskProgressCB) error {
log := pm.log.WithField("platform", platformRelease)

log.Info("Uninstalling platform")
taskCB(&rpc.TaskProgress{Name: tr("Uninstalling %s", platformRelease)})

if platformRelease.InstallDir == nil {
return fmt.Errorf(tr("platform not installed"))
err := fmt.Errorf(tr("platform not installed"))
log.WithError(err).Error("Error uninstalling")
return &arduino.FailedUninstallError{Message: err.Error()}
}

// Safety measure
if !pm.IsManagedPlatformRelease(platformRelease) {
return fmt.Errorf(tr("%s is not managed by package manager"), platformRelease)
err := fmt.Errorf(tr("%s is not managed by package manager"), platformRelease)
log.WithError(err).Error("Error uninstalling")
return &arduino.FailedUninstallError{Message: err.Error()}
}

if err := platformRelease.InstallDir.RemoveAll(); err != nil {
return fmt.Errorf(tr("removing platform files: %s"), err)
err = fmt.Errorf(tr("removing platform files: %s"), err)
log.WithError(err).Error("Error uninstalling")
return &arduino.FailedUninstallError{Message: err.Error()}
}

platformRelease.InstallDir = nil

log.Info("Platform uninstalled")
taskCB(&rpc.TaskProgress{Message: tr("Platform %s uninstalled", platformRelease), Completed: true})
return nil
}

// InstallTool installs a specific release of a tool.
func (pm *PackageManager) InstallTool(toolRelease *cores.ToolRelease) error {
func (pm *PackageManager) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.TaskProgressCB) error {
log := pm.log.WithField("Tool", toolRelease)

if toolRelease.IsInstalled() {
log.Warn("Tool already installed")
taskCB(&rpc.TaskProgress{Name: tr("Tool %s already installed", toolRelease), Completed: true})
return nil
}

log.Info("Installing tool")
taskCB(&rpc.TaskProgress{Name: tr("Installing %s", toolRelease)})

toolResource := toolRelease.GetCompatibleFlavour()
if toolResource == nil {
return fmt.Errorf(tr("no compatible version of %s tools found for the current os"), toolRelease.Tool.Name)
Expand All @@ -137,7 +320,15 @@ func (pm *PackageManager) InstallTool(toolRelease *cores.ToolRelease) error {
"tools",
toolRelease.Tool.Name,
toolRelease.Version.String())
return toolResource.Install(pm.DownloadDir, pm.TempDir, destDir)
err := toolResource.Install(pm.DownloadDir, pm.tempDir, destDir)
if err != nil {
log.WithError(err).Warn("Cannot install tool")
return &arduino.FailedInstallError{Message: tr("Cannot install tool %s", toolRelease), Cause: err}
}
log.Info("Tool installed")
taskCB(&rpc.TaskProgress{Message: tr("%s installed", toolRelease), Completed: true})

return nil
}

// IsManagedToolRelease returns true if the ToolRelease is managed by the PackageManager
Expand All @@ -161,20 +352,31 @@ func (pm *PackageManager) IsManagedToolRelease(toolRelease *cores.ToolRelease) b
}

// UninstallTool remove a ToolRelease.
func (pm *PackageManager) UninstallTool(toolRelease *cores.ToolRelease) error {
func (pm *PackageManager) UninstallTool(toolRelease *cores.ToolRelease, taskCB rpc.TaskProgressCB) error {
log := pm.log.WithField("Tool", toolRelease)
log.Info("Uninstalling tool")

if toolRelease.InstallDir == nil {
return fmt.Errorf(tr("tool not installed"))
}

// Safety measure
if !pm.IsManagedToolRelease(toolRelease) {
return fmt.Errorf(tr("tool %s is not managed by package manager"), toolRelease)
err := &arduino.FailedUninstallError{Message: tr("tool %s is not managed by package manager", toolRelease)}
log.WithError(err).Error("Error uninstalling")
return err
}

if err := toolRelease.InstallDir.RemoveAll(); err != nil {
return fmt.Errorf(tr("removing tool files: %s"), err)
err = &arduino.FailedUninstallError{Message: err.Error()}
log.WithError(err).Error("Error uninstalling")
return err
}

toolRelease.InstallDir = nil

log.Info("Tool uninstalled")
taskCB(&rpc.TaskProgress{Message: tr("Tool %s uninstalled", toolRelease), Completed: true})
return nil
}

Expand Down
Loading