extras-buildsys/builder builder.py,1.35,1.36

Daniel Williams (dcbw) fedora-extras-commits at redhat.com
Wed Aug 31 01:56:40 UTC 2005


Author: dcbw

Update of /cvs/fedora/extras-buildsys/builder
In directory cvs-int.fedora.redhat.com:/tmp/cvs-serv22735/builder

Modified Files:
	builder.py 
Log Message:
2005-08-30  Dan Williams <dcbw at redhat.com>

    * builder/builder.py
      common/ExecUtils.py
        - To execute mock, we now fork() and execv() the mock process
            so that we can more reliably gather its output
        - Correctly clean up the mock root directory
        - Rename builder.log -> job.log so it can't get confused
            with build.log from mock




Index: builder.py
===================================================================
RCS file: /cvs/fedora/extras-buildsys/builder/builder.py,v
retrieving revision 1.35
retrieving revision 1.36
diff -u -r1.35 -r1.36
--- builder.py	30 Aug 2005 21:47:20 -0000	1.35
+++ builder.py	31 Aug 2005 01:56:38 -0000	1.36
@@ -21,7 +21,6 @@
 import socket
 import os
 import shutil
-import popen2
 import sha
 import time
 import sys
@@ -37,6 +36,7 @@
 from plague import AuthedXMLRPCServer
 from plague import HTTPServer
 from plague import daemonize
+from plague import ExecUtils
 from optparse import OptionParser
 
 sys.path.append('/usr/share/plague/builder')
@@ -88,13 +88,14 @@
         self._repo_locked = True
         self._repo_locked_msg = False
         self._files = []
-        self._pobj = None
+        self._childpid = 0
         self._target_cfg = target_cfg
         self._builder_cfg = target_cfg.parent_cfg()
         self._srpm_url = srpm_url
         self._log_fd = None
         self._mock_config = None
         self._done_status = ''
+        self._mock_log = None
         self.buildroot = self._target_cfg.mock_config()
 
         work_dir = self._builder_cfg.get_str("Directories", "builder_work_dir")
@@ -106,7 +107,7 @@
         if not os.path.exists(self._state_dir):
             os.makedirs(self._state_dir)
 
-        logfile = os.path.join(self._result_dir, "builder.log")
+        logfile = os.path.join(self._result_dir, "job.log")
         self._log_fd = open(logfile, "w+")
 
         target_dict = self._target_cfg.target_dict()
@@ -137,17 +138,18 @@
 
     def die(self, sig=15):
         if self.is_done_status() or self._done_status == 'killed':
-            return
+            return True
         self._die = True
+        return True
 
     def _handle_death(self):
         self._log("Killing build process...\n")
         # Don't try to kill a running cleanup process
-        if self._status != 'cleanup' and self._pobj and self._pobj.pid:
+        if self._status != 'cleanup' and self._childpid:
             try:
-                os.kill(self._pobj.pid, 15)
+                os.kill(self._childpid, 15)
             except OSError, e:
-                self._log("Couldn't kill process %d: %s\n" % (self._pobj.pid, e))
+                self._log("Couldn't kill process %d: %s\n" % (self._childpid, e))
 
         self._log("Killed.\n");
         self._done_status = 'killed'
@@ -186,19 +188,47 @@
                 self._status = 'failed'
                 self._log("Failed to retrieve %s.\n" % url)
 
+    def _copy_mock_output_to_log(self):
+        if self._mock_log and os.path.exists(self._mock_log):
+            ml = open(self._mock_log, "r")
+            line = "foo"
+            while len(line):
+                line = ml.readline()
+                if len(line):
+                    self._log_fd.write(line)
+            ml.close()
+            os.remove(self._mock_log)
+            self._mock_log = None
+
     def _start_build(self):
         self._log("Starting step 'building' with command:\n")
         if not os.path.exists(self._result_dir):
             os.makedirs(self._result_dir)
         if not os.path.exists(self._result_dir):
             os.makedirs(self._result_dir)
-        mock_args = "-r %s --arch %s --resultdir=%s --statedir=%s --uniqueext=%s %s" % (self.buildroot,
-                        self.buildarch, self._result_dir, self._state_dir, self._uniqid, self._srpm_path)
-        builder_cmd = self._builder_cfg.get_str("General", "builder_cmd")
-        cmd = '%s %s %s' % (self.arch_command, builder_cmd, mock_args)
-        self._log("   %s\n" % cmd)
-        self._pobj = popen2.Popen4(cmd=cmd, bufsize=1024)
-        fcntl.fcntl(self._pobj.fromchild.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
+
+        # Set up build process arguments
+        args = []
+        builder_cmd = os.path.abspath(self._builder_cfg.get_str("General", "builder_cmd"))
+        cmd = builder_cmd
+        if self.arch_command and len(self.arch_command):
+            arg_list = self.arch_command.split()
+            for arg in arg_list:
+                args.append(arg)
+            cmd = os.path.abspath(arg_list[0])
+        args.append(builder_cmd)
+        args.append("-r")
+        args.append(self.buildroot)
+        args.append("--arch")
+        args.append(self.buildarch)
+        args.append("--resultdir=%s" % self._result_dir)
+        args.append("--statedir=%s" % self._state_dir)
+        args.append("--uniqueext=%s" % self._uniqid)
+        args.append(self._srpm_path)
+        self._log("   %s\n" % string.join(args))
+
+        self._mock_log = os.path.join(self._result_dir, "mock-output.log")
+        self._childpid = ExecUtils.exec_with_redirect(cmd, args, None, self._mock_log, self._mock_log)
         self._status = 'prepping'
 
         # Poll a bit to wait for mock to write out the status file if
@@ -210,33 +240,54 @@
                 time.sleep(0.5)
             except KeyboardInterrupt:
                 pass
+
             # if mock exited with an error report that error and not
             # the missing status file.
-            exit_status = self._pobj.poll()
-            if exit_status > 0:
+            (aux_pid, status) = os.waitpid(self._childpid, os.WNOHANG)
+            status = os.WEXITSTATUS(status)
+            if aux_pid:
+                # If mock exits anywhere here, something is wrong no matter
+                # what it's exit status
+                self._childpid = 0
+                self._copy_mock_output_to_log()
                 self._status = 'failed'
                 break
 
-            # Kill mock after 7s if it didn't dump the status file
-            if time.time() - start_time > 7:
+            # Kill mock after 15s if it didn't dump the status file
+            if time.time() - start_time > 15:
+                self._copy_mock_output_to_log()
                 self._log("Timed out waiting for the mock status file!  %s\n" % mockstatusfile)
                 try:
                     self._log("Killing mock...\n")
-                    os.kill(self._pobj.pid, 15)
+                    os.kill(self._childpid, 15)
                 except OSError, e:
-                    self._log("Couldn't kill mock process %d: %s\n" % (self._pobj.pid, e))
+                    self._log("Couldn't kill mock process %d: %s\n" % (self._childpid, e))
                 else:
                     self._log("Killed.\n")
+
                 self._status = 'failed'
                 break
 
     def _start_cleanup(self):
         self._log("Cleaning up the buildroot...\n")
-        builder_cmd = self._builder_cfg.get_str("General", "builder_cmd")
-        cmd = '%s %s clean --uniqueext=%s -r %s' % (self.arch_command,
-                            builder_cmd, self._uniqid, self.buildroot)
-        self._log("   %s\n" % cmd)
-        self._pobj = popen2.Popen4(cmd=cmd)
+
+        args = []
+        builder_cmd = os.path.abspath(self._builder_cfg.get_str("General", "builder_cmd"))
+        cmd = builder_cmd
+        if self.arch_command and len(self.arch_command):
+            arg_list = self.arch_command.split()
+            for arg in arg_list:
+                args.append(arg)
+            cmd = os.path.abspath(arg_list[0])
+        args.append(builder_cmd)
+        args.append("clean")
+        args.append("--uniqueext=%s" % self._uniqid)
+        args.append("-r")
+        args.append(self.buildroot)
+
+        self._log("   %s\n" % string.join(args))
+        self._childpid = ExecUtils.exec_with_redirect(cmd, args, None, None, None)
+
         self._status = 'cleanup'
 
     def _mock_is_prepping(self):
@@ -309,37 +360,23 @@
         f.close()
         return contents
 
-    def _grab_mock_output(self):
-        """ Grab mock output and write it to a log """
-        if self._pobj:
-            string = ' '
-            while len(string) > 0:
-                try:
-                    string = os.read(self._pobj.fromchild.fileno(), 1024)
-                except OSError, e:
-                    if e.errno == errno.EAGAIN:     # Resource temporarily unavailable
-                        break
-                    else:
-                        self._log("Error reading mock output: %s\n" % e)
-                else:
-                    # We don't care about output from the 'cleanup' stage
-                    if self._status != 'cleanup':
-                        self._log_fd.write(string)
-                        self._log_fd.flush()
-                        os.fsync(self._log_fd.fileno())
-
     def _mock_done(self):
-        # Ensure child mock is reaped
-        if self._pobj:
-            self._pobj.poll()
+        # Ensure child process is reaped
+        if self._childpid:
+            try:
+                (pid, status) = os.waitpid(self._childpid, 0)
+            except OSError, e:
+                self._childpid = 0
+                pass
+
+        self._copy_mock_output_to_log()
 
         self._files = self._find_files()
         self._log("\n\n-----------------------\n\n")
         if self._status == 'done':
             self._log("Job completed successfully.\n")
         elif self._status == 'failed':
-            if self._pobj:
-                exit_status = self._pobj.poll()
+            if self._childpid:
                 self._log("Job failed due to mock errors!  Please see output in root.log and build.log\n")
         elif self._status == 'killed':
             self._log("Job failed because it was killed.\n")
@@ -372,36 +409,31 @@
             self._status = 'building'
 
     def _status_building(self):
-        exit_status = self._pobj.poll()
-        if exit_status == 0:
-            # mock completed successfully
-            if self._status != 'building':
-                self._log("Bad job end status %s encountered!" % self._status)
-            self._done_status = 'done'
-            self._start_cleanup()
-        elif exit_status > 0:
-            # mock exited with an error
-            self._done_status = 'failed'
-            self._start_cleanup()
+        (aux_pid, status) = os.waitpid(self._childpid, os.WNOHANG)
+        status = os.WEXITSTATUS(status)
+        if aux_pid:
+            self._childpid = 0
+            if status == 0:
+                self._done_status = 'done'
+            elif status > 0:
+                self._done_status = 'failed'
+
+        self._start_cleanup()
 
     def _status_cleanup(self):
-        exit_status = self._pobj.poll()
-        if exit_status >= 0:
+        (aux_pid, status) = os.waitpid(self._childpid, os.WNOHANG)
+        if aux_pid:
             # Mock exited
             self._status = self._done_status
-            if self._mock_config and self._mock_config.has_key('rootdir') and self._mock_config.has_key('statedir'):
-                # Kill the entire job dir, not just the rootdir
-                job_dir = os.path.normpath(self._mock_config['rootdir'] + "/../")
-                job_dir2 = os.path.normpath(self._mock_config['statedir'] + "/../")
-                print job_dir, job_dir2
-
-                # Be a little paranoid about randomly removing an entire directory.
-                # Compare the rootdir's parent to the statedir's parent and remove the
-                # parent only if they match.
-                if job_dir == job_dir2:
-                    shutil.rmtree(job_dir, ignore_errors=True)
-                else:
-                    shutil.rmtree(self._mock_config['rootdir'], ignore_errors=True)
+            if self._mock_config:
+                if self._mock_config.has_key('rootdir'):
+                    mock_root_dir = os.path.abspath(os.path.join(self._mock_config['rootdir'], "../"))
+                    # Ensure we're actually deleteing the job's rootdir
+                    if mock_root_dir.endswith(self._uniqid):
+                        shutil.rmtree(mock_root_dir, ignore_errors=True)
+
+                if self._mock_config.has_key('statedir'):
+                    shutil.rmtree(self._mock_config['statedir'], ignore_errors=True)
 
     def run(self):
         while True:
@@ -416,7 +448,6 @@
                 self._log("ERROR: internal builder inconsistency, didn't recognize status '%s'." % self._status)
                 self._status = 'failed'
 
-            self._grab_mock_output()
             if self.is_done_status():
                 self._mock_done()
                 break




More information about the fedora-extras-commits mailing list