| 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" |
| } |
| |
| |