1515 */
1616import com.github.gradle.node.npm.task.NpmInstallTask
1717import com.github.gradle.node.task.NodeTask
18+ import org.apache.tools.ant.filters.ReplaceTokens
1819import org.gradle.internal.extensions.stdlib.capitalized
1920import org.gradle.internal.os.OperatingSystem
2021import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
2122
2223plugins {
2324 application
25+ idea
2426 alias(libs.plugins.kotlin)
2527 alias(libs.plugins.kotlinSerialization)
2628 alias(libs.plugins.nodeGradle)
@@ -37,6 +39,15 @@ java {
3739
3840val pklCli: Configuration by configurations.creating
3941
42+ val jtreeSitterSources: Configuration by configurations.creating
43+
44+ val buildInfo = extensions.create<BuildInfo >(" buildInfo" , project)
45+
46+ val jsitterMonkeyPatchSourceDir = layout.buildDirectory.dir(" generated/libs/jtreesitter" )
47+ val nativeLibDir = layout.buildDirectory.dir(" generated/libs/native/" )
48+ val treeSitterPklRepoDir = layout.buildDirectory.dir(" repos/tree-sitter-pkl" )
49+ val treeSitterRepoDir = layout.buildDirectory.dir(" repos/tree-sitter" )
50+
4051val osName
4152 get(): String {
4253 val os = OperatingSystem .current()
@@ -70,9 +81,41 @@ dependencies {
7081 testRuntimeOnly(" org.junit.platform:junit-platform-launcher" )
7182 testImplementation(libs.assertJ)
7283 testImplementation(libs.junit.jupiter)
84+ jtreeSitterSources(variantOf(libs.jtreesitter) { classifier(" sources" ) })
7385 pklCli(" org.pkl-lang:pkl-cli-$osName -$arch :${libs.versions.pkl.get()} " )
7486}
7587
88+ idea { module { generatedSourceDirs.add(jsitterMonkeyPatchSourceDir.get().asFile) } }
89+
90+ /* *
91+ * jtreesitter expects the tree-sitter library to exist in system dirs, or to be provided through
92+ * `java.library.path`.
93+ *
94+ * This patches its source code so that we can control exactly where the tree-sitter library
95+ * resides.
96+ */
97+ val monkeyPatchTreeSitter by
98+ tasks.registering(Copy ::class ) {
99+ from(zipTree(jtreeSitterSources.singleFile)) {
100+ include(" **/TreeSitter.java" )
101+ filter { line ->
102+ when {
103+ line.contains(" static final SymbolLookup" ) ->
104+ " static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(NativeLibraries.getTreeSitter().getLibraryPath(), LIBRARY_ARENA)"
105+ line.contains(" package io.github.treesitter.jtreesitter.internal;" ) ->
106+ """
107+ $line
108+
109+ import org.pkl.lsp.treesitter.NativeLibraries;
110+ """
111+ .trimIndent()
112+ else -> line
113+ }
114+ }
115+ }
116+ into(jsitterMonkeyPatchSourceDir)
117+ }
118+
76119val configurePklCliExecutable by
77120 tasks.registering { doLast { pklCli.singleFile.setExecutable(true ) } }
78121
@@ -106,17 +149,12 @@ val javaExecutable by
106149 // jvmArgs.addAll("-ea", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005")
107150 }
108151
109- val treeSitterPklRepo = layout.buildDirectory.dir(" repos/tree-sitter-pkl" )
110- val treeSitterRepo = layout.buildDirectory.dir(" repos/tree-sitter" )
111-
112152node {
113153 version = libs.versions.node
114- nodeProjectDir = treeSitterPklRepo
154+ nodeProjectDir = treeSitterPklRepoDir
115155 download = true
116156}
117157
118- private val nativeLibDir = layout.buildDirectory.dir(" native-lib" )
119-
120158fun configureRepo (
121159 repo : String ,
122160 simpleRepoName : String ,
@@ -136,9 +174,6 @@ fun configureRepo(
136174 val updateTask =
137175 tasks.register(" update$taskSuffix " ) {
138176 outputs.dir(repoDir)
139- outputs.upToDateWhen {
140- versionFile.get().asFile.let { it.exists() && it.readText() == gitTagOrCommit.get() }
141- }
142177 doLast {
143178 exec {
144179 workingDir = repoDir.get().asFile
@@ -155,6 +190,9 @@ fun configureRepo(
155190 dependsOn(cloneTask)
156191 dependsOn(updateTask)
157192 outputs.dir(repoDir)
193+ outputs.upToDateWhen {
194+ versionFile.get().asFile.let { it.exists() && it.readText() == gitTagOrCommit.get() }
195+ }
158196 doLast {
159197 versionFile.get().asFile.let { file ->
160198 file.ensureParentDirsCreated()
@@ -169,27 +207,31 @@ val setupTreeSitterRepo =
169207 " [email protected] :tree-sitter/tree-sitter" ,
170208 " treeSitter" ,
171209 libs.versions.treeSitterRepo,
172- treeSitterRepo ,
210+ treeSitterRepoDir ,
173211 )
174212
175213val setupTreeSitterPklRepo =
176214 configureRepo(
177215 " [email protected] :apple/tree-sitter-pkl" ,
178216 " treeSitterPkl" ,
179217 libs.versions.treeSitterPklRepo,
180- treeSitterPklRepo ,
218+ treeSitterPklRepoDir ,
181219 )
182220
221+ // Keep in sync with `org.pkl.lsp.treesitter.NativeLibrary.getResourcePath`
222+ private fun resourceLibraryPath (libraryName : String ) =
223+ " NATIVE/org/pkl/lsp/treesitter/$osName -$arch /$libraryName "
224+
183225val makeTreeSitterLib by
184226 tasks.registering(Exec ::class ) {
185227 dependsOn(setupTreeSitterRepo)
186- workingDir = treeSitterRepo .get().asFile
228+ workingDir = treeSitterRepoDir .get().asFile
187229 inputs.dir(workingDir)
188230
189231 val libraryName = System .mapLibraryName(" tree-sitter" )
190232 commandLine(" make" , libraryName)
191233
192- val outputFile = nativeLibDir.map { it.file(libraryName) }
234+ val outputFile = nativeLibDir.map { it.file(resourceLibraryPath( libraryName) ) }
193235 outputs.file(outputFile)
194236
195237 doLast { workingDir.resolve(libraryName).renameTo(outputFile.get().asFile) }
@@ -198,20 +240,20 @@ val makeTreeSitterLib by
198240val npmInstallTreeSitter by
199241 tasks.registering(NpmInstallTask ::class ) {
200242 dependsOn(setupTreeSitterPklRepo)
201- doFirst { workingDir = treeSitterPklRepo .get().asFile }
243+ doFirst { workingDir = treeSitterPklRepoDir .get().asFile }
202244 }
203245
204246val makeTreeSitterPklLib by
205247 tasks.registering(NodeTask ::class ) {
206248 dependsOn(npmInstallTreeSitter)
207- inputs.dir(treeSitterPklRepo )
208- doFirst { workingDir = treeSitterPklRepo .get().asFile }
249+ inputs.dir(treeSitterPklRepoDir )
250+ doFirst { workingDir = treeSitterPklRepoDir .get().asFile }
209251
210252 val libraryName = System .mapLibraryName(" tree-sitter-pkl" )
211253
212- val outputFile = nativeLibDir.map { it.file(libraryName) }
254+ val outputFile = nativeLibDir.map { it.file(resourceLibraryPath( libraryName) ) }
213255
214- script.set(treeSitterPklRepo .get().asFile.resolve(" node_modules/.bin/tree-sitter" ))
256+ script.set(treeSitterPklRepoDir .get().asFile.resolve(" node_modules/.bin/tree-sitter" ))
215257 args = listOf (" build" , " --output" , outputFile.get().asFile.absolutePath)
216258
217259 outputs.file(outputFile)
@@ -220,9 +262,30 @@ val makeTreeSitterPklLib by
220262tasks.processResources {
221263 dependsOn(makeTreeSitterLib)
222264 dependsOn(makeTreeSitterPklLib)
265+ // tree-sitter's CLI always generates debug symbols when on version 0.22.
266+ // we can remove this when tree-sitter-pkl upgrades the tree-sitter-cli dependency to 0.23 or
267+ // newer.
268+ exclude(" **/*.dSYM/**" )
269+ filesMatching(" org/pkl/lsp/Release.properties" ) {
270+ filter<ReplaceTokens >(
271+ " tokens" to
272+ mapOf (
273+ " version" to buildInfo.pklLspVersion,
274+ " treeSitterVersion" to libs.versions.treeSitterRepo.get(),
275+ " treeSitterPklVersion" to libs.versions.treeSitterPklRepo.get(),
276+ )
277+ )
278+ }
223279}
224280
225- sourceSets { main { resources { srcDirs(nativeLibDir) } } }
281+ tasks.compileKotlin { dependsOn(monkeyPatchTreeSitter) }
282+
283+ sourceSets {
284+ main {
285+ java { srcDirs(jsitterMonkeyPatchSourceDir) }
286+ resources { srcDirs(nativeLibDir) }
287+ }
288+ }
226289
227290private val licenseHeader =
228291 """
@@ -256,39 +319,3 @@ spotless {
256319 licenseHeader(licenseHeader)
257320 }
258321}
259-
260- /* *
261- * Builds a self-contained Pkl LSP CLI Jar that is directly executable on *nix and executable with
262- * `java -jar` on Windows.
263- *
264- * For direct execution, the `java` command must be on the PATH.
265- *
266- * https://skife.org/java/unix/2011/06/20/really_executable_jars.html
267- */
268- abstract class ExecutableJar : DefaultTask () {
269- @get:InputFile abstract val inJar: RegularFileProperty
270-
271- @get:OutputFile abstract val outJar: RegularFileProperty
272-
273- @get:Input abstract val jvmArgs: ListProperty <String >
274-
275- @TaskAction
276- fun buildJar () {
277- val inFile = inJar.get().asFile
278- val outFile = outJar.get().asFile
279- val escapedJvmArgs = jvmArgs.get().joinToString(separator = " " ) { " \" $it \" " }
280- val startScript =
281- """
282- #!/bin/sh
283- exec java $escapedJvmArgs -jar $0 "$@"
284- """
285- .trimIndent() + " \n\n\n "
286- outFile.outputStream().use { outStream ->
287- startScript.byteInputStream().use { it.copyTo(outStream) }
288- inFile.inputStream().use { it.copyTo(outStream) }
289- }
290-
291- // chmod a+x
292- outFile.setExecutable(true , false )
293- }
294- }
0 commit comments