Hi all,

I've been on vacation for a bit in general, and on vacation from this
mailing list even longer.  I'm not entirely caught up yet on the
latest developments so apologies if something like this is entirely
moot by now.

But I have seen some discussions here and in other lists related to
using pip for all installations, and phasing out the old distutils
`./setup.py install` (eg. [1]).  This is not a new discussion, and
there are many related discussions, for example, about changing
setuptools not to default to egg installs anymore (see [2]).

I'm definitely all for this in general.  Nowadays whenever I install a
package from source I run `pip install .`  But of course there are a
lot of existing tools, not to mention folk wisdom, assuming
`./setup.py install`.  We also don't want to change the long-existing
behavior in setuptools.

I have a modest proposal for a small addition to setuptools that might
be helpful in a transition away from using setuptools+distutils for
installation.  This would be to add a `--pip` flag to setuptools'
install command (or possibly straight in distutils too, but might as
well start with setuptools).

Therefore, running

$ ./setup.py install --pip

would be equivalent to running

$ pip install .

By extension, running

$ ./setup.py install --pip --arg1 --arg2=foo

would be equivalent to

$ pip install --install-option="--arg1" --install-option="--arg2=foo" .

and so on.

By making `--pip` opt-in, it does not automatically break backward
compatibility for users expecting `./setup.py install` to use
easy_install.  However, individual users may opt into it globally by
adding

[install]
pip = True

to their .pydistutils.cfg.  Similarly, package authors who are
confident that none of their users are ever going to care about egg
installs (e.g. me) can add the same to their project's setup.cfg.

Does something like this have any merit?  I hacked together a
prototype which follows the sig line.  It's just a proof of concept,
but seems to work in the most basic cases.  I'd like to add it to my
own projects too, but would appreciate some peer review.

Thanks,
Erik



[1] https://mail.scipy.org/pipermail/numpy-discussion/2015-November/074142.html
[2] 
https://bitbucket.org/pypa/setuptools/issues/371/setuptools-and-state-of-pep-376


$ cat pipinstall.py
from distutils.errors import DistutilsArgError
from setuptools.command.install import install as SetuptoolsInstall


class PipInstall(SetuptoolsInstall):
    command_name = 'install'
    user_options = SetuptoolsInstall.user_options + [
        ('pip', None, 'install using pip; ignored when also using '
                      '--single-version-externally-managed')
    ]
    boolean_options = SetuptoolsInstall.boolean_options + ['pip']

    def initialize_options(self):
        SetuptoolsInstall.initialize_options(self)
        self.pip = False

    def finalize_options(self):
        SetuptoolsInstall.finalize_options(self)

        if self.single_version_externally_managed:
            self.pip = False

        if self.pip:
            try:
                import pip
            except ImportError:
                raise DistutilsArgError(
                    'pip must be installed in order to install with the '
                    '--pip option')

    def run(self):
        if self.pip:
            import pip
            opts = (['install', '--ignore-installed'] +
                    ['--install-option="{0}"'.format(opt)
                     for opt in self._get_command_line_opts()])
            pip.main(opts + ['.'])
        else:
            SetuptoolsInstall.run(self)

    def _get_command_line_opts(self):
        # Generate a mapping from the attribute name associated with a
        # command-line option to the name of the command-line option (including
        # an = if the option takes an argument)
        attr_to_opt = dict((opt[0].rstrip('=').replace('-', '_'), opt[0])
                           for opt in self.user_options)

        opt_dict = self.distribution.get_option_dict(self.get_command_name())
        opts = []

        for attr, value in opt_dict.items():
            if value[0] != 'command line' or attr == 'pip':
                # Only look at options passed in on the command line (ignoring
                # the pip option itself)
                continue

            opt = attr_to_opt[attr]

            if opt in self.boolean_options:
                opts.append('--' + opt)
            else:
                opts.append('--{0}{1}'.format(opt, value[1]))

        return opts

    @staticmethod
    def _called_from_setup(run_frame):
        # A hack to work around a setuptools hack
        return SetuptoolsInstall._called_from_setup(run_frame.f_back)
_______________________________________________
Distutils-SIG maillist  -  Distutils-SIG@python.org
https://mail.python.org/mailman/listinfo/distutils-sig

Reply via email to