#! /usr/bin/env python
# Part of the A-A-P recipe executive: The main function.
# Copyright (C) 2002-2003 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING
# Main Python code that executes an A-A-P recipe
import sys
from traceback import print_exception,format_exception
# Each phase of executing a recipe is done by one module
from DoAddDef import doadddef
from DoArgs import doargs
from DoBuild import dobuild
from DoRead import doread
from DoInstall import doinstall
from Cache import dump_cache
from Error import *
from Sign import sign_write_all,sign_clear_all
from Util import *
import Global
from Message import *
# Globals
exit_status = 0 # exit status; default is zero (success)
exit_info = None # exception stack when something went wrong.
def error_msg(recdict, msg):
"""Print an error message and set the exit status to one."""
global exit_status
msg_error(recdict, msg)
if exit_status == 0:
exit_status = 1
profiling = 0
def do_the_work(argv, find_recipe, commands):
"""Common function for main() and execute().
"argv" is the list of command line arguments (excluding the program
name).
When "find_recipe" is non-zero, search for a recipe to load.
When "commands" is None, execute the specified or default target(s).
When "commands" is empty, do nothing.
When "commands" is a non-empty string, execute these commands.
Returns an error message and a work object.
exit_status is set to a non-zero value when something failed.
"""
global exit_status, exit_info
exit_status = 0
exit_info = None
# We require Python 1.5 or later.
if sys.version[0] == '1' and int(sys.version[2]) < 5:
exit_status = 1
return "A-A-P requires Python version 1.5 or later.", None
# Need to know the directory of this module. But __file__ isn't defined
# when executed directly, then use argv[0] instead, see below.
if not Global.aap_rootdir:
Global.set_aap_rootdir(os.path.abspath(os.path.dirname(__file__)))
# Internationalisation inits: setlocale and gettext.
i18n_init()
#
# Do the main work.
#
msg = None
work = None
try:
# 1. Process the command line arguments.
Global.cmd_args = doargs(argv)
if Global.cmd_args.has_option("verbose"):
Global.cmd_args.printit()
# When profiling is requested and it wasn't started yet, start all over
# with profiling enabled.
global profiling
if not profiling and Global.cmd_args.has_option("profile"):
profiling = 1
import profile
prof = profile.Profile()
try:
res = prof.runcall(do_the_work, argv, find_recipe, commands)
finally:
prof.dump_stats(Global.cmd_args.options.get("profile"))
return res
# When "--install pkg" is used: install a package.
if Global.cmd_args.has_option("install"):
work = doinstall(Global.cmd_args.options.get("install"))
else:
# 2. Read the recipe and included recipes. Assignments and commands
# are executed directly, rules and dependencies are stored.
# "work" is a Work object with collected items from the recipe.
work = doread(find_recipe)
# 3. Add the default dependencies
doadddef(work, work.recdict, 1)
# 4. Build each target or execute the commands.
if commands:
from ParsePos import ParsePos
from RecPos import RecPos
from Process import Process
fp = ParsePos([ RecPos(_('execute()'), 0) ],
string = commands + '\n')
Process(fp, work.recdict, 0)
elif commands is None:
dobuild(work)
except NormalExit, r: # planned exit
exit_status = r
except SystemExit, r:
exit_status = r
msg = _("Aborted")
except KeyboardInterrupt:
exit_status = 1
msg = _("Interrupted")
except UserError, e:
exit_status = 1
msg = e.args
except SyntaxError, e:
exit_status = 1
exit_info = sys.exc_info()
msg = _("Syntax error") + str(e)
except:
exit_status = 1
exit_info = sys.exc_info()
msg = _("Internal Error")
if work:
# Dump entries for the downloaded files.
dump_cache(work.recdict)
# Dump the sign files. Clear all signatures (for any next run).
sign_write_all(work.recdict)
sign_clear_all()
# Close any subshell for installing packages.
from Port import close_sushell
close_sushell(work.recdict)
# Close any root shell.
close_rootshell(work.recdict)
# Cleanup for any configure checks.
import DoConf
DoConf.cleanup(work.recdict)
if msg == None:
return None, work
else:
return msg[0], work
def main(setroot = 0):
"""
The main function to execute an A-A-P recipe.
"""
if setroot:
# We need to know the location of our modules (find ccskim there).
try:
progname = os.path.realpath(sys.argv[0])
except:
# Doesn't have os.path.realpath(), it's new in Python 2.2
# Use our copy of it.
try:
progname = myrealpath(sys.argv[0])
except:
# Still not working? Fall back to using abspath().
progname = os.path.abspath(sys.argv[0])
Global.set_aap_rootdir(os.path.dirname(progname))
# When started with a relative path and changing directories we still
# need to be able to find our modules.
sys.path.append(Global.aap_rootdir)
# Do the work.
msg, work = do_the_work(sys.argv[1:], 1, None)
handle_work_done(msg, work)
sys.exit(exit_status)
def handle_work_done(msg, work):
"""Handle errors after calling do_the_work()."""
if msg:
if work:
error_msg(work.recdict, msg)
else:
error_msg(None, msg)
if exit_info:
print_exception(exit_info[0], exit_info[1], exit_info[2])
error_msg(None, string.join(format_exception(exit_info[0],
exit_info[1], exit_info[2])))
# Get the log file name before it's cleared by stopping the log.
# Stop the log before the message, so that it doesn't get into the log.
if exit_status:
if msg_logname():
logmsg = "All messages are in the logfile: " + msg_logname()
else:
logmsg = ''
msg_stoplog()
if exit_status and logmsg:
if work:
msg_info(work.recdict, logmsg)
else:
msg_info(None, logmsg)
# Return a canonical path (i.e. the absolute location of a file on the
# filesystem). This is from os.path of Python 2.2.
def myrealpath(filename):
"""Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path."""
filename = os.path.abspath(filename)
bits = ['/'] + filename.split('/')[1:]
for i in range(2, len(bits)+1):
component = apply(os.path.join, tuple(bits[0:i]))
if os.path.islink(component):
resolved = os.readlink(component)
(adir, afile) = os.path.split(component)
resolved = os.path.normpath(os.path.join(adir, resolved))
newpath = apply(os.path.join, tuple([resolved] + bits[i:]))
return myrealpath(newpath)
return filename
# When executed directly, call the main function.
if __name__ == '__main__':
main(1)
#
# Other programs may call this funtion to execute one or more recipe commands.
# For example: execute(":do view thisfile")
#
def execute(commands, argv = [], find_recipe = 0):
"""Execute recipe commands "commands". See do_the_work() for details about
"commands".
"argv" is a list of command line arguments.
"find_recipe" is non-zero to find a default recipe.
Returns an error message or None."""
msg, work = do_the_work(argv, find_recipe, commands)
if exit_info:
print msg
print_exception(exit_info[0], exit_info[1], exit_info[2])
msg_stoplog()
return msg
#
# Other programs may call this function to fetch a list of files.
# Uses the "main.aap" recipe, unless "argv" specifies another recipe to use.
#
def fetch(fnames, argv = []):
"""Fetch files in list "fnames".
"argv" is a list of command line arguments.
Will search for a default recipe if none is specified.
Returns an error message or None."""
from Dictlist import list2str
return execute(":fetch %s" % list2str(fnames), argv, 1)
#
# Other programs may call this function to obtain the list of nodes and
# recdict. It was added to be used by the IDE.
# Uses the "main.aap" recipe, unless "argv" specifies another recipe to use.
#
def get_nodelist(argv = []):
"""Obtain the information from the recipe specified with "argv" or the
default recipe.
Return a tuple with the list of nodes and a dictionary for the global
variables."""
msg, work = do_the_work(argv, 1, '')
handle_work_done(msg, work)
if not work:
return [], {}
return work.nodes.values(), work.recdict
def get_actionlist(argv = []):
"""Obtain a dictionary that lists the actions supported for each file
type. Can be used to make sure that executing the action will actually
work. "argv" is usually empty."""
msg, work = do_the_work(argv, 0, '')
handle_work_done(msg, work)
if not work:
return {}
import Action
return Action.action_get_list()
# vim: set sw=4 et sts=4 tw=79 fo+=l:
|