Skip to content

Commit

Permalink
Creates PIG Gradle plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell committed Jul 14, 2022
1 parent a18726e commit 92d2db4
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 43 deletions.
44 changes: 44 additions & 0 deletions pig-gradle-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# PIG Gradle Plugin

```groovy
plugins {
id 'pig-gradle-plugin'
}
```

## Source Sets
```groovy
sourceSets {
main {
pig {
// in addition to the default 'src/main/pig'
srcDir 'path/to/type/universes'
}
}
test {
pig {
// in addition to the default 'src/test/pig'
srcDir 'path/to/test/type/universes'
}
}
}
```

## Tasks

```
pig {
target = "kotlin" // required
namespace = ... // optional
template = ... // optional
outDir = 'build/generated-sources/pig/' // default
}
```

## Dependencies

| Task Name | Depends On |
|---------------------------|-------------------------------|
| compileJava | generatePigSource |
| compileTestJava | generatePigTestSource |
| compile*SourceSet*Java | generatePig*SourceSet*Source |
69 changes: 69 additions & 0 deletions pig-gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
plugins {
id 'java-gradle-plugin'
id 'org.jetbrains.kotlin.jvm' version '1.4.0'
id 'com.gradle.plugin-publish' version '1.0.0'
}

repositories {
mavenCentral()
}

dependencies {
// It is non-trivial to depend on a local plugin within a gradle project
// The simplest way is using a composite build: https://docs.gradle.org/current/userguide/composite_builds.html
// Other methods involved adding the build/lib/... jar to classpath, or publish to maven local
// By adding the plugin as a dep in `pig-tests`, I cannot use an included build of `pig` in the plugin
// Hence it's much simpler to use the latest published version in the plugin
implementation 'org.partiql:partiql-ir-generator:0.5.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}

test {
useJUnitPlatform()
}

pluginBundle {
website = 'https://github.com/partiql/partiql-ir-generator/wiki'
vcsUrl = 'https://github.com/partiql/partiql-ir-generator'
tags = ['partiql', 'pig', 'ir', 'partiql-ir-generator']
}

gradlePlugin {
plugins {
create('pig-gradle-plugin') {
id = 'pig-gradle-plugin'
// version — inherited from root
// group — inherited from root
displayName = "PIG Gradle Plugin"
description = "The PIG gradle plugin exposes a Gradle task to generate sources from a PIG type universe"
implementationClass = 'org.partiql.pig.plugin.PigPlugin'
}
}
}

publishing {
repositories {
maven {
name = 'mavenLocalPlugin'
url = '../maven-local-plugin'
}
}
}

java {
sourceSets {
main.java.srcDirs = ["src"]
main.resources.srcDirs = ["resources"]
test.java.srcDirs = ["test"]
test.resources.srcDirs = ["test-resources"]
}
}

plugins.withId('org.jetbrains.kotlin.jvm', { _ ->

sourceSets {
main.kotlin.srcDirs = ["src"]
test.kotlin.srcDirs = ["test"]
}
})
33 changes: 33 additions & 0 deletions pig-gradle-plugin/src/org/partiql/pig/plugin/PigExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.partiql.pig.plugin

import org.gradle.api.Project
import org.gradle.api.provider.Property
import javax.inject.Inject

abstract class PigExtension @Inject constructor(project: Project) {

private val objects = project.objects

val conventionalOutDir: String

init {
conventionalOutDir = "${project.buildDir}/generated-sources/pig"
}

// required
val target: Property<String> = objects.property(String::class.java)

// optional
val outputFile: Property<String> = objects.property(String::class.java)

// optional
val outputDir: Property<String> = objects
.property(String::class.java)
.convention(conventionalOutDir)

// optional
val namespace: Property<String> = objects.property(String::class.java)

// optional
val template: Property<String> = objects.property(String::class.java)
}
89 changes: 89 additions & 0 deletions pig-gradle-plugin/src/org/partiql/pig/plugin/PigPlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.partiql.pig.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer

abstract class PigPlugin : Plugin<Project> {

override fun apply(project: Project) {
// Ensure `sourceSets` extension exists
project.pluginManager.apply(JavaPlugin::class.java)

// Adds pig source set extension to all source sets
project.sourceSets().forEach { sourceSet ->
val name = sourceSet.name
val sds = project.objects.sourceDirectorySet(name, "$name PIG source")
sds.srcDir("src/$name/pig")
sds.include("**/*.ion")
sourceSet.extensions.add("pig", sds)
}

// Extensions for pig compiler arguments
val ext = project.extensions.create("pig", PigExtension::class.java, project)

// Create tasks after source sets have been evaluated
project.afterEvaluate {
project.sourceSets().forEach { sourceSet ->
// Pig generate all for the given source set
val pigAllTaskName = getPigAllTaskName(sourceSet)
val pigAllTask = project.tasks.create(pigAllTaskName) {
it.group = "pig"
it.description = "Generate all PIG sources for ${sourceSet.name} source set"
}

// If outDir is conventional, add generated sources to javac sources
// Else you're responsible for your own configuration choices
var outDir = ext.outputDir.get()
if (outDir == ext.conventionalOutDir) {
outDir = outDir + "/" + sourceSet.name
sourceSet.java.srcDir(outDir)
}

// Create a pig task for each type universe and each source set
(sourceSet.extensions.getByName("pig") as SourceDirectorySet).files.forEach { file ->
val universeName = file.name.removeSuffix(".ion").lowerToCamelCase().capitalize()
val pigTask = project.tasks.create(pigAllTaskName + universeName, PigTask::class.java) { task ->
task.description = "Generated PIG sources for $universeName"
task.universe.set(file.absolutePath)
task.target.set(ext.target)
task.outputDir.set(outDir)
task.outputFile.set(ext.outputFile)
task.namespace.set(ext.namespace)
task.template.set(ext.template)
}
pigAllTask.dependsOn(pigTask)
}

// Execute pig tasks before compiling
project.tasks.named(sourceSet.compileJavaTaskName) {
it.dependsOn(pigAllTask)
}
}
}
}

private fun Project.sourceSets(): List<SourceSet> = extensions.getByType(SourceSetContainer::class.java).toList()

private fun getPigAllTaskName(sourceSet: SourceSet) = when (sourceSet.name) {
"main" -> "generatePigSource"
else -> "generatePig${sourceSet.name.capitalize()}Source"
}

/**
* Type Universe files are lower hyphen, but Gradle tasks are lower camel
*/
private fun String.lowerToCamelCase(): String =
this.split('-')
.filter { it.isNotEmpty() }
.mapIndexed { i, str ->
when (i) {
0 -> str
else -> str.capitalize()
}
}
.joinToString(separator = "")
}
86 changes: 86 additions & 0 deletions pig-gradle-plugin/src/org/partiql/pig/plugin/PigTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.partiql.pig.plugin

import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option

abstract class PigTask : DefaultTask() {

init {
group = "pig"
}

@get:Input
@get:Option(
option = "universe",
description = "Type universe input file",
)
abstract val universe: Property<String>

@get:Input
@get:Option(
option = "target",
description = "Target language",
)
abstract val target: Property<String>

@get:Input
@get:Optional
@get:Option(
option = "outputFile",
description = "Generated output file (for targets that output a single file)",
)
abstract val outputFile: Property<String>

@get:Input
@get:Optional
@get:Option(
option = "outputDir",
description = "Generated output directory (for targets that output multiple files)",
)
abstract val outputDir: Property<String>

@get:Input
@get:Optional
@get:Option(
option = "namespace",
description = "Namespace for generated code",
)
abstract val namespace: Property<String>

@get:Input
@get:Optional
@get:Option(
option = "template",
description = "Path to an Apache FreeMarker template",
)
abstract val template: Property<String>

@TaskAction
fun action() {
val args = mutableListOf<String>()
// required args
args += listOf("-u", universe.get())
args += listOf("-t", target.get())
// optional args
if (outputFile.isPresent) {
args += listOf("-o", outputFile.get())
}
if (outputDir.isPresent) {
args += listOf("-d", outputDir.get())
}
if (namespace.isPresent) {
args += listOf("-n", namespace.get())
}
if (template.isPresent) {
args += listOf("-e", template.get())
}
// invoke pig compiler, offloads all arg handling to the application
// also invoking via the public interface for consistency
println("pig ${args.joinToString(" ")}")
org.partiql.pig.main(args.toTypedArray())
}
}
Loading

0 comments on commit 92d2db4

Please sign in to comment.