blob: 7006fa4669ce996a5d18d67548eda1961b74312b [file] [log] [blame]
#!/usr/bin/env python3
# ***************************************************************************************************************************
# * 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. *
# ***************************************************************************************************************************
"""
Release Documentation to Staging Branch
This script:
1. Runs build-docs.py to build the documentation
2. Checks out the asf-staging branch to a temporary directory
3. Copies the contents of docs/build to the temp directory
4. Adds and commits the changes
5. Pushes to the remote asf-staging branch
Usage:
python3 scripts/release-docs-stage.py [--no-push] [--commit-message MESSAGE]
Options:
--no-push Build and commit but don't push to remote
--commit-message Custom commit message (default: "Deploy documentation staging")
"""
import argparse
import os
import platform
import shutil
import subprocess
import sys
import webbrowser
from pathlib import Path
def run_command(cmd, cwd=None, check=True, description=None):
"""Run a shell command and handle errors."""
if description:
print(f"\n{description}")
print(f"Running: {' '.join(cmd) if isinstance(cmd, list) else cmd}")
try:
result = subprocess.run(
cmd,
cwd=cwd,
shell=isinstance(cmd, str),
check=check,
capture_output=False,
text=True
)
# Check return code even if check=False
if result.returncode != 0:
if description:
print(f"❌ {description} - FAILED (exit code: {result.returncode})")
return False
if description:
print(f"✅ {description} - SUCCESS")
return True
except subprocess.CalledProcessError as e:
if description:
print(f"❌ {description} - FAILED (exit code: {e.returncode})")
return False
except Exception as e:
if description:
print(f"❌ {description} - FAILED: {e}")
return False
def get_git_remote_url():
"""Get the git remote URL."""
try:
result = subprocess.run(
["git", "config", "--get", "remote.origin.url"],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except Exception:
return None
def play_sound(success=True):
"""
Play a system sound to indicate success or failure.
Args:
success: True for success sound, False for failure sound
"""
try:
system = platform.system()
if system == "Darwin": # macOS
if success:
# Success sound
sound_path = "/System/Library/Sounds/Glass.aiff"
else:
# Failure sound
sound_path = "/System/Library/Sounds/Basso.aiff"
if os.path.exists(sound_path):
subprocess.run(
["afplay", sound_path],
capture_output=True,
timeout=5
)
elif system == "Linux":
# Try to use paplay (PulseAudio) or aplay (ALSA)
if success:
# Try to play a beep or use speaker-test
try:
subprocess.run(
["paplay", "/usr/share/sounds/freedesktop/stereo/complete.oga"],
capture_output=True,
timeout=5
)
except:
# Fallback to speaker-test
subprocess.run(
["speaker-test", "-t", "sine", "-f", "1000", "-l", "1"],
capture_output=True,
timeout=2
)
else:
try:
subprocess.run(
["paplay", "/usr/share/sounds/freedesktop/stereo/dialog-error.oga"],
capture_output=True,
timeout=5
)
except:
# Fallback to speaker-test with lower frequency
subprocess.run(
["speaker-test", "-t", "sine", "-f", "400", "-l", "1"],
capture_output=True,
timeout=2
)
elif system == "Windows":
# Use winsound module
import winsound
if success:
winsound.MessageBeep(winsound.MB_OK)
else:
winsound.MessageBeep(winsound.MB_ICONHAND)
except Exception:
# Silently fail if sound can't be played
pass
def main():
parser = argparse.ArgumentParser(
description="Release documentation to asf-staging branch",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--no-push',
action='store_true',
help='Build and commit but don\'t push to remote'
)
parser.add_argument(
'--commit-message',
type=str,
default='Deploy documentation staging',
help='Custom commit message (default: "Deploy documentation staging")'
)
args = parser.parse_args()
script_dir = Path(__file__).parent
docs_dir = script_dir.parent # docs/
build_dir = docs_dir / 'build'
print("=" * 79)
print("Release Documentation to Staging Branch")
print("=" * 79)
print()
# Step 1: Run build-docs.py with --staging flag
print("Step 1: Building documentation for staging...")
build_script = script_dir / 'build-docs.py'
if not build_script.exists():
print(f"❌ ERROR: {build_script} not found")
play_sound(success=False)
sys.exit(1)
if not run_command(
[sys.executable, str(build_script), '--staging'],
cwd=docs_dir,
description="Building documentation for staging"
):
print("\n❌ Documentation build failed. Aborting.")
play_sound(success=False)
sys.exit(1)
if not build_dir.exists():
print(f"❌ ERROR: Build directory not found at {build_dir}")
print(" Documentation build may have failed.")
play_sound(success=False)
sys.exit(1)
# Step 2: Setup sibling directory and checkout asf-staging
print("\nStep 2: Setting up sibling directory with asf-staging branch...")
# Get git remote URL
remote_url = get_git_remote_url()
if not remote_url:
print("❌ ERROR: Could not determine git remote URL")
play_sound(success=False)
sys.exit(1)
# Use sibling directory structure: /git/apache/juneau/asf-staging
parent_dir = docs_dir.parent # /git/apache/juneau/
staging_dir = parent_dir / 'asf-staging'
print(f"Staging directory: {staging_dir}")
# Verify parent directory structure
if not parent_dir.exists():
print(f"❌ ERROR: Parent directory not found: {parent_dir}")
print("Expected structure:")
print(f" {parent_dir}/docs/ (current)")
print(f" {parent_dir}/asf-staging/ (will be created)")
play_sound(success=False)
sys.exit(1)
try:
# Check if directory already exists
if staging_dir.exists() and (staging_dir / '.git').exists():
# Directory exists and is a git repo, update it
print("📁 Staging directory already exists, updating...")
if not run_command(
["git", "fetch", "origin"],
cwd=staging_dir,
description="Fetching latest changes"
):
print("\n❌ Failed to fetch latest changes")
play_sound(success=False)
sys.exit(1)
else:
# Directory doesn't exist or isn't a git repo, clone it
if staging_dir.exists():
print(f"⚠️ Directory exists but is not a git repository. Removing: {staging_dir}")
shutil.rmtree(staging_dir)
if not run_command(
["git", "clone", remote_url, str(staging_dir)],
description="Cloning repository to staging directory"
):
print("\n❌ Failed to clone repository")
play_sound(success=False)
sys.exit(1)
# Fetch asf-staging branch
if not run_command(
["git", "fetch", "origin", "asf-staging"],
cwd=staging_dir,
check=False, # Don't fail if branch doesn't exist yet
description="Fetching asf-staging branch"
):
print("⚠ Warning: Could not fetch asf-staging (branch may not exist yet)")
# Checkout or create asf-staging branch
result = subprocess.run(
["git", "checkout", "-B", "asf-staging", "origin/asf-staging"],
cwd=staging_dir,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
if result.returncode != 0:
# Branch doesn't exist, create it
if not run_command(
["git", "checkout", "-b", "asf-staging"],
cwd=staging_dir,
description="Creating new asf-staging branch"
):
print("\n❌ Failed to create asf-staging branch")
play_sound(success=False)
sys.exit(1)
else:
print("✅ Checked out asf-staging branch")
# Step 3: Remove all existing files (except .git)
print("\nStep 3: Cleaning staging directory...")
for item in staging_dir.iterdir():
if item.name != '.git':
if item.is_dir():
shutil.rmtree(item)
else:
item.unlink()
print("✅ Cleaned staging directory")
# Step 4: Copy build directory contents
print("\nStep 4: Copying build directory contents...")
for item in build_dir.iterdir():
dest = staging_dir / item.name
if item.is_dir():
shutil.copytree(item, dest)
else:
shutil.copy2(item, dest)
print(f"✅ Copied contents from {build_dir} to {staging_dir}")
# Step 5: Add and commit changes
print("\nStep 5: Committing changes...")
# Set git user (use current user's git config or defaults)
try:
result = subprocess.run(
["git", "config", "--get", "user.name"],
capture_output=True,
text=True
)
git_user = result.stdout.strip() if result.returncode == 0 else "Documentation Builder"
result = subprocess.run(
["git", "config", "--get", "user.email"],
capture_output=True,
text=True
)
git_email = result.stdout.strip() if result.returncode == 0 else "docs@juneau.apache.org"
except Exception:
git_user = "Documentation Builder"
git_email = "docs@juneau.apache.org"
run_command(
["git", "config", "user.name", git_user],
cwd=staging_dir,
description="Setting git user name"
)
run_command(
["git", "config", "user.email", git_email],
cwd=staging_dir,
description="Setting git user email"
)
# Add all files
if not run_command(
["git", "add", "-A"],
cwd=staging_dir,
description="Adding all files"
):
print("\n❌ Failed to add files")
play_sound(success=False)
sys.exit(1)
# Check if there are changes to commit
result = subprocess.run(
["git", "diff", "--staged", "--quiet"],
cwd=staging_dir
)
if result.returncode != 0:
# There are changes, commit them
if not run_command(
["git", "commit", "-m", args.commit_message],
cwd=staging_dir,
description=f"Committing changes: {args.commit_message}"
):
print("\n❌ Failed to commit changes")
play_sound(success=False)
sys.exit(1)
else:
print("ℹ️ No changes to commit")
# Step 6: Push to remote (if not --no-push)
if not args.no_push:
print("\nStep 6: Pushing to remote asf-staging branch...")
if not run_command(
["git", "push", "origin", "asf-staging", "--force"],
cwd=staging_dir,
description="Pushing to remote"
):
print("\n❌ Failed to push to remote")
play_sound(success=False)
sys.exit(1)
else:
print("\n⏭️ Skipping push (--no-push flag set)")
print(f" Changes are in: {staging_dir}")
print(" You can manually push with:")
print(f" cd {staging_dir}")
print(" git push origin asf-staging --force")
print("\n" + "=" * 79)
print("✅ Documentation staging deployment complete!")
print("=" * 79)
print(f"\nStaging directory: {staging_dir}")
print("(This directory is persistent and will be reused for future deployments)")
# Play success sound
play_sound(success=True)
# Open staging URL in browser (only if push was successful)
if not args.no_push:
staging_url = "http://juneau.staged.apache.org"
print(f"\n🌐 Opening staging site in browser: {staging_url}")
try:
webbrowser.open(staging_url)
except Exception as e:
print(f"⚠️ Could not open browser automatically: {e}")
print(f" Please visit {staging_url} manually")
except KeyboardInterrupt:
print("\n\n⚠️ Process interrupted by user")
play_sound(success=False)
sys.exit(1)
except Exception as e:
print(f"\n❌ ERROR: {e}")
import traceback
traceback.print_exc()
play_sound(success=False)
sys.exit(1)
if __name__ == '__main__':
main()