blob: c6ea2b6fc6f5035f4f1d9601784af9ad1f726c6b [file]
#!/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.
# =============================================================================
# Apache Hamilton Release Candidate Validation Script
#
# Validates an Apache Hamilton release candidate by:
# 1. Downloading artifacts from Apache SVN
# 2. Importing signing keys and verifying GPG signatures
# 3. Verifying SHA512 checksums
# 4. Checking Apache license headers with Apache RAT
# 5. Building from source
# 6. Running unit tests and plugin tests
# 7. Running representative examples
#
# Prerequisites: svn, gpg, java (for RAT), uv, curl
#
# Usage:
# ./verification-script.sh # defaults: version=1.90.0 rc=0
# ./verification-script.sh 1.90.0 0 # explicit version and RC
# ./verification-script.sh 1.90.0 1 # validate RC1
# =============================================================================
set -euo pipefail
VERSION="${1:-1.90.0}"
RC="${2:-0}"
PACKAGE="apache-hamilton"
PYTHON_VERSION="${PYTHON_VERSION:-3.11}"
# Derived names
SRC_TAR="${PACKAGE}-${VERSION}-incubating-src.tar.gz"
WHEEL_NAME="$(echo "${PACKAGE}" | tr '-' '_')-${VERSION}-py3-none-any.whl"
EXTRACTED_DIR="$(echo "${PACKAGE}" | tr '-' '_')-${VERSION}"
WORK_DIR="hamilton-rc${RC}-validation"
RAT_VERSION="0.15"
RAT_JAR="apache-rat-${RAT_VERSION}.jar"
# Updated .rat-excludes content (from commit 518a516)
RAT_EXCLUDES_CONTENT='# Requirements/data files (not source code)
.*\.txt
.*\.jsonl
# SPDX short license template (is itself a license reference)
SHORT_LICENSE\.md
# Data files (not source code)
.*\.json
.*\.csv
.*\.fwf
.*\.xml
# Git and version control config
\.gitignore
\.rat-excludes
# Third-party vendored code with its own license (MIT)
.*databackend\.py
# PKG-INFO (generated metadata)
PKG-INFO'
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
PASS_COUNT=0
FAIL_COUNT=0
WARN_COUNT=0
pass_check() {
echo -e " ${GREEN}PASS${NC}: $1"
PASS_COUNT=$((PASS_COUNT + 1))
}
fail_check() {
echo -e " ${RED}FAIL${NC}: $1"
FAIL_COUNT=$((FAIL_COUNT + 1))
}
warn_check() {
echo -e " ${YELLOW}WARN${NC}: $1"
WARN_COUNT=$((WARN_COUNT + 1))
}
section() {
echo ""
echo -e "${BLUE}================================================================${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}================================================================${NC}"
echo ""
}
# =============================================================================
# Check prerequisites
# =============================================================================
section "Checking Prerequisites"
for cmd in svn gpg java curl uv; do
if command -v "$cmd" &>/dev/null; then
pass_check "$cmd found: $(command -v "$cmd")"
else
fail_check "$cmd not found - please install it"
fi
done
if [ "$FAIL_COUNT" -gt 0 ]; then
echo -e "\n${RED}Missing prerequisites. Please install them and retry.${NC}"
exit 1
fi
# Ensure GPG can prompt for passphrase
export GPG_TTY=$(tty)
# =============================================================================
# Step 1: Download artifacts
# =============================================================================
section "Step 1: Downloading Artifacts from SVN"
if [ -d "$WORK_DIR" ]; then
echo "Removing existing work directory: $WORK_DIR"
rm -rf "$WORK_DIR"
fi
SVN_URL="https://dist.apache.org/repos/dist/dev/incubator/hamilton/${PACKAGE}/${VERSION}-RC${RC}/"
echo "SVN URL: $SVN_URL"
if svn export "$SVN_URL" "$WORK_DIR" 2>&1; then
pass_check "Artifacts downloaded from SVN"
else
fail_check "Failed to download artifacts from SVN"
exit 1
fi
cd "$WORK_DIR"
echo ""
echo "Downloaded artifacts:"
ls -la
# =============================================================================
# Step 2: Import KEYS
# =============================================================================
section "Step 2: Importing Signing Keys"
curl -sO https://downloads.apache.org/incubator/hamilton/KEYS
if gpg --import KEYS 2>&1; then
pass_check "KEYS imported into GPG keyring"
else
fail_check "Failed to import KEYS"
fi
# =============================================================================
# Step 3: Verify GPG Signatures
# =============================================================================
section "Step 3: Verifying GPG Signatures"
for artifact in "$SRC_TAR" "$WHEEL_NAME"; do
if [ -f "$artifact" ] && [ -f "${artifact}.asc" ]; then
if gpg --verify "${artifact}.asc" "$artifact" 2>&1; then
pass_check "GPG signature valid: $artifact"
else
fail_check "GPG signature INVALID: $artifact"
fi
else
fail_check "Missing artifact or signature: $artifact"
fi
done
# =============================================================================
# Step 4: Verify SHA512 Checksums
# =============================================================================
section "Step 4: Verifying SHA512 Checksums"
for artifact in "$SRC_TAR" "$WHEEL_NAME"; do
if [ -f "$artifact" ] && [ -f "${artifact}.sha512" ]; then
expected=$(tr -d '[:space:]' < "${artifact}.sha512")
actual=$(shasum -a 512 "$artifact" | awk '{print $1}')
if [ "$expected" = "$actual" ]; then
pass_check "SHA512 checksum matches: $artifact"
else
fail_check "SHA512 checksum MISMATCH: $artifact"
echo " Expected: $expected"
echo " Actual: $actual"
fi
else
fail_check "Missing artifact or checksum file: $artifact"
fi
done
# =============================================================================
# Step 5: Extract and Set Up Environment
# =============================================================================
section "Step 5: Extracting Source and Setting Up Environment"
tar -xzf "$SRC_TAR"
if [ -d "$EXTRACTED_DIR" ]; then
pass_check "Source archive extracted: $EXTRACTED_DIR"
else
fail_check "Extraction failed - directory not found: $EXTRACTED_DIR"
exit 1
fi
cd "$EXTRACTED_DIR"
# Check required files
for f in LICENSE NOTICE DISCLAIMER README.md pyproject.toml; do
if [ -f "$f" ]; then
pass_check "Required file present: $f"
else
fail_check "Required file missing: $f"
fi
done
# Set up Python environment
echo ""
echo "Setting up Python $PYTHON_VERSION environment..."
uv venv --python "$PYTHON_VERSION" --quiet 2>&1
uv sync --group release 2>&1
pass_check "Python environment created and release deps installed"
# =============================================================================
# Step 6: Apache RAT License Check
# =============================================================================
section "Step 6: Checking License Headers with Apache RAT"
curl -sO "https://repo1.maven.org/maven2/org/apache/rat/apache-rat/${RAT_VERSION}/apache-rat-${RAT_VERSION}.jar"
if [ -f "$RAT_JAR" ]; then
pass_check "Apache RAT downloaded"
else
fail_check "Failed to download Apache RAT"
fi
# Write the updated .rat-excludes
echo "$RAT_EXCLUDES_CONTENT" > .rat-excludes
# Copy artifacts into dist/ for the verification script
mkdir -p dist
cp ../"$SRC_TAR"* dist/
cp ../"$WHEEL_NAME"* dist/
# Run verification script
echo ""
echo "Running automated verification script..."
if uv run python scripts/verify_apache_artifacts.py all --rat-jar "$RAT_JAR" 2>&1; then
pass_check "Automated verification (signatures + checksums + licenses)"
else
fail_check "Automated verification failed"
fi
# Twine check
echo ""
if uv run python scripts/verify_apache_artifacts.py twine-check 2>&1; then
pass_check "Twine wheel metadata validation"
else
fail_check "Twine check failed"
fi
# =============================================================================
# Step 7: Build from Source
# =============================================================================
section "Step 7: Building from Source"
if uv run flit build --no-use-vcs 2>&1; then
pass_check "Built sdist and wheel from source"
else
fail_check "Build from source failed"
fi
# Install the freshly built wheel
if uv pip install "dist/${WHEEL_NAME}" --force-reinstall 2>&1; then
pass_check "Installed built wheel"
else
fail_check "Failed to install built wheel"
fi
# Verify version
INSTALLED_VERSION=$(uv run python -c "import hamilton; print(hamilton.version.VERSION)" 2>&1)
echo "Installed version: $INSTALLED_VERSION"
if echo "$INSTALLED_VERSION" | grep -q "${VERSION%%.*}"; then
pass_check "Version check: $INSTALLED_VERSION"
else
warn_check "Version string may not match expected: $INSTALLED_VERSION"
fi
# =============================================================================
# Step 8: Run Unit Tests
# =============================================================================
section "Step 8: Running Unit Tests"
uv sync --group test 2>&1
echo ""
echo "Running core unit tests..."
# The plotly static writer test requires a working chromium/kaleido setup and
# can leave zombie processes that hang the script, so we skip it here.
if uv run pytest tests/ -q \
--deselect tests/plugins/test_plotly_extensions.py::test_plotly_static_writer \
--timeout=120 2>&1; then
pass_check "Core unit tests"
else
fail_check "Core unit tests (check output above)"
fi
echo ""
echo "Running plugin tests (skipping ray/spark/vaex - optional deps)..."
if uv run pytest plugin_tests/ -q \
--ignore=plugin_tests/h_ray \
--ignore=plugin_tests/h_spark \
--ignore=plugin_tests/h_vaex \
--timeout=120 2>&1; then
pass_check "Plugin tests (polars, pandas, etc.)"
else
warn_check "Some plugin tests failed (check output above)"
fi
# =============================================================================
# Step 9: Run Examples
# =============================================================================
section "Step 9: Running Representative Examples"
# Hello World
echo "--- Hello World ---"
if uv run python examples/hello_world/my_script.py 2>&1; then
pass_check "Example: hello_world"
else
fail_check "Example: hello_world"
fi
# Reusing Functions
echo ""
echo "--- Reusing Functions ---"
if uv run python examples/reusing_functions/main.py 2>&1; then
pass_check "Example: reusing_functions"
else
fail_check "Example: reusing_functions"
fi
# Schema
echo ""
echo "--- Schema ---"
if uv run python examples/schema/dataflow.py 2>&1; then
pass_check "Example: schema"
else
fail_check "Example: schema"
fi
# Data Quality (needs to run from its directory for CSV path)
echo ""
echo "--- Data Quality ---"
ORIG_DIR=$(pwd)
cd examples/data_quality/simple
if uv run python run.py 2>&1; then
pass_check "Example: data_quality"
else
fail_check "Example: data_quality"
fi
cd "$ORIG_DIR"
# Pandas Materialization
echo ""
echo "--- Pandas Materialization ---"
if uv run python examples/pandas/materialization/my_script.py 2>&1; then
pass_check "Example: pandas/materialization"
else
fail_check "Example: pandas/materialization"
fi
# =============================================================================
# Summary
# =============================================================================
section "Validation Summary for ${PACKAGE} ${VERSION}-incubating RC${RC}"
echo -e " ${GREEN}Passed: ${PASS_COUNT}${NC}"
echo -e " ${RED}Failed: ${FAIL_COUNT}${NC}"
echo -e " ${YELLOW}Warnings: ${WARN_COUNT}${NC}"
echo ""
if [ "$FAIL_COUNT" -eq 0 ]; then
echo -e "${GREEN}All checks passed! This RC looks good for a +1 (binding/non-binding) vote.${NC}"
echo ""
echo "Sample vote email:"
echo "---"
echo "+1 (non-binding)"
echo ""
echo "I verified the following:"
echo "- GPG signatures and SHA512 checksums: PASS"
echo "- Apache RAT license headers: PASS (0 issues)"
echo "- Build from source with flit: PASS"
echo "- Unit tests: PASS"
echo "- Plugin tests (polars, pandas): PASS"
echo "- Representative examples: PASS"
echo "- LICENSE, NOTICE, DISCLAIMER present and correct"
echo "---"
exit 0
else
echo -e "${RED}Some checks failed. Review the output above before voting.${NC}"
exit 1
fi