diff --git a/build.gradle b/build.gradle index 43cd640fd..197b6d9be 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ -import java.util.regex.Pattern - plugins { + id "local.versions" + id 'java-library' id 'maven-publish' id 'com.diffplug.spotless' version '6.25.0' @@ -8,6 +8,10 @@ plugins { id 'io.github.gradle-nexus.publish-plugin' version '1.3.0' } +group = "org.hibernate.reactive" +// leverage the ProjectVersion which comes from the `local.versions` plugin +version = project.projectVersion.fullName + ext { if ( !project.hasProperty( 'sonatypeOssrhUser' ) ) { sonatypeOssrhUser = null @@ -17,57 +21,12 @@ ext { } } -ext { - projectGroup = 'org.hibernate.reactive' - projectVersionFile = file( "${rootProject.projectDir}/gradle/version.properties" ) - projectVersion = Version.parseProjectVersion( readVersionFromProperties( projectVersionFile ) ) - - if ( project.hasProperty('releaseVersion') ) { - releaseVersion = Version.parseReleaseVersion( project.releaseVersion ) - } - if ( project.hasProperty('developmentVersion') ) { - developmentVersion = Version.parseDevelopmentVersion( project.developmentVersion ) - } -} - -// nexusPublishing uses group and version -// as defaults -group = projectGroup -version = projectVersion - // Versions which need to be aligned across modules; this also // allows overriding the build using a parameter, which can be // useful to monitor compatibility for upcoming versions on CI: // // ./gradlew clean build -PhibernateOrmVersion=5.6.15-SNAPSHOT ext { - if ( !project.hasProperty('hibernateOrmVersion') ) { - hibernateOrmVersion = '6.6.0.Final' - } - if ( !project.hasProperty( 'hibernateOrmGradlePluginVersion' ) ) { - // Same as ORM as default - hibernateOrmGradlePluginVersion = project.hibernateOrmVersion - } - // For ORM, we need a parsed version (to get the family, ...) - - // For ORM, we need a parsed version (to get the family for the documentation during release). - // We use intervals to build with the latest ORM snapshot and this will fail version check. - // Because we don't need to publish or release anything we can disable the check in this case. - // Example: - // ./gradlew build -PhibernateOrmVersion='[5.4,5.5)' -PskipOrmVersionParsing - if ( project.hasProperty( 'skipOrmVersionParsing' ) ) { - // Default to true if the property is null - skipOrmVersionParsing = project.skipOrmVersionParsing ?: true - } - else { - skipOrmVersionParsing = false - } - - if ( !Boolean.valueOf( project.skipOrmVersionParsing ) ) { - logger.lifecycle "Parsing ORM version" - hibernateOrmVersion = Version.parseProjectVersion( project.hibernateOrmVersion ) - } - // Mainly, to allow CI to test the latest versions of Vert.X // Example: // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT @@ -77,10 +36,7 @@ ext { testcontainersVersion = '1.19.8' - logger.lifecycle "Hibernate ORM Version: " + project.hibernateOrmVersion - logger.lifecycle "Hibernate ORM Gradle plugin Version: " + project.hibernateOrmGradlePluginVersion logger.lifecycle "Vert.x SQL Client Version: " + project.vertxSqlClientVersion - logger.lifecycle "Hibernate Reactive: " + project.version } // To release, see task ciRelease in release/build.gradle @@ -99,9 +55,8 @@ subprojects { apply plugin: 'java-library' apply plugin: 'com.diffplug.spotless' - // FIXME: Also setting the group and version here otherwise not all tasks will find them - group = projectGroup - version = projectVersion + group = rootProject.group + version = rootProject.version spotless { //Don't fail during the check: rather than enforcing guidelines, we use this plugin to fix mistakes automatically. @@ -201,79 +156,3 @@ subprojects { } } -private static String readVersionFromProperties(File file) { - if ( !file.exists() ) { - throw new GradleException( "Version file $file.canonicalPath does not exists" ) - } - Properties versionProperties = new Properties() - file.withInputStream { - stream -> versionProperties.load( stream ) - } - return versionProperties.projectVersion -} - -class Version { - - private static final Pattern RELEASE_VERSION_PATTERN = ~/^(\d+)\.(\d+)\.(\d+)\.((?:Alpha\d+|Beta\d+|CR\d+)|Final)$/ - - private static final Pattern DEVELOPMENT_VERSION_PATTERN = ~/^(\d+)\.(\d+)\.(\d+)-SNAPSHOT$/ - - static Version parseReleaseVersion(String versionString) { - def matcher = (versionString =~ RELEASE_VERSION_PATTERN) - if ( !matcher.matches() ) { - throw new IllegalArgumentException( - "Invalid version number: '$versionString'." + - " Release version numbers must match /$RELEASE_VERSION_PATTERN/." - ) - } - return new Version( matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4), false ) - } - - static Version parseDevelopmentVersion(String versionString) { - def matcher = (versionString =~ DEVELOPMENT_VERSION_PATTERN) - if ( !matcher.matches() ) { - throw new IllegalArgumentException( - "Invalid version number: '$versionString'." + - " Development version numbers must match /$DEVELOPMENT_VERSION_PATTERN/." - ) - } - - return new Version( matcher.group(1), matcher.group(2), matcher.group(3), null, true ) - } - - static Version parseProjectVersion(String versionString) { - if ( (versionString =~ RELEASE_VERSION_PATTERN).matches() ) { - return parseReleaseVersion( versionString ) - } - if ( (versionString =~ DEVELOPMENT_VERSION_PATTERN).matches() ) { - return parseDevelopmentVersion( versionString ) - } - throw new IllegalArgumentException( - "Invalid version number: '$versionString'." + - " Project version numbers must match either /$RELEASE_VERSION_PATTERN/ or /$DEVELOPMENT_VERSION_PATTERN/." - ) - } - - final String major - final String minor - final String micro - final String qualifier - final boolean snapshot - - Version(String major, String minor, String micro, String qualifier, boolean snapshot) { - this.major = major - this.minor = minor - this.micro = micro - this.qualifier = qualifier - this.snapshot = snapshot - } - - @Override - String toString() { - [major, minor, micro, qualifier].findAll({ it != null }).join('.') + (snapshot ? '-SNAPSHOT' : '') - } - - String getFamily() { - "$major.$minor" - } -} 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/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile new file mode 100644 index 000000000..c24e7c1e3 --- /dev/null +++ b/ci/snapshot-publish.Jenkinsfile @@ -0,0 +1,59 @@ +/* + * See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers + */ +@Library('hibernate-jenkins-pipeline-helpers@1.13') _ + +// Avoid running the pipeline on branch indexing +if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { + print "INFO: Build skipped due to trigger being Branch Indexing" + currentBuild.result = 'NOT_BUILT' + return +} + +pipeline { + agent { + label 'Fedora' + } + tools { + jdk 'OpenJDK 11 Latest' + } + options { + rateLimitBuilds(throttle: [count: 1, durationName: 'hour', userBoost: true]) + buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '3')) + disableConcurrentBuilds(abortPrevious: true) + } + stages { + stage('Checkout') { + steps { + checkout scm + } + } + stage('Publish') { + steps { + withCredentials([ + usernamePassword(credentialsId: 'ossrh.sonatype.org', usernameVariable: 'hibernatePublishUsername', passwordVariable: 'hibernatePublishPassword'), + usernamePassword(credentialsId: 'plugins.gradle.org', usernameVariable: 'hibernatePluginPortalUsername', passwordVariable: 'hibernatePluginPortalPassword'), + string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_PASS'), + file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_KEYRING') + ]) { + sh '''./gradlew clean publish \ + -PhibernatePublishUsername=$hibernatePublishUsername \ + -PhibernatePublishPassword=$hibernatePublishPassword \ + -Pgradle.publish.key=$hibernatePluginPortalUsername \ + -Pgradle.publish.secret=$hibernatePluginPortalPassword \ + --no-scan \ + -DsigningPassword=$SIGNING_PASS \ + -DsigningKeyFile=$SIGNING_KEYRING \ + ''' + } + } + } + } + post { + always { + configFileProvider([configFile(fileId: 'job-configuration.yaml', variable: 'JOB_CONFIGURATION_FILE')]) { + notifyBuildResult maintainers: (String) readYaml(file: env.JOB_CONFIGURATION_FILE).notification?.email?.recipients + } + } + } +} \ No newline at end of file diff --git a/documentation/build.gradle b/documentation/build.gradle index 94e42c62c..aa6a27622 100644 --- a/documentation/build.gradle +++ b/documentation/build.gradle @@ -1,8 +1,10 @@ -import java.time.Year - import org.asciidoctor.gradle.jvm.AsciidoctorTask -apply plugin: 'org.asciidoctor.jvm.convert' +import java.time.Year + +plugins { + id "org.asciidoctor.jvm.convert" +} ext { projectsToSkipWhenAggregatingJavadocs = [ @@ -22,21 +24,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 @@ -75,18 +77,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,41 +100,40 @@ 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 + attributes icons: 'font', 'source-highlighter': 'rouge', experimental: true, linkcss: true, - majorMinorVersion: project.version.family, + majorMinorVersion: project.projectVersion.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/gradle.properties b/gradle.properties index de31bc77b..e2801322e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -34,8 +34,8 @@ org.gradle.java.installations.auto-download=false # Enable the maven local repository (for local development when needed) when present (value ignored) #enableMavenLocalRepo = true -# Override default Hibernate ORM version -#hibernateOrmVersion = 6.6.0.Final +# The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) +hibernateOrmVersion = 6.6.0.Final # Override default Hibernate ORM Gradle plugin version # Using the stable version because I don't know how to configure the build to download the snapshot version from diff --git a/local-build-plugins/build.gradle b/local-build-plugins/build.gradle new file mode 100644 index 000000000..7cc4db2f9 --- /dev/null +++ b/local-build-plugins/build.gradle @@ -0,0 +1,30 @@ +plugins { + id "java-gradle-plugin" +} + +repositories { + mavenCentral() +} + +group = 'org.hibernate.reactive.build' +version = '1.0.0-SNAPSHOT' + + +dependencies { + implementation gradleApi() +} + +java { + sourceCompatibility = 11 + targetCompatibility = 11 +} + + +gradlePlugin { + plugins { + projectEnv { + id = 'local.versions' + implementationClass = 'org.hibernate.reactive.env.VersionsPlugin' + } + } +} \ No newline at end of file diff --git a/local-build-plugins/settings.gradle b/local-build-plugins/settings.gradle new file mode 100644 index 000000000..1412c27cb --- /dev/null +++ b/local-build-plugins/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'local-build-plugins' diff --git a/local-build-plugins/src/main/java/org/hibernate/reactive/env/ProjectVersion.java b/local-build-plugins/src/main/java/org/hibernate/reactive/env/ProjectVersion.java new file mode 100644 index 000000000..17d760151 --- /dev/null +++ b/local-build-plugins/src/main/java/org/hibernate/reactive/env/ProjectVersion.java @@ -0,0 +1,54 @@ +package org.hibernate.reactive.env; + +/** + * @author Steve Ebersole + */ +public class ProjectVersion { + final String major; + final String minor; + final boolean snapshot; + + private final String family; + private final String fullName; + + ProjectVersion(String fullName) { + this.fullName = fullName; + + try { + final String[] hibernateVersionComponents = fullName.split( "\\." ); + major = hibernateVersionComponents[0]; + minor = hibernateVersionComponents[1]; + } + catch (Exception e) { + throw new IllegalArgumentException( "Invalid version number: " + fullName + "." ); + } + + family = major + "." + minor; + snapshot = fullName.endsWith( "-SNAPSHOT" ); + } + + public String getMajor() { + return major; + } + + public String getMinor() { + return minor; + } + + public String getFullName() { + return fullName; + } + + public String getFamily() { + return family; + } + + public boolean isSnapshot() { + return snapshot; + } + + @Override + public String toString() { + return fullName; + } +} diff --git a/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java new file mode 100644 index 000000000..36b5ae10a --- /dev/null +++ b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java @@ -0,0 +1,147 @@ +package org.hibernate.reactive.env; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.function.Consumer; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +/** + * Build plugin which applies some DSL extensions to the Project. Currently, these + * extensions are all related to version. + * + * @author Steve Ebersole + */ +public class VersionsPlugin implements Plugin { + public static final String VERSION_FILE = "versionFile"; + + public static final String PROJECT_VERSION = "projectVersion"; + public static final String RELEASE_VERSION = "releaseVersion"; + public static final String DEVELOPMENT_VERSION = "developmentVersion"; + + public static final String ORM_VERSION = "hibernateOrmVersion"; + public static final String ORM_PLUGIN_VERSION = "hibernateOrmGradlePluginVersion"; + public static final String SKIP_ORM_VERSION_PARSING = "skipOrmVersionParsing"; + + public static final String RELATIVE_FILE = "gradle/version.properties"; + + @Override + public void apply(Project project) { + final File versionFile = project.getRootProject().file( RELATIVE_FILE ); + project.getExtensions().add( VERSION_FILE, versionFile ); + + final ProjectVersion releaseVersion = determineReleaseVersion( project ); + final ProjectVersion developmentVersion = determineDevelopmentVersion( project ); + final ProjectVersion projectVersion = determineProjectVersion( project, releaseVersion, versionFile ); + + project.getLogger().lifecycle( "Project version: {} ({})", projectVersion.getFullName(), projectVersion.getFamily() ); + project.getExtensions().add( PROJECT_VERSION, projectVersion ); + + if ( releaseVersion != null ) { + project.getLogger().lifecycle( "Release version: {} ({})", releaseVersion.getFullName(), releaseVersion.getFamily() ); + project.getExtensions().add( RELEASE_VERSION, releaseVersion ); + } + else { + project.getLogger().lifecycle( "Release version: n/a" ); + } + + if ( developmentVersion != null ) { + project.getLogger().lifecycle( "Development version: {} ({})", developmentVersion.getFullName(), developmentVersion.getFamily() ); + project.getExtensions().add( DEVELOPMENT_VERSION, developmentVersion ); + } + else { + project.getLogger().lifecycle( "Development version: n/a" ); + } + + final String ormVersionString = determineOrmVersion( project ); + final Object ormVersion = resolveOrmVersion( ormVersionString, project ); + project.getLogger().lifecycle( "ORM version: {}", ormVersion ); + project.getExtensions().add( ORM_VERSION, ormVersion ); + + final Object ormPluginVersion = determineOrmPluginVersion( ormVersion, project ); + project.getLogger().lifecycle( "ORM Gradle plugin version: {}", ormPluginVersion ); + project.getExtensions().add( ORM_PLUGIN_VERSION, ormPluginVersion ); + } + + private ProjectVersion determineReleaseVersion(Project project) { + if ( project.hasProperty( RELEASE_VERSION ) ) { + final Object version = project.property( RELEASE_VERSION ); + if ( version != null ) { + return new ProjectVersion( (String) version ); + } + } + return null; + } + + private ProjectVersion determineDevelopmentVersion(Project project) { + if ( project.hasProperty( DEVELOPMENT_VERSION ) ) { + final Object version = project.property( DEVELOPMENT_VERSION ); + if ( version != null ) { + return new ProjectVersion( (String) version ); + } + } + return null; + } + + public static ProjectVersion determineProjectVersion(Project project, ProjectVersion releaseVersion, File versionFile) { + if ( releaseVersion != null ) { + return releaseVersion; + } + + final String fullName = readVersionProperties( versionFile ); + return new ProjectVersion( fullName ); + } + + private static String readVersionProperties(File file) { + if ( !file.exists() ) { + throw new RuntimeException( "Version file " + file.getAbsolutePath() + " does not exists" ); + } + + final Properties versionProperties = new Properties(); + withInputStream( file, (stream) -> { + try { + versionProperties.load( stream ); + } + catch (IOException e) { + throw new RuntimeException( "Unable to load properties from file - " + file.getAbsolutePath(), e ); + } + } ); + + return versionProperties.getProperty( "projectVersion" ); + } + + private static void withInputStream(File file, Consumer action) { + try ( final FileInputStream stream = new FileInputStream( file ) ) { + action.accept( stream ); + } + catch (IOException e) { + throw new RuntimeException( "Error reading file stream = " + file.getAbsolutePath(), e ); + } + } + + private String determineOrmVersion(Project project) { + if ( project.hasProperty( ORM_VERSION ) ) { + return (String) project.property( ORM_VERSION ); + } + throw new IllegalStateException( "Hibernate ORM version not specified on project" ); + } + + private Object resolveOrmVersion(String stringForm, Project project) { + if ( project.hasProperty( SKIP_ORM_VERSION_PARSING ) + && Boolean.parseBoolean( (String) project.property( SKIP_ORM_VERSION_PARSING ) ) ) { + return stringForm; + } + return new ProjectVersion( stringForm ); + } + + private Object determineOrmPluginVersion(Object ormVersion, Project project) { + if ( project.hasProperty( ORM_PLUGIN_VERSION ) ) { + return project.property( ORM_PLUGIN_VERSION ); + } + return ormVersion; + } +} 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 +} diff --git a/settings.gradle b/settings.gradle index aaf96a1b9..af4c707eb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,10 @@ * in the user guide at https://docs.gradle.org/4.10.2/userguide/multi_project_builds.html */ +pluginManagement { + includeBuild 'local-build-plugins' +} + rootProject.name = 'hibernate-reactive' gradle.ext.baselineJavaVersion = JavaLanguageVersion.of( 11 )