Distutils: 如何向 setup.py 传递用户定义的参数?

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

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

python setup.py install -foo myfoo
49679 次浏览

您实际上不能将自定义参数传递给脚本。然而,下列事情是可能的,可以解决你的问题:

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

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

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

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

关于如何使用 distutils 实现这一点的文档非常糟糕,最后我遇到了这样一个文档: 搭便车旅行者包装指南,它使用 sdist及其 user_options。 我发现 延伸的蒸馏酒精参考文献不是特别有帮助。

虽然这看起来像是用 distutils 实现的“正确”方法(至少是我能找到的唯一一种模糊文档化的方法)。我在另一个答案中提到的 --with--without开关上找不到任何东西。

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

与 totaam 提供的方法类似的一种快速简单的方法是使用 argparse 获取-foo 参数,并将其余参数留给 distutils.setup ()调用。为此使用 argparse 比手动遍历 sys.argv imho 要好。例如,将其添加到 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的话)。

由于 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,
}
)

我成功地使用了一个变通方法来使用类似于 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(...)

可以添加一些额外的验证来确保输入是正确的,但是我是这样做的

是的,现在是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,
},
:

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

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

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

  1. 管道 --install-option跨线泄漏
  2. 确定如何使用 Wheels 处理——(install | global)-选项
  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. 如何使用 & # 39; —— install-option & # 39; 从 pip 获取传递给 setup.py 的参数?
  2. 向 pip install 传递命令行参数
  3. 将库路径作为命令行参数传递给 setup.py