Commit e5eac13d authored by PJ Eby's avatar PJ Eby

Added ``develop`` command to ``setuptools``-based packages. This command

installs an ``.egg-link`` pointing to the package's source directory, and
script wrappers that ``execfile()`` the source versions of the package's
scripts.  This lets you put your development checkout(s) on sys.path
without having to actually install them.  (To uninstall the link, use
use ``setup.py develop --uninstall``.)

--HG--
branch : setuptools
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041080
parent 8468e915
......@@ -482,10 +482,17 @@ Known Issues
time out or be missing a file.
0.5a5
* Added ``develop`` command to ``setuptools``-based packages. This command
installs an ``.egg-link`` pointing to the package's source directory, and
script wrappers that ``execfile()`` the source versions of the package's
scripts. This lets you put your development checkout(s) on sys.path without
having to actually install them. (To uninstall the link, use
use ``setup.py develop --uninstall``.)
* Added ``egg_info`` command to ``setuptools``-based packages. This command
just creates or updates the "projectname.egg-info" directory, without
building an egg. It's used by the ``bdist_egg`` and ``test`` commands now,
and will be used by the ``develop`` command later on.
building an egg. (It's used by the ``bdist_egg``, ``test``, and ``develop``
commands.)
* Enhanced the ``test`` command so that it doesn't install the package, but
instead builds any C extensions in-place, updates the ``.egg-info``
......
......@@ -984,10 +984,8 @@ def StringIO(*args, **kw):
def find_nothing(importer,path_item):
return ()
register_finder(object,find_nothing)
def find_on_path(importer,path_item):
"""Yield distributions accessible on a sys.path directory"""
if not os.path.exists(path_item):
......@@ -1004,25 +1002,27 @@ def find_on_path(importer,path_item):
# scan for .egg and .egg-info in directory
for entry in os.listdir(path_item):
fullpath = os.path.join(path_item, entry)
if entry.lower().endswith('.egg'):
lower = entry.lower()
if lower.endswith('.egg'):
for dist in find_on_path(importer,fullpath):
yield dist
elif entry.lower().endswith('.egg-info'):
elif lower.endswith('.egg-info'):
if os.path.isdir(fullpath):
# development egg
metadata = PathMetadata(path_item, fullpath)
dist_name = os.path.splitext(entry)[0]
yield Distribution(path_item,metadata,name=dist_name)
elif path_item.lower().endswith('.egg'):
# packed egg
elif lower.endswith('.egg-link'):
for line in file(fullpath):
if not line.strip(): continue
for item in find_distributions(line.rstrip()):
yield item
elif path_item.lower().endswith('.egg'): # packed egg
metadata = EggMetadata(zipimport.zipimporter(path_item))
yield Distribution.from_filename(path_item, metadata=metadata)
register_finder(ImpWrapper,find_on_path)
_namespace_handlers = {}
_namespace_packages = {}
......
import distutils.command
__all__ = ['test', 'depends', 'bdist_egg']
__all__ = ['test', 'develop', 'bdist_egg']
# Make our commands available as though they were part of the distutils
......
from setuptools.command.easy_install import easy_install
from distutils.util import convert_path
from pkg_resources import Distribution, PathMetadata
from distutils import log
import sys, os
class develop(easy_install):
"""Set up package for development"""
description = "install package in 'development mode'"
user_options = [
("install-dir=", "d", "link package from DIR"),
("script-dir=", "s", "create script wrappers in DIR"),
("multi-version", "m", "make apps have to require() a version"),
("exclude-scripts", "x", "Don't install scripts"),
("always-copy", "a", "Copy all needed dependencies to install dir"),
("uninstall", "u", "Uninstall this source package"),
]
boolean_options = [
'multi-version', 'exclude-scripts', 'always-copy', 'uninstall'
]
command_consumes_arguments = False # override base
def initialize_options(self):
self.uninstall = None
easy_install.initialize_options(self)
def finalize_options(self):
ei = self.get_finalized_command("egg_info")
self.args = [ei.egg_name]
easy_install.finalize_options(self)
self.egg_link = os.path.join(self.install_dir, ei.egg_name+'.egg-link')
self.egg_base = ei.egg_base
self.egg_path = os.path.abspath(ei.egg_base)
# Make a distribution for the package's source
self.dist = Distribution(
self.egg_path,
PathMetadata(self.egg_path, os.path.abspath(ei.egg_info)),
name = ei.egg_name
)
def run(self):
if self.uninstall:
self.multi_version = True
self.uninstall_link()
else:
self.install_for_development()
def install_for_development(self):
# Ensure metadata is up-to-date
self.run_command('egg_info')
ei = self.get_finalized_command("egg_info")
# Build extensions in-place
self.reinitialize_command('build_ext', inplace=1)
self.run_command('build_ext')
# create an .egg-link in the installation dir, pointing to our egg
log.info("Creating %s (link to %s)", self.egg_link, self.egg_base)
if not self.dry_run:
f = open(self.egg_link,"w")
f.write(self.egg_path)
f.close()
# postprocess the installed distro, fixing up .pth, installing scripts,
# and handling requirements
self.process_distribution(None, self.dist)
def uninstall_link(self):
if os.path.exists(self.egg_link):
log.info("Removing %s (link to %s)", self.egg_link, self.egg_base)
contents = [line.rstrip() for line in file(self.egg_link)]
if contents != [self.egg_path]:
log.warn("Link points to %s: uninstall aborted", contents)
return
if not self.dry_run:
os.unlink(self.egg_link)
self.update_pth(self.dist) # remove any .pth link to us
if self.distribution.scripts:
log.warn("Note: you must uninstall or replace scripts manually!")
def install_egg_scripts(self, dist):
if dist is not self.dist:
# Installing a dependency, so fall back to normal behavior
return easy_install.install_egg_scripts(self,dist)
# create wrapper scripts in the script dir, pointing to dist.scripts
for script_name in self.distribution.scripts or []:
script_path = os.path.abspath(convert_path(script_name))
script_name = os.path.basename(script_path)
f = open(script_path,'rU')
script_text = f.read()
f.close()
self.install_script(dist, script_name, script_text, script_path)
......@@ -126,7 +126,7 @@ class easy_install(Command):
for path_item in self.install_dir, self.script_dir:
if path_item not in self.shadow_path:
self.shadow_path.insert(0, self.install_dir)
if self.package_index is None:
self.package_index = self.create_index(
self.index_url, search_path = self.shadow_path
......@@ -207,12 +207,12 @@ class easy_install(Command):
tmpdir = self.alloc_tmp()
download = None
try:
try:
if not isinstance(spec,Requirement):
if URL_SCHEME(spec):
# It's a url, download it to tmpdir and process
download = self.package_index.download(spec, tmpdir)
return self.install_item(None, download, tmpdir, True)
return self.install_item(None, download, tmpdir, True)
elif os.path.exists(spec):
# Existing file or directory, just process it directly
......@@ -290,40 +290,81 @@ class easy_install(Command):
if self.exclude_scripts or not metadata.metadata_isdir('scripts'):
return
for script_name in metadata.metadata_listdir('scripts'):
self.install_script(
dist, script_name,
metadata.get_metadata('scripts/'+script_name).replace('\r','\n')
)
def install_script(self, dist, script_name, script_text, dev_path=None):
log.info("Installing %s script to %s", script_name,self.script_dir)
target = os.path.join(self.script_dir, script_name)
first, rest = script_text.split('\n',1)
from distutils.command.build_scripts import first_line_re
match = first_line_re.match(first)
options = ''
if match:
options = match.group(1) or ''
if options:
options = ' '+options
spec = '%s==%s' % (dist.name,dist.version)
executable = os.path.normpath(sys.executable)
if dev_path:
script_text = (
"#!%(executable)s%(options)s\n"
"# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r\n"
"from pkg_resources import require; require(%(spec)r)\n"
"del require\n"
"__file__ = %(dev_path)r\n"
"execfile(__file__)\n"
) % locals()
else:
script_text = (
"#!%(executable)s%(options)s\n"
"# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r\n"
"import pkg_resources\n"
"pkg_resources.run_main(%(spec)r, %(script_name)r)\n"
) % locals()
for script_name in metadata.metadata_listdir('scripts'):
target = os.path.join(self.script_dir, script_name)
log.info("Installing %s script to %s", script_name,self.script_dir)
script_text = metadata.get_metadata('scripts/'+script_name)
script_text = script_text.replace('\r','\n')
first, rest = script_text.split('\n',1)
match = first_line_re.match(first)
options = ''
if match:
options = match.group(1) or ''
if options:
options = ' '+options
spec = '%s==%s' % (dist.name,dist.version)
script_text = '\n'.join([
"#!%s%s" % (os.path.normpath(sys.executable),options),
"# EASY-INSTALL-SCRIPT: %r,%r" % (spec, script_name),
"import pkg_resources",
"pkg_resources.run_main(%r, %r)" % (spec, script_name)
])
if not self.dry_run:
f = open(target,"w")
f.write(script_text)
f.close()
try:
os.chmod(target,0755)
except (AttributeError, os.error):
pass
if not self.dry_run:
f = open(target,"w")
f.write(script_text)
f.close()
try:
os.chmod(target,0755)
except (AttributeError, os.error):
pass
def install_eggs(self, dist_filename, zip_ok, tmpdir):
......@@ -373,7 +414,7 @@ class easy_install(Command):
else:
metadata = EggMetadata(zipimport.zipimporter(egg_path))
return Distribution.from_filename(egg_path,metadata=metadata)
def install_egg(self, egg_path, zip_ok, tmpdir):
destination = os.path.join(self.install_dir,os.path.basename(egg_path))
destination = os.path.abspath(destination)
......@@ -443,7 +484,7 @@ class easy_install(Command):
verbose=self.verbose, dry_run=self.dry_run
)
# install the .egg
# install the .egg
return self.install_egg(egg_path, self.zip_ok, tmpdir)
......@@ -474,7 +515,7 @@ class easy_install(Command):
# extract, tracking .pyd/.dll->native_libs and .py -> to_compile
unpack_archive(dist_filename, egg_tmp, process)
for res in native_libs:
if res.lower().endswith('.pyd'): # create stubs for .pyd's
parts = res.split('/')
......@@ -482,9 +523,9 @@ class easy_install(Command):
pyfile = os.path.join(egg_tmp, *parts)
to_compile.append(pyfile)
bdist_egg.write_stub(resource, pyfile)
self.byte_compile(to_compile) # compile .py's
if native_libs: # write EGG-INFO/native_libs.txt
nl_txt = os.path.join(egg_tmp, 'EGG-INFO', 'native_libs.txt')
ensure_directory(nl_txt)
......@@ -599,6 +640,7 @@ PYTHONPATH, or by being added to sys.path by your code.)
if dist.name=='setuptools':
# Ensure that setuptools itself never becomes unavailable!
# XXX should this check for latest version?
f = open(os.path.join(self.install_dir,'setuptools.pth'), 'w')
f.write(dist.path+'\n')
f.close()
......@@ -612,7 +654,6 @@ PYTHONPATH, or by being added to sys.path by your code.)
def unpack_and_compile(self, egg_path, destination):
to_compile = []
......@@ -632,7 +673,7 @@ PYTHONPATH, or by being added to sys.path by your code.)
# try to make the byte compile messages quieter
log.set_verbosity(self.verbose - 1)
byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run)
byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run)
if self.optimize:
byte_compile(
to_compile, optimize=self.optimize, force=1,
......@@ -667,7 +708,7 @@ def extract_wininst_cfg(dist_filename):
prepended = (endrec[9] - endrec[5]) - endrec[6]
if prepended < 12: # no wininst data here
return None
return None
f.seek(prepended-12)
import struct, StringIO, ConfigParser
......@@ -683,7 +724,7 @@ def extract_wininst_cfg(dist_filename):
return None
if not cfg.has_section('metadata') or not cfg.has_section('Setup'):
return None
return cfg
return cfg
finally:
f.close()
......
......@@ -4,9 +4,9 @@ import sys
class test(Command):
"""Command to run unit tests after installation"""
"""Command to run unit tests after in-place build"""
description = "run unit tests after installation"
description = "run unit tests after in-place build"
user_options = [
('test-module=','m', "Run 'test_suite' in specified module"),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment