summaryrefslogtreecommitdiffstats
path: root/build_tools/larch8/larch0/cli/backend.py
diff options
context:
space:
mode:
Diffstat (limited to 'build_tools/larch8/larch0/cli/backend.py')
-rw-r--r--build_tools/larch8/larch0/cli/backend.py459
1 files changed, 459 insertions, 0 deletions
diff --git a/build_tools/larch8/larch0/cli/backend.py b/build_tools/larch8/larch0/cli/backend.py
new file mode 100644
index 0000000..510491c
--- /dev/null
+++ b/build_tools/larch8/larch0/cli/backend.py
@@ -0,0 +1,459 @@
+# 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.11.09
+
+# 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 debug(text):
+ sys.stderr.write("DEBUG: " + text.strip() + "\n")
+ sys.stderr.flush()
+
+sys.path.append(os.path.dirname(base_dir))
+from liblarch.translation import i18n_module, lang
+__builtin__._ = i18n_module(base_dir, 'larch')
+__builtin__.lang = lang
+# Run subprocesses without i18n in case the output is parsed.
+os.environ["LANGUAGE"] = "C"
+
+
+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)
+ if _controlled:
+ _out('>-_$$_%d' % os.getpid())
+
+ 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 and not text.startswith('>-'):
+ # Don't log the progress report lines
+ _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):
+ _out('?>' + message)
+ if _dontask:
+ result = True
+
+ elif _controlled:
+ result = (raw_input().strip() == '??YES')
+
+ else:
+ prompt = _("Yes:y|No:n")
+ py, pn = prompt.split('|')
+ respy = py.lower().split(':')
+ respn = pn.lower().split(':')
+ while True:
+ resp = raw_input(" [ %s ]: " % prompt).strip().lower()
+ if resp in respy:
+ result = True
+ break
+ if resp in respn:
+ result = False
+ break
+
+ _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:
+ nl, 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:
+ mdir = "%s/%s" % (ip, m)
+ if not os.path.isdir(mdir):
+ runcmd('mkdir -p %s' % mdir)
+ mount("/" + m, mdir, "--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_psub = re.compile(r'\[[#-]+\]')
+_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
+ xfromy = ms.group(1)
+ if _controlled:
+ if not xfromy:
+ xfromy = ''
+ line = 'pacman:%s|%s|%s%%' % (xfromy, ms.group(2),
+ ms.group(4))
+ elif ms.group(4) == '100':
+ line = re_psub.sub('[##########]', line)
+ if nl:
+ sys.stdout.write(' '*80 + '\r')
+ else:
+ line = '/*/'
+ return (nl, 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:
+ line = 'mksquashfs:' + ms.group(1)
+ else:
+ line = re.sub(r'=[-\\/|]', '= ', line)
+ else:
+ line = '/*/'
+ return (nl, line)
+
+
+class mkisofs_filter_gen:
+ """Return a function to detect and process the progress output of
+ mkisofs.
+ """
+ def __init__(self):
+ self.running = None
+
+ def __call__(self, line, nl):
+ ms = _re_mkisofs.match(line)
+ if ms:
+ if _controlled:
+ line = 'mkisofs:' + line
+ self.running = line
+ nl = False
+ elif self.running:
+ line = self.running + '\n' + line
+ self.running = None
+ return (nl, 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()
+