rife2 / bld

Pure java build tool for developers who don't like dealing with build tools
https://rife2.com/bld
Apache License 2.0
178 stars 9 forks source link

Alternate Groovy syntax for projects that would use bld #19

Closed paul-hammant closed 1 week ago

paul-hammant commented 5 months ago

I write about a particular Groovy language feature a lot - https://paulhammant.com/2024/02/14/that-groovy-language-feature/. The best GPT4 can do to describe it is "Groovy in builder-style DSL syntax that leverages closures". It needs a shorter name, which is the request of that blog entry. Anyway, I know Gradle exists, but I wonder what Bld itself would look like in an alternative Groovy syntax.

Code below is GPT4-made and for a initial subset of Bld features.

A sample Grooby bld script:

// This is how you would use the above classes to interpret your DSL
def buildConfig = new BuildConfig()
buildConfig.build {
    project(name: 'MyApp', version: '1.0.0') {
        sourceSets {
            main {
                java { srcDir 'src/main/java' }
                resources { srcDir 'src/main/resources' }
            }
            test {
                java { srcDir 'src/test/java' }
                resources { srcDir 'src/test/resources' }
            }
        }
        dependencies {
            compile 'org.springframework:spring-core:5.2.3.RELEASE'
            testCompile 'junit:junit:4.13'
        }
        tasks {
            compileJava {
                source = sourceSets.main.javaSrcDir
                destination = 'build/classes'
            }
            test {
                source = sourceSets.test.javaSrcDir
                classpath = ['build/classes'] + dependencies.testCompileDependencies
            }
        }
    }
}

The classes that would minimally implement that:

// Define the BuildConfig class to handle the 'build' closure
class BuildConfig {
    Project project

    def project(Map attributes, Closure config) {
        project = new Project(attributes.name, attributes.version)
        config.delegate = project
        config()
    }
}

// Define the Project class to handle the 'project' closure
class Project {
    String name
    String version
    SourceSets sourceSets = new SourceSets()
    Dependencies dependencies = new Dependencies()
    Tasks tasks = new Tasks()

    Project(String name, String version) {
        this.name = name
        this.version = version
    }

    def sourceSets(Closure config) {
        config.delegate = sourceSets
        config()
    }

    def dependencies(Closure config) {
        config.delegate = dependencies
        config()
    }

    def tasks(Closure config) {
        config.delegate = tasks
        config()
    }
}

// Define the SourceSets class to handle the 'sourceSets' closure
class SourceSets {
    SourceSet main = new SourceSet()
    SourceSet test = new SourceSet()

    // Define inner class for each source set
    class SourceSet {
        String javaSrcDir
        String resourcesSrcDir

        def java(Closure config) {
            javaSrcDir = config.srcDir
        }

        def resources(Closure config) {
            resourcesSrcDir = config.srcDir
        }
    }
}

// Define the Dependencies class to handle the 'dependencies' closure
class Dependencies {
    List<String> compileDependencies = []
    List<String> testCompileDependencies = []

    def compile(String dependency) {
        compileDependencies.add(dependency)
    }

    def testCompile(String dependency) {
        testCompileDependencies.add(dependency)
    }
}

// Define the Tasks class to handle the 'tasks' closure
class Tasks {
    Task compileJava = new Task()
    Task test = new Task()

    // Define inner class for tasks
    class Task {
        String source
        String destination
        List<String> classpath

        def source(String source) {
            this.source = source
        }

        def destination(String destination) {
            this.destination = destination
        }

        def classpath(List<String> classpath) {
            this.classpath = classpath
        }
    }
}

For the uniitiatated the Closure config is how the magic is passed around. Ruby has the same language feature.

Groovy also has a "Grapes" way of declaring deps at the top of the groovy source file. Plucking bld jars from MavenCentral, this could also be a way of doing bld without ./lib/bld/ bits and pieces.

The tech could easily be a separate build technology that secretly delegates to bld, but it might as well be part of bld seeing as it would have 1:1 concept/method mapping. It could simultaneously allow bld-using projects to mix Groovy and Java sources in one solution. Well, maybe it could

paul-hammant commented 5 months ago

GPT4 can go a little further:

import rife.bld.Project as BldProject
import rife.bld.SourceSet as BldSourceSet
import rife.bld.Task as BldTask
// Additional bld imports as needed

class BuildConfig {
    Project project

    def build(Closure config) {
        project = new Project()
        config.delegate = project
        config()
    }
}

class Project {
    String name
    String version
    BldProject bldProject
    SourceSets sourceSets = new SourceSets(this)
    Dependencies dependencies = new Dependencies(this)
    Tasks tasks = new Tasks(this)

    Project() {
        this.bldProject = new BldProject()
    }

    def name(String name) { this.name = name }
    def version(String version) { this.version = version }

    def sourceSets(Closure config) {
        config.delegate = sourceSets
        config()
    }

    def dependencies(Closure config) {
        config.delegate = dependencies
        config()
    }

    def tasks(Closure config) {
        config.delegate = tasks
        config()
    }

    // Example of finalizing configuration and applying to bldProject
    def configureBldProject() {
        bldProject.setName(this.name)
        bldProject.setVersion(this.version)
        // Additional configuration mappings
    }
}

class SourceSets {
    Project project
    Map<String, BldSourceSet> bldSourceSets = [:]

    SourceSets(Project project) {
        this.project = project
    }

    def main(Closure config) {
        BldSourceSet bldSourceSet = new BldSourceSet()
        config.delegate = bldSourceSet
        config()
        bldSourceSets['main'] = bldSourceSet
        // Update the project's bldProject with the new source set
    }

    def test(Closure config) {
        BldSourceSet bldSourceSet = new BldSourceSet()
        config.delegate = bldSourceSet
        config()
        bldSourceSets['test'] = bldSourceSet
        // Update the project's bldProject with the new source set
    }
}

class Dependencies {
    Project project
    // Assume BldProject has methods for handling dependencies
    Dependencies(Project project) {
        this.project = project
    }

    def compile(String dependency) {
        // Add compile dependency to the bldProject
        project.bldProject.addCompileDependency(dependency)
    }

    def testCompile(String dependency) {
        // Add test compile dependency to the bldProject
        project.bldProject.addTestCompileDependency(dependency)
    }
}

class Tasks {
    Project project
    Map<String, BldTask> bldTasks = [:]

    Tasks(Project project) {
        this.project = project
    }

    def compileJava(Closure config) {
        BldTask bldTask = new BldTask()
        config.delegate = bldTask
        config()
        bldTasks['compileJava'] = bldTask
        // Update the project's bldProject with the new task
    }

    def test(Closure config) {
        BldTask bldTask = new BldTask()
        config.delegate = bldTask
        config()
        bldTasks['test'] = bldTask
        // Update the project's bldProject with the new task
    }
}

// Usage example
def buildConfig = new BuildConfig()
buildConfig.build {
    name 'MyApp'
    version '1.0.0'
    // Additional configuration
}
paul-hammant commented 5 months ago

You'd need to publish a second Jar of the groovy classes that depended on the regular Java Bld classes. That could also be another uberJar that included the regular bld classes.

ethauvin commented 5 months ago

@paul-hammant Thanks for looking into this.

It could be implemented as an extension without requiring modification to the bld core.

Is it something you would be interested in tackling? If so, don't hesitate to reach out. I've written many extensions. @gbevin thinks I'm the extenspert.

paul-hammant commented 5 months ago

This - https://github.com/rife2/bld-kotlin - would be my starting point as I am biased toward SSCCEs?

ethauvin commented 5 months ago

This - https://github.com/rife2/bld-kotlin - would be my starting point as I am biased toward SSCCEs?

Yes, if you're planning on using the embedded Groovy compiler.

paul-hammant commented 5 months ago

For making the Groovy capability for Bld, bringing in Groovy's jar as a dep and writing the extension in Java is the way, right?

After that for end-users: if they've done sudo apt install groovy before/after the git-clone of the repo that would use it instead of vanilla Bld, they need only do groovy bld.groovy right? That rather than rely on bld.bat and it's unix equivalent w/o suffix?

ethauvin commented 5 months ago

For making the Groovy capability for Bld, bringing in Groovy's jar as a dep and writing the extension in Java is the way, right?

Yes

After that for end-users: if they've done sudo apt install groovy before/after the git-clone of the repo that would use it instead of vanilla Bld, they need only do groovy bld.groovy right? That rather than rely on bld.bat and it's unix equivalent w/o suffix?

I don't think that would be needed. They'd just have a Groovy build class file and use the bld.bat as usual.

If you just follow the Kotlin extension code, you'll basically make bld capable of building Groovy code. It will take a bit more massaging to make bld use a build file written in Groovy.