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