blob: 3b6430734ab03710ff038f4cc168d4cb185a93d7 [file] [log] [blame]
import at.bxm.gradleplugins.svntools.api.SvnDepth
import at.bxm.gradleplugins.svntools.tasks.SvnAdd
import at.bxm.gradleplugins.svntools.tasks.SvnCheckout
import at.bxm.gradleplugins.svntools.tasks.SvnCommit
import at.bxm.gradleplugins.svntools.tasks.SvnDelete
import groovyx.net.http.HttpBuilder
import org.ajoberstar.grgit.Credentials
buildscript {
repositories {
jcenter()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.ajoberstar:grgit:${getProperty('version.grgit')}"
classpath "at.bxm.gradleplugins:gradle-svntools-plugin:${getProperty('version.svntools')}"
classpath "io.github.http-builder-ng:http-builder-ng-okhttp:${getProperty('version.httpbuilderng')}"
classpath "org.hidetake:gradle-ssh-plugin:${getProperty('version.sshplugin')}"
classpath "gradle.plugin.io.sdkman:gradle-sdkvendor-plugin:${getProperty('version.sdkmanplugin')}"
}
}
apply plugin: "at.bxm.svntools"
apply plugin: "org.hidetake.ssh"
apply plugin: "io.sdkman.vendors"
svntools {
username = apacheUser
password = apachePassword
}
ssh.settings {
dryRun = project.hasProperty('dryRun')
// TODO explore whether this can be made more secure - below not working on windows
// knownHosts = file(System.getProperty('user.home') + '/.ssh/known_hosts')
knownHosts = allowAnyHosts
}
remotes {
ciServer {
host = 'ci.groovy-lang.org'
user = findProperty('ciserver.user')
password = findProperty('ciserver.password')
//identity = file('id_rsa')
}
}
sdkman {
api = "https://vendors.sdkman.io/"
consumerKey = findProperty('gvm.consumerKey')
consumerToken = findProperty('gvm.consumerPassword')
candidate = "groovy"
version = "$relVersion"
url = "https://dl.bintray.com/groovy/maven/apache-groovy-sdk-${relVersion}.zip"
hashtag = "#groovylang"
}
task jiraCheckPhase2(dependsOn: assumesRelVersion) {
doLast {
def prefix = '/jira/rest/api/2'
def jira = HttpBuilder.configure {
request.uri = 'https://issues.apache.org'
request.auth.basic apacheUser, apachePassword
}
def resp = jira.get {
request.uri.path = "$prefix/project/GROOVY/versions"
}
def versionFields = resp.find { it.name == relVersion }
assert versionFields, "Version $relVersion not found in Jira!"
assert versionFields.released, "Version $relVersion not yet released!"
project.ext.versionId = versionFields.id
project.ext.projectId = versionFields.projectId
resp = jira.get {
request.uri.path = "$prefix/version/$versionId/unresolvedIssueCount"
}
if (resp.issuesUnresolvedCount) {
logger.warn "Warning found $resp.issuesUnresolvedCount unresolved issues for version $relVersion"
}
resp = jira.get {
request.uri.path = "$prefix/version/$versionId/relatedIssueCounts"
}
project.ext.fixCount = resp.issuesFixedCount
}
}
task promoteOnBintray(dependsOn: jiraCheckPhase2) {
group = "Post-passed phase"
description = "Releases the version on Bintray"
doLast {
def artifactory = HttpBuilder.configure {
request.uri = 'https://groovy.jfrog.io'
request.auth.basic artifactoryUser, artifactoryPassword
}
def resp = artifactory.get {
request.uri.path = '/groovy/api/build/groovy'
request.headers['Accept'] = 'application/json'
}
// sort to minimise work - normally we want the last started
def builds = resp.buildsNumbers.sort{ it.started }.reverse()
// check version - in case we are releasing multiple builds at the one time
println "----------------"
def build = builds.find { b ->
resp = artifactory.get {
request.uri.path = "/groovy/api/build/groovy/$b.uri"
request.headers['Accept'] = 'application/json'
}
def prefix = apacheGroupId ? 'org.apache.groovy:groovy:' : 'org.codehaus.groovy:groovy:'
def coreModuleId = resp.buildInfo.modules*.id.find{ it.startsWith(prefix) }
def found = coreModuleId?.endsWith(':' + relVersion)
println "${found ? 'FOUND' : 'SKIPPING'} $coreModuleId @ ${b.uri}"
found
}
println "----------------"
assert build, "No build matching $relVersion found in artifactory"
def buildNum = resp.buildInfo.number
def body = /{
"dryRun" : ${project.hasProperty('dryRun').toString()},
"publish" : true,
"async" : false,
"targetRepo" : "${apacheGroupId ? 'distribution-repo-apache' : 'distribution-repo'}",
"sourceRepos" : ["libs-release-local"]
}/
if (project.hasProperty('dryRun')) println "path: api/build/distribute/groovy/$buildNum\nbody: $body"
resp = artifactory.post{
request.uri.path = "/groovy/api/build/distribute/groovy/$buildNum"
request.body = body
request.contentType = 'application/json'
}
if (project.hasProperty('dryRun')) println resp
}
}
task augmentProperties(dependsOn: jiraCheckPhase2) {
group = "Post-passed phase"
description = "Display info about build"
doLast {
def artifactory = HttpBuilder.configure {
request.uri = 'https://groovy.jfrog.io'
request.auth.basic artifactoryUser, artifactoryPassword
}
def resp = artifactory.get {
request.uri.path = '/groovy/api/build/groovy'
request.headers['Accept'] = 'application/json'
}
// sort to minimise work - normally we want the last started
def builds = resp.buildsNumbers.sort{ it.started }.reverse()
// check version - in case we are releasing multiple builds at the one time
println "----------------"
def build = builds.find { b ->
resp = artifactory.get {
request.uri.path = "/groovy/api/build/groovy/$b.uri"
request.headers['Accept'] = 'application/json'
}
def prefix = apacheGroupId ? 'org.apache.groovy:groovy:' : 'org.codehaus.groovy:groovy:'
def coreModuleId = resp.buildInfo.modules*.id.find{ it.startsWith(prefix) }
def found = coreModuleId?.endsWith(':' + relVersion)
println "${found ? 'FOUND' : 'SKIPPING'} $coreModuleId @ ${b.uri}"
found
}
println "----------------"
assert build, "No build matching $relVersion found in artifactory"
def buildNum = resp.buildInfo.number
def buildName = resp.buildInfo.name
def prefix = '/groovy/api/storage/libs-release-local'
def suffix = "build.name=$buildName;build.number=$buildNum&recursive=1"
resp.buildInfo.modules*.id.each { module ->
println "Augmenting properties for module $module"
def pieces = module.split(':')
def middle = "${pieces[0].replace('.', '/')}/${pieces[1]}/${pieces[2]}"
artifactory.put {
request.uri.path = "$prefix/$middle"
request.uri.query = [properties: suffix, recursive: '1']
response.failure { fs ->
println "request failed: $fs.statusCode : $fs.message"
}
}
}
}
}
augmentProperties.onlyIf{ apacheGroupId }
task waitForBintrayPublication(dependsOn: [promoteOnBintray]) {
group = "Post-passed phase"
description = "Polls the Bintray website to check if it is released"
doLast {
def found = false
def delay = 30000 // 1/2 a minute
def numTries = 60 // wait for up to 30 mins
def bintray = HttpBuilder.configure {
request.uri = 'https://dl.bintray.com/'
}
while (!found && numTries-- > 0) {
found = true
bintray.head {
request.uri.path = "/groovy/maven/apache-groovy-sdk-${relVersion}.zip"
response.failure { fs ->
sleep delay
found = false
}
}
}
assert found, 'Timed out waiting for bintray publish/sync - please check manually'
}
}
sdkReleaseVersion.dependsOn waitForBintrayPublication
task synchronizeWithMavenCentral(dependsOn: promoteOnBintray) {
group = "Post-passed phase"
description = "Syncs with Maven Central/Sonatype"
doLast {
println """
Synchronizing with Maven central. This may take a few minutes ...
If this fails, log on to https://oss.sonatype.org/ using the centralUser credentials
and progress through process manually -> Close -> Publish ... under staging repositories
"""
def bintray = HttpBuilder.configure {
request.uri = 'https://dl.bintray.com/'
request.headers['Authorization'] = 'Basic ' + "$bintrayUser:$bintrayKey".getBytes('iso-8859-1').encodeBase64()
}
def body = /{
"username" : "${project.findProperty('centralUser')}",
"password" : "${project.findProperty('centralKey')}"
}/
bintray.post {
request.uri.path = "/maven_central_sync/groovy/maven/groovy/versions/$relVersion"
request.body = body
request.contentType = 'application/json'
}
}
}
synchronizeWithMavenCentral.onlyIf{ !apacheGroupId }
task publishZipsOnBintray(dependsOn: [jiraCheckPhase2, assumesRelVersion]) {
group = "Post-passed phase"
description = "Publish distribution zips to bintray"
doLast {
def bintray = HttpBuilder.configure {
request.uri = 'https://api.bintray.com'
request.headers['Authorization'] = 'Basic ' + "$bintrayUser:$bintrayKey".getBytes('iso-8859-1').encodeBase64()
}
fileTree("$distParentDir/distributions").files.each { File f ->
println "Uploading $f.name"
bintray.put {
request.uri.path = "/content/groovy/maven/groovy/$relVersion/${f.name}"
request.body = f.bytes
request.contentType = 'application/octet-stream'
}
}
println "Zips uploaded! You may need to release manually."
// TODO automate release
}
}
task cleanSvnReleaseWorkspace(type: Delete, dependsOn: assumesRelVersion) {
delete releaseWorkspaceRoot
}
task prepareSvnReleaseWorkspace(type: SvnCheckout, dependsOn: cleanSvnReleaseWorkspace) {
svnUrl = "https://dist.apache.org/repos/dist/release/groovy"
workspaceDir = releaseWorkspaceRoot
depth = SvnDepth.FILES // slightly more efficient if we have two concurrent releases (e.g. 2.4.latest, 2.5.0)
}
task copyReleaseArtifacts(type: Copy, dependsOn: prepareSvnReleaseWorkspace) {
description = "Copies all files from DEV to RELEASE"
from(devWorkspace)
into releaseWorkspace
}
task addSvnReleaseFiles(type: SvnAdd, dependsOn: copyReleaseArtifacts) {
description = "Adds the changed files to dist svn"
add releaseWorkspace
recursive true
}
task deleteSvnDevFiles(type: SvnDelete, dependsOn: addSvnReleaseFiles) {
description = "Deletes the changed files to svn"
delete devWorkspace
}
task commitDeleteFromDevSvn(type: SvnCommit, dependsOn: deleteSvnDevFiles) {
description = "Deletes the version from the DEV staging area"
source << devWorkspaceRoot
recursive = true
commitMessage = "Deleting version $relVersion from the DEV staging area"
}
task commitAddToReleaseSvn(type: SvnCommit, dependsOn: addSvnReleaseFiles) {
source << releaseWorkspace
recursive = true
commitMessage = "Releasing version $relVersion"
}
task uploadToApacheReleaseServer(dependsOn: [commitDeleteFromDevSvn, commitAddToReleaseSvn]) {
// svntools has no move so add and delete explicitly
group = "Post-passed phase"
description = "Moves the artifacts from the DEV svn repo to the RELEASE svn repo"
doLast {
println """
Once the release has been announced and mirrors have the latest artifacts, please remove old versions manually
from ASF svn servers - they will remain automatically on the archive servers.
"""
}
}
task uploadDocumentationToGroovyWebsite() {
group = "Post-passed phase"
description = "Uploads the documentation to the Groovy website server"
doLast {
ssh.run {
session(remotes.ciServer) {
execute 'uname -a'
put from: "$distParentDir/distributions/apache-groovy-docs-${relVersion}.zip", into: '/var/www/docs/docs'
execute "rm -rf /var/www/docs/docs/groovy-${relVersion}/"
execute "unzip -d /var/www/docs/docs/ /var/www/docs/docs/apache-groovy-docs-${relVersion}.zip"
execute "chmod 664 /var/www/docs/docs/apache-groovy-docs-${relVersion}.zip"
// execute "chgrp -R teamcity /var/www/docs/docs/groovy-${relVersion}/"
// execute "chown -R teamcity /var/www/docs/docs/groovy-${relVersion}/"
execute "rm /var/www/docs/docs/apache-groovy-docs-${relVersion}.zip"
}
}
}
}
task checkoutGroovyWebsite(dependsOn: uploadDocumentationToGroovyWebsite) {
group = "Post-passed phase"
description = "Checks out the Groovy website repository"
doLast {
if (!project.hasProperty('skipClone')) {
println "Cloning $websiteRepo to $stagingWebsiteDir. This may take a few minutes ..."
grgitClass.clone(dir: stagingWebsiteDir, uri: websiteRepo)
}
}
}
task findGroovyVersions(dependsOn: checkoutGroovyWebsite) {
doLast {
def sitemapFile = file("$stagingWebsiteDir/site/src/site/sitemap-user.groovy")
def matcher = sitemapFile.text =~ /(?ism).*groovyDocumentationVersions\(([^)]*)\).*/
assert matcher[0]
rootProject.ext.versionsText = matcher[0][1]
def majorMinor = { String s -> s.split(/\./).with{ it[0].toInteger() * 100 + it[1].toInteger() } }
def versions = Eval.me(versionsText)
def relMajorMinor = majorMinor(relVersion)
def foundNewer = versions.findAll{ !(it.contains('alpha') || it.contains('beta') || it.contains('rc')) }.collect{ majorMinor(it) }.any{ it > relMajorMinor }
rootProject.ext.newDefault = project.hasProperty('forceDefault') ||
(!project.hasProperty('skipDefault') && stableBuild && !foundNewer)
}
}
task maybeUpdateDocumentationSymlink(dependsOn: findGroovyVersions) {
group = "Post-passed phase"
description = "Changes the symlink to the latest documentation if and only if it's a stable release"
doLast {
// TODO work out unix group permissions - currently might require manual chown/chgrp fix ups
ssh.run {
session(remotes.ciServer) {
execute 'uname -a'
execute "cd /var/www/docs/docs; ln -s -f -T groovy-$relVersion latest; ln -s -f -T groovy-$relVersion groovy-latest"
// execute "chgrp -h teamcity /var/www/docs/docs/latest"
// execute "chown -h teamcity /var/www/docs/docs/latest"
}
}
}
}
maybeUpdateDocumentationSymlink.onlyIf{ newDefault }
task updateGroovySitemap(dependsOn: findGroovyVersions) {
group = "Post-passed phase"
description = "Updates sitemap-user.groovy to include the newly released version and commits the result"
doLast {
def sitemapFile = file("$stagingWebsiteDir/site/src/site/sitemap-user.groovy")
def sitemapText = sitemapFile.text
def newText = newRelease ? versionsText.replaceFirst(/(?sm)'\s*\]/, "',\n '$relVersion'\n \\]")
: versionsText.replaceFirst(/(?sm)(.*)('$baseVersion'[',. 0-9]*)/, "\$1\$2 '$relVersion',")
sitemapText = sitemapText.replace(versionsText, newText)
// TODO add download distributions section ...
sitemapFile.text = sitemapText
}
}
updateGroovySitemap.onlyIf{ releaseBuild }
task pushGroovyWebsite(dependsOn: updateGroovySitemap) {
group = "Post-passed phase"
description = "Pushes the Groovy website so that the new website is published"
doLast {
// def githubCredentials = new Credentials(username: githubUser, password: githubPassword)
def githubCredentials = new Credentials(githubUser, githubPassword)
def grgit = grgitClass.open(dir: stagingWebsiteDir, creds: githubCredentials)
grgit.add(patterns: ['sitemap.groovy'])
def commit = grgit.commit(message: "Release $relVersion: update sitemap")
println "@ $commit.abbreviatedId ($commit.shortMessage)"
grgit.push()
}
}
pushGroovyWebsite.onlyIf{ releaseBuild }
task waitForWebsitePublication(dependsOn: pushGroovyWebsite) {
group = "Post-passed phase"
description = "Polls the Groovy website to check if the changelog for this version is published"
doLast {
def found = false
def delay = 30000 // 1/2 a minute
def numTries = 60 // wait for up to 30 mins
def usersite = HttpBuilder.configure {
request.uri = 'https://groovy-lang.org'
}
while (!found && numTries-- > 0) {
found = true
usersite.head {
request.uri.path = "/changelogs/changelog-${relVersion}.html"
response.failure { fs ->
sleep delay
found = false
}
}
}
assert found, 'Timed out waiting for website to be published - please check manually'
}
}
waitForWebsitePublication.onlyIf{ releaseBuild }
task publishToSDKman(dependsOn: [synchronizeWithMavenCentral, publishZipsOnBintray, uploadToApacheReleaseServer, waitForWebsitePublication, maybeUpdateDocumentationSymlink, sdkReleaseVersion]) {
group = "Post-passed phase"
description = "Publishes the release on SDKman"
}
sdkDefaultVersion.dependsOn findGroovyVersions
task makeDefaultOnSDKman(dependsOn: [publishToSDKman, sdkDefaultVersion]) {
group = "Post-passed phase"
description = "Make it the default version on SDKman"
}
makeDefaultOnSDKman.onlyIf{ rootProject.ext.newDefault }
sdkDefaultVersion.onlyIf{ rootProject.ext.newDefault }
task createNextVersionInJira(dependsOn: [jiraCheckPhase2]) {
group = "Post-passed phase"
description = "Make sure that Jira is ready for the next version on this branch"
doLast {
def prefix = '/jira/rest/api/2'
def jira = HttpBuilder.configure {
request.uri = 'https://issues.apache.org'
request.auth.basic apacheUser, apachePassword
}
def resp = jira.get {
request.uri.path = "$prefix/project/GROOVY/versions"
}
def versionFields = resp.find { it.name == nextVersion }
if (versionFields) {
println "Version $nextVersion already found in Jira!"
} else {
jira.post {
request.uri.path = "$prefix/version"
request.body = /{ "name": "$nextVersion", "project": "GROOVY", "projectId": $projectId }/
request.contentType = 'application/json'
}
}
}
}
task bumpVersionInGit(dependsOn: [findGroovyVersions]) {
group = "Post-passed phase"
description = "The version in the gradle.properties file in the branch repo should be bumped"
doLast {
// def apacheCredentials = new Credentials(username: apacheUser, password: apachePassword)
def apacheCredentials = new Credentials(apacheUser, apachePassword)
def grgit = grgitClass.open(dir: stagingDir, creds: apacheCredentials)
grgit.checkout(branch: branch)
def propsFile = file("$stagingDir/gradle.properties")
def propsText = propsFile.text
propsText = propsText.replace(numVersion + '-SNAPSHOT', nextVersion + '-SNAPSHOT')
propsText = propsText.replace(numVersion + '.SNAPSHOT', nextVersion + '.SNAPSHOT')
propsFile.text = propsText
grgit.add(patterns: ['gradle.properties'])
def commit = grgit.commit(message: "Bump version on $branch branch")
println "@ $commit.abbreviatedId ($commit.shortMessage)"
grgit.push()
}
}
bumpVersionInGit.onlyIf{ stableBuild }
task gitTasks(dependsOn: [bumpVersionInGit, jiraCheckPhase2]) { }
task jiraTasks(dependsOn: [findGroovyVersions, createNextVersionInJira]) { }
task sdkmanTasks(dependsOn: [jiraCheckPhase2, findGroovyVersions, makeDefaultOnSDKman]) {}
task proposeAnnouncementEmail(dependsOn: [gitTasks, jiraTasks, sdkmanTasks, findGroovyVersions]) {
group = "Post-passed phase"
description = "Generates an [ANNOUNCE] thread to be tweaked and sent to the dev@, user@ and announce@ mailing lists"
doLast {
def securityFix = project.hasProperty('securityFix')
println """"
Below is a template email to tweak and send to the normaly mailing lists including
dev@groovy.apache.org, users@groovy.apache.org and announce@apache.org mailing lists
as an [ANNOUNCE] thread. This should be sent using an Apache email from address.
---------------- >8 -----------------
Dear community,
The Apache Groovy team is pleased to announce version $relVersion of Apache Groovy.
Apache Groovy is a multi-faceted programming language for the JVM.
Further details can be found at the https://groovy.apache.org website.
${ stableBuild ?
(newRelease ?
'''We are sure you'll enjoy the features in this new version of Groovy.
Your feedback on any unintentional glitches is welcome.'''
: """This release is a maintenance release of the $branch branch.
It is strongly encouraged that all users using prior
versions on this branch upgrade to this version.""")
: '''This is a pre-release of a new version of Groovy.
We greatly appreciate any feedback you can give us when using this version.'''
}
${securityFix ? '''
This release contains critical security fixes.
Details can be found on https://groovy-lang.org/security.html
''' : '' }
This release includes $fixCount bug fixes/improvements as outlined in the changelog:
https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=$projectId&version=$versionId
Sources, convenience binaries, downloadable documentation and an SDK
bundle can be found at: https://groovy.apache.org/download.html
We recommend you verify your installation using the information on that page.
Jars are also available within the major binary repositories.
We welcome your help and feedback and in particular want
to thank everyone who contributed to this release.
For more information on how to report problems, and to get involved,
visit the project website at https://groovy.apache.org/
Best regards,
The Apache Groovy team.
"""
}
}
task promptForReleaseUpdater(dependsOn: proposeAnnouncementEmail) {
group = "Post-passed phase"
description = "Prompts the release manager to update the Apache Release info system"
doLast {
println '''
If you are a PMC member of this project, we ask that you log on to:
https://reporter.apache.org/addrelease.html?groovy
and add your release data (version and date) to the database.
If you are not a PMC member, please have a PMC member add this information.
'''
}
}
task announceReleaseOnSDKman(dependsOn: [promptForReleaseUpdater, sdkAnnounceVersion]) {
group = "Post-passed phase"
description = "Announces the release on SDKman"
}