diff options
author | James Meyer <james.meyer@operamail.com> | 2010-11-04 18:06:25 (GMT) |
---|---|---|
committer | James Meyer <james.meyer@operamail.com> | 2010-11-04 18:06:27 (GMT) |
commit | 429f14eea9f4c00ddd8d82f25612213df8d4af84 (patch) | |
tree | d8441dd4ef5ef539c0b263b138d36fb1995c38d8 /build_tools/larch7/larch0/cli/backend.py | |
parent | 11ef4af01d6e197a54d0759e688ab5cbd336be4b (diff) | |
download | linhes_dev-429f14eea9f4c00ddd8d82f25612213df8d4af84.zip |
add profiles for larch 7
Diffstat (limited to 'build_tools/larch7/larch0/cli/backend.py')
-rw-r--r-- | build_tools/larch7/larch0/cli/backend.py | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/build_tools/larch7/larch0/cli/backend.py b/build_tools/larch7/larch0/cli/backend.py new file mode 100644 index 0000000..95b01bd --- /dev/null +++ b/build_tools/larch7/larch0/cli/backend.py @@ -0,0 +1,444 @@ +# backend.py - for the cli modules: handles processes and io +# +# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# larch is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +# 2010.07.14 + +# There was also the vague idea of a web interface, using a sort of state- +# based approach. Connecting to a running larch process would then require +# the ability to get the logging history, but presumably not the whole +# history on every ui update, which would need to be incremental. +# The logging function would need to be modified to accommodate this. + +import os, sys, signal, atexit, __builtin__ +import traceback, pwd +from subprocess import Popen, PIPE, STDOUT +import pexpect +try: + import json as serialize +except: + import simplejson as serialize +from config import * + +def init(app, options, app_quit=None): + global _options, _quit_function, _log, _controlled, _dontask, _quiet + _options = options + _quit_function = app_quit + _controlled = options.slave + _dontask = options.force + _log = None + _quiet = False if _controlled else options.quiet + + atexit.register(sys_quit) + + + def sigint(num, frame): + """A handler for SIGINT. Tidy up properly and quit. + """ + errout("INTERRUPTED - killing subprocesses", 0) + if _sub_process and _sub_process.pid: + Popen(["pkill", "-g", str(_sub_process.pid)], + stdout=PIPE).communicate() + errout("QUITTING", 2) + signal.signal(signal.SIGINT, sigint) + + + # Check no other instance of the script is running + if os.path.isfile(LOCKFILE): + app0 = readfile(LOCKFILE) + if not query_yn(_( + "larch (%s) seems to be running already." + "\nIf you are absolutely sure this is not the case," + "\nyou may continue. Otherwise you should cancel." + "\n\nShall I continue?") % app0): + sys.exit(102) + writefile(app, LOCKFILE) + _log = open(LOGFILE + app, 'w') + + # For systems without /sbin and /usr/sbin in the normal PATH + p = os.environ['PATH'] + ps = p.split(':') + for px in ('/sbin', '/usr/sbin'): + if px not in ps: + p = px + ':' + p + os.environ['PATH'] = p + + +def _out(text, force=False): + """Send the string to standard output. + How it is output depends on the '-s' command line option (whether the + script is being run on the console or as a subprocess of another script). + In the latter case the text will be slightly encoded - to avoid newline + characters - and sent as a single unit. + Otherwise output the lines as they are, but all lines except + the first get a '--' prefix. + """ + lines = text.encode('utf-8').splitlines() + if _log: + _log.write(lines[0] + '\n') + for l in lines[1:]: + _log.write('--' + l + '\n') + + if force or not _quiet: + if _controlled: + sys.stdout.write(serialize.dumps(text) + '\n') + else: + prefix = '' + for line in lines: + sys.stdout.write(prefix + line + '\n') + prefix = '--' + sys.stdout.flush() + + +def sys_quit(): + unmount() + if _quit_function: + _quit_function() + if _errorcount: + _out('!! ' + (_("The backend reported %d failed calls," + " you may want to investigate") % _errorcount)) + if _log: + _log.close() + os.remove(LOCKFILE) + + +def comment(text): + _out('##' + text) + + +def query_yn(message, default=False): + _out('?>' + message) + result = default + if _dontask: + result = True + elif not _quiet: + if _controlled: + result = (raw_input().strip() == '??YES') + + else: + # The character after '_' is the response key + # The default will be capitalized automatically + prompt = _("_yes|_no").split('|') + promptkey = [word[word.index('_') + 1] for word in prompt] + if default: + py = prompt[0].upper() + pn = prompt[1] + else: + py = prompt[0] + pn = prompt[1].upper() + resp = raw_input(" [ %s / %s ]: " % (py, pn)).strip() + if resp: + testkey = promptkey[1] if default else promptkey[0] + resp == resp.lower() + if resp == prompt[0]: + result = True + elif resp == prompt[1]: + result = False + elif testkey in resp: + result = not default + + _out('#>%s' % ('Yes' if result else 'No')) + return result + + +def errout(message="ERROR", quit=1): + _out('!>' + message, True) + if quit: + sys_quit() + os._exit(quit) + + +def error0(message): + errout(message, 0) +__builtin__.error0 = error0 + + +# Catch all unhandled errors. +def errortrap(type, value, tb): + etext = "".join(traceback.format_exception(type, value, tb)) + errout(_("Something went wrong:\n") + etext, 100) +sys.excepthook = errortrap + + +_sub_process = None +_errorcount = 0 +def runcmd(cmd, filter=None): + global _sub_process, _errorcount + _out('>>' + cmd) + _sub_process = pexpect.spawn(cmd) + result = [] + line0 = '' + # A normal end-of-line is '\r\n', so split on '\r' but don't + # process a line until the next character is available. + while True: + try: + line0 += _sub_process.read_nonblocking(size=256, timeout=None) + except: + break + + while True: + lines = line0.split('\r', 1) + if (len(lines) > 1) and lines[1]: + line = lines[0] + line0 = lines[1] + nl = (line0[0] == '\n') + if nl: + # Strip the '\n' + line0 = line0[1:] + if filter: + line = filter(line, nl) + if line == '/*/': + continue + if nl: + line = line.rstrip() + _out('>_' + line) + result.append(line) + else: + # Probably a progress line + if _controlled: + _out('>-' + line) + else: + sys.stdout.write(line + '\r') + sys.stdout.flush() + + else: + break + + _sub_process.close() + rc = _sub_process.exitstatus + ok = (rc == 0) + if not ok: + _errorcount += 1 + _out(('>?%s' % repr(rc)) + ('' if ok else (' $$$ %s $$$' % cmd))) + return (ok, result) + + +def script(cmd): + s = runcmd("%s/%s" % (script_dir, cmd)) + if s[0]: + return "" + else: + return "SCRIPT ERROR: (%s)\n" % cmd + "".join(s[1]) + + +def chroot(ip, cmd, mnts=[], filter=None): + if ip: + for m in mnts: + mount("/" + m, "%s/%s" % (ip, m), "--bind") + cmd = "chroot %s %s" % (ip, cmd) + + s = runcmd(cmd, filter) + + if ip: + unmount(["%s/%s" % (ip, m) for m in mnts]) + + if s[0]: + if s[1]: + return s[1] + else: + return True + return False + + +_mounts = [] +def mount(src, dst, opts=""): + if runcmd("mount %s %s %s" % (opts, src, dst))[0]: + _mounts.append(dst) + return True + return False + + +def unmount(dst=None): + if dst == None: + mnts = list(_mounts) + elif type(dst) in (list, tuple): + mnts = list(dst) + else: + mnts = [dst] + + r = True + for m in mnts: + if runcmd("umount %s" % m)[0]: + _mounts.remove(m) + else: + r = False + return r + + +def get_installation_dir(): + return os.path.realpath(_options.idir if _options.idir + else INSTALLATION) + + +def get_profile(): + """Get the absolute path to the profile folder given its path in any + acceptable form, including 'user:profile-name' + """ + pd = (_options.profile if _options.profile + else base_dir + '/profiles/default') + p = pd.split(':') + if len(p) == 1: + pd = os.path.realpath(pd) + else: + try: + pd = (pwd.getpwnam(p[0])[5] + PROFILE_DIR + + '/' + p[1]) + except: + errout(_("Invalid profile: %s") % pd, quit=0) + raise + if not os.path.isfile(pd + '/addedpacks'): + errout(_("Invalid profile folder: %s") % pd) + return pd + + + +#+++++++++++++++++++++++++++++++++++++++++ +#Regular expression search strings for progress reports +import re +#lit: give []() a \-prefix +#grp: surround string in () +#opt: surround string in [] + +def _lit(s): + for c in r'[()]': + s = s.replace(c, '\\' + c) + return s + +def _grp(s, x=''): + return '(' + s + ')' + x + +def _grp0(s, x=''): + return '(?:' + s + ')' + x + +def _opt(s, x=''): + return '[' + s + ']' + x + + +_re_pacman = re.compile( _grp0(_lit('(') + + _grp(_opt('^/', '+') + '/' + _opt('^)', '+')) + + _lit(')'), '?') + + _grp('.*?') + + _lit('[') + _grp(_opt('-#', '+')) + _lit(r']\s+') + + _grp(_opt('0-9', '+')) + + '%' + ) + +_re_mksquashfs = re.compile(_lit('[.*]') + + _grp('.* ' + + _grp(_opt('0-9', '+')) + + '%') + ) + +_re_mkisofs = re.compile(_opt(' 1') + _opt(' \d') + '\d\.\d\d%') + +#----------------------------------------- +class pacman_filter_gen: + """Return a function to detect and process the progress output of + pacman. + """ + def __init__(self): + self.progress = '' + + def __call__(self, line, nl): + ms = _re_pacman.match(line) + if ms: + p = ms.group(3) + if (self.progress != p) or nl: + self.progress = p + if _controlled: + xfromy = ms.group(1) + if not xfromy: + xfromy = '' + return 'pacman:%s|%s|%s' % (xfromy, ms.group(2), + ms.group(4)) + if nl: + sys.stdout.write(' '*80 + '\r') + return line.rsplit(None, 1)[0] + else: + return '/*/' + return line + + +class mksquashfs_filter_gen: + """Return a function to detect and process the progress output of + mksquashfs. + """ + def __init__(self): + self.progress = '' + + def __call__(self, line, nl): + ms = _re_mksquashfs.match(line) + if ms: + percent = ms.group(2) + if (self.progress != percent) or nl: + self.progress = percent + if _controlled: + return 'mksquashfs:' + ms.group(1) + return re.sub(r'=[-\\/|]', '= ', line) + else: + return '/*/' + return line + + +class mkisofs_filter_gen: + """Return a function to detect and process the progress output of + mkisofs. + """ + def __call__(self, line, nl): + ms = _re_mkisofs.match(line) + if ms: + if _controlled: + return 'mkisofs:' + line + sys.stdout.write(line + '\r') + sys.stdout.flush() + return '/*/' + return line + + +def readdata(filename): + return readfile(base_dir + '/data/' + filename) + + +def readfile(fpath): + try: + fh = open(fpath) + text = fh.read() + fh.close() + except: + errout(_("Couldn't read file: %s") % fpath) + return None + return text + + +def writefile(text, path): + try: + pd = os.path.dirname(path) + if not os.path.isdir(pd): + os.makedirs(pd) + fh = None + fh = open(path, 'w') + fh.write(text) + return True + except: + return False + finally: + if fh: + fh.close() + |