diff -urN ../portage-2.1.5.4-orig/pym/output.py pym/output.py --- ../portage-2.1.5.4-orig/pym/output.py 2008-06-07 14:10:34 +0900 +++ pym/output.py 2008-06-07 14:11:11 +0900 @@ -4,7 +4,7 @@ __docformat__ = "epytext" -import commands,errno,os,re,shlex,sys +import commands,errno,os,re,shlex,sys,time from portage_const import COLOR_MAP_FILE from portage_util import writemsg from portage_exception import CommandNotFound, FileNotFound, \ @@ -500,3 +500,101 @@ if not self.quiet: self.__eend("ewend", errno, msg) self.__last_e_cmd = "ewend" + +class EProgressBar: + """ + Implements a textual progress bar. + + @ivar progress: The current progress through the operation, in either + bytes or arbitrary units. + @type progress: IntType + @ivar total: The maximum value C{progress} is expected to reach, in the + same units as C{progress}. If zero or negative, assumed to be + unknown. + @type total: IntType + @ivar is_bytes: True if C{progress} and C{total} are byte counters, + otherwise false. Affects the manner in which the progress is + displayed. + @type is_bytes: Boolean + @ivar start_time: Time at which the operation was started (assumed to + be the time at which the progress bar was created). + @type start_time: Float + @ivar start_progress: The value of C{progress} when the operation was + started. + @type start_progress: IntType + @ivar term_columns: Width of terminal in characters. Defaults to the value + specified by the shell's C{COLUMNS} variable, else to the queried tty + size, else to C{80}. + @type term_columns: IntType + """ + + def __init__(self, total, progress = 0, is_bytes = False): + self.total = total + self.progress = progress + self.is_bytes = is_bytes + self.start_time = time.time() + self.start_progress = self.progress + lines, columns = get_term_size() + if columns <= 0: + columns = 80 + # Adjust columns so that eend works properly on a standard BSD console. + if os.environ.get("TERM") == "cons25": + columns = columns - 1 + self.term_columns = columns + + def __size_to_string(self, size): + if size >= 100000000000L: + return "%uGB" % (size / 1000000000) + elif size >= 100000000: + return "%uMB" % (size / 1000000) + else: + return "%ukB" % (size / 1000) + + def update(self, progress = -1): + """ + Optionally updates the current progress value, then draws the + progress bar on the terminal. + + @param progress: I{(optional)} The current progress value. + @type progress: IntType + """ + if progress >= 0: + self.progress = progress + if self.is_bytes: + progress_string = self.__size_to_string(self.progress) + total_string = self.__size_to_string(self.total) + else: + progress_string = "" + total_string = "%d%% " % (self.progress*100 / self.total) + if self.total > 0: + bar_size = self.term_columns - 27 + time_string = "---:--" + elapsed_time = time.time() - self.start_time + elapsed_progress = self.progress - self.start_progress + progress_left = (self.total - self.start_progress) - elapsed_progress + if elapsed_time > 0 and elapsed_progress > 0: + import math + eta = int(math.ceil((elapsed_time * progress_left) / elapsed_progress)) + if eta > 1000*60-1: + eta = 1000*60-1 + time_string = "%3d:%02d" % (eta/60, eta%60) + if bar_size > 0: + bar_width = bar_size * self.progress / self.total + bar_string = "%-*s" % (bar_size, '=' * bar_width) + sys.stderr.write("%7s |%s| %7s %s\r" \ + % (progress_string, bar_string, total_string, time_string)) + else: + sys.stderr.write("%7s/%-7s ---:--\r" \ + % (progress_string, total_string, time_string)) + else: + sys.stderr.write("%7s\r" % progress_string) + sys.stdout.flush() + + def finish(self): + """ + Draws the progress bar on the terminal, then advances to the next + line so that subsequent terminal output does not overwrite the + progress bar. + """ + self.update() + writemsg("\n") diff -urN ../portage-2.1.5.4-orig/pym/portage.py pym/portage.py --- ../portage-2.1.5.4-orig/pym/portage.py 2008-06-07 14:10:34 +0900 +++ pym/portage.py 2008-06-07 14:22:49 +0900 @@ -3418,14 +3418,20 @@ distdir_writable = can_fetch and not fetch_to_ro + concise_progress = "concise-progress" in features + + total_size = 0 # Total size to fetch in bytes, negative = unknown + fetched_size = 0 # Amount fetched so far in bytes + filestatus = {} # Status of each file (see below) + filelocks = {} # Lock for each file for myfile in filedict: """ - fetched status - 0 nonexistent - 1 partially downloaded - 2 completely downloaded + filestatus + 0 = nonexistent + 1 = partially downloaded + 2 = completely downloaded """ - fetched = 0 + filestatus[myfile] = 0 orig_digests = mydigests.get(myfile, {}) size = orig_digests.get("size") @@ -3437,7 +3443,7 @@ myfile_path = os.path.join(mysettings["DISTDIR"], myfile) has_space = True - file_lock = None + filelocks[myfile] = None if listonly: writemsg_stdout("\n", noiselevel=-1) else: @@ -3471,19 +3477,20 @@ waiting_msg = "\n".join(msg_prefix + line \ for line in wrap(waiting_msg, 65)) if locks_in_subdir: - file_lock = portage_locks.lockfile( + filelocks[myfile] = portage_locks.lockfile( os.path.join(mysettings["DISTDIR"], locks_in_subdir, myfile), wantnewlockfile=1, waiting_msg=waiting_msg) else: - file_lock = portage_locks.lockfile( + filelocks[myfile] = portage_locks.lockfile( myfile_path, wantnewlockfile=1, waiting_msg=waiting_msg) - try: - if not listonly: + + if not listonly: # no longer needed, but left in to avoid unindentation creating huge diffs --AC eout = output.EOutput() - eout.quiet = mysettings.get("PORTAGE_QUIET") == "1" + eout.quiet = concise_progress or \ + mysettings.get("PORTAGE_QUIET") == "1" match, mystat = _check_distfile( myfile_path, pruned_digests, eout) if match: @@ -3497,6 +3504,7 @@ writemsg("!!! Failed to adjust permissions:" + \ " %s\n" % str(e), noiselevel=-1) del e + filestatus[myfile] = 2 continue if distdir_writable and mystat is None: @@ -3550,6 +3558,7 @@ raise del e os.symlink(readonly_file, myfile_path) + filestatus[myfile] = 2 continue if fsmirrors and not os.path.exists(myfile_path) and has_space: @@ -3565,6 +3574,11 @@ raise del e + if myfile not in mydigests: + total_size = -1 # At least one file has an unknown size, so give up on counting + elif total_size >= 0: + total_size += mydigests[myfile]["size"]; + try: mystat = os.stat(myfile_path) except OSError, e: @@ -3596,11 +3610,12 @@ else: if mystat.st_size < mydigests[myfile]["size"] and \ not restrict_fetch: - fetched = 1 # Try to resume this download. + filestatus[myfile] = 1 # Try to resume this download. + fetched_size += mystat.st_size elif parallel_fetchonly and \ mystat.st_size == mydigests[myfile]["size"]: eout = output.EOutput() - eout.quiet = \ + eout.quiet = concise_progress or \ mysettings.get("PORTAGE_QUIET") == "1" eout.ebegin( "%s size ;-)" % (myfile, )) @@ -3628,7 +3643,7 @@ temp_filename, noiselevel=-1) else: eout = output.EOutput() - eout.quiet = \ + eout.quiet = concise_progress or \ mysettings.get("PORTAGE_QUIET", None) == "1" digests = mydigests.get(myfile) if digests: @@ -3637,8 +3652,26 @@ eout.ebegin( "%s %s ;-)" % (myfile, " ".join(digests))) eout.eend(0) - continue # fetch any remaining files - + if total_size >= 0: + total_size -= mydigests[myfile]["size"] + filestatus[myfile] = 2 # No need to fetch this one + + progress_bar = None + if concise_progress and not listonly and total_size != 0: + progress_bar = output.EProgressBar(total_size, fetched_size, is_bytes=1) + for myfile in filedict: + myfile_path = os.path.join(mysettings["DISTDIR"], myfile) + if progress_bar: + # If it's a partially-completed file, the file size will be + # already included in fetched_size. Show the progress bar + # as is, then remove this file's contribution (since we'll + # add it back in later on). + progress_bar.update(fetched_size) + try: + fetched_size -= os.stat(myfile_path).st_size; + except EnvironmentError: + pass + try: # Create a reversed list since that is optimal for list.pop(). uri_list = filedict[myfile][:] uri_list.reverse() @@ -3666,8 +3699,10 @@ resumecommand=mysettings["RESUMECOMMAND"] if not can_fetch: - if fetched != 2: - if fetched == 0: + if filestatus[myfile] != 2: + if progress_bar: + progress_bar.finish() + if filestatus[myfile] == 0: writemsg("!!! File %s isn't fetched but unable to get it.\n" % myfile, noiselevel=-1) else: @@ -3682,16 +3717,16 @@ else: continue - if fetched != 2 and has_space: + if filestatus[myfile] != 2 and has_space: #we either need to resume or start the download - if fetched == 1: + if filestatus[myfile] == 1: try: mystat = os.stat(myfile_path) except OSError, e: if e.errno != errno.ENOENT: raise del e - fetched = 0 + filestatus[myfile] = 0 else: if mystat.st_size < fetch_resume_size: writemsg((">>> Deleting distfile with size " + \ @@ -3703,16 +3738,18 @@ if e.errno != errno.ENOENT: raise del e - fetched = 0 - if fetched == 1: + filestatus[myfile] = 0 + if filestatus[myfile] == 1: #resume mode: - writemsg(">>> Resuming download...\n") + if not progress_bar: + writemsg(">>> Resuming download...\n") locfetch=resumecommand else: #normal mode: locfetch=fetchcommand - writemsg_stdout(">>> Downloading '%s'\n" % \ - re.sub(r'//(.+):.+@(.+)/',r'//\1:*password*@\2/', loc)) + if not progress_bar: + writemsg_stdout(">>> Downloading '%s'\n" % \ + re.sub(r'//(.+):.+@(.+)/',r'//\1:*password*@\2/', loc)) variables = { "DISTDIR": mysettings["DISTDIR"], "URI": loc, @@ -3732,6 +3769,16 @@ 1:sys.stdout.fileno(), 2:sys.stdout.fileno() } + if progress_bar: + # Run asynchronously so we can watch the file progress + spawn_keywords["returnpid"] = True + # Hide stdout/err so we don't disturb the progress bar + try: + fd = os.open("/dev/null", os.O_RDONLY) + spawn_keywords["fd_pipes"][1] = fd + spawn_keywords["fd_pipes"][2] = fd + except OSError, e: + pass if "userfetch" in mysettings.features and \ os.getuid() == 0 and portage_gid and portage_uid: spawn_keywords.update({ @@ -3752,6 +3799,31 @@ myret = portage_exec.spawn(myfetch, env=mysettings.environ(), **spawn_keywords) + if progress_bar: + # Wait for the process to complete, updating the + # file size as we go + mypid = myret[-1] + while mypid != 0: + # Check for process completion (do this first, + # so we get the final file size + myret = os.waitpid(mypid, os.WNOHANG) + if myret[0] == mypid: + mypid = 0 + # Update the progress bar with the current file size + try: + filesize = os.stat(myfile_path).st_size; + progress_bar.update(fetched_size + filesize) + except EnvironmentError: + pass + # Wait a bit so we don't sit on the CPU too much + time.sleep(0.1) + portage_exec.spawned_pids.remove(myret[0]) + myret = myret[1] + if myret != os.EX_OK: + if myret & 0xff: + myret = (myret & 0xff) << 8 + myret = myret >> 8 + if mysettings.selinux_enabled(): selinux.setexec(None) @@ -3763,6 +3835,8 @@ pass except portage_exception.PortageException, e: if not os.access(myfile_path, os.R_OK): + if progress_bar: + progress_bar.finish() writemsg("!!! Failed to adjust permissions:" + \ " %s\n" % str(e), noiselevel=-1) @@ -3772,7 +3846,7 @@ try: if os.stat(myfile_path).st_size == 0: os.unlink(myfile_path) - fetched = 0 + filestatus[myfile] = 0 continue except EnvironmentError: pass @@ -3784,7 +3858,7 @@ if e.errno != errno.ENOENT: raise del e - fetched = 0 + filestatus[myfile] = 0 else: # no exception? file exists. let digestcheck() report # an appropriately for size or checksum errors @@ -3804,11 +3878,11 @@ try: os.unlink(mysettings["DISTDIR"]+"/"+myfile) writemsg(">>> Deleting invalid distfile. (Improper 404 redirect from server.)\n") - fetched = 0 + filestatus[myfile] = 0 continue except (IOError, OSError): pass - fetched = 1 + filestatus[myfile] = 1 continue if True: # File is the correct size--check the checksums for the fetched @@ -3817,6 +3891,8 @@ # from another mirror... verified_ok,reason = portage_checksum.verify_all(mysettings["DISTDIR"]+"/"+myfile, mydigests[myfile]) if not verified_ok: + if progress_bar: + progress_bar.finish() print reason writemsg("!!! Fetched file: "+str(myfile)+" VERIFY FAILED!\n", noiselevel=-1) @@ -3832,7 +3908,7 @@ writemsg_stdout("Refetching... " + \ "File renamed to '%s'\n\n" % \ temp_filename, noiselevel=-1) - fetched=0 + filestatus[myfile] = 0 checksum_failure_count += 1 if checksum_failure_count == \ checksum_failure_primaryuri: @@ -3849,28 +3925,34 @@ break else: eout = output.EOutput() - eout.quiet = mysettings.get("PORTAGE_QUIET", None) == "1" + eout.quiet = progress_bar or \ + mysettings.get("PORTAGE_QUIET", None) == "1" digests = mydigests.get(myfile) if digests: eout.ebegin("%s %s ;-)" % \ (myfile, " ".join(sorted(digests)))) eout.eend(0) - fetched=2 + filestatus[myfile] = 2 break else: if not myret: - fetched=2 + filestatus[myfile] = 2 break elif mydigests!=None: writemsg("No digest file available and download failed.\n\n", noiselevel=-1) finally: - if use_locks and file_lock: - portage_locks.unlockfile(file_lock) + if use_locks and filelocks[myfile]: + portage_locks.unlockfile(filelocks[myfile]) if listonly: writemsg_stdout("\n", noiselevel=-1) - if fetched != 2: + if progress_bar: + # Save the final progress from this file + fetched_size = progress_bar.progress; + if filestatus[myfile] != 2: + if progress_bar: + progress_bar.finish() if restrict_fetch: print "\n!!!", mysettings["CATEGORY"] + "/" + \ mysettings["PF"], "has fetch restriction turned on." @@ -3889,6 +3971,10 @@ writemsg("!!! Couldn't download '%s'. Aborting.\n" % myfile, noiselevel=-1) return 0 + + if progress_bar: + progress_bar.finish() + return 1 def digestgen(myarchives, mysettings, overwrite=1, manifestonly=0, myportdb=None): @@ -4052,7 +4138,8 @@ return 0 mf = Manifest(pkgdir, mysettings["DISTDIR"]) eout = output.EOutput() - eout.quiet = mysettings.get("PORTAGE_QUIET", None) == "1" + eout.quiet = mysettings.get("PORTAGE_QUIET", None) == "1" \ + or "concise-progress" in mysettings.features try: if strict: eout.ebegin("checking ebuild checksums ;-)") @@ -5259,8 +5346,17 @@ if not os.access(parent_dir, os.W_OK): raise portage_exception.PermissionDenied( "access('%s', os.W_OK)" % parent_dir) - retval = spawnebuild(mydo, - actionmap, mysettings, debug, logfile=logfile) + if mydo=="install" and "concise-progress" in mysettings.features: + timingfile = os.path.join( + mysettings["PORTAGE_TIMINGDIR"], mysettings["PN"]) + import portage_timing + writemsg(">>> Compiling %s...\n" % mysettings.mycpv) + retval = portage_timing.timed_spawnebuild(timingfile, + "install", actionmap, mysettings, debug, + logfile=logfile) + else: + retval = spawnebuild(mydo, + actionmap, mysettings, debug, logfile=logfile) elif mydo=="qmerge": # check to ensure install was run. this *only* pops up when users # forget it and are using ebuild @@ -5279,8 +5375,17 @@ myroot, mysettings, myebuild=mysettings["EBUILD"], mytree=tree, mydbapi=mydbapi, vartree=vartree, prev_mtimes=prev_mtimes) elif mydo=="merge": - retval = spawnebuild("install", actionmap, mysettings, debug, - alwaysdep=1, logfile=logfile) + if "concise-progress" in mysettings.features: + timingfile = os.path.join( + mysettings["PORTAGE_TIMINGDIR"], mysettings["PN"]) + import portage_timing + writemsg(">>> Compiling %s...\n" % mysettings.mycpv) + retval = portage_timing.timed_spawnebuild(timingfile, + "install", actionmap, mysettings, debug, + alwaysdep=1, logfile=logfile) + else: + retval = spawnebuild("install", actionmap, mysettings, debug, + alwaysdep=1, logfile=logfile) retval = exit_status_check(retval) if retval != os.EX_OK: # The merge phase handles this already. Callers don't know how diff -urN ../portage-2.1.5.4-orig/pym/portage_timing.py pym/portage_timing.py --- ../portage-2.1.5.4-orig/pym/portage_timing.py 1970-01-01 09:00:00 +0900 +++ pym/portage_timing.py 2008-06-07 14:11:11 +0900 @@ -0,0 +1,328 @@ +# portage_timing.py -- routines to time build execution +# Written by Andrew Church +# This source code is public domain. + +import os, re, select, sys, time +import portage +from portage_util import writemsg, writemsg_stdout +from portage_versions import pkgsplit +import output + + +# Output recorded from the current build: +timing_output = [] +# Time at which each line in timing_output[] was recorded: +timing_outtimes = [] +# Current position in timing_{output,times}[]: (to avoid having to extend +# the array every time a new element is added) +timing_outpos = 0 + + +def read_timing(timingfile): + """ + Read a possibly compressed timing file, returning the timing template + as an array of 3-element arrays: the first element of each subarray is + the elapsed time in microseconds between that line and the next, the + second element is the line itself as a string (with no trailing + newline), and the third is the completion fraction at the time the line + is first seen. The timing template array is terminated by an entry + whose first (time) element is the total execution time in microseconds, + whose third (fraction) element is 1.0, and whose remaining elements are + undefined. + + @param timingfile Full pathname of the timing file, without any + compression extension. + @type timingfile String + """ + try: + f = open(timingfile, "r") + timing = [] + for line in f: + timestamp, command = line.split(" ", 1) + command = command.strip("\n") + timing.append([int(timestamp), command, 0.0]) + f.close() + total = 0 + for entry in timing: + entry[2] = (total * 1.0) / timing[-1][0] + total += entry[0] + return timing + except EnvironmentError: + # FIXME: compression not handled at present + return None + + +def write_timing(timingfile): + """ + Write a new timing file from the timing_output[] and timing_outtimes[] + arrays. + + @param timingfile Full pathname of the timing file, without any + compression extension. + @type timingfile String + """ + + try: + timing_outtimes.append(None) # just in case + timing_outtimes[timing_outpos] = time.time() + f = open(timingfile, "w") + total = 0 + for i in range(0, timing_outpos): + line_time = int((timing_outtimes[i+1] - timing_outtimes[i]) * 1000000) + f.write("%u %s\n" % (line_time, timing_output[i])) + total += line_time + f.write("%u -\n" % total) + f.close() + # FIXME: compression not handled at present + except EnvironmentError: + pass + + +def timed_spawnebuild(timingfile,mydo,actionmap,mysettings,debug,alwaysdep=0,logfile=None): + """ + Call spawnebuild() in a subprocess, comparing the output to a template + file to display a progress bar instead of the raw output (if the + template file is unavailable, the raw output is shown). If the + operation succeeds, the output and timing information is recorded in + the same template file for future reference. + + @param timingfile Full pathname of the timing file, without any + compression extension. + @type timingfile String + + The remaining parameters are as for spawnebuild(). + """ + + # Read in the timing file, if it exists, and initialize the template + # index. + timing = read_timing(timingfile) + timing_index = -1 + + # Allocate a pseudo-TTY for reading output from the subprocess. If we + # can't, just give up and run the build normally. + master_fd = None + slave_fd = None + from pty import openpty + try: + master_fd, slave_fd = openpty() + except EnvironmentError, e: + writemsg("openpty failed: '%s'\n" % str(e), noiselevel=1) + del e + return portage.spawnebuild(mydo, actionmap, mysettings, debug, + alwaysdep, logfile) + + # Run spawnebuild() in a subprocess, with output redirected to our + # pseudo-TTY. + pid = os.fork() + if pid == 0: + os.close(0) + try: + null_fd = os.open("/dev/null", os.O_RDONLY) + if null_fd != 0: + os.dup2(null_fd, 0) + os.close(null_fd) + except EnvironmentError: + pass + os.dup2(slave_fd, 1) + os.dup2(slave_fd, 2) + os.close(master_fd) + os.close(slave_fd) + retval = portage.spawnebuild(mydo, actionmap, mysettings, debug, + alwaysdep, logfile) + sys.exit(retval) + else: + os.close(slave_fd) + import fcntl + fcntl.fcntl(master_fd, fcntl.F_SETFL, + fcntl.fcntl(master_fd, fcntl.F_GETFL) | os.O_NONBLOCK) + + # Set up a progress bar if we have a timing template. + progress_bar = None + if timing: + progress_bar = output.EProgressBar(timing[-1][0]) + progress_bar.update(0) + last_update = time.time() + + # Clear the new timing data. + global timing_output, timing_outtimes, timing_outpos + timing_output = [] + timing_outtimes = [] + timing_outpos = 0 + + # Read output from the subprocess until it completes, updating the new + # timing template for each line read. + master_file = os.fdopen(master_fd, "r") + saved_line = "" # In case we only get a partial line when reading + eof = False # Becomes true when we get an EOF from the pseudo-TTY + finished = False # Becomes true when the subprocess exits + result = None # Result code from the subprocess + while not finished or not eof: + # Check for subprocess completion. + waitres = os.waitpid(pid, os.WNOHANG) + if waitres and waitres[0] == pid: + finished = True + if waitres[1] == os.EX_OK: + result = os.EX_OK + elif waitres[1] & 0xFF: + result = (waitres[1] & 0xFF) << 8 + else: + result = waitres[1] >> 8 + # Check for build output. + events = select.select([master_file], [], [], 0.1) + if events: + s = None + try: + s = master_file.read(1000) + except IOError: + eof = True + if s is None: + if len(saved_line) > 0: + # The final line didn't end with a newline, so add one. + s = "\n" + s = "" + eof = True + elif s == "": + break # No more output this time around + saved_line += s + now = time.time() + index = saved_line.find("\n") + while index >= 0: + line = saved_line[0:index] + saved_line = saved_line[index+1:] + timing_index = add_line(line, timing, timing_index, \ + master_file, mysettings) + if not timing: + writemsg_stdout("%s\n" % line); + index = saved_line.find("\n") + # Update the progress bar (but not too frequently). + if timing and now - last_update >= 0.01: + # Calculate the rate at which we're progressing relative to the + # data recorded in the template. + rate = 0 + if timing_index >= 0 and timing_outpos > 0 \ + and timing[timing_index][2] > 0: + rate = (timing_outtimes[timing_outpos-1] - timing_outtimes[0]) \ + / (timing[-1][0]/1000000.0 * timing[timing_index][2]) + # Calculate the percentage complete, including partial processing + # for the current line. + pct = 0 + if timing_index >= 0: + pct = timing[timing_index][2] + if rate > 0 and now > timing_outtimes[timing_outpos-1]: + extra_time = now - timing_outtimes[timing_outpos-1] + extra_pct = (extra_time / rate) / (timing[-1][0] / 1000000.0) + if extra_pct > timing[timing_index+1][2] - pct: + extra_pct = timing[timing_index+1][2] - pct + pct += extra_pct + # Update the progress bar, making sure it uses our idea of the + # starting time. + if timing_outpos > 0: + progress_bar.start_time = timing_outtimes[0] + progress_bar.update(int(timing[-1][0] * pct)) + # Save the last update time to avoid over-frequent updates. + last_update = now + # while not finished + + # Finish off the progress bar. + if timing: + progress_bar.progress = progress_bar.total + progress_bar.finish() + + # Write out the new timing data + if result == os.EX_OK: + write_timing(timingfile) + + # Return the build result + return result + + +# Variables used by add_line: +search_last = 0 +search_wait = 0 +search_max = 0 + +def add_line(line, timing, timing_index, master_file, mysettings): + """ + Process a line of output from the build subprocess. + + @param line: The line of text, without any trailing newline. + @type line: String + @param timing: The timing template, or None if not available. + @type timing: Array + @param timing_index: The current index into the timing template. + @type timing_index: Integer + @param master_file: Pseudo-TTY from which build output is read. + @type master_file: Filehandle + @param mysettings: Portage configuration. + @type mysettings: Instance of portage.config + @returns The new timing index, or 0 if no template is available. + """ + + global timing_output, timing_outtimes, timing_outpos + + now = time.time() + + # First, append the line to the output arrays. Massage the line a bit + # so that changes in version number, compilation options and the like + # don't cause mismatches later on. + package, version = pkgsplit(mysettings.mycpv.split("/",1)[1])[0:2] + line = re.sub(r'\s+', r' ', line) + line = line.replace(package+'-'+version, package+'-VER') + line = re.sub(r'(^|\W)(cc|gcc|xgcc|gcc3|icc)(\W)', r'\1CC\3', line) + line = re.sub(r'(^|\W)(c\+\+|g\+\+|g\+\+3)(\W)', r'\1CXX\3', line) + if re.match(r'^\s*(cd\s+\S+\s*;\s*)?(if\s+)?(stage\d\/)?(CC|CXX)[^=]', line): + line = re.sub(r'(\s+-(-\S+|[^-\s]\S*))+', r' -OPTS', line) + if timing_outpos >= len(timing_output): + # Extend the arrays in big jumps for efficiency + timing_output.append([None]*1000) + timing_outtimes.append([None]*1000) + timing_output[timing_outpos] = line + timing_outtimes[timing_outpos] = now + timing_outpos += 1 + + # Now look for a matching line in the template (if we don't have a + # template, just return 0). + if not timing: + return 0 + global search_last # Time of last search + global search_wait # Time in seconds to wait before the next search + global search_max # Maximum time in seconds to search + # If the next line matches, use it. + if timing[timing_index+1][1] == line: + search_wait = 0 + search_max = 0.05 + return timing_index+1 + # Otherwise we have to search. But don't do so on every line, or we + # could eat huge amounts of CPU time if the build output differs from + # the template. + if time.time() - search_last < search_wait: + return timing_index + # Search forward for a matching line until we hit our time limit, but + # don't keep the build process waiting if it has more output for us. + found = False + got_output = False + search_limit = time.time() + search_max + index = timing_index + 2 + while index < len(timing)-1 and time.time() - search_limit: + if timing[index][1] == line: + found = True + break + if not got_output and select.select([master_file], [], [], 0): + got_output = True + search_limit = time.time() + 0.01 + index += 1 + # Save the time this search ended. + search_last = time.time() + # If we found a match, return it; otherwise try harder next time. + if found: + search_wait = 0; + search_max = 0.05; + return index + else: + search_wait += 0.05 + if search_wait > 3: + search_wait = 3 + search_max += 0.05 + if search_wait > 1: + search_wait = 1 + return timing_index