From 9614bb3873abbeb2e794be04a00fde49fc1bc3d3 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 16 Sep 2024 14:53:15 +0200 Subject: [PATCH] [#1956] Update release tasks They are now compatible with the release scripts in https://github.com/hibernate/hibernate-release-scripts: * Calling `prepare-release.sh` won't upload any change upstream * They are compatible with the way ORM works --- build.gradle | 2 +- ci/release/Jenkinsfile | 33 ++++ documentation/build.gradle | 67 ++++---- release/build.gradle | 331 ++++++++++++++++++++++++++----------- 4 files changed, 306 insertions(+), 127 deletions(-) diff --git a/build.gradle b/build.gradle index 43cd640fd..84dbe9b9d 100644 --- a/build.gradle +++ b/build.gradle @@ -203,7 +203,7 @@ subprojects { private static String readVersionFromProperties(File file) { if ( !file.exists() ) { - throw new GradleException( "Version file $file.canonicalPath does not exists" ) + throw new FileNotFoundException( "Version file $file.canonicalPath does not exists" ) } Properties versionProperties = new Properties() file.withInputStream { diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 91469eb77..5da312823 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -156,6 +156,39 @@ pipeline { } } } + stage('Release prepare') { + steps { + script { + checkoutReleaseScripts() + + configFileProvider([ + configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), + configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") + ]) { + withCredentials([ + usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USER'), + usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'PLUGIN_PORTAL_PASSWORD', usernameVariable: 'PLUGIN_PORTAL_USERNAME'), + file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), + string(credentialsId: 'release.gpg.passphrase', variable: 'RELEASE_GPG_PASSPHRASE') + ]) { + sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { + // set release version + // update changelog from JIRA + // tags the version + // changes the version to the provided development version + withEnv([ + "BRANCH=${env.GIT_BRANCH}", + // Increase the amount of memory for this part since asciidoctor doc rendering consumes a lot of metaspace + "GRADLE_OPTS=-Dorg.gradle.jvmargs='-Dlog4j2.disableJmx -Xmx4g -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8'" + ]) { + sh ".release/scripts/prepare-release.sh ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}" + } + } + } + } + } + } + } stage('Publish release') { steps { script { diff --git a/documentation/build.gradle b/documentation/build.gradle index 94e42c62c..1340e0517 100644 --- a/documentation/build.gradle +++ b/documentation/build.gradle @@ -1,7 +1,7 @@ -import java.time.Year - import org.asciidoctor.gradle.jvm.AsciidoctorTask +import java.time.Year + apply plugin: 'org.asciidoctor.jvm.convert' ext { @@ -22,21 +22,21 @@ rootProject.subprojects { subproject -> // Aggregated JavaDoc // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -final File javadocDir = mkdir( new File( (File) project.buildDir, 'javadocs' ) ) +final File javadocDir = mkdir( project.layout.buildDirectory.dir( "javadocs" ) ) /** * Builds the JavaDocs aggregated (unified) across all the sub-projects */ -task aggregateJavadocs(type: Javadoc, group: 'Documentation') { - description = 'Builds the aggregated (unified) JavaDocs across all sub-projects' +def aggregateJavadocsTask = tasks.register( 'aggregateJavadocs', Javadoc ) { + description = 'Builds the aggregated (unified) JavaDocs across all sub-projects' final int inceptionYear = 2020 - final int currentYear = Year.now().getValue() + final int currentYear = Year.now().getValue() - // exclude any generated sources and internal packages - exclude( '**/generated-src/**' ) + // exclude any generated sources and internal packages + exclude( '**/generated-src/**' ) exclude( '**/src/main/generated/**' ) - exclude( '**/internal/**' ) + exclude( '**/internal/**' ) exclude( '**/impl/**' ) // apply standard config @@ -50,6 +50,8 @@ task aggregateJavadocs(type: Javadoc, group: 'Documentation') { use = true options.encoding = 'UTF-8' + logger.lifecycle "---- Aggregating javadoc with version $project.version" + def matcher = hibernateOrmVersion =~ /\d+\.\d+/ def ormMinorVersion = matcher.find() ? matcher.group() : "5.6"; @@ -63,8 +65,10 @@ task aggregateJavadocs(type: Javadoc, group: 'Documentation') { options.addStringOption( 'Xdoclint:none', '-quiet' ) if ( gradle.ext.javaToolchainEnabled ) { - options.setJFlags( getProperty( 'toolchain.javadoc.jvmargs' ).toString(). - split( ' ' ).toList().findAll( { !it.isEmpty() } ) ) + options.setJFlags( + getProperty( 'toolchain.javadoc.jvmargs' ).toString(). + split( ' ' ).toList().findAll( { !it.isEmpty() } ) + ) } } @@ -75,18 +79,18 @@ task aggregateJavadocs(type: Javadoc, group: 'Documentation') { } } - // process each project, building up: - // 1) appropriate sources - // 2) classpath - parent.subprojects.each { Project subProject-> - // skip certain sub-projects - if ( ! project.projectsToSkipWhenAggregatingJavadocs.contains( subProject.name ) ) { + // process each project, building up: + // 1) appropriate sources + // 2) classpath + parent.subprojects.each { Project subProject -> + // skip certain sub-projects + if ( !project.projectsToSkipWhenAggregatingJavadocs.contains( subProject.name ) ) { // we only care about the main SourceSet... source subProject.sourceSets.main.java classpath += subProject.sourceSets.main.output + subProject.sourceSets.main.compileClasspath } - } + } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -98,22 +102,25 @@ asciidoctor { enabled = false } -task renderReferenceDocumentation(type: AsciidoctorTask, group: 'Documentation') { - description = 'Renders the Reference Documentation in HTML format using Asciidoctor.' - sourceDir = file( 'src/main/asciidoc/reference' ) - sources { - include 'index.adoc' - } +def renderReferenceDocumentationTask = tasks.register( 'renderReferenceDocumentation', AsciidoctorTask ) { + description = 'Renders the Reference Documentation in HTML format using Asciidoctor.' + sourceDir = file( 'src/main/asciidoc/reference' ) + sources { + include 'index.adoc' + } resources { - from(sourceDir) { + from( sourceDir ) { include 'images/**' include 'css/**' } } - outputDir = new File("$buildDir/asciidoc/reference/html_single") - options logDocuments: true + outputDir = project.layout.buildDirectory.dir( "asciidoc/reference/html_single" ).get().asFile + options logDocuments: true + + logger.lifecycle "---- Rendering docs with version $project.version" + attributes icons: 'font', 'source-highlighter': 'rouge', experimental: true, @@ -121,18 +128,18 @@ task renderReferenceDocumentation(type: AsciidoctorTask, group: 'Documentation') majorMinorVersion: project.version.family, fullVersion: project.version.toString(), docinfo: 'private' - } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // All // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -task assembleDocumentation(dependsOn: [aggregateJavadocs, renderReferenceDocumentation]) { +def assembleDocumentationTask = tasks.register( 'assembleDocumentation' ) { + dependsOn aggregateJavadocsTask, renderReferenceDocumentationTask group 'Documentation' description 'Grouping task for performing all documentation building tasks' logger.lifecycle "Documentation groupId: '" + project.group + "', version: '" + project.version + "'" } -assemble.dependsOn assembleDocumentation +assemble.dependsOn assembleDocumentationTask diff --git a/release/build.gradle b/release/build.gradle index 81f98cf88..41b114be1 100644 --- a/release/build.gradle +++ b/release/build.gradle @@ -3,7 +3,7 @@ import java.nio.charset.StandardCharsets ext { // Select which repository to use for publishing the documentation // Example: - // ./gradlew publishDocumentation \ + // ./gradlew uploadDocumentation \ // -PdocPublishRepoUri="git@github.com:DavideD/hibernate.org.git" \ // -PdocPublishBranch="staging" if ( !project.hasProperty('docPublishRepoUri') ) { @@ -22,46 +22,99 @@ description = 'Release module' // To publish the documentation: // 1. Add the relevant SSH key to your SSH agent. // 2. Execute this: -// ./gradlew publishDocumentation -PdocPublishBranch=production +// ./gradlew uploadDocumentation -PdocPublishBranch=production // To tag a version and trigger a release on CI (which will include publishing to Bintray and publishing documentation): // ./gradlew ciRelease -PreleaseVersion=x.y.z.Final -PdevelopmentVersion=x.y.z-SNAPSHOT -PgitRemote=origin -PgitBranch=main // The folder containing the rendered documentation -final String documentationDir = rootProject.layout.buildDirectory.dir( "documentation" ) +final Directory documentationDir = project(":documentation").layout.buildDirectory.get() // Relative path on the static website where the documentation is located final String docWebsiteRelativePath = "reactive/documentation/${projectVersion.family}" // The location of the docs when the website has been cloned -final String docWebsiteReactiveFolder = project.layout.buildDirectory.dir( "docs-website/${docWebsiteRelativePath}" ) +final Directory docWebsiteReactiveFolder = project.layout.buildDirectory.dir( "docs-website/${docWebsiteRelativePath}" ).get() + +def releaseChecksTask = tasks.register( "releaseChecks" ) { + description 'Checks and preparation for release' + group 'Release' + + doFirst { + logger.lifecycle("Checking that the working tree is clean...") + String uncommittedFiles = executeGitCommand('status', '--porcelain') + if (!uncommittedFiles.isEmpty()) { + throw new GradleException( + "Cannot release because there are uncommitted or untracked files in the working tree.\n" + + "Commit or stash your changes first.\n" + + "Uncommitted files:\n " + + uncommittedFiles + ) + } + + String gitBranchLocal = project.hasProperty( 'gitBranch' ) && !project.property( 'gitBranch' ).isEmpty() + ? project.property( 'gitBranch' ) + : executeGitCommand( 'branch', '--show-current' ).trim() + + String gitRemoteLocal + if ( project.hasProperty( 'gitRemote' ) && !project.property( 'gitRemote' ).isEmpty() ) { + gitRemoteLocal = project.property( 'gitRemote' ) + } + else { + final String remotes = executeGitCommand( 'remote', 'show' ).trim() + final List tokens = remotes.tokenize() + if ( tokens.size() != 1 ) { + throw new GradleException( "Could not determine `gitRemote` property for `releaseChecks` tasks." ) + } + gitRemoteLocal = tokens.get( 0 ) + } + + project.ext { + gitBranch = gitBranchLocal + gitRemote = gitRemoteLocal + } + + logger.lifecycle( "Switching to branch '${project.gitBranch}'..." ) + executeGitCommand( 'checkout', project.gitBranch ) + + logger.lifecycle( "Checking that all commits are pushed..." ) + String diffWithUpstream = executeGitCommand( 'diff', '@{u}' ) + if ( !diffWithUpstream.isEmpty() ) { + throw new GradleException( + "Cannot perform `ciRelease` tasks because there are un-pushed local commits .\n" + + "Push your commits first." + ) + } + } +} /** * Assembles all documentation into the {buildDir}/documentation directory. */ -tasks.register( 'assembleDocumentation' ) { +def assembleDocumentationTask = tasks.register( 'assembleDocumentation' ) { dependsOn ':documentation:assemble' group 'Documentation' description 'Render the documentation' } -assemble.dependsOn assembleDocumentation +assemble.dependsOn assembleDocumentationTask /** * Clone the website */ -tasks.register( 'removeDocsWebsite', Delete ) { +def removeDocsWebsiteTask = tasks.register( 'removeDocsWebsite', Delete ) { delete project.layout.buildDirectory.dir( "docs-website" ) } -// Depending on compileJava makes sure that the buildDir exists. Otherwise this task will fail. -tasks.register( 'cloneDocsWebsite', Exec ) { - dependsOn removeDocsWebsite, compileJava +def cloneDocsWebsiteTask = tasks.register( 'cloneDocsWebsite', Exec ) { + dependsOn removeDocsWebsiteTask + // Assure that the buildDir exists. Otherwise this task will fail. + dependsOn compileJava workingDir project.layout.buildDirectory commandLine 'git', 'clone', docPublishRepoUri, '-b', docPublishBranch, '--sparse', '--depth', '1', 'docs-website' } -tasks.register( 'sparseCheckoutDocumentation', Exec ) { - dependsOn cloneDocsWebsite +def sparseCheckoutDocumentationTask = tasks.register( 'sparseCheckoutDocumentation', Exec ) { + dependsOn cloneDocsWebsiteTask workingDir project.layout.buildDirectory.dir( "docs-website" ) commandLine 'git', 'sparse-checkout', 'set', docWebsiteRelativePath } @@ -69,9 +122,22 @@ tasks.register( 'sparseCheckoutDocumentation', Exec ) { /** * Update the docs on the cloned website */ -tasks.register( 'updateDocumentation' ) { - dependsOn assembleDocumentation, sparseCheckoutDocumentation - description = "Update the documentation on the cloned static website" +def changeToReleaseVersionTask = tasks.register( 'changeToReleaseVersion' ) { + description 'Updates `gradle/version.properties` file to the specified release-version' + group 'Release' + + dependsOn releaseChecksTask + + doFirst { + logger.lifecycle( "Updating version-file to release-version : `${project.releaseVersion}`" ) + updateVersionFile( "${project.releaseVersion}" ) + } +} + +def updateDocumentationTask = tasks.register( 'updateDocumentation' ) { + description "Update the documentation on the cloned static website" + dependsOn assembleDocumentationTask, sparseCheckoutDocumentationTask + mustRunAfter changeToReleaseVersion // copy documentation outputs into target/documentation: // * this is used in building the dist bundles @@ -79,134 +145,149 @@ tasks.register( 'updateDocumentation' ) { doLast { // delete the folders in case some files have been removed - delete "${docWebsiteReactiveFolder}/javadocs", "${docWebsiteReactiveFolder}/reference" + delete docWebsiteReactiveFolder.dir("javadocs"), docWebsiteReactiveFolder.dir("reference") // Aggregated JavaDoc copy { - from "${documentationDir}/javadocs" - into "${docWebsiteReactiveFolder}/javadocs" + from documentationDir.dir("javadocs") + into docWebsiteReactiveFolder.dir("javadocs") } // Reference Documentation copy { - from "${documentationDir}/asciidoc/reference/html_single" - into "${docWebsiteReactiveFolder}/reference/html_single" + from documentationDir.dir("asciidoc/reference/html_single") + into docWebsiteReactiveFolder.dir("reference/html_single") } } } -/** -* Push documentation changes on the remote repository -*/ -tasks.register( 'stageDocChanges', Exec ) { - dependsOn updateDocumentation +def stageDocChangesTask = tasks.register( 'stageDocChanges', Exec ) { workingDir project.layout.buildDirectory.dir( "docs-website" ) commandLine 'git', 'add', '-A', '.' } -tasks.register( 'commitDocChanges', Exec ) { - dependsOn stageDocChanges - workingDir "${project.buildDir}/docs-website" +def commitDocChangesTask = tasks.register( 'commitDocChanges', Exec ) { + dependsOn stageDocChangesTask + workingDir project.layout.buildDirectory.dir( "docs-website" ) commandLine 'git', 'commit', '-m', "[HR] Hibernate Reactive documentation for ${projectVersion}" } -tasks.register( 'pushDocChanges', Exec ) { - dependsOn commitDocChanges +def pushDocChangesTask = tasks.register( 'pushDocChanges', Exec ) { + description "Push documentation changes on the remote repository" + dependsOn commitDocChangesTask workingDir project.layout.buildDirectory.dir( "docs-website" ) commandLine 'git', 'push', '--atomic', 'origin', docPublishBranch } -tasks.register( 'publishDocumentation' ) { - dependsOn pushDocChanges - group = "Release" - description = "Upload documentation on the website" +def uploadDocumentationTask = tasks.register( 'uploadDocumentation' ) { + description "Upload documentation on the website" + group "Release" + dependsOn pushDocChangesTask doLast { logger.lifecycle "Documentation published on '${docPublishRepoUri}' branch '${docPublishBranch}'" } } -tasks.register( "releasePrepare" ) { - group = "Release" - description = "Performs release preparations on local check-out, including updating changelog" +def gitPreparationForReleaseTask = tasks.register( 'gitPreparationForRelease' ) { + dependsOn releaseChecksTask, changeToReleaseVersionTask + finalizedBy updateDocumentationTask + + doLast { + logger.lifecycle( "Performing pre-steps Git commit : `${project.releaseVersion}`" ) + executeGitCommand( 'add', '.' ) + executeGitCommand( 'commit', '-m', "Update project version to : `${project.releaseVersion}`" ) + } +} + +def changeToDevelopmentVersionTask = tasks.register( 'changeToDevelopmentVersion' ) { + description 'Updates `gradle/version.properties` file to the specified development-version' + group 'Release' + + dependsOn releaseChecksTask + doFirst { - if ( !project.hasProperty( 'releaseVersion' ) || !project.hasProperty( 'developmentVersion' ) - || !project.hasProperty( 'gitRemote' ) || !project.hasProperty( 'gitBranch' ) ) { - throw new GradleException( - "Task 'releasePrepare' requires all of the following properties to be set:" - + "'releaseVersion', 'developmentVersion', 'gitRemote' and 'gitBranch'." - ) - } + logger.lifecycle( "Updating version-file to development-version : `${project.developmentVersion}`" ) + updateVersionFile( "${project.developmentVersion}" ) } +} - doLast { - logger.lifecycle( "Switching to branch '${project.gitBranch}'..." ) - executeGitCommand( 'switch', project.gitBranch ) +def releasePreparePostGitTask = tasks.register( 'gitTasksAfterRelease' ) { + dependsOn changeToDevelopmentVersionTask - logger.lifecycle( "Checking that all commits are pushed..." ) - String diffWithUpstream = executeGitCommand( 'diff', '@{u}' ) - if ( !diffWithUpstream.isEmpty() ) { - throw new GradleException( - "Cannot release because there are commits on the branch to release that haven't been pushed yet." - + "\nPush your commits to the branch to release first." - ) + doLast { + if ( project.createTag ) { + logger.lifecycle( "Tagging release : `${project.releaseTag}`..." ) + executeGitCommand( 'tag', '-a', project.releaseTag, '-m', "Release $project.projectVersion" ) } - logger.lifecycle( "Adding commit to update version to '${project.releaseVersion}'..." ) - project.projectVersionFile.text = "projectVersion=${project.releaseVersion}" + logger.lifecycle( "Performing post-steps Git commit : `${project.releaseVersion}`" ) executeGitCommand( 'add', '.' ) - executeGitCommand( 'commit', '-m', project.releaseVersion ) + executeGitCommand( 'commit', '-m', "Update project version to : `${project.developmentVersion}`" ) } } -/* -* Release everything -*/ -tasks.register( 'ciRelease' ) { - group = "Release" - description = "Triggers the release on CI: creates commits to change the version (release, then development), creates a tag, pushes everything. Then CI will take over and perform the release." - dependsOn releasePrepare +void updateVersionFile(var version) { + logger.lifecycle( "Updating `gradle/version.properties` version to `${version}`" ) + project.versionFile.text = "projectVersion=${version}" + project.version = version +} - doFirst { - if ( !project.hasProperty( 'releaseVersion' ) || !project.hasProperty( 'developmentVersion' ) - || !project.hasProperty( 'gitRemote' ) || !project.hasProperty( 'gitBranch' ) ) { - throw new GradleException( - "Task 'ciRelease' requires all of the following properties to be set:" - + "'releaseVersion', 'developmentVersion', 'gitRemote' and 'gitBranch'." - ) - } - } +def publishReleaseArtifactsTask = tasks.register( 'publishReleaseArtifacts' ) { + dependsOn uploadDocumentationTask + dependsOn ":publishToSonatype" + + mustRunAfter gitPreparationForReleaseTask +} + +def releasePerformPostGitTask = tasks.register( 'gitTasksAfterReleasePerform' ) { doLast { - logger.lifecycle( "Checking that the working tree is clean..." ) - String uncommittedFiles = executeGitCommand( 'status', '--porcelain' ) - if ( !uncommittedFiles.isEmpty() ) { - throw new GradleException( - "Cannot release because there are uncommitted or untracked files in the working tree." - + "\nCommit or stash your changes first." - + "\nUncommitted files:\n" + uncommittedFiles - ) + if ( project.createTag ) { + logger.lifecycle( "Pushing branch and tag to remote `${project.gitRemote}`..." ) + executeGitCommand( 'push', '--atomic', project.gitRemote, project.gitBranch, project.releaseTag ) } - String tag = project.releaseVersion - if ( tag.endsWith( ".Final" ) ) { - tag = tag.replace( ".Final", "" ) + else { + logger.lifecycle( "Pushing branch to remote `${project.gitRemote}`..." ) + executeGitCommand( 'push', project.gitRemote, project.gitBranch ) } - logger.lifecycle( "Tagging '${tag}'..." ) - executeGitCommand( 'tag', '-a', '-m', "Release ${project.releaseVersion}", tag ) + } +} - logger.lifecycle( "Adding commit to update version to '${project.developmentVersion}'..." ) - project.projectVersionFile.text = "projectVersion=${project.developmentVersion}" - executeGitCommand( 'add', '.' ) - executeGitCommand( 'commit', '-m', project.developmentVersion ) +def releasePrepareTask = tasks.register( "releasePrepare" ) { + description "On a local checkout, performs all the changes required for the release, website updates included" + group "Release" - logger.lifecycle( "Pushing branch and tag to remote '${project.gitRemote}'..." ) - executeGitCommand( 'push', '--atomic', project.gitRemote, project.gitBranch, tag ) + dependsOn gitPreparationForReleaseTask - logger.lifecycle( "Done!" ) + finalizedBy releasePreparePostGitTask +} -// logger -// .lifecycle( "Go to https://github.com/hibernate/hibernate-reactive/actions?query=branch%3A${tag} to check the progress of the automated release." ) - } +def releasePerformTask = tasks.register( 'releasePerform' ) { + group 'Release' + description 'Performs a release on local check-out, including updating changelog and ' + + dependsOn publishReleaseArtifactsTask + + finalizedBy releasePerformPostGitTask +} + +/* +* Release everything +*/ +def releaseTask = tasks.register( 'release' ) { + description 'Performs a release on local check-out' + group 'Release' + + dependsOn releasePrepareTask + dependsOn releasePerformTask +} + +def ciReleaseTask = tasks.register( 'ciRelease' ) { + description "Triggers the release on CI: creates commits to change the version (release, then development), creates a tag, pushes everything. Then CI will take over and perform the release." + group "Release" + + dependsOn releaseTask } static String executeGitCommand(Object ... subcommand){ @@ -236,3 +317,61 @@ static String inputStreamToString(InputStream inputStream) { } } } + +gradle.getTaskGraph().whenReady { tg-> + if ( ( tg.hasTask( project.tasks.releasePrepare ) || tg.hasTask( project.tasks.releasePerform ) ) + && ! project.getGradle().getStartParameter().isDryRun() ) { + String releaseVersionLocal + String developmentVersionLocal + + def console = tg.hasTask( project.tasks.release ) && !tg.hasTask( project.tasks.ciRelease ) + ? System.console() + : null + + if (project.hasProperty('releaseVersion')) { + releaseVersionLocal = project.property('releaseVersion') + } + else { + if (console) { + // prompt for `releaseVersion` + releaseVersionLocal = console.readLine('> Enter the release version: ') + } + else { + throw new GradleException( + "`release`-related tasks require the following properties: 'releaseVersion', 'developmentVersion'" + ) + } + } + + if (project.hasProperty('developmentVersion')) { + developmentVersionLocal = project.property('developmentVersion') + } + else { + if (console) { + // prompt for `developmentVersion` + developmentVersionLocal = console.readLine('> Enter the next development version: ') + } + else { + throw new GradleException( + "`release`-related tasks require the following properties: 'releaseVersion', 'developmentVersion'" + ) + } + } + + assert releaseVersionLocal != null && developmentVersionLocal != null + + // set up information for the release-related tasks + project.ext { + releaseVersion = releaseVersionLocal + developmentVersion = developmentVersionLocal + createTag = !project.hasProperty('noTag') + releaseTag = project.createTag ? determineReleaseTag(releaseVersionLocal) : '' + } + } +} + +static String determineReleaseTag(String releaseVersion) { + return releaseVersion.endsWith( '.Final' ) + ? releaseVersion.replace( ".Final", "" ) + : releaseVersion +}