Stan Seibert avatar Stan Seibert committed ae6326d

shrinkwrap_skip option for skipping installation if the system already provides the package

Comments (0)

Files changed (7)

    how
    packaging
    api
+   relnotes
    roadmap
 
 

doc/packaging.rst

     source_url = 'http://www.bzip.org/%(version)s/bzip2-%(version)s.tar.gz' % {'version': version}
 
 
-    def installer(self):
+    def installer(inst):
         self.download_and_unpack_tarball(source_url)
 
         bzip2_dir = 'bzip2-' + version
 
     python bzip2-1.0.6.py install
 
-Here, the ``shrinkwrap_installer`` argument to ``setup()`` is set to our own installer function. The installer uses two shrinkwrap-provided convenience functions, ``download_and_unpack_tarball`` and ``make`` to download, untar, and compile the bzip2 library. See :ref:`shrinkwrap_install_api` for a complete list of convenice functions. By passing extra options to make, the software is installed into the root of the active virtualenv.
+Here, the ``shrinkwrap_installer`` argument to ``setup()`` is set to our own installer function.
+The installer function is called with an instance of ShrinkwrapInstall as the argument, which provides several convenience functions. This installer uses two of these functions, ``download_and_unpack_tarball`` and ``make`` to download, untar, and compile the bzip2 library. See :ref:`shrinkwrap_install_api` for a complete list of available functions. By passing extra options to make, the software is installed into the root of the active virtualenv.
 
 .. note:: For several examples of custom installers, see `https://bitbucket.org/seibert/shrinkwrap_pkgs <https://bitbucket.org/seibert/shrinkwrap_pkgs>`_.
 
     Examples include getting code from version control, installing with cmake, and customizing install paths.
 
+.. _system_packages:
+
+Detecting System-provided Packages
+``````````````````````````````````
+
+In some situations, you only want to install a package if the operating system does not
+already provide it.  The ``shrinkwrap_skip`` argument to ``setup()`` allows you to specify
+a function to call to check if installation is performed.  If the skip function returns 
+True, then the ``installer()`` function is skipped, but the package is marked as installed.
+
+Here is an example of a package that installs curl only if version 7.26.0 is not present::
+
+    try:
+        from shrinkwrap.install import ShrinkwrapInstall
+    except ImportError:
+        import subprocess; subprocess.check_call('pip install -b $VIRTUAL_ENV/build/build-shrinkwrap shrinkwrap', shell=True)
+        from shrinkwrap.install import ShrinkwrapInstall
+    from setuptools import setup
+    from distutils.version import LooseVersion
+
+    version = '7.27.0'
+
+    def skip(inst):
+        try:
+            output = inst.cmd_output('curl-config --version')
+            name, version_str = output.split()
+            system_version = LooseVersion(version_str)
+            min_version = LooseVersion('7.26.0')
+            if system_version > min_version:
+                return True # Don't install
+            else:
+                return False # Version too old
+        except:
+            return False # install package if anything went wrong
+
+    setup(
+        name='curl-check',
+        version=version,
+        author='Stan Seibert',
+        author_email='stan@mtrr.org',
+        shrinkwrap_installer='autoconf',
+        shrinkwrap_skip=skip,
+        shrinkwrap_source_url='http://curl.haxx.se/download/curl-%s.tar.bz2' % version,
+        cmdclass={'install': ShrinkwrapInstall},
+    )
 
 .. _package_dependencies:
 
+Release Notes
+=============
+
+Version 0.10
+````````````
+
+* Added ``shrinkwrap_skip`` option for skipping installation of
+  packages if the system already provides them.  See :ref:`system_packages` for more information.
+
+Version 0.9
+```````````
+
+* First real release.
 
 * SHA1 hash checking of downloaded tar files.
 
-* An interface for shrinkwrap packages to test and report whether the wrapped software is already installed system-wide.  This would allow a shrinkwrap package to optionally skip a lengthy compile and install step when the appropriate system packages are already present.
-
 
 Far future goals:
 
             "shrinkwrap_source_url = shrinkwrap.install:assert_string",
             "shrinkwrap_source_dir = shrinkwrap.install:assert_string",
             "shrinkwrap_installer = shrinkwrap.install:validate_installer_option",
+            "shrinkwrap_skip = shrinkwrap.install:assert_callable",
             "shrinkwrap_requires = setuptools.dist:assert_string_list",
         ],
     },

shrinkwrap/__init__.py

-__version__ = '0.9'
+__version__ = '0.10'
 
 # Require a virtual environment to use shrinkwrap
 import os

shrinkwrap/install.py

             "%r must be a string value (got %r)" % (attr,value)
         )
 
+def assert_callable(dist, attr, value):
+    '''Verify that value is a callable function'''
+    if callable(value):
+        return # OK
+    else:
+        raise DistutilsSetupError(
+            "%r must be a callable function (got %r)" % (attr,value)
+        )
 
 def validate_installer_option(dist, attr, value):
     '''Verify that value is either an allowed string or a callable function'''
         if ret != success_return_code:
             raise subprocess.CalledProcessError(cmd=cmd, returncode=ret)
 
+    def cmd_output(self, cmd, success_return_code=0):
+        '''Run command in the shell and return its output as a byte string.
+
+        If return code from command not equal to ``success_return_code``,
+        raises subprocess.CalledProcessError.
+
+        Based on implementation from: https://gist.github.com/1027906
+        '''
+        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
+        output, unused_err = process.communicate()
+        retcode = process.poll()
+        if retcode:
+            error = subprocess.CalledProcessError(retcode, cmd)
+            error.output = output
+            raise error
+        return output
+        
     def make(self, parallel=True, extra_opts=None):
         '''Run make in current directory.  If ``parallel`` is true,
         will run make with the -j option and the number of CPU cores.
             build_dir = os.path.join(self.virtualenv, 'build', 'build_' + dep)
             self.shell('pip install -b ' + build_dir + ' ' + dep)
 
-        # Do the install process for this package
-        installer = self.distribution.shrinkwrap_installer
-        if installer in install_functions:
-            installer = install_functions[installer]
+        skip = getattr(self.distribution, 'shrinkwrap_skip', None)
 
-        installer(self)
+        # Test if we need to actually install anything
+        if skip is None or not skip(self):
+            # Do the install process for this package
+            installer = self.distribution.shrinkwrap_installer
+            if installer in install_functions:
+                installer = install_functions[installer]
+
+            installer(self)
+        else:
+            log.warn('Package already installed.  Skipping...')
 
         # Now finish up and register installation
         _install.install.run(self)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.