Commits

Michał Górny  committed 22eebb7

Employ a bash server to handle environment parsing.

  • Participants
  • Parent commits f381830

Comments (0)

Files changed (2)

File SmartLiveRebuild/core.py

 # (c) 2010 Michał Górny <mgorny@gentoo.org>
 # Released under the terms of the 3-clause BSD license or the GPL-2 license.
 
-import bz2, os.path, pickle, signal, subprocess, sys, tempfile, time
+import bz2, errno, fcntl, os.path, pickle, shutil, signal, subprocess, sys, tempfile, time
 import portage
 
 try:
 class SLRFailure(Exception):
 	pass
 
+class BashParser(object):
+	def __init__(self):
+		self._tmpf = tempfile.NamedTemporaryFile('w+b')
+		self._bashproc = subprocess.Popen(['bash', '-c',
+				'while read -r SLR_VARS; do source %s; eval set -- ${SLR_VARS}; printf "%%s\\0" "${@}"; done' % self._tmpf.name],
+			stdin = subprocess.PIPE, stdout = subprocess.PIPE)
+
+		fd = self._bashproc.stdout
+		fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK | fcntl.fcntl(fd, fcntl.F_GETFL))
+
+	def grabenv(self, envf):
+		f = self._tmpf
+		f.seek(0, 0)
+		f.truncate(0)
+		shutil.copyfileobj(envf, f)
+		f.flush()
+
+	def __call__(self, vars):
+		# File ready, now ping the server...
+		self._bashproc.stdin.write('%s\n' % ' '.join(['"${%s}"' % x for x in vars]))
+		self._bashproc.stdin.flush()
+
+		# ...and grab the output (hopefully, we're in non-blocking mode).
+		l = ''
+		while len(l.split('\0')) <= len(vars):
+			try:
+				l += self._bashproc.stdout.read()
+			except IOError as e:
+				if e.errno not in (errno.EAGAIN, errno.EWOULDBLOCK):
+					raise
+
+		return dict(zip(vars, l.split('\0')))
+
+	def terminate(self):
+		self._bashproc.terminate()
+		self._bashproc.communicate()
+		self._tmpf.close()
+
 def SmartLiveRebuild(opts, db = None, saveuid = False, settings = None):
 	if settings is None:
 		settings = portage.settings
 			rebuilds = {}
 
 			vcses = {}
-			envtmpf = tempfile.NamedTemporaryFile('w+b')
+			bash = BashParser()
 			try:
 				if db is None:
 					db = portage.db[settings['ROOT']]['vartree'].dbapi
 
 							if vcses[vcs] is not None:
 								env = bz2.BZ2File('%s/environment.bz2' % db.getpath(cpv), 'r')
-								vcs = vcses[vcs](cpv, env, envtmpf, opts, settings)
+								bash.grabenv(env)
+								vcs = vcses[vcs](cpv, bash, opts, settings)
 								env.close()
 								if opts.network or vcs.getsavedrev():
 									dir = vcs.getpath()
 						out.err('Error enumerating %s: [%s] %s' % (cpv, e.__class__.__name__, e))
 						erraneous.append(cpv)
 			finally:
-				envtmpf.close()
+				bash.terminate()
 
 			if opts.jobs == 1:
 				out.s1('Updating the repositories...')

File SmartLiveRebuild/vcs/__init__.py

 # (c) 2010 Michał Górny <mgorny@gentoo.org>
 # Released under the terms of the 3-clause BSD license or the GPL-2 license.
 
-import os, shutil, subprocess, sys
+import os, subprocess, sys
 
 from SmartLiveRebuild.output import out
 
 	reqenv = []
 	optenv = []
 
-	def bashparse(self, envf, vars, f):
-		f.seek(0, 0)
-		f.truncate(0)
-		shutil.copyfileobj(envf, f)
-		f.write('\nprintf "%%s\\0" %s\n' % ' '.join(['"${%s}"' % x for x in vars]))
-		f.flush()
-
-		return dict(zip(vars, self.call(['bash', f.name]).split('\0')))
-
-	def __init__(self, cpv, envf, tmpf, opts, settings):
+	def __init__(self, cpv, bash, opts, settings):
 		self.cpv = [cpv]
-		self.env = self.bashparse(envf, self.reqenv + self.optenv, tmpf)
+		self.env = bash(self.reqenv + self.optenv)
 		self._opts = opts
 		self._settings = settings