setup.py里有什么?
C/C++扩展总结
对于C或C++ 代码,当我们在编译的时候,需要指定 包含的头文件目录,源代码目录,引用的库文件名称,引用的库文件的目录,这样编译器才能找到 我们自己编写的头文件及源文件的对应关系,主要是其中函数的对应关系,也能找到我们引用的外部库的头文件和库的对应符号关系。说白了,库就是打包后的源文件。如果没有指定某些库的库目录,编译器会默认从系统目录下查找。
gcc/g++的编译参数:
-I (i 的大写):指定头文件的所在的目录,可以使用相对路径。
-L :表示要链接的库所在的目录。 -L/usr/lib 表示要连接的库在/usr/lib下。一般情况下,编译器会自动搜索 /usr/lib 和 /usr/local/lib 目录,可以不用指明,自己定义的库需要格外指定路径;
-L. :表示要链接的库在当前目录
-l (L的小写):表示需要链接库的名称。注意不是库文件名称,Linux下的库文件有一个约定,全部以lib开头,因此可以省去lib,比如库文件为 librandy.so,那么库名称为randy;
-shared :指定生成动态链接库;
-fPIC: 表示编译为位置独立的代码,表示编译后的代码是位置不相关的,其他程序都可以独立使用。
-static : 表示指定使用静态链接库
// -I 参数指定头文件搜索目录
gcc/g++ hello.c -I /home/randy/include -o hello
// -L 参数指定库文件搜索路径, -l 指定库名称
gcc hello.c -L /home/randy/lib -l mylib -o hello
// -static 强制使用静态链接库
gcc hello.c -L /home/randy/lib -static -l mylib -o hello
gcc -o hello main.c -static -L. –lhellos
Windows Visual Studio
VC++目录:
包含目录:寻找#include中的randyx.h的搜索目录
库目录:寻找.lib文件的搜索目录
C/C++:
常规->附加包含目录:寻找#include中的randyx.h的搜索目录(每一项对应一个文件夹randyX,文件夹中包含了编译时所需的头文件,使用时直接#include即可)
链接器:
常规->附加库目录:寻找.lib文件的搜索目录
输入->附加依赖项:lib库(C++的库会把函数、类的声明放在*.h中,实现放在*.cpp或*.cc中。编译之后,.cpp,.cc,*.c会被打包成一个.lib文件,这样可以保护源代码)
Cmake
在编写CMakeLists.txt 文件时,也是同样要指明上述路径及文件名称:
-
include_directories : 添加路径到头文件的搜索路径
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...]) include_directories(${PROJECT_SOURCE_DIR}/testFunc/inc
-
add_executable : 利用源码文件生成目标可执行程序
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
-
link_libraries : 将库链接到以后添加的所有目标
link_libraries([item1 [item2 [...]]] [[debug|optimized|general] <item>] ...)
-
add_library : 生成动态库或静态库
add_library(<name> [STATIC | SHARED | MODULE] [source1] [source2 ...]) add_library(<指定库的名字> [STATIC静态 | SHARED动态 | MODULE] [source1源文件] [source2源文件 ...]) add_library(${PROJECT_NAME} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/source/ )
-
find_library :在指定路径下查找库,并把库的绝对路径存放到变量里
find_library(变量名称 库名称 提示 路径 ${PROJECT_SOURCE_DIR}/testFunc/lib) find_library(CUDNN_STATIC_LIBRARY NAMES ${CUDNN_STATIC_LIB_NAME}
-
target_link_libraries : 把目标文件与库文件进行链接
target_link_libraries(${PROJECT_NAME} ${catkin_LIBRARIES} )
setup.py C/C++扩展模块
setup.py 添加C或C++的扩展模块的时候,同样需要制定 源文件目录 sources 、 头文件目录 include_dirs 、库名称 libraries 及 库目录 library_dirs。
from distutils.core import setup, Extension
module1 = Extension('demo',
define_macros = [('MAJOR_VERSION', '1'),
('MINOR_VERSION', '0')],
include_dirs = ['/usr/local/include'],
libraries = ['tcl83'],
library_dirs = ['/usr/local/lib'],
sources = ['demo.c'])
setup (name = 'PackageName',
version = '1.0',
description = 'This is a demo package',
author = 'Randy',
author_email = 'randy@jeff',
url = 'https://docs.python.org/extending/building',
long_description = '''
This is really just a demo package.
''',
ext_modules = [module1])
进行 python setup.py develop
或者 python setup.py install
的时候,python 会调用 g++ 进行编译,将对应参数传递过去即可,本质上是一样的。
g++ -pthread -B /home/randy/anaconda3/envs/randy-v1.0/compiler_compat -Wl,--sysroot=/ -pthread -shared -B /home/randy/anaconda3/envs/randy-v1.0/compiler_compat -L/home/randy/anaconda3/envs/randy-v1.0/lib -Wl,-rpath=/home/randy/anaconda3/envs/randy-v1.0/lib -Wl,--no-as-needed -Wl,--sysroot=/ /home/randy/codes/lidarops/build/temp.linux-x86_64-cpython-38/lidarops/rslabel/src/RSLabelGenerator.o -L/home/randy/anaconda3/envs/randy-v1.0/lib/python3.8/site-packages/torch/lib -L/home/randy/anaconda3/envs/randy-v1.0/lib/python3.8/site-packages/torch/lib -L/home/randy/anaconda3/envs/randy-v1.0/lib/python3.8/site-packages/torch/lib -lc10 -ltorch -ltorch_cpu -ltorch_python -lc10 -ltorch -ltorch_cpu -ltorch_python -lc10 -ltorch -ltorch_cpu -ltorch_python -o build/lib.linux-x86_64-cpython-38/lidarops/rslabel/label_generator.cpython-38-x86_64-linux-gnu.so
为什么需要分发打包?
平常我们习惯了使用 pip 来安装一些第三方模块,有点像我们在 windows 系统上安装 exe 程序,方便后面直接点击快捷键使用一样,也像 linux 系统里面,我们通过 apt-get install
各类软件包一样,这些 exe 程序或者安装的软件包,都是别人替我们封装好的程序或者库,封装的过程就是 打包
。
打包,就是将源代码或者库文件进行封装,安装后释放到指定位置,这样使用者可以即装即用,直接引用接口或者调用程序即可,不用关心其具体的实现,同时也有利于开发者封装自己的源代码,保护源码。
Distutils
distutils
(distribute utils)是 Python 的一个标准库,即分发工具,所有后续的打包工具,全部都是基于它进行开发的。
Distutils 用起来非常简单,对于模块开发者或安装第三方模块的用户/管理员均是如此。开发者的责任(当然还有编写可靠、良好文档和经过良好测试的代码!)就是:
- 编写一个设置脚本 (
setup.py
by convention) - (可选)编写设置脚本的配置文件
- 创建源码的发行版
- (可选)创建一个或多个编译好(二进制)的发行版
一个简单的例子
setup 脚本通常很简单,尽管是用 Python 编写的,它能干的事情没有限制。
如果只想发布一个名为 foo
的模块,位于 foo.py
文件中,那么 setup 脚本可以如此简单:
from distutils.core import setup
setup(name='foo',
version='1.0',
py_modules=['foo'],
)
注意:
- 提供给 Distutils 的大部分信息将作为关键字参数发给
setup()
函数。 - 这些关键字参数分为两类:包的元数据(名称、版本号)和包中内容的描述信息(本例中是纯 Python 模块的列表)。
- 模块由模块名指定,而不是文件名(包和扩展也是如此)。
- 建议多提供一些元数据,特别是开发者姓名、电子邮件地址和项目的URL。
为该模块创建一个源码发布版本:
python setup.py sdist
sdist 将创建一个归档文件(例如在 Unix 中为 tarball,在 Windows 中为 ZIP 文件),其中包含你的配置脚本 setup.py
以及你的模块 foo.py
。 此归档文件将被命名为 foo-1.0.tar.gz
(或 .zip
),并将解包到目录 foo-1.0
当中。
如果最终用户希望安装 foo
模块,只需下载 foo-1.0.tar.gz
(或 .zip
)并解压,进入 foo-1.0
目录运行:
python setup.py install
这会把 foo.py
复制到 Python 安装环境的第三方模块目录中。
如果想让用户真正轻松使用,你可以为他们创建一个或多个内置发行版。例如,如果您在 Windows 计算机上运行,并且想让其他 Windows 用户轻松使用,则可以使用 bdist_wininst
命令创建可执行安装程序(最适合此平台的内置发行版类型)。例如:
python setup.py bdist_wininst
将在当前目录中创建一个可执行安装程序 foo-1.0.win32.exe。
linux 系统可以使用下述命令,将源码编译成 .whl
文件:
python setup.py bdist_wheel
.whl文件就像压缩包一样,可以点击打开,如果源文件包含python 代码,则python 代码仍然可见源码,而 C/C++ 文件则编译成 so 库文件
其他有用的内置分发格式是 RPM,可由 bdist_rpm 、Solaris pkgtool(:command:
bdist_pkgtool)和 HP-UX swinstall(:command:
bdist_sdux)实现。比如,以下命令将创建一个名为 foo-1.0.noarch.rpm
的RPM文件:
python setup.py bdist_rpm
(bdist_rpm 命令用到了 rpm 可执行文件,因此必须运行在基于 RPM 的系统中,如 Red Hat Linux 、 SuSE Linux 或 Mandrake Linux)。
可以随时运行以下命令,以便了解当前可用的分发格式:
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=xztar xz'ed tar file
--formats=ztar compressed tar file
--formats=tar tar file
--formats=zip ZIP file
--formats=egg Python .egg file
通用的 Python 术语
-
module
实现 Python 代码重用的基本单位:可被其他代码导入的一段代码。有三种类型的模块与本文有关:纯 Python 模块、扩展模块和包。
-
纯 Python 模块
用 Python 编写的模块,包含在某
.py
文件中(可能还会有相关的.pyc
文件)。有时被称为 “纯模块”。 -
extension module
– 扩展模块用低级语言编写的 Python 模块。Python 用 C/C++ ,而 Jython 则用Java。
通常包含在一个可动态加载的预编译文件中,比如 Unix 中的 Python 扩展是一个共享对象(
.so
)文件,Windows 中的 Python 扩展则是一个 DLL (扩展名为.pyd
),而 Jython 的扩展是个 Java class 文件。(注意,目前,Distutils 只处理 Python 的 C/C++ 扩展。) -
package
(包)包含其他模块的模块;
通常位于文件系统的某个目录中,区别于其他目录的标记就是存在一个
__init__.py
文件,如
packages=find_packages(exclude=("configs", "tests",))
find_packages(exclude=("configs", "tests",))
是递归地包含当前目录下除了configs和tests外所有文件夹的包。
-
root package
(根包)包的层次结构的根。(其并非一个真正的包,因为没有
__init__.py
文件。但总得给它起个名字)。 绝大多数标准库都在根包中,还有许多不属于任何大型模块的小型、独立的第三方模块。与普通的包不同,根包中的模块可能会在很多目录中出现:事实上,sys.path
列出的每个目录都会为根包提供模块。
使用 Setuptools 构建和分发软件包
Setuptools 是 Python distutils 的增强功能集合,可让开发人员更轻松地构建和分发 Python 包,尤其是那些依赖于其他包的包。
使用 setuptools 构建和分发的包在用户看来就像基于 distutils 的普通 Python 包一样。
源码包与二进制包
-
以源码包的方式发布
源码包安装的过程,是先解压,再编译,最后才安装,可以跨平台。本质是一个压缩包,其常见的格式有:
格式 后缀 zip .zip gztar .tar.gz bztar .tar.bz2 ztar .tar.Z tar .tar -
以二进制包形式发布
二进制包的安装过程省去了编译的过程,直接进行解压安装,所以安装速度较源码包来说更快。
由于不同平台的编译出来的包无法通用,所以在发布时,需事先编译好多个平台的包。
二进制包的常见格式有:
格式 | 后缀 |
---|---|
egg | egg |
wheel | .whl |
eggs VS wheels
Wheel 的出现是为了替代 Egg,实际上是一个zip包,其现在被认为是 Python 的二进制包的标准格式。
Wheel 和 Egg 的主要区别:
- Wheel 有一个官方的 PEP427 来定义,而 Egg 没有 PEP 定义
- Wheel 是一种分发格式,即打包格式。而 Egg 既是一种分发格式,也是一种运行时安装的格式(如果保持压缩状态),并且是可以被直接 import
- Wheel 文件不会包含 .pyc 文件。因此,当发行版仅包含 Python 文件(即没有编译的扩展)并且与 Python 2 和 3 兼容时,wheel 可能是“通用”的,类似于 sdist。
- Wheel 使用和 PEP376 兼容的 .dist-info 目录,而 Egg 使用 .egg-info 目录
- Wheel 有着更丰富的命名规则。单个 wheel 存档可以指示它与许多 Python 语言版本和实现、ABI 和系统架构的兼容性。
- Wheel 已进行版本控制。每个 Wheel 文件都包含 wheel 规范的版本和打包它的实现。
- Wheel 在内部被 sysconfig 路径类型进行组织,因此可以更轻松地转换为其他格式。
- Egg其实是对源文件的一种引用,文件内是源码的地址,本身不包含源码。
(base) qiancj@Randy-HP-ZBook