Package _emerge :: Module EbuildPhase
[hide private]

Source Code for Module _emerge.EbuildPhase

  1  # Copyright 1999-2013 Gentoo Foundation 
  2  # Distributed under the terms of the GNU General Public License v2 
  3   
  4  import gzip 
  5  import io 
  6  import sys 
  7  import tempfile 
  8   
  9  from _emerge.AsynchronousLock import AsynchronousLock 
 10  from _emerge.BinpkgEnvExtractor import BinpkgEnvExtractor 
 11  from _emerge.MiscFunctionsProcess import MiscFunctionsProcess 
 12  from _emerge.EbuildProcess import EbuildProcess 
 13  from _emerge.CompositeTask import CompositeTask 
 14  from portage.package.ebuild.prepare_build_dirs import _prepare_workdir 
 15  from portage.util import writemsg 
 16   
 17  try: 
 18          from portage.xml.metadata import MetaDataXML 
 19  except (SystemExit, KeyboardInterrupt): 
 20          raise 
 21  except (ImportError, SystemError, RuntimeError, Exception): 
 22          # broken or missing xml support 
 23          # http://bugs.python.org/issue14988 
 24          MetaDataXML = None 
 25   
 26  import portage 
 27  portage.proxy.lazyimport.lazyimport(globals(), 
 28          'portage.elog:messages@elog_messages', 
 29          'portage.package.ebuild.doebuild:_check_build_log,' + \ 
 30                  '_post_phase_cmds,_post_phase_userpriv_perms,' + \ 
 31                  '_post_src_install_soname_symlinks,' + \ 
 32                  '_post_src_install_uid_fix,_postinst_bsdflags,' + \ 
 33                  '_post_src_install_write_metadata,' + \ 
 34                  '_preinst_bsdflags' 
 35  ) 
 36  from portage import os 
 37  from portage import _encodings 
 38  from portage import _unicode_encode 
 39   
40 -class EbuildPhase(CompositeTask):
41 42 __slots__ = ("actionmap", "fd_pipes", "phase", "settings") + \ 43 ("_ebuild_lock",) 44 45 # FEATURES displayed prior to setup phase 46 _features_display = ( 47 "ccache", "compressdebug", "distcc", "distcc-pump", "fakeroot", 48 "installsources", "keeptemp", "keepwork", "nostrip", 49 "preserve-libs", "sandbox", "selinux", "sesandbox", 50 "splitdebug", "suidctl", "test", "userpriv", 51 "usersandbox" 52 ) 53 54 # Locked phases 55 _locked_phases = ("setup", "preinst", "postinst", "prerm", "postrm") 56
57 - def _start(self):
58 59 need_builddir = self.phase not in EbuildProcess._phases_without_builddir 60 61 if need_builddir: 62 phase_completed_file = os.path.join( 63 self.settings['PORTAGE_BUILDDIR'], 64 ".%sed" % self.phase.rstrip('e')) 65 if not os.path.exists(phase_completed_file): 66 # If the phase is really going to run then we want 67 # to eliminate any stale elog messages that may 68 # exist from a previous run. 69 try: 70 os.unlink(os.path.join(self.settings['T'], 71 'logging', self.phase)) 72 except OSError: 73 pass 74 75 if self.phase in ('nofetch', 'pretend', 'setup'): 76 77 use = self.settings.get('PORTAGE_BUILT_USE') 78 if use is None: 79 use = self.settings['PORTAGE_USE'] 80 81 maint_str = "" 82 upstr_str = "" 83 metadata_xml_path = os.path.join(os.path.dirname(self.settings['EBUILD']), "metadata.xml") 84 if MetaDataXML is not None and os.path.isfile(metadata_xml_path): 85 herds_path = os.path.join(self.settings['PORTDIR'], 86 'metadata/herds.xml') 87 try: 88 metadata_xml = MetaDataXML(metadata_xml_path, herds_path) 89 maint_str = metadata_xml.format_maintainer_string() 90 upstr_str = metadata_xml.format_upstream_string() 91 except SyntaxError: 92 maint_str = "<invalid metadata.xml>" 93 94 msg = [] 95 msg.append("Package: %s" % self.settings.mycpv) 96 if self.settings.get('PORTAGE_REPO_NAME'): 97 msg.append("Repository: %s" % self.settings['PORTAGE_REPO_NAME']) 98 if maint_str: 99 msg.append("Maintainer: %s" % maint_str) 100 if upstr_str: 101 msg.append("Upstream: %s" % upstr_str) 102 103 msg.append("USE: %s" % use) 104 relevant_features = [] 105 enabled_features = self.settings.features 106 for x in self._features_display: 107 if x in enabled_features: 108 relevant_features.append(x) 109 if relevant_features: 110 msg.append("FEATURES: %s" % " ".join(relevant_features)) 111 112 # Force background=True for this header since it's intended 113 # for the log and it doesn't necessarily need to be visible 114 # elsewhere. 115 self._elog('einfo', msg, background=True) 116 117 if self.phase == 'package': 118 if 'PORTAGE_BINPKG_TMPFILE' not in self.settings: 119 self.settings['PORTAGE_BINPKG_TMPFILE'] = \ 120 os.path.join(self.settings['PKGDIR'], 121 self.settings['CATEGORY'], self.settings['PF']) + '.tbz2' 122 123 if self.phase in ("pretend", "prerm"): 124 env_extractor = BinpkgEnvExtractor(background=self.background, 125 scheduler=self.scheduler, settings=self.settings) 126 if env_extractor.saved_env_exists(): 127 self._start_task(env_extractor, self._env_extractor_exit) 128 return 129 # If the environment.bz2 doesn't exist, then ebuild.sh will 130 # source the ebuild as a fallback. 131 132 self._start_lock()
133
134 - def _env_extractor_exit(self, env_extractor):
135 if self._default_exit(env_extractor) != os.EX_OK: 136 self.wait() 137 return 138 139 self._start_lock()
140
141 - def _start_lock(self):
142 if (self.phase in self._locked_phases and 143 "ebuild-locks" in self.settings.features): 144 eroot = self.settings["EROOT"] 145 lock_path = os.path.join(eroot, portage.VDB_PATH + "-ebuild") 146 if os.access(os.path.dirname(lock_path), os.W_OK): 147 self._ebuild_lock = AsynchronousLock(path=lock_path, 148 scheduler=self.scheduler) 149 self._start_task(self._ebuild_lock, self._lock_exit) 150 return 151 152 self._start_ebuild()
153
154 - def _lock_exit(self, ebuild_lock):
155 if self._default_exit(ebuild_lock) != os.EX_OK: 156 self.wait() 157 return 158 self._start_ebuild()
159
160 - def _get_log_path(self):
161 # Don't open the log file during the clean phase since the 162 # open file can result in an nfs lock on $T/build.log which 163 # prevents the clean phase from removing $T. 164 logfile = None 165 if self.phase not in ("clean", "cleanrm") and \ 166 self.settings.get("PORTAGE_BACKGROUND") != "subprocess": 167 logfile = self.settings.get("PORTAGE_LOG_FILE") 168 return logfile
169
170 - def _start_ebuild(self):
171 172 fd_pipes = self.fd_pipes 173 if fd_pipes is None: 174 if not self.background and self.phase == 'nofetch': 175 # All the pkg_nofetch output goes to stderr since 176 # it's considered to be an error message. 177 fd_pipes = {1 : sys.__stderr__.fileno()} 178 179 ebuild_process = EbuildProcess(actionmap=self.actionmap, 180 background=self.background, fd_pipes=fd_pipes, 181 logfile=self._get_log_path(), phase=self.phase, 182 scheduler=self.scheduler, settings=self.settings) 183 184 self._start_task(ebuild_process, self._ebuild_exit)
185
186 - def _ebuild_exit(self, ebuild_process):
187 188 if self._ebuild_lock is not None: 189 self._ebuild_lock.unlock() 190 self._ebuild_lock = None 191 192 fail = False 193 if self._default_exit(ebuild_process) != os.EX_OK: 194 if self.phase == "test" and \ 195 "test-fail-continue" in self.settings.features: 196 # mark test phase as complete (bug #452030) 197 try: 198 open(_unicode_encode(os.path.join( 199 self.settings["PORTAGE_BUILDDIR"], ".tested"), 200 encoding=_encodings['fs'], errors='strict'), 201 'wb').close() 202 except OSError: 203 pass 204 else: 205 fail = True 206 207 if not fail: 208 self.returncode = None 209 210 logfile = self._get_log_path() 211 212 if self.phase == "install": 213 out = io.StringIO() 214 _check_build_log(self.settings, out=out) 215 msg = out.getvalue() 216 self.scheduler.output(msg, log_path=logfile) 217 218 if fail: 219 self._die_hooks() 220 return 221 222 settings = self.settings 223 _post_phase_userpriv_perms(settings) 224 225 if self.phase == "unpack": 226 # Bump WORKDIR timestamp, in case tar gave it a timestamp 227 # that will interfere with distfiles / WORKDIR timestamp 228 # comparisons as reported in bug #332217. Also, fix 229 # ownership since tar can change that too. 230 os.utime(settings["WORKDIR"], None) 231 _prepare_workdir(settings) 232 elif self.phase == "install": 233 out = io.StringIO() 234 _post_src_install_write_metadata(settings) 235 _post_src_install_uid_fix(settings, out) 236 msg = out.getvalue() 237 if msg: 238 self.scheduler.output(msg, log_path=logfile) 239 elif self.phase == "preinst": 240 _preinst_bsdflags(settings) 241 elif self.phase == "postinst": 242 _postinst_bsdflags(settings) 243 244 post_phase_cmds = _post_phase_cmds.get(self.phase) 245 if post_phase_cmds is not None: 246 if logfile is not None and self.phase in ("install",): 247 # Log to a temporary file, since the code we are running 248 # reads PORTAGE_LOG_FILE for QA checks, and we want to 249 # avoid annoying "gzip: unexpected end of file" messages 250 # when FEATURES=compress-build-logs is enabled. 251 fd, logfile = tempfile.mkstemp() 252 os.close(fd) 253 post_phase = MiscFunctionsProcess(background=self.background, 254 commands=post_phase_cmds, fd_pipes=self.fd_pipes, 255 logfile=logfile, phase=self.phase, scheduler=self.scheduler, 256 settings=settings) 257 self._start_task(post_phase, self._post_phase_exit) 258 return 259 260 # this point is not reachable if there was a failure and 261 # we returned for die_hooks above, so returncode must 262 # indicate success (especially if ebuild_process.returncode 263 # is unsuccessful and test-fail-continue came into play) 264 self.returncode = os.EX_OK 265 self._current_task = None 266 self.wait()
267
268 - def _post_phase_exit(self, post_phase):
269 270 self._assert_current(post_phase) 271 272 log_path = None 273 if self.settings.get("PORTAGE_BACKGROUND") != "subprocess": 274 log_path = self.settings.get("PORTAGE_LOG_FILE") 275 276 if post_phase.logfile is not None and \ 277 post_phase.logfile != log_path: 278 # We were logging to a temp file (see above), so append 279 # temp file to main log and remove temp file. 280 self._append_temp_log(post_phase.logfile, log_path) 281 282 if self._final_exit(post_phase) != os.EX_OK: 283 writemsg("!!! post %s failed; exiting.\n" % self.phase, 284 noiselevel=-1) 285 self._die_hooks() 286 return 287 288 if self.phase == "install": 289 out = io.StringIO() 290 _post_src_install_soname_symlinks(self.settings, out) 291 msg = out.getvalue() 292 if msg: 293 self.scheduler.output(msg, log_path=log_path) 294 295 self._current_task = None 296 self.wait() 297 return
298
299 - def _append_temp_log(self, temp_log, log_path):
300 301 temp_file = open(_unicode_encode(temp_log, 302 encoding=_encodings['fs'], errors='strict'), 'rb') 303 304 log_file, log_file_real = self._open_log(log_path) 305 306 for line in temp_file: 307 log_file.write(line) 308 309 temp_file.close() 310 log_file.close() 311 if log_file_real is not log_file: 312 log_file_real.close() 313 os.unlink(temp_log)
314
315 - def _open_log(self, log_path):
316 317 f = open(_unicode_encode(log_path, 318 encoding=_encodings['fs'], errors='strict'), 319 mode='ab') 320 f_real = f 321 322 if log_path.endswith('.gz'): 323 f = gzip.GzipFile(filename='', mode='ab', fileobj=f) 324 325 return (f, f_real)
326
327 - def _die_hooks(self):
328 self.returncode = None 329 phase = 'die_hooks' 330 die_hooks = MiscFunctionsProcess(background=self.background, 331 commands=[phase], phase=phase, logfile=self._get_log_path(), 332 fd_pipes=self.fd_pipes, scheduler=self.scheduler, 333 settings=self.settings) 334 self._start_task(die_hooks, self._die_hooks_exit)
335
336 - def _die_hooks_exit(self, die_hooks):
337 if self.phase != 'clean' and \ 338 'noclean' not in self.settings.features and \ 339 'fail-clean' in self.settings.features: 340 self._default_exit(die_hooks) 341 self._fail_clean() 342 return 343 self._final_exit(die_hooks) 344 self.returncode = 1 345 self.wait()
346
347 - def _fail_clean(self):
348 self.returncode = None 349 portage.elog.elog_process(self.settings.mycpv, self.settings) 350 phase = "clean" 351 clean_phase = EbuildPhase(background=self.background, 352 fd_pipes=self.fd_pipes, phase=phase, scheduler=self.scheduler, 353 settings=self.settings) 354 self._start_task(clean_phase, self._fail_clean_exit) 355 return
356
357 - def _fail_clean_exit(self, clean_phase):
358 self._final_exit(clean_phase) 359 self.returncode = 1 360 self.wait()
361
362 - def _elog(self, elog_funcname, lines, background=None):
363 if background is None: 364 background = self.background 365 out = io.StringIO() 366 phase = self.phase 367 elog_func = getattr(elog_messages, elog_funcname) 368 global_havecolor = portage.output.havecolor 369 try: 370 portage.output.havecolor = \ 371 self.settings.get('NOCOLOR', 'false').lower() in ('no', 'false') 372 for line in lines: 373 elog_func(line, phase=phase, key=self.settings.mycpv, out=out) 374 finally: 375 portage.output.havecolor = global_havecolor 376 msg = out.getvalue() 377 if msg: 378 log_path = None 379 if self.settings.get("PORTAGE_BACKGROUND") != "subprocess": 380 log_path = self.settings.get("PORTAGE_LOG_FILE") 381 self.scheduler.output(msg, log_path=log_path, 382 background=background)
383