github.com/getgauge/gauge@v1.6.9/plugin/install/install.go (about)

     1  /*----------------------------------------------------------------
     2   *  Copyright (c) ThoughtWorks, Inc.
     3   *  Licensed under the Apache License, Version 2.0
     4   *  See LICENSE in the project root for license information.
     5   *----------------------------------------------------------------*/
     6  
     7  package install
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"regexp"
    17  	"runtime"
    18  	"sort"
    19  	"strings"
    20  
    21  	"github.com/getgauge/common"
    22  	"github.com/getgauge/gauge/config"
    23  	"github.com/getgauge/gauge/logger"
    24  	"github.com/getgauge/gauge/manifest"
    25  	"github.com/getgauge/gauge/plugin"
    26  	"github.com/getgauge/gauge/plugin/pluginInfo"
    27  	"github.com/getgauge/gauge/runner"
    28  	"github.com/getgauge/gauge/util"
    29  	"github.com/getgauge/gauge/version"
    30  )
    31  
    32  const (
    33  	pluginJSON = "plugin.json"
    34  	jsonExt    = ".json"
    35  	x86        = "386"
    36  	arm64      = "arm64"
    37  )
    38  
    39  type installDescription struct {
    40  	Name        string
    41  	Description string
    42  	Versions    []versionInstallDescription
    43  }
    44  
    45  type versionInstallDescription struct {
    46  	Version             string
    47  	GaugeVersionSupport version.VersionSupport
    48  	Install             platformSpecificCommand
    49  	DownloadUrls        downloadUrls
    50  }
    51  
    52  type downloadUrls struct {
    53  	X86   platformSpecificURL
    54  	X64   platformSpecificURL
    55  	ARM64 platformSpecificURL
    56  }
    57  
    58  type platformSpecificCommand struct {
    59  	Windows []string
    60  	Linux   []string
    61  	Darwin  []string
    62  }
    63  
    64  type platformSpecificURL struct {
    65  	Windows string
    66  	Linux   string
    67  	Darwin  string
    68  }
    69  
    70  // InstallResult represents the result of plugin installation
    71  type InstallResult struct {
    72  	Error   error
    73  	Warning string
    74  	Info    string
    75  	Success bool
    76  	Skipped bool
    77  	Version string
    78  }
    79  
    80  func (installResult *InstallResult) getMessage() string {
    81  	return installResult.Error.Error()
    82  }
    83  
    84  func installError(err error) InstallResult {
    85  	return InstallResult{Error: err, Success: false}
    86  }
    87  
    88  func installSuccess(info string) InstallResult {
    89  	return InstallResult{Info: info, Success: true}
    90  }
    91  func installSkipped(warning, info string) InstallResult {
    92  	return InstallResult{Warning: warning, Info: info, Skipped: true}
    93  }
    94  
    95  // GaugePlugin represents any plugin to Gauge. It can be an language runner or any other plugin.
    96  type GaugePlugin struct {
    97  	ID          string
    98  	Version     string
    99  	Description string
   100  	PreInstall  struct {
   101  		Windows []string
   102  		Linux   []string
   103  		Darwin  []string
   104  	}
   105  	PostInstall struct {
   106  		Windows []string
   107  		Linux   []string
   108  		Darwin  []string
   109  	}
   110  	PreUnInstall struct {
   111  		Windows []string
   112  		Linux   []string
   113  		Darwin  []string
   114  	}
   115  	PostUnInstall struct {
   116  		Windows []string
   117  		Linux   []string
   118  		Darwin  []string
   119  	}
   120  	GaugeVersionSupport version.VersionSupport
   121  }
   122  
   123  func getGoArch() string {
   124  	arch := runtime.GOARCH
   125  	if arch == arm64 {
   126  		return arm64
   127  	}
   128  	if arch == x86 {
   129  		return "x86"
   130  	}
   131  	return "x86_64"
   132  }
   133  
   134  func isPlatformIndependent(zipfile string) bool {
   135  	re, err := regexp.Compile(`-([a-z]*)\.`)
   136  	if err != nil {
   137  		logger.Errorf(false, "unable to compile regex '-([a-z]*)\\.': %s", err.Error())
   138  	}
   139  	return !re.MatchString(zipfile)
   140  }
   141  
   142  func isOSCompatible(zipfile string) bool {
   143  	os := runtime.GOOS
   144  	arch := getGoArch()
   145  	if os == "darwin" && arch == "arm64" {
   146  		// darwin/arm64 can run darwin/x86_64 binaries under rosetta.
   147  		return strings.Contains(zipfile, "darwin.arm64") || strings.Contains(zipfile, "darwin.x86_64")
   148  	}
   149  	return strings.Contains(zipfile, fmt.Sprintf("%s.%s", os, arch))
   150  }
   151  
   152  // InstallPluginFromZipFile installs plugin from given zip file
   153  func InstallPluginFromZipFile(zipFile string, pluginName string) InstallResult {
   154  	if !isPlatformIndependent(zipFile) && !isOSCompatible(zipFile) {
   155  		err := fmt.Errorf("provided plugin is not compatible with OS %s %s", runtime.GOOS, runtime.GOARCH)
   156  		return installError(err)
   157  	}
   158  	tempDir := common.GetTempDir()
   159  	defer func() {
   160  		err := common.Remove(tempDir)
   161  		if err != nil {
   162  			logger.Errorf(false, "unable to remove temp directory: %s", err.Error())
   163  		}
   164  	}()
   165  
   166  	unzippedPluginDir, err := common.UnzipArchive(zipFile, tempDir)
   167  	if err != nil {
   168  		return installError(err)
   169  	}
   170  	gp, err := parsePluginJSON(unzippedPluginDir, pluginName)
   171  	if err != nil {
   172  		return installError(err)
   173  	}
   174  	if gp.ID != pluginName {
   175  		err := fmt.Errorf("provided zip file is not a valid plugin of %s", pluginName)
   176  		return installError(err)
   177  	}
   178  	if err = runPlatformCommands(gp.PreInstall, unzippedPluginDir); err != nil {
   179  		return installError(err)
   180  	}
   181  
   182  	if err = runPlatformCommands(gp.PostInstall, unzippedPluginDir); err != nil {
   183  		return installError(err)
   184  	}
   185  
   186  	pluginInstallDir, err := getPluginInstallDir(gp.ID, getVersionedPluginDirName(zipFile))
   187  	if err != nil {
   188  		return installError(err)
   189  	}
   190  
   191  	// copy files to gauge plugin install location
   192  	logger.Debugf(true, "Installing plugin %s %s", gp.ID, filepath.Base(pluginInstallDir))
   193  	if _, err = common.MirrorDir(unzippedPluginDir, pluginInstallDir); err != nil {
   194  		return installError(err)
   195  	}
   196  	installResult := installSuccess("")
   197  	installResult.Version = gp.Version
   198  	return installResult
   199  }
   200  
   201  func getPluginInstallDir(pluginID, pluginDirName string) (string, error) {
   202  	pluginsDir, err := common.GetPrimaryPluginsInstallDir()
   203  	if err != nil {
   204  		return "", err
   205  	}
   206  	pluginDirPath := filepath.Join(pluginsDir, pluginID, pluginDirName)
   207  	if common.DirExists(pluginDirPath) {
   208  		return "", fmt.Errorf("Plugin %s %s already installed at %s", pluginID, pluginDirName, pluginDirPath)
   209  	}
   210  	return pluginDirPath, nil
   211  }
   212  
   213  func parsePluginJSON(pluginDir, pluginName string) (*GaugePlugin, error) {
   214  	var file string
   215  	if common.FileExists(filepath.Join(pluginDir, pluginName+jsonExt)) {
   216  		file = filepath.Join(pluginDir, pluginName+jsonExt)
   217  	} else {
   218  		file = filepath.Join(pluginDir, pluginJSON)
   219  	}
   220  
   221  	var gp GaugePlugin
   222  	contents, err := common.ReadFileContents(file)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	if err = json.Unmarshal([]byte(contents), &gp); err != nil {
   227  		return nil, err
   228  	}
   229  	return &gp, nil
   230  }
   231  
   232  // Plugin download and install the latest plugin(if version not specified) of given plugin name
   233  func Plugin(pluginName, version string, silent bool) InstallResult {
   234  	logger.Debugf(true, "Gathering metadata for %s", pluginName)
   235  	installDescription, result := getInstallDescription(pluginName, false)
   236  	defer util.RemoveTempDir()
   237  	if !result.Success {
   238  		return result
   239  	}
   240  	return installPluginWithDescription(installDescription, version, silent)
   241  }
   242  
   243  func installPluginWithDescription(installDescription *installDescription, currentVersion string, silent bool) InstallResult {
   244  	var versionInstallDescription *versionInstallDescription
   245  	var err error
   246  	if currentVersion != "" {
   247  		versionInstallDescription, err = installDescription.getVersion(currentVersion)
   248  		if err != nil {
   249  			return installError(err)
   250  		}
   251  		if compatibilityError := version.CheckCompatibility(version.CurrentGaugeVersion, &versionInstallDescription.GaugeVersionSupport); compatibilityError != nil {
   252  			return installError(fmt.Errorf("Plugin Version %s-%s is not supported for gauge %s : %s", installDescription.Name, versionInstallDescription.Version, version.CurrentGaugeVersion.String(), compatibilityError.Error()))
   253  		}
   254  	} else {
   255  		versionInstallDescription, err = installDescription.getLatestCompatibleVersionTo(version.CurrentGaugeVersion)
   256  		if err != nil {
   257  			return installError(fmt.Errorf("Could not find compatible version for plugin %s. : %s", installDescription.Name, err))
   258  		}
   259  	}
   260  	return installPluginVersion(installDescription, versionInstallDescription, silent)
   261  }
   262  
   263  func installPluginVersion(installDesc *installDescription, versionInstallDescription *versionInstallDescription, silent bool) InstallResult {
   264  	if common.IsPluginInstalled(installDesc.Name, versionInstallDescription.Version) {
   265  		return installSkipped("", fmt.Sprintf("Plugin %s %s is already installed.", installDesc.Name, versionInstallDescription.Version))
   266  	}
   267  
   268  	downloadLink, err := getDownloadLink(versionInstallDescription.DownloadUrls)
   269  	if err != nil {
   270  		return installError(fmt.Errorf("Could not get download link: %s", err.Error()))
   271  	}
   272  
   273  	tempDir := common.GetTempDir()
   274  	defer func() {
   275  		err := common.Remove(tempDir)
   276  		if err != nil {
   277  			logger.Errorf(false, "unable to remove temp directory: %s", err.Error())
   278  		}
   279  	}()
   280  	pluginZip, err := util.Download(downloadLink, tempDir, "", silent)
   281  	if err != nil {
   282  		return installError(fmt.Errorf("Failed to download the plugin. %s", err.Error()))
   283  	}
   284  	res := InstallPluginFromZipFile(pluginZip, installDesc.Name)
   285  	res.Version = versionInstallDescription.Version
   286  	return res
   287  }
   288  
   289  func runPlatformCommands(commands platformSpecificCommand, workingDir string) error {
   290  	var command []string
   291  	switch runtime.GOOS {
   292  	case "windows":
   293  		command = commands.Windows
   294  	case "darwin":
   295  		command = commands.Darwin
   296  	default:
   297  		command = commands.Linux
   298  	}
   299  
   300  	if len(command) == 0 {
   301  		return nil
   302  	}
   303  
   304  	logger.Debugf(true, "Running plugin hook command => %s", command)
   305  	cmd, err := common.ExecuteSystemCommand(command, workingDir, os.Stdout, os.Stderr)
   306  
   307  	if err != nil {
   308  		return err
   309  	}
   310  
   311  	return cmd.Wait()
   312  }
   313  
   314  // UninstallPlugin uninstall the given plugin of the given uninstallVersion
   315  // If uninstallVersion is not specified, it uninstalls all the versions of given plugin
   316  func UninstallPlugin(pluginName string, uninstallVersion string) {
   317  	pluginsHome, err := common.GetPrimaryPluginsInstallDir()
   318  	if err != nil {
   319  		logger.Fatalf(true, "Failed to uninstall plugin %s. %s", pluginName, err.Error())
   320  	}
   321  	if !common.DirExists(filepath.Join(pluginsHome, pluginName, uninstallVersion)) {
   322  		logger.Errorf(true, "Plugin %s not found.", strings.TrimSpace(pluginName+" "+uninstallVersion))
   323  		os.Exit(0)
   324  	}
   325  	var failed bool
   326  	pluginsDir := filepath.Join(pluginsHome, pluginName)
   327  	err = filepath.Walk(pluginsDir, func(dir string, info os.FileInfo, err error) error {
   328  		if err == nil && info.IsDir() && dir != pluginsDir {
   329  			if matchesUninstallVersion(filepath.Base(dir), uninstallVersion) {
   330  				if err := uninstallVersionOfPlugin(dir, pluginName, filepath.Base(dir)); err != nil {
   331  					failed = true
   332  					return fmt.Errorf("failed to uninstall plugin %s %s. %s", pluginName, uninstallVersion, err.Error())
   333  				}
   334  			}
   335  		}
   336  		return nil
   337  	})
   338  	if err != nil {
   339  		logger.Error(true, err.Error())
   340  	}
   341  	if failed {
   342  		os.Exit(1)
   343  	}
   344  	if uninstallVersion == "" {
   345  		if err := os.RemoveAll(pluginsDir); err != nil {
   346  			logger.Fatalf(true, "Failed to remove directory %s. %s", pluginsDir, err.Error())
   347  		}
   348  	}
   349  }
   350  
   351  func matchesUninstallVersion(pluginDirPath, uninstallVersion string) bool {
   352  	if uninstallVersion == "" {
   353  		return true
   354  	}
   355  	return pluginDirPath == uninstallVersion
   356  }
   357  
   358  func uninstallVersionOfPlugin(pluginDir, pluginName, uninstallVersion string) error {
   359  	gp, err := parsePluginJSON(pluginDir, pluginName)
   360  	if err != nil {
   361  		logger.Infof(true, "Unable to read plugin's metadata, removing %s", pluginDir)
   362  		return os.RemoveAll(pluginDir)
   363  	}
   364  	if err := runPlatformCommands(gp.PreUnInstall, pluginDir); err != nil {
   365  		return err
   366  	}
   367  
   368  	if err := os.RemoveAll(pluginDir); err != nil {
   369  		return err
   370  	}
   371  	if err := runPlatformCommands(gp.PostUnInstall, path.Dir(pluginDir)); err != nil {
   372  		return err
   373  	}
   374  	logger.Infof(true, "Successfully uninstalled plugin %s %s.", pluginName, uninstallVersion)
   375  	return nil
   376  }
   377  
   378  func getDownloadLink(downloadUrls downloadUrls) (string, error) {
   379  	var platformLinks *platformSpecificURL
   380  	switch getGoArch() {
   381  	case arm64:
   382  		if downloadUrls.ARM64.Linux == "" {
   383  			logger.Info(true, "Download URL for 'arm64' architecture is not set for this plugin. Falling back to 'x86_64', but this may cause issues.")
   384  			platformLinks = &downloadUrls.X64
   385  		} else {
   386  			platformLinks = &downloadUrls.ARM64
   387  		}
   388  	case x86:
   389  		platformLinks = &downloadUrls.X86
   390  	default:
   391  		platformLinks = &downloadUrls.X64
   392  	}
   393  
   394  	var downloadLink string
   395  	switch runtime.GOOS {
   396  	case "windows":
   397  		downloadLink = platformLinks.Windows
   398  	case "darwin":
   399  		downloadLink = platformLinks.Darwin
   400  	default:
   401  		downloadLink = platformLinks.Linux
   402  	}
   403  	if downloadLink == "" {
   404  		return "", fmt.Errorf("Platform not supported for %s. Download URL not specified.", runtime.GOOS)
   405  	}
   406  	return downloadLink, nil
   407  }
   408  
   409  func getInstallDescription(plugin string, silent bool) (*installDescription, InstallResult) {
   410  	versionInstallDescriptionJSONFile := plugin + "-install.json"
   411  	versionInstallDescriptionJSONUrl, result := constructPluginInstallJSONURL(plugin)
   412  	if !result.Success {
   413  		return nil, installError(fmt.Errorf("Could not construct plugin install json file URL. %s", result.Error))
   414  	}
   415  	tempDir := common.GetTempDir()
   416  	defer func() {
   417  		err := common.Remove(tempDir)
   418  		if err != nil {
   419  			logger.Errorf(false, "unable to remove temp directory: %s", err.Error())
   420  		}
   421  	}()
   422  
   423  	downloadedFile, downloadErr := util.Download(versionInstallDescriptionJSONUrl, tempDir, versionInstallDescriptionJSONFile, silent)
   424  	if downloadErr != nil {
   425  		logger.Debugf(true, "Failed to download %s file: %s", versionInstallDescriptionJSONFile, downloadErr)
   426  		return nil, installError(fmt.Errorf("Invalid plugin name or there's a network issue while fetching plugin details."))
   427  	}
   428  
   429  	return getInstallDescriptionFromJSON(downloadedFile)
   430  }
   431  
   432  func getInstallDescriptionFromJSON(installJSON string) (*installDescription, InstallResult) {
   433  	InstallJSONContents, readErr := common.ReadFileContents(installJSON)
   434  	if readErr != nil {
   435  		return nil, installError(readErr)
   436  	}
   437  	installDescription := &installDescription{}
   438  	if err := json.Unmarshal([]byte(InstallJSONContents), installDescription); err != nil {
   439  		return nil, installError(err)
   440  	}
   441  	return installDescription, installSuccess("")
   442  }
   443  
   444  func constructPluginInstallJSONURL(p string) (string, InstallResult) {
   445  	repoURL := config.GaugeRepositoryUrl()
   446  	if repoURL == "" {
   447  		return "", installError(fmt.Errorf("Could not find gauge repository url from configuration."))
   448  	}
   449  	jsonURL := fmt.Sprintf("%s/%s", repoURL, p)
   450  	if qp := plugin.QueryParams(); qp != "" {
   451  		jsonURL += qp
   452  	}
   453  	return jsonURL, installSuccess("")
   454  }
   455  
   456  func (installDesc *installDescription) getVersion(version string) (*versionInstallDescription, error) {
   457  	for _, versionInstallDescription := range installDesc.Versions {
   458  		if versionInstallDescription.Version == version {
   459  			return &versionInstallDescription, nil
   460  		}
   461  	}
   462  	return nil, errors.New("Could not find install description for Version " + version)
   463  }
   464  
   465  func (installDesc *installDescription) getLatestCompatibleVersionTo(currentVersion *version.Version) (*versionInstallDescription, error) {
   466  	installDesc.sortVersionInstallDescriptions()
   467  	for _, versionInstallDesc := range installDesc.Versions {
   468  		if err := version.CheckCompatibility(currentVersion, &versionInstallDesc.GaugeVersionSupport); err == nil {
   469  			return &versionInstallDesc, nil
   470  		}
   471  	}
   472  	return nil, fmt.Errorf("Compatible version to %s not found", currentVersion)
   473  }
   474  
   475  func (installDesc *installDescription) sortVersionInstallDescriptions() {
   476  	sort.Sort(byDecreasingVersion(installDesc.Versions))
   477  }
   478  
   479  func getVersionedPluginDirName(pluginZip string) string {
   480  	zipFileName := filepath.Base(pluginZip)
   481  	if !strings.Contains(zipFileName, "nightly") {
   482  		rStr := `[0-9]+\.[0-9]+\.[0-9]+`
   483  		re, err := regexp.Compile(rStr)
   484  		if err != nil {
   485  			logger.Errorf(false, "Unable to compile regex %s: %s", rStr, err.Error())
   486  		}
   487  		return re.FindString(zipFileName)
   488  	}
   489  	rStr := `[0-9]+\.[0-9]+\.[0-9]+\.nightly-[0-9]+-[0-9]+-[0-9]+`
   490  	re, err := regexp.Compile(rStr)
   491  	if err != nil {
   492  		logger.Errorf(false, "Unable to compile regex %s: %s", rStr, err.Error())
   493  	}
   494  	return re.FindString(zipFileName)
   495  }
   496  
   497  func getRunnerJSONContents(file string) (*runner.RunnerInfo, error) {
   498  	var r runner.RunnerInfo
   499  	contents, err := common.ReadFileContents(file)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  	err = json.Unmarshal([]byte(contents), &r)
   504  	if err != nil {
   505  		return nil, err
   506  	}
   507  	return &r, nil
   508  }
   509  
   510  // AllPlugins install the latest version of all plugins specified in Gauge project manifest file
   511  func AllPlugins(silent, languageOnly bool) {
   512  	manifest, err := manifest.ProjectManifest()
   513  	if err != nil {
   514  		logger.Fatal(true, err.Error())
   515  	}
   516  	installPluginsFromManifest(manifest, silent, languageOnly)
   517  }
   518  
   519  // UpdatePlugins updates all the currently installed plugins to its latest version
   520  func UpdatePlugins(silent bool) {
   521  	var failedPlugin []string
   522  	pluginInfos, err := pluginInfo.GetPluginsInfo()
   523  	if err != nil {
   524  		logger.Info(true, err.Error())
   525  		os.Exit(0)
   526  	}
   527  	for _, pluginInfo := range pluginInfos {
   528  		logger.Debugf(true, "Updating plugin '%s'", pluginInfo.Name)
   529  		passed := HandleUpdateResult(Plugin(pluginInfo.Name, "", silent), pluginInfo.Name, false)
   530  		if !passed {
   531  			failedPlugin = append(failedPlugin, pluginInfo.Name)
   532  		}
   533  	}
   534  	if len(failedPlugin) > 0 {
   535  		logger.Fatalf(true, "Failed to update '%s' plugins.", strings.Join(failedPlugin, ", "))
   536  	}
   537  	logger.Info(true, "Successfully updated all the plugins.")
   538  }
   539  
   540  // HandleInstallResult handles the result of plugin Installation
   541  // TODO: Merge both HandleInstallResult and HandleUpdateResult, eliminate boolean exitIfFailure
   542  func HandleInstallResult(result InstallResult, pluginName string, exitIfFailure bool) bool {
   543  	if result.Info != "" {
   544  		logger.Debug(true, result.Info)
   545  	}
   546  	if result.Warning != "" {
   547  		logger.Warning(true, result.Warning)
   548  	}
   549  	if result.Skipped {
   550  		return true
   551  	}
   552  	if !result.Success {
   553  		if result.Version != "" {
   554  			logger.Errorf(true, "Failed to install plugin '%s' version %s.\nReason: %s", pluginName, result.Version, result.getMessage())
   555  		} else {
   556  			logger.Errorf(true, "Failed to install plugin '%s'.\nReason: %s", pluginName, result.getMessage())
   557  		}
   558  		if exitIfFailure {
   559  			os.Exit(1)
   560  		}
   561  		return false
   562  	}
   563  	if result.Version != "" {
   564  		logger.Infof(true, "Successfully installed plugin '%s' version %s", pluginName, result.Version)
   565  	} else {
   566  		logger.Infof(true, "Successfully installed plugin '%s'", pluginName)
   567  	}
   568  	return true
   569  }
   570  
   571  // HandleUpdateResult handles the result of plugin Installation
   572  func HandleUpdateResult(result InstallResult, pluginName string, exitIfFailure bool) bool {
   573  	if result.Info != "" {
   574  		logger.Debug(true, result.Info)
   575  	}
   576  	if result.Warning != "" {
   577  		logger.Warning(true, result.Warning)
   578  	}
   579  	if result.Skipped {
   580  		logger.Infof(true, "Plugin '%s' is up to date.", pluginName)
   581  		return true
   582  	}
   583  	if !result.Success {
   584  		logger.Errorf(true, "Failed to update plugin '%s'.\nReason: %s", pluginName, result.getMessage())
   585  		if exitIfFailure {
   586  			os.Exit(1)
   587  		}
   588  		return false
   589  	}
   590  	logger.Infof(true, "Successfully updated plugin '%s'.", pluginName)
   591  	return true
   592  }
   593  
   594  func installPluginsFromManifest(manifest *manifest.Manifest, silent, languageOnly bool) {
   595  	pluginsMap := make(map[string]bool)
   596  	if manifest.Language != "" {
   597  		pluginsMap[manifest.Language] = true
   598  	}
   599  
   600  	if !languageOnly {
   601  		for _, plugin := range manifest.Plugins {
   602  			pluginsMap[plugin] = false
   603  		}
   604  	}
   605  
   606  	for pluginName, isRunner := range pluginsMap {
   607  		if !IsCompatiblePluginInstalled(pluginName, isRunner) {
   608  			logger.Infof(true, "Compatible version of plugin %s not found. Installing plugin %s...", pluginName, pluginName)
   609  			HandleInstallResult(Plugin(pluginName, "", silent), pluginName, false)
   610  		} else {
   611  			logger.Debugf(true, "Plugin %s is already installed.", pluginName)
   612  		}
   613  	}
   614  }
   615  
   616  // IsCompatiblePluginInstalled checks if a plugin compatible to gauge is installed
   617  // TODO: This always checks if latest installed version of a given plugin is compatible. This should also check for older versions.
   618  func IsCompatiblePluginInstalled(pluginName string, isRunner bool) bool {
   619  	if isRunner {
   620  		return isCompatibleLanguagePluginInstalled(pluginName)
   621  	}
   622  	pd, err := plugin.GetPluginDescriptor(pluginName, "")
   623  	if err != nil {
   624  		return false
   625  	}
   626  	return version.CheckCompatibility(version.CurrentGaugeVersion, &pd.GaugeVersionSupport) == nil
   627  }
   628  
   629  func isCompatibleLanguagePluginInstalled(name string) bool {
   630  	jsonFilePath, err := plugin.GetLanguageJSONFilePath(name)
   631  	if err != nil {
   632  		return false
   633  	}
   634  
   635  	r, err := getRunnerJSONContents(jsonFilePath)
   636  	if err != nil {
   637  		return false
   638  	}
   639  	return version.CheckCompatibility(version.CurrentGaugeVersion, &r.GaugeVersionSupport) == nil
   640  }
   641  
   642  type byDecreasingVersion []versionInstallDescription
   643  
   644  func (a byDecreasingVersion) Len() int      { return len(a) }
   645  func (a byDecreasingVersion) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   646  func (a byDecreasingVersion) Less(i, j int) bool {
   647  	version1, _ := version.ParseVersion(a[i].Version)
   648  	version2, _ := version.ParseVersion(a[j].Version)
   649  	return version1.IsGreaterThan(version2)
   650  }
   651  
   652  // AddPluginToProject adds the given plugin to current Gauge project.
   653  func AddPluginToProject(pluginName string) error {
   654  	m, err := manifest.ProjectManifest()
   655  	if err != nil {
   656  		return nil
   657  	}
   658  	if plugin.IsLanguagePlugin(pluginName) {
   659  		return nil
   660  	}
   661  	pd, err := plugin.GetPluginDescriptor(pluginName, "")
   662  	if err != nil {
   663  		return err
   664  	}
   665  	if plugin.IsPluginAdded(m, pd) {
   666  		logger.Debugf(true, "Plugin %s is already added.", pd.Name)
   667  		return nil
   668  	}
   669  	m.Plugins = append(m.Plugins, pd.ID)
   670  	if err = m.Save(); err != nil {
   671  		return err
   672  	}
   673  	logger.Infof(true, "Plugin %s was successfully added to the project\n", pluginName)
   674  	return nil
   675  }