[jhbuild/wip/packaging: 2/6] autotools: Use make install DESTDIR=
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [jhbuild/wip/packaging: 2/6] autotools: Use make install DESTDIR=
- Date: Thu, 5 May 2011 23:23:58 +0000 (UTC)
commit 01a5af5ffe25c99b7f7bd9e441ee03c3490e9256
Author: Colin Walters <walters verbum org>
Date: Mon Apr 25 20:36:47 2011 -0400
autotools: Use make install DESTDIR=
Like most "package managers", use DESTDIR= to create a temporary
installation tree, from which we can generate a file manifest.
Add a <manifest> node to PackageDB which contains the contents of a
build.
Then implement "uninstall" using this. A big advantage now is that we
don't need to do a build in order to do an uninstall, and uninstall is
independent of version.
https://bugzilla.gnome.org/show_bug.cgi?id=647231
jhbuild/modtypes/__init__.py | 52 ++++++++++++
jhbuild/modtypes/autotools.py | 21 +++---
jhbuild/utils/Makefile.am | 1 +
jhbuild/utils/fileutils.py | 47 +++++++++++
jhbuild/utils/packagedb.py | 172 +++++++++++++++++++++++++++++++++++-----
5 files changed, 261 insertions(+), 32 deletions(-)
---
diff --git a/jhbuild/modtypes/__init__.py b/jhbuild/modtypes/__init__.py
index 6b27eeb..9a511dc 100644
--- a/jhbuild/modtypes/__init__.py
+++ b/jhbuild/modtypes/__init__.py
@@ -28,10 +28,12 @@ __all__ = [
]
import os
+import shutil
from jhbuild.errors import FatalError, CommandError, BuildStateError, \
SkipToEnd, UndefinedRepositoryError
from jhbuild.utils.sxml import sxml
+from jhbuild.utils.fileutils import move_directory_tree
_module_types = {}
def register_module_type(name, parse_func):
@@ -131,6 +133,7 @@ class Package:
self.suggests = suggests
self.tags = []
self.moduleset_name = None
+ self.supports_install_destdir = False
def __repr__(self):
return "<%s '%s'>" % (self.__class__.__name__, self.name)
@@ -144,6 +147,55 @@ class Package:
def get_builddir(self, buildscript):
raise NotImplementedError
+ def _get_destdir(self, buildscript):
+ return os.path.join(buildscript.config.workdir, 'root-%s' % (self.name, ))
+
+ def prepare_installroot(self, buildscript):
+ assert self.supports_install_destdir
+ """Return a directory suitable for use as e.g. DESTDIR with "make install"."""
+ destdir = self._get_destdir(buildscript)
+ if os.path.exists(destdir):
+ shutil.rmtree(destdir)
+ os.makedirs(destdir)
+ return destdir
+
+ def _process_install_files(self, installroot, curdir, prefix):
+ """Strip the prefix from all files in the install root, and move
+them into the prefix."""
+ assert os.path.isdir(installroot) and os.path.isabs(installroot)
+ assert os.path.isdir(curdir) and os.path.isabs(curdir)
+ assert os.path.isdir(prefix) and os.path.isabs(prefix)
+
+ if prefix.endswith('/'):
+ prefix = prefix[:-1]
+
+ names = os.listdir(curdir)
+ for filename in names:
+ src_path = os.path.join(curdir, filename)
+ assert src_path.startswith(installroot)
+ dest_path = src_path[len(installroot):]
+ if os.path.isdir(src_path):
+ if os.path.exists(dest_path):
+ if not os.path.isdir(dest_path):
+ os.unlink(dest_path)
+ os.mkdir(dest_path)
+ else:
+ os.mkdir(dest_path)
+ self._process_install_files(installroot, src_path, prefix)
+ os.rmdir(src_path)
+ else:
+ os.rename(src_path, dest_path)
+
+ def process_install(self, buildscript, revision):
+ assert self.supports_install_destdir
+ destdir = self._get_destdir(buildscript)
+ buildscript.packagedb.add(self.name, revision or '', destdir)
+ self._process_install_files(destdir, destdir, buildscript.config.prefix)
+ try:
+ os.rmdir(destdir)
+ except:
+ pass
+
def get_revision(self):
return self.branch.tree_id()
diff --git a/jhbuild/modtypes/autotools.py b/jhbuild/modtypes/autotools.py
index 215df91..f9c576f 100644
--- a/jhbuild/modtypes/autotools.py
+++ b/jhbuild/modtypes/autotools.py
@@ -66,6 +66,7 @@ class AutogenModule(Package, DownloadableModule):
self.makefile = makefile
self.autogen_template = autogen_template
self.check_target = check_target
+ self.supports_install_destdir = True
def get_srcdir(self, buildscript):
return self.branch.srcdir
@@ -262,14 +263,18 @@ class AutogenModule(Package, DownloadableModule):
def do_install(self, buildscript):
buildscript.set_action(_('Installing'), self)
+ destdir = self.prepare_installroot(buildscript)
if self.makeinstallargs:
- cmd = '%s %s' % (os.environ.get('MAKE', 'make'), self.makeinstallargs)
+ cmd = '%s %s DESTDIR=%s' % (os.environ.get('MAKE', 'make'),
+ self.makeinstallargs,
+ destdir)
else:
- cmd = '%s install' % os.environ.get('MAKE', 'make')
-
+ cmd = '%s install DESTDIR=%s' % (os.environ.get('MAKE', 'make'),
+ destdir)
buildscript.execute(cmd, cwd = self.get_builddir(buildscript),
extra_env = self.extra_env)
- buildscript.packagedb.add(self.name, self.get_revision() or '')
+ self.process_install(buildscript, self.get_revision())
+
do_install.depends = [PHASE_BUILD]
def do_distclean(self, buildscript):
@@ -291,12 +296,8 @@ class AutogenModule(Package, DownloadableModule):
def do_uninstall(self, buildscript):
buildscript.set_action(_('Uninstalling'), self)
- makeargs = self.makeargs + ' ' + self.config.module_makeargs.get(
- self.name, self.config.makeargs)
- cmd = '%s %s uninstall' % (os.environ.get('MAKE', 'make'), makeargs)
- buildscript.execute(cmd, cwd = self.get_builddir(buildscript),
- extra_env = self.extra_env)
- buildscript.packagedb.remove(self.name)
+ # Since we are supports_install_destdir = True, just delegate to packagedb
+ buildscript.packagedb.uninstall(self.name, buildscript)
def xml_tag_and_attrs(self):
return ('autotools',
diff --git a/jhbuild/utils/Makefile.am b/jhbuild/utils/Makefile.am
index b6d63bf..a404892 100644
--- a/jhbuild/utils/Makefile.am
+++ b/jhbuild/utils/Makefile.am
@@ -3,6 +3,7 @@ appdir = $(pythondir)/jhbuild/utils/
app_PYTHON = \
__init__.py \
cmds.py \
+ fileutils.py \
httpcache.py \
notify.py \
packagedb.py \
diff --git a/jhbuild/utils/fileutils.py b/jhbuild/utils/fileutils.py
new file mode 100644
index 0000000..adcb240
--- /dev/null
+++ b/jhbuild/utils/fileutils.py
@@ -0,0 +1,47 @@
+# jhbuild - a build script for GNOME 1.x and 2.x
+# Copyright (C) 2011 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import sys
+import os
+
+def _move_directory_tree_recurse(source, target):
+ assert os.path.isdir(source) and os.path.isabs(source)
+ assert os.path.isabs(target)
+ # We don't require here that the destination path exist
+ if os.path.exists(target):
+ if not os.path.isdir(target):
+ os.unlink(target)
+ os.mkdir(target)
+ else:
+ os.mkdir(target)
+
+ names = os.listdir(source)
+ for filename in names:
+ source_path = os.path.join(source, filename)
+ dest_path = os.path.join(target, filename)
+ if os.path.isdir(source_path):
+ _move_directory_tree_recurse(source_path, dest_path)
+ os.rmdir(source_path)
+ else:
+ os.rename(source_path, dest_path)
+
+def move_directory_tree(source, target):
+ """Move all of the files from TARGET into SOURCE."""
+ assert os.path.isdir(source) and os.path.isabs(source)
+ assert os.path.isdir(target) and os.path.isabs(target)
+
+ _move_directory_tree_recurse(source, target)
diff --git a/jhbuild/utils/packagedb.py b/jhbuild/utils/packagedb.py
index 09d4b8b..d26c7e7 100644
--- a/jhbuild/utils/packagedb.py
+++ b/jhbuild/utils/packagedb.py
@@ -17,6 +17,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+import os
import time
try:
import xml.dom.minidom
@@ -32,6 +33,80 @@ def _parse_isotime(string):
def _format_isotime(tm):
return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(tm))
+def _get_text_content(node):
+ content = ''
+ for child in node.childNodes:
+ if (child.nodeType == child.TEXT_NODE):
+ content += child.nodeValue
+ return content
+
+def _list_from_xml(node, child_name):
+ """Parse XML like:
+ <foolist>
+ <item>content</item>
+ <item>more content</item>
+ ...
+ </foolist>."""
+ result = []
+ for child in node.childNodes:
+ if not (child.nodeType == child.ELEMENT_NODE and child.nodeName == child_name):
+ continue
+ result.append(_get_text_content(child))
+ return result
+
+def _find_node(node, child_name):
+ """Get the first child node named @child_name"""
+ for child in node.childNodes:
+ if not (child.nodeType == child.ELEMENT_NODE and child.nodeName == child_name):
+ continue
+ return child
+ return None
+
+class PackageEntry:
+ def __init__(self, package, version, manifest,
+ metadata):
+ self.package = package # string
+ self.version = version # string
+ self.manifest = manifest # list of strings
+ self.metadata = metadata # hash of string to value
+
+ @classmethod
+ def from_xml(cls, node):
+ package = node.getAttribute('package')
+ version = node.getAttribute('version')
+ metadata = {}
+
+ installed_string = node.getAttribute('installed')
+ if installed_string:
+ metadata['installed-date'] = _parse_isotime(installed_string)
+
+ manifestNode = _find_node(node, 'manifest')
+ if manifestNode:
+ manifest = _list_from_xml(manifestNode, 'file')
+ else:
+ manifest = None
+ return cls(package, version, manifest, metadata)
+
+ def to_xml(self, document):
+ entryNode = document.createElement('entry')
+ entryNode.setAttribute('package', self.package)
+ entryNode.setAttribute('version', self.version)
+ if 'installed-date' in self.metadata:
+ entryNode.setAttribute('installed', _format_isotime(self.metadata['installed-date']))
+ entryNode.appendChild(document.createTextNode('\n'))
+ if self.manifest is not None:
+ manifestNode = document.createElement('manifest')
+ entryNode.appendChild(manifestNode)
+ manifestNode.appendChild(document.createTextNode('\n'))
+ for filename in self.manifest:
+ node = document.createElement('file')
+ node.appendChild(document.createTextNode(filename))
+ manifestNode.appendChild(document.createTextNode(' '))
+ manifestNode.appendChild(node)
+ manifestNode.appendChild(document.createTextNode('\n'))
+ entryNode.appendChild(document.createTextNode('\n'))
+ return entryNode
+
class PackageDB:
def __init__(self, dbfile):
self.dbfile = dbfile
@@ -49,10 +124,9 @@ class PackageDB:
for node in document.documentElement.childNodes:
if node.nodeType != node.ELEMENT_NODE: continue
if node.nodeName != 'entry': continue
- package = node.getAttribute('package')
- version = node.getAttribute('version')
- installed = _parse_isotime(node.getAttribute('installed'))
- self.entries[package] = (version, installed)
+
+ entry = PackageEntry.from_xml(node)
+ self.entries[entry.package] = entry
document.unlink()
def _write_cache(self):
@@ -60,44 +134,98 @@ class PackageDB:
document.appendChild(document.createElement('packagedb'))
node = document.createTextNode('\n')
document.documentElement.appendChild(node)
- for package in self.entries:
- version, installed = self.entries[package]
- node = document.createElement('entry')
- node.setAttribute('package', package)
- node.setAttribute('version', version)
- node.setAttribute('installed', _format_isotime(installed))
+ for package,entry in self.entries.iteritems():
+ node = entry.to_xml(document)
document.documentElement.appendChild(node)
-
node = document.createTextNode('\n')
document.documentElement.appendChild(node)
- document.writexml(open(self.dbfile, 'w'))
+ tmp_dbfile_path = self.dbfile + '.tmp'
+ tmp_dbfile = open(tmp_dbfile_path, 'w')
+ try:
+ document.writexml(tmp_dbfile)
+ except:
+ tmp_dbfile.close()
+ os.unlink(tmp_dbfile_path)
+ raise
+ tmp_dbfile.close()
+ os.rename(tmp_dbfile_path, self.dbfile)
document.unlink()
- def add(self, package, version):
+ def _accumulate_dirtree_contents_recurse(self, path, contents):
+ assert os.path.isdir(path)
+ names = os.listdir(path)
+ for name in names:
+ subpath = os.path.join(path, name)
+ if os.path.isdir(subpath):
+ contents.append(subpath + '/')
+ self._accumulate_dirtree_contents_recurse(subpath, contents)
+ else:
+ contents.append(subpath)
+
+ def _accumulate_dirtree_contents(self, path):
+ contents = []
+ self._accumulate_dirtree_contents_recurse(path, contents)
+ if not path.endswith('/'):
+ path = path + '/'
+ pathlen = len(path)
+ for i,subpath in enumerate(contents):
+ assert subpath.startswith(path)
+ # Strip the temporary prefix, then make it absolute again for our target
+ contents[i] = '/' + subpath[pathlen:]
+ return contents
+
+ def add(self, package, version, destdir):
'''Add a module to the install cache.'''
now = time.time()
- self.entries[package] = (version, now)
+ contents = self._accumulate_dirtree_contents(destdir)
+ metadata = {'installed-date': now}
+ self.entries[package] = PackageEntry(package, version, contents, metadata)
self._write_cache()
def check(self, package, version=None):
'''Check whether a particular module is installed.'''
if not self.entries.has_key(package): return False
- p_version, p_installed = self.entries[package]
+ entry = self.entries[package]
if version is not None:
- if version != p_version: return False
+ if entry.version != version: return False
return True
def installdate(self, package, version=None):
'''Get the install date for a particular module.'''
if not self.entries.has_key(package): return None
- p_version, p_installed = self.entries[package]
+ entry = self.entries[package]
if version:
- if version != p_version: return None
- return p_installed
+ if entry.version != version: return None
+ return entry.metadata['installed-date']
- def remove(self, package):
+ def uninstall(self, package_name, buildscript):
'''Remove a module from the install cache.'''
- if self.entries.has_key(package):
- del self.entries[package]
+ if package_name in self.entries:
+ entry = self.entries[package_name]
+ if entry.manifest is None:
+ buildscript.message("warning: no manifest known for '%s', not deleting files")
+ else:
+ directories = []
+ for path in entry.manifest:
+ assert os.path.isabs(path)
+ print "Deleting %r" % (path, )
+ if os.path.isdir(path):
+ directories.append(path)
+ else:
+ os.unlink(path)
+ for directory in directories:
+ if not directory.startswith(buildscript.prefix):
+ # Skip non-prefix directories; otherwise we
+ # may try to remove the user's ~ or something
+ # (presumably we'd fail, but better not to try)
+ continue
+ try:
+ os.rmdir(directory)
+ except OSError, e:
+ # Allow multiple components to use directories
+ pass
+ del self.entries[package_name]
self._write_cache()
+ else:
+ buildscript.message("warning: no package known for '%s'")
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]