QCon全球软件开发大会8折优惠倒计时,购票立减¥1760!了解详情 >>> 了解详情
写点什么

通过 demo 学习 OpenStack 开发——软件包管理

2015 年 12 月 27 日

编者按:《通过 demo 学习 OpenStack 开发》专栏是刘陈泓的系列文章,专栏通过开发一个 demo 的形式来介绍一些参与 OpenStack 项目开发的必要的基础知识,希望帮助大家入门企业级 Python 项目的开发和 OpenStack 项目的开发。刘陈泓主要关注 OpenStack 的身份认证和计费领域。另外,还对云计算、分布式系统应用和开发感兴趣。

为什么写这个系列

OpenStack 是目前我所知的最大最复杂的基于 Python 项目。整个 OpenStack 项目包含了数十个主要的子项目,每个子项目所用到的库也不尽相同。因此,对于 Python 初学者和未接触过 OpenStack 项目的人 Python 来说,入门的难度相当大。

幸运的是,OpenStack 中的项目有很多共同点。比如,它们有相同的代码库结构,都尽可能是用同样的库,配置文件和单元测试的规范也都几乎一样。因此,通过学习这些共通的部分,我们就可以快速掌握多个 OpenStack 项目。但是,万事开头难,学习这些基础知识总是痛苦的。不过,学习的难点并不在于这些知识点本身有多难理解,而是这些基础知识的应用场景和应用效果对初学者来说都是模糊的。这个系列文章的目的就是帮助有需要的人了解 OpenStack 中一些常见的知识点。理解过程就是通过动手做一个 Web application demo 来实现的。

这个系列文章会涉及到以下的知识点:

  • 包管理和 pbr
  • WSGI, RESTful Service 和 Pecan 框架
  • eventlet
  • SQLAlchymy
  • 单元测试

下面的知识点是不会专门讲的,如果有遇到不会的请自学:

  • git

软件包管理

软件包管理是每个 OpenStack 项目的基础,其目的是用来将项目代码打包成源码包或者二进制包进行分发。一个项目的代码可能会被打包放到 PyPI 上,这样你可以通过pip命令安装这个包;也可能会被打包放到项目的软件仓库里,这样你可以通过apt-get install或者yum install来安装这个软件包。

不幸的是,Python 在软件包管理十分混乱,至少历史上十分混乱。原因有两个:

  • 一是标准库提供的软件包管理功能十分弱,
  • 二是官方没有提供统一的软件包管理标准。

对于这个领域,我曾经也是混乱的,只知道使用 easy_install 和 pip 来安装软件包。不过自从看了 _The Hacker’s Guide to Python_(《Python 高手之路》)之后,算是知道点来龙去脉。

软件打包工具的历史

这里我会讲一下我知道的 Python 的软件打包工具的历史,我们按照历史顺序来叙述。

distutils (before 2000)

disutils 自从 1998 年起就是 Python 标准库的一部分了,不过它在 2000 年就停止了开发。disutils 是最早的 Python 打包工具和标准,也奠定了对 Python 软件进行打包的一个基本工作方式:使用 setup.py 文件。来看一个 setup.py 文件的例子:

复制代码
<span>from</span> disutils.core <span>import</span> setup
setup(name=<span>'webdemo'</span>,
description=<span>'A simple web demo.'</span>,
author=<span>'author name'</span>,
author_email=<span>'author_name@example.com'</span>
url=<span>'http://example.com'</span>,
packages=[<span>'webdemo'</span>])

setup.py 文件是放在项目根目录下的:

复制代码
➜ ~/programming/Python/webdemo git:(master) ✗ $ ls
LICENSE README.md setup.py webdemo

然后你就可以使用命令 Python setup.py build 来编译包,可以使用 Python setup.py install 来安装这个项目。如果需要帮助,可以通过 Python setup.py --help-commands 来查看支持的命令。

setuptools

disutils 停止开发后,setuptools 成了继任者。setuptools 提供了很多高级功能,包括自动依赖处理、Egg 分发格式以及easy_install命令。setuptools 的使用方式和 disutils 差不多,也是以一个 setup 函数作为入口,只不过该函数来自于 setuptools 模块,而且支持更多的参数,比如 classifiers, setup_requires 等,参数更多意味着功能更多。

后来有一段时间 setuptools 项目发展开始变得缓慢了,就有人从 setuptools 项目创建了distribute项目。distribute 开始支持 Python 3 等新特性。不过一段时间后,distribute 项目又和 setuptools 项目合并了(2013 年 3 月)。因此,现在已经不存在 distribute 项目了。

到目前为止,setuptools 还是使用最多的打包工具,而且开发很活跃,2015 年 6 月刚刚发布了 18.0 版本。 setuptools 项目的文档。OpenStack 目前也是使用 setuptools 库来执行打包操作,我们下面会详细点介绍 setuptools 工具。

disutils2

在 setuptools 项目发展的过程中,有一个叫 disutils2 的项目也在并行开发中,其目的是全面取代 Python 标准库中的 distutils。disutils2 的最大改进是将 setup 函数的参数单独放到一个 setup.cfg 的文件中(这些成为包的元数据)。不够 disutils2 这个项目缺点很多,而且没有功能上还不如 setuptools 项目,所以在 2012 年的时候,这个项目被废弃了。

distlib

这个是一个新的打包工具,目标也是取代 disutils。不过这个项目的开发进展也不快,到 2015 年才发布了 0.2.0 版本。目前还未能并入到 Python 的标准库中。不过可以保持关注。项目文档地址

在OpenStack 中使用打包工具

前面已经提到了,OpenStack 也是使用setuptools 工具来进行打包,不过为了满足OpenStack 项目的需求,引入了一个辅助工具 pbr来配合 setuptools 完成打包工作。

pbr (Python Build Reasonableness)

pbr 是一个 setuptools 的扩展工具,被开发出来的主要目的是为了方便使用 setuptools,其项目文档地址也在OpenStack 官网内

先说一下pbr 如何使用:

复制代码
import setuptools
setuptools.setup(setup_requires=[<span>'pbr'</span>], pbr=True)

按照上面的方式就可以配置 setuptools 工具使用 pbr 来协助完成打包工作。这里的setup_requires参数意思是 setup 函数在执行之前需要依赖的包的列表。这里的依赖的包的功能可以理解为生成 setup 的实际参数。你可以看到,当使用 pbr 的时候,setup 函数只有两个参数,然而实际上setuptools.setup函数实际上是disutils.core.setup函数,会接收任何参数,这些参数可以通过在调用时指定,也可以通过所依赖的扩展来生成(比如 pbr)。

那么 OpenStack 社区为啥要开发 pbr 呢?因为 setuptools 库使用起来还是有点麻烦,参数太多,而且直接通过指定 setup 函数的参数的方法实在太不方便了。pbr 就是为了方便而生的,它带了了如下的改进:

  1. 使用 setup.cfg 文件来提供包的元数据。这个是从 disutils2 学来的。
  2. 基于 requirements.txt 文件来实现自动依赖安装。requirements.txt 文件中包含了一个项目所要依赖的库,这个文件的格式是和 pip 兼容的。
  3. 利用 Sphinx 实现文档自动化。
  4. 基于 git history 自动生成 AUTHORS 和 ChangeLog 文件。
  5. 针对 git 自动创建文件列表。
  6. 基于 git tags 的版本号管理。

pbr 的版本推导

这里重点说明一下基于 git tag 的版本号管理这个功能。当使用 pbr 的时候,版本号有两种方式:postversioningpreversioning,postversioning 是默认方式。要是用 preversioning 的方式,则需要设置 setup.cfg 文件中的 _[metadata]_ 段的version字段的值。无论采用哪种方式,版本号都是从 git 的历史推理得到的。pbr 使用的版本号标准是 Linux/Python Compatible Semantic Versioning 3.0.0 ,简单的说就是下面这个标准:

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards-compatible manner,
  3. and PATCH version when you make backwards-compatible bug fixes.

pbr 的版本推导按照如下的步骤进行(注意,最终版本号才是软件包的版本号):

  1. 如果设置 version 的值为一个给定的版本号,且这个版本号刚好对应一个 tag,则这个值就是最终版本号(注意,这里只有签名的 tag 才有效)。
  2. 如果不是上面情况,则 pbr 会找到最近的一个 tag,然后为其 MINOR 值加 1 得到一个比它大的最小版本号(注意,这个还不是最终版本号)。
  3. 然后 pbr 会从最近的一个 tag 开始遍历所有的 git commit,并检查每个提交的 commit message,在 commit message 中查找Sem-Ver:这样的行:
  • 如果 Sem-Ver 的值是 _bugfix_,则会增加版本号中 PATCH 部分的值。

  • 如果 Sem-Ver 的值是 _feature_ 或者 _deprecation_,则会增加版本号中 MINOR 部分的值。

  • 如果 Sem-Ver 的值是 _api-break_,则会增加版本号中 MAJOR 部分的值。

  • 如果 Sem-Ver 行不存在,则认为值是 _bugfix_。

  • 如果 Sem-Ver 的值不在上面列出的范围内,则会给出警告。

    1. 如果使用的是 postversioning 的方式,也就是 setup.cfg 中不指定 version 的值,则 pbr 会使用规则 3 推导出来的值作为目标版本号(只是目标版本号,不是最终版本号)。
    2. 如果使用的是 preversioning 的方式,也就是 setup.cfg 中指定了 version 的值(而且不符合规则 1),则会检查指定的 version 是否高于规则 3 推导出来的版本号,如果没有,则会抛出异常,如果有,则使用指定的版本号作为目标版本号。
    3. 在得到目标版本号之后,开始计算开发版本号。开发版本号的形式如下:MAJOR.MINOR.PATCH.devN。这里要计算的是devN中的N。这个值等于从最近的 git tag 开始的提交数量。计算完开发版本号之后,就得到了最终版本号。

总的来说,从上面的规则计算出来的版本号只有两种形式,一种是发布版本号(对应到某个 tag),另一种是开发版本号。注意:pbr 要求 tag 都是要签名的,也就是打 tag 时要使用git tag -a -s X.Y.Z的形式。

setup.cfg 和 requirements.txt

setup.cfg

由于 OpenStack 项目都使用了 setuptools 和 pbr 来执行打包工作,因此项目的元数据都放在 setup.cfg 文件中。我们以 keystone 项目的 setup.cfg 文件为例来说明这个文件里一般会包含什么内容。以下是写这篇文章时最新的 keystone 项目的 setup.cfg 文件的内容(以#开头的是我加的注释):

复制代码
[metadata]
name = keystone
version = <span>8.0</span>.<span>0</span>
summary = OpenStack Identity
description-file =
README.rst
author = OpenStack
author-email = OpenStack-dev@lists.OpenStack.org
home-page = http://www.OpenStack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: <span>2</span>
Programming Language :: Python :: <span>2.7</span>
[files]
packages =
keystone
[global]
setup-hooks =
pbr.hooks.setup_hook
[egg_info]
tag_build =
tag_date = <span>0</span>
tag_svn_revision = <span>0</span>
[build_sphinx]
all_files = <span>1</span>
build-dir = doc/build
<span>source</span>-dir = doc/<span>source</span>
[compile_catalog]
directory = keystone/locale
domain = keystone
[update_catalog]
domain = keystone
output_dir = keystone/locale
input_file = keystone/locale/keystone.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = keystone/locale/keystone.pot
copyright_holder = OpenStack Foundation
msgid_bugs_address = https://bugs.launchpad.net/keystone
[pbr]
warnerrors = True
autodoc_tree_index_modules = True
[entry_points]
console_scripts =
keystone-all = keystone.cmd.all:main
keystone-manage = keystone.cmd.manage:main
...

(上面有些未注释的部分我目前还不太清楚,后续补充,可以先参考 PEP301 )

这里说说一下 classifier 这个参数。这个参数是用来指定一个软件包的分类、许可证、允许运行的操作系统、允许运行的 Python 的版本的信息。这些信息是在一个叫 trove 的项目。关于 Python 和 trove 的关系,请参考

你可以在PyPI 上找到完整的classifier 值列表,地址。另外,你也可以通过setuptools 的命令来获取这个列表,在项目根目录下执行: Python setup.py register --list-classifiers

requirements.txt

这个文件指定了一个项目依赖的包有哪些,并且支出了依赖的包的版本需求,可以看看 keystone 项目的 requirements.txt:

复制代码
<span># The order of packages is significant, because pip processes them in the order</span>
<span># of appearance. Changing the order has an impact on the overall integration</span>
<span># process, which may cause wedges in the gate later.</span>
pbr<<span>2.0</span>,>=<span>0.11</span>
WebOb>=<span>1.2</span><span>.3</span>
eventlet>=<span>0.17</span><span>.4</span>
greenlet>=<span>0.3</span><span>.2</span>
PasteDeploy>=<span>1.5</span><span>.0</span>
Paste
Routes!=<span>2.0</span>,>=<span>1.12</span><span>.3</span>
cryptography>=<span>0.8</span><span>.2</span> <span># Apache-2.0</span>
six>=<span>1.9</span><span>.0</span>
SQLAlchemy<<span>1.1</span><span>.0</span>,>=<span>0.9</span><span>.7</span>
sqlalchemy-migrate>=<span>0.9</span><span>.6</span>
stevedore>=<span>1.5</span><span>.0</span> <span># Apache-2.0</span>
passlib
Python-keystoneclient>=<span>1.6</span><span>.0</span>
keystonemiddleware>=<span>1.5</span><span>.0</span>
oslo<span>.concurrency</span>>=<span>2.1</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.config</span>>=<span>1.11</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.messaging</span>!=<span>1.12</span><span>.0</span>,>=<span>1.8</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.db</span>>=<span>1.10</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.i</span>18n>=<span>1.5</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.log</span>>=<span>1.2</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.middleware</span>!=<span>2.0</span><span>.0</span>,>=<span>1.2</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.policy</span>>=<span>0.5</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.serialization</span>>=<span>1.4</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.service</span>>=<span>0.1</span><span>.0</span> <span># Apache-2.0</span>
oslo<span>.utils</span>>=<span>1.6</span><span>.0</span> <span># Apache-2.0</span>
oauthlib>=<span>0.6</span>
pysaml2>=<span>2.4</span><span>.0</span>
dogpile<span>.cache</span>>=<span>0.5</span><span>.3</span>
jsonschema!=<span>2.5</span><span>.0</span>,<<span>3.0</span><span>.0</span>,>=<span>2.0</span><span>.0</span>
pycadf>=<span>0.8</span><span>.0</span>
msgpack-Python>=<span>0.4</span><span>.0</span>

软件包归档格式

Python 的软件包一开始是没有官方的标准分发格式的。比如 Java 有 jar 包或者 war 包作为分发格式,Python 则什么都没有。后来不同的工具都开始引入一些比较通用的归档格式。比如,setuptools 引入了 Egg 格式。但是,这些都不是官方支持的,存在元数据和包结构彼此不兼容的问题。因此,为了解决这个问题,PEP 427 定义了新的分发包标准,名为 Wheel。目前 pip 和 setuptools 工具都支持 Wheel 格式。这里我们简单总结一下常用的分发格式:

  • tar.gz 格式:这个就是标准压缩格式,里面包含了项目元数据和代码,可以使用Python setup.py sdist命令生成。
  • .egg 格式:这个本质上也是一个压缩文件,只是扩展名换了,里面也包含了项目元数据以及源代码。这个格式由 setuptools 项目引入。可以通过命令Python setup.py bdist_egg命令生成。
  • .whl 格式:这个是 Wheel 包,也是一个压缩文件,只是扩展名换了,里面也包含了项目元数据和代码,还支持免安装直接运行。whl 分发包内的元数据和 egg 包是有些不同的。这个格式是由 PEP 427 引入的。可以通过命令Python setup.py bdist_wheel生成。

.egg-info 和.dist-info 目录

如果你到系统中安装 Python 库的路径下看看,就能看到很多名称以.egg-info 或者以.dist-info 结尾的目录。这些目录的内容就是这个库的元数据,是从库的分发包中拷贝出来的。其中.egg-info 类型的目录来自于 Egg 格式的分发包,.dist-info 类型的目录来自于 Wheel 格式的分发包。

软件包的安装

安装工具

上面已经提到了,setuptools 项目提供了一个软件包安装工具 _esay_install_。easy_install 支持从软件归档文件中或者从 PyPI 上安装软件包,不过这个工具并不好用,比如缺少卸载功能等,因此并不流行,现在更多的都是使用 pip 工具。

pip项目提供了很好的软件包安装方式,并且已经被包含到 Python 3.4 中,可以从 PyPI、tarball 或者 Wheel 归档中安装和卸载软件按包。关于 pip 常见的用法,这里就不赘述了 (pip install, pip uninstall, pip search, …)。

安装路径

软件包的安装路径依赖于操作系统、Python 版本和安装方式。

  • 在 Debian 系的系统上(比如 Ubuntu)

    • 使用apt-get install从系统软件源安装

      • Python 2.7: /usr/lib/Python2.7/dist-packages
      • Python 3.4: /usr/lib/Python3.4/dist-packages
    • 使用pip install命令安装

      • Python 2.7: /usr/local/lib/Python2.7/dist-packages
      • Python 3.4: /usr/local/lib/Python3.4/dist-packages
    • 在 virtualenv 中使用pip install安装

      • Python 2.7: lib/Python2.7/site-packages
      • Python 3.4: lib/Python3.4/site-packages
  • 在 CentOS 系的系统上

    • 使用yum install命令安装

      • Python 2.7: /usr/lib/Python2.7/site-packages

以开发模式安装

pip 的安装命令可以使用 -e 选项,用来从本地代码目录或者版本库 URL 来安装一个开发版本的库。采用这种方式的时候,在安装目录下只会创建一个包含软件包信息的文件,真正的代码不会安装到系统目录下。

Webdemo 的打包管理

学习过包管理相关的知识后,我们就要以 OpenStack 的方法来创建一个我们自己的项目。这个项目的名称是 Webdemo,就是一个简单的 Web 服务器。这个项目会贯穿这个系列文章。在本文中,我们首先要创建 Webdemo 的项目框架并添加软件包管理相关的内容。

项目目录结构

复制代码
➜ ~/programming/Python/webdemo git:(master) ✗ $ tree .
.
├── LICENSE
├── README.md
├── requirement.txt
├── setup.cfg
├── setup.py
└── webdemo
└── __init__.py
<span>1</span> directory, <span>6</span> files

这个是一个最简单的 Python 项目目录:

  • 源代码放在子目录 Webdemo/ 下
  • 然后包含了软件包管理的所需的文件:setup.py, setup.cfg, requirements.txt
  • LICENSE 和 README

软件包管理相关

首先是 setup.py,就是这么简单:

复制代码
<span>import</span> setuptools
<span>try</span>:
<span>import</span> multiprocessing
<span>except</span> ImportError:
<span>pass</span>
setuptools.setup(
setup_requires=[<span>'pbr'</span>], pbr=<span>True</span>)

然后是 setup.cfg:

复制代码
[metadata]
name = webdemo
version = <span>0.0</span>.<span>1</span>
summary = Web Application Demo
description-file = README.md
author = author
author-email = author@example.com
classifier =
<span> Environment ::</span> Web Environment
<span> Intended Audience ::</span> Developers
<span> Intended Audience ::</span> Education
<span> License :: OSI Approved ::</span> GNU General Public License v2 (GPLv2)
<span> Operating System :: POSIX ::</span> Linux
<span> Programming Language ::</span> Python
<span> Programming Language :: Python ::</span> <span>2</span>
<span> Programming Language :: Python ::</span> <span>2.7</span>
[global]
setup-hooks =
pbr.hooks.setup_hook
[files]
packages =
webdemo
[entry_points]
console_scripts =

只包含最基本的信息。接下来是 requirements.txt 文件:

复制代码
# The <span>order</span> <span>of</span> packages <span>is</span> significant, because pip processes them <span>in</span> the <span>order</span>
# <span>of</span> appearance. Changing the <span>order</span> <span>has</span> an impact <span>on</span> the overall integration
# process, which may cause wedges <span>in</span> the gate later.
pbr<<span>2.0</span>,>=<span>0.11</span>

目前只依赖于 pbr 库。源代码目录下现在只有一个空的init.py文件。我们已经搭建好了这个最简单的项目框架了。首先,我们要把这些代码提交到 git 库,然后打上 _tag 0.0.1_:

复制代码
➜ ~<span>/programming/</span><span>Python</span>/webdemo <span>git:</span>(master) ✗ <span>$ </span>git log --oneline
<span>697427</span>c <span>Add</span> packaging information
<span>2</span>cbbf4d <span>Initial</span> commit
➜ ~<span>/programming/</span><span>Python</span>/webdemo <span>git:</span>(master) ✗ <span>$ </span>git tag -a -s <span>0</span>.<span>0</span>.<span>1</span>
➜ ~<span>/programming/</span><span>Python</span>/webdemo <span>git:</span>(master) ✗ <span>$ </span>git tag
<span>0</span>.<span>0</span>.<span>1</span>

然后就可以使用Python setup.py sdist命令来生成一个 0.0.1 版本的源码归档了:

复制代码
➜ ~/programming/Python/webdemo <span>git</span>:(master) ✗ $ Python setup.py sdist
running sdist
[pbr] Writing ChangeLog
[pbr] Generating ChangeLog
[pbr] Generating AUTHORS
running egg_info
writing pbr to webdemo.egg-info/pbr.json
writing webdemo.egg-info/PKG-INFO
writing top-level names to webdemo.egg-info/top_level.txt
writing dependency_links to webdemo.egg-info/dependency_links.txt
writing entry points to webdemo.egg-info/entry_points.txt
[pbr] Processing SOURCES.txt
[pbr] In git context, generating filelist from git
<span>warning</span>: <span>no</span> previously-included files found matching <span>'.gitreview'</span>
<span>warning</span>: <span>no</span> previously-included files matching <span>'*.pyc'</span> found anywhere <span>in</span> distribution
writing manifest file <span>'webdemo.egg-info/SOURCES.txt'</span>
<span>warning</span>: <span>sdist</span>: standard file <span>not</span> <span>found</span>: should have one <span>of</span> README, README.rst, README.txt
running check
<span>warning</span>: <span>check</span>: missing required meta-<span>data</span>: url
creating webdemo-<span>0.0</span><span>.1</span>
creating webdemo-<span>0.0</span><span>.1</span>/webdemo
creating webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info
making hard links <span>in</span> webdemo-<span>0.0</span><span>.1</span>...
hard linking AUTHORS<span> -></span> webdemo-<span>0.0</span><span>.1</span>
hard linking ChangeLog<span> -></span> webdemo-<span>0.0</span><span>.1</span>
hard linking LICENSE<span> -></span> webdemo-<span>0.0</span><span>.1</span>
hard linking README.md<span> -></span> webdemo-<span>0.0</span><span>.1</span>
hard linking requirement.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>
hard linking setup.cfg<span> -></span> webdemo-<span>0.0</span><span>.1</span>
hard linking setup.py<span> -></span> webdemo-<span>0.0</span><span>.1</span>
hard linking webdemo/__init__.py<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo
hard linking webdemo.egg-info/PKG-INFO<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info
hard linking webdemo.egg-info/SOURCES.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info
hard linking webdemo.egg-info/dependency_links.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info
hard linking webdemo.egg-info/entry_points.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info
hard linking webdemo.egg-info/<span>not</span>-zip-safe<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info
hard linking webdemo.egg-info/pbr.json<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info
hard linking webdemo.egg-info/top_level.txt<span> -></span> webdemo-<span>0.0</span><span>.1</span>/webdemo.egg-info
copying setup.cfg<span> -></span> webdemo-<span>0.0</span><span>.1</span>
Writing webdemo-<span>0.0</span><span>.1</span>/setup.cfg
Creating tar archive
removing <span>'webdemo-0.0.1'</span> (<span>and</span> everything under it)
➜ ~/programming/Python/webdemo <span>git</span>:(master) ✗ $ ls dist
webdemo-<span>0.0</span><span>.1</span>.tar.gz
➜ ~/programming/Python/webdemo <span>git</span>:(master) ✗ $ ls
AUTHORS ChangeLog dist LICENSE README.md requirement.txt setup.cfg setup.py webdemo webdemo.egg-info

验证成功,在 _dist/_ 目录下生成了一个 0.0.1 版本的源码归档,同时生成了如下的文件和目录:AUTHORS, ChangeLog, webdemo.egg-info


感谢魏星对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。

2015 年 12 月 27 日 23:183704

评论

发布
暂无评论
发现更多内容

食堂就餐卡系统设计-week1

Mr_No爱学习

Week_05 作业

golangboy

极客大学架构师训练营

学习总结-week1

Mr_No爱学习

5.3分布式缓存架构:一致性hash算法

张荣召

第五周总结

Geek_ac4080

第五周作业

Geek_ac4080

第五周 技术选型 作业一

应鹏

极客大学架构师训练营 课程作业

架构师训练营第 5 周:技术选型(一)

子青

Week 5 作业01

Croesus

AirPods过河,苹果拆桥:被“钞能力”征服的Beats何以至此?

脑极体

第五周作业

fmouse

极客大学架构师训练营

5.1分布式缓存架构:架构原理与注意事项

张荣召

第五周 作业二

Yangjing

极客大学架构师训练营

食堂就餐卡系统UML设计

CJian

架构师

week1 学习总结

幸福小子

第一周作业-食堂就餐卡设计

hunk

极客大学架构师训练营

架构师训练营第 1 期 - 第 5 周 - 学习总结

wgl

极客大学架构师训练营

架构方法-学习总结笔记

Xuenqlve

架构师训练营 1 期第 5 周:技术选型(一) - 作业

灵霄

极客大学架构师训练营

架构师训练营第5周作业

TheSRE

极客大学架构师训练营

5.2分布式缓存架构:常见的缓存实现形式

张荣召

【架构师训练营第 1 期 05 周】 学习总结

Bear

极客大学架构师训练营

架构师训练营第五周总结

xs-geek

极客大学架构师训练营

week1作业

幸福小子

架构师训练营第五周命题作业

一马行千里

极客大学架构师训练营 命题作业

第五周 作业1

Yangjing

极客大学架构师训练营

架构师训练营二期 1周总结

月下独酌

极客大学架构师训练营

第1周作业-学习总结

jingx

第一周10/25

张冬冬

总结

5.4消息队列:如何避免系统故障传递?

张荣召

第一周 架构方法-学习总结

jizhi7

极客大学架构师训练营

移动应用开发的下一站

移动应用开发的下一站

通过demo学习OpenStack开发——软件包管理-InfoQ