diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ac29a410..8557925b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,5 +49,15 @@ repos: "-j0", "--rcfile=.pylintrc", ] - exclude: ^(docs/|changelog.py) + exclude: ^(docs/) require_serial: true + + # Shell scripts: release tooling and repo githooks. Widen to all *.sh once legacy + # scripts under .buildkite/, recipes/, repo root, etc. pass shellcheck. + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.11.0.1 + hooks: + - id: shellcheck + name: shellcheck + args: [-x] + files: (^scripts/release/.*\.sh$) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 50e403f78..f060d6a78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,7 +60,7 @@ Once your changes and tests are ready to submit for review: Ensure that all tests pass by running `make check-all`. This runs sequentially lint checks, unit tests and integration tests. These can be executed in isolation using `make lint`, `make test` and `make it` respectively, in case you need to iterate over a subset of tests. - Note: Integration tests are much slower than unit tests and require `docker-compose`. + Note: Integration tests are much slower than unit tests and require `docker compose`. They also require **Java 21**; set `JAVA_HOME` (or `JAVA21_HOME`) to match. 3. Sign the Contributor License Agreement @@ -79,6 +79,10 @@ Then sit back and wait. There will probably be discussion about the pull request Note: Contributors belonging to the "Elastic" organization on Github can merge PRs themselves after getting a "LGTM" (Looks good to me); this workflow is similar to the established one in the Elasticsearch project. +## Release process (maintainers) + +Release preparation is automated via `make release` and scripts under `scripts/release/`; see the Makefile and script headers for details. + # Contributing to the Rally codebase **Repository:** [https://github.com/elastic/rally](https://github.com/elastic/rally) diff --git a/Makefile b/Makefile index 9d7dd6a57..cde9aa670 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,7 @@ check-all: all test-3.11 \ test-3.12 \ test-3.13 \ + test-release \ it \ it_serverless \ it_tracks_compat \ @@ -143,7 +144,7 @@ lint: venv precommit pre-commit: venv uv run -- pre-commit run -# It install a pre-commit hook in the project .git dir so modified files are checked before creating every commit. +# It installs a pre-commit hook in the project .git dir so modified files are checked before creating every commit. install-pre-commit: $(PRE_COMMIT_HOOK_PATH) $(PRE_COMMIT_HOOK_PATH): @@ -216,12 +217,18 @@ benchmark: venv # --- Release goals --- -release-checks: venv - $(VENV_ACTIVATE); ./release-checks.sh $(release_version) $(next_version) +# usage: e.g. make release-checks RELEASE_VERSION=X.Y.Z +release-checks test-release: venv + @if [ -z "$(RELEASE_VERSION)" ]; then echo "error: set RELEASE_VERSION (e.g. make release-checks RELEASE_VERSION=X.Y.Z)" >&2; exit 1; fi + $(MAKE) clean lint test-all docs + uv run -- ./scripts/release/prepare.sh --dry "$(RELEASE_VERSION)" -# usage: e.g. make release release_version=0.9.2 next_version=0.9.3 -release: venv release-checks clean docs lint test it - $(VENV_ACTIVATE); ./release.sh $(release_version) $(next_version) +# usage: e.g. make release RELEASE_VERSION=X.Y.Z +release: + @if [ -z "$(RELEASE_VERSION)" ]; then echo "error: set RELEASE_VERSION (e.g. make release RELEASE_VERSION=X.Y.Z)" >&2; exit 1; fi + uv run -- ./scripts/release/prepare.sh "$(RELEASE_VERSION)" + +# --- Other goals --- # This is a shortcut for creating a shell running inside the project virtual environment. sh: diff --git a/docs/developing.rst b/docs/developing.rst index 0b41102d7..80478e872 100644 --- a/docs/developing.rst +++ b/docs/developing.rst @@ -8,6 +8,7 @@ Install the following software packages: * `uv `_ * JDK version required to build Elasticsearch. Please refer to the `build setup requirements `_. + For running Rally's integration tests (e.g. ``make it`` or ``make it_tracks_compat``), ensure your environment uses **Java 21** (recent Rally versions use Java 21 in CI). Set ``JAVA_HOME`` or ``JAVA21_HOME`` accordingly. * `Docker `_ and on Linux additionally `docker-compose `_. * `jq `_ * git @@ -36,7 +37,7 @@ Rally uses automatic code formatters. You can apply them by running ``make forma However, consider using editor integrations to do it automatically: you'll need to configure `black `_ and `isort `_. -Also consider running `pre-commit install` to run lint as part of your git commits. +Also consider running ``make install-pre-commit`` to ensure quick verifications are being performed just before new Git commits are created. Automatic Updates ~~~~~~~~~~~~~~~~~ diff --git a/esrally/utils/io.py b/esrally/utils/io.py index 653a23c22..39cdb0a1e 100644 --- a/esrally/utils/io.py +++ b/esrally/utils/io.py @@ -24,6 +24,7 @@ import os import shutil import subprocess +import sys import tarfile import zipfile from collections.abc import Collection, Mapping, Sequence @@ -397,7 +398,10 @@ def _do_decompress_manually_with_lib(target_directory: str, filename: str, compr def _do_tar_decompress(target_directory: str, compressed_file: tarfile.TarFile) -> None: try: - compressed_file.extractall(path=target_directory, filter="tar") + if sys.version_info >= (3, 12): + compressed_file.extractall(path=target_directory, filter="tar") + else: + compressed_file.extractall(path=target_directory) except Exception: raise RuntimeError(f"Could not decompress provided archive [{compressed_file.name!r}]. Please check if it is a valid tar file.") finally: diff --git a/prepare-release.sh b/prepare-release.sh deleted file mode 100755 index 7a71dddf1..000000000 --- a/prepare-release.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash - -# Licensed to Elasticsearch B.V. under one or more contributor -# license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright -# ownership. Elasticsearch B.V. 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. - -# fail this script immediately if any command fails with a non-zero exit code -set -eu - -RELEASE_VERSION=$1 -__NOTICE_OUTPUT_FILE="NOTICE.txt" - - -echo "=============================" -echo "Preparing Rally release $RELEASE_VERSION" -echo "=============================" - -echo "Preparing ${__NOTICE_OUTPUT_FILE}" -source create-notice.sh - -echo "Updating author information" -git log --format='%aN' | sort -fu > AUTHORS -# This will produce a non-zero exit code iff there are changes. -# Obviously we should disable exiting on error temporarily. -set +e -git diff --exit-code -set -e - -echo "Updating changelog" -# For exit on error to work we have to separate -# CHANGELOG.md generation into two steps. -CHANGELOG="$(python3 changelog.py ${RELEASE_VERSION})" -printf "$CHANGELOG\n\n$(cat CHANGELOG.md)" > CHANGELOG.md - -echo "Updating release version number" -printf '__version__ = "%s"\n' $RELEASE_VERSION > esrally/_version.py -git commit -a -m "Bump version to $RELEASE_VERSION" - -pip install --editable . - -# Check version -if ! [[ $(esrally --version) =~ "esrally ${RELEASE_VERSION} (git revision" ]] -then - echo "ERROR: Rally version string [$(esrally --version)] does not start with expected version string [esrally $RELEASE_VERSION]" - exit 2 -fi - -echo "" -echo "====================" -echo "Please open a pull request for ${RELEASE_VERSION}" -echo "====================" diff --git a/pyproject.toml b/pyproject.toml index 33f106760..f1c58ceb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ classifiers = [ ] ################################################################################################ # -# Adapt `create-notice.sh` whenever changing dependencies here. +# Adapt `scripts/release/create-notice.sh` whenever changing dependencies here. # # That script grabs all license files so we include them in the notice file. # @@ -93,7 +93,7 @@ dependencies = [ "hatchling==1.6.0", "wheel==0.46.2", # License: MIT — Rally runs `python -m pip install` to install track dependencies (see esrally.track.loader). - "pip>=24.0", + "pip==26.0.1", ] [project.optional-dependencies] diff --git a/release-checks.sh b/release-checks.sh deleted file mode 100755 index 73e1d89c8..000000000 --- a/release-checks.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash - -# Licensed to Elasticsearch B.V. under one or more contributor -# license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright -# ownership. Elasticsearch B.V. 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. - -# fail this script immediately if any command fails with a non-zero exit code -set -eu -RELEASE_VERSION=$1 - -KERNEL_NAME=$(uname -s) -if [[ ${KERNEL_NAME} != *"Linux"* ]] -then - echo "Error: release needs to be run on a Linux workstation but you are running on ${KERNEL_NAME}." - echo "Switch to a Linux workstation and try again." - exit 1 -fi - -# test number of parameters -if [[ $# != 2 ]] -then - echo "Usage: make release release_version=RELEASE_VERSION next_version=NEXT_VERSION" - exit 1 -fi - -if ! git config user.signingkey >/dev/null -then - echo "Error: the variable user.signingkey is not configured for git on this system." - echo "The release process requires a valid gpg key configured both locally and on GitHub." - echo "Please follow the instructions in https://git-scm.com/book/id/v2/Git-Tools-Signing-Your-Work" - echo "to set your gpg for git." - exit 1 -fi - -if [[ ! -f ~/.github/rally_release_changelog.token ]] -then - echo "Error: didn't find a valid GitHub token in ~/.github/rally_release_changelog.token." - echo "The release process requires a valid GitHub token." - exit 1 -fi - -if [[ ! -v GPG_TTY ]] -then - echo "Error: env variable GPG_TTY is not set. Please execute export GPG_TTY=\$(tty)" - exit 1 -fi - -ORIGIN_URL=$(git remote get-url --push origin) -if [[ ${ORIGIN_URL} != *"elastic/rally"* ]] -then - echo "Error: the git remote [origin] does not point to Rally's main repo at elastic/rally but to [${ORIGIN_URL}]." - exit 1 -fi - -# Check if there will be any errors during CHANGELOG.md generation -CHANGELOG="$(python3 changelog.py ${RELEASE_VERSION})" - diff --git a/scripts/offline-install.sh b/scripts/offline-install.sh index fc6999b10..f0a7713bf 100755 --- a/scripts/offline-install.sh +++ b/scripts/offline-install.sh @@ -70,7 +70,7 @@ function main { echo "Preparing NOTICE file" __NOTICE_OUTPUT_FILE="${ABSOLUTE_DOWNLOAD_BIN_DIR}/NOTICE.txt" - source "${SCRIPT_SRC_HOME}/../create-notice.sh" + source "${SCRIPT_SRC_HOME}/release/create-notice.sh" # create an offline install script cat >"${install_script}" < 0: print("There are [%d] open issues on milestone [%s]. Aborting..." % (milestone.open_issues, milestone_name), file=sys.stderr) - exit(2) + sys.exit(2) - print("### %s\n" % milestone_name) + print("changelog.py: generating changelog for milestone [%s] (markdown on stdout)" % milestone_name, file=sys.stderr) + print("### %s\n" % milestone_name, file=sys.stdout) print_category("Highlights", prs(gh, milestone, with_labels="highlight")) print_category( @@ -112,4 +153,8 @@ def main(): if __name__ == "__main__": - main() + try: + main() + except Exception as e: + print("changelog.py: error: %s" % e, file=sys.stderr) + sys.exit(1) diff --git a/create-notice.sh b/scripts/release/create-notice.sh similarity index 100% rename from create-notice.sh rename to scripts/release/create-notice.sh diff --git a/scripts/release/prepare.sh b/scripts/release/prepare.sh new file mode 100755 index 000000000..3d977df8a --- /dev/null +++ b/scripts/release/prepare.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash + +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. 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. + +# fail this script immediately if any command fails with a non-zero exit code +set -eu + +DRY_RUN=0 +if [[ "${1:-}" == "--dry" ]]; then + DRY_RUN=1 + shift +fi + +if [[ $# -ne 1 ]]; then + echo "usage: $0 [--dry] " >&2 + echo "example: $0 2.13.0" >&2 + echo "example: $0 --dry 2.13.0" >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +RELEASE_VERSION=$1 + +echo "=============================" +if [[ "$DRY_RUN" -eq 1 ]]; then + echo "Dry run: preparing Rally release $RELEASE_VERSION" +else + echo "Preparing Rally release $RELEASE_VERSION" +fi +echo "=============================" + +if [[ "$DRY_RUN" -eq 1 ]]; then + _tmp_notice=$(mktemp) + trap 'rm -f "${_tmp_notice:-}"' EXIT + __NOTICE_OUTPUT_FILE="$_tmp_notice" + echo "Preparing notice (dry run → temporary file, not NOTICE.txt)" >&2 +else + __NOTICE_OUTPUT_FILE="NOTICE.txt" + echo "Preparing ${__NOTICE_OUTPUT_FILE}" +fi +# shellcheck source=scripts/release/create-notice.sh +source "${SCRIPT_DIR}/create-notice.sh" +if [[ "$DRY_RUN" -eq 1 ]]; then + trap - EXIT + rm -f "$_tmp_notice" +fi + +if [[ "$DRY_RUN" -eq 1 ]]; then + echo "dry-run: skipping AUTHORS (requires git)." >&2 +else + echo "Updating author information" + git log --format='%aN' | sort -fu > AUTHORS +fi + +echo "Updating changelog" +if [[ "$DRY_RUN" -eq 1 ]]; then + python3 "${SCRIPT_DIR}/changelog.py" --dry "${RELEASE_VERSION}" >/dev/null || { + _changelog_ec=$? + echo "error: changelog.py failed for milestone ${RELEASE_VERSION} (dry run); see messages above." >&2 + exit "$_changelog_ec" + } +else + # For exit on error we check the substitution explicitly (set -e is unreliable here). + CHANGELOG="$(python3 "${SCRIPT_DIR}/changelog.py" "${RELEASE_VERSION}")" || { + _changelog_ec=$? + echo "error: changelog.py failed for milestone ${RELEASE_VERSION}; see messages above." >&2 + exit "$_changelog_ec" + } + printf '%s\n\n%s' "$CHANGELOG" "$(cat CHANGELOG.md)" > CHANGELOG.md +fi + +if [[ "$DRY_RUN" -eq 1 ]]; then + echo "dry-run: would write esrally/_version.py: __version__ = \"$RELEASE_VERSION\"" >&2 + echo "dry-run: skipping git add/commit, pip install, and esrally --version check." >&2 + echo "" + echo "====================" + echo "Dry run finished for ${RELEASE_VERSION}" + echo "====================" + exit 0 +fi + +echo "Updating release version number" +printf '__version__ = "%s"\n' "$RELEASE_VERSION" > esrally/_version.py + +# Stage only files this script generates. Avoids `git commit -a`, which would include any +# other dirty tracked files if the tree was not clean. +git add AUTHORS CHANGELOG.md esrally/_version.py +while IFS= read -r staged; do + [[ -z "$staged" ]] && continue + case "$staged" in + AUTHORS|CHANGELOG.md|esrally/_version.py) ;; + *) + echo "error: staged file outside release allowlist: $staged" >&2 + echo "Unstage unrelated changes (e.g. git restore --staged ) or start from a clean tree." >&2 + exit 1 + ;; + esac +done < <(git diff --cached --name-only) + +# Non-empty PREPARE_RELEASE_NO_VERIFY adds --no-verify (e.g. scripts/release/prepare-docker.sh). +git commit -m "Bump version to $RELEASE_VERSION" ${PREPARE_RELEASE_NO_VERIFY:+--no-verify} + +pip install --editable . + +# Check version +_version_out=$(esrally --version) +_version_needle="esrally ${RELEASE_VERSION} (git revision" +case "$_version_out" in +*"${_version_needle}"*) ;; +*) + echo "ERROR: Rally version string [${_version_out}] does not start with expected version string [esrally ${RELEASE_VERSION}]" + exit 2 + ;; +esac + +echo "" +echo "====================" +echo "Please open a pull request for ${RELEASE_VERSION}" +echo "====================" diff --git a/uv.lock b/uv.lock index bead37ad5..1c4e07c6e 100644 --- a/uv.lock +++ b/uv.lock @@ -700,7 +700,7 @@ requires-dist = [ { name = "jsonschema", specifier = "==3.1.1" }, { name = "markupsafe", specifier = "==2.0.1" }, { name = "mypy", marker = "extra == 'develop'", specifier = "==1.15.0" }, - { name = "pip", specifier = ">=24.0" }, + { name = "pip", specifier = "==26.0.1" }, { name = "pre-commit", marker = "extra == 'develop'", specifier = "==2.20.0" }, { name = "psutil", specifier = "==5.9.4" }, { name = "py-cpuinfo", specifier = "==7.0.0" },