From 4913b13a2dfff1d278931eb47e96bb3066e62a54 Mon Sep 17 00:00:00 2001
From: Matt Ellis <m.t.ellis@gmail.com>
Date: Thu, 9 May 2024 13:48:56 +0100
Subject: [PATCH] Migrate to IntelliJ Platform Gradle Plugin 2.0

Also updates the GitHub workflows to use the new name for the testIdeUi task, and the TeamCity files to use the new name for the VerifyPlugin task
---
 .github/workflows/runUiOctopusTests.yml       |   6 +-
 .github/workflows/runUiPyTests.yml            |   4 +-
 .github/workflows/runUiTests.yml              |   6 +-
 .gitignore                                    |   1 +
 .../IdeaVim_full_verification.xml             |   3 +-
 .teamcity/_Self/buildTypes/PluginVerifier.kt  |   2 +-
 build.gradle.kts                              | 135 ++++++++++--------
 gradle.properties                             |  21 ++-
 tests/java-tests/build.gradle.kts             |  45 +++---
 tests/long-running-tests/build.gradle.kts     |  48 +++----
 tests/property-tests/build.gradle.kts         |  48 +++----
 vim-engine/build.gradle.kts                   |  10 +-
 12 files changed, 184 insertions(+), 145 deletions(-)

diff --git a/.github/workflows/runUiOctopusTests.yml b/.github/workflows/runUiOctopusTests.yml
index 65314d0fc..65576aef1 100644
--- a/.github/workflows/runUiOctopusTests.yml
+++ b/.github/workflows/runUiOctopusTests.yml
@@ -23,7 +23,7 @@ jobs:
       - name: Run Idea
         run: |
           mkdir -p build/reports
-          gradle runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
+          gradle testIdeUi -Doctopus.handler=false > build/reports/idea.log &
       - name: Wait for Idea started
         uses: jtalk/url-health-check-action@v3
         with:
@@ -63,7 +63,7 @@ jobs:
 #          export DISPLAY=:99.0
 #          Xvfb -ac :99 -screen 0 1920x1080x16 &
 #          mkdir -p build/reports
-#          gradle :runIdeForUiTests #> build/reports/idea.log
+#          gradle :testIdeUi #> build/reports/idea.log
 #      - name: Wait for Idea started
 #        uses: jtalk/url-health-check-action@1.5
 #        with:
@@ -78,4 +78,4 @@ jobs:
 #        with:
 #          name: ui-test-fails-report-linux
 #          path: |
-#            ui-test-example/build/reports
\ No newline at end of file
+#            ui-test-example/build/reports
diff --git a/.github/workflows/runUiPyTests.yml b/.github/workflows/runUiPyTests.yml
index e67bd9fd3..2ac837779 100644
--- a/.github/workflows/runUiPyTests.yml
+++ b/.github/workflows/runUiPyTests.yml
@@ -26,7 +26,7 @@ jobs:
       - name: Run Idea
         run: |
           mkdir -p build/reports
-          gradle :runIdeForUiTests -PideaType=PC > build/reports/idea.log &
+          gradle :testIdeUi -PideaType=PC > build/reports/idea.log &
       - name: Wait for Idea started
         uses: jtalk/url-health-check-action@v3
         with:
@@ -49,4 +49,4 @@ jobs:
           path: |
             build/reports
             tests/ui-py-tests/build/reports
-            sandbox-idea-log
\ No newline at end of file
+            sandbox-idea-log
diff --git a/.github/workflows/runUiTests.yml b/.github/workflows/runUiTests.yml
index 26f5f1023..9df972b86 100644
--- a/.github/workflows/runUiTests.yml
+++ b/.github/workflows/runUiTests.yml
@@ -23,7 +23,7 @@ jobs:
       - name: Run Idea
         run: |
           mkdir -p build/reports
-          gradle runIdeForUiTests > build/reports/idea.log &
+          gradle testIdeUi > build/reports/idea.log &
       - name: Wait for Idea started
         uses: jtalk/url-health-check-action@v3
         with:
@@ -63,7 +63,7 @@ jobs:
 #          export DISPLAY=:99.0
 #          Xvfb -ac :99 -screen 0 1920x1080x16 &
 #          mkdir -p build/reports
-#          gradle :runIdeForUiTests #> build/reports/idea.log
+#          gradle :testIdeUi #> build/reports/idea.log
 #      - name: Wait for Idea started
 #        uses: jtalk/url-health-check-action@1.5
 #        with:
@@ -78,4 +78,4 @@ jobs:
 #        with:
 #          name: ui-test-fails-report-linux
 #          path: |
-#            ui-test-example/build/reports
\ No newline at end of file
+#            ui-test-example/build/reports
diff --git a/.gitignore b/.gitignore
index 50ac91409..85c6f93d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 *.swp
 /.gradle/
+/.intellijPlatform/
 
 /.idea/
 !/.idea/scopes
diff --git a/.idea/runConfigurations/IdeaVim_full_verification.xml b/.idea/runConfigurations/IdeaVim_full_verification.xml
index d6b1b41af..9b6ac4c11 100644
--- a/.idea/runConfigurations/IdeaVim_full_verification.xml
+++ b/.idea/runConfigurations/IdeaVim_full_verification.xml
@@ -12,7 +12,7 @@
       <option name="taskNames">
         <list>
           <option value="check" />
-          <option value="runPluginVerifier" />
+          <option value="verifyPlugin" />
         </list>
       </option>
       <option name="vmOptions" value="" />
@@ -20,6 +20,7 @@
     <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
     <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
     <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
     <method v="2" />
   </configuration>
 </component>
\ No newline at end of file
diff --git a/.teamcity/_Self/buildTypes/PluginVerifier.kt b/.teamcity/_Self/buildTypes/PluginVerifier.kt
index f25fa97c7..9d607db16 100644
--- a/.teamcity/_Self/buildTypes/PluginVerifier.kt
+++ b/.teamcity/_Self/buildTypes/PluginVerifier.kt
@@ -22,7 +22,7 @@ object PluginVerifier : IdeaVimBuildType({
 
   steps {
     gradle {
-      tasks = "clean runPluginVerifier"
+      tasks = "clean verifyPlugin"
       buildFile = ""
       enableStacktrace = true
     }
diff --git a/build.gradle.kts b/build.gradle.kts
index aa6ac24bb..70024b739 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -32,6 +32,7 @@ import org.eclipse.jgit.api.Git
 import org.eclipse.jgit.lib.RepositoryBuilder
 import org.intellij.markdown.ast.getTextInNode
 import org.jetbrains.changelog.Changelog
+import org.jetbrains.intellij.platform.gradle.TestFrameworkType
 import org.kohsuke.github.GHUser
 import java.net.HttpURLConnection
 import java.net.URL
@@ -67,22 +68,20 @@ plugins {
   kotlin("jvm") version "1.9.22"
   application
   id("java-test-fixtures")
-
-  id("org.jetbrains.intellij") version "1.17.3"
+  id("org.jetbrains.intellij.platform") version "2.0.0-beta7"
   id("org.jetbrains.changelog") version "2.2.0"
-
   id("org.jetbrains.kotlinx.kover") version "0.6.1"
   id("com.dorongold.task-tree") version "4.0.0"
-
   id("com.google.devtools.ksp") version "1.9.22-1.0.17"
 }
 
+val moduleSources by configurations.registering
+
 // Import variables from gradle.properties file
 val javaVersion: String by project
 val kotlinVersion: String by project
 val ideaVersion: String by project
 val ideaType: String by project
-val downloadIdeaSources: String by project
 val instrumentPluginCode: String by project
 val remoteRobotVersion: String by project
 
@@ -94,7 +93,9 @@ val youtrackToken: String by project
 
 repositories {
   mavenCentral()
-  maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
+  intellijPlatform {
+    defaultRepositories()
+  }
 }
 
 dependencies {
@@ -105,9 +106,26 @@ dependencies {
   compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
   compileOnly("org.jetbrains:annotations:24.1.0")
 
-  // --------- Test dependencies ----------
+  intellijPlatform {
+    // Note that it is also possible to use local("...") to compile against a locally installed IDE
+    // E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
+    // Or something like: intellijIdeaUltimate(ideaVersion)
+    create(ideaType, ideaVersion)
 
-  testImplementation(testFixtures(project(":")))
+    pluginVerifier()
+    zipSigner()
+    instrumentationTools()
+
+    testFramework(TestFrameworkType.Platform)
+    testFramework(TestFrameworkType.JUnit5)
+
+    // AceJump is an optional dependency. We use their SessionManager class to check if it's active
+    plugin("AceJump", "3.8.11")
+  }
+
+  moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
+
+  // --------- Test dependencies ----------
 
   testApi("com.squareup.okhttp3:okhttp:4.12.0")
 
@@ -179,15 +197,28 @@ tasks {
     }
   }
 
+  // Note that this will run the plugin installed in the IDE specified in dependencies. To run in a different IDE, use
+  // a custom task (see below)
   runIde {
     systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
   }
 
-  downloadRobotServerPlugin {
-    version.set(remoteRobotVersion)
-  }
+  // Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies
+  // Note that the version must be greater than the plugin's target version, for obvious reasons
+//  val runIdeCustom by registering(CustomRunIdeTask::class) {
+//    type = IntelliJPlatformType.Rider
+//    version = "2024.1.2"
+//  }
 
-  runIdeForUiTests {
+  // Uncomment to run the plugin in a locally installed IDE
+//  val runIdeLocal by registering(CustomRunIdeTask::class) {
+//    localPath = file("/Users/{user}/Applications/WebStorm.app")
+//  }
+
+  // Start the default IDE with both IdeaVim and the robot server plugin installed, ready to run a UI test task. The
+  // robot server plugin is automatically added as a dependency to this task, and Gradle will take care of downloading.
+  // Note that the CustomTestIdeUiTask can be used to run tests against a different IDE
+  testIdeUi {
     systemProperty("robot-server.port", "8082")
     systemProperty("ide.mac.message.dialogs.as.sheets", "false")
     systemProperty("jb.privacy.policy.text", "<!--999.999-->")
@@ -198,28 +229,21 @@ tasks {
   }
 
   // Add plugin open API sources to the plugin ZIP
-  val createOpenApiSourceJar by registering(Jar::class) {
-    // Java sources
-    from(sourceSets.main.get().java) {
-      include("**/com/maddyhome/idea/vim/**/*.java")
-    }
-    from(project(":vim-engine").sourceSets.main.get().java) {
-      include("**/com/maddyhome/idea/vim/**/*.java")
-    }
-    // Kotlin sources
-    from(kotlin.sourceSets.main.get().kotlin) {
-      include("**/com/maddyhome/idea/vim/**/*.kt")
-    }
-    from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
-      include("**/com/maddyhome/idea/vim/**/*.kt")
-    }
+  val sourcesJar by registering(Jar::class) {
+    dependsOn(moduleSources)
     destinationDirectory.set(layout.buildDirectory.dir("libs"))
-    archiveClassifier.set("src")
+    archiveClassifier.set(DocsType.SOURCES)
+    from(sourceSets.main.map { it.kotlin })
+    from(provider {
+      moduleSources.map {
+        it.map { jarFile -> zipTree(jarFile) }
+      }
+    })
   }
 
   buildPlugin {
-    dependsOn(createOpenApiSourceJar)
-    from(createOpenApiSourceJar) { into("lib/src") }
+    dependsOn(sourcesJar)
+    from(sourcesJar) { into("lib/src") }
   }
 }
 
@@ -245,44 +269,41 @@ gradle.projectsEvaluated {
 
 // --- Intellij plugin
 
-intellij {
-  version.set(ideaVersion)
-  type.set(ideaType)
-  pluginName.set("IdeaVim")
+intellijPlatform {
+  pluginConfiguration {
+    name = "IdeaVim"
+    changeNotes.set(
+      """<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
+    )
 
-  updateSinceUntilBuild.set(false)
+    ideaVersion {
+      // Set the since-build value, but leave until-build open ended (default is MAJOR.*)
+      // Don't forget to update plugin.xml
+      // TODO: Do we need this to be here *and* in plugin.xml?
+      sinceBuild.set("241.15989.150")
+      untilBuild.set(provider { null })
+    }
+  }
 
-  downloadSources.set(downloadIdeaSources.toBoolean())
-  instrumentCode.set(instrumentPluginCode.toBoolean())
-  intellijRepository.set("https://www.jetbrains.com/intellij-repository")
-  plugins.set(listOf("AceJump:3.8.11"))
-}
-
-tasks {
-  publishPlugin {
+  publishing {
     channels.set(publishChannels.split(","))
     token.set(publishToken)
   }
 
-  signPlugin {
+  signing {
     certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN"))
     privateKey.set(providers.environmentVariable("PRIVATE_KEY"))
     password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
   }
 
-  runPluginVerifier {
-    downloadDir.set("${project.buildDir}/pluginVerifier/ides")
-    teamCityOutputFormat.set(true)
+  verifyPlugin {
+    teamCityOutputFormat = true
+    ides {
+      recommended()
+    }
   }
 
-  patchPluginXml {
-    // Don't forget to update plugin.xml
-    sinceBuild.set("241.15989.150")
-
-    changeNotes.set(
-      """<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
-    )
-  }
+  instrumentCode.set(instrumentPluginCode.toBoolean())
 }
 
 ksp {
@@ -884,12 +905,12 @@ fun changes(): List<Change> {
   println("Start changes processing")
   for (message in messages) {
     println("Processing '$message'...")
-    val lowercaseMessage = message.toLowerCase()
+    val lowercaseMessage = message.lowercase()
     val regex = "^fix\\((vim-\\d+)\\):".toRegex()
     val findResult = regex.find(lowercaseMessage)
     if (findResult != null) {
       println("Message matches")
-      val value = findResult.groups[1]!!.value.toUpperCase()
+      val value = findResult.groups[1]!!.value.uppercase()
       val shortMessage = message.drop(findResult.range.last + 1).trim()
       newFixes += Change(value, shortMessage)
     } else {
diff --git a/gradle.properties b/gradle.properties
index 9b769d771..1b6f8e933 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,11 +8,20 @@
 
 # suppress inspection "UnusedProperty" for whole file
 
+# ideaVersion is the version of the IDE that we'll add as a dependency. The format of the version string depends on the
+# value of the `org.jetbrains.intellij.platform.buildFeature.useBinaryReleases` property/build feature.
+# If enabled (default) then the IDE will be a normal, packaged release, which you could install and run like a retail
+# version of the IDE, downloaded from CDN. The `ideaVersion` property should be a marketing version such as `2024.1` or
+# `2024.1.1` (note no trailing `0`). You can find an example list of all versions for IDEA Community here:
+# https://data.services.jetbrains.com/products?code=IC
+# If the build feature is disabled, the IDE will be downloaded from Maven, and should match the format published in
+# Maven, such as `2024.1` (again, no trailing `.0`), `2024.1.1`, `241-EAP-SNAPSHOT`, etc.
+# You can see a list of release versions here: https://www.jetbrains.com/intellij-repository/releases
+# And a list of snapshot versions here: https://www.jetbrains.com/intellij-repository/snapshots
 #ideaVersion=LATEST-EAP-SNAPSHOT
 ideaVersion=2024.1.1
 # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
 ideaType=IC
-downloadIdeaSources=true
 instrumentPluginCode=true
 version=SNAPSHOT
 javaVersion=17
@@ -40,4 +49,12 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
 kotlin.stdlib.default.dependency=false
 
 # Disable incremental annotation processing
-ksp.incremental=false
\ No newline at end of file
+ksp.incremental=false
+
+# Build features
+# Temporarily disable downloading the IDE dependency from CDN, and use Maven The Plugin DevKit plugin is currently
+# unable to download sources when the IDE is packaged as a normal binary release. This only affects developers working
+# with and debugging IdeaVim. Once the fixed version of DevKit is available, IdeaVim developers can update and this flag
+# can be removed.
+# DevKit will be fixed in IDEA 2024.1.4 and a future EAP of 2024.2
+org.jetbrains.intellij.platform.buildFeature.useBinaryReleases=false
diff --git a/tests/java-tests/build.gradle.kts b/tests/java-tests/build.gradle.kts
index 0e8f29b89..47287a832 100644
--- a/tests/java-tests/build.gradle.kts
+++ b/tests/java-tests/build.gradle.kts
@@ -1,3 +1,5 @@
+import org.jetbrains.intellij.platform.gradle.TestFrameworkType
+
 /*
  * Copyright 2003-2024 The IdeaVim authors
  *
@@ -9,16 +11,20 @@
 plugins {
   id("java")
   kotlin("jvm")
-  id("org.jetbrains.intellij")
+  id("org.jetbrains.intellij.platform.module")
 }
 
 val kotlinVersion: String by project
+val ideaType: String by project
 val ideaVersion: String by project
 val javaVersion: String by project
 
 repositories {
   mavenCentral()
-  maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
+
+  intellijPlatform {
+    defaultRepositories()
+  }
 }
 
 dependencies {
@@ -26,35 +32,24 @@ dependencies {
   compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
   testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
   testImplementation(testFixtures(project(":"))) // The root project
+
+  intellijPlatform {
+    create(ideaType, ideaVersion)
+    testFramework(TestFrameworkType.Platform)
+    testFramework(TestFrameworkType.JUnit5)
+    bundledPlugins("com.intellij.java", "org.jetbrains.plugins.yaml")
+    instrumentationTools()
+  }
+}
+
+intellijPlatform {
+  buildSearchableOptions = false
 }
 
 tasks {
   test {
     useJUnitPlatform()
   }
-
-  verifyPlugin {
-    enabled = false
-  }
-
-  publishPlugin {
-    enabled = false
-  }
-
-  runIde {
-    enabled = false
-  }
-
-  runPluginVerifier {
-    enabled = false
-  }
-}
-
-intellij {
-  version.set(ideaVersion)
-  type.set("IC")
-  // Yaml is only used for testing. It's part of the IdeaIC distribution, but needs to be included as a reference
-  plugins.set(listOf("java", "yaml"))
 }
 
 java {
diff --git a/tests/long-running-tests/build.gradle.kts b/tests/long-running-tests/build.gradle.kts
index a8d726b33..6b5962fea 100644
--- a/tests/long-running-tests/build.gradle.kts
+++ b/tests/long-running-tests/build.gradle.kts
@@ -1,15 +1,23 @@
+import org.jetbrains.intellij.platform.gradle.TestFrameworkType
+import org.jetbrains.intellij.platform.gradle.extensions.intellijPlatform
+import org.jetbrains.intellij.platform.gradle.tasks.CustomTestIdeTask
+
 plugins {
   java
   kotlin("jvm")
-  id("org.jetbrains.intellij")
+  id("org.jetbrains.intellij.platform.module")
 }
 
 repositories {
   mavenCentral()
-  maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
+
+  intellijPlatform {
+    defaultRepositories()
+  }
 }
 
 val kotlinVersion: String by project
+val ideaType: String by project
 val ideaVersion: String by project
 val javaVersion: String by project
 
@@ -18,42 +26,32 @@ dependencies {
   compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
   testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
   testImplementation(testFixtures(project(":"))) // The root project
+
+  intellijPlatform {
+    create(ideaType, ideaVersion)
+    testFramework(TestFrameworkType.Platform)
+    testFramework(TestFrameworkType.JUnit5)
+    instrumentationTools()
+  }
+}
+
+intellijPlatform {
+  buildSearchableOptions = false
 }
 
 tasks {
   // This task is disabled because it should be excluded from `gradle test` run (because it's slow)
   // I didn't find a better way to exclude except disabling and defining a new task with a different name
+  // Note that useJUnitTestPlatform() is required to prevent red code
   test {
     enabled = false
     useJUnitPlatform()
   }
 
-  register<Test>("testLongRunning") {
+  register<CustomTestIdeTask>("testLongRunning") {
     group = "verification"
     useJUnitPlatform()
   }
-
-  verifyPlugin {
-    enabled = false
-  }
-
-  publishPlugin {
-    enabled = false
-  }
-
-  runIde {
-    enabled = false
-  }
-
-  runPluginVerifier {
-    enabled = false
-  }
-}
-
-intellij {
-  version.set(ideaVersion)
-  type.set("IC")
-  plugins.set(listOf("java"))
 }
 
 java {
diff --git a/tests/property-tests/build.gradle.kts b/tests/property-tests/build.gradle.kts
index 7fcbd7767..cd4bf458b 100644
--- a/tests/property-tests/build.gradle.kts
+++ b/tests/property-tests/build.gradle.kts
@@ -1,15 +1,23 @@
+import org.jetbrains.intellij.platform.gradle.TestFrameworkType
+import org.jetbrains.intellij.platform.gradle.extensions.intellijPlatform
+import org.jetbrains.intellij.platform.gradle.tasks.CustomTestIdeTask
+
 plugins {
   java
   kotlin("jvm")
-  id("org.jetbrains.intellij")
+  id("org.jetbrains.intellij.platform.module")
 }
 
 repositories {
   mavenCentral()
-  maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
+
+  intellijPlatform {
+    defaultRepositories()
+  }
 }
 
 val kotlinVersion: String by project
+val ideaType: String by project
 val ideaVersion: String by project
 val javaVersion: String by project
 
@@ -18,6 +26,18 @@ dependencies {
   compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
   testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
   testImplementation(testFixtures(project(":"))) // The root project
+
+  intellijPlatform {
+    create(ideaType, ideaVersion)
+    bundledPlugins("com.intellij.java")
+    testFramework(TestFrameworkType.Platform)
+    testFramework(TestFrameworkType.JUnit5)
+    instrumentationTools()
+  }
+}
+
+intellijPlatform {
+  buildSearchableOptions = false
 }
 
 tasks {
@@ -28,32 +48,10 @@ tasks {
     enabled = false
   }
 
-  register<Test>("testPropertyBased") {
+  register<CustomTestIdeTask>("testPropertyBased") {
     group = "verification"
     useJUnitPlatform()
   }
-
-  verifyPlugin {
-    enabled = false
-  }
-
-  publishPlugin {
-    enabled = false
-  }
-
-  runIde {
-    enabled = false
-  }
-
-  runPluginVerifier {
-    enabled = false
-  }
-}
-
-intellij {
-  version.set(ideaVersion)
-  type.set("IC")
-  plugins.set(listOf("java"))
 }
 
 java {
diff --git a/vim-engine/build.gradle.kts b/vim-engine/build.gradle.kts
index 9af976fa1..f5284e33f 100644
--- a/vim-engine/build.gradle.kts
+++ b/vim-engine/build.gradle.kts
@@ -16,6 +16,12 @@ plugins {
     antlr
 }
 
+val sourcesJarArtifacts by configurations.registering {
+  attributes {
+    attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
+  }
+}
+
 val kotlinVersion: String by project
 val kotlinxSerializationVersion: String by project
 
@@ -99,6 +105,8 @@ java {
   withJavadocJar()
 }
 
+artifacts.add(sourcesJarArtifacts.name, tasks.named("sourcesJar"))
+
 val spaceUsername: String by project
 val spacePassword: String by project
 val engineVersion: String by project
@@ -122,4 +130,4 @@ publishing {
       }
     }
   }
-}
\ No newline at end of file
+}