# Part of the A-A-P recipe executive: Utility functions
# 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
#
# Util: utility functions
#
# It's OK to do "from Util import *", these things are supposed to be global.
#
import string
import os.path
import types
import sys
import import_re # import the re module in a special way
import glob
from Error import *
from Message import *
def i18n_init():
"""Set up Internationalisation: setlocale() and gettext()."""
# Chicken-egg problem: Should give informational messages here, but since
# the options haven't been parsed yet we don't know if the user wants us to
# be verbose. Let's keep quiet.
# If already done return quickly.
import __builtin__
if __builtin__.__dict__.has_key("_"):
return
# Set the locale to the users default.
try:
import locale
try:
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
pass # there is a locale module but it doesn't work
except ImportError:
pass # locale module cannot be imported
# Set up for translating messages, if possible.
# When the gettext module is missing this results in an ImportError.
# An older version of gettext doesn't support install(), it generates an
# AttributeError.
# An IOError is generated when the module "a-a-p" cannot be found.
# If not possible, define the _() and N_() functions to do nothing.
# Make them builtin so that they are available everywhere.
try:
import gettext
gettext.install("a-a-p")
except (ImportError, AttributeError, IOError):
def nogettext(s):
return s
__builtin__.__dict__['_'] = nogettext
__builtin__.__dict__['N_'] = nogettext
def is_white(c):
"""Return 1 if "c" is a space or a Tab."""
return c == ' ' or c == '\t'
def skip_white(line, i):
"""Skip whitespace, starting at line[i]. Return the index of the next
non-white character, or past the end of "line"."""
try:
while line[i] == ' ' or line[i] == '\t':
i = i + 1
except IndexError:
pass
return i
def skip_to_white(line, i):
"""Skip non-whitespace, starting at line[i]. Return the index of the next
white character, or past the end of "line"."""
try:
while line[i] != ' ' and line[i] != '\t':
i = i + 1
except IndexError:
pass
return i
def get_token(arg, i):
"""Get one white-space separated token from arg[i:].
Handles single and double quotes and keeps them (see get_item() to
remove quotes).
A sequence of white space is also a token.
Returns the token and the index after it."""
# If "arg" starts with white space, return the span of white space.
if is_white(arg[i]):
e = skip_white(arg, i)
return arg[i:e], e
# Isolate the token until white space or end of the argument.
inquote = ''
arg_len = len(arg)
token = ''
while i < arg_len:
if inquote:
if arg[i] == inquote:
inquote = ''
elif arg[i] == "'" or arg[i] == '"':
inquote = arg[i]
elif is_white(arg[i]):
break
token = token + arg[i]
i = i + 1
return token, i
def list2string(lines):
"""Convert a list of strings into a single string."""
# The simple but slow method:
# return reduce(lambda a, b: a + b, lines)
# The clumsy but fast method:
import cStringIO
s = cStringIO.StringIO()
for line in lines:
s.write(line)
return s.getvalue()
def check_exists(rpstack, fname):
"""Give an error message if file "fname" already exists."""
if os.path.exists(fname):
from Process import recipe_error
recipe_error(rpstack, _('File already exists: "%s"') % fname)
def scopechar(c):
"""Return 1 when "c" is a scope name character, 0 otherwise."""
return c in string.digits or c in string.letters or c == "_"
def varchar(c):
"""Return 1 when "c" is a variable name character, 0 otherwise."""
return c in string.digits or c in string.letters or c == "_" or c == "."
def skip_varchars(arg, idx):
"""Skip over varable name chars in arg[idx:], return the index."""
e = idx
try:
while 1:
c = arg[e]
if not (c in string.digits or c in string.letters
or c == "_" or c == "."):
break
e = e + 1
except:
pass
return e
def unquote(recdict, str):
"""Remove quotes from "str". Assumes aap style quoting."""
res = ''
inquote = ''
for c in str:
if c == inquote:
inquote = '' # End of quoted text.
elif not inquote and (c == '"' or c == "'"):
inquote = c # Start of quoted text.
else:
res = res + c
if inquote:
msg_info(recdict, _('Missing quote in "%s"') % str)
return res
def enquote(s, quote = '"'):
"""Put quotes around "s" so that it is handled as one item. Uses aap style
quoting (a mix of single and double quotes)."""
result = quote
slen = len(s)
i = 0
while i < slen:
if s[i] == quote:
if quote == '"':
result = result + "'\"'\"'"
else:
result = result + '"\'"\'"'
else:
result = result + s[i]
i = i + 1
return result + quote
def double_quote(str):
"""Put double quotes around "str" when it contains white space or a quote.
Contained double quotes are doubled."""
quote = ''
for c in str:
if c == "'" or c == '"' or is_white(c):
quote = '"'
break
if not quote:
return str # no quoting required
res = quote
for c in str:
if c == '"':
res = res + '"'
res = res + c
return res + quote
def bs_quote(str):
"""Escape special characters in "str" with a backslash. Special characters
are white space, quotes and backslashes."""
res = ''
for c in str:
if c == '\\' or c == "'" or c == '"' or is_white(c):
res = res + '\\'
res = res + c
return res
def get_indent(line):
"""Count the number of columns of indent at the start of "line"."""
i = 0
col = 0
try:
while 1:
if line[i] == ' ':
col = col + 1
elif line[i] == '\t':
col = col + 8 - (col % 8)
else:
break
i = i + 1
except IndexError:
pass
return col
def append2var(recdict, scope, varname, str):
"""Add "str" the variable "scope.varname" for "recdict"."""
v = recdict[scope].get(varname)
if v:
if isinstance(v, ExpandVar):
recdict[scope][varname] = ExpandVar(v.getVal() + ' ' + str)
else:
recdict[scope][varname] = v + ' ' + str
else:
recdict[scope][varname] = str
def add_cleanfiles(recdict, str):
"""Add "str" the "CLEANFILES" for the recipe with "recdict"."""
append2var(recdict, "_recipe", "CLEANFILES", str)
def add_distfiles(recdict, str):
"""Add "str" the "DISTFILES" for the recipe with "recdict"."""
append2var(recdict, "_recipe", "DISTFILES", str)
def rem_dup(var):
"""Remove duplicates from a variable string. Attributes are ignored when
comparing items."""
from Dictlist import dictlist2str,str2dictlist
expand = isinstance(var, ExpandVar)
if expand:
val = var.getVal()
else:
val = var
ndl = []
for d in str2dictlist([], val):
dup = 0
for nd in ndl:
if d["name"] == nd["name"]:
dup = 1
break
if not dup:
ndl.append(d)
s = dictlist2str(ndl)
if expand:
return ExpandVar(s)
return s
class Expand:
"""Kind of expansion used for $VAR."""
quote_none = 0 # no quoting
quote_aap = 1 # quoting with " and '
quote_double = 2 # quoting with ", backslash for escaping
quote_bs = 3 # escaping with backslash
quote_shell = 4 # quoting with backslash or " for the shell
def __init__(self, attr = 1, quote = quote_aap, skip_errors = 0):
self.attr = attr # include attributes
self.quote = quote # quoting with " and '
self.rcstyle = 0 # rc-style expansion
self.skip_errors = skip_errors # ignore errors
self.optional = 0 # empty when not defined
self.slash = 0 # no / to \ replacement
class ExpandVar:
"""
Class to hold a variable value that needs to be expanded when it is
used.
"""
def __init__(self, val):
self.val = val
def getVal(self):
return self.val
def __add__(self, other):
"Concatenate ExpandVar like a string. Equivalent to $+= in aap."
return ExpandVar(self.getVal() + other)
def __str__(self):
"Expand the value"
from Commands import expand
return expand(0, Global.globals, self.getVal(),
Expand(1, Expand.quote_aap))
def get_var_val_int(recdict, name):
"""
Get variable for internal use. Get it from "recdict" directly or from
the "_up" scope. Do not use the "_no" scope to avoid setting the flag
that the variable was read before set.
"""
if recdict.has_key(name):
return recdict[name]
return get_var_val(0, recdict, "_up", name)
def get_var_val(line_nr, recdict, scope, name, argexpand = None):
"""
Get the value of variable "name", expanding it when postponed evaluation
was specified for the assignment.
Use "scope" if it is set. "_no" can be used to search up the stack.
When the variable "name" does not exist return None.
"""
from Commands import expand
if not scope:
val = recdict.get(name)
else:
try:
val = recdict[scope][name]
except:
val = None # either the scope or the variable doesn't exist
if val is None:
return None
# The variable may have been set by a Python statement, using a type
# different from the string we expect.
# Automatically convert a number to a string.
if isinstance(val, types.IntType) or isinstance(val, types.LongType):
val = str(val)
if isinstance(val, ExpandVar):
val = expand(line_nr, recdict, val.getVal(),
Expand(1, Expand.quote_aap))
# Return if no expansion to be done.
if not argexpand:
return val
# $/var: change all slashes to backslashes.
if argexpand.slash:
val = string.replace(val, '/', '\\')
# Return when expanding doesn't change the value:
# - Include attributes and aap quoting is the default.
# - There are no attributes and aap quoting.
if argexpand.quote == Expand.quote_aap and (argexpand.attr
or not '{' in val):
return val
# Remove attributes and/or change the quoting. This is done by turning the
# string into a dictlist and then back into a string.
# Be permissive about errors and keep whitespace when there are no
# attributes are to be removed.
from Dictlist import dictlist2str,str2dictlist
try:
dl = str2dictlist([], val)
if argexpand.quote != Expand.quote_aap:
use_val = 0
else:
use_val = 1
for d in dl:
if len(d.keys()) > 1:
use_val = 0
break
if use_val:
res = val
else:
res = dictlist2str(dl, argexpand)
except UserError, e:
res = val # ignore the error, return unexpanded
if not argexpand.skip_errors:
from Work import getrpstack
msg_warning(recdict, (_('Error expanding "%s": ') % val) + str(e),
rpstack = getrpstack(recdict, line_nr))
return res
def expand_item(item, argexpand, key = "name"):
"""Expand one "item" (one entry of a variable converted to a dictlist),
according to "argexpand"."""
res = expand_itemstr(item[key], argexpand)
if argexpand.attr:
from Dictlist import dictlistattr2str
res = res + dictlistattr2str(item)
return res
def expand_itemstr(str, argexpand):
"""Expand the string value of an item accoding to "argexpand"."""
if argexpand.quote == Expand.quote_shell:
if os.name == "posix":
# On Unix a mix of double and single quotes works well
# Also escape characters that have a special meaning.
from Dictlist import listitem2str
res = listitem2str(str, ' \t&;|$<>', '&;|')
else:
# On MS-Windows double quotes works well
res = double_quote(str)
else:
if argexpand.quote == Expand.quote_none:
res = str
elif argexpand.quote == Expand.quote_aap:
from Dictlist import listitem2str
res = listitem2str(str)
elif argexpand.quote == Expand.quote_double:
res = double_quote(str)
else:
res = bs_quote(str)
return res
def has_wildcard(s):
"""
Return non-zero when "s" contains '*', '?' or '[abc]'.
"""
return re.search("[*?[]", s) != None
def expand_unescape(s):
"""
Remove wildcard escaping from "s".
Currenly only changes "[*]" things to "*".
"""
# Be quick when nothing to be done.
ni = string.find(s, '[')
if ni < 0:
return s
res = ""
i = 0
while 1:
res = res + s[i:ni]
if s[ni + 2] == ']':
res = res + s[ni + 1]
i = ni + 3
else:
res = res + '['
i = ni + 1
if i >= len(s):
break
ni = string.find(s, '[', i)
if ni < 0 or ni + 2 >= len(s):
return res + s[i:]
return res
def oct2int(s):
"""convert string "s", which is an octal number, to an int. Isn't there a
standard Python function for this?"""
v = 0
for c in s:
if not c in string.octdigits:
raise UserError, _('non-octal chacacter encountered in "%s"') % s
v = v * 8 + int(c)
return v
def full_fname(name):
"""Make a full, uniform file name out of "name". Used to be able to
compare filenames with "./" and "../" things in them, also after
changing directories."""
res = os.path.abspath(os.path.normpath(name))
if os.name == "posix":
return res
return fname_fold(res)
def fname_fold(name):
"""
Turn a file name into a uniform format: on non-Unix systems change
backslashes to slashes and make all characters lowercase. Cannot use
os.path.normcase() and os.path.normpath() here, they change slashes to
backslashes on MS-Windows.
"""
if os.name == "posix":
return name
return string.replace(string.lower(name), '\\', '/')
def fname_equal(one, two):
"""
Return non-zero when "one" and "two" are the same file (or directory).
"""
if os.name == "posix":
# Use samefile() to detect symbolic links. But this only works for
# existing files, thus compare the names if it fails. And it's not
# supported on all systems.
try:
r = os.path.samefile(one, two)
except:
r = 0
if r:
return r
return fname_fold(one) == fname_fold(two)
def fname_fold_same():
"""Return if fname_fold() always returns a file name unmodified."""
return os.name == "posix"
def path_join(first, second):
"""Special version of os.path.join() that uses the path separator that is
being used already."""
c = os.sep
if string.find(first, '/') >= 0 and os.sep == '\\':
os.sep = '/'
ret = os.path.join(first, second)
os.sep = c
return ret
def shorten_name(name, dir = None):
"""Shorten a file name when it's relative to directory "dir".
If "dir" is not given, use the current directory.
Prefers using "../" when part of "dir" matches.
Also handles backslashes on non-posix systems"""
from Remote import is_url
if is_url(name):
return name # can't shorten a URL
adir = dir
if adir is None:
adir = os.getcwd()
dir_len = len(adir)
c = adir[dir_len - 1]
if c != '/' and c != '\\':
adir = adir + os.sep # make sure "adir" ends in a slash
dir_len = dir_len + 1
# Skip over the path components that are equal
name_len = len(name)
i = 0
slash = -1
while i < dir_len and i < name_len:
if os.name == "posix":
if adir[i] != name[i]:
break
if adir[i] == '/':
slash = i
else:
if ((adir[i] == '/' or adir[i] == '\\')
and (name[i] == '/' or name[i] == '\\')):
slash = i
elif string.lower(adir[i]) != string.lower(name[i]):
break
i = i + 1
# For a full match and "name" is a directory pretend "name" ends in a
# slash.
if (i == name_len
and name_len > 0
and i < dir_len
and (adir[i] == '/' or adir[i] == '\\')
and name[-1] != '/'
and name[-1] != '\\'
and os.path.isdir(name)):
slash = i
i = i + 1
# If nothing is equal, return the full name
if slash <= 0:
return name
# For a full match with "adir" return the name without it.
if i == dir_len:
return name[dir_len:]
# Insert "../" for the components in "adir" that are not equal.
# Example: name = "/foo/bdir/foo.o"
# adir = "/foo/test"
# result = "../bdir/foo.o"
back = ''
while i < dir_len:
if adir[i] == '/' or adir[i] == '\\':
back = back + ".." + os.sep
i = i + 1
# Don't include a trailing slash when "name" is part of "adir".
if slash + 1 >= name_len and back:
return back[:-1]
return back + name[slash + 1:]
def display_name(name):
"""Change a file name into a form suitable for displaying:
fname.txt (/path/dir/) """
absname = full_fname(name)
return os.path.basename(absname) + " (" + os.path.dirname(absname) + ")"
def shorten_dictlist(dictlist):
"""Shorten a dictlist to the current directory. Returns a copy of the
dictlist with identical attributes and shortened names."""
adir = os.getcwd()
newlist = []
for item in dictlist:
new_item = {}
for k in item.keys():
if k == "name":
if item.has_key("_node") and k == "name":
new_item[k] = shorten_name(item["_node"].get_name(), adir)
else:
new_item[k] = shorten_name(item[k], adir)
else:
new_item[k] = item[k]
newlist.append(new_item)
return newlist
def aap_checkdir(rpstack, recdict, fname):
"""Make sure the directory for file "fname" exists."""
dirname = os.path.dirname(fname)
if dirname:
assert_dir(rpstack, recdict, dirname)
def assert_dir(rpstack, recdict, dirname):
"""Make sure the directory "dirname" exists."""
if not os.path.exists(dirname):
msg_info(recdict, _('Creating directory "%s"') % dirname)
try:
os.makedirs(dirname)
except StandardError, e:
from Process import recipe_error
recipe_error(rpstack, (_('Cannot create directory "%s": ')
% dirname) + str(e))
elif not os.path.isdir(dirname):
from Process import recipe_error
recipe_error(rpstack, _('"%s" exists but is not a directory') % dirname)
def dir_contents(dir, recurse = 0, join = 1):
"""Return a list with the contents of directory "dir". Takes care not to
expand wildcards in "dir".
When "recurse" is non-zero, recursively get contents of directories.
When "join" is zero, don't join "dir" to the resulting list.
Raises an Exception when something is wrong."""
cwd = os.getcwd()
os.chdir(dir)
try:
# Get contents of "dir".
alist = glob.glob("*")
if os.name == "posix":
alist = alist + glob.glob(".*")
# When recursive, get contents of each directory in "dir".
if recurse:
newlist = []
for item in alist:
if os.path.isdir(item):
newlist.extend(dir_contents(item, 1))
else:
newlist.append(item)
alist = newlist
finally:
os.chdir(cwd)
if join:
newlist = []
for l in alist:
newlist.append(os.path.join(dir, l))
return newlist
return alist
def deltree(dir):
"""
Recursively delete a directory or a file. But for a symlink delete the
link itself, not a directory it's pointing to.
"""
if os.path.isdir(dir) and not isalink(dir):
for f in dir_contents(dir):
deltree(f)
os.rmdir(dir)
else:
os.remove(dir)
def isalink(fname):
"""
Version of os os.readlink() that returns False on non-Unix systems.
"""
islink = 0
try:
islink = os.readlink(fname)
except:
# Ignore errors, doesn't work on non-Unix systems.
pass
return islink
def date2secs(str):
"""Convert a string like "12 days" to a number of seconds."""
str_len = len(str)
i = 0
while i < str_len:
if not str[i] in string.digits:
break
i = i + 1
if i == 0:
raise UserError, _('Must start with a number: "%s"') % str
nr = int(str[:i])
i = skip_white(str, i)
if str[i:] == "day":
return nr * 24 * 60 * 60
if str[i:] == "hour":
return nr * 60 * 60
if str[i:] == "min":
return nr * 60
if str[i:] == "sec":
return nr
raise UserError, _('Must have day, hour, min or sec: "%s"') % str
def dictlist_sameentries(one, two):
"""
Compare the entries in dictlists "one" and "two" and return non-zero if
they contain the same entries. Only checks the "name" of the entries and
ignores the order.
"""
if len(one) != len(two):
return 0
for o in one:
found = 0
for t in two:
if o["name"] == t["name"]:
found = 1
if not found:
return 0
return 1
def cflags_normal():
"""
Return $OPTIMIZE and $DEBUG in a form that works for most C compilers.
"""
recdict = Global.globals
opt = get_var_val(0, recdict, "_no", "OPTIMIZE")
if opt:
if opt == "size":
opt = 's'
else:
try:
opt = int(opt)
except:
msg_warning(recdict,
_("OPTIMIZE must be 'size' or a number: %s") % opt)
opt = 0
if opt > 0:
opt = "%d" % opt
else:
opt = None
dbg = get_var_val(0, recdict, "_no", "DEBUG")
if dbg == "no":
dbg = None
if opt and dbg:
# TODO: check if the compiler accepts both arguments
res = "-O%s -g" % opt
elif opt:
res = "-O%s" % opt
elif dbg:
res = "-g"
else:
res = ''
return res
def get_sys_option(cmd):
"""Check for system command options. Ignore anything that is not a valid
option.
Return the remaining command and a dictionary of the options."""
from Dictlist import parse_attr
cmd_len = len(cmd)
idx = 0
adict = {}
while 1:
idx = skip_white(cmd, idx)
if idx >= cmd_len or cmd[idx] != '{':
break
try:
name, val, idx = parse_attr([], cmd, idx)
except UserError:
# An error probably means the shell command starts with "{".
name = None
if name == 'q' or name == 'quiet':
adict['quiet'] = val
elif name == 'i' or name == 'interactive':
adict['interactive'] = val
elif name == 'l' or name == 'log':
adict['log'] = val
elif name == 'f' or name == 'force':
adict['force'] = val
else:
break
return cmd[idx:], adict
def logged_system(recdict, cmd):
"""Execute a system command.
Display the command and log the output.
Returns the return value of os.system()."""
# Redirect the output of each line to a file.
# Don't do this for lines that contain redirection themselves.
# TODO: make this work on non-Posix systems.
tmpfilex = ''
if msg_logname():
from RecPython import tempfname
tmpfile = tempfname()
if os.name == "posix":
tmpfilex = tempfname()
else:
tmpfile = None
newcmd = ''
append = ""
for line in string.split(cmd, '\n'):
if not line:
continue # skip empty lines
rest, adict = get_sys_option(line)
if adict.get("quiet"):
msg_log(recdict, line)
else:
msg_system(recdict, line)
if adict.get("interactive"):
newcmd = newcmd + rest + '\n'
elif adict.get("log") or Global.sys_cmd_log:
rd = ">"
if not tmpfile: # not logging, throw it in the bit bucket
of = "/dev/null"
else:
of = tmpfile # write or append to the logfile
if append:
rd = ">>"
if os.name == "posix":
# The extra "2>&1" just after the command is unexpectedly
# needed to redirect stderr, otherwise it's echoed.
newcmd = newcmd + ("{ %s 2>&1; echo $? > %s; } 2>&1 %s%s\n"
% (rest, tmpfilex, rd, of))
else:
if of == "/dev/null":
newcmd = newcmd + ("%s\n" % (rest))
else:
newcmd = newcmd + ("%s %s%s\n" % (rest, rd, of))
append = "-a " # append next command output
elif tmpfile and string.find(rest, '>') < 0:
if os.name == "posix":
newcmd = newcmd + ("{ %s; echo $? > %s; } 2>&1 | tee %s%s\n"
% (rest, tmpfilex, append, tmpfile))
else:
rd = ">"
if append:
rd = ">>"
newcmd = newcmd + ("%s %s%s\n" % (rest, rd, tmpfile))
append = "-a " # append next command output
else:
newcmd = newcmd + rest + '\n'
try:
if os.name == "posix":
res = os.system(newcmd)
else:
# system() on MS-Windows can handle only one command at a time and
# must not end in a NL. Do the same on other non-Unix systems for
# now.
# TODO: system() isn't available on the Mac
# TODO: system() always returns zero for Windows 9x
for line in string.split(newcmd, '\n'):
if line: # skip empty lines
if osname() == "mswin":
# Hack: os.system() doesn't always handle command names
# with a space in them or with double quotes. Write
# the command in a batch file and execute it.
tmpbat = tempfname() + ".bat"
f = open(tmpbat, "w")
f.write("@echo off\r\n")
f.write(line + "\r\n")
f.close()
line = tmpbat
try:
res = os.system(line)
finally:
if osname() == "mswin":
os.unlink(line)
if res:
break
except KeyboardInterrupt:
msg_info(recdict, _("Interrupted"))
res = 1
if tmpfile:
# Read the file that contains the exit status of the executed command.
# "res" could be the exit status of "tee".
if os.name == "posix":
rr = '0'
try:
f = open(tmpfilex)
rr = f.read(10)
f.close()
except:
pass
# Be prepared for strange results: Only use the number at the start
# of the file. Avoids crashing here.
rr = re.sub("^(\\d*)(.|\\s)*", "\\1", rr)
if rr and rr != '0':
res = int(rr)
if res == 0:
res = 1
# Append the output to the logfile.
try:
f = open(tmpfile)
text = f.read()
f.close()
except:
text = ''
if text:
if Global.sys_cmd_log:
Global.sys_cmd_log = Global.sys_cmd_log + text
else:
msg_log(recdict, text)
if os.name != "posix" or sys.stdout != sys.__stdout__:
# No "tee" command or stdout is going somehwere else, echo
# the system command output.
print text
# Always delete the temp files
try_delete(tmpfile)
if tmpfilex:
try_delete(tmpfilex)
return res
def redir_system_int(recdict, cmd, use_tee = 1):
"""Execute "cmd" with the shell and catch the output.
Returns two things: a number (non-zero for success) and the output of
the command. The caller may want to use msg_log() for the text.
"""
from RecPython import tempfname
msg_system(recdict, cmd)
tmpfile = tempfname()
if os.name == "posix":
tmpexit = tempfname()
if use_tee:
cmd = "{ %s; echo $? > %s; } 2>&1 | tee %s" % (cmd, tmpexit, tmpfile)
else:
cmd = "{ %s; echo $? > %s; } 2>&1 > %s" % (cmd, tmpexit, tmpfile)
else:
if '>' in cmd:
# Redirecting output of a command that redirects output itself only
# works for stderr, but that is not available on MS-Windows.
# Leave the temp file empty.
# TODO: copy the redirected output to tmpfile?
tmpfile = ''
else:
cmd = "%s > %s" % (cmd, tmpfile)
tmpexit = ''
try:
try:
ok = (os.system(cmd) == 0)
except StandardError, e:
msg_note(recdict, (_('Executing "%s" failed: ') % cmd) + str(e))
ok = 0
except KeyboardInterrupt:
msg_note(recdict, _('Executing "%s" interrupted') % cmd)
ok = 0
if tmpexit:
rr = '0'
try:
f = open(tmpexit)
rr = f.read(1)[0]
f.close()
except:
pass
if rr != '0':
ok = 0
# Read the output of the command, also when it failed (might explain
# why it failed).
text = ''
if tmpfile:
try:
f = open(tmpfile)
text = f.read()
f.close()
except StandardError, e:
msg_note(recdict,
(_('Reading output of "%s" failed: ') % cmd) + str(e))
ok = 0
# always remove the tempfiles, even when system() failed.
finally:
if tmpexit:
try_delete(tmpexit)
if tmpfile:
try_delete(tmpfile)
return ok, text
def async_system(rpstack, recdict, cmd):
"""Run command "cmd" with the shell, without waiting for it to finish."""
cmd, adict = get_sys_option(cmd)
if not adict.get("quiet"):
msg_system(recdict, cmd)
try:
if os.name == "posix":
# Unix: use fork/exec
if not os.fork():
n = os.system(cmd)
os._exit(n)
else:
shell = get_shell_name()
os.spawnv(os.P_DETACH, shell, [ "/c", cmd ])
except StandardError, e:
from Process import recipe_error
recipe_error(rpstack, (_('Could not execute "%s": ') % cmd) + str(e))
def get_shell_name():
"""Get the name of the shell to use (either command.com or cmd.exe)."""
if os.environ.has_key("SHELL"):
shell = os.environ["SHELL"]
elif os.environ.has_key("COMSPEC"):
shell = os.environ["COMSPEC"]
else:
from RecPython import program_path
shell = program_path("cmd.exe")
if not shell:
shell = program_path("command.com")
return shell
def get_progname(recdict, varname, progname, args):
"""
Get the program name from variable "varname". If it doesn't exist search
for program "progname" and use arguments "arg".
"""
n = get_var_val(0, recdict, "_no", varname)
if not n:
# Use program_path() so that programs in the "aap" directory are also
# found (useful for automatically installed packages).
from RecPython import program_path
n = program_path(progname)
if not n:
n = progname # not found, use the default.
n = n + args
return n
def assert_aap_dir(recdict):
"""Create the "AAPDIR" directory if it doesn't exist yet.
Return non-zero if it exists or could be created."""
if not os.path.exists(Global.aap_dirname):
try:
os.mkdir(Global.aap_dirname)
except StandardError, e:
msg_error(recdict, (_('Warning: Could not create "%s" directory: ')
% Global.aap_dirname) + str(e))
return 0
return 1
def in_aap_dir(fname):
"""Return the path of file "fname" in the aap directory."""
return os.path.join(Global.aap_dirname, fname)
def skip_commands():
"""Return non-zero when build commands are to be skipped: -n and/or -t
command line argument. But don't skip when force_build was set."""
return (not Global.force_build
and (Global.cmd_args.options.get("touch")
or Global.cmd_args.options.get("nobuild")))
def home_dir():
"""Return a directory to read/write files specifically for the current
user, E.g., ~/.aap."""
if os.name == "posix":
return os.path.expanduser(os.path.join("~", ".aap"))
if os.environ.get("HOME"):
# Use $HOME/aap.
return os.path.join(os.environ.get("HOME"), "aap")
if os.environ.get("HOMEDRIVE"):
# Use $HOMEDRIVE/HOMEPATH/aap.
h = os.environ.get("HOMEDRIVE")
if os.environ.get("HOMEPATH"):
h = os.path.join(h, os.environ.get("HOMEPATH"))
else:
h = h + os.sep
return os.path.join(h, "aap")
if os.path.isdir("c:/"):
# Use C:/aap
return "c:/aap"
# Nothing found...
return ""
def default_dirs(recdict, homedirfirst = 0):
"""
Return a list of directories to look for recipes.
When "homedirfirst" is set put the home dir before a global dir.
"recdict" is used to obtain $AAPSYSDIR. When this variable doesn't exist
fall back to the default directory (for filetype detection).
"""
from Dictlist import str2dictlist
aapsysdir = get_var_val_int(recdict, "AAPSYSDIR")
if aapsysdir is None and os.path.isdir("/usr/local/share/aap"):
aapsysdir = "/usr/local/share/aap"
if aapsysdir:
dirs = map(lambda x: x["name"], str2dictlist([], aapsysdir))
else:
dirs = []
homedir = home_dir()
if homedir:
if homedirfirst:
return [ homedir ] + dirs
return dirs + [ homedir ]
return dirs
def get_import_dirs(recdict):
"""
Return the list of directories from which modules and tools are to be
imported. Without the "modules" or "tools" sub-directory name.
"""
dirs = default_dirs(recdict, homedirfirst = 1)
dirs.append(Global.aap_rootdir)
return dirs
def goto_dir(recdict, dir):
"""Change to directory "dir" and give a message about it."""
if dir != os.getcwd():
# Note: This message is not translated, so that a parser for the
# messages isn't confused by various languages.
os.chdir(dir)
msg_changedir(recdict, os.getcwd())
# Compiling a regexp is slow. This cache keeps the result.
compiled_re = {}
def cre_match(regexp, text):
"""Call re.match while caching the compiled regexp."""
if compiled_re.has_key(regexp):
cre = compiled_re[regexp]
else:
cre = re.compile(regexp)
compiled_re[regexp] = cre
return cre.match(text)
def cre_search(regexp, text):
"""Call re.search while caching the compiled regexp."""
if compiled_re.has_key(regexp):
cre = compiled_re[regexp]
else:
cre = re.compile(regexp)
compiled_re[regexp] = cre
return cre.search(text)
def osname():
"""
Return the os.name in a slightly modified way.
Returns "mswin" for all kinds of MS-Windows.
Returns "posix" for all kinds of Unix.
"""
if os.name == "nt" or os.name == "win32":
return "mswin"
return os.name
asroot_in = None
asroot_out = None
asroot_err = None
def do_as_root(recdict, rpstack, cmd):
"""
Open a connection to a su-shell to execute commands under root
permissions.
Return 1 for success, zero if failure was detected, 2 if the user refused
to execute the command.
"""
global asroot_out, asroot_in, asroot_err
import popenerr
import select
# Ask the user if he really wants to execute this command as root.
# TODO: replace unprintable characters.
msg_info(recdict, _('Directory: "%s"') % os.getcwd())
msg_info(recdict, _('Command: "%s"') % cmd)
r = raw_input(_("Execute as root? y(es)/n(o)/a(bort) "))
if not r or (r[0] != 'y' and r[0] != 'Y'):
if r and (r[0] == 'a' or r[0] == 'A'):
from Process import recipe_error
recipe_error(rpstack, _("Command aborted"))
# Skipping a command is not equal to failure.
msg_info(recdict, _("Command not executed"))
return 2
if asroot_out is None and asroot_in is None:
# Run the shell and get the input and output files.
msg_info(recdict, _("Starting a separate shell to execute commands as root."))
msg_info(recdict, _("For safety you will be asked to confirm each command passed to the shell."))
msg_info(recdict, _("Please enter the root password:"))
asroot_out, asroot_in, asroot_err = popenerr.popen3('su root -c %s'
% os.path.join(Global.aap_rootdir, "RootShell.py"))
# Loop until logging in has been done.
if asroot_err:
fdlist = [asroot_out, asroot_err]
else:
fdlist = [asroot_out]
try:
while 1:
# Read both from stdout and stderr until RootShell has been
# started. "su" may write to stderr.
inp, outp, exc = select.select(fdlist, [], [])
m = inp[0].readline()
if not m:
from Process import recipe_error
recipe_error(rpstack, _('Starting super-user shell failed'))
if string.find(m, "RootShell ready") >= 0:
msg_info(recdict, _("super-user shell started"))
break
msg_info(recdict, m)
except StandardError, e:
from Process import recipe_error
recipe_error(rpstack, _('Error while opening root shell: ') + str(e))
if asroot_out is None or asroot_in is None:
from Process import recipe_error
recipe_error(rpstack, _('Failed to start a root shell'))
# Send the directory and the command to the root shell.
asroot_in.write('C' + os.getcwd() + '\n')
asroot_in.write('X' + cmd + '\n')
asroot_in.flush()
# Loop until the root shell did its work. Echo messages.
# Read one character at a time, otherwise a prompt may not be displayed.
m = ''
while 1:
try:
c = asroot_out.read(1)
except KeyboardInterrupt:
msg_error(recdict, 'Executing interrupted for "%s"' % cmd)
_close_rootshell_fds()
return 0
if not c:
msg_error(recdict, 'Executing aborted for "%s"' % cmd)
_close_rootshell_fds()
return 0
sys.stdout.write(c)
sys.stdout.flush()
if c == '\n':
if m[:13] == "RootShell: OK":
return 1
if m[:9] == "RootShell":
msg_extra(recdict, m)
return 0
# Don't echo here, the write() above already did it.
msg_info({"MESSAGE" : ''}, m)
m = ''
else:
m = m + c
# NOTREACHED
return 0
def close_rootshell(recdict):
"""Close the root shell."""
if asroot_in:
try:
asroot_in.write('Q\n')
asroot_in.flush()
_close_rootshell_fds()
except StandardError, e:
msg_info(recdict, 'Could not close root shell: %s' % str(e))
else:
msg_info(recdict, 'Closed root shell.')
def _close_rootshell_fds():
global asroot_out, asroot_in, asroot_err
asroot_in.close()
asroot_in = None
asroot_out.close()
asroot_out = None
try:
asroot_err.close() # see openerr.py why this fails
except:
pass
asroot_err = None
def try_delete(fname):
"""
Try to delete a file, ignore failure.
"""
try:
os.remove(fname)
except:
pass
# vim: set sw=4 et sts=4 tw=79 fo+=l:
|