编写好程序后,如果想要移植到另外的一台机器上,比较方便的方式之一就是将程序代码、配置文件和依赖打包成一个标准的库,然后分发给各个客户机,使用python setup install命令安装即可,也可以上传到pypi将自己的轮子贡献出来给大家使用。
制作一个安装包
其实制作安装包非常简单,在项目文件夹下创建一个setup.py文件,然后填入一下内容:
1 | from distutils.core import setup |
这里使用的是distutils,它是python标准的打包工具,包含了标准的库文件。还有一种是 setuptools,支持easy_install安装,相对于distutils来说,更为全面。
这是一个非常简单的安装脚本,setup()里填写的是一个些跟包相关的信息,具体包含的字段信息,可以查阅官方文档。
有了安装脚本,接下来执行打包命令:1
python setup.py sdist
- 该命令会创建一个dist文件夹,里边包含一个后缀为tar.gz的包,这就是我们可以解压,进行安装的包。
- 使用者拿到这个包后,解压,到目录下执行:python setup.py install,那么脚本文件就会被拷贝到python类路径下,可以被导入使用(如果安装是egg文件,会把egg文件拷贝到dist-packages目录下)。
- 对于windows,可以执行
ython setup.py bdist_wininst
但系统必须有rpm命令的支持。可以运行下面的命令查看所有格式的支持:1
2
3
4
5
6
7
8
9
10root@network:/kong/setup# python setup.py bdist --help-formats
List of available distribution formats:
--formats=rpm RPM distribution
--formats=gztar gzip'ed tar file
--formats=bztar bzip2'ed tar file
--formats=ztar compressed tar file
--formats=tar tar file
--formats=wininst Windows executable installer
--formats=zip ZIP file
--formats=msi Microsoft Installer
setup的一些参数
1. packages
告诉Distutils需要处理那些包(包含init.py的文件夹)
2. package_dir
告诉Distutils哪些目录下的文件被映射到哪个源码包,感觉好像是一个相对路径的定义。一个例子:package_dir = {‘’: ‘lib’},表示以lib为主目录。
3. ext_modules
是一个包含Extension实例的列表,Extension的定义也有一些参数。
4. ext_package
定义extension的相对路径
5. requires
定义依赖哪些模块
6. provides
定义可以为哪些模块提供依赖
7. scripts
指定python源码文件,可以从命令行执行。在安装时指定–install-script
8. package_data
通常包含与包实现相关的一些数据文件或类似于readme的文件。1
package_data = {'': ['*.txt'], 'mypkg': ['data/*.dat'],}
表示包含所有目录下的txt文件和mypkg/data目录下的所有dat文件.
9. data_files
指定其他的一些文件(如配置文件)1
2
3
4
5setup(...,
data_files=[('bitmaps', ['bm/b1.gif', 'bm/b2.gif']),
('config', ['cfg/data.cfg']),
('/etc/init.d', ['init-script'])]
)
规定了哪些文件被安装到哪些目录中。如果目录名是相对路径,则是相对于sys.prefix或sys.exec_prefix的路径。如果没有提供模板,会被添加到MANIFEST文件中。
执行sdist命令时,默认会打包哪些东西呢?
- 所有由py_modules或packages指定的源码文件
- 所有由ext_modules或libraries指定的C源码文件
- 由scripts指定的脚本文件
- 类似于test/test*.py的文件
- README.txt或README,setup.py,setup.cfg
- 所有package_data或data_files指定的文件
还有一种方式是写一个manifest template,名为MANIFEST.in,定义如何生成MANIFEST文件,内容就是需要包含在分发包中的文件。一个MANIFEST.in文件如下:1
2
3include *.txt
recursive-include examples *.txt *.py
prune examples/sample?/buil
管理依赖
我们在编写程序的时候,肯定会用到各种各样的库文件,如果不对依赖进行安装打包,那么使用者就要一个一个地对依赖进行安装,显然是非常不方便的。而我们可以在setup.py文件中通过使用install_requires显示说明需要安装的依赖,setup.py在执行安装的时候,会自动在pypi搜索并安装该依赖到客户机中。
对于非pypi中的第三方库,setup提供了另外的一个dependency_来供开发者填入需要安装的库url。
安装过程中的交互
一般情况下,我们的配置文件都是写在data_files参数中,从而跟随安装过程到目标文件夹中。但是有些时候,我们希望可以在安装过程中跟用户进行一些交互,让用户输入一些特定的环境变量。 这个时候,我们就可以在setup.py文件中编写一些input 用来记录用户的输入:
1 | def initialize_options(self): |
这段程序就是把用户的输入,通过写文件的方式写到配置文件中,再跟随安装脚本移动到目标文件中。
自启动服务
如果希望将安装的脚本做成一个服务,就需要将写好的服务脚本,移动到/lib/systemd/system目录中。这里有个问题,服务脚本中的程序路径都是要求绝对路径,那么我们如何在安装过程中确定程序会被安装到哪个目录下呢?
这里提供一个函数,用来确定目标机器的安装路径:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20def binaries_directory(self):
'''
获取安装路径
'''
if '--user' in sys.argv:
paths = (site.getusersitepackages(),)
else:
py_version = '%s.%s' % (sys.version_info[0], sys.version_info[1])
paths = (s % (py_version) for s in (
sys.prefix + '/lib/python%s/dist-packages/',
sys.prefix + '/lib/python%s/site-packages/',
sys.prefix + '/local/lib/python%s/dist-packages/',
sys.prefix + '/local/lib/python%s/site-packages/',
'/Library/Python/%s/site-packages/',
))
for path in paths:
if os.path.exists(path):
return path
return None
当然只针对*unix系统啦。
知道路径,我们可以跟配置文件类似的动态写入我们的服务脚本:1
2
3
4
5
6
7
8
9
10
11
12
13cf = open('/lib/systemd/system/rlgpio.service', 'w')
fcontent = [
"[Unit]\n",
"Description = Runlian GPIO Service\n\n",
"[Service]\n",
"Restart=on-failure\n",
"ExecStart={0} {1}\n".format(
sys.executable, pth+"gpio_service.py"),
"RemainAfterExit=yes\n\n",
"[Install]\n",
"WantedBy=multi-user.target"
]
cf.writelines(fcontent)
安装完成后的动作
如果需要在安装完成后执行一些动作,就需要在run方法里写一些脚本,比如,我这里就是在安装过程中,对安装的服务执行了enable和restart操作:
1 | def run(self): |
这样就是一个安装包的制作过程。