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