plugins {
    id "java"
}

repositories {
    mavenCentral()
}

group = "org.embulk"
version = "0.11.5-SNAPSHOT"

def embeddedPlugins = [
    [ group: "org.embulk", name: "embulk-decoder-bzip2", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-decoder-gzip", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-encoder-bzip2", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-encoder-gzip", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-filter-remove_columns", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-filter-rename", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-formatter-csv", version: "0.11.2" ],
    [ group: "org.embulk", name: "embulk-guess-bzip2", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-guess-csv", version: "0.11.6" ],
    [ group: "org.embulk", name: "embulk-guess-csv_all_strings", version: "0.11.6" ],
    [ group: "org.embulk", name: "embulk-guess-gzip", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-guess-json", version: "0.11.2" ],
    [ group: "org.embulk", name: "embulk-input-config", version: "0.11.2" ],
    [ group: "org.embulk", name: "embulk-input-file", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-output-file", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-output-null", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-output-stdout", version: "0.11.1" ],
    [ group: "org.embulk", name: "embulk-parser-csv", version: "0.11.6" ],
    [ group: "org.embulk", name: "embulk-parser-json", version: "0.11.2" ],
]

configurations {
    compileClasspath.resolutionStrategy.activateDependencyLocking()
    runtimeClasspath.resolutionStrategy.activateDependencyLocking()

    // "deps" is internal dependencies for the Embulk core (embulk-deps).
    deps {
        resolutionStrategy.activateDependencyLocking()
    }

    // They are "standard" plugins embedded in the executable Embulk binary distribution.
    embeddedPlugins.each { pluginArtifact ->
        create(pluginArtifact["name"]).resolutionStrategy.activateDependencyLocking()
    }
}

dependencies {
    // The classes of "embulk-spi" are excluded from "embulk-core" below for the consistency as a JAR file.
    // But, they should be included in the compile classpath.
    compileOnly "org.embulk:embulk-spi:0.11"
    compileOnly "org.slf4j:slf4j-api:2.0.13"
    compileOnly "org.msgpack:msgpack-core:0.8.24"

    implementation(project(":embulk-core")) {
        // The JAR file of "embulk-core" should already contain classes of "embulk-spi".
        exclude group: "org.embulk", module: "embulk-spi"
    }

    // Logback 1.5.x seems to be the latest as of May, 2024, but we use the Logback 1.3.x as of now because
    // Logback 1.5.x requires JDK 11. It also expects Jakarta EE (jakarta.*), not Java EE (javax.*).
    // https://logback.qos.ch/download.html
    //
    // Embulk still needs to work on JDK 8 for a while.
    //
    // Logback 1.3.x and 1.4.x are both older stable versions, but still a kind of maintained. Their version strategy is:
    // * Logback 1.3.x for Java 8 and Java EE (javax.*)
    // * Logback 1.4.x for Java 11 and Jakarta EE (jakarta.*)
    // https://logback.qos.ch/dependencies.html
    implementation "ch.qos.logback:logback-classic:1.3.14"

    deps project(":embulk-deps")

    embeddedPlugins.each { pluginArtifact ->
        add(pluginArtifact["name"], pluginArtifact)
    }
}

// A safer and strict alternative to: "dependencies" (and "dependencies --write-locks")
//
// This task fails explicitly when the specified dependency is not available.
// In contrast, "dependencies (--write-locks)" does not fail even when a part the dependencies are unavailable.
//
// https://docs.gradle.org/7.6.1/userguide/dependency_locking.html#generating_and_updating_dependency_locks
allprojects {
    task checkDependencies {
        notCompatibleWithConfigurationCache("The task \"checkDependencies\" filters configurations at execution time.")
        doLast {
            configurations.findAll { it.canBeResolved }.each { it.resolve() }
        }
    }
}

subprojects.each { subProject ->
    rootProject.tasks.getByPath(":checkDependencies").dependsOn(subProject.tasks.getByPath("checkDependencies"))
}

def listEmbeddedDependencies = { configurationName, rootModuleName, prefix ->
    def firstArtifact = []  // The artifact of rootModuleName -- the list should contain only one.
    def followingArtifacts = []  // Artifacts depended from the artifact of rootModuleName.

    configurations.getByName(configurationName).resolvedConfiguration.firstLevelModuleDependencies.each { firstLevelDependency ->
        if (firstLevelDependency.moduleName == rootModuleName) {
            firstLevelDependency.allModuleArtifacts.each { requiredArtifact ->
                if (requiredArtifact.name == rootModuleName) {
                    firstArtifact.add(prefix + requiredArtifact.file.name)
                } else {
                    followingArtifacts.add(prefix + requiredArtifact.file.name)
                }
            }
        }
    }

    if (firstArtifact.isEmpty()) {
        if (followingArtifacts.isEmpty()) {
            throw new GradleException('Failed to collect libraries to embedded: no dependencies found')
        } else {
            throw new GradleException('Failed to collect libraries to embedded: specified root module is not found')
        }
    } else if (firstArtifact.size() > 1) {
        throw new GradleException('Failed to collect libraries to embedded: multiple root modules are found')
    }

    // It guarantees that the artifact of rootModuleName comes first.
    return String.join(' ', firstArtifact + followingArtifacts)
}

// Standard "jar" task to build a JAR with dependency JAR resources embedded.
jar {
    dependsOn ":embulk-core:jar",
              ":embulk-deps:jar"

    // The root directory in the executable JAR should not accept duplicated files.
    duplicatesStrategy(DuplicatesStrategy.FAIL)

    // Expands all dependencies including "embulk-core"
    from(configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }) {
        // META-INF/INDEX.LIST from dependencies should be definitely excluded in the all-in-one executable JAR file.
        exclude "META-INF/INDEX.LIST"

        // Exclude dependencies' LICENSE and NOTICE files here. They should be included in a different way.
        exclude "META-INF/LICENSE*"
        exclude "META-INF/NOTICE*"

        // Exclude module-info.class from dependencies.
        //
        // It should be okay while Embulk basically expects Java 8, and it should not break things even for Java 9+.
        // But, it is nice to revisit module-info.class when Embulk officially supports modern Java versions.
        //
        // TODO: Provide module-info.class unified for the all-in-one executable JAR file for the Java 9+ Module System.
        exclude "META-INF/versions/*/module-info.class"
    }

    metaInf {
        from rootProject.file("LICENSE")
        from rootProject.file("LICENSE-icu4j")
        from rootProject.file("LICENSE-jctools")
        from rootProject.file("LICENSE-jsr166y")
        from rootProject.file("LICENSE-logback")
        from rootProject.file("LICENSE-slf4j")
        from rootProject.file("LICENSE-slf4j-netty")
        from rootProject.file("NOTICE-executable")
        rename ("NOTICE-executable", "NOTICE")
    }

    into("/lib") {
        // The "/lib" directory in the executable JAR should have duplicated files from deps and plugins embedded.
        // They are just excluded.
        duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
        from configurations.deps
    }

    embeddedPlugins.each { pluginArtifact ->
        into("/lib") {
            // The "/lib" directory in the executable JAR should have duplicated files from deps and plugins embedded.
            // They are just excluded.
            setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE)
            from(configurations.getByName(pluginArtifact["name"]))
        }
    }

    // NOTE: This 'Implementation-Version' in the manifest is referred to provide the Embulk version at runtime.
    // See also: embulk-core/src/main/java/org/embulk/EmbulkVersion.java
    manifest {
        attributes 'Implementation-Title': 'embulk',
                   'Implementation-Version': project.version,
                   'Implementation-Vendor-Id': project.group,
                   'Specification-Title': 'embulk',
                   'Specification-Version': project.version,
                   'Embulk-Resource-Class-Path': listEmbeddedDependencies("deps", "embulk-deps", "/lib/"),
                   "Main-Class": "org.embulk.cli.Main",
                   "Embulk-Plugins": String.join(" ", embeddedPlugins.collect { artifact -> artifact["name"] }),

                   // "json" should be registered before "csv".
                   // charset and newline guess plugins are loaded and invoked by CsvGuessPlugin
                   "Embulk-Default-Guess-Plugins": "gzip,bzip2,json,csv"

        def pluginAttributes = embeddedPlugins.collectEntries { artifact ->
            [ ("Embulk-Plugin-${artifact['name']}".toString()): listEmbeddedDependencies(artifact["name"], artifact["name"], "/lib/") ]
        }
        attributes(pluginAttributes)
    }
}

task executableJar(dependsOn: "jar") {
    ext.destinationDir = file("${buildDir}/executable")
    doFirst {
        destinationDir.mkdirs()
    }
    ext.destination = file("${destinationDir}/embulk-${project.version}.jar")
    doLast {
        destination.write("")
        destination.append("\n: <<END_OF_EMBULK_SELFRUN_BATCH_PART\r\n")
        destination.append(file("embulk-core/src/main/bat/selfrun.bat").readBytes())
        destination.append("\r\nEND_OF_EMBULK_SELFRUN_BATCH_PART\r\n\n")
        destination.append(file("embulk-core/src/main/sh/selfrun.sh").readBytes())
        destination.append(jar.outputs.files.singleFile.readBytes())
        destination.setExecutable(true)
    }
}

task releaseCheck {
    doFirst {
        if (rootProject.version.endsWith("-SNAPSHOT")) {
            throw new GradleException("Not for release. The version in build.gradle is SNAPSHOT: ${rootProject.version}")
        }
        // TODO: Revisit if we would check the format of tag annotation.
    }
    doLast {
        println "Ready. Run 'release' task."
    }
}

task release {
    dependsOn "releaseCheck"

    dependsOn "executableJar"
    tasks.findByPath(":executableJar").mustRunAfter(":releaseCheck")

    dependsOn "embulk-core:publishMavenPublicationToMavenCentralRepository"
    tasks.findByPath(":embulk-core:publishMavenPublicationToMavenCentralRepository").mustRunAfter(":releaseCheck")

    dependsOn "embulk-deps:publishMavenPublicationToMavenCentralRepository"
    tasks.findByPath(":embulk-deps:publishMavenPublicationToMavenCentralRepository").mustRunAfter(":releaseCheck")

    dependsOn "embulk-junit4:publishMavenPublicationToMavenCentralRepository"
    tasks.findByPath(":embulk-junit4:publishMavenPublicationToMavenCentralRepository").mustRunAfter(":releaseCheck")

    dependsOn "embulk-ruby:gemPush"
    tasks.findByPath(":embulk-ruby:gemPush").mustRunAfter(":embulk-core:publishMavenPublicationToMavenCentralRepository")
    tasks.findByPath(":embulk-ruby:gemPush").mustRunAfter(":embulk-deps:publishMavenPublicationToMavenCentralRepository")
    tasks.findByPath(":embulk-ruby:gemPush").mustRunAfter(":embulk-junit4:publishMavenPublicationToMavenCentralRepository")
    tasks.findByPath(":embulk-ruby:gemPush").mustRunAfter(":executableJar")
}
