程序包概述
程序包(Paclet)是 Wolfram 的功能单元,能够以打包的方式被发现、安装、更新和无缝集成到 Wolfram 环境中. 程序包可以包含多种类型的内容,包括 Wolfram 语言文件、LibraryLink 库、前端资源(如调色板和样式表)、用于 J/Link 的 Java 库、文档笔记本等. 制作程序包的基本元素是 PacletInfo.wl 文件,这是一个小型、简单的元数据文件,描述程序包、要求及其扩展 Wolfram 环境的方式.
在使用时,程序包由一个包含 PacletInfo.wl 文件的目录以及一个或多个内容文件和子目录组成. 程序包可以被压缩成单独的 .paclet 文件,这是一种方便分发和安装的形式,但是程序包被打包成一个 .paclet 文件并不是必须的.
PacletInfo.wl 文件是一个 Wolfram 语言格式的简单元数据文件,描述程序包和它所提供的资源. 正是 PacletInfo.wl 文件的存在使目录或文件集合成为一个程序包. 如果愿意,您也可以将此文件命名为 PacletInfo.m.
这是一个名为 FileUtilities 的非常简单的新程序包的 PacletInfo.wl 文件. 这个程序包只是一个单独的执行文件 FileUtilities.wl,它定义了包含一些有用函数的 "FileUtilities`" 语境. 程序包的用户将使用 Needs[“FileUtilities`”]以通常的方式在其会话中加载该程序包.
PacletObject[<|
"Name" "FileUtilities",
"Version" "1.0",
"Extensions" {
{"Kernel", "Root" "Kernel", "Context" "FileUtilities`"}
}
|>]
"Name" 和 "Version" 字段是所有程序包中所必需的,并且是仅有的必填字段(尽管只有 "Name" 和 "Version" 的数据包不是很有用). "Extensions" 字段描述您的程序包所提供资源的类型,即您的程序包扩展系统的方式. 程序包包含 .wl 或 .m 文件,定义使用 Get 或 Needs 加载的程序包,应指定 "Kernel" 扩展名,并列出程序包提供的语境. 在前面的例子中,Wolfram 系统知道当用户执行 Needs["FileUtilities`"]时,这个 程序包提供了对应于那个语境的 .wl 文件.
每个程序包都有一个版本号,尽管程序包系统支持并行安装一个程序包的多个版本,但在默认情况下,它使用版本号最高的那个. 如果您要发布程序包以供他人使用,请确保每次发布时都增加版本号,否则您的新版本将不会认为与旧版本有所不同.
程序包功能的本质是通过 PacletInfo.wl 文件声明您的程序包提供的内容,当安装程序包时,系统会自动找到这些类型的内容. 除了安装程序包,用户通常不需要做任何事情. 程序包将自己“编织”到系统中,以便找到它们的资源. 调色板显示在菜单中,文档显示在帮助查看器中,语境可以通过 Needs 加载等,诸如此类.
您或许知道 Wolfram 引擎根据 $Path 的值查找多种类型的资源. 程序包系统有一个更强大的查找机制,不再需要手动修改 $Path. 当 Wolfram 引擎查找资源时,仍然使用 $Path,但总是首先咨询程序包系统,只有在没有程序包提供资源时才使用 $Path.
从版本 12.1 开始的更改:
版本 12.1 对 PacletInfo.wl 文件格式进行了重大更改. 以前,表达式的头部是 Paclet,而不是 PacletObject,内容没有包装在 Association 中,规则的左侧通常是符号(尽管可能是字符串). 旧格式的文件仍受支持,但不建议在新程序包开发中使用. 这是以前的 PacletInfo.wl 文件在旧格式中的样子:
Paclet[
Name "FileUtilities",
Version "1.0",
Extensions {
{"Kernel", Root "Kernel", Context "FileUtilities`"}
}
]
PacletInfo.wl 文件支持多个字段. 字段始终采用 "FieldName" -> value 的形式,其中 value 通常是字符串或简单元素的列表,如规则、列表和字符串等. 不能将任意 Wolfram 语言表达式放入字段值; 这些文件在读入内核时不会被运算,因此您不能将运算为所需值的内容放入其中,文件必须包含字面值.
字段 "Name" 是必需的. 这是程序包的名称. 它用于 PacletInstall 和 PacletFind 等函数. 可以在程序包名称中使用您喜欢的任何字符,但不建议使用连字符 (-),因此如果需要,通常使用单个下划线 (_) 作为单词分隔符. 程序包名称除了作为唯一且有意义的标识符之外没有任何意义. 例如,它不需要与您的程序包提供的语境或文件的名称相匹配,尽管这种情况很普遍.
字段 "Version" 是必需的. 该字段是字符串,而不是数字(即"1.0",而不是 1.0). 版本最多可以有五个数字块 ("1.0.0.0.1234"),版本比较完全符合您的预期("1.10"大于"1.9"). 不能包含诸如 "a" 之类的非数字字符,尽管这一功能将来可能会提供.
使用该字段指定您的程序包兼容的 Wolfram 语言版本. 如果忽略此字段,则假定与所有版本兼容. 如果您的程序包的 "WolframVersion" 规范与当前运行的实例不匹配,您的程序包将在该会话中不可见. 您可以在本规范中使用 *、+、- 和逗号字符,如下所示:
"13+" | 从 13.0.0 开始的所有版本 |
"13.1.0-" | 版本 13.1.0 及更早版本 |
"13.*" | 仅版本 13(以及任何次要版本,如 13.1 等) |
"13" | 仅版本 13.xx(与“13.*”相同) |
"12.3,13.0" | 仅版本 12.3 和 13.0 |
使用此字段来指定您的程序包与哪些操作系统兼容. 如果忽略此字段,则假定它与所有系统兼容. 如果您的程序包的 "SystemID" 规范与当前运行的实例不匹配,您的程序包将在该会话中不可见. 该值可以是像 "MacOSX-x86-64" 这样的单个字符串,也可以是像 {"Windows-x86-64", "Linux-x86-64"} 这样的列表.
带有 "Kernel" 扩展名的程序包可以指定如何加载语境. 默认为 Manual,这意味着用户需要在您的语境中调用 Get 或 Needs 来加载它们,但您可以指定 "Startup" 以在每次内核启动时加载您的语境,或 Automatic 以在第一次使用您导出的符号之一时指定应自动加载的语境. 这个系统的细节将在后面介绍. 大多数开发人员会忽略这一字段,使用默认的 Manual 加载行为.
如果指定 Updating->Automatic,您的程序包将在第一次用于会话时自动更新. 如果没有可用的更新,则此检查会非常快且不涉及网络. 请注意,自动更新仅适用于具有 "Kernel" 扩展名的程序包,因为执行更新的时刻就是程序包所提供的语境首次被要求加载的时刻. 对于具有其他扩展名类型的程序包,如 "FrontEnd" 或 "Documentation",在使用数据包的资源之前不执行更新检查.
"Root" 字段指定程序包内容相对于程序包的位置. 程序包的位置被定义为 PacletInfo.wl 文件所在的目录. "Root" 的默认值为 ".",这意味着应该在包含 PacletInfo.wl 文件的同一目录中查找程序包的内容,但如果您喜欢不同的文件组织,比如 PacletInfo.wl 文件所在的位置位于主内容目录之外,可以使用 "Root" 字段指向该内容目录. 大多数开发人员不会使用此字段. 不应将此处讨论且很少指定的程序包 "Root" 与单个扩展的 "Root" 字段混淆,后者很重要且常用,将在后面讨论.
"Qualifier" 字段用于程序包应分成多个程序包的复杂情况,通常用于不同的操作系统. 例如,您可能有一个包含大型可执行文件或大型 LibraryLink 库的程序包,并且每个 SystemID 都有一个不同的程序包. 您可以制作一个包含每个 SystemID 的子目录的程序包,但这可能会非常大,因为每个系统都有每个二进制文件的副本. 您当然不希望用户必须为他们不使用的操作系统下载数十兆字节的库. 相反,您可以将您的程序包根据 SystemID 拆分. 后面将描述此场景的详细信息.
程序包系统本质上是声明性的,您通过 PacletInfo.wl 文件声明您的程序包提供了哪些元素,程序包系统确保可以通过正常的系统操作找到这些元素. 从某种意义上说,程序包是被动的; 您不需要将程序包“加载”到您的会话中. 您只需安装一次,然后它就会将自己编织到系统中,以便可以自动找到它提供的资源.
- paclet root 是相对于程序包位置的路径,它指定程序包主要内容所在的位置. 这是通过 PacletInfo.wl 顶层的 "Root" 字段指定的. 默认为“.”,表示内容位于同一目录中,但如果您想在 PacletInfo.wl 文件下创建另一级目录层次结构来保存内容,您可以将其指定为 "Root". 大多数开发人员不会使用它.
- extension root 是该特定扩展类型的内容在程序包内的位置. 这由扩展中的 "Root" 字段指定. 大多数扩展都有一个默认的根,因此您不必明确指定 "Root" 字段,只要使用通常的程序包布局约定即可. 例如,文档的默认根目录是 "Documentation",大多数程序包作者将使用文档子目录来保存文档笔记本. 一个例外是 "Kernel" 扩展. 按照惯例,程序包应该在内核子目录中包含它们的 .wl 和 .m 文件. 然而,"Kernel" 不是 "Kernel" 扩展的默认根(出于与旧的非程序包样式布局向后兼容的复杂原因). 这意味着大多数开发人员将需要在他们的 "Kernel" 扩展中专门包含 "Root" -> "Kernel".
- resource 路径组件通常只是文件名. 例如,对于内核子目录中的 .wl 文件,它只是从上下文派生的预期文件名(例如,"FileUtilities`" -> FileUtilities.wl). 如果需要,某些扩展允许您为实际文件指定替代的相对路径和/或文件名.
如前所述,您的程序包可以具有指定其兼容性的顶级 "WolframVersion" 和 "SystemID" 字段. 这两个字段也可以出现在扩展中,以控制哪些扩展在特定系统中可见. 例如,如果根据运行的 SystemID 有不同版本的 .wl 文件,则可以有多个 "Kernel" 扩展,这些扩展具有不同的 "SystemID" 字段和指向每个系统的不同子目录的不同 "Root" 字段.
内核
PacletObject[<|
"Name" "MyPaclet",
"Version" "0.0.1",
"Extensions" {
{"Kernel", "Root" "Kernel", "Context" {"MyPaclet`"}},
(* ... other extensions *)
}
|>]
将程序包的 .wl 和 .m 文件放在 Kernel 子目录中是一个有意的约定,但并不是必需. 不幸的是,"Kernel" 不是 "Kernel" 扩展的默认 "Root",因此如果您使用 Kernel 子目录,则需要像上面所示的一样,在扩展规范中包含 "Root"->"Kernel". 如果您希望 .wl 文件位于 PacletInfo.wl 文件旁边,而不是在 Kernel 子目录中,则需要在您的内核扩展中使用 "." 的 "Root" 规范.
子语境
如果您的主 .wl 文件定义 "MyContext`",并且有另一个 .wl file ,SomeSubcontext.wl 定义子语境 "MyContext`SomeSubcontext`",那么您将 SomeSubcontext.wl 文件放在布局中的哪个位置以便自动找到它?换句话说,SomeSubcontext.wl 文件应该在哪里以便 "MyContext`SomeSubcontext`"的查找将解析到该文件?答案是它可以位于 MyContext.wl 文件旁边. 程序包系统支持将"MyContext`SomeSubcontext`" 解析为内核目录中的 SomeSubcontext.wl 文件. 您可以考虑通过在 "Kernel" 扩展中声明 "MyContext`" 来将 "MyContext`" 解析为您的内核目录,然后将 SomeSubcontext 部分解析为 SomeSubcontext.wl 文件. 如果您有更深层嵌套的子语境,例如 "MyContext`SomeSubcontext`AnotherSubcontext`",则 AnotherSubcontext.m 文件需要位于 Kernel/SomeSubcontext 目录中. 子语境层次结构需要对应一个目录层次结构,只是第一部分 "MyContext`" 是 "free",映射到内核目录本身.
init.m 文件
许多旧式应用程序使用特殊的内核特性来定位与语境相对应的文件:如果语境映射到 $Path 上的文件位置,但在该位置找不到适当命名的 .wl 或 .m 文件,内核会查找 一个包含 init.m 文件的内核子目录并读取该文件. init.m 文件通常只是说明类似 Get[“MyContext`MyContext`”] 的内容。 双语境将正确映射到 MyContext 父目录中的 MyContext.m 文件. 这实际上只是允许语境 "MyContext`" 映射到 $Path 上 MyContext 目录中的 MyContext.m 文件的一个技巧. 程序包支持这种类型的布局,但完全没有必要并且强烈建议不要这样做. 在前面的 "Kernel" 扩展示例中,没有必要用“双重语境”来欺骗系统. 语境 "MyContext`" 直接映射到 Kernel/MyContext.m 文件,不需要 init.m 文件.
参考文档
程序包通常提供一些 Wolfram 风格的参考文档,如果你有这样的文档,需要声明一个 "Documentation" 扩展. "Documentation" 扩展支持标准的 "Root"、"SystemID" 和 "WolframVersion" 字段,以及 "Language" 字段. 大多数程序包作者不需要这些字段,而是依赖于传统的文档布局:
PacletObject[<|
"Name" "MyPaclet",
"Version" "0.0.1",
"Extensions" {
{"Documentation"},
(* ... other extensions *)
}
|>]
FrontEnd
PacletObject[<|
"Name" "MyPaclet",
"Version" "0.0.1",
"Extensions" {
{"FrontEnd"},
(* ... other extensions *)
}
|>]
路径
PacletObject[<|
"Name" "MyPaclet",
"Version" "0.0.1",
"Extensions" {
{"Path"},
(* ... other extensions *)
}
|>]
对于任何需要文件路径的函数(如 Get、OpenRead、FindFile、Import 等),您现在可以传入 "MyPaclet/SomeFile.txt". 路径的第一部分必须与程序包名称匹配. 如果 SomeFile.txt 文件位于名为 Stuff 的子目录中,则应指定 "MyPaclet/Stuff/SomeFile.txt".
LibraryLink
PacletObject[<|
"Name" "MyPaclet",
"Version" "0.0.1",
"Extensions" {
{"LibraryLink"},
(* ... other extensions *)
}
|>]
JLink
PacletObject[<|
"Name" "MyPaclet",
"Version" "0.0.1",
"Extensions" {
{"JLink"},
(* ... other extensions *)
}
|>]
使用这种布局,J/Link 将能够自动在您的 jar 文件中查找类. 无需调用 J/Link 的 AddToClassPath 函数.
资源
到目前为止所描述的所有扩展类型都是自动“编织”到系统中的. 您不必进行特殊的 paclet-aware 调用(例如 Needs)来查找程序包提供的 .wl 文件. 但是您可能希望提供的很多类型的内容在 Wolfram 系统中没有内置处理. "Asset" 扩展是一种指定任意类型资源的通用方法,可以使用 "AssetLocation" 选择器按名称找到这些资源.
假设您将使用 StartProcess 来启动您的程序包中的一个可执行文件. 最好的方法是使用 "Asset" 扩展名为该可执行文件分配一个名称,然后通过该名称查找其路径. 通过这种方式,您可以避免程序包代码中的任何硬编码路径. 只有在 PacletInfo.wl 文件中,资源名称才会映射到实际的文件位置,而这是唯一需要更改以修改位置甚至文件名本身的位置. 它还可以轻松提供多个版本的可执行文件,每个版本对应不同的 SystemID or WolframVersion.
PacletObject[<|
"Name" "MyPaclet",
"Version" "0.0.1",
"Extensions" {
{"Asset", "Root" "Mac", "SystemID" "MacOSX-x86-64", "Assets" {{"my_exe", "MyExe.app"}}},
{"Asset", "Root" "Windows", "SystemID" "Windows-x86-64", "Assets" {{"my_exe", "MyExe.exe"}}},
{"Asset", "Root" "Linux", "SystemID" "Linux-x86-64", "Assets" {{"my_exe", "MyExe"}}},
(* ... other extensions *)
}
|>]
自定义扩展
您还可以创建自定义扩展. 这样做的一个原因是实现“插件”类型的系统. 例如,假设您正在编写一个用 Wolfram 语言编写的数字滤波器框架. 您的程序包提供了使用这些滤波器的代码,也许它提供了自己的一些滤波器,但是您希望其他程序包能够提供可以在运行时自动找到的滤波器. 您可以创建一个名为"DigitalFilters" 的自定义扩展,并且提供此类滤波器的程序包将使用它:
PacletObject[<|
"Name" "MyPaclet",
"Version" "0.0.1",
"Extensions" {
{"DigitalFilters", "Root" "Filters"},
(* ... other extensions *)
}
|>]
PacletManager`PacletResources 返回对列表:{{_PacletObject, {“/full/path/to/extension/root”}}, ...}. 然后,您可以提取所有扩展根目录的路径,枚举并加载这些目录中的所有 .wl 文件(或任何其他文件).
该设计目前仍处于试验阶段,所以 PacletResources 位于 "PacletManager`" 语境而不是 "System`" 语境. 用于此目的的功能最终将成为程序包系统主界面的一部分,但此时您可以使用 PacletManager`PacletResources (将来会支持此函数).
PacletObject[<|
"Name" "MyPaclet",
"Version" "2.0.1.0.16382",
"SystemID" {"MacOSX-x86-64", "Windows-x86-64"}, (* this paclet will only be visible to Mac and Windows users *)
"WolframVersion" "12.1+", (* this paclet will only be visible to Wolfram Engine version 12 and later. *)
"Creator" "tgayley@wolfram.com",
"Description" "A demonstration paclet with no actual content.",
"Extensions" {
(* This extension provides versions of the .m files appropriate only for 12.1 *)
{"Kernel", "Root" "Kernel121", "WolframVersion" "12.1", "Context" {"MyPackage`", "MyOtherPackage`"}},
(* This extension provides versions of the .m files appropriate only for 12.2 and later *)
{"Kernel", "Root" "Kernel", "WolframVersion" "12.2+", "Context" {"MyPackage`", "MyOtherPackage`"}},
{"LibraryLink"},
{"Documentation"},
{"FrontEnd"},
{"Asset", "Root" -> "Assets", "Assets" {{"sun", "images/sun.jpg"}, {"moon", "images/moon.jpg"}}}
}
|>]
PacletInstall 是安装和更新程序包的函数. 这里,该函数安装一个 Wolfram 程序包,偶尔会在 Wolfram 程序包服务器上更新:
请注意,结果是一个 PacletObject 表达式,它是程序包的 Wolfram 语言表示. 如果您已经安装了 Benchmarking 程序包,但在服务器上有可用的更新,则该命令会下载并安装该更新. 如果您的机器上已经存在一个程序包,并且没有已知可用的更新,则 PacletInstall 会在不花费显著时间的情况下返回,因此如果您写的代码要用到另一个程序包,可以在该程序包上调用 PacletInstall,而不必担心在您的系统或您的用户的系统上做一些不必要的花费.
您可以手动将程序包目录复制到存储库中并删除它们,尽管这并不是常规操作. 更改在下次内核重新启动之前不会被检测到,但是您可以调用 PacletDataRebuild[] 来强制程序包系统立即检测您手动进行的任何更改.
从 .paclet 文件或 URL 安装
通过 PacletInstall["PacletName"] 安装和更新程序包非常方便,但它只适用于部署在程序包服务器上的程序包. 虽然建立您自己的程序包服务器(稍后描述)非常容易,但您也可以将程序包分发为 .paclet 文件,以便从文件系统或网络(包括 Wolfram Cloud)安装. ".paclet" 文件是程序包目录的打包版本,由 CreatePacletArchive 函数(稍后描述)生成. ".paclet" 文件是部署到程序包服务器上,但您也可以直接安装它:
您也可以手动将程序包目录复制到 $UserBasePacletsDirectory/Repository. 在这种情况下,程序包只有在下次内核重启时才会被检测到,但您可以调用 PacletDataRebuild[]来强制程序包系统立即检测更改.
PacletSites
PacletSites 是返回系统已知的程序包站点列表的函数. 在撰写本文时,对于大多数用户来说,Wolfram 公共程序包服务器只是一个站点:
您可以使用 PacletSiteRegister 和 PacletSiteUnregister 来管理此已知程序包站点列表. 如果您尝试删除 Wolfram 主站点,它将在下次会话开始时恢复. 使用 PacletSiteRegister 和 PacletSiteUnregister 的唯一原因是,您希望您的系统了解其他的程序包服务器,例如由您的工作单位建立的那些服务器. 本文档稍后将讨论如何创建这样的程序包站点.
PacletSiteUpdate
当您调用 PacletInstall 来获取或更新一个程序包时,它如何知道服务器上是否有可用的更新? 用户通常认为 PacletInstall 每次都会检查服务器,因此是一项昂贵的操作,但事实并非如此. PacletInstall 仅检查有关服务器上的程序包的本地缓存数据. 通过调用 PacletSiteUpdate 更新本地缓存数据:
PacletSiteUpdate 在不同时间在内部调用,至少每隔几天调用一次,而且通常比这更频繁. 如果您想确保您拥有有关可用程序包的最新数据,可以自己调用 PacletSiteUpdate. 如果您知道程序包是最近才部署到服务器,可以调用 PacletSiteUpdate 以确保您的系统知道该新版本.
如前所述,对 PacletInstall 的调用通常不会触发对 PacletSiteUpdate 的调用,但如果所请求的程序包未安装在本地并且未在服务器数据缓存中找到的情况除外. 在这种情况下,PacletSiteUpdate 会在内部调用以查看程序包是否可用,而不是仅仅基于可能过时的缓存信息而失败.
程序包的设计理念就是将自己编织到系统中,因此几乎不需要直接对它们进行操作. 例如,Mathematica 13 布局中有数百个程序包,它们是系统的静默组件; 您无需了解任何程序包系统功能即可使用它们. 但有些时候,检查系统上的程序包并对其执行操作很有用,这对于自己创建程序包的开发人员来说通常是必要的.
PacletFind
PacletFind 是查找已安装在系统上的程序包的函数. 它返回 PacletObject 表达式的一个列表. PacletObject 是 Wolfram 语言中程序包的表示,可用于查询各种程序包属性. PacletFind 采用属性的 Association 来控制它返回的程序包.
程序包系统只使用版本号最高的程序包(并且这个列表是按顺序排列的),但 PacletFind 会返回所有与当前系统兼容的程序包. 如果您好奇为什么会有三个不同的版本,那是因为一个在 Mathematica 13 布局中,另外两个是在本地存储库中,在旧版本的 Mathematica 中作为更新下载. 这些程序包出现在输出中是因为它们仍然与 Mathematica 13 兼容,但将只使用版本 13.0.3,因为它具有最高的版本号.
默认情况下,PacletFind 只返回与当前 SystemID 和 Wolfram 引擎版本兼容的程序包,但您可以使用 "SystemID"->All 和 "WolframVersion"->All 选项来显示不兼容的程序包.
PacletFindRemote
PacletFindRemote 是查找程序包服务器上可用程序包的函数.
这表明有多个可用的 NeuralNetworks 版本是兼容的,但如果使用 PacletInstall["NeuralNetworks"] ,则这些版本都不会被下载,因为它们的版本都低于已安装的版本. 用户一般很少会调用 PacletFindRemote,但是程序包开发人员可以使用它来了解服务器上可用的内容,并确定给定程序包是否有可用的更新.
获取有关特定程序包的信息有几种方法. 在大多数情况下,第一步是获取 PacletObject 表达式,它代表您想了解的程序包. 获取 PacletObject 表达式的一种方法是使用 PacletFind 函数:
PacletFind 返回一个按顺序排列的列表,因此首先列出的是系统正在使用的程序包,即版本号最高的程序包. 如果你只想要程序包的活动版本,可以使用 PacletObject["pacletname"]:
Information 函数是一种获取合理完整数据集的简单方法:
大多数关于程序包的信息提取是通过应用于 PacletObject 本身的属性提取语法完成的:
PacletTools 程序包是为了帮助用户创建程序包而提供的,您也可以手动轻松完成. 下面通过含有一个 .wl 文件的程序包的简单示例进行操作.
首先为您的程序包创建一个目录,例如 $UserDocumentsDirectory/MyPaclet. 在此目录中,创建一个包含以下内容的 PacletInfo.wl 文件. 可以使用笔记本前端或任何文本编辑器来创建:
PacletObject[<|
"Name" "MyPaclet",
"Version" "1.0",
"Extensions" {
{"Kernel", "Root" "Kernel", "Context" {"MyContext`"}}
}
|>]
请注意,目录的实际名称 (MyPaclet) 不重要. 它可以有任何名称,不一定与 PacletInfo.wl 文件中的名称匹配. 程序包名称来自 PacletInfo.wl,父目录同名只是一个通用约定.
创建 MyPaclet 的 Kernel 子目录. 这是保存 MyContext.wl 文件的目录,该文件包含您要提供的函数定义. 语境的名称 ("MyContext`") 通常会与程序包名称匹配,但并不是必须的.
BeginPackage["MyContext`"]
Squared
Begin["`Private`"]
Squared[x_] := x^2
End[]
EndPackage[]
现在您已经创建了一个程序包. 要将其分发给其他人,需要将其打包为 .paclet 文件(这实际上只是一个压缩文件,但它有一些特殊约定,因此请始终使用 CreatePacletArchive 而不是其他归档实用程序). CreatePacletArchive 在要打包的目录旁边生成一个 .paclet 文件:
请注意,CreatePacletArchive 创建一个文件名,它将程序包名称和版本号混合在一起. 程序包不需要这个精确的文件名,可以随意对它重命名(仍保留 .paclet 扩展名). 对程序包重要的是 PacletInfo.wl 文件中的内容.
PacletDirectoryLoad 和 PacletDirectoryUnload
上一节对程序包的开发和部署过程进行了演练. 如果您正在开发程序包中的代码,可能并不想将其打包到 .paclet 文件并安装来测试最新的更改. 这时就需要用到 PacletDirectoryLoad 函数. PacletDirectoryLoad 告诉程序包系统在非标准位置寻找程序包. 将在您添加的目录中搜索程序包,深度最多两层,这些程序包立即可供系统使用. 添加的目录仅适用于当前内核会话. 您可以将 PacletDirectoryLoad 类比为非程序包中的 PrependTo[$Path, "dir"].
开发程序包的典型工作流程是在标准程序包布局中创建文件,然后就地编辑它们. 可以调用 PacletDirectoryLoad["/my/development/dir"] 让程序包系统加载您的开发版本的程序包. 这在一次会话中只需执行一次. 现在您可以编辑您的 .wl 文件并重复调用 Get["MyContext`"] 以重新加载它们,获取最新的更改.
如果您在开发过程中修改了 PacletInfo.wl 文件,比如添加了一个新的扩展类型,您需要调用 PacletDataRebuild[] 让程序包系统知道它需要重建它的程序包数据缓存.
PacletDirectoryLoad 的一个便利功能是,如果给定目录中的程序包与安装在别处的程序包具有完全相同的版本号,则来自 PacletDirectoryLoad 的程序包将优先于其他程序包使用. 该规则可以表述为“PacletDirectoryLoad 胜出”. 这意味着,如果您正在开发一个 程序包,并且您已经在本地存储库中安装了该程序包的 1.0.0 版,则可以使用 PacletDirectoryLoad 指向您的开发版本的程序包,而无需在 PacletInfo.wl 文件中增加版本号. 每当您发布对程序包的任何类型的修改时,必须给它一个比任何先前版本都高的版本号,这一点是至关重要的. 但是在开发过程中,不必不断增加版本号是很方便的.
PacletDirectoryUnload 从程序包搜索路径中删除目录,这些目录中的任何程序包将立即与系统分离. 例如,Needs 将不再在这些程序包中找到语境,并且任何调色板或其他前端资源将从前端菜单中消失. 当然,对于已经从程序包加载的代码,PacletDirectoryUnload 不会将其从会话中清除.
PacletDataRebuild
当您在任何正常程序包系统函数(如 PacletInstall)之外更改程序包时,需要调用 PacletDataRebuild 函数. 如果您只使用内置的程序包系统函数,那么程序包系统会跟踪任何更改. 但如果您手动进行了更改,例如通过更改 PacletInfo.wl 文件,程序包系统则无法知晓所发生的变化,它的缓存可能已经过时. 因此,您需要运行 PacletDataRebuild[],这会导致程序包系统重新扫描系统中的所有 PacletInfo.wl 文件. 这只需要一两秒钟.
对 PacletDataRebuild 的作用加以了解,可以避免经常在不必要的情况下调用它,尽管这样做是安全的. 用户只有在手动更改 PacletInfo.wl 文件或将程序包目录写入写出 $UserBasePacletsDirectory/Repository 时才需要用到该函数.
创作和构建文档
创建和构建文档是程序包开发的复杂部分. Wolfram 产品附带的文档以特定格式创建,带有一组专用工具,然后由另一个专用工具处理成您在产品中看到的文件. 程序包作者希望能够创建外观和行为类似于内置文档的文档,PacletTools and DocumentationTools 程序包可以来协助完成此任务. 有关详细信息,请参阅这些程序包的文档.
在您自己的机器上或为您的公司或单位设置程序包服务器很容易. 服务器不需要任何逻辑,事实上,程序包“服务器”可以像网络上可见的目录一样简单,其中包含一些特殊文件. 程序包系统具有自动创建所需文件的工具.
比如您有一组由其他人构建或提供的 .paclet 文件,您希望公司的其他同事可以找到和安装它们. 您所需要的只是一个目录,网络上的其他人可以通过以下文件访问该目录:URL. 这个示例在主目录的 PacletServer 目录中设有一个 程序包“服务器”. 在此 PacletSite 目录中,必须首先创建一个 Paclets 子目录(它必须具有此名称),然后将所需的 .paclet 文件集合放入其中. 现在调用 PacletManager`BuildPacletSiteFiles 函数,指向 PacletServer 目录:
此函数检查所有 .paclet 文件,从中提取信息,并在 PacletSite 目录中创建两个文件:PacletSite.m 和 PacletSite.mz. 这些是您调用 PacletSiteUpdate 时客户端将读取的索引文件. 一旦创建了这两个文件,您的程序包站点就建成了. 您可以通过文件 URL 添加它.
建立一个基于网络服务器的程序包站点的过程非常相似. 您创建的目录结构相同,一个带有包含 .paclet 文件的 Paclets 子目录的父目录,然后在该父目录上调用 PacletManager`BuildPacletSiteFiles. 您只需让这个目录对您的网络服务器可见,并将父目录提供给同事,可能像下面这样:
当要向服务器添加新的程序包时,只需再次运行 BuildPacletSiteFiles. 如果程序包是对旧版本的更新,请在调用 BuildPacletSiteFiles 之前从 Paclets 目录中删除旧版本.