From cbef47fa236fe487bf7d03bec314e4390cf99694 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Thu, 6 Nov 2025 13:02:01 +0100 Subject: [PATCH 1/2] Update the order of release steps and simplify the ORM build script [HHH-19913] --- build.gradle | 8 - ci/release/Jenkinsfile | 29 +- .../local.publishing-java-module.gradle | 3 + .../org/hibernate/build/OrmBuildDetails.java | 10 +- .../org/hibernate/build/ReleaseDetails.java | 68 ---- release/release.gradle | 328 +----------------- .../hibernate-gradle-plugin.gradle | 41 +-- 7 files changed, 17 insertions(+), 470 deletions(-) delete mode 100644 local-build-plugins/src/main/java/org/hibernate/build/ReleaseDetails.java diff --git a/build.gradle b/build.gradle index 3f71deabf2c9..9f5600731a06 100644 --- a/build.gradle +++ b/build.gradle @@ -47,14 +47,6 @@ tasks.register( 'releasePrepare' ) { // See `:release:releasePrepare` which does a lot of heavy lifting here } -tasks.register( 'releasePerform' ) { - group "release-perform" - description "Scripted release 'Release Perform' stage. " + - "Generally this entails publishing artifacts to various servers. " + - "Sub-projects register their own `releasePerform` to hook into this stage." - // See `:release:releasePerform` which does a lot of heavy lifting here -} - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CI Build Task diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index f2baecc4b083..5278d3463e4d 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -222,7 +222,15 @@ pipeline { withEnv([ "DISABLE_REMOTE_GRADLE_CACHE=true" ]) { - sh ".release/scripts/publish.sh -j ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" + def notesFiles = findFiles(glob: 'release_notes.md') + if ( notesFiles.length < 1 ) { + throw new IllegalStateException( "Could not locate `release_notes.md`" ) + } + if ( notesFiles.length > 1 ) { + throw new IllegalStateException( "Located more than 1 `release_notes.md`" ) + } + + sh ".release/scripts/publish.sh -j --notes=${notesFiles[0].path} ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH} " } } } @@ -271,25 +279,6 @@ pipeline { } } } - stage('Create GitHub release') { - steps { - script { - checkoutReleaseScripts() - - def notesFiles = findFiles(glob: 'release_notes.md') - if ( notesFiles.length < 1 ) { - throw new IllegalStateException( "Could not locate `release_notes.md`" ) - } - if ( notesFiles.length > 1 ) { - throw new IllegalStateException( "Located more than 1 `release_notes.md`" ) - } - - withCredentials([string(credentialsId: 'Hibernate-CI.github.com', variable: 'GITHUB_API_TOKEN')]) { - sh ".release/scripts/github-release.sh ${env.SCRIPT_OPTIONS} --notes=${notesFiles[0].path} ${env.PROJECT} ${env.RELEASE_VERSION}" - } - } - } - } } post { always { diff --git a/local-build-plugins/src/main/groovy/local.publishing-java-module.gradle b/local-build-plugins/src/main/groovy/local.publishing-java-module.gradle index 0a045fdf94d1..78423d787db3 100644 --- a/local-build-plugins/src/main/groovy/local.publishing-java-module.gradle +++ b/local-build-plugins/src/main/groovy/local.publishing-java-module.gradle @@ -52,6 +52,9 @@ def releasePrepareTask = tasks.register("releasePrepare") { dependsOn tasks.check dependsOn tasks.generateMetadataFileForPublishedArtifactsPublication dependsOn tasks.generatePomFileForPublishedArtifactsPublication + // we depend on publishAllPublicationsToStagingRepository to make sure that the artifacts are "published" to a local staging directory + // used by JReleaser during the release process + dependsOn tasks.publishAllPublicationsToStagingRepository } // used from the h2 CI job diff --git a/local-build-plugins/src/main/java/org/hibernate/build/OrmBuildDetails.java b/local-build-plugins/src/main/java/org/hibernate/build/OrmBuildDetails.java index 06ddb4589da7..991d4e0fbf6a 100644 --- a/local-build-plugins/src/main/java/org/hibernate/build/OrmBuildDetails.java +++ b/local-build-plugins/src/main/java/org/hibernate/build/OrmBuildDetails.java @@ -16,7 +16,6 @@ * @author Steve Ebersole */ public abstract class OrmBuildDetails { - private final ReleaseDetails releaseDetails; private final Provider versionFileAccess; private final HibernateVersion hibernateVersion; @@ -26,12 +25,9 @@ public abstract class OrmBuildDetails { @Inject public OrmBuildDetails(Project project) { - releaseDetails = new ReleaseDetails( project ); versionFileAccess = project.provider( () -> new File( project.getRootDir(), HibernateVersion.RELATIVE_FILE ) ); - hibernateVersion = releaseDetails.getReleaseVersion() != null - ? releaseDetails.getReleaseVersion() - : fromVersionFile( versionFileAccess.get() ); + hibernateVersion = fromVersionFile( versionFileAccess.get() ); project.setVersion( hibernateVersion.getFullName() ); jpaVersion = JpaVersion.from( project ); @@ -59,10 +55,6 @@ public String getHibernateVersionNameOsgi() { return getHibernateVersion().getOsgiVersion(); } - public ReleaseDetails getReleaseDetails() { - return releaseDetails; - } - public JpaVersion getJpaVersion() { return jpaVersion; } diff --git a/local-build-plugins/src/main/java/org/hibernate/build/ReleaseDetails.java b/local-build-plugins/src/main/java/org/hibernate/build/ReleaseDetails.java deleted file mode 100644 index bee4dcd5285c..000000000000 --- a/local-build-plugins/src/main/java/org/hibernate/build/ReleaseDetails.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.build; - -import org.gradle.api.Project; -import org.gradle.api.provider.Provider; - -/** - * @author Steve Ebersole - */ -public class ReleaseDetails { - private final HibernateVersion releaseVersion; - private final HibernateVersion developmentVersion; - private final Provider createTagAccess; - private final Provider tagNameAccess; - - public ReleaseDetails(Project project) { - final Object releaseVersionSetting = project.findProperty( "releaseVersion" ); - final Object developmentVersionSetting = project.findProperty( "developmentVersion" ); - if ( releaseVersionSetting != null ) { - if ( developmentVersionSetting == null ) { - throw new IllegalStateException( "`releaseVersion` with no `developmentVersion`" ); - } - releaseVersion = new HibernateVersion( releaseVersionSetting.toString() ); - developmentVersion = new HibernateVersion( developmentVersionSetting.toString() ); - tagNameAccess = project.provider( () -> determineReleaseTag( releaseVersion.getFullName() ) ); - } - else { - releaseVersion = null; - developmentVersion = null; - tagNameAccess = project.provider( () -> null ); - } - - createTagAccess = project.provider( () -> !project.hasProperty( "noTag" ) ); - } - - public HibernateVersion getReleaseVersion() { - return releaseVersion; - } - - public HibernateVersion getDevelopmentVersion() { - return developmentVersion; - } - - public Provider getCreateTagAccess() { - return createTagAccess; - } - - public boolean shouldCreateTag() { - return getCreateTagAccess().get(); - } - - public Provider getTagNameAccess() { - return tagNameAccess; - } - - public String getTagNameToUse() { - return getTagNameAccess().get(); - } - - private static String determineReleaseTag(String releaseVersion) { - return releaseVersion.endsWith( ".Final" ) - ? releaseVersion.replace( ".Final", "" ) - : releaseVersion; - } -} diff --git a/release/release.gradle b/release/release.gradle index 1acbf309d86a..c188dc447ea1 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -2,11 +2,6 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -import java.nio.charset.StandardCharsets - -import groovy.json.JsonSlurper - - plugins { id "local.module" @@ -27,157 +22,15 @@ tasks.build.enabled = false // Stage - Release Prepare : see `releasePrepare` // // This coordinates: -// 1. some pre-release checks -// 2. updating the changelog -// 3. update version.properties to RELEASE_VERSION -// 4. assemble documentation (we want this to run here as a "check") -// 5. git commit (changelog and RELEASE_VERSION) -// 6. update version.properties to DEVELOPMENT_VERSION -// 7. git tag -// 8. git commit (DEVELOPMENT_VERSION) -// -// and at some point in these steps, it assembles documentation. the order is unimportant -// -// NOTE : ideally we'd switch (6) and (7). it works out as-is, but confusing +// 1. assemble documentation (we want this to run here as a "check") +// 2. generate and place all the artifacts in the staging directory (see local.publishing-java-module) // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -def releaseChecksTask = tasks.register( "releaseChecks" ) { - group = 'release-prepare' - description = 'Checks and preparation for 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 - String gitRemoteLocal - - if (project.hasProperty('gitBranch') && !project.property('gitBranch').isEmpty()) { - gitBranchLocal = project.property('gitBranch') - } - else { - gitBranchLocal = executeGitCommand( 'branch', '--show-current' ).trim() - } - - 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." - ); - } - } -} - -def changeLogFileTask = tasks.register( "changeLogFile" ) { - group = 'release-prepare' - description = 'Updates the changelog.txt file based on the change-log report from Jira' - dependsOn releaseChecksTask - - doFirst { - logger.lifecycle( "Appending version `${project.releaseVersion}` to changelog..." ) - ChangeLogFile.update( ormBuildDetails.hibernateVersion.fullName, rootProject ); - } -} - -def changeToReleaseVersionTask = tasks.register( "changeToReleaseVersion" ) { - group = 'release-prepare' - description = 'Updates `gradle/version.properties` file to the specified release-version' - - dependsOn releaseChecksTask - - doFirst { - def releaseVersion = ormBuildDetails.releaseDetails.releaseVersion - logger.lifecycle( "Updating version-file to release-version : `${releaseVersion}`" ) - updateVersionFile( releaseVersion.toString() ) - } -} - -def gitPreStepsTask = tasks.register( 'gitPreSteps' ) { - group = "release-prepare" - description = "update changelog, update version file and commit" - - dependsOn releaseChecksTask - dependsOn changeLogFileTask - dependsOn changeToReleaseVersionTask - - doLast { - def releaseVersion = ormBuildDetails.releaseDetails.releaseVersion - logger.lifecycle( "Performing pre-steps Git commit : `${releaseVersion}`" ) - executeGitCommand( 'add', '.' ) - executeGitCommand( 'commit', '-m', "Pre-steps for release : `${releaseVersion.fullName}`" ) - } -} - -def gitPostStepsTask = tasks.register( 'gitPostSteps' ) { - group = "release-prepare" - description = "possibly create tag. update version-file and commit " - - doFirst { - } - - doLast { - def releaseDetails = ormBuildDetails.releaseDetails - def releaseVersion = releaseDetails.releaseVersion - def developmentVersion = releaseDetails.developmentVersion - - if ( releaseDetails.shouldCreateTag() ) { - logger.lifecycle( "Tagging release : `${releaseDetails.tagNameToUse}`..." ) - executeGitCommand( 'tag', '-a', releaseDetails.tagNameToUse, '-m', "Release $releaseVersion.fullName" ) - } - - logger.lifecycle( "Updating version-file to development-version : `${developmentVersion.fullName}`" ) - updateVersionFile( developmentVersion.toString() ) - - logger.lifecycle( "Performing post-steps Git commit : `${releaseVersion}`" ) - executeGitCommand( 'add', '.' ) - executeGitCommand( 'commit', '-m', "Post-steps for release : `${releaseVersion.fullName}`" ) - } -} - -void updateVersionFile(String version) { - logger.lifecycle( "Updating `gradle/version.properties` version to `${version}`" ) - ormBuildDetails.versionFileAccess.get().text = "hibernateVersion=${version}" -} - tasks.register("releasePrepare") { group = "release-prepare" description = "Scripted release 'Release Prepare' stage" - dependsOn releaseChecksTask dependsOn "assembleDocumentation" - dependsOn gitPreStepsTask - - finalizedBy gitPostStepsTask } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -370,180 +223,3 @@ def assembleDocumentationTask = tasks.register( "assembleDocumentation" ) { dependsOn stageWhatsNewGuideTask dependsOn stageOrmReportsTask } - - - - - - -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Stage - Release Perform : see `releasePerform` -// -// This stage is all about publishing artifacts - -// * On the `:release` project, this means uploading documentation -// * On the `:hibernate-gradle-plugin` project, this means publishing to the Plugin Portal -// * On the published java modules, this means publishing its artifacts to Sonatype -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -tasks.register( 'releasePerform' ) { - group = "release-perform" - description = "Scripted release 'Release Perform' stage" - - doFirst { - if ( ormBuildDetails.releaseDetails.shouldCreateTag() ) { - assert project.gitRemote != null - assert project.gitBranch != null - assert project.ormBuildDetails.releaseDetails.tagNameToUse != null - } - } - - doLast { - if ( ormBuildDetails.releaseDetails.shouldCreateTag() ) { - logger.lifecycle( "Pushing branch and tag to remote `${project.gitRemote}`..." ) - executeGitCommand( 'push', '--atomic', project.gitRemote, project.gitBranch, project.ormBuildDetails.releaseDetails.tagNameToUse ) - } - else { - logger.lifecycle( "Pushing branch to remote `${project.gitRemote}`..." ) - executeGitCommand( 'push', project.gitRemote, project.gitBranch ) - } - } -} - - - - - -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Utilities -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -static String executeGitCommand(Object ... subcommand){ - List command = ['git'] - Collections.addAll( command, subcommand ) - def proc = command.execute() - def code = proc.waitFor() - def stdout = inputStreamToString( proc.getInputStream() ) - def stderr = inputStreamToString( proc.getErrorStream() ) - if ( code != 0 ) { - throw new GradleException( "An error occurred while executing " + command + "\n\nstdout:\n" + stdout + "\n\nstderr:\n" + stderr ) - } - return stdout -} - -static String inputStreamToString(InputStream inputStream) { - inputStream.withCloseable { ins -> - new BufferedInputStream(ins).withCloseable { bis -> - new ByteArrayOutputStream().withCloseable { buf -> - int result = bis.read(); - while (result != -1) { - buf.write((byte) result); - result = bis.read(); - } - return buf.toString( StandardCharsets.UTF_8.name()); - } - } - } -} - -class ChangeLogFile { - - // Get the Release Notes from Jira and add them to the Hibernate changelog.txt file - static void update(String releaseVersion, Project project) { - def text = "" - File changelog = project.rootProject.layout.projectDirectory.file( "changelog.txt" ).asFile - def newReleaseNoteBlock = getNewReleaseNoteBlock(releaseVersion) - changelog.eachLine { - line -> - if ( line.startsWith( "Note:" ) ) { - text += line + System.lineSeparator() + System.lineSeparator() + newReleaseNoteBlock - } - else { - text += line + System.lineSeparator() - } - } - changelog.text = text - } - - // Get the Release Notes from Jira - static String getNewReleaseNoteBlock(String releaseVersion) { - def restReleaseVersion; - if ( releaseVersion.endsWith( ".Final" ) ) { - restReleaseVersion = releaseVersion.replace( ".Final", "" ) - } - else { - restReleaseVersion = releaseVersion - } - def ReleaseNote releaseNotes = null - def String nextPageToken = null - def issuetype = null - do { - def apiString = "https://hibernate.atlassian.net/rest/api/3/search/jql/?jql=project=HHH%20AND%20fixVersion=${restReleaseVersion}%20AND%20statusCategory%20%3D%20Done%20order%20by%20issuetype%20ASC&fields=issuetype,summary,fixVersions&maxResults=200${nextPageToken == null ? '' : '&nextPageToken=' + nextPageToken}" - def apiUrl = new URI(apiString).toURL() - def jsonReleaseNotes = new JsonSlurper().parse(apiUrl) - if (releaseNotes == null) { - def releaseDate = new Date().format( 'MMMM dd, YYYY' ) - def versionId = getVersionId(jsonReleaseNotes, restReleaseVersion) - releaseNotes = new ReleaseNote(releaseVersion, releaseDate, versionId) - } - - jsonReleaseNotes.issues.each { - issue -> - if ( issuetype != issue.fields.issuetype.name ) { - issuetype = issue.fields.issuetype.name - releaseNotes.addEmptyLine(); - releaseNotes.addLine( "** ${issue.fields.issuetype.name}" ) - } - releaseNotes.addLine( " * [" + issue.key + "] - " + issue.fields.summary ) - } - - nextPageToken = jsonReleaseNotes.nextPageToken - - } while (nextPageToken != null) - - releaseNotes.addEmptyLine() - return releaseNotes.notes - } - - private static String getVersionId(jsonReleaseNotes, String restReleaseVersion) { - for ( def issue : jsonReleaseNotes.issues ) { - for ( def fixVersion : issue.fields.fixVersions ) { - if ( fixVersion.name == restReleaseVersion ) { - return fixVersion.id - } - } - } - throw new GradleException("No issues found for current release version (" + restReleaseVersion + "), aborting.") - } -} - -class ReleaseNote { - String notes; - String notesHeaderSeparator = "------------------------------------------------------------------------------------------------------------------------" - - ReleaseNote(String releaseVersion, String releaseDate, String versionId) { - notes = "Changes in ${releaseVersion} (${releaseDate})" + System.lineSeparator() - addHeaderSeparator() - addEmptyLine() - addLine( "https://hibernate.atlassian.net/projects/HHH/versions/${versionId}" ) - } - - void addLine(String text) { - notes += text + System.lineSeparator() - } - - void addHeaderSeparator() { - addLine( notesHeaderSeparator ) - } - - void addEmptyLine() { - notes += System.lineSeparator() - } - - void addEmptyLines(int numberOfLines) { - for ( i in 1..numberOfLines ) { - notes += System.lineSeparator() - } - } -} - diff --git a/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle b/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle index b75be0dfdb29..7187d8e3929e 100644 --- a/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle +++ b/tooling/hibernate-gradle-plugin/hibernate-gradle-plugin.gradle @@ -89,9 +89,9 @@ tasks.register("preVerifyRelease") { dependsOn releasePrepareTask } -tasks.register("releasePerform") { +tasks.register("releaseGradlePluginPerform") { group "release-perform" - description "See :release:releasePerform for details. Here we hook in publishing to the Plugin Portal" + description "An explicit task for publishing Gradle Plugins to the Plugin Portal" dependsOn tasks.publishPlugins } @@ -161,40 +161,3 @@ else { tasks.publish.enabled = !ormBuildDetails.hibernateVersion.isSnapshot tasks.publishPlugins.enabled = !ormBuildDetails.hibernateVersion.isSnapshot - -gradle.taskGraph.whenReady { tg -> - // local publishing (SNAPSHOT testing), cont. - // - https://github.com/gradle-nexus/publish-plugin/issues/143 - // - https://github.com/gradle-nexus/publish-plugin/pull/144 - tasks.withType(PublishToMavenRepository).configureEach {t-> - if (t.repository == null) { - logger.info("Task `{}` had null repository", t.path) - } - else if (t.repository.name == "sonatype") { - logger.debug("Disabling task `{}` because it publishes to Sonatype", t.path) - t.enabled = false - } - } - - // verify credentials for publishing the plugin up front to avoid any work (only if we are publishing) - if ( tg.hasTask( tasks.publishPlugins ) && project.tasks.publishPlugins.enabled ) { - // we are publishing the plugin - make sure there is a credentials pair - // - // first, check the `GRADLE_PUBLISH_KEY` / `GRADLE_PUBLISH_SECRET` combo (env vars) - // and then the `gradle.publish.key` / `gradle.publish.secret` combo (project prop) - // - see https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html#account_setup - if ( System.getenv().get("GRADLE_PUBLISH_KEY") != null ) { - if ( System.getenv().get("GRADLE_PUBLISH_SECRET") == null ) { - throw new RuntimeException( "`GRADLE_PUBLISH_KEY` specified, but not `GRADLE_PUBLISH_SECRET` for publishing Gradle plugin" ) - } - } - else if ( project.findProperty( 'gradle.publish.key' ) != null ) { - if ( project.findProperty( 'gradle.publish.secret' ) == null ) { - throw new RuntimeException( "`gradle.publish.key` specified, but not `gradle.publish.secret` for publishing Gradle plugin" ) - } - } - else { - throw new RuntimeException( "No credentials specified for publishing Gradle plugin" ) - } - } -} From 956f9a9fcf7f8fb8e0df6d67a90ebb0e4eb69a62 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Thu, 13 Nov 2025 12:31:01 +0100 Subject: [PATCH 2/2] Fix the release note template --- release_notes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/release_notes.md b/release_notes.md index c6e0330dbb1e..f37246787c31 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,4 +1,4 @@ -* See the [website](https://hibernate.org/orm/releases/7.1) for requirements and compatibilities. -* See the [What's New](https://docs.jboss.org/hibernate/orm/7.1/whats-new/whats-new.html) guide for details about new features and capabilities. -* See the [Migration Guide](https://docs.jboss.org/hibernate/orm/7.1/migration-guide/migration-guide.html) for details about migration from version 7.0 to 7.1. +* See the [website](https://hibernate.org/orm/releases/{{releaseVersionFamily}}) for requirements and compatibilities. +* See the [What's New](https://docs.hibernate.org/orm/{{releaseVersionFamily}}/whats-new/whats-new.html) guide for details about new features and capabilities. +* See the [Migration Guide](https://docs.hibernate.org/orm/{{releaseVersionFamily}}/migration-guide/) for details about migration to 7.1 version.