516 lines
18 KiB
Groovy
516 lines
18 KiB
Groovy
/*
|
|
* Copyright 2013-2026, Seqera Labs
|
|
*
|
|
* 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
|
|
*
|
|
* http://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'
|
|
id 'idea'
|
|
}
|
|
|
|
// Add ability to test with upcoming versions of Groovy
|
|
def groovyVer = System.getenv('CI_GROOVY_VERSION')
|
|
if (groovyVer) {
|
|
def repo = groovyVer.startsWith('com.github.apache:') ? 'https://jitpack.io' : 'https://oss.jfrog.org/oss-snapshot-local/'
|
|
logger.lifecycle "Overridden Groovy dependency to use $groovyVer - repository: $repo"
|
|
allprojects {
|
|
repositories {
|
|
maven { url repo }
|
|
}
|
|
|
|
configurations.all {
|
|
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
|
|
if (details.requested.group == 'org.apache.groovy') {
|
|
if( groovyVer.contains(':') )
|
|
details.useTarget(groovyVer)
|
|
else
|
|
details.useVersion(groovyVer)
|
|
println ">> Overriding $details.requested with version: $groovyVer"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def projects(String...args) {
|
|
args.collect {project(it)}
|
|
}
|
|
|
|
String gitVersion() {
|
|
def p = new ProcessBuilder() .command('sh','-c','git rev-parse --short HEAD') .start()
|
|
def r = p.waitFor()
|
|
return r==0 ? p.text.trim() : '(unknown)'
|
|
}
|
|
|
|
group = 'io.nextflow'
|
|
version = rootProject.file('VERSION').text.trim()
|
|
ext.commitId = gitVersion()
|
|
|
|
allprojects {
|
|
apply plugin: 'java'
|
|
apply plugin: 'java-test-fixtures'
|
|
apply plugin: 'idea'
|
|
apply plugin: 'groovy'
|
|
apply plugin: 'java-library'
|
|
apply plugin: 'jacoco'
|
|
|
|
java {
|
|
// these settings apply to all jvm tooling, including groovy
|
|
toolchain {
|
|
languageVersion = JavaLanguageVersion.of(21)
|
|
}
|
|
sourceCompatibility = 17
|
|
targetCompatibility = 17
|
|
}
|
|
|
|
idea {
|
|
module.inheritOutputDirs = true
|
|
}
|
|
|
|
repositories {
|
|
mavenCentral()
|
|
maven { url 'https://repo.eclipse.org/content/groups/releases' }
|
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
|
|
maven { url = "https://s3-eu-west-1.amazonaws.com/maven.seqera.io/releases" }
|
|
maven { url = "https://s3-eu-west-1.amazonaws.com/maven.seqera.io/snapshots" }
|
|
}
|
|
|
|
configurations {
|
|
// see https://docs.gradle.org/4.1/userguide/dependency_management.html#sub:exclude_transitive_dependencies
|
|
all*.exclude group: 'org.apache.groovy', module: 'groovy-all'
|
|
all*.exclude group: 'org.apache.groovy', module: 'groovy-cli-picocli'
|
|
// groovydoc libs
|
|
groovyDoc.extendsFrom runtime
|
|
}
|
|
|
|
dependencies {
|
|
// see https://docs.gradle.org/4.1/userguide/dependency_management.html#sec:module_replacement
|
|
modules {
|
|
module("commons-logging:commons-logging") { replacedBy("org.slf4j:jcl-over-slf4j") }
|
|
}
|
|
|
|
// JUnit Platform launcher required for Gradle 9.1+ when using useJUnitPlatform()
|
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.5'
|
|
|
|
// Documentation required libraries
|
|
groovyDoc 'org.fusesource.jansi:jansi:2.4.0'
|
|
groovyDoc "org.apache.groovy:groovy-groovydoc:4.0.31"
|
|
groovyDoc "org.apache.groovy:groovy-ant:4.0.31"
|
|
}
|
|
|
|
test {
|
|
useJUnitPlatform()
|
|
}
|
|
|
|
// this is required due to this IDEA bug
|
|
// https://youtrack.jetbrains.com/issue/IDEA-129282
|
|
sourceSets {
|
|
main {
|
|
output.resourcesDir = 'build/classes/main'
|
|
}
|
|
}
|
|
|
|
// Disable strict javadoc checks
|
|
// See http://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html
|
|
if (JavaVersion.current().isJava8Compatible()) {
|
|
tasks.withType(Javadoc) {
|
|
options.addStringOption('Xdoclint:none', '-quiet')
|
|
}
|
|
}
|
|
|
|
tasks.withType(Jar) {
|
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
|
}
|
|
|
|
// patched as described here
|
|
// http://forums.gradle.org/gradle/topics/gradle_task_groovydoc_failing_with_noclassdeffounderror
|
|
tasks.withType(Groovydoc) {
|
|
groovyClasspath = project.configurations.groovyDoc
|
|
includes = ["nextflow/**"]
|
|
}
|
|
|
|
// Required to run tests on Java 9 and higher in compatibility mode
|
|
tasks.withType(Test) {
|
|
jvmArgs ([
|
|
'--enable-preview',
|
|
'--add-opens=java.base/java.lang=ALL-UNNAMED',
|
|
'--add-opens=java.base/java.io=ALL-UNNAMED',
|
|
'--add-opens=java.base/java.nio=ALL-UNNAMED',
|
|
'--add-opens=java.base/java.nio.file.spi=ALL-UNNAMED',
|
|
'--add-opens=java.base/java.net=ALL-UNNAMED',
|
|
'--add-opens=java.base/java.util=ALL-UNNAMED',
|
|
'--add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED',
|
|
'--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED',
|
|
'--add-opens=java.base/sun.nio.ch=ALL-UNNAMED',
|
|
'--add-opens=java.base/sun.nio.fs=ALL-UNNAMED',
|
|
'--add-opens=java.base/sun.net.www.protocol.http=ALL-UNNAMED',
|
|
'--add-opens=java.base/sun.net.www.protocol.https=ALL-UNNAMED',
|
|
'--add-opens=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED',
|
|
'--add-opens=java.base/sun.net.www.protocol.file=ALL-UNNAMED',
|
|
'--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED',
|
|
'--add-opens=java.base/jdk.internal.vm=ALL-UNNAMED',
|
|
])
|
|
}
|
|
|
|
/**
|
|
* Code coverage with JaCoCo.
|
|
* See: https://www.jacoco.org/; https://docs.gradle.org/current/userguide/jacoco_plugin.html
|
|
*/
|
|
// Code coverage report is always generated after tests run
|
|
test { finalizedBy jacocoTestReport }
|
|
jacocoTestReport {
|
|
// Tests are required to run before generating the code coverage report
|
|
dependsOn test
|
|
|
|
// Remove closure classes from the report, as they are already covered by the enclosing class coverage stats adding only noise.
|
|
// See: https://stackoverflow.com/questions/39453696
|
|
afterEvaluate {
|
|
classDirectories.setFrom(files(classDirectories.files.collect { dir ->
|
|
fileTree(dir: dir, excludes: ['**/*$*_closure*'])
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
// disable jar for root project
|
|
jar.enabled = false
|
|
|
|
/*
|
|
* Update the build timestamp in the source source file
|
|
*/
|
|
task buildInfo {
|
|
// Always run this task - never consider it up-to-date
|
|
outputs.upToDateWhen { false }
|
|
|
|
doLast {
|
|
|
|
def file0 = file('modules/nextflow/src/main/resources/META-INF/build-info.properties')
|
|
def buildNum = 0
|
|
|
|
// Use GitHub Actions run number if available, otherwise increment local counter
|
|
if (System.getenv('GITHUB_RUN_NUMBER')) {
|
|
buildNum = System.getenv('GITHUB_RUN_NUMBER').toInteger()
|
|
println "Using GitHub Actions run number: $buildNum"
|
|
}
|
|
|
|
// -- update build-info file
|
|
file0.text = """\
|
|
build=${buildNum}
|
|
version=${version}
|
|
timestamp=${System.currentTimeMillis()}
|
|
commitId=${project.property('commitId')}
|
|
""".stripIndent()
|
|
}}
|
|
|
|
/*
|
|
* Update release information in nextflow wrapper, dockerfile, and plugin metadata.
|
|
*
|
|
* This task:
|
|
* 1. Updates the NXF_VER version string in the nextflow launch script
|
|
* 2. Updates the release version in docker/Dockerfile
|
|
* 3. Generates plugins-info.txt with current plugin versions from VERSION files
|
|
*
|
|
* This task always runs to ensure all release artifacts are updated with current versions.
|
|
*/
|
|
task releaseInfo {
|
|
dependsOn buildInfo
|
|
// Always run this task - never consider it up-to-date
|
|
outputs.upToDateWhen { false }
|
|
|
|
doLast {
|
|
|
|
// -- update 'nextflow' wrapper
|
|
def file0 = file('nextflow')
|
|
def src = file0.text
|
|
src = src.replaceAll(/NXF_VER\=\$\{NXF_VER:-'.*'\}/, 'NXF_VER=\\${NXF_VER:-\'' + version + '\'}')
|
|
file0.text = src
|
|
|
|
// -- update dockerfile
|
|
file0 = file('docker/Dockerfile')
|
|
src = file0.text
|
|
src = src.replaceAll(/releases\/v[0-9a-zA-Z_\-\.]+\//, "releases/v$version/" as String)
|
|
file0.text = src
|
|
|
|
// -- create plugins-info file
|
|
def plugins = []
|
|
new File(rootProject.rootDir, 'plugins')
|
|
.eachDir { if(it.name.startsWith('nf-')) plugins << project(":plugins:${it.name}") }
|
|
def meta = plugins.collect { "$it.name@$it.version" }
|
|
file('modules/nextflow/src/main/resources/META-INF/plugins-info.txt').text = meta.toSorted().join('\n')
|
|
}}
|
|
|
|
/*
|
|
* Validate that plugins-info.txt matches plugin VERSION files and that build-info.properties
|
|
* contains the correct build number and commit ID when running in GitHub Actions.
|
|
*
|
|
* This task ensures:
|
|
* 1. All plugin versions in plugins-info.txt match their corresponding VERSION files
|
|
* 2. The build number in build-info.properties matches GITHUB_RUN_NUMBER (in CI)
|
|
* 3. The commit ID in build-info.properties matches GITHUB_SHA (in CI)
|
|
*
|
|
* This validation prevents releases with stale or mismatched metadata.
|
|
*/
|
|
task validatePluginVersions {
|
|
dependsOn buildInfo
|
|
inputs.file('modules/nextflow/src/main/resources/META-INF/plugins-info.txt')
|
|
inputs.file('modules/nextflow/src/main/resources/META-INF/build-info.properties')
|
|
inputs.files(fileTree('plugins') { include '*/VERSION' })
|
|
|
|
doLast {
|
|
// Get expected versions from plugin projects
|
|
def expected = []
|
|
new File(rootProject.rootDir, 'plugins')
|
|
.eachDir { if(it.name.startsWith('nf-')) expected << project(":plugins:${it.name}") }
|
|
def expectedVersions = expected.collect { "$it.name@$it.version" }.toSorted()
|
|
|
|
// Get actual versions from plugins-info.txt
|
|
def actualVersions = file('modules/nextflow/src/main/resources/META-INF/plugins-info.txt').readLines()
|
|
|
|
// Compare plugin versions
|
|
if (expectedVersions != actualVersions) {
|
|
def diffs = []
|
|
expectedVersions.eachWithIndex { exp, i ->
|
|
def act = actualVersions.size() > i ? actualVersions[i] : 'missing'
|
|
if (exp != act) diffs << " expected: $exp, actual: $act"
|
|
}
|
|
throw new GradleException("Plugin version mismatch:\n${diffs.join('\n')}\nRun 'make assemble' to fix.")
|
|
}
|
|
|
|
// Validate build-info.properties - require GitHub Actions environment variables
|
|
if (!System.getenv('GITHUB_RUN_NUMBER')) {
|
|
throw new GradleException("GITHUB_RUN_NUMBER environment variable is required")
|
|
}
|
|
if (!System.getenv('GITHUB_SHA')) {
|
|
throw new GradleException("GITHUB_SHA environment variable is required")
|
|
}
|
|
|
|
def buildInfoFile = file('modules/nextflow/src/main/resources/META-INF/build-info.properties')
|
|
def props = new Properties()
|
|
buildInfoFile.withInputStream { props.load(it) }
|
|
|
|
def actualBuild = props.getProperty('build')
|
|
def expectedBuild = System.getenv('GITHUB_RUN_NUMBER')
|
|
if (actualBuild != expectedBuild) {
|
|
throw new GradleException("Build number mismatch: build-info.properties has '${actualBuild}' but GITHUB_RUN_NUMBER is '${expectedBuild}'. Run 'make assemble' to fix.")
|
|
}
|
|
|
|
def actualCommit = props.getProperty('commitId')
|
|
def expectedCommit = System.getenv('GITHUB_SHA').take(9) // GitHub SHA is full hash, we use short form
|
|
if (actualCommit != expectedCommit) {
|
|
throw new GradleException("Commit ID mismatch: build-info.properties has '${actualCommit}' but GITHUB_SHA is '${expectedCommit}'. Run 'make assemble' to fix.")
|
|
}
|
|
|
|
println "✅ Build info validation passed: build=${actualBuild}, commitId=${actualCommit}"
|
|
println "✅ Plugin version validation passed: all ${expected.size()} plugin versions match"
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compile sources and copies all libs to target directory
|
|
*/
|
|
task compile {
|
|
dependsOn allprojects.classes
|
|
}
|
|
|
|
def getRuntimeConfigs() {
|
|
def names = subprojects
|
|
.findAll { prj -> prj.name in ['nextflow','nf-commons','nf-httpfs','nf-lang','nf-lineage'] }
|
|
.collect { it.name }
|
|
|
|
FileCollection result = null
|
|
for( def it : names ) {
|
|
def cfg = project(it).configurations.getByName('runtimeClasspath')
|
|
if( result==null )
|
|
result = cfg
|
|
else
|
|
result += cfg
|
|
// this include the module actual jar file
|
|
// note: migrating to gradle 7 does not work any more
|
|
//result = result + cfg.getOutgoing().getArtifacts().getFiles()
|
|
}
|
|
return result?.files ?: []
|
|
}
|
|
|
|
/*
|
|
* Save the runtime classpath
|
|
* NOTE: This task uses a provider to delay execution, but still triggers configuration
|
|
* resolution when the provider is evaluated. While not ideal for Gradle 9.1's strict
|
|
* configuration resolution requirements, this approach works in practice for our use case.
|
|
*/
|
|
task exportClasspath {
|
|
dependsOn allprojects.jar
|
|
|
|
// Use provider to delay configuration resolution until task execution
|
|
def configurationFiles = provider {
|
|
def libs = []
|
|
|
|
// Resolve configurations during provider evaluation (not ideal but functional)
|
|
['nextflow','nf-commons','nf-httpfs','nf-lang','nf-lineage'].each { moduleName ->
|
|
def moduleProject = project(":$moduleName")
|
|
def cfg = moduleProject.configurations.getByName('runtimeClasspath')
|
|
libs.addAll(cfg.files.collect { it.canonicalPath })
|
|
}
|
|
|
|
// Add module jars
|
|
['nextflow','nf-commons','nf-httpfs','nf-lang','nf-lineage'].each {
|
|
libs << file("modules/$it/build/libs/${it}-${version}.jar").canonicalPath
|
|
}
|
|
|
|
return libs.unique()
|
|
}
|
|
|
|
inputs.files(configurationFiles)
|
|
outputs.file('.launch.classpath')
|
|
|
|
doLast {
|
|
def libs = configurationFiles.get()
|
|
file('.launch.classpath').text = libs.join(':')
|
|
}
|
|
}
|
|
|
|
ext.nexusUsername = project.findProperty('nexusUsername') ?: System.getenv('AWS_ACCESS_KEY_ID')
|
|
ext.nexusPassword = project.findProperty('nexusPassword') ?: System.getenv('AWS_SECRET_ACCESS_KEY')
|
|
ext.nexusFullName = project.findProperty('nexusFullName')
|
|
ext.nexusEmail = project.findProperty('nexusEmail')
|
|
|
|
// `signing.keyId` property needs to be defined in the `gradle.properties` file
|
|
ext.enableSignArchives = project.findProperty('signing.keyId')
|
|
|
|
ext.coreProjects = projects( ':nextflow', ':nf-commons', ':nf-httpfs', ':nf-lang', ':nf-lineage' )
|
|
|
|
configure(coreProjects) {
|
|
group = 'io.nextflow'
|
|
version = rootProject.file('VERSION').text.trim()
|
|
}
|
|
|
|
/*
|
|
* Maven central deployment
|
|
* http://central.sonatype.org/pages/gradle.html
|
|
*/
|
|
configure(coreProjects) {
|
|
apply plugin: 'maven-publish'
|
|
apply plugin: 'signing'
|
|
|
|
task javadocJar(type: Jar) {
|
|
archiveClassifier = 'javadoc'
|
|
from configurations.groovyDoc
|
|
}
|
|
|
|
task sourcesJar(type: Jar) {
|
|
archiveClassifier = 'sources'
|
|
from sourceSets.main.allSource
|
|
}
|
|
|
|
publishing {
|
|
publications {
|
|
mavenJava(MavenPublication) {
|
|
suppressPomMetadataWarningsFor('testFixturesApiElements')
|
|
suppressPomMetadataWarningsFor('testFixturesRuntimeElements')
|
|
from components.java
|
|
versionMapping {
|
|
usage('java-api') {
|
|
fromResolutionOf('runtimeClasspath')
|
|
}
|
|
usage('java-runtime') {
|
|
fromResolutionResult()
|
|
}
|
|
}
|
|
pom {
|
|
name = 'Nextflow'
|
|
description = 'A DSL modelled around the UNIX pipe concept, that simplifies writing parallel and scalable pipelines in a portable manner'
|
|
url = 'http://www.nextflow.io'
|
|
licenses {
|
|
license {
|
|
name = 'The Apache License, Version 2.0'
|
|
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
|
}
|
|
}
|
|
developers {
|
|
developer {
|
|
id = nexusUsername
|
|
name = nexusFullName
|
|
email = nexusEmail
|
|
}
|
|
}
|
|
scm {
|
|
connection = 'scm:git:https://github.com/nextflow-io/nextflow'
|
|
developerConnection = 'scm:git:git@github.com:nextflow-io/nextflow.git'
|
|
url = 'https://github.com/nextflow-io/nextflow'
|
|
}
|
|
}
|
|
|
|
artifact sourcesJar
|
|
artifact javadocJar
|
|
}
|
|
}
|
|
|
|
repositories {
|
|
maven {
|
|
name = 'Seqera'
|
|
// change URLs to point to your repos, e.g. http://my.org/repo
|
|
def releasesRepoUrl = "s3://maven.seqera.io/releases/"
|
|
def snapshotsRepoUrl = "s3://maven.seqera.io/snapshots/"
|
|
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
|
|
credentials(AwsCredentials) {
|
|
accessKey nexusUsername
|
|
secretKey nexusPassword
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
signing {
|
|
required { enableSignArchives }
|
|
sign publishing.publications.mavenJava
|
|
}
|
|
|
|
}
|
|
|
|
|
|
String bytesToHex(byte[] bytes) {
|
|
StringBuffer result = new StringBuffer();
|
|
for (byte byt : bytes) result.append(Integer.toString((byt & 0xff) + 0x100, 16).substring(1));
|
|
return result.toString();
|
|
}
|
|
|
|
task makeDigest { doLast {
|
|
byte[] digest
|
|
String str = file('nextflow').text
|
|
// create sha1
|
|
digest = java.security.MessageDigest.getInstance("SHA1").digest(str.getBytes())
|
|
file('nextflow.sha1').text = new BigInteger(1, digest).toString(16) + '\n'
|
|
// create sha-256
|
|
digest = java.security.MessageDigest.getInstance("SHA-256").digest(str.getBytes())
|
|
file('nextflow.sha256').text = bytesToHex(digest) + '\n'
|
|
// create md5
|
|
digest = java.security.MessageDigest.getInstance("MD5").digest(str.getBytes())
|
|
file('nextflow.md5').text = bytesToHex(digest) + '\n'
|
|
}}
|
|
|
|
// Make releaseInfo task automatically run makeDigest after updating versions
|
|
releaseInfo.finalizedBy makeDigest
|
|
|
|
|
|
task upload {
|
|
dependsOn compile
|
|
dependsOn coreProjects.publish
|
|
dependsOn validatePluginVersions
|
|
}
|
|
|
|
|
|
if( System.env.BUILD_PACK ) {
|
|
apply from: 'packing.gradle'
|
|
}
|
|
|