From 3f5b42d6d3c48c68f52566a8bfdc27cc431c5b54 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Thu, 14 Dec 2023 12:47:00 +0100 Subject: [PATCH 1/7] build: align the antlr-kotlin-tests build script with Gradle setup instructions --- README.md | 54 ++++++++++++-------- antlr-kotlin-tests/build.gradle.kts | 9 +++- antlr-kotlin-tests/src/commonMain/.gitignore | 2 - 3 files changed, 39 insertions(+), 26 deletions(-) delete mode 100644 antlr-kotlin-tests/src/commonMain/.gitignore diff --git a/README.md b/README.md index ff397c8f..0eda6ce5 100644 --- a/README.md +++ b/README.md @@ -114,34 +114,44 @@ To start using ANTLR Kotlin: 6. Create the ANTLR Kotlin grammar generation task. ```kotlin - val generateKotlinGrammarSource = tasks.register("generateKotlinGrammarSource") { - dependsOn("cleanGenerateKotlinGrammarSource") - - // ANTLR .g4 files are under {example-project}/antlr - setSource(layout.projectDirectory.dir("src/main/antlr")) - - val pkgName = "com.strumenta.antlrkotlin.parsers.generated" - arguments = listOf( - "-Dlanguage=Kotlin", // We want to generate Kotlin sources - "-visitor", // We want visitors alongside listeners - "-package", pkgName, // We want the generated sources to have this package declared - "-encoding", "UTF-8", // We want the generated sources to be encoded in UTF-8 - ) - - // Generated files are outputted inside build/ - val outDir = "generatedAntlr/${pkgName.replace(".", "/")}" - outputDirectory = layout.buildDirectory.get().dir(outDir).asFile + val generateKotlinGrammarSource = tasks.register("generateKotlinGrammarSource") { + dependsOn("cleanGenerateKotlinGrammarSource") + + // ANTLR .g4 files are under {example-project}/antlr + setSource(layout.projectDirectory.dir("src/main/antlr")) + + val pkgName = "com.strumenta.antlrkotlin.parsers.generated" + arguments = listOf( + "-Dlanguage=Kotlin", // We want to generate Kotlin sources + "-visitor", // We want visitors alongside listeners + "-package", pkgName, // We want the generated sources to have this package declared + "-encoding", "UTF-8", // We want the generated sources to be encoded in UTF-8 + ) - sourceSets.getByName("main") { - java.srcDir(layout.buildDirectory.get().dir("generatedAntlr")) - } - } + // Generated files are outputted inside build/generatedAntlr + val outDir = "generatedAntlr/${pkgName.replace(".", "/")}" + outputDirectory = layout.buildDirectory.dir(outDir).get().asFile + } ``` Depending on `cleanGenerateKotlinGrammarSource` ensures the `.tokens` files are always fresh, and we do not end up with out-of-sync lexers and parsers. -7. Optionally instruct the Kotlin compilation tasks to depend on the grammar generation. +7. Register the `build/generatedAntlr` directory as part of the common source set. + + ```kotlin + kotlin { + sourceSets { + commonMain { + kotlin { + srcDir(layout.buildDirectory.dir("generatedAntlr")) + } + } + } + } + ``` + +8. Optionally instruct the Kotlin compilation tasks to depend on the grammar generation. ```kotlin withType> { diff --git a/antlr-kotlin-tests/build.gradle.kts b/antlr-kotlin-tests/build.gradle.kts index a09256b9..49cb9145 100644 --- a/antlr-kotlin-tests/build.gradle.kts +++ b/antlr-kotlin-tests/build.gradle.kts @@ -25,6 +25,10 @@ kotlin { sourceSets { commonMain { + kotlin { + srcDir(layout.buildDirectory.dir("generatedAntlr")) + } + dependencies { implementation(projects.antlrKotlinRuntime) } @@ -63,8 +67,9 @@ tasks { "-encoding", "UTF-8", // We want the generated sources to be encoded in UTF-8 ) - val outDir = "src/commonMain/kotlin/${pkgName.replace(".", "/")}" - outputDirectory = layout.projectDirectory.dir(outDir).asFile + // Generated files are outputted inside build/generatedAntlr + val outDir = "generatedAntlr/${pkgName.replace(".", "/")}" + outputDirectory = layout.buildDirectory.dir(outDir).get().asFile } withType> { diff --git a/antlr-kotlin-tests/src/commonMain/.gitignore b/antlr-kotlin-tests/src/commonMain/.gitignore deleted file mode 100644 index b52250d5..00000000 --- a/antlr-kotlin-tests/src/commonMain/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# This folder *must* contain only generated sources. -/kotlin/com/strumenta/antlrkotlin/tests/generated/ From d22828d5e2cbd79f82dedade2882a67b09647d68 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Thu, 14 Dec 2023 14:44:00 +0100 Subject: [PATCH 2/7] build: cleanup and restore ability to publish to a private repository --- antlr-kotlin-runtime/build.gradle.kts | 44 +++++---- antlr-kotlin-target/build.gradle.kts | 16 ++-- build.gradle.kts | 47 +++++++--- buildSrc/build.gradle.kts | 4 +- .../ext/MavenPublishBaseExtension.ext.kt | 41 +++++++++ .../gradle/ext/Project.ext.kt | 91 +++++-------------- gradle.properties | 10 +- gradle/libs.versions.toml | 5 + 8 files changed, 145 insertions(+), 113 deletions(-) create mode 100644 buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/ext/MavenPublishBaseExtension.ext.kt diff --git a/antlr-kotlin-runtime/build.gradle.kts b/antlr-kotlin-runtime/build.gradle.kts index d2088200..0bf1d8db 100644 --- a/antlr-kotlin-runtime/build.gradle.kts +++ b/antlr-kotlin-runtime/build.gradle.kts @@ -1,20 +1,20 @@ -import com.strumenta.kotlinmultiplatform.gradle.ext.* -import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode -import org.jetbrains.dokka.gradle.DokkaTask +@file:Suppress("UnstableApiUsage") + +import com.strumenta.kotlinmultiplatform.gradle.ext.setupPom +import com.strumenta.kotlinmultiplatform.gradle.ext.targetsNative import com.vanniktech.maven.publish.SonatypeHost +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode plugins { id("strumenta.multiplatform") id("org.jetbrains.dokka") - id("com.vanniktech.maven.publish") } strumentaMultiplatform { applyJvm() - - if (targetsJS()) { - applyJs() - } + applyJs() // Opting-in for native targets should be explicit, // as it makes the build and test process slower. @@ -51,24 +51,28 @@ kotlin { } } +mavenPublishing { + @Suppress("UnstableApiUsage") + coordinates( + groupId = project.group as String, + artifactId = project.name, + version = project.version as String, + ) + + setupPom(project, projectDescription = "Runtime for ANTLR Kotlin") + publishToMavenCentral(SonatypeHost.DEFAULT, true) + signAllPublications() +} + tasks.withType().configureEach { dokkaSourceSets { configureEach { - suppress.set(true) + suppress = true } val commonMain by getting { - suppress.set(false) - platform.set(org.jetbrains.dokka.Platform.jvm) + suppress = false + platform = Platform.jvm } } } - -mavenPublishing { - coordinates(project.group as String, project.name, project.version as String) - setupPom(project, "Runtime for ANTLR Kotlin") - - publishToMavenCentral(SonatypeHost.DEFAULT, true) - - signAllPublications() -} \ No newline at end of file diff --git a/antlr-kotlin-target/build.gradle.kts b/antlr-kotlin-target/build.gradle.kts index 0d35a272..64a2d987 100644 --- a/antlr-kotlin-target/build.gradle.kts +++ b/antlr-kotlin-target/build.gradle.kts @@ -1,13 +1,9 @@ -import com.strumenta.kotlinmultiplatform.gradle.ext.* -import org.jetbrains.dokka.gradle.DokkaTask -import java.net.URI -import java.net.URL +import com.strumenta.kotlinmultiplatform.gradle.ext.setupPom import com.vanniktech.maven.publish.SonatypeHost plugins { id("strumenta.jvm.library") id("org.jetbrains.dokka") - id("com.vanniktech.maven.publish") } dependencies { @@ -15,11 +11,15 @@ dependencies { } mavenPublishing { - coordinates(project.group as String, project.name, project.version as String) - setupPom(project, "Kotlin target for ANTLR") + @Suppress("UnstableApiUsage") + coordinates( + groupId = project.group as String, + artifactId = project.name, + version = project.version as String, + ) + setupPom(project, projectDescription = "Kotlin target for ANTLR") publishToMavenCentral(SonatypeHost.DEFAULT, true) - signAllPublications() } diff --git a/build.gradle.kts b/build.gradle.kts index a3944a34..05a08132 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,12 @@ +@file:Suppress("UnstableApiUsage") + +import com.strumenta.kotlinmultiplatform.gradle.ext.mavenRepositoryName +import com.strumenta.kotlinmultiplatform.gradle.ext.mavenRepositoryUrl +import org.gradle.api.tasks.testing.logging.TestExceptionFormat + plugins { - id("net.researchgate.release") version "3.0.2" + alias(libs.plugins.researchgate.release) + id("com.vanniktech.maven.publish") apply false } allprojects { @@ -9,18 +16,44 @@ allprojects { mavenCentral() } + apply(plugin = "com.vanniktech.maven.publish") + + // Allow publishing to a private Maven repository, other than to Maven Central + val privateRepoUrl = mavenRepositoryUrl() + + if (privateRepoUrl != null) { + extensions.configure("publishing") { + repositories { + maven { + name = mavenRepositoryName() ?: "PrivateNexus" + url = uri(privateRepoUrl) + isAllowInsecureProtocol = true + } + } + } + } } subprojects { - tasks.withType().all { testLogging { showStandardStreams = true showExceptions = true - exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + exceptionFormat = TestExceptionFormat.FULL } } +} +release { + buildTasks = listOf( + ":antlr-kotlin-runtime:publishAllPublicationsToMavenCentralRepository", + ":antlr-kotlin-target:publishAllPublicationsToMavenCentralRepository", + ) + + git { + requireBranch = "" + pushToRemote = "origin" + } } tasks { @@ -29,11 +62,3 @@ tasks { distributionType = Wrapper.DistributionType.ALL } } - -release { - buildTasks.set(listOf(":antlr-kotlin-runtime:publishAllPublicationsToMavenCentralRepository", ":antlr-kotlin-target:publishAllPublicationsToMavenCentralRepository")) - git { - requireBranch.set("") - pushToRemote.set("origin") - } -} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index c1ca4366..ad4f3de7 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -5,8 +5,8 @@ plugins { dependencies { // The Kotlin Gradle Plugin dependency propagates to the entire workspace implementation(libs.kotlin.plugin) - implementation("com.vanniktech:gradle-maven-publish-plugin:0.25.3") - implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10") + implementation(libs.maven.publish.plugin) + implementation(libs.dokka.plugin) } gradlePlugin { diff --git a/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/ext/MavenPublishBaseExtension.ext.kt b/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/ext/MavenPublishBaseExtension.ext.kt new file mode 100644 index 00000000..c5b81aeb --- /dev/null +++ b/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/ext/MavenPublishBaseExtension.ext.kt @@ -0,0 +1,41 @@ +package com.strumenta.kotlinmultiplatform.gradle.ext + +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import org.gradle.api.Project + +@Suppress("UnstableApiUsage") +fun MavenPublishBaseExtension.setupPom(project: Project, projectDescription: String) { + pom { + name.set(project.name) + description.set(projectDescription) + url.set("https://github.com/Strumenta/antlr-kotlin") + + scm { + connection.set("scm:git:https://github.com/Strumenta/antlr-kotlin.git") + developerConnection.set("scm:git:git@github.com:Strumenta/antlr-kotlin.git") + url.set("https://github.com/Strumenta/antlr-kotlin.git") + } + + licenses { + license { + name.set("Apache License V2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0") + distribution.set("repo") + } + } + + // The developers entry is strictly required by Maven Central + developers { + developer { + id.set("ftomassetti") + name.set("Federico Tomassetti") + email.set("federico@strumenta.com") + } + developer { + id.set("lppedd") + name.set("Edoardo Luppi") + email.set("lp.edoardo@gmail.com") + } + } + } +} diff --git a/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/ext/Project.ext.kt b/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/ext/Project.ext.kt index 1714bfe4..fa5d6a01 100644 --- a/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/ext/Project.ext.kt +++ b/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/ext/Project.ext.kt @@ -1,18 +1,10 @@ package com.strumenta.kotlinmultiplatform.gradle.ext import org.gradle.api.Project -import org.gradle.api.artifacts.dsl.RepositoryHandler import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.repositories -import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import java.net.URI -import com.vanniktech.maven.publish.MavenPublishBaseExtension /** * Returns the Java project extension. @@ -47,12 +39,29 @@ fun Project.booleanProperty(name: String): Boolean = /** * Returns whether we are building for a release, or not. */ -fun Project.releaseBuild(): Boolean = - !(stringProperty("version")?.endsWith("-SNAPSHOT") - ?: throw IllegalStateException("Project version not specified")) +fun Project.releaseBuild(): Boolean { + val isSnapshot = stringProperty("version")?.endsWith("-SNAPSHOT") + ?: throw IllegalStateException("Project version not specified") + + return !isSnapshot +} + +/** + * Returns the name of the private Maven repository to publish artifacts to, + * or `null` if none is configured for the given publishing policy (release/snapshot). + */ +fun Project.mavenRepositoryName(): String? { + val name = if (releaseBuild()) { + stringProperty("repo.release.name") + } else { + stringProperty("repo.snapshot.name") + } + + return name?.ifBlank { null } +} /** - * Returns the URL of the Maven repository to publish artifacts to, + * Returns the URL of the private Maven repository to publish artifacts to, * or `null` if none is registered for the given publishing policy (release/snapshot). */ fun Project.mavenRepositoryUrl(): String? { @@ -70,61 +79,3 @@ fun Project.mavenRepositoryUrl(): String? { */ fun Project.targetsNative(): Boolean = booleanProperty("target.is.native") - -fun Project.targetsJS(): Boolean = - booleanProperty("target.is.js") - - -fun PublishingExtension.addSonatypeRepository(project: Project) { - repositories { - maven { - name = "oss" - url = URI(project.mavenRepositoryUrl()) - credentials { - username = project.findProperty("ossrhUsername") as? String ?: "Unknown user" - password = project.findProperty("ossrhPassword") as? String ?: "Unknown password" - } - } - } -} - -val Project.publicationName - get() = project.name.replace("-", "_") - -fun MavenPublishBaseExtension.setupPom(project: Project, descriptionValue: String) { - pom { - name.set(project.name) - description.set(descriptionValue) -// version = project.version as String -// packaging = "jar" - url.set("https://github.com/Strumenta/antlr-kotlin") - - scm { - connection.set("scm:git:https://github.com/Strumenta/antlr-kotlin.git") - developerConnection.set("scm:git:git@github.com:Strumenta/antlr-kotlin.git") - url.set("https://github.com/Strumenta/antlr-kotlin.git") - } - - licenses { - license { - name.set("Apache Licenve V2.0") - url.set("https://www.apache.org/licenses/LICENSE-2.0") - distribution.set("repo") - } - } - - // The developers entry is strictly required by Maven Central - developers { - developer { - id.set("ftomassetti") - name.set("Federico Tomassetti") - email.set("federico@strumenta.com") - } - developer { - id.set("lppedd") - name.set("Edoardo Luppi") - email.set("lp.edoardo@gmail.com") - } - } - } -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 20c3b07e..150851e7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,6 +18,12 @@ kotlin.incremental.js = true ######################### # Project settings ######################### -target.is.native = true -target.is.js = true version = 0.1.0-RC7-SNAPSHOT +target.is.native = true + +# Publish to a private repository +repo.snapshot.name = +repo.snapshot.url = + +repo.release.name = +repo.release.url = diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 575cf927..af8f1bcd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,8 @@ kotlin = "1.9.21" kotlin-wrappers = "1.0.0-pre.659" antlr4 = "4.13.1" dokka = "1.9.10" +researchgate-release = "3.0.2" +maven-publish = "0.25.3" [libraries] kotlin-wrappers-bom = { group = "org.jetbrains.kotlin-wrappers", name = "kotlin-wrappers-bom", version.ref = "kotlin-wrappers" } @@ -11,8 +13,11 @@ antlr4 = { group = "org.antlr", name = "antlr4", version.ref = "antlr4" } # Gradle kotlin-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +dokka-plugin = { group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version.ref = "dokka" } +maven-publish-plugin = { group = "com.vanniktech", name = "gradle-maven-publish-plugin", version.ref = "maven-publish" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +researchgate-release = { id = "net.researchgate.release", version.ref = "researchgate-release" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } From 9b5b62b7f3de4c777282a1b395fbe1485a699a5c Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Thu, 14 Dec 2023 18:59:39 +0100 Subject: [PATCH 3/7] refactor: resurrect the custom Gradle plugin --- antlr-kotlin-gradle-plugin/build.gradle.kts | 24 +++ .../antlrkotlin/gradle/AntlrKotlinPlugin.kt | 10 + .../antlrkotlin/gradle/AntlrKotlinTask.kt | 195 ++++++++++++++++++ .../antlrkotlin/gradle/internal/Antlr4Tool.kt | 35 ++++ .../gradle/internal/AntlrExecutor.kt | 23 +++ .../gradle/internal/AntlrResult.kt | 13 ++ .../AntlrSourceGenerationException.kt | 12 ++ .../antlrkotlin/gradle/internal/AntlrSpec.kt | 31 +++ .../gradle/internal/AntlrSpecFactory.kt | 57 +++++ .../antlrkotlin/gradle/internal/AntlrTool.kt | 91 ++++++++ .../gradle/internal/AntlrWorkerManager.kt | 42 ++++ buildSrc/build.gradle.kts | 6 + .../StrumentaGradlePluginModulePlugin.kt | 41 ++++ gradle/libs.versions.toml | 2 + settings.gradle.kts | 1 + 15 files changed, 583 insertions(+) create mode 100644 antlr-kotlin-gradle-plugin/build.gradle.kts create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/AntlrKotlinPlugin.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/AntlrKotlinTask.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/Antlr4Tool.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrExecutor.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrResult.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSourceGenerationException.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSpec.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSpecFactory.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrTool.kt create mode 100644 antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrWorkerManager.kt create mode 100644 buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/plugins/StrumentaGradlePluginModulePlugin.kt diff --git a/antlr-kotlin-gradle-plugin/build.gradle.kts b/antlr-kotlin-gradle-plugin/build.gradle.kts new file mode 100644 index 00000000..9eb52451 --- /dev/null +++ b/antlr-kotlin-gradle-plugin/build.gradle.kts @@ -0,0 +1,24 @@ +@file:Suppress("UnstableApiUsage") + +plugins { + id("strumenta.gradle.plugin") +} + +dependencies { + implementation(libs.antlr4) + implementation(projects.antlrKotlinTarget) +} + +gradlePlugin { + website = "https://github.com/Strumenta/antlr-kotlin" + vcsUrl = "https://github.com/Strumenta/antlr-kotlin" + + plugins { + create("StrumentaAntlrKotlin") { + id = "com.strumenta.antlr-kotlin" + description = "The ANTLR Gradle plugin for the Kotlin target" + tags = listOf("antlr", "antlr4", "kotlin", "multiplatform") + implementationClass = "com.strumenta.antlrkotlin.gradle.AntlrKotlinPlugin" + } + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/AntlrKotlinPlugin.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/AntlrKotlinPlugin.kt new file mode 100644 index 00000000..43d11362 --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/AntlrKotlinPlugin.kt @@ -0,0 +1,10 @@ +package com.strumenta.antlrkotlin.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project + +public class AntlrKotlinPlugin : Plugin { + public override fun apply(target: Project) { + // Noop + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/AntlrKotlinTask.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/AntlrKotlinTask.kt new file mode 100644 index 00000000..e6f62930 --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/AntlrKotlinTask.kt @@ -0,0 +1,195 @@ +package com.strumenta.antlrkotlin.gradle + +import com.strumenta.antlrkotlin.gradle.internal.AntlrResult +import com.strumenta.antlrkotlin.gradle.internal.AntlrSourceGenerationException +import com.strumenta.antlrkotlin.gradle.internal.AntlrSpecFactory +import com.strumenta.antlrkotlin.gradle.internal.AntlrWorkerManager +import org.gradle.api.NonNullApi +import org.gradle.api.file.* +import org.gradle.api.tasks.* +import org.gradle.api.tasks.Optional +import org.gradle.internal.file.Deleter +import org.gradle.process.internal.worker.WorkerProcessFactory +import org.gradle.work.ChangeType +import org.gradle.work.InputChanges +import java.io.File +import java.util.* +import javax.inject.Inject + +/** + * Generates parsers from ANTLR grammars. + */ +@NonNullApi +@CacheableTask +public abstract class AntlrKotlinTask @Inject constructor( + private val deleter: Deleter, + private val projectLayout: ProjectLayout, + private val workerProcessBuilderFactory: WorkerProcessFactory, +) : SourceTask() { + private var sourceSetDirectories: FileCollection? = null + + /** + * Specifies that all rules call `traceIn`/`traceOut`. + */ + @get:Input + public var isTrace: Boolean = false + + /** + * Specifies that all lexer rules call `traceIn`/`traceOut`. + */ + @get:Input + public var isTraceLexer: Boolean = false + + /** + * Specifies that all parser rules call `traceIn`/`traceOut`. + */ + @get:Input + public var isTraceParser: Boolean = false + + /** + * Specifies that all tree walker rules call `traceIn`/`traceOut`. + */ + @get:Input + public var isTraceTreeWalker: Boolean = false + + /** + * List of command-line arguments passed to the antlr process. + */ + @get:Input + public var arguments: List = LinkedList() + + /** + * Specifies the classpath containing the Ant ANTLR task implementation. + */ + @get:Classpath + @get:Optional + public var antlrClasspath: FileCollection? = null + + /** + * Specifies the directory to generate the parser source files into. + */ + @get:OutputDirectory + public var outputDirectory: File? = null + + /** + * The package name of the generated files. + */ + @get:Input + public var packageName: String? = null + + /** + * The generated parsers file encoding (ex: 'UTF-8'). + */ + @get:Input + public var encoding: String = "UTF-8" + + /** + * The maximum heap size for the forked antlr process (ex: '512m', '1g'). + */ + @get:Internal + public var maxHeapSize: String = "512m" + + /** + * The sources for incremental change detection. + */ + @get:SkipWhenEmpty + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:InputFiles + protected val stableSources: FileCollection = project.files({ this.source }) + + /** + * Generate the parsers. + */ + @TaskAction + public fun execute(inputChanges: InputChanges) { + val grammarFiles = mutableSetOf() + val stableSources = stableSources + + if (inputChanges.isIncremental) { + var rebuildRequired = false + + for (fileChange in inputChanges.getFileChanges(stableSources)) { + if (fileChange.fileType == FileType.FILE) { + if (fileChange.changeType == ChangeType.REMOVED) { + rebuildRequired = true + break + } + + grammarFiles.add(fileChange.file) + } + } + + if (rebuildRequired) { + deleter.ensureEmptyDirectory(outputDirectory!!) + grammarFiles.addAll(stableSources.files) + } + } else { + grammarFiles.addAll(stableSources.files) + } + + val projectDir = projectLayout.projectDirectory.asFile + val manager = AntlrWorkerManager() + val spec = AntlrSpecFactory().create(this, grammarFiles, sourceSetDirectories) + val result = manager.runWorker(projectDir, workerProcessBuilderFactory, antlrClasspath, spec) + evaluate(result) + } + + /** + * Sets the source for this task. Delegates to [SourceTask.setSource]. + * + * If the source is of type [SourceDirectorySet], then the relative path of each + * source grammar files is used to determine the relative output path of the generated source. + * + * If the source is not of type [SourceDirectorySet], then the generated source files end up + * flattened in the specified output directory. + */ + override fun setSource(source: FileTree) { + setSource(source as Any) + } + + /** + * Sets the source for this task. Delegates to [SourceTask.setSource]. + * + * If the source is of type [SourceDirectorySet], then the relative path of each + * source grammar files is used to determine the relative output path of the generated source. + * + * If the source is not of type [SourceDirectorySet], then the generated source files end up + * flattened in the specified output directory. + */ + override fun setSource(source: Any) { + super.setSource(source) + + if (source is SourceDirectorySet) { + sourceSetDirectories = source.sourceDirectories + } + } + + /** + * Returns the source for this task, after the include and exclude patterns have been applied. + * + * Ignores source files which do not exist. + */ + @Internal("Tracked via stableSources") + override fun getSource(): FileTree = + super.getSource() + + private fun evaluate(result: AntlrResult) { + val errorCount = result.errorCount + + when { + errorCount < 0 -> throw AntlrSourceGenerationException( + "There were errors during grammar generation", + result.exception, + ) + errorCount == 1 -> throw AntlrSourceGenerationException( + "There was 1 error during grammar generation", + result.exception, + ) + errorCount > 1 -> throw AntlrSourceGenerationException( + "There were $errorCount errors during grammar generation", + result.exception, + ) + } + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/Antlr4Tool.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/Antlr4Tool.kt new file mode 100644 index 00000000..c82d54a8 --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/Antlr4Tool.kt @@ -0,0 +1,35 @@ +package com.strumenta.antlrkotlin.gradle.internal + +import org.gradle.internal.reflect.JavaMethod +import java.io.File + +internal class Antlr4Tool : AntlrTool() { + override fun invoke(arguments: List, inputDirectory: File?): Int { + val backedObject = loadTool("org.antlr.v4.Tool", arguments.toTypedArray()) + + if (inputDirectory != null) { + setField(backedObject, "inputDirectory", inputDirectory) + } + + JavaMethod.of(backedObject, Void::class.javaObjectType, "processGrammarsOnCommandLine").invoke(backedObject) + return JavaMethod.of(backedObject, Int::class.javaObjectType, "getNumErrors").invoke(backedObject) + } + + override fun available(): Boolean { + try { + loadTool("org.antlr.v4.Tool", null) + return true + } catch (_: ClassNotFoundException) { + // Ignored + } + + return false + } + + @Suppress("SameParameterValue") + private fun setField(obj: Any, fieldName: String, value: File) { + val field = obj.javaClass.getField(fieldName) + field.isAccessible = true + field[obj] = value + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrExecutor.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrExecutor.kt new file mode 100644 index 00000000..7fb7b3e8 --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrExecutor.kt @@ -0,0 +1,23 @@ +package com.strumenta.antlrkotlin.gradle.internal + +import org.gradle.process.internal.worker.RequestHandler +import org.slf4j.LoggerFactory + +internal class AntlrExecutor : RequestHandler { + private companion object { + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + private val logger = LoggerFactory.getLogger(AntlrExecutor::class.java) + } + + override fun run(spec: AntlrSpec): AntlrResult { + val antlrTool = Antlr4Tool() + + if (antlrTool.available()) { + logger.info("Processing with ANTLR 4") + return antlrTool.process(spec) + } + + throw IllegalStateException("No ANTLR 4 implementation available") + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrResult.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrResult.kt new file mode 100644 index 00000000..c77aa8eb --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrResult.kt @@ -0,0 +1,13 @@ +package com.strumenta.antlrkotlin.gradle.internal + +import java.io.Serializable + +internal data class AntlrResult( + val errorCount: Int, + val exception: Exception? = null, +) : Serializable { + private companion object { + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSourceGenerationException.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSourceGenerationException.kt new file mode 100644 index 00000000..0217b638 --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSourceGenerationException.kt @@ -0,0 +1,12 @@ +package com.strumenta.antlrkotlin.gradle.internal + +import org.gradle.api.GradleException +import org.gradle.internal.exceptions.Contextual + +@Contextual +internal class AntlrSourceGenerationException(message: String, cause: Throwable?) : GradleException(message, cause) { + private companion object { + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSpec.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSpec.kt new file mode 100644 index 00000000..100a6c4e --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSpec.kt @@ -0,0 +1,31 @@ +package com.strumenta.antlrkotlin.gradle.internal + +import java.io.File +import java.io.Serializable +import java.util.* + +internal data class AntlrSpec( + val arguments: List, + val grammarFiles: Set, + val inputDirectories: Set, + val outputDirectory: File, + val maxHeapSize: String?, +) : Serializable { + private companion object { + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + } + + fun asArgumentsWithFiles(): List { + val commandLine = LinkedList(arguments) + commandLine.add("-Dlanguage=Kotlin") + commandLine.add("-o") + commandLine.add(outputDirectory.absolutePath) + + for (file in grammarFiles) { + commandLine.add(file.absolutePath) + } + + return commandLine + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSpecFactory.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSpecFactory.kt new file mode 100644 index 00000000..62e55932 --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrSpecFactory.kt @@ -0,0 +1,57 @@ +package com.strumenta.antlrkotlin.gradle.internal + +import com.strumenta.antlrkotlin.gradle.AntlrKotlinTask +import org.gradle.api.file.FileCollection +import java.io.File +import java.util.* + +internal class AntlrSpecFactory { + fun create( + antlrTask: AntlrKotlinTask, + grammarFiles: Set, + sourceSetDirectories: FileCollection?, + ): AntlrSpec { + val arguments = LinkedList(antlrTask.arguments) + val packageName = antlrTask.packageName + + if (packageName != null) { + arguments.add("-package") + arguments.add(packageName) + } + + if (antlrTask.isTrace) { + arguments.add("-trace") + } + + if (antlrTask.isTraceLexer) { + arguments.add("-traceLexer") + } + + if (antlrTask.isTraceParser) { + arguments.add("-traceParser") + } + + if (antlrTask.isTraceTreeWalker) { + arguments.add("-traceTreeWalker") + } + + // See https://github.com/Strumenta/antlr-kotlin/issues/85 + arguments.add("-encoding") + arguments.add(antlrTask.encoding) + + val sourceSetDirectoriesFiles = + if (sourceSetDirectories == null) { + emptySet() + } else { + sourceSetDirectories.files + } + + return AntlrSpec( + arguments, + grammarFiles, + sourceSetDirectoriesFiles, + antlrTask.outputDirectory!!, + antlrTask.maxHeapSize, + ) + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrTool.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrTool.kt new file mode 100644 index 00000000..52c19ea9 --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrTool.kt @@ -0,0 +1,91 @@ +package com.strumenta.antlrkotlin.gradle.internal + +import org.apache.tools.ant.util.FileUtils +import org.gradle.api.GradleException +import org.gradle.internal.os.OperatingSystem +import org.gradle.internal.reflect.JavaReflectionUtil +import org.slf4j.LoggerFactory +import java.io.File +import java.lang.reflect.InvocationTargetException + +internal abstract class AntlrTool { + companion object { + private val logger = LoggerFactory.getLogger(AntlrTool::class.java) + + /** + * Utility method to create an instance of the Tool class. + */ + fun loadTool(className: String, args: Array?): Any { + logger.debug("Loading tool $className") + + try { + val toolClass = Class.forName(className) // ok to use caller classloader + logger.debug("Tool loaded $className") + + if (args == null) { + return JavaReflectionUtil.newInstance(toolClass) + } else { + val constructor = toolClass.getConstructor(Array::class.java) + return constructor.newInstance(*arrayOf(args)) + } + } catch (e: ClassNotFoundException) { + logger.error("Tool not loaded: ${e.message}") + throw e + } catch (e: InvocationTargetException) { + logger.error("Tool not loaded: ${e.message}") + throw GradleException("Failed to load ANTLR", e.cause) + } catch (e: Exception) { + logger.error("Tool not loaded: ${e.message}") + throw GradleException("Failed to load ANTLR", e) + } + } + } + + abstract fun invoke(arguments: List, inputDirectory: File?): Int + abstract fun available(): Boolean + + fun process(spec: AntlrSpec): AntlrResult = + try { + doProcess(spec) + } catch (e: ClassNotFoundException) { + // This should not happen if you call check availability with #available first + throw GradleException("Cannot process ANTLR sources", e) + } + + /** + * Process used for ANTLR 3/4. + */ + private fun doProcess(spec: AntlrSpec): AntlrResult { + var numErrors = 0 + + if (spec.inputDirectories.isEmpty()) { + // We have no root source folder information for the grammar files, + // so we do not force relativeOutput as we cannot calculate it. + // This results in flat generated sources in the output directory + numErrors += invoke(spec.asArgumentsWithFiles(), null) + } else { + val onWindows = OperatingSystem.current().isWindows + + for (inputDirectory in spec.inputDirectories) { + val arguments = spec.arguments.toMutableList() + arguments.add("-Dlanguage=Kotlin") + arguments.add("-o") + arguments.add(spec.outputDirectory.absolutePath) + + for (grammarFile in spec.grammarFiles) { + var relativeGrammarFilePath = FileUtils.getRelativePath(inputDirectory, grammarFile) + + if (onWindows) { + relativeGrammarFilePath = relativeGrammarFilePath.replace('/', File.separatorChar) + } + + arguments.add(relativeGrammarFilePath) + } + + numErrors += invoke(arguments, inputDirectory) + } + } + + return AntlrResult(numErrors, null) + } +} diff --git a/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrWorkerManager.kt b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrWorkerManager.kt new file mode 100644 index 00000000..ce12fe6b --- /dev/null +++ b/antlr-kotlin-gradle-plugin/src/main/kotlin/com/strumenta/antlrkotlin/gradle/internal/AntlrWorkerManager.kt @@ -0,0 +1,42 @@ +package com.strumenta.antlrkotlin.gradle.internal + +import org.gradle.api.file.FileCollection +import org.gradle.process.internal.worker.RequestHandler +import org.gradle.process.internal.worker.WorkerProcessFactory +import java.io.File + +internal class AntlrWorkerManager { + fun runWorker( + workingDir: File, + workerFactory: WorkerProcessFactory, + antlrClasspath: FileCollection?, + spec: AntlrSpec, + ): AntlrResult { + val antlrWorker = createWorkerProcess(workingDir, workerFactory, antlrClasspath, spec) + return antlrWorker.run(spec) + } + + private fun createWorkerProcess( + workingDir: File, + workerFactory: WorkerProcessFactory, + antlrClasspath: FileCollection?, + spec: AntlrSpec, + ): RequestHandler { + val builder = workerFactory.singleRequestWorker(AntlrExecutor::class.java) + builder.setBaseName("Gradle ANTLR Kotlin Worker") + + if (antlrClasspath != null) { + builder.applicationClasspath(antlrClasspath) + } + + builder.sharedPackages("antlr", "org.antlr") + + val javaCommand = builder.javaCommand + javaCommand.workingDir = workingDir + javaCommand.maxHeapSize = spec.maxHeapSize + javaCommand.systemProperty("ANTLR_DO_NOT_EXIT", "true") + javaCommand.redirectErrorStream() + + return builder.build() + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index ad4f3de7..02157e13 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { implementation(libs.kotlin.plugin) implementation(libs.maven.publish.plugin) implementation(libs.dokka.plugin) + implementation(libs.gradle.plugin.publish.plugin) } gradlePlugin { @@ -20,5 +21,10 @@ gradlePlugin { id = "strumenta.multiplatform" implementationClass = "com.strumenta.kotlinmultiplatform.gradle.plugins.StrumentaMultiplatformModulePlugin" } + + register("strumentaGradlePlugin") { + id = "strumenta.gradle.plugin" + implementationClass = "com.strumenta.kotlinmultiplatform.gradle.plugins.StrumentaGradlePluginModulePlugin" + } } } diff --git a/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/plugins/StrumentaGradlePluginModulePlugin.kt b/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/plugins/StrumentaGradlePluginModulePlugin.kt new file mode 100644 index 00000000..dd00d2a0 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/strumenta/kotlinmultiplatform/gradle/plugins/StrumentaGradlePluginModulePlugin.kt @@ -0,0 +1,41 @@ +package com.strumenta.kotlinmultiplatform.gradle.plugins + +import com.gradle.publish.PublishPlugin +import com.strumenta.kotlinmultiplatform.gradle.ext.javaExtension +import com.strumenta.kotlinmultiplatform.gradle.ext.kotlinJvmExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper + +/** + * Applies and set up the necessary dependencies to develop a Gradle Plugin. + */ +class StrumentaGradlePluginModulePlugin : Plugin { + override fun apply(project: Project) { + // Apply the required plugins + project.apply() + project.apply() + project.apply { + plugin("org.gradle.java-gradle-plugin") + } + + // General Kotlin configuration + val kotlin = project.kotlinJvmExtension + kotlin.explicitApiWarning() + kotlin.compilerOptions { + apiVersion.set(KotlinVersion.KOTLIN_1_9) + languageVersion.set(KotlinVersion.KOTLIN_1_9) + + jvmTarget.set(JvmTarget.JVM_1_8) + freeCompilerArgs.add("-Xjvm-default=all") + } + + val java = project.javaExtension + java.sourceCompatibility = JavaVersion.VERSION_1_8 + java.targetCompatibility = JavaVersion.VERSION_1_8 + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index af8f1bcd..bc3c6e95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ antlr4 = "4.13.1" dokka = "1.9.10" researchgate-release = "3.0.2" maven-publish = "0.25.3" +gradle-plugin-publish = "1.2.1" [libraries] kotlin-wrappers-bom = { group = "org.jetbrains.kotlin-wrappers", name = "kotlin-wrappers-bom", version.ref = "kotlin-wrappers" } @@ -15,6 +16,7 @@ antlr4 = { group = "org.antlr", name = "antlr4", version.ref = "antlr4" } kotlin-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } dokka-plugin = { group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version.ref = "dokka" } maven-publish-plugin = { group = "com.vanniktech", name = "gradle-maven-publish-plugin", version.ref = "maven-publish" } +gradle-plugin-publish-plugin = { group = "com.gradle.publish", name = "plugin-publish-plugin", version.ref = "gradle-plugin-publish" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index d9997539..14f1cae1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,3 +5,4 @@ rootProject.name = "antlr-kotlin" include("antlr-kotlin-runtime") include("antlr-kotlin-target") include("antlr-kotlin-tests") +include("antlr-kotlin-gradle-plugin") From 0b23b453bb4531d1d7d4adbdd240b8de01586ce4 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Thu, 14 Dec 2023 19:01:18 +0100 Subject: [PATCH 4/7] docs: update Gradle setup instructions --- README.md | 52 ++++++++++++---------------------------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 0eda6ce5..57c103fe 100644 --- a/README.md +++ b/README.md @@ -57,31 +57,16 @@ To start using ANTLR Kotlin: } ``` -2. Add the Gradle `antlr` plugin to the list of plugins in your build script. +2. Add the ANTLR Gradle plugin for the Kotlin target to the list of plugins in your build script. ```kotlin plugins { ... - antlr + id("com.strumenta.antlr-kotlin") version "$antlrKotlinVersion" } ``` -3. Add ANTLR 4 and the ANTLR Kotlin target to the classpath of the Gradle ANTLR plugin. - At the top level of your build script, add: - - ```kotlin - dependencies { - // The ANTLR 4 dependency, which instructs the Gradle ANTLR plugin - // to use ANTLR 4 instead of the bundled version - antlr("org.antlr:antlr4:4.13.1") - - // The ANTLR Kotlin target - antlr("com.strumenta:antlr-kotlin-target:$antlrKotlinVersion") - } - ``` - For more details, check out [Gradle - The ANTLR Plugin](https://docs.gradle.org/current/userguide/antlr_plugin.html). - -4. Add the ANTLR Kotlin Runtime to the list of dependencies. +3. Add the ANTLR Kotlin Runtime to the list of dependencies. If you are working in a multiplatform project, add it to the common source set. ```kotlin @@ -97,38 +82,25 @@ To start using ANTLR Kotlin: } ``` -5. Disable the default ANTLR grammar generation task. - This is set up by the Gradle ANTLR plugin, but it does not suit our needs. - - ```kotlin - tasks { - generateGrammarSource { - // The default task is set up considering a Java source set, - // which we might not have in a Kotlin project. - // Using it is messier than simply registering a new task - enabled = false - } - } - ``` - -6. Create the ANTLR Kotlin grammar generation task. +4. Register the ANTLR Kotlin grammar generation task. ```kotlin - val generateKotlinGrammarSource = tasks.register("generateKotlinGrammarSource") { + val generateKotlinGrammarSource = tasks.register("generateKotlinGrammarSource") { dependsOn("cleanGenerateKotlinGrammarSource") // ANTLR .g4 files are under {example-project}/antlr - setSource(layout.projectDirectory.dir("src/main/antlr")) + setSource(layout.projectDirectory.dir("antlr")) + // We want the generated source files to have this package name val pkgName = "com.strumenta.antlrkotlin.parsers.generated" + packageName = pkgName + arguments = listOf( "-Dlanguage=Kotlin", // We want to generate Kotlin sources "-visitor", // We want visitors alongside listeners - "-package", pkgName, // We want the generated sources to have this package declared - "-encoding", "UTF-8", // We want the generated sources to be encoded in UTF-8 ) - // Generated files are outputted inside build/generatedAntlr + // Generated files are outputted inside build/generatedAntlr/{package-name} val outDir = "generatedAntlr/${pkgName.replace(".", "/")}" outputDirectory = layout.buildDirectory.dir(outDir).get().asFile } @@ -137,7 +109,7 @@ To start using ANTLR Kotlin: Depending on `cleanGenerateKotlinGrammarSource` ensures the `.tokens` files are always fresh, and we do not end up with out-of-sync lexers and parsers. -7. Register the `build/generatedAntlr` directory as part of the common source set. +5. Register the `build/generatedAntlr` directory as part of the common source set. ```kotlin kotlin { @@ -151,7 +123,7 @@ To start using ANTLR Kotlin: } ``` -8. Optionally instruct the Kotlin compilation tasks to depend on the grammar generation. +6. Instruct the Kotlin compilation tasks to depend on the grammar generation. ```kotlin withType> { From ccb4094e142c62c44ecf03f5f4909ecd3411ef70 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Thu, 14 Dec 2023 19:10:18 +0100 Subject: [PATCH 5/7] ci: add a run to check the Gradle plugin --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a54c06d..e5c4b973 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,9 @@ jobs: strategy: matrix: include: + - java: '17' + target: Gradle Plugin + task: ':antlr-kotlin-gradle-plugin:check' - java: '11' target: JVM task: jvmTest From ad06c13c08913ec303dabef1c47205ed0b862ebc Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Thu, 14 Dec 2023 19:15:24 +0100 Subject: [PATCH 6/7] docs: minor cleanup --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 57c103fe..4066fbe8 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,16 @@ To start using ANTLR Kotlin: Depending on `cleanGenerateKotlinGrammarSource` ensures the `.tokens` files are always fresh, and we do not end up with out-of-sync lexers and parsers. -5. Register the `build/generatedAntlr` directory as part of the common source set. +5. Instruct the Kotlin compilation tasks to depend on the grammar generation. + + ```kotlin + tasks.withType> { + dependsOn(generateKotlinGrammarSource) + } + + ``` + +6. Register the `build/generatedAntlr` directory as part of the common source set. ```kotlin kotlin { @@ -123,15 +132,6 @@ To start using ANTLR Kotlin: } ``` -6. Instruct the Kotlin compilation tasks to depend on the grammar generation. - - ```kotlin - withType> { - dependsOn(generateKotlinGrammarSource) - } - - ``` - ## Maven Central Publication Publication can be performed running: From 865792d02df61c915a3c9f13005a417c57df8ab5 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Thu, 14 Dec 2023 19:30:16 +0100 Subject: [PATCH 7/7] docs: specify the Kotlin target language and the encoding are implicit --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4066fbe8..57a161b2 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,9 @@ To start using ANTLR Kotlin: val pkgName = "com.strumenta.antlrkotlin.parsers.generated" packageName = pkgName - arguments = listOf( - "-Dlanguage=Kotlin", // We want to generate Kotlin sources - "-visitor", // We want visitors alongside listeners - ) + // We want visitors alongside listeners. + // The Kotlin target language is implicit, as is the file encoding (UTF-8) + arguments = listOf("-visitor") // Generated files are outputted inside build/generatedAntlr/{package-name} val outDir = "generatedAntlr/${pkgName.replace(".", "/")}"