From 7cdef86f4b88bcf5fc2e7dbff8843f7b386ab604 Mon Sep 17 00:00:00 2001 From: MatteoPologruto Date: Wed, 6 Sep 2023 14:01:59 +0200 Subject: [PATCH] Run pre-uninstall script when a platform or tool is uninstalled --- .../cores/packagemanager/install_uninstall.go | 63 ++++++++++++++----- .../packagemanager/package_manager_test.go | 2 +- commands/core/install.go | 2 +- commands/core/uninstall.go | 4 +- commands/core/upgrade.go | 3 +- internal/cli/core/install.go | 21 ++++--- internal/cli/core/uninstall.go | 15 +++-- internal/cli/core/upgrade.go | 17 ++--- internal/cli/upgrade/upgrade.go | 6 +- 9 files changed, 85 insertions(+), 48 deletions(-) diff --git a/arduino/cores/packagemanager/install_uninstall.go b/arduino/cores/packagemanager/install_uninstall.go index 73a75c7b00d..fb34d3971c9 100644 --- a/arduino/cores/packagemanager/install_uninstall.go +++ b/arduino/cores/packagemanager/install_uninstall.go @@ -38,6 +38,7 @@ func (pme *Explorer) DownloadAndInstallPlatformUpgrades( downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, skipPostInstall bool, + skipPreUninstall bool, ) (*cores.PlatformRelease, error) { if platformRef.PlatformVersion != nil { return nil, &arduino.InvalidArgumentError{Message: tr("Upgrade doesn't accept parameters with version")} @@ -62,7 +63,7 @@ func (pme *Explorer) DownloadAndInstallPlatformUpgrades( if err != nil { return nil, &arduino.PlatformNotFoundError{Platform: platformRef.String()} } - if err := pme.DownloadAndInstallPlatformAndTools(platformRelease, tools, downloadCB, taskCB, skipPostInstall); err != nil { + if err := pme.DownloadAndInstallPlatformAndTools(platformRelease, tools, downloadCB, taskCB, skipPostInstall, skipPreUninstall); err != nil { return nil, err } @@ -75,7 +76,7 @@ func (pme *Explorer) DownloadAndInstallPlatformUpgrades( func (pme *Explorer) DownloadAndInstallPlatformAndTools( platformRelease *cores.PlatformRelease, requiredTools []*cores.ToolRelease, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, - skipPostInstall bool) error { + skipPostInstall bool, skipPreUninstall bool) error { log := pme.log.WithField("platform", platformRelease) // Prerequisite checks before install @@ -142,7 +143,7 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools( // If upgrading remove previous release if installed != nil { - uninstallErr := pme.UninstallPlatform(installed, taskCB) + uninstallErr := pme.UninstallPlatform(installed, taskCB, skipPreUninstall) // In case of error try to rollback if uninstallErr != nil { @@ -150,7 +151,7 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools( taskCB(&rpc.TaskProgress{Message: tr("Error upgrading platform: %s", uninstallErr)}) // Rollback - if err := pme.UninstallPlatform(platformRelease, taskCB); err != nil { + if err := pme.UninstallPlatform(platformRelease, taskCB, skipPreUninstall); err != nil { log.WithError(err).Error("Error rolling-back changes.") taskCB(&rpc.TaskProgress{Message: tr("Error rolling-back changes: %s", err)}) } @@ -162,7 +163,7 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools( for _, tool := range installedTools { taskCB(&rpc.TaskProgress{Name: tr("Uninstalling %s, tool is no more required", tool)}) if !pme.IsToolRequired(tool) { - pme.UninstallTool(tool, taskCB) + pme.UninstallTool(tool, taskCB, skipPreUninstall) } } @@ -175,7 +176,7 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools( if !platformRelease.IsInstalled() { return errors.New(tr("platform not installed")) } - stdout, stderr, err := pme.RunPostInstallScript(platformRelease.InstallDir) + stdout, stderr, err := pme.RunPreOrPostScript(platformRelease.InstallDir, "post_install") skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stdout), Completed: true}) skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stderr), Completed: true}) if err != nil { @@ -229,16 +230,16 @@ func (pme *Explorer) cacheInstalledJSON(platformRelease *cores.PlatformRelease) return nil } -// RunPostInstallScript runs the post_install.sh (or post_install.bat) script for the -// specified platformRelease or toolRelease. -func (pme *Explorer) RunPostInstallScript(installDir *paths.Path) ([]byte, []byte, error) { - postInstallFilename := "post_install.sh" +// RunPreOrPostScript runs either the post_install.sh (or post_install.bat) or the pre_uninstall.sh (or pre_uninstall.bat) +// script for the specified platformRelease or toolRelease. +func (pme *Explorer) RunPreOrPostScript(installDir *paths.Path, prefix string) ([]byte, []byte, error) { + scriptFilename := prefix + ".sh" if runtime.GOOS == "windows" { - postInstallFilename = "post_install.bat" + scriptFilename = prefix + ".bat" } - postInstall := installDir.Join(postInstallFilename) - if postInstall.Exist() && postInstall.IsNotDir() { - cmd, err := executils.NewProcessFromPath(pme.GetEnvVarsForSpawnedProcess(), postInstall) + script := installDir.Join(scriptFilename) + if script.Exist() && script.IsNotDir() { + cmd, err := executils.NewProcessFromPath(pme.GetEnvVarsForSpawnedProcess(), script) if err != nil { return []byte{}, []byte{}, err } @@ -270,7 +271,7 @@ func (pme *Explorer) IsManagedPlatformRelease(platformRelease *cores.PlatformRel } // UninstallPlatform remove a PlatformRelease. -func (pme *Explorer) UninstallPlatform(platformRelease *cores.PlatformRelease, taskCB rpc.TaskProgressCB) error { +func (pme *Explorer) UninstallPlatform(platformRelease *cores.PlatformRelease, taskCB rpc.TaskProgressCB, skipPreUninstall bool) error { log := pme.log.WithField("platform", platformRelease) log.Info("Uninstalling platform") @@ -289,6 +290,20 @@ func (pme *Explorer) UninstallPlatform(platformRelease *cores.PlatformRelease, t return &arduino.FailedUninstallError{Message: err.Error()} } + if !skipPreUninstall { + log.Info("Running pre_uninstall script") + taskCB(&rpc.TaskProgress{Message: tr("Running pre_uninstall script.")}) + stdout, stderr, err := pme.RunPreOrPostScript(platformRelease.InstallDir, "pre_uninstall") + skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stdout), Completed: true}) + skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stderr), Completed: true}) + if err != nil { + taskCB(&rpc.TaskProgress{Message: tr("WARNING cannot run pre_uninstall script: %s", err), Completed: true}) + } + } else { + log.Info("Skipping pre_uninstall script.") + taskCB(&rpc.TaskProgress{Message: tr("Skipping pre_uninstall script.")}) + } + if err := platformRelease.InstallDir.RemoveAll(); err != nil { err = fmt.Errorf(tr("removing platform files: %s"), err) log.WithError(err).Error("Error uninstalling") @@ -339,7 +354,7 @@ func (pme *Explorer) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.Task if !skipPostInstall { log.Info("Running tool post_install script") taskCB(&rpc.TaskProgress{Message: tr("Configuring tool.")}) - stdout, stderr, err := pme.RunPostInstallScript(toolRelease.InstallDir) + stdout, stderr, err := pme.RunPreOrPostScript(toolRelease.InstallDir, "post_install") skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stdout)}) skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stderr)}) if err != nil { @@ -373,7 +388,7 @@ func (pme *Explorer) IsManagedToolRelease(toolRelease *cores.ToolRelease) bool { } // UninstallTool remove a ToolRelease. -func (pme *Explorer) UninstallTool(toolRelease *cores.ToolRelease, taskCB rpc.TaskProgressCB) error { +func (pme *Explorer) UninstallTool(toolRelease *cores.ToolRelease, taskCB rpc.TaskProgressCB, skipPreUninstall bool) error { log := pme.log.WithField("Tool", toolRelease) log.Info("Uninstalling tool") @@ -388,6 +403,20 @@ func (pme *Explorer) UninstallTool(toolRelease *cores.ToolRelease, taskCB rpc.Ta return err } + if !skipPreUninstall { + log.Info("Running pre_uninstall script") + taskCB(&rpc.TaskProgress{Message: tr("Running pre_uninstall script.")}) + stdout, stderr, err := pme.RunPreOrPostScript(toolRelease.InstallDir, "pre_uninstall") + skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stdout), Completed: true}) + skipEmptyMessageTaskProgressCB(taskCB)(&rpc.TaskProgress{Message: string(stderr), Completed: true}) + if err != nil { + taskCB(&rpc.TaskProgress{Message: tr("WARNING cannot run pre_uninstall script: %s", err), Completed: true}) + } + } else { + log.Info("Skipping pre_uninstall script.") + taskCB(&rpc.TaskProgress{Message: tr("Skipping pre_uninstall script.")}) + } + if err := toolRelease.InstallDir.RemoveAll(); err != nil { err = &arduino.FailedUninstallError{Message: err.Error()} log.WithError(err).Error("Error uninstalling") diff --git a/arduino/cores/packagemanager/package_manager_test.go b/arduino/cores/packagemanager/package_manager_test.go index 7a6d8994004..0a0542aed7a 100644 --- a/arduino/cores/packagemanager/package_manager_test.go +++ b/arduino/cores/packagemanager/package_manager_test.go @@ -949,7 +949,7 @@ func TestRunPostInstall(t *testing.T) { require.NoError(t, err) err = os.Chmod(scriptPath.String(), 0777) require.NoError(t, err) - stdout, stderr, err := pme.RunPostInstallScript(dir) + stdout, stderr, err := pme.RunPreOrPostScript(dir, "post_install") require.NoError(t, err) // `HasPrefix` because windows seem to add a trailing space at the end diff --git a/commands/core/install.go b/commands/core/install.go index 7eb0383aa09..20525ec58ae 100644 --- a/commands/core/install.go +++ b/commands/core/install.go @@ -63,7 +63,7 @@ func PlatformInstall(ctx context.Context, req *rpc.PlatformInstallRequest, downl } } - if err := pme.DownloadAndInstallPlatformAndTools(platformRelease, tools, downloadCB, taskCB, req.GetSkipPostInstall()); err != nil { + if err := pme.DownloadAndInstallPlatformAndTools(platformRelease, tools, downloadCB, taskCB, req.GetSkipPostInstall(), req.GetSkipPreUninstall()); err != nil { return err } diff --git a/commands/core/uninstall.go b/commands/core/uninstall.go index 165b1fc16ea..3bdbf85fbcc 100644 --- a/commands/core/uninstall.go +++ b/commands/core/uninstall.go @@ -64,14 +64,14 @@ func platformUninstall(ctx context.Context, req *rpc.PlatformUninstallRequest, t return &arduino.NotFoundError{Message: tr("Can't find dependencies for platform %s", ref), Cause: err} } - if err := pme.UninstallPlatform(platform, taskCB); err != nil { + if err := pme.UninstallPlatform(platform, taskCB, req.GetSkipPreUninstall()); err != nil { return err } for _, tool := range tools { if !pme.IsToolRequired(tool) { taskCB(&rpc.TaskProgress{Name: tr("Uninstalling %s, tool is no more required", tool)}) - pme.UninstallTool(tool, taskCB) + pme.UninstallTool(tool, taskCB, req.GetSkipPreUninstall()) } } diff --git a/commands/core/upgrade.go b/commands/core/upgrade.go index ee8f4b0a3f7..49b81e68488 100644 --- a/commands/core/upgrade.go +++ b/commands/core/upgrade.go @@ -17,6 +17,7 @@ package core import ( "context" + "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/arduino" @@ -39,7 +40,7 @@ func PlatformUpgrade(ctx context.Context, req *rpc.PlatformUpgradeRequest, downl Package: req.PlatformPackage, PlatformArchitecture: req.Architecture, } - platform, err := pme.DownloadAndInstallPlatformUpgrades(ref, downloadCB, taskCB, req.GetSkipPostInstall()) + platform, err := pme.DownloadAndInstallPlatformUpgrades(ref, downloadCB, taskCB, req.GetSkipPostInstall(), req.GetSkipPreUninstall()) if err != nil { return platform, err } diff --git a/internal/cli/core/install.go b/internal/cli/core/install.go index 507819d286d..8193a8b4344 100644 --- a/internal/cli/core/install.go +++ b/internal/cli/core/install.go @@ -31,7 +31,7 @@ import ( func initInstallCommand() *cobra.Command { var noOverwrite bool - var postInstallFlags arguments.PrePostScriptsFlags + var scriptFlags arguments.PrePostScriptsFlags installCommand := &cobra.Command{ Use: fmt.Sprintf("install %s:%s[@%s]...", tr("PACKAGER"), tr("ARCH"), tr("VERSION")), Short: tr("Installs one or more cores and corresponding tool dependencies."), @@ -45,18 +45,18 @@ func initInstallCommand() *cobra.Command { arguments.CheckFlagsConflicts(cmd, "run-post-install", "skip-post-install") }, Run: func(cmd *cobra.Command, args []string) { - runInstallCommand(args, postInstallFlags, noOverwrite) + runInstallCommand(args, scriptFlags, noOverwrite) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return arguments.GetInstallableCores(), cobra.ShellCompDirectiveDefault }, } - postInstallFlags.AddToCommand(installCommand) + scriptFlags.AddToCommand(installCommand) installCommand.Flags().BoolVar(&noOverwrite, "no-overwrite", false, tr("Do not overwrite already installed platforms.")) return installCommand } -func runInstallCommand(args []string, postInstallFlags arguments.PrePostScriptsFlags, noOverwrite bool) { +func runInstallCommand(args []string, scriptFlags arguments.PrePostScriptsFlags, noOverwrite bool) { inst := instance.CreateAndInit() logrus.Info("Executing `arduino-cli core install`") @@ -67,12 +67,13 @@ func runInstallCommand(args []string, postInstallFlags arguments.PrePostScriptsF for _, platformRef := range platformsRefs { platformInstallRequest := &rpc.PlatformInstallRequest{ - Instance: inst, - PlatformPackage: platformRef.PackageName, - Architecture: platformRef.Architecture, - Version: platformRef.Version, - SkipPostInstall: postInstallFlags.DetectSkipPostInstallValue(), - NoOverwrite: noOverwrite, + Instance: inst, + PlatformPackage: platformRef.PackageName, + Architecture: platformRef.Architecture, + Version: platformRef.Version, + SkipPostInstall: scriptFlags.DetectSkipPostInstallValue(), + NoOverwrite: noOverwrite, + SkipPreUninstall: scriptFlags.DetectSkipPreUninstallValue(), } _, err := core.PlatformInstall(context.Background(), platformInstallRequest, feedback.ProgressBar(), feedback.TaskProgress()) if err != nil { diff --git a/internal/cli/core/uninstall.go b/internal/cli/core/uninstall.go index fb31d4ea1b5..10992ef9bdf 100644 --- a/internal/cli/core/uninstall.go +++ b/internal/cli/core/uninstall.go @@ -30,21 +30,25 @@ import ( ) func initUninstallCommand() *cobra.Command { + var preUninstallFlags arguments.PrePostScriptsFlags uninstallCommand := &cobra.Command{ Use: fmt.Sprintf("uninstall %s:%s ...", tr("PACKAGER"), tr("ARCH")), Short: tr("Uninstalls one or more cores and corresponding tool dependencies if no longer used."), Long: tr("Uninstalls one or more cores and corresponding tool dependencies if no longer used."), Example: " " + os.Args[0] + " core uninstall arduino:samd\n", Args: cobra.MinimumNArgs(1), - Run: runUninstallCommand, + Run: func(cmd *cobra.Command, args []string) { + runUninstallCommand(args, preUninstallFlags) + }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return arguments.GetUninstallableCores(), cobra.ShellCompDirectiveDefault }, } + preUninstallFlags.AddToCommand(uninstallCommand) return uninstallCommand } -func runUninstallCommand(cmd *cobra.Command, args []string) { +func runUninstallCommand(args []string, preUninstallFlags arguments.PrePostScriptsFlags) { inst := instance.CreateAndInit() logrus.Info("Executing `arduino-cli core uninstall`") @@ -60,9 +64,10 @@ func runUninstallCommand(cmd *cobra.Command, args []string) { } for _, platformRef := range platformsRefs { _, err := core.PlatformUninstall(context.Background(), &rpc.PlatformUninstallRequest{ - Instance: inst, - PlatformPackage: platformRef.PackageName, - Architecture: platformRef.Architecture, + Instance: inst, + PlatformPackage: platformRef.PackageName, + Architecture: platformRef.Architecture, + SkipPreUninstall: preUninstallFlags.DetectSkipPreUninstallValue(), }, feedback.NewTaskProgressCB()) if err != nil { feedback.Fatal(tr("Error during uninstall: %v", err), feedback.ErrGeneric) diff --git a/internal/cli/core/upgrade.go b/internal/cli/core/upgrade.go index afeb3623796..f8484dc985b 100644 --- a/internal/cli/core/upgrade.go +++ b/internal/cli/core/upgrade.go @@ -43,21 +43,21 @@ func initUpgradeCommand() *cobra.Command { " # " + tr("upgrade arduino:samd to the latest version") + "\n" + " " + os.Args[0] + " core upgrade arduino:samd", Run: func(cmd *cobra.Command, args []string) { - runUpgradeCommand(args, postInstallFlags.DetectSkipPostInstallValue()) + runUpgradeCommand(args, postInstallFlags.DetectSkipPostInstallValue(), postInstallFlags.DetectSkipPreUninstallValue()) }, } postInstallFlags.AddToCommand(upgradeCommand) return upgradeCommand } -func runUpgradeCommand(args []string, skipPostInstall bool) { +func runUpgradeCommand(args []string, skipPostInstall bool, skipPreUninstall bool) { inst := instance.CreateAndInit() logrus.Info("Executing `arduino-cli core upgrade`") - Upgrade(inst, args, skipPostInstall) + Upgrade(inst, args, skipPostInstall, skipPreUninstall) } // Upgrade upgrades one or all installed platforms to the latest version. -func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool) { +func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool, skipPreUninstall bool) { // if no platform was passed, upgrade allthethings if len(args) == 0 { targets, err := core.PlatformList(&rpc.PlatformListRequest{ @@ -102,10 +102,11 @@ func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool) { } r := &rpc.PlatformUpgradeRequest{ - Instance: inst, - PlatformPackage: platformRef.PackageName, - Architecture: platformRef.Architecture, - SkipPostInstall: skipPostInstall, + Instance: inst, + PlatformPackage: platformRef.PackageName, + Architecture: platformRef.Architecture, + SkipPostInstall: skipPostInstall, + SkipPreUninstall: skipPreUninstall, } response, err := core.PlatformUpgrade(context.Background(), r, feedback.ProgressBar(), feedback.TaskProgress()) warningMissingIndex(response) diff --git a/internal/cli/upgrade/upgrade.go b/internal/cli/upgrade/upgrade.go index 84943414e00..937abd845fa 100644 --- a/internal/cli/upgrade/upgrade.go +++ b/internal/cli/upgrade/upgrade.go @@ -39,16 +39,16 @@ func NewCommand() *cobra.Command { Example: " " + os.Args[0] + " upgrade", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - runUpgradeCommand(postInstallFlags.DetectSkipPostInstallValue()) + runUpgradeCommand(postInstallFlags.DetectSkipPostInstallValue(), postInstallFlags.DetectSkipPreUninstallValue()) }, } postInstallFlags.AddToCommand(upgradeCommand) return upgradeCommand } -func runUpgradeCommand(skipPostInstall bool) { +func runUpgradeCommand(skipPostInstall bool, skipPreUninstall bool) { inst := instance.CreateAndInit() logrus.Info("Executing `arduino-cli upgrade`") lib.Upgrade(inst, []string{}) - core.Upgrade(inst, []string{}, skipPostInstall) + core.Upgrade(inst, []string{}, skipPostInstall, skipPreUninstall) }