#!/usr/bin/env python
# -*- mode:Python; tab-width: 3 -*-
"""
This program is meant to update a CVS checkout of the allegrowww
module without human intervention. A human might write a quick
one liner for cron which updates the web, but SourceForge (and
in general anything to do with networks) tends to fail for simple
commands with "Connection reset by peer" and crap like that. That
translates into very unuseful cron emails and zero updates even if
the net is OK and it is SF who has a bad day.
This program also hides the output of messages in the case the whole
process went right, to avoid generating cron messages due to Makefile
or cvs superfluos messages/warnings. So if you get a cron mail for
this script, it is serious stuff you should take care to read (unlike
those silly "I am having a bad day, please retry CVS later...").
This script was written by Grzegorz Adam Hankiewicz and is Giftware:
you are free to do what you want with it without any restriction. I
do not accept responsibility for any effects, adverse or otherwise,
that this script may have on you, your computer, your sanity,
your dog, and anything else that you can think of. Use it at your
own risk.
$Id: update-website.py 7614 2006-12-02 18:13:42Z gradha $
"""
import getopt
import os
import re
import singleton
import sys
HUMAN_VERSION = "0.1.1"
NAME = "update-website.py"
ALLEGRO_CVS_ROOT = "/cvsroot/alleg"
def show_program_usage(argv_zero = "", exit_code = 0):
"""Simple function which explains the commandline switches and exits.
argv_zero is the first entry in sys.argv, it's used to extract the name
of the script being run from the commandline. exit_code is the code
which will be returned to the OS.
"""
print "Usage: %s [-hvV] -d path_to_allegrowww_cvs_root_directory \n" % os.path.split(argv_zero)[1]
print "-d x, --directory x\tUse x as path to allegrowww CVS checkout dir."
print "-h, --help \tShows this help message"
print "-v, --version \tDisplays version and exits"
print "-V, --verbose \tShow output of commands even if they succeed"
print
sys.exit(exit_code)
def process_arguments(arguments):
"""Processes the arguments used to invoke the program.
arguments has to include sys.argv[0] too, so I can extract the
name used to call the binary. It is just for user messages,
so it doesn't have to be really correct.
Returns a tuple with a boolean verbosity parameter and the path
to the allegrowww cvs directory or exits before with a user help
message if it is unable to get that.
"""
short_arguments = "hvVd:"
long_arguments = ["help", "version", "verbose", "directory="]
try:
optlist, args = getopt.getopt(arguments[1:], short_arguments, long_arguments)
except getopt.error, msg:
print "Error parsing arguments:\n", msg, "\n"
show_program_usage(arguments[0], 1)
verbose = 0
path = ""
for option, value in optlist:
if option in ("--help", "-h"):
show_program_usage(arguments[0], 0)
elif option in ("--version", "-v"):
print NAME, HUMAN_VERSION
sys.exit(0)
elif option in ("--verbose", "-V"):
verbose = 1
elif option in ("--directory", "-d"):
path = value
else:
raise Exception("Huh?")
if not path:
print "You have to specify the path to the allegrowww module checkout.\n"
show_program_usage(arguments[0], 2)
if not os.path.isdir(path):
print "`%s' is not a directory\n" % path
show_program_usage(arguments[0], 3)
return verbose, os.path.realpath(path)
def verify_available_commands():
"""Runs some --version tests on external commands.
The program will exit with a user message if something is wrong.
"""
for command in [["svn", "--version"], ["make", "--version"]]:
exit_code, output = singleton.run_external(command)
if exit_code:
print (
"""An external program required to update the web module is not
present or not available in your defalut PATH. Please make sure
you can run it yourself and then see why it is failing to be run
from this script.""")
print "\nError output I got for running %s:\n" % command
for line in output:
print line.rstrip()
sys.exit(1)
def verify_cvs_integrity(cvs_path):
"""Checks if the CVS path is really the path we want.
The program will exit with a user message if something is wrong.
"""
try:
for filename in ["text-base"]:
path = os.path.join(cvs_path, ".svn", filename)
if not os.path.isdir(path):
raise Exception("`%s' is not a directory." % path)
# path holds the path to the Root file, check the contents.
input_file = file(path, "rt")
lines = input_file.readlines()
input_file.close()
if len(lines) != 1:
raise Exception("`%s' contains %d lines, "
"should have one." % (path, len(lines)))
regexp = re.compile(r":\w+:\w+@[.\w]+:([/\w]+)")
m = regexp.match(lines[0])
if not m:
raise Exception("`%s' doesn't contain a CVS path." % path)
if m.group(1) != ALLEGRO_CVS_ROOT:
raise Exception("`%s' contains an incorrect path:\n"
"`%s' != `%s'" % (path, m.group(1), ALLEGRO_CVS_ROOT))
except Exception, msg:
print ("""Uh, looks like there is an integrity problem with your CVS path.
The CVS path you specify has to contain a CVS directory with the
files Entries, Repository and Root in them, with Root being a single
line text file with the path to the Allegro CVS root directory on
the SourceForge servers. If you think this is all correct, contact
the webmasters. For now, the reason I aborted was:\n\n%s\n""" % msg)
sys.exit(1)
# The last final step is to run a dummy target of the Makefile.
exit_code, output = singleton.run_external(["make",
"-C", cvs_path, "whoareyou"])
output_string = "".join(output)
if exit_code or output_string.find("It's me, the white rabbit.") == -1:
print "I tried to verify allegrowww's dummy target, but failed."
for line in output:
print "Output: `%s'" % line.rstrip()
sys.exit(exit_code)
def update_website(cvs_path):
"""func(path) -> (exit_code, [stdout_stderr_lines])
Updates the Allegro website, running in sucession the necessary
commands to keep an exact copy of the web locally. Returns the
exit code of the last command and the output of all executed
commands.
"""
exit_code = 0
output = []
# Change to the correct path, otherwise cvs won't work.
os.chdir(cvs_path)
commands = (
(["svn", "update"], 2, 60),
(["make", "mirror"], 0, 0),
(["make", "dep"], 0, 0),
(["make", "-k"], 0, 0),
(["make"], 0, 0),
)
for command_list, retries, seconds in commands:
try:
exit_code, new_output = singleton.run_forgiving(command_list,
retries, seconds)
output.extend(new_output)
except KeyboardInterrupt:
exit_code = -1
output.append("Command `%s' interrupted" % command_list)
if exit_code:
break
return exit_code, output
def main(argv = None):
"""Entry point of the program."""
if argv is None:
argv = sys.argv
verify_available_commands()
verbose, cvs_path = process_arguments(argv)
# TODO, replace this with verify_svn_integrity
#verify_cvs_integrity(cvs_path)
exit_code, output = update_website(cvs_path)
if exit_code or verbose:
sys.stdout.writelines(output)
if __name__ == "__main__":
"""Protect the entry point with a file lock."""
singleton.run_if_possible(main)