/*
 * Copyright 2014-2025 Real Logic Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
plugins {
    id 'java-library'
    alias(libs.plugins.shadow).apply(false)
    alias(libs.plugins.versions)
}

defaultTasks 'clean', 'build'

static def rawBuildJavaVersion()
{
    return System.getenv('BUILD_JAVA_VERSION') ?: JavaVersion.current().getMajorVersion()
}
boolean isEarlyAccessJavaVersion = rawBuildJavaVersion().endsWith("-ea")

static def getBuildJavaVersion() {
    def buildJavaVersion = rawBuildJavaVersion()

    if (buildJavaVersion.indexOf('.') > 0) {
        buildJavaVersion = buildJavaVersion.substring(0, buildJavaVersion.indexOf('.'))
    }

    if (buildJavaVersion.indexOf('-') > 0) {
        buildJavaVersion = buildJavaVersion.substring(0, buildJavaVersion.indexOf('-'))
    }

    Integer.parseInt(buildJavaVersion)
}
int buildJavaVersion = getBuildJavaVersion()

def toolchainLauncher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(buildJavaVersion)
}

def aeronGroup = 'io.aeron'
def aeronVersion = file('version.txt').text.trim()
def gitCommitHash = io.aeron.build.GithubUtil.currentGitHash("${projectDir}")

def getConfigProperty(final String projectPropertyName, final String envVarName) {
    String value = project.findProperty(projectPropertyName)
    if (!value) {
        value = System.getenv(envVarName)
        if (!value) {
            return null
        }
    }

    value = value.trim()

    return value ? value : null
}

ext {
    isReleaseVersion = !aeronVersion.endsWith('-SNAPSHOT')

    sonatypeCentralPortalReleasesRepoUrl = 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/'
    sonatypeCentralPortalSnapshotsRepoUrl = 'https://central.sonatype.com/repository/maven-snapshots/'
    sonatypeCentralPortalUsername = getConfigProperty('sonatypeCentralPortalUsername', 'SONATYPE_CENTRAL_PORTAL_USERNAME')
    sonatypeCentralPortalPassword = getConfigProperty('sonatypeCentralPortalPassword', 'SONATYPE_CENTRAL_PORTAL_PASSWORD')

    signingKey = getConfigProperty('signingKey', 'SIGNING_GPG_SECRET_KEY')         // NOTE: ASCII armored secret key
    signingPassword = getConfigProperty('signingPassword', 'SIGNING_GPG_PASSWORD') // NOTE: Plain text
}

def projectPom = {
    name = 'aeron'
    // optionally artifactId can be defined here
    description = 'Efficient reliable UDP unicast, UDP multicast, and IPC transport protocol.'
    url = 'https://github.com/aeron-io/aeron'

    scm {
        connection = 'scm:git:https://github.com/aeron-io/aeron.git'
        developerConnection = 'scm:git:https://github.com/aeron-io/aeron.git'
        url = 'https://github.com/aeron-io/aeron.git'
    }

    licenses {
        license {
            name = 'The Apache License, Version 2.0'
            url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
        }
    }

    developers {
        developer {
            id = 'tmontgomery'
            name = 'Todd L. Montgomery'
            email = 'tmont@nard.net'
            url = 'https://github.com/tmontgomery'
        }
        developer {
            id = 'mjpt777'
            name = 'Martin Thompson'
            email = 'mjpt777@gmail.com'
            url = 'https://github.com/mjpt777'
        }
        developer {
            id = 'RichardWarburton'
            name = 'Richard Warburton'
            email = 'richard.warburton@gmail.com'
            url = 'https://github.com/RichardWarburton'
        }
        developer {
            id = 'nitsanw'
            name = 'Nitsan Wakart'
            email = 'nitsanw@yahoo.com'
            url = 'https://github.com/nitsanw'
        }
        developer {
            id = 'mikeb01'
            name = 'Mike Barker'
            email = 'mikeb01@gmail.com'
            url = 'https://github.com/mikeb01'
        }
        developer {
            id = 'vyazelenko'
            name = 'Dmytro Vyazelenko'
            email = 'vyazelenko@protonmail.com'
            url = 'https://github.com/vyazelenko'
        }
    }
}

jar.enabled = false

allprojects {
    repositories {
        mavenLocal()
        mavenCentral()
    }

    configurations.configureEach {
        resolutionStrategy {
            failOnVersionConflict()

            force libs.agrona,
                  libs.byteBuddy,
                  libs.byteBuddy.agent,
                  // patching conflicting Checkstyle dependencies
                  libs.commons.codec,
                  libs.commons.lang3,
                  libs.httpcore,
                  libs.plexus.utils
        }
    }

    tasks.withType(JavaExec).configureEach {
        javaLauncher.set(toolchainLauncher)
    }
}

subprojects {
    apply plugin: 'base'
    apply plugin: 'java-library'
    apply plugin: 'checkstyle'

    group = aeronGroup
    version = aeronVersion

    checkstyle.toolVersion = libs.versions.checkstyle.get()

    dependencies {
        testImplementation libs.hamcrest
        testImplementation libs.mockito
        testImplementation platform(libs.junit.bom)
        testImplementation "org.junit.jupiter:junit-jupiter-params"
        testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    }

    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(buildJavaVersion)
        }
        sourceCompatibility = JavaVersion.VERSION_17
    }

    tasks.withType(Sign).configureEach {
        onlyIf {
            isReleaseVersion && gradle.taskGraph.hasTask(tasks.publish)
        }
    }

    tasks.withType(Jar).configureEach {
        enabled = true
        includeEmptyDirs = false
    }

    tasks.withType(JavaCompile).configureEach {
        options.encoding = 'UTF-8'
        options.deprecation = true
        options.compilerArgs.add('--release')
        options.compilerArgs.add(java.sourceCompatibility.majorVersion)
        options.compilerArgs.addAll(['-Xlint:all,-processing', '-Werror']) // Enable all warnings and treat them as errors
        options.compilerArgs.add("-Aio.aeron.version=${aeronVersion}")
        options.compilerArgs.add("-Aio.aeron.gitsha=${gitCommitHash}")
    }

    def mockitoAgent = configurations.create('mockitoAgent') { transitive = false }
    mockitoAgent.withDependencies {it.add(libs.mockito.get()) }

    tasks.withType(Test).configureEach { test ->
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        jvmArgs('--add-opens', 'java.base/java.util.zip=ALL-UNNAMED')
        jvmArgs("-javaagent:${mockitoAgent.asPath}")
        if (buildJavaVersion >= 21) {
            jvmArgs('-XX:+EnableDynamicAgentLoading')
        }
        testClassesDirs = testing.suites.test.sources.output.classesDirs
        classpath = testing.suites.test.sources.runtimeClasspath
        maxHeapSize = '1g'
        jvmArgs("-XX:MaxDirectMemorySize=1g",
                "-XX:+CreateCoredumpOnCrash",
                "-XX:+HeapDumpOnOutOfMemoryError",
                "-XX:+CrashOnOutOfMemoryError",
                "-XX:HeapDumpPath=${project.rootDir}/heap.hprof")

        useJUnitPlatform {
            if (test.name != 'slowTest' && test.name != 'bindingsTest' && test.name != 'topologyTest')
            {
                excludeTags 'any()'
            }
        }

        testLogging {
            for (def level : LogLevel.values())
            {
                def testLogging = get(level)
                testLogging.exceptionFormat = 'full'
                testLogging.events = ["FAILED", "STANDARD_OUT", "STANDARD_ERROR"]
            }
        }

        systemProperties(
            'java.util.logging.config.class': 'io.aeron.test.DisableJavaUtilLogging',
            'aeron.shared.idle.strategy': 'yield',
            'aeron.conductor.idle.strategy': 'yield',
            'aeron.sender.idle.strategy': 'yield',
            'aeron.receiver.idle.strategy': 'yield',
            'aeron.use.windows.high.res.timer': 'true',
            'aeron.timer.interval': '100ms',
            'aeron.dir.delete.on.start': 'true',
            'aeron.term.buffer.sparse.file': 'true',
            'aeron.perform.storage.checks': 'false',
            'agrona.disable.bounds.checks': 'false',
            'agrona.strict.alignment.checks': 'true',
            'net.bytebuddy.experimental': 'true',
            'io.aeron.shadow.net.bytebuddy.experimental': 'true',
            'net.bytebuddy.safe': 'true',
            'io.aeron.shadow.net.bytebuddy.safe': 'true',
            'java.net.preferIPv4Stack': 'true',
            'aeron.fallback.logger': 'no_op',
            'aeron.test.system.aeronmd.path': System.getProperty('aeron.test.system.aeronmd.path'),
            'aeron.test.system.binding.remote.host': System.getProperty('aeron.test.system.binding.remote.host'),
            'aeron.test.system.binding.local.host': System.getProperty('aeron.test.system.binding.local.host'),
            'aeron.test.system.aeron.dir': System.getProperty('aeron.test.system.aeron.dir'),
            'aeron.test.system.ats.path': System.getProperty('aeron.test.system.ats.path'),
            'aeron.test.system.ats.conf.dir': System.getProperty('aeron.test.system.ats.conf.dir'),
            'aeron.test.system.ats.conf.file': System.getProperty('aeron.test.system.ats.conf.file'),
            'aeron.test.system.driver.await.counters': System.getProperty('aeron.test.system.driver.await.counters'))

        javaLauncher.set(toolchainLauncher)
    }

    def aeronLoggingAgent = project.file("../aeron-agent/build/libs/aeron-agent-${aeronVersion}.jar").absolutePath

    tasks.register('slowTest', Test) {
        group = 'verification'
        dependsOn ':aeron-agent:jar'

        maxParallelForks = 1
        useJUnitPlatform {
            includeTags 'slow'
        }

        jvmArgs("-javaagent:${aeronLoggingAgent}")
        systemProperty 'aeron.event.cluster.log', 'all'
        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION'
        systemProperty 'aeron.event.archive.log', 'all'
        systemProperty 'aeron.event.log', 'admin'
        systemProperty 'aeron.debug.timeout', '3600s'
        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'
    }

    tasks.register('bindingsTest', Test) {
        group = 'verification'
        dependsOn ':aeron-agent:jar'

        maxParallelForks = 1
        useJUnitPlatform {
            includeTags 'bindings'
        }

        jvmArgs("-javaagent:${aeronLoggingAgent}")
        systemProperty 'aeron.event.cluster.log', 'all'
        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION'
        systemProperty 'aeron.debug.timeout', '3600s'
        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'
    }

    tasks.register('topologyTest', Test) {
        group = 'verification'
        maxParallelForks = 1
        useJUnitPlatform {
            includeTags 'topology'
        }
    }

    javadoc {
        failOnError = false
        title = '<h1>Aeron Message Transport</h1>'
        options.bottom = '<i>Copyright &#169; 2014-2025 Real Logic Limited. All Rights Reserved.</i>'
        options.encoding = 'UTF-8'
        options.docEncoding = 'UTF-8'
        options.charSet = 'UTF-8'
        options.links("https://www.javadoc.io/doc/org.agrona/agrona/${libs.versions.agrona.get()}/")

        if (isEarlyAccessJavaVersion) {
            options.links("https://download.java.net/java/early_access/jdk${buildJavaVersion}/docs/api/")
        }
        else {
            options.links("https://docs.oracle.com/en/java/javase/${buildJavaVersion}/docs/api/")
        }

        exclude 'io/aeron/archive/codecs/**'
        exclude 'io/aeron/cluster/codecs/**'
        exclude 'io/aeron/samples/**'
        exclude 'io/aeron/test/**'

        options.addStringOption('-release', java.sourceCompatibility.majorVersion)
        options.addBooleanOption('Werror', true)
        options.addBooleanOption('Xdoclint:all', true)
    }

    tasks.register('testJar', Jar) {
        dependsOn 'testClasses'
        archiveClassifier.set("test-${base.archivesName}")
        from sourceSets.test.output
    }

    configurations {
        tests
    }

    artifacts {
        tests testJar
    }
}

project(':aeron-annotations') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'

    jar {
        manifest.attributes(
            'Automatic-Module-Name': 'io.aeron.annotations',
            'Implementation-Title': 'Aeron',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': aeronVersion
        )
    }

    java {
        withSourcesJar()
        withJavadocJar()
    }

    publishing {
        publications {
            aeronAnnotations(MavenPublication) {
                from components.java
                pom(projectPom)
            }
        }

        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronAnnotations
    }
}

project(':aeron-client') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'

    dependencies {
        api libs.agrona
        implementation project(':aeron-annotations')
        annotationProcessor project(':aeron-annotations')
        testImplementation project(':aeron-test-support')
    }

    jar {
        manifest.attributes(
            'Automatic-Module-Name': 'io.aeron.client',
            'Implementation-Title': 'Aeron',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': aeronVersion
        )
    }

    java {
        withSourcesJar()
        withJavadocJar()
    }

    javadoc {
        source += project(':aeron-annotations').sourceSets.main.allJava
    }

    publishing {
        publications {
            aeronClient(MavenPublication) {
                from components.java
                pom(projectPom)
            }
        }

        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronClient
    }

    def configInfoFile = "${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat"

    tasks.register('validateConfigExpectations', JavaExec) {
        def sourceDir = 'src/main/c'

        inputs.files(configInfoFile)

        mainClass.set('io.aeron.config.validation.ValidateConfigExpectationsTask')
        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath
        args = [configInfoFile, sourceDir]
    }

    tasks.register('generateConfigDoc', JavaExec) {
        def generatedDocFile = "${layout.buildDirectory.get()}/generated-doc/out.md"

        inputs.files(configInfoFile)
        outputs.files(generatedDocFile)

        mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask')
        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath
        args = [configInfoFile, generatedDocFile]
    }

    tasks.register('validateCounterExpectations', JavaExec) {
        def counterInfoFile = "${layout.buildDirectory.get()}/generated/sources/headers/java/main/counter-info.dat"
        def sourceDir = 'src/main/c'

        inputs.files(counterInfoFile)

        mainClass.set('io.aeron.counter.validation.ValidateCounterExpectationsTask')
        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath
        args = [counterInfoFile, sourceDir]
    }
}

project(':aeron-driver') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'
    apply plugin: 'application'

    application {
        mainClass.set('io.aeron.driver.MediaDriver')
    }

    dependencies {
        api project(':aeron-client')
        implementation project(':aeron-annotations')
        annotationProcessor project(':aeron-annotations')
        testImplementation project(':aeron-test-support')
    }

    jar {
        manifest.attributes(
            'Automatic-Module-Name': 'io.aeron.driver',
            'Implementation-Title': 'Aeron',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': aeronVersion
        )
    }

    jar.finalizedBy assembleDist

    java {
        withSourcesJar()
        withJavadocJar()
    }

    javadoc {
        source += project(':aeron-annotations').sourceSets.main.allJava
        source += project(':aeron-client').sourceSets.main.allJava
    }

    publishing {
        publications {
            aeronDriver(MavenPublication) {
                from components.java
                artifact distZip
                artifact distTar
                pom(projectPom)
            }
        }

        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronDriver
    }

    tasks.register('validateConfigExpectations', JavaExec) {
        def configInfoFile = "${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat"
        def sourceDir = 'src/main/c'

        inputs.files(configInfoFile)

        mainClass.set('io.aeron.config.validation.ValidateConfigExpectationsTask')
        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath
        args = [configInfoFile, sourceDir]
    }

    tasks.register('generateConfigDoc', JavaExec) {
        def configInfoFile = "${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat"
        def generatedDocFile = "${layout.buildDirectory.get()}/generated-doc/out.md"

        inputs.files(configInfoFile)
        outputs.files(generatedDocFile)

        mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask')
        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath
        args = [configInfoFile, generatedDocFile]
    }
}

project(':aeron-archive') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'

    configurations{
        codecGeneration
    }

    dependencies {
        api project(':aeron-driver')
        api files('build/classes/java/generated')
        codecGeneration libs.sbe
        implementation project(':aeron-annotations')
        annotationProcessor project(':aeron-annotations')
        testImplementation project(':aeron-test-support')
        testImplementation files("${layout.buildDirectory.get()}/classes/java/generatedTest")
    }

    def generatedSrcDir = file("${layout.buildDirectory.get()}/generated-src")
    def generatedTestDir = file("${layout.buildDirectory.get()}/generated-test")

    sourceSets {
        generated {
            java.srcDir generatedSrcDir
            compileClasspath += configurations.codecGeneration
        }
        generatedTest {
            java.srcDir generatedTestDir
            compileClasspath += configurations.codecGeneration
        }
    }

    def codecsFile = 'src/main/resources/archive/aeron-archive-codecs.xml'
    def markCodecsFile = 'src/main/resources/archive/aeron-archive-mark-codecs.xml'
    def sbeFile = 'src/main/resources/archive/fpl/sbe.xsd'

    tasks.register('generateCodecs', JavaExec) {
        inputs.files(codecsFile, markCodecsFile, sbeFile)
        outputs.dir generatedSrcDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        classpath = configurations.codecGeneration
        systemProperties(
            'sbe.output.dir': generatedSrcDir,
            'sbe.target.language': 'Java',
            'sbe.validation.xsd': sbeFile,
            'sbe.validation.stop.on.error': 'true')
        args = [codecsFile, markCodecsFile]
    }

    def generatedCppDir = file(System.getProperty('codec.target.dir') ?: "${rootDir}/cppbuild/Release/generated")
    tasks.register('generateCppCodecs', JavaExec) {
        inputs.files(codecsFile, sbeFile)
        outputs.dir generatedCppDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        classpath = configurations.codecGeneration
        systemProperties(
            'sbe.output.dir': generatedCppDir,
            'sbe.target.language': 'Cpp',
            'sbe.target.namespace': 'aeron.archive.client',
            'sbe.validation.xsd': sbeFile,
            'sbe.validation.stop.on.error': 'true')
        args = [codecsFile]
    }

    def generatedCDir = file(System.getProperty('codec.target.dir') ?: "${rootDir}/cppbuild/Release/generated/c")
    tasks.register('generateCCodecs', JavaExec) {
        inputs.files(codecsFile, sbeFile)
        outputs.dir generatedCDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        classpath = configurations.codecGeneration
        systemProperties(
            'sbe.output.dir': generatedCDir,
            'sbe.target.language': 'C',
            'sbe.target.namespace': 'aeron.archive.client',
            'sbe.validation.xsd': sbeFile,
            'sbe.validation.stop.on.error': 'true')
        args = [codecsFile]
    }

    tasks.register('generateTestCodecsV6', JavaExec) {
        inputs.files(codecsFile, sbeFile)
        outputs.dir generatedTestDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        classpath = configurations.codecGeneration
        systemProperties(
            'sbe.output.dir': generatedTestDir,
            'sbe.target.language': 'Java',
            'sbe.validation.xsd': sbeFile,
            'sbe.validation.stop.on.error': 'true',
            'sbe.target.namespace': 'io.aeron.archive.codecs.v6',
            'sbe.schema.transform.version': '*:6')
        args = [codecsFile]
    }

    tasks.register('generateTestCodecsMarkFileV0', JavaExec) {
        inputs.files(markCodecsFile, sbeFile)
        outputs.dir generatedTestDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        classpath = configurations.codecGeneration
        systemProperties(
            'sbe.output.dir': generatedTestDir,
            'sbe.target.language': 'Java',
            'sbe.validation.xsd': sbeFile,
            'sbe.validation.stop.on.error': 'true',
            'sbe.target.namespace': 'io.aeron.archive.codecs.mark.v0',
            'sbe.schema.transform.version': '*:0')
        args = [markCodecsFile]
    }

    tasks.register('generateTestCodecsMarkFileV1', JavaExec) {
        inputs.files(markCodecsFile, sbeFile)
        outputs.dir generatedTestDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        classpath = configurations.codecGeneration
        systemProperties(
            'sbe.output.dir': generatedTestDir,
            'sbe.target.language': 'Java',
            'sbe.validation.xsd': sbeFile,
            'sbe.validation.stop.on.error': 'true',
            'sbe.target.namespace': 'io.aeron.archive.codecs.mark.v1',
            'sbe.schema.transform.version': '*:1')
        args = [markCodecsFile]
    }

    compileJava.dependsOn 'compileGeneratedJava'
    compileGeneratedJava.dependsOn 'generateCodecs'
    compileTestJava.dependsOn 'compileGeneratedTestJava'
    compileGeneratedTestJava.dependsOn 'generateTestCodecsV6', 'generateTestCodecsMarkFileV0', 'generateTestCodecsMarkFileV1'

    jar {
        from sourceSets.generated.output

        manifest.attributes(
            'Automatic-Module-Name': 'io.aeron.archive',
            'Implementation-Title': 'Aeron',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': aeronVersion
        )
    }

    tasks.register('sourcesJar', Jar) {
        dependsOn 'generateCodecs'
        archiveClassifier.set('sources')
        from sourceSets.main.allSource
        from sourceSets.generated.allSource
    }

    javadoc {
        source += sourceSets.generated.allJava
        source += project(':aeron-annotations').sourceSets.main.allJava
        source += project(':aeron-client').sourceSets.main.allJava
        source += project(':aeron-driver').sourceSets.main.allJava
    }

    javadoc.dependsOn generateCodecs

    java {
        withSourcesJar()
        withJavadocJar()
    }

    publishing {
        publications {
            aeronArchive(MavenPublication) {
                from components.java
                pom(projectPom)
            }
        }

        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronArchive
    }

    tasks.register('generateConfigDoc', JavaExec) {
        def configInfoFile = "${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat"
        def generatedDocFile = "${layout.buildDirectory.get()}/generated-doc/out.md"

        inputs.files(configInfoFile)
        outputs.files(generatedDocFile)

        mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask')
        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath
        args = [configInfoFile, generatedDocFile]
    }
}

project(':aeron-cluster') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'

    configurations{
        codecGeneration
    }

    dependencies {
        api project(':aeron-archive')
        api files('build/classes/java/generated')
        codecGeneration libs.sbe
        implementation project(':aeron-annotations')
        annotationProcessor project(':aeron-annotations')
        testImplementation project(':aeron-test-support')
        testImplementation files("${layout.buildDirectory.get()}/classes/java/generatedTest")
    }

    def generatedSrcDir = file("${layout.buildDirectory.get()}/generated-src")
    def generatedTestDir = file("${layout.buildDirectory.get()}/generated-test")

    sourceSets {
        generated {
            java.srcDir generatedSrcDir
            compileClasspath += configurations.codecGeneration
        }
        generatedTest {
            java.srcDir generatedTestDir
            compileClasspath += configurations.codecGeneration
        }
    }

    def codecsFile = 'src/main/resources/cluster/aeron-cluster-codecs.xml'
    def markCodecsFile = 'src/main/resources/cluster/aeron-cluster-mark-codecs.xml'
    def nodeStateCodecsFile = 'src/main/resources/cluster/aeron-cluster-node-state-codecs.xml'
    def sbeFile = 'src/main/resources/cluster/fpl/sbe.xsd'

    tasks.register('generateCodecs', JavaExec) {
        inputs.files(codecsFile, markCodecsFile, sbeFile)
        outputs.dir generatedSrcDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        classpath = configurations.codecGeneration
        systemProperties(
            'sbe.output.dir': generatedSrcDir,
            'sbe.target.language': 'Java',
            'sbe.validation.xsd': sbeFile,
            'sbe.validation.stop.on.error': 'true')
        args = [codecsFile, markCodecsFile, nodeStateCodecsFile]
    }

    def markCodecsFileV0 = 'src/test/resources/aeron-cluster-mark-codecs-v0.xml'
    tasks.register('generateTestCodecsMarkFileV0', JavaExec) {
        inputs.files(markCodecsFileV0, sbeFile)
        outputs.dir generatedTestDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        classpath = configurations.codecGeneration
        systemProperties(
            'sbe.output.dir': generatedTestDir,
            'sbe.target.language': 'Java',
            'sbe.validation.xsd': sbeFile,
            'sbe.validation.stop.on.error': 'true',
            'sbe.target.namespace': 'io.aeron.cluster.codecs.mark.v0',
            'sbe.schema.transform.version': '*:0')
        args = [markCodecsFileV0]
    }

    tasks.register('generateTestCodecsV5', JavaExec) {
        inputs.files(codecsFile, sbeFile)
        outputs.dir generatedTestDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        classpath = configurations.codecGeneration
        systemProperties(
                'sbe.output.dir': generatedTestDir,
                'sbe.target.language': 'Java',
                'sbe.validation.xsd': sbeFile,
                'sbe.validation.stop.on.error': 'true',
                'sbe.target.namespace': 'io.aeron.cluster.codecs.v5',
                'sbe.schema.transform.version': '*:5')
        args = [codecsFile]
    }

    tasks.register('generateTestCodecsV12', JavaExec) {
        inputs.files(codecsFile, sbeFile)
        outputs.dir generatedTestDir

        mainClass.set('uk.co.real_logic.sbe.SbeTool')
        classpath = configurations.codecGeneration
        systemProperties(
                'sbe.output.dir': generatedTestDir,
                'sbe.target.language': 'Java',
                'sbe.validation.xsd': sbeFile,
                'sbe.validation.stop.on.error': 'true',
                'sbe.target.namespace': 'io.aeron.cluster.codecs.v12',
                'sbe.schema.transform.version': '*:12')
        args = [codecsFile]
    }

    compileJava.dependsOn 'compileGeneratedJava'
    compileGeneratedJava.dependsOn 'generateCodecs'
    compileTestJava.dependsOn 'compileGeneratedTestJava'
    compileGeneratedTestJava.dependsOn 'generateTestCodecsMarkFileV0', 'generateTestCodecsV5', 'generateTestCodecsV12'

    jar {
        from sourceSets.generated.output

        manifest.attributes(
            'Automatic-Module-Name': 'io.aeron.cluster',
            'Implementation-Title': 'Aeron',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': aeronVersion
        )
    }

    tasks.register('sourcesJar', Jar) {
        dependsOn 'generateCodecs'
        archiveClassifier.set('sources')
        from sourceSets.main.allSource
        from sourceSets.generated.allSource
    }

    javadoc {
        source += sourceSets.generated.allJava
        source += project(':aeron-annotations').sourceSets.main.allJava
        source += project(':aeron-client').sourceSets.main.allJava
        source += project(':aeron-driver').sourceSets.main.allJava
        source += project(':aeron-archive').sourceSets.main.allJava
        source += project(':aeron-archive').sourceSets.generated.allJava
    }

    javadoc.dependsOn generateCodecs

    java {
        withSourcesJar()
        withJavadocJar()
    }

    publishing {
        publications {
            aeronCluster(MavenPublication) {
                from components.java
                pom(projectPom)
            }
        }

        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronCluster
    }

    tasks.register('generateConfigDoc', JavaExec) {
        def configInfoFile = "${layout.buildDirectory.get()}/generated/sources/headers/java/main/config-info.dat"
        def generatedDocFile = "${layout.buildDirectory.get()}/generated-doc/out.md"

        inputs.files(configInfoFile)
        outputs.files(generatedDocFile)

        mainClass.set('io.aeron.config.docgen.GenerateConfigDocTask')
        classpath project(':aeron-annotations').sourceSets.main.runtimeClasspath
        args = [configInfoFile, generatedDocFile]
    }
}

project(':aeron-agent') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'
    apply plugin: 'com.gradleup.shadow'

    def aeronClientProject = project(':aeron-client')
    def aeronDriverProject = project(':aeron-driver')
    def aeronArchiveProject = project(':aeron-archive')
    def aeronClusterProject = project(':aeron-cluster')

    dependencies {
        implementation aeronClusterProject
        implementation libs.byteBuddy
        implementation libs.byteBuddy.agent
        compileOnly libs.findbugs.annotations
        testCompileOnly libs.findbugs.annotations
        testImplementation project(':aeron-test-support')
    }

    shadowJar {
        archiveClassifier.set('')

        manifest.attributes(
            'Automatic-Module-Name': 'io.aeron.agent',
            'Implementation-Title': 'Aeron',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': aeronVersion,
            "Premain-Class": "io.aeron.agent.EventLogAgent",
            "Agent-Class": "io.aeron.agent.EventLogAgent",
            "Can-Redefine-Classes": "true",
            "Can-Retransform-Classes": "true"
        )

        relocate 'net.bytebuddy', 'io.aeron.shadow.net.bytebuddy'
    }

    jar.finalizedBy shadowJar

    tasks.register('dedupJar', io.aeron.build.DeduplicateTask) {
        source = shadowJar.outputs.files.singleFile
    }

    shadowJar.finalizedBy('dedupJar')

    tasks.register('sourcesJar', Jar) {
        dependsOn ':aeron-archive:generateCodecs', ':aeron-cluster:generateCodecs'
        archiveClassifier.set('sources')
        from files(
            aeronClientProject.sourceSets.main.allSource,
            aeronDriverProject.sourceSets.main.allSource,
            aeronArchiveProject.sourceSets.main.allSource,
            aeronArchiveProject.sourceSets.generated.allSource,
            aeronClusterProject.sourceSets.main.allSource,
            aeronClusterProject.sourceSets.generated.allSource)
    }

    javadoc {
        source += project(':aeron-annotations').sourceSets.main.allJava
        source += aeronClientProject.sourceSets.main.allJava
        source += aeronDriverProject.sourceSets.main.allJava
        source += aeronArchiveProject.sourceSets.main.allJava
        source += aeronArchiveProject.sourceSets.generated.allJava
        source += aeronClusterProject.sourceSets.main.allJava
        source += aeronClusterProject.sourceSets.generated.allJava
    }

    javadoc.dependsOn ':aeron-archive:generateCodecs', ':aeron-cluster:generateCodecs'

    tasks.register('javadocJar', Jar) {
        dependsOn 'javadoc'
        archiveClassifier.set('javadoc')
        from javadoc.destinationDir
    }

    publishing {
        publications {
            aeronAgent(MavenPublication) {
                artifact shadowJar
                artifact sourcesJar
                artifact javadocJar
                pom(projectPom)
            }
        }
        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronAgent
    }
}

project(':aeron-samples') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'

    dependencies {
        api project(':aeron-cluster')
        implementation libs.hdrHistogram
        testImplementation project(':aeron-test-support')
    }

    java {
        withSourcesJar()
        withJavadocJar()
    }

    publishing {
        publications {
            aeronSamples(MavenPublication) {
                from components.java
                pom(projectPom)
            }
        }

        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    javadoc {
        source += project(':aeron-annotations').sourceSets.main.allJava
        source += project(':aeron-client').sourceSets.main.allJava
        source += project(':aeron-driver').sourceSets.main.allJava
        source += project(':aeron-archive').sourceSets.main.allJava
        source += project(':aeron-archive').sourceSets.generated.allJava
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronSamples
    }

    tasks.named('test').configure {
        dependsOn ':aeron-agent:jar'

        ext.agentFilename = {
            project.file("../aeron-agent/build/libs/aeron-agent-${aeronVersion}.jar").absolutePath
        }

        jvmArgs '-javaagent:' + agentFilename()
        systemProperty 'aeron.event.cluster.log', 'all'
        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION'
        systemProperty 'aeron.event.archive.log', 'all'
        systemProperty 'aeron.event.log', 'admin'
        systemProperty 'aeron.debug.timeout', '3600s'
        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'
    }

    tasks.register('asciidoctorGithub', io.aeron.build.AsciidoctorPreprocessTask) {
        versionText = aeronVersion
    }

    tasks.register('removeWikiDirectory', Delete) {
        delete "${layout.buildDirectory.get()}/tmp/tutorialPublish"
    }

    tasks.register('tutorialPublish', io.aeron.build.TutorialPublishTask) {
        dependsOn 'removeWikiDirectory', 'asciidoctorGithub'
        apiKey = project.hasProperty('aeron.ci.apiKey') ? project.property('aeron.ci.apiKey') : ''
        source = asciidoctorGithub.target
        remoteName = project.hasProperty('aeron.ci.remoteName') ? project.property('aeron.ci.remoteName') : 'origin'
    }
}

project(':aeron-system-tests') {
    apply plugin: 'com.gradleup.shadow'

    dependencies {
        testImplementation project(':aeron-archive')
        testImplementation project(':aeron-test-support')
        testImplementation project(':aeron-samples')
        testImplementation project(path: ':aeron-client', configuration: 'tests')
        testImplementation project(path: ':aeron-archive', configuration: 'tests')
        testImplementation project(path: ':aeron-cluster')
        testImplementation libs.byteBuddy
        testImplementation libs.byteBuddy.agent
        testCompileOnly libs.findbugs.annotations
    }

    tasks.named('test').configure {
        dependsOn ':aeron-agent:jar'

        ext.agentFilename = {
            project.file("../aeron-agent/build/libs/aeron-agent-${aeronVersion}.jar").absolutePath
        }

        jvmArgs '-javaagent:' + agentFilename()
        systemProperty 'aeron.event.cluster.log', 'all'
        systemProperty 'aeron.event.cluster.log.disable', 'CANVASS_POSITION,APPEND_POSITION,COMMIT_POSITION'
        systemProperty 'aeron.event.archive.log', 'all'
        systemProperty 'aeron.event.log', 'admin'
        systemProperty 'aeron.debug.timeout', '3600s'
        systemProperty 'aeron.event.log.reader.classname', 'io.aeron.agent.CollectingEventLogReaderAgent'
    }

    shadowJar {
        archiveClassifier.set('tests')
        from sourceSets.test.output
        configurations = [project.configurations.testRuntimeClasspath]
    }

    javadoc.enabled = false
}

project(':aeron-test-support') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'

    dependencies {
        implementation project(':aeron-client')
        implementation project(':aeron-cluster')
        implementation project(':aeron-samples')
        implementation platform(libs.junit.bom)
        implementation "org.junit.jupiter:junit-jupiter-api"
        implementation "org.junit.jupiter:junit-jupiter-params"
        implementation libs.mockito
    }

    jar {
        manifest.attributes(
            'Automatic-Module-Name': 'io.aeron.test-support',
            'Implementation-Title': 'Aeron',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': aeronVersion
        )
    }

    java {
        withSourcesJar()
        withJavadocJar()
    }

    publishing {
        publications {
            aeronTestSupport(MavenPublication) {
                from components.java
                pom(projectPom)
            }
        }

        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronTestSupport
    }
}

project(':aeron-all') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'
    apply plugin: 'com.gradleup.shadow'

    def aeronClientProject = project(':aeron-client')
    def aeronDriverProject = project(':aeron-driver')
    def aeronArchiveProject = project(':aeron-archive')
    def aeronClusterProject = project(':aeron-cluster')
    def aeronSamplesProject = project(':aeron-samples')

    dependencies {
        implementation aeronClusterProject
        implementation aeronSamplesProject
    }

    shadowJar {
        archiveClassifier.set('')
        manifest.attributes(
            'Automatic-Module-Name': 'io.aeron.all',
            'Implementation-Title': 'Aeron',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': aeronVersion
        )

        relocate 'net.bytebuddy', 'io.aeron.shadow.net.bytebuddy'
        relocate 'org.HdrHistogram', 'io.aeron.shadow.org.HdrHistogram'
    }

    jar.finalizedBy shadowJar

    tasks.register('dedupJar', io.aeron.build.DeduplicateTask) {
        source = shadowJar.outputs.files.singleFile
    }

    shadowJar.finalizedBy('dedupJar')

    tasks.register('sourcesJar', Jar) {
        dependsOn ':aeron-archive:generateCodecs', ':aeron-cluster:generateCodecs'
        archiveClassifier.set('sources')
        from files(
            aeronClientProject.sourceSets.main.allSource,
            aeronDriverProject.sourceSets.main.allSource,
            aeronArchiveProject.sourceSets.main.allSource,
            aeronArchiveProject.sourceSets.generated.allSource,
            aeronClusterProject.sourceSets.main.allSource,
            aeronClusterProject.sourceSets.generated.allSource,
            aeronSamplesProject.sourceSets.main.allSource)
    }

    javadoc {
        source += project(':aeron-annotations').sourceSets.main.allJava
        source += aeronClientProject.sourceSets.main.allJava
        source += aeronDriverProject.sourceSets.main.allJava
        source += aeronArchiveProject.sourceSets.main.allJava
        source += aeronArchiveProject.sourceSets.generated.allJava
        source += aeronClusterProject.sourceSets.main.allJava
        source += aeronClusterProject.sourceSets.generated.allJava
        source += aeronSamplesProject.sourceSets.main.allJava
    }

    javadoc.dependsOn ':aeron-archive:generateCodecs', ':aeron-cluster:generateCodecs'

    tasks.register('javadocJar', Jar) {
        dependsOn 'javadoc'
        archiveClassifier.set('javadoc')
        from javadoc.destinationDir
    }

    publishing {
        publications {
            aeronAll(MavenPublication) {
                artifact shadowJar
                artifact sourcesJar
                artifact javadocJar
                pom(projectPom)
            }
        }
        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (signingKey != null) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.aeronAll
    }
}

tasks.register('testReport', TestReport) {
    destinationDirectory = file("${layout.buildDirectory.get()}/reports/allTests")
    // Include the results from the `test` task in all sub-projects
    testResults.setFrom(subprojects*.test)
}

tasks.register('copyTestLogs', Copy) {
    from '.'
    include '**/build/test-output/**'
    include '**/*.hprof'
    include '**/*.mdmp'
    include '/var/coredump/core*'
    include '**/*.log'
    include '**/*.tlog'
    include '**/*.PML'
    include '**/build/reports/tests/**'
    include '**/build/test-results/**/*'
    include 'LICENSE'
    exclude 'build'
    into 'build/test_logs'

    includeEmptyDirs = false
}

tasks.register('tarTestLogs', Tar) {
    dependsOn tasks.named('copyTestLogs')
    archiveBaseName.set('test_logs')
    from 'build/test_logs'
    compression = Compression.BZIP2
}

def isNonStable = { String version ->
    def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }
    def regex = /^[0-9,.v-]+(-r)?$/
    return !stableKeyword && !(version ==~ regex)
}

tasks.named('dependencyUpdates').configure {
    // Reject all non stable versions
    rejectVersionIf {
        isNonStable(it.candidate.version)
    }
}

tasks.register('uploadArtifactsToCentralPortal', io.aeron.build.SonatypeCentralPortalUploadRepositoryTask) {
    portalUsername.set(sonatypeCentralPortalUsername)
    portalPassword.set(sonatypeCentralPortalPassword)
    groupId.set(aeronGroup)
    snapshotRelease.set(!isReleaseVersion)
}

wrapper {
    distributionType = 'ALL'
}
