distutils:如何将用户定义的参数传递给 setup.py?

发布于 2024-07-15 20:30:34 字数 171 浏览 8 评论 0 原文

如何将用户定义的参数从命令行和 setup.cfg 配置文件传递到 distutils 的 setup.py 脚本?

我想编写一个 setup.py 脚本,它接受我的包特定参数。 例如:

python setup.py install -foo myfoo

How can I pass a user-defined parameter both from the command line and setup.cfg configuration file to distutils' setup.py script?

I want to write a setup.py script, which accepts my package specific parameters. For example:

python setup.py install -foo myfoo

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

末骤雨初歇 2024-07-22 20:30:34

由于 Setuptools/Distuils 的文档非常多,我自己找不到答案。 但最终我偶然发现了这个示例。 另外,这个类似的问题很有帮助。 基本上,带有选项的自定义命令如下所示:

from distutils.core import setup, Command

class InstallCommand(Command):
    description = "Installs the foo."
    user_options = [
        ('foo=', None, 'Specify the foo to bar.'),
    ]
    def initialize_options(self):
        self.foo = None
    def finalize_options(self):
        assert self.foo in (None, 'myFoo', 'myFoo2'), 'Invalid foo!'
    def run(self):
        install_all_the_things()

setup(
    ...,
    cmdclass={
        'install': InstallCommand,
    }
)

As Setuptools/Distuils are horribly documented, I had problems finding the answer to this myself. But eventually I stumbled across this example. Also, this similar question was helpful. Basically, a custom command with an option would look like:

from distutils.core import setup, Command

class InstallCommand(Command):
    description = "Installs the foo."
    user_options = [
        ('foo=', None, 'Specify the foo to bar.'),
    ]
    def initialize_options(self):
        self.foo = None
    def finalize_options(self):
        assert self.foo in (None, 'myFoo', 'myFoo2'), 'Invalid foo!'
    def run(self):
        install_all_the_things()

setup(
    ...,
    cmdclass={
        'install': InstallCommand,
    }
)
我一直都在从未离去 2024-07-22 20:30:34

这是一个非常简单的解决方案,您所要做的就是在调用 distutils setup(..) 之前过滤掉 sys.argv 并自行处理它。
像这样的事情:

if "--foo" in sys.argv:
    do_foo_stuff()
    sys.argv.remove("--foo")
...
setup(..)

关于如何使用 distutils 执行此操作的文档非常糟糕,最终我遇到了这个:搭便车指南打包,它使用 sdist 及其 user_options
我发现 扩展 distutils 参考并不是特别有用。

尽管这看起来像是使用 distutils 执行此操作的“正确”方法(至少是我能找到的唯一一种模糊记录的方法)。 我在其他答案中提到的 --with--without 开关上找不到任何内容。

这个 distutils 解决方案的问题是它对于我正在寻找的东西来说太复杂了(对于你来说也可能是这种情况)。
添加几十行并子类化 sdist 对我来说是错误的。

Here is a very simple solution, all you have to do is filter out sys.argv and handle it yourself before you call to distutils setup(..).
Something like this:

if "--foo" in sys.argv:
    do_foo_stuff()
    sys.argv.remove("--foo")
...
setup(..)

The documentation on how to do this with distutils is terrible, eventually I came across this one: the hitchhikers guide to packaging, which uses sdist and its user_options.
I find the extending distutils reference not particularly helpful.

Although this looks like the "proper" way of doing it with distutils (at least the only one that I could find that is vaguely documented). I could not find anything on --with and --without switches mentioned in the other answer.

The problem with this distutils solution is that it is just way too involved for what I am looking for (which may also be the case for you).
Adding dozens of lines and subclassing sdist is just wrong for me.

一瞬间的火花 2024-07-22 20:30:34

是的,现在已经是 2015 年了,在 setuptoolsdistutils 中添加命令和选项的文档仍然很大程度上缺失。

经过几个令人沮丧的小时后,我找到了以下代码,用于向 setup.pyinstall 命令添加自定义选项:

from setuptools.command.install import install


class InstallCommand(install):
    user_options = install.user_options + [
        ('custom_option=', None, 'Path to something')
    ]

    def initialize_options(self):
        install.initialize_options(self)
        self.custom_option = None

    def finalize_options(self):
        #print('The custom option for install is ', self.custom_option)
        install.finalize_options(self)

    def run(self):
        global my_custom_option
        my_custom_option = self.custom_option
        install.run(self)  # OR: install.do_egg_install(self)

值得一提的是 install.run() 检查如果它被称为“本机”或已修补:

if not self._called_from_setup(inspect.currentframe()):
    orig.install.run(self)
else:
    self.do_egg_install()

此时,您可以使用 setup 注册您的命令:

setup(
    cmdclass={
        'install': InstallCommand,
    },
    :

Yes, it's 2015 and the documentation for adding commands and options in both setuptools and distutils is still largely missing.

After a few frustrating hours I figured out the following code for adding a custom option to the install command of setup.py:

from setuptools.command.install import install


class InstallCommand(install):
    user_options = install.user_options + [
        ('custom_option=', None, 'Path to something')
    ]

    def initialize_options(self):
        install.initialize_options(self)
        self.custom_option = None

    def finalize_options(self):
        #print('The custom option for install is ', self.custom_option)
        install.finalize_options(self)

    def run(self):
        global my_custom_option
        my_custom_option = self.custom_option
        install.run(self)  # OR: install.do_egg_install(self)

It's worth to mention that install.run() checks if it's called "natively" or had been patched:

if not self._called_from_setup(inspect.currentframe()):
    orig.install.run(self)
else:
    self.do_egg_install()

At this point you register your command with setup:

setup(
    cmdclass={
        'install': InstallCommand,
    },
    :
最后的乘客 2024-07-22 20:30:34

您无法真正将自定义参数传递给脚本。 但是,以下情况是可能的,并且可以解决您的问题:

  • 可以使用 --with-featurename 启用可选功能,可以使用 --without-featurename 禁用标准功能。 [AFAIR 这需要 setuptools]
  • 你可以使用环境变量,但是这些需要在 Windows 上设置,而前缀它们可以在 linux/ OS X 上使用(FOO=bar python setup.py >)。
  • 您可以使用自己的 cmd_class 扩展 distutils 来实现新功能。 它们也是可链接的,因此您可以使用它来更改脚本中的变量。 (python setup.py foo install) 将在执行 install 之前执行 foo 命令。

希望能有所帮助。 一般来说,我建议提供更多信息,您的额外参数到底应该做什么,也许有更好的解决方案可用。

You can't really pass custom parameters to the script. However the following things are possible and could solve your problem:

  • optional features can be enabled using --with-featurename, standard features can be disabled using --without-featurename. [AFAIR this requires setuptools]
  • you can use environment variables, these however require to be set on windows whereas prefixing them works on linux/ OS X (FOO=bar python setup.py).
  • you can extend distutils with your own cmd_classes which can implement new features. They are also chainable, so you can use that to change variables in your script. (python setup.py foo install) will execute the foo command before it executes install.

Hope that helps somehow. Generally speaking I would suggest providing a bit more information what exactly your extra parameter should do, maybe there is a better solution available.

紫﹏色ふ单纯 2024-07-22 20:30:34

我成功地使用了一种解决方法来使用类似于 totaam 的建议的解决方案。 我最终从 sys.argv 列表中弹出了额外的参数:

import sys
from distutils.core import setup
foo = 0
if '--foo' in sys.argv:
    index = sys.argv.index('--foo')
    sys.argv.pop(index)  # Removes the '--foo'
    foo = sys.argv.pop(index)  # Returns the element after the '--foo'
# The foo is now ready to use for the setup
setup(...)

可以添加一些额外的验证以确保输入良好,但这就是我的做法

I successfully used a workaround to use a solution similar to totaam's suggestion. I ended up popping my extra arguments from the sys.argv list:

import sys
from distutils.core import setup
foo = 0
if '--foo' in sys.argv:
    index = sys.argv.index('--foo')
    sys.argv.pop(index)  # Removes the '--foo'
    foo = sys.argv.pop(index)  # Returns the element after the '--foo'
# The foo is now ready to use for the setup
setup(...)

Some extra validation could be added to ensure the inputs are good, but this is how I did it

怼怹恏 2024-07-22 20:30:34

与 totaam 给出的类似的快速简单的方法是使用 argparse 来获取 -foo 参数,并将其余参数留给 distutils.setup() 的调用。 恕我直言,为此使用 argparse 比手动迭代 sys.argv 更好。 例如,在 setup.py 的开头添加以下内容:

argparser = argparse.ArgumentParser(add_help=False)
argparser.add_argument('--foo', help='required foo argument', required=True)
args, unknown = argparser.parse_known_args()
sys.argv = [sys.argv[0]] + unknown

add_help=False 参数意味着您仍然可以使用 -h 获取常规 setup.py 帮助(前提是给出了 --foo)。

A quick and easy way similar to that given by totaam would be to use argparse to grab the -foo argument and leave the remaining arguments for the call to distutils.setup(). Using argparse for this would be better than iterating through sys.argv manually imho. For instance, add this at the beginning of your setup.py:

argparser = argparse.ArgumentParser(add_help=False)
argparser.add_argument('--foo', help='required foo argument', required=True)
args, unknown = argparser.parse_known_args()
sys.argv = [sys.argv[0]] + unknown

The add_help=False argument means that you can still get the regular setup.py help using -h (provided --foo is given).

徒留西风 2024-07-22 20:30:34

也许你和我一样是一个没有经验的程序员,在阅读了上面的所有答案后仍然在挣扎。 因此,您可能会发现另一个可能有用的示例(并解决先前答案中有关输入命令行参数的注释):

class RunClientCommand(Command):
    """
    A command class to runs the client GUI.
    """

    description = "runs client gui"

    # The format is (long option, short option, description).
    user_options = [
        ('socket=', None, 'The socket of the server to connect (e.g. '127.0.0.1:8000')',
    ]

    def initialize_options(self):
        """
        Sets the default value for the server socket.

        The method is responsible for setting default values for
        all the options that the command supports.

        Option dependencies should not be set here.
        """
        self.socket = '127.0.0.1:8000'

    def finalize_options(self):
        """
        Overriding a required abstract method.

        The method is responsible for setting and checking the 
        final values and option dependencies for all the options 
        just before the method run is executed.

        In practice, this is where the values are assigned and verified.
        """
        pass

    def run(self):
        """
        Semantically, runs 'python src/client/view.py SERVER_SOCKET' on the
        command line.
        """
        print(self.socket)
        errno = subprocess.call([sys.executable, 'src/client/view.py ' + self.socket])
        if errno != 0:
            raise SystemExit("Unable to run client GUI!")

setup(
    # Some other omitted details
    cmdclass={
        'runClient': RunClientCommand,
    },

上面的内容经过测试并来自我编写的一些代码。 我还包含了稍微更详细的文档字符串,以使事情更容易理解。

至于命令行:python setup.py runClient --socket=127.0.0.1:7777。 使用 print 语句进行快速双重检查表明 run 方法确实选择了正确的参数。

我发现有用的其他资源(更多更多示例):

自定义 distutils 命令

https://seasonofcode.com/posts/how-to-add-custom-build-steps-and-commands-to-setuppy.html

Perhaps you are an unseasoned programmer like me that still struggled after reading all the answers above. Thus, you might find another example potentially helpful (and to address the comments in previous answers about entering the command line arguments):

class RunClientCommand(Command):
    """
    A command class to runs the client GUI.
    """

    description = "runs client gui"

    # The format is (long option, short option, description).
    user_options = [
        ('socket=', None, 'The socket of the server to connect (e.g. '127.0.0.1:8000')',
    ]

    def initialize_options(self):
        """
        Sets the default value for the server socket.

        The method is responsible for setting default values for
        all the options that the command supports.

        Option dependencies should not be set here.
        """
        self.socket = '127.0.0.1:8000'

    def finalize_options(self):
        """
        Overriding a required abstract method.

        The method is responsible for setting and checking the 
        final values and option dependencies for all the options 
        just before the method run is executed.

        In practice, this is where the values are assigned and verified.
        """
        pass

    def run(self):
        """
        Semantically, runs 'python src/client/view.py SERVER_SOCKET' on the
        command line.
        """
        print(self.socket)
        errno = subprocess.call([sys.executable, 'src/client/view.py ' + self.socket])
        if errno != 0:
            raise SystemExit("Unable to run client GUI!")

setup(
    # Some other omitted details
    cmdclass={
        'runClient': RunClientCommand,
    },

The above is tested and from some code I wrote. I have also included slightly more detailed docstrings to make things easier to understand.

As for the command line: python setup.py runClient --socket=127.0.0.1:7777. A quick double check using print statements shows that indeed the correct argument is picked up by the run method.

Other resources I found useful (more and more examples):

Custom distutils commands

https://seasonofcode.com/posts/how-to-add-custom-build-steps-and-commands-to-setuppy.html

海未深 2024-07-22 20:30:34

为了与 python setup.py installpip install 完全兼容。,您需要使用环境变量,因为 pip 选项 -- install-option= 被 bug:

  1. pip --install-option 跨行泄漏
  2. 确定应如何处理 --(install|global)-option使用轮子
  3. pip 未正确命名 abi3 轮子

这是一个未使用的完整示例--install-option

import os
environment_variable_name = 'MY_ENVIRONMENT_VARIABLE'
environment_variable_value = os.environ.get( environment_variable_name, None )

if environment_variable_value is not None:
    sys.stderr.write( "Using '%s=%s' environment variable!\n" % (
            environment_variable_name, environment_variable_value ) )

setup(
        name = 'packagename',
        version = '1.0.0',
        ...
)

然后,您可以在 Linux 上像这样运行它:

MY_ENVIRONMENT_VARIABLE=1 pip install .
MY_ENVIRONMENT_VARIABLE=1 pip install -e .
MY_ENVIRONMENT_VARIABLE=1 python setup.py install
MY_ENVIRONMENT_VARIABLE=1 python setup.py develop

但是,如果您在 Windows 上,则像这样运行它:

set "MY_ENVIRONMENT_VARIABLE=1" && pip install .
set "MY_ENVIRONMENT_VARIABLE=1" && pip install -e .
set "MY_ENVIRONMENT_VARIABLE=1" && python setup.py install
set "MY_ENVIRONMENT_VARIABLE=1" && python setup.py develop

参考资料:

  1. 如何使用 '--install 获取从 pip 传递到 setup.py 的参数-option'?
  2. 将命令行参数传递给 pip install
  3. 将库路径作为命令传递setup.py 的行参数

To be fully compatible with both python setup.py install and pip install . you need to use environment variables because pip option --install-option= is bugged:

  1. pip --install-option leaks across lines
  2. Determine what should be done about --(install|global)-option with Wheels
  3. pip not naming abi3 wheels correctly

This is a full example not using the --install-option:

import os
environment_variable_name = 'MY_ENVIRONMENT_VARIABLE'
environment_variable_value = os.environ.get( environment_variable_name, None )

if environment_variable_value is not None:
    sys.stderr.write( "Using '%s=%s' environment variable!\n" % (
            environment_variable_name, environment_variable_value ) )

setup(
        name = 'packagename',
        version = '1.0.0',
        ...
)

Then, you can run it like this on Linux:

MY_ENVIRONMENT_VARIABLE=1 pip install .
MY_ENVIRONMENT_VARIABLE=1 pip install -e .
MY_ENVIRONMENT_VARIABLE=1 python setup.py install
MY_ENVIRONMENT_VARIABLE=1 python setup.py develop

But, if you are on Windows, run it like this:

set "MY_ENVIRONMENT_VARIABLE=1" && pip install .
set "MY_ENVIRONMENT_VARIABLE=1" && pip install -e .
set "MY_ENVIRONMENT_VARIABLE=1" && python setup.py install
set "MY_ENVIRONMENT_VARIABLE=1" && python setup.py develop

References:

  1. How to obtain arguments passed to setup.py from pip with '--install-option'?
  2. Passing command line arguments to pip install
  3. Passing the library path as a command line argument to setup.py
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文