Skip to content

Commit

Permalink
Merge pull request #115 from lppedd/build/cleanup
Browse files Browse the repository at this point in the history
Cleanup build scripts and restore ability to publish to a private repository
  • Loading branch information
ftomassetti authored Dec 18, 2023
2 parents 04d77aa + 865792d commit fe502bb
Show file tree
Hide file tree
Showing 25 changed files with 775 additions and 173 deletions.
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") {
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<*>> {
dependsOn(generateKotlinGrammarSource)
}

```

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

```kotlin
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")

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

0 comments on commit fe502bb

Please sign in to comment.