论坛相关教程:
- 2019.10 julia 开发包入门
- 2019.08 Julia开发module的工作流程
- 2019.02 求教:写好了一个Package,如何注册到官方使之可以直接Pkg.add?
- 2018.08 如何轻松地编写一个Julia应用包?
论坛相关讨论:
- 2022.06 学习制作完整的julia包的现代方法?
- 2022.05 Julia 开发包的基本流程
推荐阅读:
概要
第一部分是些比较基础的知识,主要介绍仓库常见文件,包括 Project.toml 和 Manifest.toml 等,并用 ]generate 命令演示如何新建一个包。
第二部分介绍远程开发,包括 PkgTemplates.jl 的使用,测试部署和包注册等,考虑篇幅问题,拆分到下个帖子。
项目开发通常用 Git 工具,所以这里假定已经有了一定的 Git 基础。如果不熟悉 Git,可以参考 廖雪峰的 Git 教程。
注:如果有表述不严谨的地方,欢迎指正~
文件结构及介绍
仓库文件可以分三部分:
- 开源项目常用文件,比如
README.md、LICENSE等 - 项目代码,比如
src,docs和test - 环境文件:
Project.toml和Manifest.toml
其中环境文件是讨论重点,下边以 QRCoders.jl 和 QRDecoders.jl 作为例子。
打开一个 Julia 包,查看文件结构,比如
# tree QRCoders.jl/ -L 2
QRCoders.jl/
├── docs
│ ├── make.jl
│ ├── Manifest.toml
│ ├── Project.toml
│ └── src
├── LICENSE
├── Manifest.toml
├── Project.toml
├── README.md
├── src
│ ├── QRCoders.jl
│ └── tables.jl
└── test
├── runtests.jl
└── tst_overall.jl
最外层是这几个文件(夹):
| 文件(夹) | 作用 |
|---|---|
LICENSE |
开源协议 |
README.md |
包的介绍说明 |
docs |
包的使用文档,用于生成网页 |
src |
存放源代码的地方 |
test |
测试代码 |
Manifest.toml |
项目依赖的具体版本 |
Project.toml |
项目依赖的包 |
-
LICENSE和README.md一般开源仓库都有。LICENSE为开源协议,用得比较广泛的是 MIT LICENSE;README.md用于介绍项目信息,使用方法、贡献者等等,打开 GitHub 仓库,直接看到的页面就由README.md展示。仓库通常还有
.gitignore文件,用于忽略不需要跟踪的文件,比如测试过程产生的临时文件,或者使用 Jupyter-notebook 产生的.ipynb_checkpoints文件等。 -
src/,test/和docs是 Julia 包约定或规定要有的文件夹:src存放源代码,且要求必须有模块的同名文件;假设模块名为QRCoders,则必须存在文件src/QRCoders.jl;当我们使用using/import导入 Julia 包时,背后是在执行src/<模块名>.jl文件test可选,存放测试代码;test目录下需存放runtests.jl文件;当使用包管理模式执行test命令时,会自动执行test/runtests.jl的内容

docs可选,用于生成使用文档;通常配合Documenter.jl使用,并在docs目录下存放make.jl文件,用于生成网页- 关于测试
test和文档docs,我们在下篇单独展开介绍。
-
剩下两个文件
Manifest.toml和Project.toml,用于管理包依赖的,比较重要,接下来进行介绍。
环境文件
Project.toml 和 Manifest.toml 的几点说明和比较:
-
Pkg 模式下执行
instanitate,将在当前环境所在目录生成Manifest.toml和Project.toml,用于记录包的依赖信息
每次执行包的安装、删除、更新等操作时,Pkg 会自动更新这两个文件
-
模块主目录必须有
Project.toml文件,而Manifest.toml是可选的 -
Mainifest.toml可读性较差,通常只通过执行 Pkg 命令自动更改,不建议手动修改;而Project.toml内容要简洁很多,可以手动维护 -
Project.toml记录项目的基本信息,一般来说是 Pkg 和程序员共同控制的内容;Manifest.toml是包管理器 Pkg 基于Project.toml生成的内容(以及执行包操作触发的修改),其记录了执行这个项目所需要的全部依赖的信息 -
多数项目只提供
Project.toml,就足以指定环境和依赖了,但一些特殊情况,可能需要提供Manifest.toml,后边会介绍几个例子 -
文档
docs和测试test也可以设置环境,通过在相应目录添加Project.toml(和Manifest.toml)来设置 -
从可复现性的角度来说, 从高可复现至低可复现依次是:
- 两个文件均提供:常用于各种一次性项目
- 只提供
Project.toml:常用于工具箱开发 - 啥也没有:单纯的代码库,不能被
using调用
Project.toml
实践通常用 PkgTemplates 生成这些环境文件,而不需要自己手动编辑,但了解参数含义还是很有必要
以 QRCoders.jl 为例,查看 Project.toml 的参数:
name = "QRCoders"
uuid = "f42e9828-16f3-11ed-2883-9126170b272d"
authors = ["Jérémie Gillet <jie.gillet@gmail.com> and contributors"]
version = "1.0.1"
[deps]
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19"
[compat]
FileIO = "1"
ImageCore = "0.8, 0.9"
ImageIO = "0.4, 0.5, 0.6"
julia = "1.3"
[extras]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Test", "Random"]
文件包括了五个部分:
-
顶部是项目的基本信息
name:模块名称,Julia 通过识别name来确定当前环境名称;此外src/目录下必须有以name内容命名的.jl文件uuid全称 Universally Unique Identifier(通用唯一识别码),用于标识 Julia 包authors仓库作者信息version当前模块的版本号,使用 Semantic Versioning(语义化版本)
-
deps是当前项目依赖的包,使用pkg> add添加包或pkg> remove删除包时会相应更新 -
compat是项目依赖包的版本要求,需手动设置。按开发规范应为非自带包设置版本要求,并指定兼容的 Julia 版本 -
extras是额外的依赖,比如测试环境依赖的包。例如 QRCoders 在extras这栏添加了测试依赖的Test, Random,因而仓库test目录下可以省去Project.toml文件 -
targets似乎是按extras对应出现的,其他了解不多
通常,只有 compat 要手动指定依赖版本,其他的都是自动生成,或者在用包管理器时自动更新。
语义化版本
在注册包时,可能会遇到关于版本号的相关问题,这里简单介绍一下,内容摘自 短课笔记-8。
-
代码工程中很容易遇到的一个问题就是版本冲突,例如: A 与 B 都是项目中需要的包, 而 A 与 B 都依赖于一个共同的包 C, 这时候如果 A 仅支持
C == 0.4.0而 B 仅支持C == 1.0.0的话,那么就出现了版本冲突现象。 -
为了降低版本冲突的可能性,包管理器 Pkg 采用了语义化版本 Semantic Versioning。大概意思是说,所有版本分为四个主要类别: 主版本 major, 小版本 minor, 补丁版本 patch 以及构建版本 build。在 Julia 中大部分时候仅使用前三个版本。
-
版本格式:
主版本号.次版本号.修订号,版本号递增规则如下:
- 主版本号:当你做了不兼容的 API 修改
- 次版本号:当你做了向下兼容的功能性新增
- 修订号:当你做了向下兼容的问题修正
先行版本号及版本编译信息可以加到 主版本号.次版本号.修订号的后面,作为延伸。
基于 SemVer,我们可以做出类似于这样的假设:
如果 A 兼容
C == 1.0.0, 那么 A 应该也兼容C == 1.0.1和C == 1.1.0,但未必兼容C == 2.0.0。
为此,Project.toml 引入了 [compat] 块,例如:
FileIO = "1"代表仅兼容1.x.y版本的FileIOImageCore = "0.8, 0.9"代表仅兼容0.8.x和0.9.x- 版本号还支持形如
"≥ 1.2.3"或正则表达式等语法,参看 Version specifier format
此外,以 0 开始的版本通常代表在开发阶段,此时次版本不一定能向下兼容,比如 0.2 不一定能兼容 0.1。
Manifest.toml
Mainfest.toml 由一系列格式如下的片段构成,记录了项目详细的依赖信息
[[QRCoders]]
deps = ["FileIO", "ImageCore", "ImageIO"]
git-tree-sha1 = "a7a56a2550dbea3b603b357adf81710385d1d3c7"
uuid = "f42e9828-16f3-11ed-2883-9126170b272d"
version = "1.0.1"
其中 git-tree-sha1 是标记当前 commit 的哈希值,其他参数与前边讨论类似。
多数时候,我们并不需要添加 Manifest.toml 来记录环境。但一些情况,比如依赖的模块未注册,或者依赖模块是在本地开发,就得提供 Manifest.toml 来保证环境的可复现。比较常见的两种情况:
-
安装未注册的
Github包,比如pkg> add https://github.com/JuliaImages/QRCoders.jl在
Manifest.toml中会出现类似的片段[[QRCoders]] deps = ["FileIO", "ImageCore", "ImageIO"] git-tree-sha1 = "a7a56a2550dbea3b603b357adf81710385d1d3c7" repo-rev = "master" repo-url = "https://github.com/JuliaImages/QRCoders.jl" uuid = "f42e9828-16f3-11ed-2883-9126170b272d" version = "1.0.1"其中
repo-rev记录模块所在分支,repo-url记录模块所在地址。当然不只是 GitHub 仓库,本地仓库或其他 Git 仓库也能用这种方式添加 -
安装本地开发的包
# dev /path/to/your/package pkg> dev /home/rex/ospp/QRDecoders.jlManifest.toml对应出现如下片段[[QRDecoders]] deps = ["FileIO", "ImageIO", "ImageTransformations", "QRCoders"] path = "/home/rex/ospp/QRDecoders.jl/" uuid = "d4999880-6331-4276-8b7d-7ee1f305cff8" version = "0.1.0"
以上两种情况均需要使用 Manifest.toml 来完整记录环境。
小结
本节介绍了 Julia 包的文件结构,包括
-
GitHub 仓库常用的
README.md项目介绍LICENSE开源协议.gitignore忽略无关文件
-
Julia 代码文件:
src存放源代码test存放测试代码docs存放文档
-
Julia 环境文件
Project.toml项目依赖Manifest.toml项目详细依赖
对多数包来说,只使用 Project.toml 记录环境,而将 Manifest.toml 放在 .gitignore 中忽略。但当使用未注册的包时,或有更严格的复现需求时,就得提供 Manifest.toml 来记录环境。
此外,如果 Manifest.toml 已被 .gitignore 设置了忽略,可以通过 git add -f Manifest.toml 强制添加;相反的,如果 Manifest.toml 已被添加,可以通过 git rm --cached Manifest.toml 来取消添加。
简易教程
下边演示如何用包管理命令创建模板,借此熟悉前边介绍的知识。
用
PkgTemplates.jl能一键生成环境,而不必逐个构建。但理解这些文件结构能帮助更灵活地使用模板。
Demo
作为示例,新建目录 TestPackage,后续操作都在这里进行。
-
创建 git 环境
cd TestPackage # 进入目录 git init # 初始化 git以下为 Git 仓库常用文件(可选)
# 取消对某些文件的追踪 touch .gitignore # 仓库介绍 touch README.md # 开源协议 touch LICENSE # 粘贴合适的协议 -
以当前目录作为开发环境启动 Julia
# julia --project=<环境位置> julia --project=.等价地,可以先启动 Julia ,然后在包管理模式中使用
activate切换开发环境# 先启动 Julia ] # 输入 ] 进入包管理模式 activate . # 启用当前目录作为开发环境 -
包管理模式下,使用
generate + 模块名创建模板pkg> generate TestPackage如下图,操作后
Project.toml和src文件夹已创建

打开文件并查看,
Project.toml已自动生成了name和uuid等字段,其中authors由当前环境的Git信息生成name = "TestPackage" uuid = "83012822-a8b3-402d-b3e2-a8f809b7e3a3" authors = ["rex <1073853456@qq.com>"] version = "0.1.0"src代码内容module TestPackage greet() = print("Hello World!") end # module -
我们需要将
TestPackage文件夹中的文件挪到模块主目录mv TestPackage/* ./ rmdir TestPackage -
包管理模式下,使用
instantiate命令初始化pkg> instantiate该命令会根据
Project.toml文件生成Manifest.toml文件;当Project.toml文件不存在时,会生成空白的Project.toml和Manifest.toml
-
提交更改
git add --all git commit -m "initial commit"
大功告成,一个简单的包已经创建好了!
包管理命令
包管理模式常用命令:
| 命令 | 说明 |
|---|---|
add <模块名> |
安装模块;若模块已安装,则根据环境文件执行更新操作 |
add <链接> |
通过链接(地址)安装模块 |
rm/remove <模块名> |
删除模块依赖 |
instantiate |
实例化,也即更新 Project.toml, Manifest.toml 文件 |
test |
执行 test/ 目录下的 runtests.jl |
st/status |
查看依赖信息 |
activate <环境目录> |
将开发环境切换到指定目录,不加参数则切换到默认环境 |
generate <模块名称> |
生成模板 |
update |
更新环境依赖中的模块 |
dev <模块目录> |
本地开发,添加本地模块作为依赖 |
build |
构建依赖 |
precompile |
环境预编译 |
简单来说,如果仓库没有 Project.toml 文件,则执行 instatiate 命令初始化。之后在执行 add, rm, update, dev 等操作时,仓库会自动更新 Project.toml 和 Manifest.toml 文件。
注:一些环境错误,比如执行
test提示的文件错误,可能是Manifest.toml的问题,如该文件非必要可将其删除再运行
其他知识点
-
模块定义
module ExamplePackage export greet, notdefine greet() = print("Hello World!") end其中
export导出符号。当使用using <模块名>调包时,这些符号会被导入到当前环境。特别注意,export只是导入符号而不做检查,哪怕符号对应的变量没有定义也能正常进行,且能够触发代码补全功能

-
导入模块除了
using,也可以用import,区别参看之前回答的一个帖子。 -
Revise.jl- Julia 提供了
Revise包,可以实现模块热更新,即修改模块后无需使用using重新导入,也会自动更新模块变化。 - 需注意的是,
Revise只影响在它之后导入的模块,因此建议将其添加到startup.jl,每次启动 Julia 都会先执行这个脚本。 startup.jl添加方式:直接编辑~/.julia/config/startup.jl文件或者使用命令行echo "using Revise" >> ~/.julia/config/startup.jl
- Julia 提供了
-
项目代码通常不止一个文件,当
src下写了多个文件时,可以用include命令将它们导入到主文件中## 主文件内 module ExamplePackage greet() = print("Hello World!") include("temp.jl") # 导入文件 endinclude命令不仅适用于模块开发,普通场景的代码导入也是有效的 -
在模块
module内,引用内部模块可以略写模块名,比如module Outer module Inner hi() = print("hi") end # 引用子模块 using .Inner # 等价效果 using Outer.Inner end -
变量及所在模块
- 使用
@which可以查看变量所在模块 - 在 REPL 中定义的变量和函数属于全局环境
Main

- 使用
-
查看源码技巧:
- 在 Jupyter 下,用
@which能获取函数源码的 GitHub 链接 - 用
@edit func(para..)可以查看func关于该派发的源码 @edit默认使用vim作为编辑器,但可以修改为 vscode
这段代码建议写在ENV["EDITOR"] = "code"startup.jl中,每次启动 Julia 都会先执行
- 在 Jupyter 下,用
模块开发还有些其他内容,比如 __init__ 函数等,后续接触再进行补充。
以上是模块开发的第一部分知识,PkgTemplates.jl 的使用及远程开发参阅下篇。


