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