All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.pluginverifier.options.PluginsParsing.kt Maven / Gradle / Ivy

Go to download

Command-line interface for JetBrains Plugin Verifier with set of high-level tasks for plugin and IDE validation

There is a newer version: 1.379
Show newest version
/*
 * Copyright 2000-2020 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 */

package com.jetbrains.pluginverifier.options

import com.jetbrains.plugin.structure.base.plugin.PluginCreationFail
import com.jetbrains.plugin.structure.base.plugin.PluginCreationSuccess
import com.jetbrains.plugin.structure.base.telemetry.PluginTelemetry
import com.jetbrains.plugin.structure.base.utils.exists
import com.jetbrains.plugin.structure.base.utils.readLines
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import com.jetbrains.plugin.structure.intellij.plugin.IdePluginManager
import com.jetbrains.plugin.structure.intellij.problems.remapping.JsonUrlProblemLevelRemappingManager
import com.jetbrains.plugin.structure.intellij.problems.PluginCreationResultResolver
import com.jetbrains.plugin.structure.intellij.version.IdeVersion
import com.jetbrains.pluginverifier.dependencies.resolution.LastVersionSelector
import com.jetbrains.pluginverifier.dependencies.resolution.PluginVersionSelector
import com.jetbrains.pluginverifier.misc.retry
import com.jetbrains.pluginverifier.output.READING_PLUGIN_FROM
import com.jetbrains.pluginverifier.reporting.PluginVerificationReportage
import com.jetbrains.pluginverifier.repository.PluginRepository
import com.jetbrains.pluginverifier.repository.repositories.local.LocalPluginInfo
import com.jetbrains.pluginverifier.repository.repositories.marketplace.MarketplaceRepository
import com.jetbrains.pluginverifier.tasks.InvalidPluginFile
import java.nio.file.Path
import java.nio.file.Paths


/**
 * Utility class used to fill [pluginsSet] with a list of plugins to check.
 */
class PluginsParsing(
  private val pluginRepository: PluginRepository,
  private val reportage: PluginVerificationReportage,
  private val pluginsSet: PluginsSet,
  private val configuration: PluginParsingConfiguration = PluginParsingConfiguration()
) {

  private val pluginParsingConfigurationResolution = PluginParsingConfigurationResolution()

  /**
   * Parses command line options and add specified plugins compatible with [ideVersion].
   */
  fun addPluginsFromCmdOpts(opts: CmdOpts, ideVersion: IdeVersion) {
    for (pluginId in opts.pluginToCheckAllBuilds) {
      addAllCompatibleVersionsOfPlugin(pluginId, ideVersion)
    }

    for (pluginId in opts.pluginToCheckLastBuild) {
      addLastCompatibleVersionOfPlugin(pluginId, ideVersion)
    }

    val pluginsToCheckFile = opts.pluginsToCheckFile?.let { Paths.get(it) }
    if (pluginsToCheckFile != null) {
      addPluginsListedInFile(pluginsToCheckFile, listOf(ideVersion))
    }
  }

  /**
   * Adds update #[updateId] to [pluginsSet].
   */
  fun addUpdateById(updateId: Int) {
    val updateInfo = pluginRepository.retry("get plugin info for #$updateId") {
      (pluginRepository as? MarketplaceRepository)?.getPluginInfoByUpdateId(updateId)
    } ?: throw IllegalArgumentException("Update #$updateId is not found in the $pluginRepository")
    pluginsSet.schedulePlugin(updateInfo)
  }

  /**
   * Adds last version of [pluginId] to [pluginsSet].
   */
  fun addLastPluginVersion(pluginId: String) {
    val selector = LastVersionSelector()
    val selectResult = retry("Latest version of $pluginId") {
      selector.selectPluginVersion(pluginId, pluginRepository)
    }
    if (selectResult is PluginVersionSelector.Result.Selected) {
      pluginsSet.schedulePlugin(selectResult.pluginInfo)
    }
  }

  /**
   * Parses lines of [pluginsListFile] and adds specified plugins to the [pluginsSet].
   */
  fun addPluginsListedInFile(pluginsListFile: Path, ideVersions: List) {
    reportage.logVerificationStage("Reading plugins list to check from file ${pluginsListFile.toAbsolutePath()} against IDE versions ${ideVersions.joinToString { it.asString() }}")
    val specs = pluginsListFile.readLines()
      .map { it.trim() }
      .filterNot { it.isEmpty() }
      .filterNot { it.startsWith("//") }

    for (spec in specs) {
      addPluginBySpec(spec, pluginsListFile, ideVersions)
    }
  }

  /**
   * Adds all plugins that correspond to one of the following specs:
   *
   * ```
   * - id:                    // all compatible version of 
   * - version::     // all compatible version of 
   * - $id or id$                        // only the last version of the plugin compatible with IDEs will be checked
   * - #                      // update #
   * - path:                // plugin from , where  may be relative to base path.
   * -                            // treated as a path: or id:
   * ```
   */
  fun addPluginBySpec(spec: String, basePath: Path, ideVersions: List) {
    if (spec.startsWith('$') || spec.endsWith('$')) {
      val pluginId = spec.trim('$').trim()
      ideVersions.forEach { addLastCompatibleVersionOfPlugin(pluginId, it) }
      return
    }

    if (spec.startsWith("#")) {
      val updateId = spec.substringAfter("#").toIntOrNull() ?: return
      addUpdateById(updateId)
      return
    }

    if (spec.startsWith("id:")) {
      val pluginId = spec.substringAfter("id:")
      ideVersions.forEach { addAllCompatibleVersionsOfPlugin(pluginId, it) }
      return
    }

    if (spec.startsWith("version:")) {
      val idAndVersion = spec.substringAfter("version:")
      val id = idAndVersion.substringBefore(":").trim()
      val version = idAndVersion.substringAfter(":").trim()
      require(version.isNotEmpty()) { "Empty version specified for a plugin to be checked: {$spec}" }
      addPluginVersion(id, version)
      return
    }

    val pluginFile = if (spec.startsWith("path:")) {
      val linePath = spec.substringAfter("path:")
      tryFindPluginByPath(basePath, linePath) ?: throw IllegalArgumentException("Invalid path: $linePath")
    } else {
      tryFindPluginByPath(basePath, spec)
    }

    if (pluginFile != null) {
      addPluginFile(pluginFile, true)
    } else {
      ideVersions.forEach { addAllCompatibleVersionsOfPlugin(spec, it) }
    }
  }

  private fun tryFindPluginByPath(baseFilePath: Path, linePath: String): Path? {
    val path = try {
      Paths.get(linePath)
    } catch (e: Exception) {
      return null
    }

    if (path.exists()) {
      return path
    }

    val siblingPath = baseFilePath.resolveSibling(linePath)
    if (siblingPath.exists()) {
      return siblingPath
    }
    return null
  }

  /**
   * Adds all versions of the plugin with ID `pluginId` compatible with `ideVersion`.
   */
  private fun addAllCompatibleVersionsOfPlugin(pluginId: String, ideVersion: IdeVersion) {
    val stepName = "All versions of plugin '$pluginId' compatible with $ideVersion"
    val compatibleVersions = pluginRepository.retry(stepName) {
      getAllVersionsOfPlugin(pluginId).filter { it.isCompatibleWith(ideVersion) }
    }
    reportage.logVerificationStage("$stepName: " + if (compatibleVersions.isEmpty()) "no compatible versions" else compatibleVersions.joinToString { it.presentableName })
    pluginsSet.schedulePlugins(compatibleVersions)
  }

  /**
   * Adds version [version] of plugin with XML ID [pluginId].
   */
  private fun addPluginVersion(pluginId: String, version: String) {
    val stepName = "Plugin '$pluginId' of version '$version'"
    val allVersionsOfPlugin = pluginRepository.retry(stepName) {
      getAllVersionsOfPlugin(pluginId)
    }
    val pluginInfo = allVersionsOfPlugin.find { it.version == version }
    reportage.logVerificationStage(
      stepName + ": " + (pluginInfo?.presentableName ?: "no version '$version' of plugin '$pluginId' available")
    )
    pluginInfo ?: return
    pluginsSet.schedulePlugin(pluginInfo)
  }

  /**
   * Adds the last version of plugin with ID `pluginId` compatible with `ideVersion`.
   */
  private fun addLastCompatibleVersionOfPlugin(pluginId: String, ideVersion: IdeVersion) {
    val stepName = "Last version of plugin '$pluginId' compatible with $ideVersion"
    val lastVersion = pluginRepository.retry(stepName) {
      getLastCompatibleVersionOfPlugin(ideVersion, pluginId)
    }
    reportage.logVerificationStage("$stepName: ${lastVersion?.presentableName ?: "no compatible version"}")
    lastVersion ?: return
    pluginsSet.schedulePlugin(lastVersion)
  }

  /**
   * Adds plugin from [pluginFile].
   */
  fun addPluginFile(pluginFile: Path, validateDescriptor: Boolean) {
    if (!pluginFile.exists()) {
      throw RuntimeException("Plugin file '$pluginFile' with absolute path '${pluginFile.toAbsolutePath()}' doesn't exist")
    }

    reportage.logVerificationStage(READING_PLUGIN_FROM.format(pluginFile))
    val pluginCreationResult = IdePluginManager
      .createManager()
      .createPlugin(pluginFile, validateDescriptor, problemResolver = configuration.problemResolver)
    with(pluginCreationResult) {
      when (this) {
        is PluginCreationSuccess -> {
          pluginsSet.scheduleLocalPlugin(plugin).also {
            reportLocalPluginTelemetry(plugin, telemetry)
          }
        }
        is PluginCreationFail -> {
          reportage.logVerificationStage("Plugin is invalid in $pluginFile: ${errorsAndWarnings.joinToString()}")
          pluginsSet.invalidPluginFiles.add(InvalidPluginFile(pluginFile, errorsAndWarnings))
        }
      }
    }
  }

  private fun reportLocalPluginTelemetry(plugin: IdePlugin, telemetry: PluginTelemetry) {
    reportage.reportTelemetry(LocalPluginInfo(plugin), telemetry)
  }

  private val PluginParsingConfiguration.problemResolver: PluginCreationResultResolver
    get() = pluginParsingConfigurationResolution.resolveProblemLevelMapping(this, JsonUrlProblemLevelRemappingManager.fromClassPathJson())

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy