From 25402f320ac266689d477883fe043e8276f8ef78 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Thu, 12 Sep 2024 18:56:39 +0200 Subject: [PATCH] WIP Release infra stuff Introduce a couple of shell scripts to automate the release process. Some docs are included under `releases/README.md`. Generally, releases at the ASF follow the following workflow: 1. Draft a release 2. Start a VOTE on the dev mailing list 3. If the VOTE fails, the release has failed - "go to step 1" 4. If the VOTE passes, publish the release The above process is, without release branches, reflected in the scripts: 1. `releases/bin/draft-release.sh --major --minor --commit ` 2. if the vote passes: `releases/bin/publish-release.sh --major --minor ` 3. if the vote fails, just run `draft-release.sh` again The change includes scripts to handle version branches, however, using those is not required for the two release scripts above. It's important to know that the scripts handle changes to the `version.txt` file and that a specific syntax for Git tags is expected, which is required to automatically use/generate RC and final versions, including the artifact publishing via Sonatype. --- NEXT_RELEASE_NOTES.md | 18 + .../publishing/PublishingHelperPlugin.kt | 7 - .../src/main/kotlin/publishing/rootProject.kt | 141 +----- releases/README.md | 103 ++++ releases/bin/_lib.sh | 113 +++++ releases/bin/_releases_lib.sh | 139 ++++++ releases/bin/create-release-branch.sh | 153 ++++++ releases/bin/draft-release.sh | 451 ++++++++++++++++++ releases/bin/generate-release-notes.sh | 207 ++++++++ releases/bin/list-release-branches.sh | 37 ++ releases/bin/publish-release.sh | 388 +++++++++++++++ site/bin/checkout-releases.sh | 2 +- site/bin/remove-releases.sh | 37 +- site/content/in-dev/release_index.md | 5 +- site/hugo.yaml | 2 +- 15 files changed, 1655 insertions(+), 148 deletions(-) create mode 100644 NEXT_RELEASE_NOTES.md create mode 100644 releases/README.md create mode 100644 releases/bin/_lib.sh create mode 100644 releases/bin/_releases_lib.sh create mode 100755 releases/bin/create-release-branch.sh create mode 100755 releases/bin/draft-release.sh create mode 100755 releases/bin/generate-release-notes.sh create mode 100755 releases/bin/list-release-branches.sh create mode 100755 releases/bin/publish-release.sh diff --git a/NEXT_RELEASE_NOTES.md b/NEXT_RELEASE_NOTES.md new file mode 100644 index 000000000..e219f85a4 --- /dev/null +++ b/NEXT_RELEASE_NOTES.md @@ -0,0 +1,18 @@ + diff --git a/build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt b/build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt index 7bb9960ee..8cf2343ea 100644 --- a/build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt +++ b/build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt @@ -45,15 +45,8 @@ import org.gradle.plugins.signing.SigningPlugin * The task `sourceTarball` (available on the root project) generates a source tarball using `git * archive`. * - * The task `releaseEmailTemplate` generates the release-vote email subject + body. Outputs on the - * console and in the `build/distributions/` directory. - * * Signing tip: If you want to use `gpg-agent`, set the `useGpgAgent` Gradle project property * - * The following command publishes the project artifacts to your local maven repository, generates - * the source tarball - and uses `gpg-agent` to sign all artifacts and the tarball. Note that this - * requires a Git tag! - * * ``` * ./gradlew publishToMavenLocal sourceTarball -Prelease -PuseGpgAgent * ``` diff --git a/build-logic/src/main/kotlin/publishing/rootProject.kt b/build-logic/src/main/kotlin/publishing/rootProject.kt index 2ef431efc..07c6a3267 100644 --- a/build-logic/src/main/kotlin/publishing/rootProject.kt +++ b/build-logic/src/main/kotlin/publishing/rootProject.kt @@ -19,14 +19,11 @@ package publishing -import io.github.gradlenexus.publishplugin.NexusPublishExtension import io.github.gradlenexus.publishplugin.NexusPublishPlugin -import io.github.gradlenexus.publishplugin.internal.StagingRepositoryDescriptorRegistryBuildService import org.gradle.api.Project -import org.gradle.api.services.BuildServiceRegistration +import org.gradle.api.tasks.Delete import org.gradle.api.tasks.Exec import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.register import org.gradle.plugins.signing.Sign @@ -41,11 +38,22 @@ internal fun configureOnRootProject(project: Project) = val isRelease = project.hasProperty("release") val isSigning = isRelease || project.hasProperty("signArtifacts") + val cleanDistributionsDir = tasks.register("cleanDistributionsDir") + cleanDistributionsDir.configure { + outputs.cacheIf { false } + + val e = project.extensions.getByType(PublishingHelperExtension::class.java) + delete(e.distributionDir) + } + val sourceTarball = tasks.register("sourceTarball") sourceTarball.configure { group = "build" description = "Generate a source tarball for a release to be uploaded to dist.apache.org/repos/dist" + outputs.cacheIf { false } + + dependsOn(cleanDistributionsDir) val e = project.extensions.getByType(PublishingHelperExtension::class.java) doFirst { mkdir(e.distributionDir) } @@ -84,129 +92,4 @@ internal fun configureOnRootProject(project: Project) = } sourceTarball.configure { finalizedBy(signSourceTarball) } } - - val releaseEmailTemplate = tasks.register("releaseEmailTemplate") - releaseEmailTemplate.configure { - group = "publishing" - description = - "Generate release-vote email subject + body, including the staging repository URL, if run during the Maven release." - - mustRunAfter("initializeApacheStagingRepository") - - doFirst { - val e = project.extensions.getByType(PublishingHelperExtension::class.java) - val asfName = e.asfProjectName.get() - - val gitInfo = MemoizedGitInfo.gitInfo(rootProject) - val gitCommitId = gitInfo["Apache-Polaris-Build-Git-Head"] - - val repos = project.extensions.getByType(NexusPublishExtension::class.java).repositories - val repo = repos.iterator().next() - - val stagingRepositoryUrlRegistryRegistration = - gradle.sharedServices.registrations.named< - BuildServiceRegistration - >( - "stagingRepositoryUrlRegistry" - ) - val staginRepoUrl = - if (stagingRepositoryUrlRegistryRegistration.isPresent) { - val stagingRepositoryUrlRegistryBuildServiceRegistration = - stagingRepositoryUrlRegistryRegistration.get() - val stagingRepositoryUrlRegistryService = - stagingRepositoryUrlRegistryBuildServiceRegistration.getService() - if (stagingRepositoryUrlRegistryService.isPresent) { - val registry = stagingRepositoryUrlRegistryService.get().registry - try { - val stagingRepoDesc = registry.get(repo.name) - val stagingRepoId = stagingRepoDesc.stagingRepositoryId - "https://repository.apache.org/content/repositories/$stagingRepoId/" - } catch (e: IllegalStateException) { - "NO STAGING REPOSITORY ($e)" - } - } else { - "NO STAGING REPOSITORY (no registry service) !!" - } - } else { - "NO STAGING REPOSITORY (no build service) !!" - } - - val asfProjectName = fetchAsfProjectName(asfName) - - val versionNoRc = version.toString().replace("-rc-?[0-9]+".toRegex(), "") - - val subjectFile = e.distributionFile("vote-email-subject.txt").relativeTo(projectDir) - val bodyFile = e.distributionFile("vote-email-body.txt").relativeTo(projectDir) - - val emailSubject = "[VOTE] Release $asfProjectName $version" - subjectFile.writeText(emailSubject) - - val emailBody = - """ - Hi everyone, - - I propose that we release the following RC as the official - $asfProjectName $versionNoRc release. - - * This corresponds to the tag: apache-$asfName-$version - * https://github.com/apache/$asfName/commits/apache-$asfName-$version - * https://github.com/apache/$asfName/tree/$gitCommitId - - The release tarball, signature, and checksums are here: - * https://dist.apache.org/repos/dist/dev/incubator/$asfName/apache-$asfName-$version - - You can find the KEYS file here: - * https://dist.apache.org/repos/dist/release/incubator/$asfName/KEYS - - Convenience binary artifacts are staged on Nexus. The Maven repository URL is: - * $staginRepoUrl - - Please download, verify, and test. - - Please vote in the next 72 hours. - - [ ] +1 Release this as Apache $asfName $version - [ ] +0 - [ ] -1 Do not release this because... - - Only PPMC members and mentors have binding votes, but other community members are - encouraged to cast non-binding votes. This vote will pass if there are - 3 binding +1 votes and more binding +1 votes than -1 votes. - - NB: if this vote pass, a new vote has to be started on the Incubator general mailing - list. - - Thanks - Regards - """ - - logger.lifecycle( - """ - - - The email for your release vote mail: - ------------------------------------- - - Suggested subject: (also in file $subjectFile) - - $emailSubject - - Suggested body: (also in file $bodyFile) - - $emailBody - - """ - .trimIndent() - ) - bodyFile.writeText(emailBody.trimIndent()) - } - } - - if (isRelease) { - sourceTarball.configure { finalizedBy(releaseEmailTemplate) } - } - - afterEvaluate { - tasks.named("closeApacheStagingRepository") { mustRunAfter(releaseEmailTemplate) } - } } diff --git a/releases/README.md b/releases/README.md new file mode 100644 index 000000000..612e830c9 --- /dev/null +++ b/releases/README.md @@ -0,0 +1,103 @@ +# Apache Polaris Release Infrastructure + +This directory holds all the infrastructure parts that are required to draft a Polaris release (RC) and to eventually +release it. + +Releases can be created from any Git commit, version branches are supported. + +Generally, releases at the ASF follow the following workflow: + +1. Draft a release +2. Start a VOTE on the dev mailing list +3. If the VOTE fails, the release has failed - "go to step 1" +4. If the VOTE passes, publish the release + +The Polaris project decided to run all release related via GitHub actions - a release is never drafted nor actually +released from a developer machine. + +## Technical process + +In the `release/bin/` directory are the scripts that are required to draft a release and to publish it as "GA". All +scripts can be called with the `--help` argument to get some usage information. There is also a `--dry-run` option +to inspect what _would_ happen. + +The technical process for a release follows the workflow above: + +1. `releases/bin/draft-release.sh --major --minor --commit ` + creates a release-candidate. The `--commit` argument is optional and defaults to the HEAD of the local Git + worktree. The patch version number and RC-number are generated automatically. This means, that RC-numbers are + automatically incremented as long as there is no "final" release tag. In that case, the patch version number + is incremented and the RC-number is set to 1. + + The Git tag name that will be used follows the pattern `polaris-..-RC` + + The content of the `version.txt` file is set to the full release version, for example `1.2.3`. + + Gradle will run with the arguments `-Prelease publishToApache closeApacheStagingRepository sourceTarball`. + Both the release artifacts (jars) and the source tarball are signed. The signing and Nexus credentials must be + provided externally using the `ORG_GRADLE_PROJECT_*` environment variables. In dry-run mode, this step runs + Gradle with the arguments `-PjarWithGitInfo -PsignArtifacts -PuseGpgAgent publishToMavenLocal sourceTarball publishToMavenLocal`. + + The staging repository ID, which is needed to release the staging repository, and URL are extracted from the + Gradle output and memoized in the files `releases/current-release-staging-repository-id` and + `releases/current-release-staging-repository-url`. The source Git commit ID is memoized in the file + `releases/current-release-commit-id`. + + The source tarball will be uploaded to the Apache infrastructure. + + Release notes will be generated and included in the file `site/content/in-dev/unreleasd/release-notes.md`. This + makes the release notes available later on the project website within the versioned docs. + + The suggested release VOTE email subject and body with the correct links and information are provided. + + The last step is to push the Git tag with a Git commit containing the above file changes. +2. Once the release VOTE passed: `releases/bin/publish-release.sh --major --minor `. + + The command will find the latest RC tag for the latest patch release of the given major/minor version. + + The final Git tag name that will be used follows the pattern `polaris-..` and created from + the latest RC-tag for that version. + + The Git tag is then pushed. + + Documentation pages for the release will then be copied from the `site/content/in-dev/unreleased` into the + `site/content/releases/..` folder within the `versioned-docs` branch. + The changes for the updated `versioned-docs` branch are then pushed to Git. + + The suggested ANNOUNCEMENT email subject and body are provided. + + Creating a release in GitHub is the last step. + +## Technical requirements + +To use the scripts in the `releases/bin/` folder, it is required to have a _full_ clone with all tags and branches. +Shallow clones, which is the default when checking out a Git repository in GitHub actions, will _not_ work properly! + +On top, all scripts need privileges to be able to push tags and branches to the GitHub repository - this is an +essential requirement for the scripts to do their job. + +The `draft-release.sh` and `publish-release.sh` scripts also require the following environment variables providing +the necessary secrets: + +* `ORG_GRADLE_PROJECT_signingKey` +* `ORG_GRADLE_PROJECT_signingPassword` +* `ORG_GRADLE_PROJECT_sonatypeUsername` +* `ORG_GRADLE_PROJECT_sonatypePassword` + +GitHub actions running the scripts must provide those secrets and privileges. + +## Version branches + +The Polaris project may use maintenance version branches following the pattern `release/.x` and +`release/.`. The scripts mentioned above are already support version branches and have validations for +this use case. Using version branches is not a requirement for the release scripts. + +The two scripts `releases/bin/create-release-branch.sh` plus the informative `releases/bin/list-release-branches.sh` +are there to help with version branches. The former must be invoked on the main branch and creates a new major-version +branch using the `release/.x` pattern. The latter must be invoked on a major-version branch and creates a new +minor version branch using the `release/.` pattern. + +# TODO + +* Close staging repo when RC is abandoned +* Adopt website (top bar menu, maintained releases, left site menu) diff --git a/releases/bin/_lib.sh b/releases/bin/_lib.sh new file mode 100644 index 000000000..2dc3f43e1 --- /dev/null +++ b/releases/bin/_lib.sh @@ -0,0 +1,113 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# Constants and generic functions for all release related scripts +# + +if [[ -z ${bin_dir} ]]; then + echo "bin_dir variable undefined, fix the issue in the scripts, aborting" > /dev/stderr + exit 1 +fi + +worktree_dir="$(realpath "${bin_dir}/../..")" +if [[ ! -f ${worktree_dir}/version.txt ]]; then + echo "Looks like ${worktree_dir}/version.txt does not exist, aborting" > /dev/stderr + exit 1 +fi + +# Constants +main_branch="releases-infra" +release_branch_prefix="release/" +release_branch_regex="^release\\/([0-9]+)[.](x|[0-9]+)$" +versioned_docs_branch="versioned-docs" +tag_prefix="apache-polaris-" +tag_regex="${tag_prefix}[0-9]+[.][0-9]+[.]([0-9]+)(-incubating)?(-rc([0-9]+))?" +# When going becoming a TLP, replace 'incubator/polaris' with 'polaris' ! +project_release_dir_part="incubator/polaris" +svn_dist_dev_repo="https://dist.apache.org/repos/dist/dev/${project_release_dir_part}/" +svn_dist_release_repo="https://dist.apache.org/repos/dist/release/${project_release_dir_part}/" + +svn_dir_dev="build/svn-source-dev" +svn_dir_release="build/svn-source-release" + + +cleanups=() + +function exit_cleanup_trap { + if [[ ${#cleanups[@]} -gt 0 ]]; then + start_group "Post script cleanup" + for cmd in "${cleanups[@]}"; do + echo "Running cleanup: $cmd" + $cmd + done + end_group + fi +} + +trap exit_cleanup_trap EXIT + +function get_podling_version_suffix { + local podling + podling="$(curl https://whimsy.apache.org/public/public_ldap_projects.json 2>/dev/null | jq --raw-output '.projects["polaris"]."podling"')" + if [[ ${podling} == "current" ]] ; then + echo "-incubating" + fi +} + +function start_group { + local heading + heading="${*}" + if [[ ${CI} ]] ; then + # For GitHub workflows + echo "::group::${heading}" + else + # For local runs + echo "" + echo "${heading}" + echo "-------------------------------------------------------------" + echo "" + fi +} + +function end_group { + if [[ ${CI} ]] ; then + # For GitHub workflows + echo "::endgroup::" + else + # For local runs + echo "" + echo "-------------------------------------------------------------" + echo "" + fi +} + +function exec_process { + local dry_run + dry_run=$1 + shift + + if [[ ${dry_run} -ne 1 ]]; then + echo "Executing '${*}'" + "$@" + return + else + echo "Dry-run, WOULD execute '${*}'" + fi +} diff --git a/releases/bin/_releases_lib.sh b/releases/bin/_releases_lib.sh new file mode 100644 index 000000000..8512b41f7 --- /dev/null +++ b/releases/bin/_releases_lib.sh @@ -0,0 +1,139 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# Additional functionality for release related scripts that deal with +# Git tags/branches and versions inferred to/from those, based on `_lib.sh`. +# +# Includes worktree checks (non-dirty, upstream, etc). +# + +if [[ -z ${bin_dir} ]]; then + echo "bin_dir variable undefined, fix the issue in the scripts, aborting" > /dev/stderr + exit 1 +fi + +. "${bin_dir}/_lib.sh" + +version_txt="$(cat ${worktree_dir}/version.txt)" +current_branch="$(git branch --show-current)" +upstream_name="$(git config branch."${current_branch}".remote)" + +function list_release_branches { + local prefix + local suffix + prefix="$1" + suffix="$2" + git ls-remote --branches "${upstream_name}" "${release_branch_prefix}${prefix}*${suffix}" | sed --regexp-extended 's/[0-9a-f]+\Wrefs\/heads\/(.*)/\1/' + return +} + +function list_release_tags { + local prefix + prefix="$1" + git ls-remote --tags "${upstream_name}" "${tag_prefix}${prefix}*" | sed --regexp-extended 's/[0-9a-f]+\Wrefs\/tags\/(.*)/\1/' + return +} + +function major_version_from_branch_name { + echo "$1" | sed --regexp-extended "s/${release_branch_regex}/\1/" +} + +function minor_version_from_branch_name { + echo "$1" | sed --regexp-extended "s/${release_branch_regex}/\2/" +} + +function patch_version_from_tag { + echo "$1" | sed --regexp-extended "s/${tag_regex}/\1/" +} + +function rc_iteration_from_tag { + echo "$1" | sed --regexp-extended "s/${tag_regex}/\4/" +} + +function get_max_patch_version { + local major + local minor + major="$1" + minor="$2" + max_patch=-1 + while read -r release_tag_name ; do + _patch="$(patch_version_from_tag "${release_tag_name}")" + [[ $_patch -gt $max_patch ]] && max_patch=$_patch + done < <(list_release_tags "${major}.${minor}.") + echo "${max_patch}" +} + +function get_max_rc_iteration { + local full_version + full_version="$1" + max_rc=-1 + while read -r release_tag_name ; do + _rc="$(rc_iteration_from_tag "${release_tag_name}")" + if [[ -z ${_rc} ]]; then + max_rc="-2" + break + fi + [[ $_rc -gt $max_rc ]] && max_rc=$_rc + done < <(list_release_tags "${full_version}") + echo "${max_rc}" +} + +function get_max_major_version { + max_major=-1 + while read -r release_branch_name ; do + _major="$(major_version_from_branch_name "${release_branch_name}")" + [[ $_major -gt $max_major ]] && max_major=$_major + done < <(list_release_branches "" ".x") + echo "${max_major}" +} + +function get_max_minor_version { + max_minor=-1 + while read -r release_branch_name ;do + _minor="$(minor_version_from_branch_name "${release_branch_name}")" + [[ $_minor -gt $max_minor ]] && max_minor=$_minor + done < <(list_release_branches "${version_major}" "") + echo "${max_minor}" +} + +branch_type= +version_major= +version_minor= +if [[ "${current_branch}" == "${main_branch}" ]]; then + branch_type="main" +elif echo "${current_branch}" | grep --extended-regexp --quiet "${release_branch_regex}"; then + version_major="$(major_version_from_branch_name "${current_branch}")" + version_minor="$(minor_version_from_branch_name "${current_branch}")" + [[ "x" == "${version_minor}" ]] && branch_type="major" || branch_type="minor" +else + echo "Current branch '${current_branch}' must be either the main branch '${main_branch}' or a release branch following exactly the pattern '${release_branch_prefix}.', aborting" > /dev/stderr + exit 1 +fi + +if [[ -z ${upstream_name} ]]; then + echo "Current branch '${current_branch}' has no remote, aborting" > /dev/stderr + exit 1 +fi + +if [[ ! -z "$(git status --untracked-files=no --porcelain)" ]]; then + echo "Current worktree has uncommitted changes, aborting" > /dev/stderr + git status --untracked-files=no --porcelain > /dev/stderr + exit 1 +fi diff --git a/releases/bin/create-release-branch.sh b/releases/bin/create-release-branch.sh new file mode 100755 index 000000000..34338f674 --- /dev/null +++ b/releases/bin/create-release-branch.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Create a new release version branch +# +# If called on the main branch, creates the release branch for the next major version. +# If called on a version branch, the tool errors out. + +set -e +bin_dir="$(dirname "$0")" +command_name="$(basename "$0")" +. "${bin_dir}/_releases_lib.sh" + +echo "Version in version.txt is '$version_txt'" +echo "Current branch is '${current_branch}' on remote '${upstream_name}'" +echo "" + +function usage { + cat << EOF +${command_name} [--major MAJOR_VERSION] [--minor MINOR_VERSION] [--commit GIT_COMMIT] [--recreate] [--dry-run] [--help | -h] + + Creates a new release branch using the pattern '${release_branch_prefix}/.'. + + The major and minor versions are determined from the current branch. + + When invoked from the main branch, a new major-version branch '${release_branch_prefix}/.x' will be created. + When invoked from a major-version branch, a new minor-version branch '${release_branch_prefix}/.' will be created. + + Options: + --commit GIT_COMMIT + The Git commit to draft the release from. Defaults to the current HEAD. + --recreate + Recreates the draft release if it already exists. + --dry-run + Do not update Git. Gradle will publish to the local Maven repository, but still sign the artifacts. + -h --help + Print usage information. +EOF +} + +dry_run= +recreate= +create_from_commit="$(git rev-parse HEAD)" +while [[ $# -gt 0 ]]; do + case $1 in + --commit) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --commit, aborting" > /dev/stderr + exit 1 + fi + create_from_commit="$1" + shift + ;; + --recreate) + recreate=1 + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option/argument $1" > /dev/stderr + usage > /dev/stderr + exit 1 + ;; + esac +done + +new_branch_name="" +case "${branch_type}" in + "main") + max_major="$(get_max_major_version)" + if [[ $max_major -eq -1 ]]; then + echo "No major release branch found" + new_branch_name="${release_branch_prefix}0.x" + else + echo "Latest major release branch is for version ${max_major}.x" + new_branch_name="${release_branch_prefix}$(( $max_major + 1 )).x" + fi + ;; + "major") + max_minor="$(get_max_minor_version)" + if [[ $max_minor -eq -1 ]]; then + echo "No minor release branch found for ${version_major}.x" + new_branch_name="${release_branch_prefix}${version_major}.0" + else + echo "Latest major release branch is for version ${max_major}.x" + new_branch_name="${release_branch_prefix}${version_major}.$(( $max_minor + 1))" + fi + ;; + "minor") + echo "On a minor version branch, aborting" > /dev/stderr + exit 1 + ;; + *) + echo "Unexpected branch type ${branch_type}" > /dev/stderr + exit 1 +esac + +if [[ -z ${new_branch_name} ]]; then + echo "Empty branch to create - internal error, aborting" > /dev/stderr + exit 1 +fi + +do_recreate= +if [[ ${recreate} ]]; then + if list_release_branches "" "" | grep --quiet "${new_branch_name}"; then + do_recreate=1 + fi +fi + +echo "" +if [[ ${dry_run} ]]; then + echo "Dry run, no changes will be made" +else + echo "Non-dry run, will update Git" +fi + +echo "" +echo "New branch name: ${new_branch_name}" +echo "From commit: ${create_from_commit}" +echo "" +git log -n1 "${create_from_commit}" +echo "" + +[[ ${do_recreate} ]] && exec_process "${dry_run}" git branch -D "${new_branch_name}" +exec_process "${dry_run}" git checkout -b "${new_branch_name}" "${create_from_commit}" + +[[ ${do_recreate} ]] && exec_process "${dry_run}" git push "${upstream_name}" --delete "${new_branch_name}" +exec_process "${dry_run}" git push --set-upstream "${upstream_name}" diff --git a/releases/bin/draft-release.sh b/releases/bin/draft-release.sh new file mode 100755 index 000000000..02e8f71d8 --- /dev/null +++ b/releases/bin/draft-release.sh @@ -0,0 +1,451 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e +bin_dir="$(dirname "$0")" +command_name="$(basename "$0")" +. "${bin_dir}/_releases_lib.sh" + +echo "Version in version.txt is '$version_txt'" +echo "Current branch is '${current_branch}' on remote '${upstream_name}'" +echo "" + +function usage { + cat << EOF +${command_name} + [--major MAJOR_VERSION] [--minor MINOR_VERSION] [--commit GIT_COMMIT] + [--previous-version PREVIOUS_VERSION] + [--recreate] + [--dry-run] + [--help | -h] + + Creates a release candidate. + + A new release candidate tag is created for the latest patch version for the major/minor version. + If no RC tag (following the '${tag_prefix}..-rcx' pattern) exists, an RC1 + will be created, otherwise the RC number will be incremented. If the latest patch version is + already promoted to GA, an RC1 for the next patch version will be created. + + Performs the Maven artifacts publication, Apache source tarball upload. Artifacts are signed, make + sure to have a compatible GPG key present. + + Release notes are generated via the external generate-release-notes.sh script, which can also be + invoked independently for development and testing purposes. + + Options: + --major MAJOR_VERSION + Major version number, must be specified when the command is called on the main branch. + --minor MINOR_VERSION + Minor version number, must be specified when the command is called on the main branch + or on a major version branch. + --commit GIT_COMMIT + The Git commit to draft the release from. Defaults to the current HEAD. + --previous-version PREVIOUS_VERSION + The full (major.minor.patch) version to use as the base to collect commits and contributor + information for the release notes file. + --recreate + Recreates the draft release if it already exists, to _replace_ an existing RC / reuse the RC iteration. + This should only be used in exceptional cases and never in production. + --dry-run + Do not update Git. Gradle will publish to the local Maven repository, but still sign the artifacts. + -h --help + Print usage information. +EOF +} + +dry_run= +recreate= +need_checkout= +new_major_version="${version_major}" +new_minor_version="${version_minor}" +create_from_commit="$(git rev-parse HEAD)" +while [[ $# -gt 0 ]]; do + case $1 in + --major) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --major, aborting" > /dev/stderr + exit 1 + fi + new_major_version="$1" + shift + ;; + --minor) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --minor, aborting" > /dev/stderr + exit 1 + fi + new_minor_version="$1" + shift + ;; + --commit) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --commit, aborting" > /dev/stderr + exit 1 + fi + create_from_commit="$1" + need_checkout=1 + shift + ;; + --previous-version) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --previous-version, aborting" > /dev/stderr + exit 1 + fi + release_notes_previous_version_full="$1" + shift + ;; + --recreate) + recreate=1 + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option/argument $1" > /dev/stderr + usage > /dev/stderr + exit 1 + ;; + esac +done + +new_tag_name="" +case "${branch_type}" in + "main") + if [[ -z ${new_major_version} || -z ${new_minor_version} ]]; then + echo "On the main branch, but specified no major and/or minor version using the '--major'/'--minor' arguments, aborting" > /dev/stderr + exit 1 + fi + ;; + "major") + if [[ ${version_major} -ne ${new_major_version} ]]; then + echo "On the major version branch ${version_major}, but specified '--major ${new_major_version}', must be on a the matching version branch, aborting" > /dev/stderr + exit 1 + fi + if [[ -z ${new_minor_version} ]]; then + echo "On the major version branch ${version_major}, but specified no minor version using the '--minor' argument, aborting" > /dev/stderr + exit 1 + fi + ;; + "minor") + if [[ ${version_major} -ne ${new_major_version} || ${version_minor} -ne ${new_minor_version} ]]; then + echo "On the minor version branch ${version_major}, but specified '--major ${new_major_version}', must be on a the matching version branch, aborting" > /dev/stderr + exit 1 + fi + ;; + *) + echo "Unexpected branch type ${branch_type}" > /dev/stderr + exit 1 +esac + +max_patch="$(get_max_patch_version ${new_major_version} ${new_minor_version})" +patch_version= +rc_iteration= +if [[ $max_patch -eq -1 ]]; then + # No previous patch release + patch_version=0 + rc_iteration=1 +else + max_rc="$(get_max_rc_iteration "${new_major_version}.${new_minor_version}.${max_patch}")" + if [[ $max_rc -eq -2 ]]; then + # that patch version is released, increase patch version + patch_version="$(( $max_patch + 1))" + rc_iteration=1 + elif [[ $max_rc -eq -1 ]]; then + echo "Unexpected result -1 from get_next_rc_iteration function, aborting" > /dev/stderr + exit 1 + else + patch_version="${max_patch}" + rc_iteration="$(( $max_rc + 1 ))" + fi +fi + +version_incubating="$(get_podling_version_suffix)" +version_full_base="${new_major_version}.${new_minor_version}.${patch_version}" +version_full="${version_full_base}${version_incubating}" +new_tag_name="${tag_prefix}${version_full_base}-rc${rc_iteration}" + +echo "" +gradleDryRunSigning="" +gradleReleaseArgs="" +if [[ ${dry_run} ]]; then + echo "Dry run, no changes will be made" + + # Only sign + use the GPG agent locally + [[ ${CI} ]] || gradleDryRunSigning="-PsignArtifacts -PuseGpgAgent" +else + echo "Non-dry run, will update Git" + + # Verify that the required secrets for the Maven publication are present. + if [[ ${CI} ]]; then + # Only publish to "Apache" from CI + gradleReleaseArgs="-Prelease publishToApache closeApacheStagingRepository" + + if [[ -z ${ORG_GRADLE_PROJECT_signingKey} || -z ${ORG_GRADLE_PROJECT_signingPassword} || -z ${ORG_GRADLE_PROJECT_sonatypeUsername} || -z ${ORG_GRADLE_PROJECT_sonatypePassword} ]] ; then + echo "One or more of the following required environment variables are missing:" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_signingKey} ]] && echo " ORG_GRADLE_PROJECT_signingKey" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_signingPassword} ]] && echo " ORG_GRADLE_PROJECT_signingPassword" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_sonatypeUsername} ]] && echo " ORG_GRADLE_PROJECT_sonatypeUsername" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_sonatypePassword} ]] && echo " ORG_GRADLE_PROJECT_sonatypePassword" > /dev/stderr + exit 1 + fi + else + # Only publish to "Apache" from CI, otherwise publish to local Maven repo + gradleReleaseArgs="-PsignArtifacts -PuseGpgAgent publishToMavenLocal" + fi +fi + +echo "" +echo "New version is: ${version_full}" +echo "RC iteration: ${rc_iteration}" +echo "New tag name is: ${new_tag_name}" +echo "From commit: ${create_from_commit}" +echo "" +git log -n1 "${create_from_commit}" +echo "" + +if [[ ${need_checkout} ]]; then + exec_process 0 git checkout "${create_from_commit}" +fi +cleanups+=("git reset --hard HEAD") +cleanups+=("git checkout ${current_branch}") + +if [[ -z ${new_tag_name} ]]; then + echo "Empty tag to create - internal error, aborting" > /dev/stderr + exit 1 +fi + +do_recreate= +if [[ ${recreate} ]]; then + if list_release_tags "" "" | grep --quiet "${new_tag_name}"; then + do_recreate=1 + fi +fi + +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" + + + +start_group "Detach Git worktree" +git checkout "${create_from_commit}" +end_group + + + +start_group "Update version.txt" +echo "Executing 'echo -n "${version_full}" > version.txt'" +echo -n "${version_full}" > version.txt +exec_process "${dry_run}" git add version.txt +end_group + + + +start_group "Create release notes" +releaseNotesFile="releases/current-release-notes.md" +export SKIP_DIRTY_WORKSPACE_CHECK=42 +echo "Release notes file: $releaseNotesFile" +echo "Executing 'releases/bin/generate-release-notes.sh --major ${new_major_version} --minor ${new_minor_version} --patch ${patch_version} --previous ${release_notes_previous_version_full}'" +"${worktree_dir}/releases/bin/generate-release-notes.sh" --major "${new_major_version}" --minor "${new_minor_version}" --patch "${patch_version}" --previous "${release_notes_previous_version_full}" > $releaseNotesFile +exec_process "${dry_run}" git add $releaseNotesFile +end_group + + + +start_group "Gradle publication" +stagingRepositoryId="DRY RUN - NOTHING HAS BEEN STAGED - NO STAGING REPOSITORY ID!" +stagingRepositoryUrl="DRY RUN - NOTHING HAS BEEN STAGED - NO STAGING REPOSITORY URL!" +if [[ ${dry_run} ]]; then + exec_process 0 ./gradlew publishToMavenLocal sourceTarball -PjarWithGitInfo ${gradleDryRunSigning} --stacktrace +else + exec_process "${dry_run}" ./gradlew ${gradleReleaseArgs} sourceTarball --stacktrace | tee build/gradle-release-build.log + if [[ ${CI} ]]; then + # Extract staging repository ID from log (only do this in CI) + # ... look for the log message similar to 'Created staging repository 'orgprojectnessie-1214' at https://oss.sonatype.org/service/local/repositories/orgprojectnessie-1214/content/' + stagingLogMsg="$(grep 'Created staging repository' build/gradle-release-build.log)" + stagingRepositoryId="$(echo $stagingLogMsg | sed --regexp-extended "s/^Created staging repository .([a-z0-9-]+). at (.*)/\1/")" + stagingRepositoryUrl="$(echo $stagingLogMsg | sed --regexp-extended "s/^Created staging repository .([a-z0-9-]+). at (.*)/\2/")" + fi +fi +# Memoize the commit-ID, staging-repository-ID+URL for later use and reference information +# The staging-repository-ID is required for the 'publish-release.sh' script to release the staging repository. +echo -n "${create_from_commit}" > releases/current-release-commit-id +echo -n "${stagingRepositoryId}" > releases/current-release-staging-repository-id +echo -n "${stagingRepositoryUrl}" > releases/current-release-staging-repository-url +exec_process "${dry_run}" git add releases/current-release-* +end_group + + + +start_group "Validate source tarball" +cd "${worktree_dir}" +cd build/distributions/ +shasum -a 512 -c "apache-polaris-$(cat "${worktree_dir}/version.txt").tar.gz.sha512" +end_group + + + +start_group "Upload source tarball" +cd "${worktree_dir}" +rm -rf "${svn_dir_dev}" +mkdir -p "${svn_dir_dev}" +cd "${svn_dir_dev}" +echo "Changed to directory $(pwd)" + +if [[ ${dry_run} == 1 || -z ${CI} ]]; then + # If not in CI and/or dry-run mode is being used, use a local SVN repo for simulation purposes + echo "Using local, temporary svn for 'dev' for dry-run mode - as a local replacement for ${svn_dist_dev_repo}" + svn_local_dummy_dev="$(realpath ../svn-local-dummy-dev)" + if [[ ! -d ${svn_local_dummy_dev} ]] ; then + exec_process 0 svnadmin create "${svn_local_dummy_dev}" + fi + exec_process 0 svn checkout "file://${svn_local_dummy_dev}" . +else + exec_process 0 svn checkout "${svn_dist_dev_repo}" . +fi + +if [[ -d "${version_full}" ]]; then + # Delete previous RC iterations + exec_process 0 svn rm --force "${version_full}" + exec_process 0 svn commit -m "Remove previous Polaris ${version_full} release candidate(s)" +fi +svn_rc_path="${version_full}/RC${rc_iteration}" +mkdir -p "${svn_rc_path}" + +echo "" +find ../distributions/ +echo "" + +# We can safely assume that the Gradle 'sourceTarball' task leaves only the relevant files in +# build/distributions and that no other files are present +cp ../distributions/* "${svn_rc_path}" +exec_process 0 svn add --force "${version_full}" "${svn_rc_path}" +exec_process 0 svn commit -m "Polaris ${version_full} release candidate ${rc_iteration}" + +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" +end_group + + + +start_group "Commit changes to Git" +exec_process "${dry_run}" git commit -m "[RELEASE] Version ${version_full}-rc${rc_iteration} + +Base release commit ID: ${create_from_commit} +Staging repository ID: ${stagingRepositoryId} +Staging repository URL: ${stagingRepositoryUrl} +Release notes in this commit in file: ${releaseNotesFile} +" +tag_commit_id="$(git rev-parse HEAD)" +end_group + + + +start_group "Create Git tag ${new_tag_name}" +[[ ${do_recreate} ]] && exec_process "${dry_run}" git tag -d "${new_tag_name}" +exec_process "${dry_run}" git tag "${new_tag_name}" "${tag_commit_id}" +end_group + + + +start_group "Release vote email" +echo "" +echo "Suggested release vote email subject:" +echo "=====================================" +echo "" +echo "[VOTE] Release Apache Polaris (Incubating) ${version_full}-rc${rc_iteration}" +echo "" +echo "" +echo "" +echo "Suggested Release vote email body:" +echo "==================================" +echo "" +cat << EOF +Hi everyone, + +I propose that we release the following RC as the official +Apache Polaris (Incubating) ${version_full} release. + +The commit ID is ${tag_commit_id} +* This corresponds to the tag: ${new_tag_name} +* https://github.com/apache/polaris/commits/${new_tag_name} +* https://github.com/apache/polaris/tree/${tag_commit_id} + +The release tarball, signature, and checksums are here: +* ${svn_dist_dev_repo}/${svn_rc_path}/ + +You can find the KEYS file here: +* ${svn_dist_release_repo}/KEYS + +Convenience binary artifacts are staged on Nexus. The Maven repository URL is: +* ${stagingRepositoryUrl} + +Please download, verify, and test. + +Please vote in the next 72 hours. + +[ ] +1 Release this as Apache Polaris ${version_full} +[ ] +0 +[ ] -1 Do not release this because... + +Only PPMC members and mentors have binding votes, but other community members +are encouraged to cast non-binding votes. This vote will pass, if there are +3 binding +1 votes and more binding +1 votes than -1 votes. + +NB: if this vote pass, a new vote has to be started on the Incubator general mailing +list. + +Thanks +Regards +EOF +echo "" +echo "" +end_group + + + +start_group "Push Git tag ${new_tag_name}" +[[ ${do_recreate} ]] && exec_process "${dry_run}" git push "${upstream_name}" --delete "${new_tag_name}" +exec_process "${dry_run}" git push "${upstream_name}" "${new_tag_name}" +end_group + + + +echo "" +if [[ ${dry_run} ]]; then + echo "***********************************" + echo "Draft-release finished successfully - but dry run was enabled, no changes were made to Git or SVN" + echo "***********************************" +else + echo "***********************************" + echo "Draft-release finished successfully" + echo "***********************************" +fi +echo "" +echo "" diff --git a/releases/bin/generate-release-notes.sh b/releases/bin/generate-release-notes.sh new file mode 100755 index 000000000..60f1c59ed --- /dev/null +++ b/releases/bin/generate-release-notes.sh @@ -0,0 +1,207 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e +bin_dir="$(dirname "$0")" +command_name="$(basename "$0")" +. "${bin_dir}/_lib.sh" + +new_major_version="" +new_minor_version="" +patch_version="" +prev_version_full="" + +function usage { + cat < --minor --patch --previous [] + + major-version Major version being released + minor-version Minor version being released + patch-version Patch version being released + previous-version-full Optional: a different major.minor.patch version of the PREVIOUS release + +If 'previous-version-full' is not given, it will be inferred from the new major/minor/patch version. +For a patch-version grater than 0, it will be new-patch-version minus 1. +For a minor-version grater than 0, it will be new-minor-version minus 1 and patch-version 0. +For a major-version grater than 0, it will be new-major-version minus 1 and minor-version 0 and patch-version 0. +! +} + +while [[ $# -gt 0 ]]; do + case $1 in + --major) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --major, aborting" > /dev/stderr + exit 1 + fi + new_major_version="$1" + shift + ;; + --minor) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --minor, aborting" > /dev/stderr + exit 1 + fi + new_minor_version="$1" + shift + ;; + --patch) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --patch, aborting" > /dev/stderr + exit 1 + fi + patch_version="$1" + shift + ;; + --previous) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --previous, aborting" > /dev/stderr + exit 1 + fi + prev_version_full="$1" + shift + ;; + *) + usage > /dev/stderr + exit 1 + ;; + esac +done + +if [[ -z ${new_major_version} ]]; then + echo "Mandatory --major option missing, aborting" > /dev/stderr + exit 1 +fi +if [[ -z ${new_minor_version} ]]; then + echo "Mandatory --minor option missing, aborting" > /dev/stderr + exit 1 +fi +if [[ -z ${patch_version} ]]; then + echo "Mandatory --patch option missing, aborting" > /dev/stderr + exit 1 +fi + +if [[ -z ${prev_version_full} ]]; then + if [[ ${patch_version} -gt 0 ]] ; then + prev_version_full="${new_major_version}.${new_minor_version}.$((${patch_version} - 1))" + since_label="Changes since Apache Polaris version ${new_major_version}.${new_minor_version}.$((${patch_version} - 1))" + elif [[ ${new_minor_version} -gt 0 ]] ; then + prev_version_full="${new_major_version}.$((${new_minor_version} - 1)).0" + since_label="Changes since Apache Polaris version ${new_major_version}.$((${new_minor_version} - 1))" + elif [[ ${new_minor_version} -gt 0 ]] ; then + prev_version_full="$((${new_major_version} - 1)).0.0" + since_label="Changes since Apache Polaris major version $((${new_major_version} - 1))" + else + prev_version_full="0.0.0" + fi +fi + +prev_tag="${tag_prefix}${prev_version_full}" + +version_full="${new_major_version}.${new_minor_version}.${patch_version}" + +total_contributor_count="$(curl https://api.github.com/repos/apache/polaris/contributors?per_page=5000 2>/dev/null | jq -r .[].login | grep --invert-match --extended-regexp "^(dependabot|renovate|sfc-).*" | sort | wc -l )" + +cat < /dev/stderr + exit 1 + fi + new_major_version="$1" + shift + ;; + --minor) + shift + if [[ $# -eq 0 ]]; then + echo "Missing argument for --minor, aborting" > /dev/stderr + exit 1 + fi + new_minor_version="$1" + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option/argument $1" > /dev/stderr + usage > /dev/stderr + exit 1 + ;; + esac +done + +new_tag_name="" +from_tag_name="" +case "${branch_type}" in + "main") + if [[ -z ${new_major_version} ]]; then + echo "On the main branch, but specified no major version using the '--major' argument, aborting" > /dev/stderr + exit 1 + fi + if [[ -z ${new_minor_version} ]]; then + echo "On the main branch, but specified no minor version using the '--minor' argument, aborting" > /dev/stderr + exit 1 + fi + ;; + "major") + if [[ ${version_major} -ne ${new_major_version} ]]; then + echo "On the major version branch ${version_major}, but specified '--major ${new_major_version}', must be on a the matching version branch, aborting" > /dev/stderr + exit 1 + fi + if [[ -z ${new_minor_version} ]]; then + echo "On the major version branch ${version_major}, but specified no minor version using the '--minor' argument, aborting" > /dev/stderr + exit 1 + fi + ;; + "minor") + if [[ ${version_major} -ne ${new_major_version} || ${version_minor} -ne ${new_minor_version} ]]; then + echo "On the minor version branch ${version_major}, but specified '--major ${new_major_version}', must be on a the matching version branch, aborting" > /dev/stderr + exit 1 + fi + ;; + *) + echo "Unexpected branch type ${branch_type}" > /dev/stderr + exit 1 +esac + +patch_version="$(get_max_patch_version ${new_major_version} ${new_minor_version})" +rc_iteration= +if [[ $patch_version -eq -1 ]]; then + # that patch version is released + echo "Version ${new_major_version}.${new_minor_version}.x has no drafted patch release, aborting" > /dev/stderr + exit 1 +else + version_full_base="${new_major_version}.${new_minor_version}.${patch_version}" + rc_iteration="$(get_max_rc_iteration "${version_full}")" + if [[ $rc_iteration -eq -2 ]]; then + # that patch version is released + echo "Version ${version_full} is already released, aborting" > /dev/stderr + exit 1 + elif [[ $rc_iteration -eq -1 ]]; then + echo "Unexpected result -1 from get_max_rc_iteration function, aborting" > /dev/stderr + exit 1 + fi +fi +version_incubating="$(get_podling_version_suffix)" +version_full="${version_full_base}${version_incubating}" +from_tag_name="${tag_prefix}${version_full_base}-rc${rc_iteration}" +new_tag_name="${tag_prefix}${version_full_base}" + +echo "" +if [[ ${dry_run} ]]; then + echo "Dry run, no changes will be made - except the versioned docs updates for local inspection" +else + echo "Non-dry run, will update Git" +fi + +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" + +echo "" +echo "From tag name is: ${from_tag_name}" +echo "New tag name is: ${new_tag_name}" +echo "" +git log -n1 "${from_tag_name}" +echo "" + +if [[ -z ${new_tag_name} || -z ${from_tag_name} ]]; then + echo "Empty tag to create - internal error, aborting" > /dev/stderr + exit 1 +fi + +exec_process 0 git checkout "${from_tag_name}" +cleanups+=("git checkout ${current_branch}") + + + +start_group "Upload source tarball" +cd "${worktree_dir}" +rm -rf "${svn_dir_dev}" +mkdir -p "${svn_dir_dev}" +cd "${svn_dir_dev}" +echo "Changed to directory $(pwd)" + +if [[ ${dry_run} == 1 || -z ${CI} ]]; then + # If not in CI and/or dry-run mode is being used, use a local SVN repo for simulation purposes + echo "Using local, temporary svn for 'dev' for dry-run mode - as a local replacement for ${svn_dist_dev_repo}" + svn_local_dummy_dev="$(realpath ../svn-local-dummy-dev)" + if [[ ! -d ${svn_local_dummy_dev} ]] ; then + echo "Expecting existing local dummy svn for 'dev' for dry-run mode, created by dry-run draft-release.sh" > /dev/stderr + exit 1 + fi + exec_process 0 svn checkout "file://${svn_local_dummy_dev}" . +else + exec_process 0 svn checkout "${svn_dist_dev_repo}" . +fi + +cd "${worktree_dir}" +rm -rf "${svn_dir_release}" +mkdir -p "${svn_dir_release}" +cd "${svn_dir_release}" +echo "Changed to directory $(pwd)" + +if [[ ${dry_run} == 1 || -z ${CI} ]]; then + # If not in CI and/or dry-run mode is being used, use a local SVN repo for simulation purposes + echo "Using local, temporary svn for 'release' for dry-run mode - as a local replacement for ${svn_dist_release_repo}" + svn_local_dummy_release="$(realpath ../svn-local-dummy-release)" + if [[ ! -d ${svn_local_dummy_release} ]]; then + exec_process 0 svnadmin create "${svn_local_dummy_release}" + fi + exec_process 0 svn checkout "file://${svn_local_dummy_release}" . +else + exec_process 0 svn checkout "${svn_dist_release_repo}" . +fi + +if [[ -d "${version_full}" ]]; then + # Delete previous patch versions + find "${new_major_version}.${new_minor_version}.*" -type f -exec svn rm --force {} + +fi +mkdir -p "${version_full}" +# Copy the source tarball files from the release candidate +cp ${worktree_dir}/${svn_dir_dev}/${version_full}/RC${rc_iteration}/* "${version_full}" +exec_process 0 svn add --force "${version_full}" +exec_process 0 svn commit -m "Polaris ${version_full} release" + +cd "${worktree_dir}/${svn_dir_dev}" +echo "Changed to directory $(pwd)" +exec_process 0 svn rm --force "${version_full}" +exec_process 0 svn commit -m "Remove RC${rc_iteration} for published Polaris ${version_full} release" + +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" +end_group + + + +start_group "Releasing staging repository" +if [[ ${CI} ]]; then + + if [[ -z ${ORG_GRADLE_PROJECT_sonatypeUsername} || -z ${ORG_GRADLE_PROJECT_sonatypePassword} ]] ; then + echo "One or more of the following required environment variables are missing:" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_sonatypeUsername} ]] && echo " ORG_GRADLE_PROJECT_sonatypeUsername" > /dev/stderr + [[ -z ${ORG_GRADLE_PROJECT_sonatypePassword} ]] && echo " ORG_GRADLE_PROJECT_sonatypePassword" > /dev/stderr + exit 1 + fi + + stagingRepositoryId="$(cat releases/current-release-staging-repository-id)" + exec_process "${dry_run}" ./gradlew releaseApacheStagingRepository --staging-repository-id "${stagingRepositoryId}" --stacktrace +else + # Don't release anything when running locally, just print the statement + exec_process 1 ./gradlew releaseApacheStagingRepository --staging-repository-id "" --stacktrace +fi +end_group + + + +start_group "Create Git tag ${new_tag_name}" +exec_process "${dry_run}" git tag "${new_tag_name}" "${from_tag_name}" +exec_process "${dry_run}" git push "${upstream_name}" "${new_tag_name}" +end_group + + + +start_group "Generate release version docs" +cd "${worktree_dir}/site" +echo "Changed to directory $(pwd)" + +echo "Checking out released version docs..." +bin/remove-releases.sh --force > /dev/null || true +bin/checkout-releases.sh + +# Copy release docs content +rel_docs_dir="content/releases/${version_full}" +mkdir "${rel_docs_dir}" +echo "Copying docs from content/in-dev/unreleased to ${rel_docs_dir}..." +(cd content/in-dev/unreleased ; tar cf - .) | (cd "${rel_docs_dir}" ; tar xf -) + +echo "Copying release notes file (potentially replacing existing one)" +cp "${worktree_dir}/releases/current-release-notes.md" "${rel_docs_dir}/release-notes.md" + +# Copy release_index.md template as _index.md for the new release +versioned_docs_index_md="${rel_docs_dir}/_index.md" +echo "Updating released version ${versioned_docs_index_md}..." +had_marker= +while read -r ln ; do + if [[ "${ln}" == "# RELEASE_INDEX_MARKER_DO_NOT_CHANGE" ]]; then + had_marker=1 + cat << EOF +--- +$(cat ../codestyle/copyright-header-hash.txt) +title: 'Polaris v${version_full}' +date: $(date --iso-8601=seconds) +params: + release_version: "${version_full}" +cascade: + exclude_search: false +EOF + fi + [[ ${had_marker} ]] && echo "${ln}" +done < content/in-dev/release_index.md > "${versioned_docs_index_md}" +end_group + + + +start_group "Announcement email" +echo "" +echo "----------------------------------------------------------------------------------------------------------------" +echo "" +echo "Suggested announcement email subject:" +echo "=====================================" +echo "" +echo "[ANNOUNCE] Apache Polaris release ${version_full}" +echo "" +echo "" +echo "" +echo "Suggested announcement email body:" +echo "==================================" +echo "" +cat << EOF +Hi everyone, + +I'm pleased to announce the release of Apache Polaris ${version_full}! + +Apache Polaris is an open-source, fully-featured catalog for Apache Iceberg™. It implements Iceberg's REST API, +enabling seamless multi-engine interoperability across a wide range of platforms, including Apache Doris™, +Apache Flink®, Apache Spark™, StarRocks, and Trino. + +This release can be downloaded from: https://dlcdn.apache.org/polaris/apache-polaris-${version_full}/apache-polaris-${version_full}.tar.gz + +Release notes at https://polaris.apache.org/release/${version_full}/release-notes +and https://github.com/apache/polaris/releases/tag/${new_tag_name}. + +Docs for the ${version_full} release are on https://polaris.apache.org/release/${version_full} + +Java artifacts are available from Maven Central. + +Thanks to everyone for contributing! +EOF +echo "" +echo "" +echo "----------------------------------------------------------------------------------------------------------------" +echo "" +echo "" + +cd content/releases +echo "Changed to directory $(pwd)" + +exec_process "${dry_run}" git add . +exec_process "${dry_run}" git commit -m "Add versioned docs for release ${version_full}" +exec_process "${dry_run}" git push + +cd "${worktree_dir}" +echo "Changed to directory $(pwd)" +end_group + + + + +start_group "GitHub release" +cd "${worktree_dir}" +if [[ ${CI} ]]; then + exec_process "${dry_run}" gh release create "${new_tag_name}" \ + --notes-file "${version_full}/release-notes.md" \ + --title "Apache Polaris ${version_full}" +else + # GitHub release only created from CI (dry-run always enabled locally) + exec_process 1 gh release create "${new_tag_name}" \ + --notes-file "${version_full}/release-notes.md" \ + --title "Apache Polaris ${version_full}" +fi +end_group + + + + +echo "" +if [[ ${dry_run} ]]; then + echo "*************************************" + echo "Publish-release finished successfully - but dry run was enabled, no changes were made to Git or SVN" + echo "*************************************" +else + echo "*************************************" + echo "Publish-release finished successfully" + echo "*************************************" +fi +echo "" +echo "" diff --git a/site/bin/checkout-releases.sh b/site/bin/checkout-releases.sh index 13bb1b318..610e89ed3 100755 --- a/site/bin/checkout-releases.sh +++ b/site/bin/checkout-releases.sh @@ -25,7 +25,7 @@ cd "$(dirname "$0")/.." git worktree prune if [[ -d content/releases ]] ; then - echo "Directory content/releases already exists" + echo "Directory content/releases already exists" > /dev/stderr exit 1 fi diff --git a/site/bin/remove-releases.sh b/site/bin/remove-releases.sh index 4a067d038..c8308aaa7 100755 --- a/site/bin/remove-releases.sh +++ b/site/bin/remove-releases.sh @@ -22,17 +22,36 @@ set -e cd "$(dirname "$0")/.." -if [[ ! -d content/releases ]] ; then - echo "Directory content/releases does not exists" - exit 1 -fi +force= +while [[ $# -gt 0 ]]; do + case $1 in + --force) + force=1 + shift + ;; + *) + echo "Unexpected argument '$1'" > /dev/stderr + exit 1 + ;; + esac +done -cd content/releases -if git diff-index --quiet HEAD -- ; then - cd ../.. +if [[ ${force} ]] ; then rm -rf content/releases git worktree prune else - echo "Directory content/releases contains uncommitted changes" - exit 1 + if [[ ! -d content/releases ]] ; then + echo "Directory content/releases does not exists" > /dev/stderr + exit 1 + fi + + cd content/releases + if git diff-index --quiet HEAD -- ; then + cd ../.. + rm -rf content/releases + git worktree prune + else + echo "Directory content/releases contains uncommitted changes" > /dev/stderr + exit 1 + fi fi diff --git a/site/content/in-dev/release_index.md b/site/content/in-dev/release_index.md index e8f38dbfa..59fb8f3fe 100644 --- a/site/content/in-dev/release_index.md +++ b/site/content/in-dev/release_index.md @@ -28,8 +28,11 @@ cascade: params: show_page_toc: true # This file will be copied as `_index.md` into a new release's versioned docs folder. +# RELEASE_INDEX_MARKER_DO_NOT_CHANGE --- -== Apache Polaris version {{< releaseVersion >}} +## Apache Polaris version {{< releaseVersion >}} + +[Release notes](./release-notes) can be found [here](./release-notes) Download from ... diff --git a/site/hugo.yaml b/site/hugo.yaml index 43836f2f5..78d3efc13 100644 --- a/site/hugo.yaml +++ b/site/hugo.yaml @@ -62,7 +62,7 @@ params: active_releases: # Mention all active releases here, in semver descending order - "1.0.0" - - "0.1.0" + - "0.1.1" ui: ul_show: 1