Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup build scripts and restore ability to publish to a private repository #115

Merged
merged 7 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 37 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -97,59 +82,55 @@ 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.
4. Register the ANTLR Kotlin grammar generation task.

```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.

```kotlin
val generateKotlinGrammarSource = tasks.register<AntlrTask>("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<AntlrKotlinTask>("generateKotlinGrammarSource") {
ftomassetti marked this conversation as resolved.
Show resolved Hide resolved
dependsOn("cleanGenerateKotlinGrammarSource")

// ANTLR .g4 files are under {example-project}/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

// We want visitors alongside listeners.
// The Kotlin target language is implicit, as is the file encoding (UTF-8)
arguments = listOf("-visitor")

sourceSets.getByName("main") {
java.srcDir(layout.buildDirectory.get().dir("generatedAntlr"))
}
}
// Generated files are outputted inside build/generatedAntlr/{package-name}
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.
5. Instruct the Kotlin compilation tasks to depend on the grammar generation.

```kotlin
withType<KotlinCompile<*>> {
tasks.withType<KotlinCompile<*>> {
ftomassetti marked this conversation as resolved.
Show resolved Hide resolved
dependsOn(generateKotlinGrammarSource)
}

```

6. Register the `build/generatedAntlr` directory as part of the common source set.

```kotlin
ftomassetti marked this conversation as resolved.
Show resolved Hide resolved
kotlin {
sourceSets {
commonMain {
kotlin {
srcDir(layout.buildDirectory.dir("generatedAntlr"))
}
}
}
}
```

## Maven Central Publication

Publication can be performed running:
Expand Down
24 changes: 24 additions & 0 deletions antlr-kotlin-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@file:Suppress("UnstableApiUsage")
ftomassetti marked this conversation as resolved.
Show resolved Hide resolved

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"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.strumenta.antlrkotlin.gradle

import org.gradle.api.Plugin
import org.gradle.api.Project

public class AntlrKotlinPlugin : Plugin<Project> {
public override fun apply(target: Project) {
// Noop
}
}
Original file line number Diff line number Diff line change
@@ -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<String> = 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<File>()
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,
)
}
}
}
Loading