From bb83e0944014f8c62b587cbee19b1889d5546d45 Mon Sep 17 00:00:00 2001 From: pje Date: Sat, 20 Mar 2004 21:00:15 +0000 Subject: Flesh out 'depends' command to display dependencies' status, and halt if all requirements aren't met. (Also, check planned install location for the dependencies, as well as checking sys.path.) Also: * Allow 'Feature()' objects to include 'Require()' objects, so that dependencies can be optional * 'Require()' objects can set a homepage, whose URL will be displayed by the 'depends' command if the dependency needs to be installed. * Misc. fixes/refactoring of version validation to properly handle "unknown" versions, and to decouple version fetching from version checking. * Updated TODO to remove various completed items. git-svn-id: svn://svn.eby-sarna.com/svnroot/wsgiref@244 571e12c6-e1fa-0310-aee7-ff1267fa46bd --- setuptools/command/depends.py | 67 +++++++++++++++++++++++++++++++++++++---- setuptools/depends.py | 34 ++++++++++----------- setuptools/dist.py | 16 +++++----- setuptools/tests/__init__.py | 69 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 140 insertions(+), 46 deletions(-) diff --git a/setuptools/command/depends.py b/setuptools/command/depends.py index e149fac..8917c83 100644 --- a/setuptools/command/depends.py +++ b/setuptools/command/depends.py @@ -1,7 +1,9 @@ from distutils.cmd import Command -import os +import os, sys + class depends(Command): + """Download and install dependencies, if needed""" description = "download and install dependencies, if needed" @@ -13,15 +15,68 @@ class depends(Command): "ignore options that won't be passed to child setup scripts"), ] + path_attrs = [ + # Note: these must be in *reverse* order, as they are pushed onto the + # *front* of a copy of sys.path. + ('install','install_libbase'), # installation base if extra_path + ('install_lib','install_dir'), # where modules are installed + ] + + def initialize_options(self): self.temp = None - self.install_purelib = self.install_platlib = None - self.install_lib = self.install_libbase = None - self.install_scripts = self.install_data = self.install_headers = None - self.compiler = self.debug = self.force = None def finalize_options(self): self.set_undefined_options('build',('build_temp', 'temp')) + self.set_search_path() + + def set_search_path(self): + """Determine paths to check for installed dependencies""" + path = sys.path[:] # copy sys path + for cmd,attr in self.path_attrs: + dir = getattr(self.get_finalized_command(cmd),attr,None) + if dir and dir not in path: + path.insert(0,dir) # prepend + self.search_path = path def run(self): - self.announce("downloading and building here") + self.announce("checking for installed dependencies") + needed = [ + dep for dep in self.distribution.requires if self.is_needed(dep) + ] + if not needed: + self.announce("all dependencies are present and up-to-date") + return + + # Alert the user to missing items + fmt = "\t%s\t%s\n" + items = [fmt % (dep.full_name(),dep.homepage) for dep in needed] + items.insert(0,"Please install the following packages first:\n") + items.append('') + raise SystemExit('\n'.join(items)) # dump msg to stderr and exit + + + def is_needed(self,dep): + """Does the specified dependency need to be installed/updated?""" + + self.announce("searching for "+dep.full_name()) + version = dep.get_version(self.search_path) + + if version is None: + self.announce(name+" not found!") + return True + + if str(version)=="unknown": + status = dep.name+" is installed" + else: + status = dep.name+" version "+str(version)+" found" + + if dep.version_ok(version): + self.announce(status+" (OK)") + return False + else: + self.announce(status+" (update needed)") + return True + + + diff --git a/setuptools/depends.py b/setuptools/depends.py index c3bc333..20e5cec 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -10,7 +10,9 @@ __all__ = [ class Require: """A prerequisite to building or installing a distribution""" - def __init__(self,name,requested_version,module,attribute=None,format=None): + def __init__(self,name,requested_version,module,homepage='', + attribute=None,format=None + ): if format is None and requested_version is not None: format = StrictVersion @@ -20,23 +22,21 @@ class Require: if attribute is None: attribute = '__version__' - self.name = name - self.requested_version = requested_version - self.module = module - self.attribute = attribute - self.format = format - - - - - - - - + self.__dict__.update(locals()) + del self.self + def full_name(self): + """Return full package/distribution name, w/version""" + if self.requested_version is not None: + return '%s-%s' % (self.name,self.requested_version) + return self.name + def version_ok(self,version): + """Is 'version' sufficiently up-to-date?""" + return self.attribute is None or self.format is None or \ + str(version)<>"unknown" and version >= self.requested_version def get_version(self, paths=None, default="unknown"): @@ -66,18 +66,18 @@ class Require: return v + def is_present(self,paths=None): """Return true if dependency is present on 'paths'""" return self.get_version(paths) is not None + def is_current(self,paths=None): """Return true if dependency is present and up-to-date on 'paths'""" version = self.get_version(paths) if version is None: return False - return self.attribute is None or self.format is None or \ - version >= self.requested_version - + return self.version_ok(version) def _iter_code(code): diff --git a/setuptools/dist.py b/setuptools/dist.py index 2941f26..cd3af26 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -1,7 +1,7 @@ __all__ = ['Distribution', 'Feature'] - from distutils.core import Distribution as _Distribution from distutils.core import Extension +from setuptools.depends import Require from setuptools.command.build_py import build_py from setuptools.command.build_ext import build_ext from setuptools.command.install import install @@ -11,7 +11,6 @@ from distutils.errors import DistutilsSetupError sequence = tuple, list class Distribution(_Distribution): - """Distribution with support for features, tests, and package data This is an enhanced version of 'distutils.dist.Distribution' that @@ -67,7 +66,6 @@ class Distribution(_Distribution): self.cmdclass.setdefault('build_ext',build_ext) self.cmdclass.setdefault('install',install) self.cmdclass.setdefault('install_lib',install_lib) - if self.features: self._set_global_opts_from_features() @@ -288,7 +286,6 @@ class Distribution(_Distribution): class Feature: - """A subset of the distribution that can be excluded if unneeded/wanted Features are created using these keyword arguments: @@ -312,6 +309,8 @@ class Feature: 'requires' -- a string or sequence of strings naming features that should also be included if this feature is included. Defaults to empty list. + May also contain 'Require' objects that should be added/removed from + the distribution. 'remove' -- a string or list of strings naming packages to be removed from the distribution if this feature is *not* included. If the @@ -345,15 +344,15 @@ class Feature: self.standard = standard self.available = available self.optional = optional - - if isinstance(requires,str): + if isinstance(requires,(str,Require)): requires = requires, - self.requires = requires + self.requires = [r for r in requires if isinstance(r,str)] + er = [r for r in requires if not isinstance(r,str)] + if er: extras['requires'] = er if isinstance(remove,str): remove = remove, - self.remove = remove self.extras = extras @@ -368,7 +367,6 @@ class Feature: """Should this feature be included by default?""" return self.available and self.standard - def include_in(self,dist): """Ensure feature and its requirements are included in distribution diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index 662d4ec..913c65b 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -10,9 +10,10 @@ from distutils.core import Extension from setuptools.depends import extract_constant, get_module_constant from setuptools.depends import find_module, Require from distutils.version import StrictVersion, LooseVersion - +from distutils.util import convert_path import sys, os.path + def makeSetup(**args): """Return distribution from 'setup(**args)', without executing commands""" @@ -38,7 +39,6 @@ def makeSetup(**args): - class DependsTests(TestCase): def testExtractConst(self): @@ -80,24 +80,21 @@ class DependsTests(TestCase): get_module_constant('setuptools.tests','__doc__'),__doc__ ) - def testDependsCmd(self): - dist = makeSetup() - cmd = dist.get_command_obj('depends') - cmd.ensure_finalized() - self.assertEqual(cmd.temp, dist.get_command_obj('build').build_temp) - self.assertEqual(cmd.install_lib, dist.get_command_obj('install').install_lib) - - def testRequire(self): + req = Require('Distutils','1.0.3','distutils') self.assertEqual(req.name, 'Distutils') self.assertEqual(req.module, 'distutils') self.assertEqual(req.requested_version, '1.0.3') self.assertEqual(req.attribute, '__version__') + self.assertEqual(req.full_name(), 'Distutils-1.0.3') from distutils import __version__ self.assertEqual(req.get_version(), __version__) + self.failUnless(req.version_ok('1.0.9')) + self.failIf(req.version_ok('0.9.1')) + self.failIf(req.version_ok('unknown')) self.failUnless(req.is_present()) self.failUnless(req.is_current()) @@ -105,19 +102,63 @@ class DependsTests(TestCase): req = Require('Distutils 3000','03000','distutils',format=LooseVersion) self.failUnless(req.is_present()) self.failIf(req.is_current()) + self.failIf(req.version_ok('unknown')) req = Require('Do-what-I-mean','1.0','d-w-i-m') self.failIf(req.is_present()) self.failIf(req.is_current()) - req = Require('Tests', None, 'tests') + req = Require('Tests', None, 'tests', homepage="http://example.com") self.assertEqual(req.format, None) self.assertEqual(req.attribute, None) self.assertEqual(req.requested_version, None) + self.assertEqual(req.full_name(), 'Tests') + self.assertEqual(req.homepage, 'http://example.com') paths = [os.path.dirname(p) for p in __path__] self.failUnless(req.is_present(paths)) self.failUnless(req.is_current(paths)) + + + + def testDependsCmd(self): + path1 = convert_path('foo/bar/baz') + path2 = convert_path('foo/bar/baz/spam') + + dist = makeSetup( + extra_path='spam', + script_args=['install','--install-lib',path1] + ) + + cmd = dist.get_command_obj('depends') + cmd.ensure_finalized() + + self.assertEqual(cmd.temp, dist.get_command_obj('build').build_temp) + self.assertEqual(cmd.search_path, [path2,path1]+sys.path) + + + + + + + + + + + + + + + + + + + + + + + + @@ -247,9 +288,10 @@ class DistroTests(TestCase): class FeatureTests(TestCase): def setUp(self): + self.req = Require('Distutils','1.0.3','distutils') self.dist = makeSetup( features={ - 'foo': Feature("foo",standard=True,requires='baz'), + 'foo': Feature("foo",standard=True,requires=['baz',self.req]), 'bar': Feature("bar", standard=True, packages=['pkg.bar'], py_modules=['bar_et'], remove=['bar.ext'], ), @@ -284,7 +326,6 @@ class FeatureTests(TestCase): self.dist.features['dwim'].include_in, self.dist ) - def testFeatureOptions(self): dist = self.dist self.failUnless( @@ -309,13 +350,13 @@ class FeatureTests(TestCase): self.assertEqual(dist.with_foo,1) self.assertEqual(dist.with_bar,0) self.assertEqual(dist.with_baz,1) - self.failIf('bar_et' in dist.py_modules) self.failIf('pkg.bar' in dist.packages) self.failUnless('pkg.baz' in dist.packages) self.failUnless('scripts/baz_it' in dist.scripts) self.failUnless(('libfoo','foo/foofoo.c') in dist.libraries) self.assertEqual(dist.ext_modules,[]) + self.assertEqual(dist.requires, [self.req]) # If we ask for bar, it should fail because we explicitly disabled # it on the command line -- cgit v1.2.1