如何通过 Sublime Text 插件正确使用 3rd 方依赖项?

How to properly use 3rd party dependencies with Sublime Text plugins?

我正在尝试为 Sublime Text 3 编写一个插件。

我必须在我的代码中使用几个第三方包。我设法通过手动将包复制到 /home/user/.config/sublime-text-3/Packages/User/ 来使代码工作,然后我使用相对导入来获取所需的代码。我如何将插件分发给最终用户?告诉他们将所需的依赖项复制到适当的位置当然不是要走的路。第三方模块应该如何与 Sublime Text 插件一起正确使用?我在网上找不到任何文档;我看到的只是建议将模块放在文件夹中。

Sublime 使用它自己的嵌入式 Python 解释器(目前是 Python 3.3.6,尽管下一个版本也将支持 Python 3.8),因此它会完全忽略任何您可能已在系统上安装或未安装的 Python 版本,以及为该版本安装的任何库。

因此,如果您想使用外部模块(以下简称 dependencies),您需要做额外的工作。有多种方法可以实现这一点,每种方法各有利弊。

下面列出了实现此目的的各种方法;所有这些都需要对模块在 Python 中的工作方式有一些了解,以便了解正在发生的事情。总的来说,除了所涉及的路径之外,"Sublime Text" 没有什么机制在起作用。

NOTE: The below is accurate as of the time of this answer. However there are plans for Package Control to change how it works with dependencies that are forthcoming that may change some aspect of this.

This is related to the upcoming version of Sublime supporting multiple versions of Python (and the manner in which it supports them) which the current Package Control mechanism does not support.

It's unclear at the moment if the change will bring a new way to specify dependencies or if only the inner workings of how the dependencies are installed will change. The existing mechanism may remain in place regardless just for backwards compatibility, however.

从 Sublime 插件访问 Python dependency 的所有途径都涉及将它的代码放在 Python 解释器将要查找它的地方。这类似于标准 Python 做事的方式,除了检查的位置包含在 Sublime 用来存储您的配置的区域(称为 Data 目录)而不是独立的Python 解释器,Python 在插件主机中 运行ning。

将库填充到 Lib 文件夹中

从 3.0 版本(build 3143)开始,Sublime 将在数据目录中创建一个名为 Lib 的文件夹,并在其中创建一个基于 Python 版本名称的目录。如果您使用 Preferences > Browse Packages 并上升一个文件夹级别,您将看到 Lib,并且在其内部有一个名为例如python3.3(或者如果您使用的是更新版本,python33python38)。

默认情况下,这些目录直接位于 Python sys.path 上,因此放置在其中的任何内容都将立即可供任何插件使用,就像普通的 Python 库(或任何那些内置的)会。您可以认为这些文件夹类似于标准 Python.

中的 site-packages 文件夹。

因此,可以使用任何可以安装标准 Python 库的方法,只要结果是文件最终位于此文件夹中即可。例如,您可以通过 pip 安装库,然后从 site-packages 手动将文件复制到该位置,从源手动安装等

Lib/python3.3/
|-- librarya
|   `-- file1.py
|-- libraryb
|   `-- file2.py
`-- singlefile.py

这里有版本限制;您要使用的 dependency 必须支持 Sublime 使用的 Python 版本,否则将无法使用。这对于具有本机组件(例如 .dll.so.dylib)的 Python 库尤其重要,这可能需要手动编译代码。

这个方法不是自动的;您需要这样做才能在本地使用您的包裹,并且任何想要使用您的包裹的人也需要这样做。由于 Sublime 当前使用的是 Python 的旧版本,因此获取正确版本的库也可能存在问题。

将来,Package Control 将在此位置安装 dependencies(将在 运行 至 3.0 版期间专门为此目的添加文件夹),但截至目前我'我正在写这个答案,但目前情况并非如此。

直接在您自己的包中供应您的依赖项

默认情况下,Packages 文件夹也在 sys.path 上;这就是 Sublime 查找和加载包的方式。这对于物理 Packages 文件夹以及包含 sublime-package 文件内容的 "virtual" 包文件夹都是如此。

例如,可以通过以下方式访问提供exec命令的class:

from Default.exec import ExecCommand

即使 exec.py 文件实际存储在 Sublime 文本安装文件夹的 Default.sublime-package 中,而不是实际存在于 Packages 文件夹中,这仍然有效。

因此,您可以 vendor 任何您需要的 dependencies 直接放在您自己的包中。这可能是 User 包或您正在创建的任何其他包。

重要的是要注意,Sublime 会将包顶层的任何 Python 文件视为插件,并尝试将其作为一个插件加载。因此,重要的是,如果你走这条路,你会在你的包中创建一个子文件夹并将库放在那里。

MyPackage/
|-- alibrary
|   `-- code.py
`-- my_plugin.py

有了这个结构,就可以直接访问模块了:

import MyPackage.alibrary 

from MyPackage.alibrary import someSymbol

并非所有 Python 模块都无需修改即可直接使用此方法; dependency 中的一些代码更改可能需要允许库的不同部分查看其自身的其他部分,例如,如果它不使用相对 import 来获取同级文件。许可证限制也可能会妨碍此操作,具体取决于您使用的库。

另一方面,这会直接将您正在使用的库版本锁定为您测试过的版本,这可确保您以后不会遇到任何不应有的意外.

使用此方法,您为分发包所做的任何操作都会自动分发包含在其中的销售库。因此,如果您通过 Package Control 进行分发,您不需要做任何特殊的事情,它会 Just Work™。

修改 sys.path 以指向自定义位置

嵌入到 Sublime 中的 Python 仍然是标准的 Python,因此如果需要,您可以手动操作 sys.path 描述要在其中查找包的文件夹,以便它会除了 Sublime 自动设置的标准位置之外,还可以在您选择的位置查找。

这通常不是一个好主意,因为如果操作不当,事情会很快变得糟糕。它还仍然需要您首先自己在某个地方手动安装库,在这种情况下,您最好使用上面概述的 Lib 文件夹,该文件夹已经在 sys.path.

我认为此方法是一种高级解决方案,您可以在开发过程中将其用于测试目的,但除此之外不会面向用户。如果您计划通过 Package Control 分发您的包裹,对您的包裹的审查可能会阻止对 sys.path 的操纵,并要求使用另一种方法。

使用 Package Control 的依赖系统(并且依赖存在)

包控件也包含 dependency mechanism that uses a combination of the two prior methods to provide a way to install a dependency automatically. There is a list of available dependencies,但列表可能不完整。

如果您有兴趣使用的 dependency 已经可用,您就可以开始了。有两种不同的方式来声明您需要对包的一个或多个依赖项。

NOTE: Package Control doesn't currently support dependencies of dependencies; if a dependency requires that another library also be installed, you need to explicitly mention them both yourself.

第一个涉及在包控制通道文件中为您的包条目添加一个 dependencies 键。这是您在将包添加到包控制时需要执行的步骤,这超出了本答案的范围。

当你正在开发你的包时(或者如果你决定在你完成后不想通过包控制分发你的包),那么你可以改为添加一个 dependencies.json 文件到包的根目录(可以使用 example dependencies.json 文件来说明这一点)。

完成后,您可以从命令面板中选择 Package Control: Satisfy Dependencies 让 Package Control 下载并安装依赖项(如果需要)。

如果您的软件包是由 Package Control 分发和安装的,则此步骤是自动的;否则,您需要告诉您的用户在安装包后执行此步骤。

使用包控制的依赖系统(但依赖不存在)

The method that Package Control uses to install dependencies is, as outlined at the top of the question subject to change at some point in the (possibly near) future. This may affect the instructions here. The overall mechanism may remain the same as far as setup is concerned, with only the locations of the installation changing, but that remains to be seen currently.

Package Control 通过 vendoring 的特殊组合以及对 sys.path 的操作来安装依赖项以允许找到东西。为此,它要求您以特定方式布置依赖项并提供一些额外的元数据。

构建时包含依赖项的包的布局将具有类似于以下的结构:

Packages/my_dependency/
├── .sublime-dependency
└── prefix
    └── my_dependency
        └── file.py

Package Control 将 dependency 作为一个包安装,并且由于 Sublime 将包根目录中的每个 Python 文件视为一个插件,因此依赖项的代码不会保留在顶部包的级别。如上所示,依赖项的实际内容存储在上面标记为 prefix 的文件夹中(稍后会详细介绍)。

安装依赖项时,Package Control 添加一个条目到它的特殊 0_package_control_loader 包中,导致 prefix 文件夹被添加到 sys.path,这使得所有内容都在import 语句正常可用。这就是库名称存在固有重复的原因(本例中为 my_dependency)。

关于 prefix 文件夹,这实际上并没有命名,而是有一个特殊的名称,用于确定依赖项可用的 Sublime Text 版本、平台和体系结构的组合(对于包含二进制文件的库很重要,例如)。

prefix 文件夹的名称实际上遵循 {st_version}_{os}_{arch}{st_version}_{os}{st_version}all 的形式。 {st_version}可以是st2或者st3{os}可以是windowslinux或者osx{arch}都可以是 x32x64.

因此您可以说您的依赖项仅支持 st3st3_linuxst3_windows_x64 或其任意组合。对于具有本机代码的内容,您可以通过多个文件夹指定多个不同的版本,尽管当 dependency 包含纯 Python 代码时通常使用 all,无论 Sublime 版本如何,OS 或体系结构。

在这个例子中,如果我们假设 prefix 文件夹被命名为 all 因为 my_dependency 是纯 Python,那么安装此依赖项的结果将是Packages/my_dependency/all 将被添加到 sys.path,这意味着如果您 import my_dependency 您将从该文件夹中获取代码。

在开发过程中(或者如果您不想通过包控制分发依赖项),您可以在包的根目录中创建一个 .sublime-dependency 文件,如上所示。这应该是一个文本文件,单行包含一个 2 位数字(例如 0150)。这控制每个安装的依赖项将以什么顺序添加到 sys.path。如果您的依赖项没有其他依赖项,您通常会选择一个较小的数字,如果有则选择一个较高的值(以便在这些依赖项之后注入)。

一旦您在 Packages 文件夹中以正确的格式布置了初始依赖项,您将使用命令面板中的命令 Package Control: Install Local Dependency,然后 select 名称你的依赖。

这会导致包控制 "install" 依赖项(即更新 0_package_control_loader 包)以使依赖项处于活动状态。此步骤通常会在第一次安装依赖项时由 Package Control 自动执行,因此如果您还手动分发依赖项,则需要提供执行此步骤的说明。