sbt-idea-plugin
SBT plugin that makes development of IntelliJ Platform plugins in Scala easier by providing features such as:
- Downloading and attaching IntelliJ Platform binaries
- Setting up the environment for running tests
- Flexible way to define plugin artifact structure
- Publishing the plugin to JetBrains plugin repository
For a comprehensive usage example see Scala plugin or HOCON plugin build definition.
A complete list of public IJ plugins implemented in Scala/SBT can be found on IntelliJ Platform Explorer
Note that some features of this plugin may be used independently, i.e. if you only want to print project structure or package artifacts you can depend on:
"org.jetbrains" % "sbt-declarative-visualizer" % "LATEST_VERSION"
or
"org.jetbrains" % "sbt-declarative-packaging" % "LATEST_VERSION"
Quickstart: IJ Plugin Template Project
To quickly create a Scala based IJ Plugin we provide a template project. You can use it in two ways:
-
Recommended: If you have Scala plugin version 2021.1+ simply use
New Project | Scala | IntelliJ Platform Plugin
This wizard will automatically clone the project template from GH and setup it with the most recent dependencies. -
Manually create your own repo on GH from the JetBrains / sbt-idea-example template by clicking the green
Use this template
button. Clone the sources and open thebuild.sbt
viaFile | Open
menu in IDEA by choosingOpen as a project
.
Manual Installation (adding to an already existing sbt build)
From version 1.0.0, this plugin is published for sbt 0.13 and 1.0
- Insert into
project/plugins.sbt
:
addSbtPlugin("org.jetbrains" % "sbt-idea-plugin" % "LATEST_VERSION")
-
Enable the plugin for your desired projects (your main plugin project and all its dependencies)
-
Run SBT and the plugin will automatically download and attach IntelliJ Platform dependencies.
-
Start coding
IntelliJ Platform and plugin
intellijPluginName in ThisBuild :: SettingKey[String]
Default: name.in(LocalRootProject).value
Name of your plugin. Better set this beforehand since several other settings such as IntelliJ Platform directories and artifact names depend on it.
intellijBuild in ThisBuild :: SettingKey[String]
Default: LATEST-EAP-SNAPSHOT
Selected IDE's build number. Binaries and sources of this build will be downloaded from the repository and used in compilation and testing. You can find build number of your IntelliJ product in Help -> About
dialog. However, it might be incomplete, so it is strongly recommended to verify it against available releases and available snapshots.
intellijPlatform in ThisBuild :: SettingKey[IntelliJPlatform]
Default: IntelliJPlatform.IdeaCommunity
Edition of IntelliJ IDE to use in project. Currently available options are:
- IdeaCommunity
- IdeaUltimate
- PyCharmCommunity
- PyCharmProfessional
- CLion
- MPS
intellijDownloadDirectory in ThisBuild :: SettingKey[File]
Default: homePrefix / s".${intellijPluginName.value.removeSpaces}Plugin${intellijPlatform.value.shortname}" / "sdk"
Directory where IntelliJ binaries and sources will be downloaded.
intellijDownloadSources in ThisBuild :: SettingKey[Boolean]
Default: true
Flag indicating whether IntelliJ sources should be downloaded alongside binaries or not.
intellijPlugins :: SettingKey[IdeaPlugin]
Default: Seq.empty
IntelliJ plugins to depend on. Bundled(internal) plugins are specified by their plugin ID. Plugins from repo can be specified by the plugin's id, optional version and update channel. Plugins will be checked for compatibility against the intellijBuild
you specified and updated to the latest version unless some specific version is given explicitly. Inter-plugin dependencies are also transitively resolved(e.g. depending on the Scala plugin will automatically attach Java and other plugin dependencies)
Plugin IDs can be either searched by plugin name with the help of searchPluginId task or manually
You can tune plugin resolving on individual plugin level by specifying several options to toPlugin
method:
transitive
- use transitive plugin resolution(default: true)optionalDeps
- resolve optional plugin dependencies(default: true)excludedIds
- blacklist certain plugins from transitive resolution(default: Set.empty)
com.intellij.java
// use properties plugin bundled with IDEA
intellijPlugins += "com.intellij.properties".toPlugin
// use Scala plugin as a dependency
intellijPlugins += "org.intellij.scala".toPlugin
// use Scala plugin version 2019.2.1
intellijPlugins += "org.intellij.scala:2019.2.1".toPlugin
// use latest nightly build from the repo
intellijPlugins += "org.intellij.scala::Nightly".toPlugin
// use specific version from Eap update channel
intellijPlugins += "org.intellij.scala:2019.3.2:Eap".toPlugin
// add JavaScript plugin but without its Grazie plugin dependency
intellijPlugins += "JavaScript".toPlugin(excludedIds = Set("tanvd.grazi"))
searchPluginId :: Map[String, (String, Boolean)]
Usage: searchPluginId [--nobundled|--noremote] <plugin name regexp>
Searches and prints plugins across locally installed IJ sdk and plugin marketplace. Use provided flags to limit search scope to only bundled or marketplace plugins.
> searchPluginId Prop
[info] bundled - Properties[com.intellij.properties]
[info] bundled - Resource Bundle Editor[com.intellij.properties.bundle.editor]
jbrInfo :: Option[JbrInfo]
Default: AutoJbr()
JetBrains Java runtime version to use when running the IDE with the plugin. By default JBR version is extracted from IDE installation metadata. Only jbr 11 is supported. Available versions can be found on jbr bintray. To disable, set to NoJbr
patchPluginXml :: SettingKey[pluginXmlOptions]
Default: pluginXmlOptions.DISABLED
Define some plugin.xml
fields to be patched when building the artifact. Only the file in target
folder is patched, original sources are left intact. Available options are:
patchPluginXml := pluginXmlOptions { xml =>
xml.version = version.value
xml.pluginDescription = "My cool IDEA plugin"
xml.changeNotes = sys.env("CHANGE_LOG_FROM_CI")
xml.sinceBuild = (intellijBuild in ThisBuild).value
xml.untilBuild = "193.*"
}
intellijVMOptions :: SettingKey[IntellijVMOptions]
Fine tune java VM options for running the plugin with runIDE
task. Example:
intellijVMOptions := intellijVMOptions.value.copy(xmx = 2048, xms = 256)
ideaConfigOptions :: SettingKey[IdeaConfigBuildingOptions]
Fine tune how IntelliJ run configurations are generated when importing the project in IDEA.
runIDE [noPCE] [noDebug] [suspend] [blocking] :: InputKey[Unit]
Runs IntelliJ IDE with current plugin. This task is non-blocking by default, so you can continue using SBT console.
By default IDE is run with non-suspending debug agent on port 5005
. This can be overridden by either optional arguments above, or by modifying default intellijVMOptions
. ProcessCancelledExceptiona
can also be disabled for current run by providing noPCE
option.
publishPlugin [channel] :: InputKey[String]
Upload and publish your IntelliJ plugin on https://plugins.jetbrains.com. In order to publish to the repo you need to obtain permanent token and either place it into ~/.ij-plugin-repo-token
file or pass via IJ_PLUGIN_REPO_TOKEN
env or java property.
This task also expects an optional argument - a custom release channel. If omitted, plugin will be published to the default plugin repository channel (Stable)
updateIntellij :: TaskKey[Unit]
This task is run automatically when sbt project is loaded. Downloads IntelliJ's binaries and sources, puts them into intellijBaseDirectory
directory. Also downloads or updates external plugins.
buildIntellijOptionsIndex :: TaskKey[Unit]
Builds index of options provided by the plugin to make them searchable via search everywhere action. This task should either be manually called instead of packageArtifact
or before packageArtifactZip
since it patches jars already built by packageArtifact
.
Packaging
packageMethod :: SettingKey[PackagingMethod]
Default for root project: PackagingMethod.Standalone(targetPath = s"lib/${name.value}.jar")
Default for all other subprojects: PackagingMethod.MergeIntoParent()
Controls how current project will be treated when packaging the plugin artifact.
// produce standalone jar with the same name as the project:
packageMethod := PackagingMethod.Standalone()
// put all classes of this project into parent's jar
// NB: this option supports transitive dependencies on projects: it will walk up the dependency
// tree to find the first Standalone() project, however if your project has multiple such parents
// this will result in an error - in this case use MergeIntoOther(project: Project) to expicitly
// specify in which project to merge into
packageMethod := PackagingMethod.MergeIntoParent()
// merge all dependencies of this project in a standalone jar
// being used together with assembleLibraries setting allows sbt-assembly like packaging
// the project may contain classes but they will be ignored during packaging
packageMethod := PackagingMethod.DepsOnly("lib/myProjectDeps.jar")
assembleLibraries := true
// skip project alltogether during packaging
packageMethod := PackagingMethod.Skip()
packageLibraryMappings :: SettingKey[Seq[(ModuleID, Option[String])]]
Default for root project: Seq.empty
Default for all other projects:
"org.scala-lang" % "scala-.*" % ".*" -> None ::
"org.scala-lang.modules" % "scala-.*" % ".*" -> None :: Nil
Sequence of rules to fine-tune how the library dependencies are packaged. By default all dependencies including transitive are placed in the subfolder defined by packageLibraryBaseDir
(defaults to "lib") of the plugin artifact.
// merge all scalameta jars into a single jar
packageLibraryMappings += "org.scalameta" %% ".*" % ".*" -> Some("lib/scalameta.jar")
// skip packaging protobuf
packageLibraryMappings += "com.google.protobuf" % "protobuf-java" % ".*" -> None
// rename scala library(strip version suffix)
packageLibraryMappings += "org.scala-lang" % "scala-library" % scalaVersion -> Some("lib/scala-library.jar")
packageLibraryBaseDir :: SettingKey[File]
Default: file("lib")
Sets the per-project default sub-folder into which external libraries are packaged. Rules from packageLibraryMappings
will override this setting.
NB!: This directory must be relative to the packageOutputDir
so don't prepend values of the keys with absolute paths (such as target
or baseDirectory
) to it
NB!: IDEA plugin classloader only adds the lib
folder to the classpath when loading your plugin. Modifying this setting will essentially exclude the libraries of a project from automatic classloading
packageLibraryBaseDir := file("lib") / "third-party"
// protobuf will still be packaged into lib/protobuf.jar
packageLibraryMappings += "com.google.protobuf" % "protobuf-java" % ".*" -> Some("lib/protobuf.jar")
packageFileMappings :: SettingKey[Seq[(File, String)]]
Default: Seq.empty
Defines mappings for adding custom files to the artifact or even override files inside jars. Target path is considered to be relative to packageOutputDir
.
// copy whole folder recursively to artifact root
packageFileMappings += target.value / "repo" -> "repo/"
// package single file info a jar
packageFileMappings += "resources" / "ILoopWrapperImpl.scala" ->
"lib/jps/repl-interface-sources.jar"
// overwrite some file inside already existing jar of the artifact
packageFileMappings += "resources" / "META-INF" / "plugin.xml" ->
"lib/scalaUltimate.jar!/META-INF/plugin.xml"
packageAdditionalProjects :: SettingKey[Seq[Project]]
Default: Seq.empty
By default the plugin builds artifact structure based on internal classpath dependencies of the projects in an SBT build(dependsOn(...)
). However, sometimes one may need to package a project that no other depends upon. This setting is used to explicitly tell the plugin which projects to package into the artifact without a need to introduce unwanted classpath dependency.
shadePatterns :: SettingKey[Seq[ShadePattern]]
Default: Seq.empty
Class shading patterns to be applied by JarJar library. Used to resolve name clashes with libraries from IntelliJ platform such as protobuf.
shadePatterns += ShadePattern("com.google.protobuf.**", "zinc.protobuf.@1")
bundleScalaLibrary in ThisBuild :: SettingKey[Boolean]
Trying to load the same classes in your plugin's classloader which have already been loaded by a parent classloader will result in classloader constraint violation. A vivid example of this scenario is depending on some other plugin, that bundles scala-library.jar(e.g. Scala plugin for IJ) and still bundling your own.
To workaround this issue sbt-idea-plugin
tries to automatically detect if your plugin project has dependencies on other plugins with Scala and filter out scala-library.jar from the resulting artifact. However, the heuristic cannot cover all possible cases and thereby this setting is exposed to allow manual control over bundling the scala-library.jar
packageOutputDir :: SettingKey[File]
Default: target.value / "plugin" / intellijPluginName.in(ThisBuild).value.removeSpaces
Folder to place the assembled artifact into.
packageArtifact :: TaskKey[File]
Builds unpacked plugin distribution. This task traverses dependency graph of the build and uses settings described in the section above to create sub-artifact structure for each project. By default all child projects' classes are merged into the root project jar, which is placed into the "lib" folder of the plugin artifact, all library dependencies including transitive are placed in the "lib" folder as well.
packageArtifactZip :: TaskKey[File]
Produces ZIP file from the artifact produced by packagePlugin
task. This is later used by publishPlugin
as an artifact to upload.
Utils
findLibraryMapping :: InputKey[Seq[(String, Seq[(ModuleKey, Option[String])])]]
Returns detailed info about libraries and their mappings by a library substring. Helps to answer questions such as "Why is this jar in the artifact?" or "Which module introduced this jar?" Example:
sbt:scalaUltimate> show findMapping interface
[info] * (runtimeDependencies,ArrayBuffer((org.scala-sbt:compiler-interface:1.4.0-M12[],Some(lib/jps/compiler-interface.jar)), (org.scala-sbt:util-interface:1.3.0[],Some(lib/jps/sbt-interface.jar))))
[info] * (repackagedZinc,ArrayBuffer((org.scala-sbt:compiler-interface:1.4.0-M12[],Some(*)), (org.scala-sbt:launcher-interface:1.1.3[],Some(*)), (org.scala-sbt:util-interface:1.3.0[],Some(*))))
[info] * (compiler-jps,ArrayBuffer((org.scala-sbt:util-interface:1.3.0[],Some(*)), (org.scala-sbt:compiler-interface:1.4.0-M12[],Some(lib/jps/compiler-interface.jar))))
[info] * (compiler-shared,ArrayBuffer((org.scala-sbt:util-interface:1.3.0[],Some(*)), (org.scala-sbt:compiler-interface:1.4.0-M12[],Some(*))))
printProjectGraph :: TaskKey[Unit]
Prints ASCII graph of currently selected project to console. Useful for debugging complex builds.
Running the plugin
From SBT
To run the plugin from SBT simply use runIDE task. Your plugin will be automatically compiled, an artifact built and attached to new IntelliJ instance.
Debugger can later be attached to the process remotely - the default port is 5005.
From IDEA
sbt-idea-plugin
generates IDEA-readable artifact xml and run configuration on project import- After artifact and run configuration have been created(they're located in
.idea
folder of the project) you can run or debug the new run configuration. This will compile the project, build the artifact and attach it to the new IDEA instance -
❗ Note that doing an "SBT Refresh" is required after making changes to your build that affect the final artifact(i.e. changinglibraryDependencies
), in order to update IDEA configs
Custom IntelliJ artifacts repo
Under some circumstances using a proxy may be required to access IntelliJ artifacts repo, or there even is a local artifact mirror set up. To use non-default repository for downloading IntelliJ product distributions set sbtidea.ijrepo
jvm property. Example: -Dsbtidea.ijrepo=https://proxy.mycompany.com/intellij-repository
Auto enable the plugin
Sbt-idea-plugin currently breaks scalaJS compilation, and thereby has autoloading disabled. To enable it either add enablePlugins(SbtIdeaPlugin)
to project definition. Example:
lazy val hocon = project.in(file(".")).settings(
scalaVersion := "2.12.8",
version := "2019.1.2",
intellijInternalPlugins := Seq("properties"),
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test",
).enablePlugins(SbtIdeaPlugin)
If you with to automatically enable the plugin for all projects in your build, place the following class into top level project
folder of your build.
import org.jetbrains.sbtidea.AbstractSbtIdeaPlugin
object AutoSbtIdeaPlugin extends AbstractSbtIdeaPlugin {
override def trigger = allRequirements
}