Commit ae347c1e authored by PJ Eby's avatar PJ Eby

Add support for "eggsecutable" headers: a /bin/sh script that is prepended

to an .egg file to allow it to be run as a script on Unix-ish platforms.
(This is mainly so that setuptools itself can have a single-file installer
on Unix, without doing multiple downloads, dealing with firewalls, etc.)
(Backport from trunk)

--HG--
branch : setuptools-0.6
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/branches/setuptools-0.6%4051969
parent 4ef57c26
...@@ -84,10 +84,13 @@ setup( ...@@ -84,10 +84,13 @@ setup(
"easy_install = setuptools.command.easy_install:main", "easy_install = setuptools.command.easy_install:main",
"easy_install-%s = setuptools.command.easy_install:main" "easy_install-%s = setuptools.command.easy_install:main"
% sys.version[:3] % sys.version[:3]
], ],
"setuptools.file_finders": "setuptools.file_finders":
["svn_cvs = setuptools.command.sdist:_default_revctrl"] ["svn_cvs = setuptools.command.sdist:_default_revctrl"],
"setuptools.installation":
['eggsecutable = setuptools.command.easy_install:bootstrap'],
}, },
classifiers = [f.strip() for f in """ classifiers = [f.strip() for f in """
...@@ -118,6 +121,3 @@ setup( ...@@ -118,6 +121,3 @@ setup(
...@@ -30,6 +30,9 @@ depends.txt = setuptools.command.egg_info:warn_depends_obsolete ...@@ -30,6 +30,9 @@ depends.txt = setuptools.command.egg_info:warn_depends_obsolete
easy_install = setuptools.command.easy_install:main easy_install = setuptools.command.easy_install:main
easy_install-2.4 = setuptools.command.easy_install:main easy_install-2.4 = setuptools.command.easy_install:main
[setuptools.installation]
eggsecutable = setuptools.command.easy_install:bootstrap
[distutils.commands] [distutils.commands]
bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm
rotate = setuptools.command.rotate:rotate rotate = setuptools.command.rotate:rotate
...@@ -44,10 +47,10 @@ install_egg_info = setuptools.command.install_egg_info:install_egg_info ...@@ -44,10 +47,10 @@ install_egg_info = setuptools.command.install_egg_info:install_egg_info
alias = setuptools.command.alias:alias alias = setuptools.command.alias:alias
easy_install = setuptools.command.easy_install:easy_install easy_install = setuptools.command.easy_install:easy_install
install_scripts = setuptools.command.install_scripts:install_scripts install_scripts = setuptools.command.install_scripts:install_scripts
bdist_wininst = setuptools.command.bdist_wininst:bdist_wininst
bdist_egg = setuptools.command.bdist_egg:bdist_egg bdist_egg = setuptools.command.bdist_egg:bdist_egg
install = setuptools.command.install:install install = setuptools.command.install:install
test = setuptools.command.test:test test = setuptools.command.test:test
install_lib = setuptools.command.install_lib:install_lib install_lib = setuptools.command.install_lib:install_lib
build_ext = setuptools.command.build_ext:build_ext build_ext = setuptools.command.build_ext:build_ext
sdist = setuptools.command.sdist:sdist sdist = setuptools.command.sdist:sdist
...@@ -498,6 +498,43 @@ on "entry points" in general, see the section below on `Dynamic Discovery of ...@@ -498,6 +498,43 @@ on "entry points" in general, see the section below on `Dynamic Discovery of
Services and Plugins`_. Services and Plugins`_.
"Eggsecutable" Scripts
----------------------
Occasionally, there are situations where it's desirable to make an ``.egg``
file directly executable. You can do this by including an entry point such
as the following::
setup(
# other arguments here...
entry_points = {
'setuptools.installation': [
'eggsecutable = my_package.some_module:main_func',
]
}
)
Any eggs built from the above setup script will include a short excecutable
prelude that imports and calls ``main_func()`` from ``my_package.some_module``.
The prelude can be run on Unix-like platforms (including Mac and Linux) by
invoking the egg with ``/bin/sh``, or by enabling execute permissions on the
``.egg`` file. For the executable prelude to run, the appropriate version of
Python must be available via the ``PATH`` environment variable, under its
"long" name. That is, if the egg is built for Python 2.3, there must be a
``python2.3`` executable present in a directory on ``PATH``.
This feature is primarily intended to support bootstrapping the installation of
setuptools itself on non-Windows platforms, but may also be useful for other
projects as well.
IMPORTANT NOTE: Eggs with an "eggsecutable" header cannot be renamed, or
invoked via symlinks. They *must* be invoked using their original filename, in
order to ensure that, once running, ``pkg_resources`` will know what project
and version is in use. The header script will check this and exit with an
error if the ``.egg`` file has been renamed or is invoked via a symlink that
changes its base name.
Declaring Dependencies Declaring Dependencies
====================== ======================
...@@ -2567,6 +2604,12 @@ Release Notes/Change History ...@@ -2567,6 +2604,12 @@ Release Notes/Change History
* Fix ``upload`` command not uploading files built by ``bdist_rpm`` or * Fix ``upload`` command not uploading files built by ``bdist_rpm`` or
``bdist_wininst`` under Python 2.3 and 2.4. ``bdist_wininst`` under Python 2.3 and 2.4.
* Add support for "eggsecutable" headers: a ``#!/bin/sh`` script that is
prepended to an ``.egg`` file to allow it to be run as a script on Unix-ish
platforms. (This is mainly so that setuptools itself can have a single-file
installer on Unix, without doing multiple downloads, dealing with firewalls,
etc.)
0.6c3 0.6c3
* Fixed breakages caused by Subversion 1.4's new "working copy" format * Fixed breakages caused by Subversion 1.4's new "working copy" format
......
...@@ -8,7 +8,9 @@ from setuptools import Command ...@@ -8,7 +8,9 @@ from setuptools import Command
from distutils.dir_util import remove_tree, mkpath from distutils.dir_util import remove_tree, mkpath
from distutils.sysconfig import get_python_version, get_python_lib from distutils.sysconfig import get_python_version, get_python_lib
from distutils import log from distutils import log
from distutils.errors import DistutilsSetupError
from pkg_resources import get_build_platform, Distribution, ensure_directory from pkg_resources import get_build_platform, Distribution, ensure_directory
from pkg_resources import EntryPoint
from types import CodeType from types import CodeType
from setuptools.extension import Library from setuptools.extension import Library
...@@ -37,8 +39,6 @@ def write_stub(resource, pyfile): ...@@ -37,8 +39,6 @@ def write_stub(resource, pyfile):
# stub __init__.py for packages distributed without one # stub __init__.py for packages distributed without one
NS_PKG_STUB = '__import__("pkg_resources").declare_namespace(__name__)' NS_PKG_STUB = '__import__("pkg_resources").declare_namespace(__name__)'
class bdist_egg(Command): class bdist_egg(Command):
description = "create an \"egg\" distribution" description = "create an \"egg\" distribution"
...@@ -233,7 +233,7 @@ class bdist_egg(Command): ...@@ -233,7 +233,7 @@ class bdist_egg(Command):
# Make the archive # Make the archive
make_zipfile(self.egg_output, archive_root, verbose=self.verbose, make_zipfile(self.egg_output, archive_root, verbose=self.verbose,
dry_run=self.dry_run) dry_run=self.dry_run, mode=self.gen_header())
if not self.keep_temp: if not self.keep_temp:
remove_tree(self.bdist_dir, dry_run=self.dry_run) remove_tree(self.bdist_dir, dry_run=self.dry_run)
...@@ -285,6 +285,47 @@ class bdist_egg(Command): ...@@ -285,6 +285,47 @@ class bdist_egg(Command):
return init_files return init_files
def gen_header(self):
epm = EntryPoint.parse_map(self.distribution.entry_points or '')
ep = epm.get('setuptools.installation',{}).get('eggsecutable')
if ep is None:
return 'w' # not an eggsecutable, do it the usual way.
if not ep.attrs or ep.extras:
raise DistutilsSetupError(
"eggsecutable entry point (%r) cannot have 'extras' "
"or refer to a module" % (ep,)
)
pyver = sys.version[:3]
pkg = ep.module_name
full = '.'.join(ep.attrs)
base = ep.attrs[0]
basename = os.path.basename(self.egg_output)
header = (
"#!/bin/sh\n"
'if [[ `basename $0` = "%(basename)s" ]]\n'
'then exec python%(pyver)s -c "'
"import sys, os; sys.path.insert(0, os.path.abspath('$0')); "
"from %(pkg)s import %(base)s; sys.exit(%(full)s())"
'" "$@"\n'
'else\n'
' echo $0 is not the correct name for this egg file.\n'
' echo Please rename it back to %(basename)s and try again.\n'
' exec false\n'
'fi\n'
) % locals()
if not self.dry_run:
f = open(self.egg_output, 'w')
f.write(header)
f.close()
return 'a'
def copy_metadata_to(self, target_dir): def copy_metadata_to(self, target_dir):
prefix = os.path.join(self.egg_info,'') prefix = os.path.join(self.egg_info,'')
for path in self.ei_cmd.filelist.files: for path in self.ei_cmd.filelist.files:
...@@ -415,7 +456,9 @@ INSTALL_DIRECTORY_ATTRS = [ ...@@ -415,7 +456,9 @@ INSTALL_DIRECTORY_ATTRS = [
'install_lib', 'install_dir', 'install_data', 'install_base' 'install_lib', 'install_dir', 'install_data', 'install_base'
] ]
def make_zipfile (zip_filename, base_dir, verbose=0, dry_run=0, compress=None): def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None,
mode='w'
):
"""Create a zip file from all the files under 'base_dir'. The output """Create a zip file from all the files under 'base_dir'. The output
zip file will be named 'base_dir' + ".zip". Uses either the "zipfile" zip file will be named 'base_dir' + ".zip". Uses either the "zipfile"
Python module (if available) or the InfoZIP "zip" utility (if installed Python module (if available) or the InfoZIP "zip" utility (if installed
...@@ -426,7 +469,7 @@ def make_zipfile (zip_filename, base_dir, verbose=0, dry_run=0, compress=None): ...@@ -426,7 +469,7 @@ def make_zipfile (zip_filename, base_dir, verbose=0, dry_run=0, compress=None):
mkpath(os.path.dirname(zip_filename), dry_run=dry_run) mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir) log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)
def visit (z, dirname, names): def visit(z, dirname, names):
for name in names: for name in names:
path = os.path.normpath(os.path.join(dirname, name)) path = os.path.normpath(os.path.join(dirname, name))
if os.path.isfile(path): if os.path.isfile(path):
...@@ -440,12 +483,10 @@ def make_zipfile (zip_filename, base_dir, verbose=0, dry_run=0, compress=None): ...@@ -440,12 +483,10 @@ def make_zipfile (zip_filename, base_dir, verbose=0, dry_run=0, compress=None):
compression = [zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED][bool(compress)] compression = [zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED][bool(compress)]
if not dry_run: if not dry_run:
z = zipfile.ZipFile(zip_filename, "w", compression=compression) z = zipfile.ZipFile(zip_filename, mode, compression=compression)
os.path.walk(base_dir, visit, z) os.path.walk(base_dir, visit, z)
z.close() z.close()
else: else:
os.path.walk(base_dir, visit, None) os.path.walk(base_dir, visit, None)
return zip_filename return zip_filename
# #
...@@ -1550,10 +1550,10 @@ def rmtree(path, ignore_errors=False, onerror=auto_chmod): ...@@ -1550,10 +1550,10 @@ def rmtree(path, ignore_errors=False, onerror=auto_chmod):
except os.error: except os.error:
onerror(os.rmdir, path, sys.exc_info()) onerror(os.rmdir, path, sys.exc_info())
def bootstrap():
# This function is called when setuptools*.egg is run using /bin/sh
import setuptools; argv0 = os.path.dirname(setuptools.__path__[0])
sys.argv[0] = argv0; sys.argv.append(argv0); main()
def main(argv=None, **kw): def main(argv=None, **kw):
......
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