Binpkg: add chpathtool support for prefix
authorZac Medico <zmedico@gentoo.org>
Sat, 10 Dec 2011 03:21:44 +0000 (19:21 -0800)
committerZac Medico <zmedico@gentoo.org>
Sat, 10 Dec 2011 03:21:44 +0000 (19:21 -0800)
This uses a python-based chpathtool implementation which is intended to
be compatible with the C-based implemenation that the prefix branch
uses.

bin/chpathtool.py [new file with mode: 0755]
pym/_emerge/Binpkg.py

diff --git a/bin/chpathtool.py b/bin/chpathtool.py
new file mode 100755 (executable)
index 0000000..d0d49cb
--- /dev/null
@@ -0,0 +1,182 @@
+#!/usr/bin/python
+# Copyright 2011 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import io
+import optparse
+import os
+import stat
+import sys
+
+CONTENT_ENCODING = "utf_8"
+FS_ENCODING = "utf_8"
+
+try:
+       import magic
+except ImportError:
+       magic = None
+else:
+       try:
+               magic.MIME_TYPE
+       except AttributeError:
+               # magic module seems to be broken
+               magic = None
+
+class IsTextFile(object):
+
+       def __init__(self):
+               if magic is not None:
+                       self._call = self._is_text_magic
+                       self._m = magic.open(magic.MIME_TYPE)
+                       self._m.load()
+               else:
+                       self._call = self._is_text_encoding
+                       self._encoding = CONTENT_ENCODING
+
+       def __call__(self, filename):
+               """
+               Returns True if the given file is a text file, and False otherwise.
+               """
+               return self._call(filename)
+
+       def _is_text_magic(self, filename):
+               mime_type = self._m.file(filename)
+               return mime_type.startswith("text/")
+
+       def _is_text_encoding(self, filename):
+               try:
+                       for line in io.open(filename, mode='r', encoding=self._encoding):
+                               pass
+               except UnicodeDecodeError:
+                       return False
+               return True
+
+def chpath_inplace(filename, is_text_file, old, new):
+       """
+       Returns True if any modifications were made, and False otherwise.
+       """
+
+       modified = False
+       orig_stat = os.lstat(filename)
+       try:
+               f = io.open(filename, buffering=0, mode='r+b')
+       except IOError:
+               try:
+                       orig_mode = stat.S_IMODE(os.lstat(filename).st_mode)
+               except OSError as e:
+                       sys.stderr.write("%s: %s\n" % (e, filename))
+                       return
+               temp_mode = 0o200 | orig_mode
+               os.chmod(filename, temp_mode)
+               try:
+                       f = io.open(filename, buffering=0, mode='r+b')
+               finally:
+                       os.chmod(filename, orig_mode)
+
+       len_old = len(old)
+       len_new = len(new)
+       matched_byte_count = 0
+       while True:
+               in_byte = f.read(1)
+
+               if not in_byte:
+                       break
+
+               if in_byte == old[matched_byte_count]:
+                       matched_byte_count += 1
+                       if matched_byte_count == len_old:
+                               modified = True
+                               matched_byte_count = 0
+                               end_position = f.tell()
+                               start_position = end_position - len_old
+                               if not is_text_file:
+                                       # search backwards for leading slashes written by
+                                       # a previous invocation of this tool
+                                       num_to_write = len_old
+                                       f.seek(start_position - 1)
+                                       while True:
+                                               if f.read(1) != b'/':
+                                                       break
+                                               num_to_write += 1
+                                               f.seek(f.tell() - 2)
+
+                                       # pad with as many leading slashes as necessary
+                                       while num_to_write > len_new:
+                                               f.write(b'/')
+                                               num_to_write -= 1
+                                       f.write(new)
+                               else:
+                                       remainder = f.read()
+                                       f.seek(start_position)
+                                       f.write(new)
+                                       if remainder:
+                                               f.write(remainder)
+                                               f.truncate()
+                                               f.seek(start_position + len_new)
+               elif matched_byte_count > 0:
+                       # back up an try to start a new match after
+                       # the first byte of the previous partial match
+                       f.seek(f.tell() - matched_byte_count)
+                       matched_byte_count = 0
+
+       f.close()
+       if modified:
+               orig_mtime = orig_stat[stat.ST_MTIME]
+               os.utime(filename, (orig_mtime, orig_mtime))
+       return modified
+
+def chpath_inplace_symlink(filename, st, old, new):
+       target = os.readlink(filename)
+       if target.startswith(old):
+               new_target = new + target[len(old):]
+               os.unlink(filename)
+               os.symlink(new_target, filename)
+               os.lchown(filename, st.st_uid, st.st_gid)
+
+def main(argv):
+
+       usage = "%s [options] <location> <old> <new>" % (os.path.basename(argv[0],))
+       parser = optparse.OptionParser(usage=usage)
+       options, args = parser.parse_args(argv[1:])
+
+       if len(args) != 3:
+               parser.error("3 args required, got %s" % (len(args),))
+
+       location, old, new = args
+
+       is_text_file = IsTextFile()
+
+       if not isinstance(location, bytes):
+               location = location.encode(FS_ENCODING)
+       if not isinstance(old, bytes):
+               old = old.encode(FS_ENCODING)
+       if not isinstance(new, bytes):
+               new = new.encode(FS_ENCODING)
+
+       st = os.lstat(location)
+
+       if stat.S_ISDIR(st.st_mode):
+               for parent, dirs, files in os.walk(location):
+                       for filename in files:
+                               filename = os.path.join(parent, filename)
+                               try:
+                                       st = os.lstat(filename)
+                               except OSError:
+                                       pass
+                               else:
+                                       if stat.S_ISREG(st.st_mode):
+                                               chpath_inplace(filename,
+                                                       is_text_file(filename), old, new)
+                                       elif stat.S_ISLNK(st.st_mode):
+                                               chpath_inplace_symlink(filename, st, old, new)
+
+       elif stat.S_ISREG(st.st_mode):
+               chpath_inplace(location,
+                       is_text_file(location), old, new)
+       elif stat.S_ISLNK(st.st_mode):
+               chpath_inplace_symlink(location, st, old, new)
+
+       return os.EX_OK
+
+if __name__ == "__main__":
+       sys.exit(main(sys.argv))
index bc6511e73ca3751d57a47a08fe9e65c0572e595d..5a58ad288e9858ee806f01cc4857c5bd509e543f 100644 (file)
@@ -9,8 +9,9 @@ from _emerge.CompositeTask import CompositeTask
 from _emerge.BinpkgVerifier import BinpkgVerifier
 from _emerge.EbuildMerge import EbuildMerge
 from _emerge.EbuildBuildDir import EbuildBuildDir
+from _emerge.SpawnProcess import SpawnProcess
 from portage.eapi import eapi_exports_replace_vars
-from portage.util import writemsg
+from portage.util import ensure_dirs, writemsg
 import portage
 from portage import os
 from portage import _encodings
@@ -18,6 +19,7 @@ from portage import _unicode_decode
 from portage import _unicode_encode
 import io
 import logging
+import shutil
 from portage.output import colorize
 
 class Binpkg(CompositeTask):
@@ -25,7 +27,8 @@ class Binpkg(CompositeTask):
        __slots__ = ("find_blockers",
                "ldpath_mtimes", "logger", "opts",
                "pkg", "pkg_count", "prefetcher", "settings", "world_atom") + \
-               ("_bintree", "_build_dir", "_ebuild_path", "_fetched_pkg",
+               ("_bintree", "_build_dir", "_build_prefix",
+               "_ebuild_path", "_fetched_pkg",
                "_image_dir", "_infloc", "_pkg_path", "_tree", "_verify")
 
        def _writemsg_level(self, msg, level=0, noiselevel=0):
@@ -83,9 +86,9 @@ class Binpkg(CompositeTask):
 
                        waiting_msg = ("Fetching '%s' " + \
                                "in the background. " + \
-                               "To view fetch progress, run `tail -f " + \
+                               "To view fetch progress, run `tail -f %s" + \
                                "/var/log/emerge-fetch.log` in another " + \
-                               "terminal.") % prefetcher.pkg_path
+                               "terminal.") % (prefetcher.pkg_path, settings["EPREFIX"])
                        msg_prefix = colorize("GOOD", " * ")
                        from textwrap import wrap
                        waiting_msg = "".join("%s%s\n" % (msg_prefix, line) \
@@ -299,10 +302,61 @@ class Binpkg(CompositeTask):
                self._start_task(extractor, self._extractor_exit)
 
        def _extractor_exit(self, extractor):
-               if self._final_exit(extractor) != os.EX_OK:
+               if self._default_exit(extractor) != os.EX_OK:
                        self._unlock_builddir()
                        self._writemsg_level("!!! Error Extracting '%s'\n" % \
                                self._pkg_path, noiselevel=-1, level=logging.ERROR)
+                       self.wait()
+                       return
+
+               try:
+                       with io.open(_unicode_encode(os.path.join(self._infloc, "EPREFIX"),
+                               encoding=_encodings['fs'], errors='strict'), mode='r',
+                               encoding=_encodings['repo.content'], errors='replace') as f:
+                               self._build_prefix = f.read().rstrip('\n')
+               except IOError:
+                       self._build_prefix = ""
+
+               if self._build_prefix == self.settings["EPREFIX"]:
+                       self._current_task = None
+                       self.returncode = os.EX_OK
+                       self.wait()
+                       return
+
+               chpathtool = SpawnProcess(
+                       args=[portage._python_interpreter,
+                       os.path.join(self.settings["PORTAGE_BIN_PATH"], "chpathtool.py"),
+                       self.settings["D"], self._build_prefix, self.settings["EPREFIX"]],
+                       background=self.background, env=self.settings.environ(), 
+                       scheduler=self.scheduler,
+                       logfile=self.settings.get('PORTAGE_LOG_FILE'))
+               self._writemsg_level(">>> Adjusting Prefix to %s\n" % self.settings["EPREFIX"])
+               self._start_task(chpathtool, self._chpathtool_exit)
+
+       def _chpathtool_exit(self, chpathtool):
+               if self._final_exit(chpathtool) != os.EX_OK:
+                       self._unlock_builddir()
+                       self._writemsg_level("!!! Error Adjusting Prefix to %s" %
+                               (self.settings["EPREFIX"],),
+                               noiselevel=-1, level=logging.ERROR)
+                       self.wait()
+                       return
+
+               # We want to install in "our" prefix, not the binary one
+               with io.open(_unicode_encode(os.path.join(self._infloc, "EPREFIX"),
+                       encoding=_encodings['fs'], errors='strict'), mode='w',
+                       encoding=_encodings['repo.content'], errors='strict') as f:
+                       f.write(self.settings["EPREFIX"] + "\n")
+
+               # Move the files to the correct location for merge.
+               image_tmp_dir = os.path.join(
+                       self.settings["PORTAGE_BUILDDIR"], "image_tmp")
+               os.rename(os.path.join(self.settings["D"],
+                       self._build_prefix.lstrip(os.sep)), image_tmp_dir)
+               shutil.rmtree(self._image_dir)
+               ensure_dirs(os.path.dirname(self.settings["ED"]))
+               os.rename(image_tmp_dir, self.settings["ED"])
+
                self.wait()
 
        def _unlock_builddir(self):