Julia 的版本管理和环境管理,通常可以分成两部分来看:

  • juliaup:Julia 版本管理
  • Pkg:项目环境和包管理

和 Python 中常见的 pyenv + venv + pip 组合(以及其他各种替代方案)相比,Julia 直接从语言角度就考虑,设计更加统一优雅:

  • Julia 版本推荐由 juliaup 管理;(如果只使用一个版本,也可以绕过 juliaup
  • 项目依赖和环境由 Julia 自带的 Pkg 标准库管理;
  • 环境本质上不是一套独立拷贝的运行时,只是两个文本文件,对应依赖声明与解析结果。

Julia 的版本和环境管理机制和 Rust 很相似,可以说是现代编程语言设计的最佳方案。

Julia 运行时和版本

Julia 运行时

Julia 运行时指的是使 Julia 程序能够执行的一组核心文件,通常包括:

  1. julia 可执行文件
  2. Julia 自带标准库(standard libraries)
  3. Julia 的系统镜像(system image)
  4. 动态库和底层运行时组件

第三方包并不直接属于 Julia 自带运行时,它们通常存放在 depot 中,由当前激活的环境决定是否可用。

Julia 运行时的抽象结构如下:

1
2
3
4
5
6
Julia Runtime

├─ julia executable
├─ standard libraries
├─ system image
└─ runtime libraries

在 Linux / macOS 中一个典型的 Julia 安装目录大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
julia-1.12.x/
├── bin/
│ └── julia

├── lib/
│ ├── julia/
│ │ ├── sys.so
│ │ ├── libjulia.so
│ │ └── ...
│ └── ...

├── share/
│ └── julia/
│ ├── base/
│ ├── stdlib/
│ └── base.cache

└── include/
└── julia/

说明:

目录 作用
bin/ Julia 可执行文件
share/julia/base/ Julia Base 源码
share/julia/stdlib/ 标准库源码
lib/julia/ 系统镜像和底层动态库
include/julia/ 嵌入 Julia 或开发扩展所需头文件

包查找规则

Julia 启动后,决定“能 using 到哪些包”的关键是两组路径:

  • LOAD_PATH:决定当前会在哪些环境里查找包
  • DEPOT_PATH:决定包缓存、注册表、预编译文件、共享环境等存放在哪里,通常是 ~/.julia 中的文件夹,如果不是通过 juliaup 安装的,那么还包括 julia 的安装目录

可以在 REPL 中直接查看:

1
2
LOAD_PATH
DEPOT_PATH

一个常见的 LOAD_PATH 形如:

1
2
3
["@",
"@v#.#",
"@stdlib"]

含义分别为:

  • @:当前激活的项目环境
  • @v#.#:当前 Julia 主次版本对应的默认全局环境,例如 ~/.julia/environments/v1.12
  • @stdlib:Julia 自带标准库

这个路径查找机制表明:

  • 如果当前项目环境中有某个包,Julia 会优先使用该环境中的依赖解析结果;
  • 如果当前项目没有,但是默认全局环境中有,也会成功加载;(这里的 fallback 机制和 Python 很不相同,Python 虚拟环境的 site-packages 通常不会使用 base 环境的 site-packages
  • 标准库始终可通过 @stdlib 找到。

depot

Julia 中非常关键的概念是 depot。默认情况下,用户级 depot 往往在 ~/.julia 中,也可以通过配置选项指定。

julia 运行时的安装目录和 depot 可以是分开的,也可以合并在一起,例如使用 juliaup 时,julia 运行时会安装 ~/.julia/juliaup 中。

depot 的典型结构如下

1
2
3
4
5
6
7
8
9
10
~/.julia/
├── artifacts/
├── compiled/
├── environments/
│ ├── v1.11/
│ └── v1.12/
├── packages/
├── registries/
├── scratchspaces/
└── logs/

说明:

目录 作用
registries/ 包注册表,例如 General
packages/ 包源码缓存
compiled/ 预编译缓存
artifacts/ 二进制依赖或数据工件
environments/ 共享环境,例如默认环境 @v1.12

这里和 Python 的设计很不一样:

  • Python 的虚拟环境通常有自己独立的 site-packages/,包含完整的第三方依赖的内容;
  • Julia 的项目环境通常只需要在项目文件夹下保存两个文本文件,分别是项目环境需要的依赖声明和锁定结果,真正的包源码、缓存、工件都是全局共享的,存储在 depot 中的。

所以 Julia 环境更像是“依赖视图”或“解析上下文”,而不是一整套独立复制的运行时。

由于 Julia 默认把绝大部分内容都存放在 ~/.julia,卸载时直接删除这个目录和安装目录即可。

Julia 版本管理

juliaup 是 Julia 官方推荐的版本管理工具,可以理解为 Julia 的多版本启动器和安装器。

它负责:

  • 安装不同版本的 Julia
  • 管理不同版本通道(channel),通常选择 releaselts 即可
  • 设置默认版本
  • 在不同目录下覆盖默认版本

可以把它理解为:

1
juliaup = Julia 版本下载 + 多版本切换 + 默认版本控制

安装 juliaup

官网推荐的安装方式:

1
2
3
4
5
# Linux / macOS
curl -fsSL https://install.julialang.org | sh

# Windows
winget install --name Julia --id 9NJNWW8PVKMN -e -s msstore

实际不推荐这两种安装方式,直接下载 juliaup 的 Github Release 的预编译版本。安装完成后会得到两个可执行文件:

  • julia
  • juliaup

注意:

  • juliaup 仓库 的 Release 的 julia 实际上并不是真正的 julia 可执行文件,而是 juliaup 提供的一个统一入口,最终会通过 juliaup 选择对应版本来启动。
  • julia 仓库 的 Release 不能直接下载使用,如果希望绕过 juliaup 直接下载 julia,也得去 Julia 官网 下载

基本使用

juliaup 主要使用 channel 概念:

  • release:当前稳定版
  • lts:长期支持版
  • 1.12:某个主次版本系列中的最新补丁版本

查看当前可用通道:

1
juliaup list

安装某个版本或通道:

1
2
3
juliaup add release
juliaup add lts
juliaup add 1.12

查看当前已安装版本:

1
juliaup status

设置默认版本:

1
juliaup default 1.12

更新已安装版本:

1
juliaup update

删除某个版本:

1
juliaup remove 1.11

可以在启动 julia 时指定 channel:

1
2
3
julia +release
julia +lts
julia +1.12

这里的 +1.12 表示临时使用 1.12 通道启动 Julia,而不改变全局默认版本。

这种做法适合临时测试不同版本兼容性,或者某些项目固定依赖特定的 Julia 版本。

juliaup 还支持目录级版本切换,与 Python 通过 .python-version 固定项目 Python 版本的思路不同,Juliaup 对版本的要求应该是记录 .julia/ 中的某个文件中,而且目前并不需要,因此略过。

小结:

  • juliaup 管的是 Julia 解释器版本;
  • Pkg 环境管的是项目依赖;

Julia 环境管理

Julia 环境本质

Julia 环境本质上是一组依赖信息,由两个文本文件负责描述:

  • Project.toml:记录项目本身信息和直接依赖,表明项目的依赖意图,类似 Python 的 pyproject.toml
  • Manifest.toml:记录完整依赖树和精确解析结果,是相当于确环境快照,类似 Python uv 的 uv.lock

一个简单的 Project.toml 可能形如:

1
2
3
4
5
6
7
8
9
10
11
12
name = "ExampleApp"
uuid = "12345678-abcd-efgh-ijkl-123456789abc"
version = "0.1.0"

[deps]
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

[compat]
julia = "1.12"
JSON = "0.21"
Plots = "1"

其中:

  • [deps] 记录直接依赖包及其 UUID;
  • [compat] 记录版本兼容要求;
  • julia = "1.12" 用于限制项目可用的 Julia 版本范围。

Manifest.toml 会记录完整依赖树、每个包的精确版本、来源和解析结果。(内容可能很长,因为一个直接依赖可能连带着几十个间接依赖)

通常会把这两个文件都纳入版本管理,或者至少把 Project.toml 纳入版本管理。如果希望别人能完全复现环境,应该同时保留这两个文件。

激活环境

在 Julia 中,进入某个项目目录后,可以直接激活当前目录环境:

1
2
using Pkg
Pkg.activate(".")

在包管理模式下也可以写成:

1
pkg> activate .

如果当前目录还没有 Project.toml,激活后可以进一步初始化或添加依赖。

对于日常项目开发,更常见的做法是在项目根目录直接启动 Julia:

1
julia --project=.

这表示使用当前目录作为项目环境。

也可以写成:

1
julia --project

此时会自动查找当前目录中的项目文件。

创建环境和添加依赖

在一个新目录中:

1
2
3
4
using Pkg
Pkg.activate(".")
Pkg.add("JSON")
Pkg.add("Plots")

执行后通常会发生这些事情:

  • 当前目录下生成 Project.toml
  • 生成或更新 Manifest.toml
  • 在 depot 中下载包源码、工件和预编译缓存

注意:

  • 包并不是安装到当前项目目录下面的某个 site-packages/
  • 当前目录主要保存环境描述文件;
  • 实际包内容通常共享存放在 ~/.julia/ 中。
  • 即使是标准库中自带的某个模块,如果使用 add 手动添加,也会记录在 Project.toml 中。

使用包管理模式

Julia REPL 中按 ] 可以进入 Pkg 模式,例如:

1
(@v1.12) pkg>

常见命令:

1
2
3
4
5
6
7
8
pkg> activate .
pkg> add JSON
pkg> add Plots
pkg> rm Plots
pkg> update
pkg> status
pkg> instantiate
pkg> precompile

说明:

命令 作用
activate 切换当前环境
add 添加依赖
rm 删除依赖
update 更新依赖
status 查看依赖状态
instantiate 根据环境文件恢复环境
precompile 手动预编译

环境导出和恢复

Julia 的环境导出和恢复比 Python 中的 requirements.txt 更结构化。

如果项目中已经有:

  • Project.toml
  • Manifest.toml

那么在其他机器或其它目录下恢复环境通常只需要:

1
2
3
using Pkg
Pkg.activate(".")
Pkg.instantiate()

或者在 Pkg 模式中:

1
2
pkg> activate .
pkg> instantiate

其中 instantiate 会根据 Manifest.toml 尽可能精确地恢复依赖;如果没有 Manifest.toml,则会根据 Project.toml 重新解析一个可用环境。

Julia 的项目环境在设计上通常可以跨平台共享:Project.toml 基本是跨平台的,Manifest.toml 也往往可以在不同平台上复用;但如果项目依赖平台相关的 artifact、系统库或构建步骤,实际恢复结果仍可能因平台不同而有差异。

共享环境

共享环境是指不依赖具体项目目录,可以被多个项目使用的环境。

为了便于与一般的环境区分,共享环境会在 REPL 显示名称时在前面加上 @

Julia 会给每个版本准备一个同名的默认环境,如果没有显式激活环境,就会进入默认环境,例如 @v1.12,对应的 Project.tomlManifest.toml 会保存在:

1
~/.julia/environments/v1.12

在这个环境里也可以添加包:

1
pkg> add IJulia

这会把包加到该版本 Julia 对应的默认环境中。
由于 Julia 项目环境存在 fallback 机制,默认环境中的包是全局可见的,在各个项目环境中都是可用的,但是通常不建议把依赖都加到默认环境里,因为不利于依赖管理,而且容易引入依赖冲突。

除了默认的全局环境 @v#.# 之外,Julia 也支持自定义共享环境,例如

1
2
using Pkg
Pkg.activate("myenv"; shared=true)

等价于在 pkg 模式下执行

1
pkg> activate --shared myenv

这会创建或进入一个名为 @myenv 的共享环境,对应的 Project.tomlManifest.toml 会保存在:

1
~/.julia/environments/myenv

共享环境适合放一些经常在 REPL 中使用的通用工具包,例如:

  • IJulia
  • Revise
  • BenchmarkTools

但是对于具体项目依赖,通常仍然更推荐使用项目本地环境。

临时环境

Julia 支持临时环境,适合一次性实验:

1
2
3
using Pkg
Pkg.activate(temp=true)
Pkg.add("DataFrames")

或者在 pkg 模式下:

1
pkg> activate --temp

此时会创建一个临时环境,Julia 退出后环境也会失效,但包缓存本身通常仍然留在 depot 中以便复用。

这里的临时环境和 Python uv 临时创建一个一次性的虚拟环境的用途类似。

Julia 包开发、本地包和注册表

包的基本结构和创建

一个标准的 Julia 包通常也是一个普通项目目录,只是它需要满足 Julia 包的组织约定。

一个典型结构如下:

1
2
3
4
5
6
7
8
MyPkg/
├── Project.toml
├── Manifest.toml
├── src/
│ └── MyPkg.jl
├── test/
│ └── runtests.jl
└── README.md

其中:

路径 作用
Project.toml 包的元信息、依赖和兼容要求
src/MyPkg.jl 包的主入口文件
test/runtests.jl 测试入口

通常包名是 MyPkg,主模块也写成:

1
2
3
module MyPkg

end

并放在 src/MyPkg.jl 文件中。

生成新包

最简单的做法是手动创建目录和文件,也可以借助社区工具生成模板。

如果手动创建,一个最小可用包通常至少包括:

  • Project.toml
  • src/MyPkg.jl
  • test/runtests.jl

一个简单的 Project.toml 可以形如:

1
2
3
4
5
6
name = "MyPkg"
uuid = "12345678-abcd-efgh-ijkl-123456789abc"
version = "0.1.0"

[compat]
julia = "1.12"

一个最简单的 src/MyPkg.jl 可以写成:

1
2
3
4
5
module MyPkg

greet() = println("Hello from MyPkg")

end

本地开发、测试和版本控制

开发 Julia 包时,通常直接在包目录中启动 Julia:

1
julia --project=.

此时当前包目录本身就是一个项目环境,可以继续添加依赖:

1
2
using Pkg
Pkg.add("JSON")

如果这个依赖只在测试中使用,也可以切换到 test/Project.toml 一类的专门测试环境,但很多小项目会先直接使用单一环境。

本地包接入当前项目

如果你在开发一个应用项目,同时希望引用本地正在开发的 Julia 包,常见做法是 develop 而不是普通 add

例如:

1
2
3
using Pkg
Pkg.activate(".")
Pkg.develop(path="../MyPkg")

或者:

1
pkg> develop ../MyPkg

这和普通 add 的区别是:

  • add 更偏向安装某个已发布版本或某个 Git 来源的快照;
  • develop 更偏向把一个本地工作目录以开发模式接入当前环境。

这样做之后,当前环境会直接使用这个本地目录中的代码变化,非常适合联调开发。

add 本地路径和 develop 本地路径的区别

对于本地路径,Julia 里容易混淆的就是 add pathdevelop path

可以简单理解为:

  • add path/to/pkg:把该路径视为一个来源,记录当前解析结果,更偏向“依赖一个包”
  • develop path/to/pkg:明确把这个目录作为开发工作副本,更偏向“我要直接改这个包”

在实际开发中,如果你会继续修改这个本地包,通常优先使用:

1
pkg> develop path/to/pkg

通过 Git 仓库使用包

Julia 也支持直接从 Git 仓库添加包,例如:

1
Pkg.add(url="https://github.com/user/MyPkg.jl")

也可以在 Pkg 模式中写成:

1
pkg> add https://github.com/user/MyPkg.jl

如果需要指定分支或提交,也可以写:

1
Pkg.add(url="https://github.com/user/MyPkg.jl", rev="main")

这里和 Python 的 pip install git+https://... 很接近。

测试包

Julia 包通常使用 test/runtests.jl 作为统一测试入口。

在包目录中可以执行:

1
2
using Pkg
Pkg.test()

或者在 Pkg 模式中:

1
pkg> test

Pkg.test() 会在合适的包测试环境中执行测试,而不是简单地直接运行某个脚本文件。

构建和预编译

有些包会提供构建步骤,例如下载资源、探测系统库等,此时可能会用到:

1
pkg> build

而常规的预编译则可以使用:

1
pkg> precompile

其中:

  • build 更偏向包安装后的额外初始化动作;
  • precompile 更偏向生成缓存,加快后续加载速度。

注册表

Julia 中的注册表(registry)可以理解为“包索引数据库”。

它记录了:

  • 有哪些包
  • 包名对应哪个 UUID
  • 各个版本对应的源码来源
  • 版本兼容信息

默认最常见的是官方通用注册表 General

当执行:

1
pkg> add DataFrames

Pkg 实际上会先在注册表中查找 DataFrames 这个名字对应的包,再根据版本约束解析依赖。

查看和管理注册表

常见命令:

1
2
3
4
pkg> registry status
pkg> registry add General
pkg> registry rm General
pkg> registry update

这些命令分别用于:

  • 查看当前注册表
  • 添加注册表
  • 删除注册表
  • 更新注册表索引

通常普通用户只需要保留 General 就够了,但在公司内部或团队内,也可能会额外维护私有注册表。

私有注册表和未注册包

如果某个包没有发布到 General,通常有两种常见使用方式:

  1. 直接通过 Git URL 添加
  2. 放到私有注册表中统一管理

直接通过 Git URL 适合:

  • 临时依赖
  • 小团队内部使用
  • 还没准备正式发布的包

私有注册表更适合:

  • 多个内部包统一分发
  • 需要稳定版本管理
  • 希望像公开包一样通过名称直接 add

注册表、本地包和 depot 的关系

这三者分别解决不同问题:

  • 注册表:回答“有哪些包、有哪些版本、去哪里下载”
  • 本地包:回答“当前开发时直接使用哪个工作目录”
  • depot:回答“包源码、工件、缓存实际放在哪里”

也就是说:

  • 注册表偏向索引和发布;
  • develop path=... 偏向本地开发联调;
  • depot 偏向底层存储。

开发本地包

如果一个本地 Julia 包需要以开发模式加入当前环境,可以使用:

1
Pkg.develop(path="path/to/MyPkg")

或者在 Pkg 模式中:

1
pkg> develop path/to/MyPkg

这类似于 Python 中的可编辑安装 pip install -e .

固定与解除固定版本

Julia 也支持固定依赖版本:

1
2
pkg> pin DataFrames
pkg> free DataFrames

含义:

  • pin:固定某个包,避免被 update 升级
  • free:解除固定,恢复正常解析

删除环境

Julia 项目环境本身通常只是一两个配置文件,因此如果不再需要某个项目环境:

  • 删除项目目录中的 Project.toml
  • 删除项目目录中的 Manifest.toml

即可视为删除该环境。

但注意:

  • 这不会自动删除 depot 中已经缓存的包源码和工件;
  • 共享缓存会继续保留,供其它环境复用。

如果要清理长期不用的包缓存,通常需要额外做 depot 清理,而不是仅删除项目目录。

在 Julia 中调用 Python

Julia 中调用 Python,最常见的方案主要有两种:

  • PyCall.jl
  • PythonCall.jl

它们都能让 Julia 导入 Python 模块、调用 Python 函数、读写 Python 对象,但底层思路和环境管理方式略有不同。

基本思路

无论使用哪一个库,本质上都是:

  1. 在 Julia 进程中嵌入一个 Python 解释器
  2. 让 Julia 可以导入 Python 模块
  3. 在 Julia 和 Python 对象之间做类型转换

因此这里会同时涉及两套环境:

  • Julia 自己的项目环境
  • 被调用的 Python 环境

也就是说:

  • Pkg.add(...) 管的是 Julia 侧依赖;
  • pip install ...uv add ...、conda 等管的是 Python 侧依赖。

PyCall.jlPythonCall.jl

PyCall.jl 是 Julia 中较早也非常常见的 Python 互操作方案。

安装:

1
2
using Pkg
Pkg.add("PyCall")

基本使用:

1
2
3
4
using PyCall

math = pyimport("math")
math.sqrt(2)

也可以直接调用 Python 内置或第三方库,例如:

1
2
3
4
using PyCall

np = pyimport("numpy")
a = np.array([1, 2, 3])

PyCall 使用哪个 Python

PyCall 最大的关键点是:它在构建时会绑定一个 Python 解释器。

也就是说,PyCall 不是每次运行时随便找一个 Python,而是通常在 build PyCall 时决定使用哪个 Python。

例如可以在启动 Julia 前设置:

1
export PYTHON=/path/to/python

然后在 Julia 中执行:

1
2
using Pkg
Pkg.build("PyCall")

此后 PyCall 就会绑定到这个 Python。

因此如果你后来切换了 Python 虚拟环境,PyCall 往往不会自动跟着变,通常需要重新 build

可以在 Julia 中查看它当前绑定的是哪个 Python:

1
2
using PyCall
PyCall.python

PythonCall.jl

PythonCall.jl 是相对更新的一套方案,设计上通常更现代,也更强调和 Julia 项目环境的配合。

安装:

1
2
using Pkg
Pkg.add("PythonCall")

基本使用:

1
2
3
4
using PythonCall

math = pyimport("math")
math.sqrt(2)

调用 Python 第三方库:

1
2
3
4
using PythonCall

np = pyimport("numpy")
x = np.array([1, 2, 3])

PythonCall 的环境管理

PythonCall 的一个特点是,它通常可以更方便地管理或定位自己的 Python 环境。

常见理解方式是:

  • PyCall 更像“先选定一个 Python,再把它嵌进 Julia”
  • PythonCall 更像“由 Julia 侧更主动地协调 Python 环境”

在很多情况下,PythonCall 会自动准备一个可用的 Python 运行环境;也可以显式要求它使用系统里已有的 Python。

因此:

  • 如果你想快速用 Python 库,PythonCall 往往更省心;
  • 如果你已经有一个明确的 Python 环境,并且希望 Julia 严格绑定它,PyCall 也很常见。

一个简单例子

例如想在 Julia 中调用 Python 的 json 模块:

1
2
3
4
5
using PythonCall

json = pyimport("json")
s = json.dumps(Dict("a" => 1, "b" => 2))
println(s)

如果使用 PyCall,写法也很相似:

1
2
3
4
5
using PyCall

json = pyimport("json")
s = json.dumps(Dict("a" => 1, "b" => 2))
println(s)

类型转换

Julia 和 Python 的对象系统不同,因此调用过程中会发生类型转换。

常见情况:

  • Julia 的数字、字符串、数组、字典可以转换给 Python
  • Python 返回的列表、字典、numpy.ndarray 等对象,也可以转换或包装成 Julia 可操作对象

但这种转换并不总是“完全自动且零成本”,尤其是:

  • 大数组
  • 可变对象
  • 自定义类实例

在实际使用中,需要注意到底是“复制了一份数据”,还是“共享/包装了一个 Python 对象”。

安装 Python 侧依赖

要成功调用某个 Python 包,除了 Julia 里装好 PyCallPythonCall,还必须确保对应 Python 环境里真的安装了那个包。

例如你想调用 numpy,那么 Python 侧也必须有:

1
pip install numpy

或者在使用 uv 的项目里:

1
uv add numpy

否则即使 Julia 侧已经安装了桥接库,下面的语句仍然会失败:

1
pyimport("numpy")

和 Python 虚拟环境配合时的注意事项

如果你的 Python 项目使用:

  • venv
  • uv
  • conda

那么 Julia 调 Python 时最关键的问题就是:当前桥接库到底连接到了哪一个 Python。

对于 PyCall,要特别注意:

  • 它通常绑定某个固定 Python
  • 切换 Python 虚拟环境后可能需要重新 Pkg.build("PyCall")

对于 PythonCall,则更要关注:

  • 它当前使用的是自动管理的 Python,还是你指定的外部 Python

否则很容易出现这种问题:

  • shell 里 python 能导入某个包
  • 但 Julia 里的 pyimport("xxx") 却失败

本质原因往往不是包没装,而是“Julia 连接的不是同一个 Python 环境”。

什么时候用哪一个

可以简单理解为:

  • PyCall:历史更久,使用广泛,适合已经有固定 Python 环境的情况
  • PythonCall:设计更新,环境管理通常更灵活,新的项目里经常更推荐优先考虑

如果只是偶尔调用少量 Python 功能,两者都够用;如果项目里 Julia 和 Python 需要长期混合协作,最好从一开始就明确:

  • 哪个库负责桥接
  • Python 解释器由谁管理
  • Python 依赖安装在哪个环境里

Julia 和 Python 环境模型的区别

1. Julia 环境不是独立复制的运行时

Python 的 venv 往往表现为:

  • 一套独立的解释器入口
  • 一套独立的 site-packages/

而 Julia 的项目环境更像:

  • 当前项目的依赖声明
  • 当前项目的依赖解析结果
  • 背后共享同一份 depot 中的包缓存

所以 Julia 环境通常更轻量。

2. Julia 环境依赖 Project.toml / Manifest.toml

Python 中很多项目历史上依赖:

  • requirements.txt
  • pyproject.toml + lockfile

Julia 则天然围绕:

  • Project.toml
  • Manifest.toml

来组织依赖管理和环境复现。

3. Julia 版本和项目环境天然分层

在 Julia 中通常是:

  • juliaup 切换解释器版本
  • Pkg.activate() 切换项目环境

这两层的职责边界相对清楚。

推荐工作流

新项目

  1. 使用合适版本启动 Julia
1
julia +1.12 --project=.
  1. 在 REPL 中激活当前目录并添加依赖
1
2
3
4
using Pkg
Pkg.activate(".")
Pkg.add("JSON")
Pkg.add("Plots")
  1. Project.tomlManifest.toml 纳入版本控制

打开已有项目

如果仓库中已经有 Project.tomlManifest.toml

1
julia --project=.

进入后执行:

1
2
using Pkg
Pkg.instantiate()

临时实验

如果只是一次性测试:

1
2
3
using Pkg
Pkg.activate(temp=true)
Pkg.add("DataFrames")

小结

关键结论:

  • Julia 版本管理通常由 juliaup 完成
  • Julia 项目环境管理通常由 Pkg 完成
  • Project.toml 记录直接依赖和兼容约束
  • Manifest.toml 记录完整依赖树和精确版本
  • Julia 环境本质上不是独立复制的运行时,而是依赖解析上下文
  • 包源码、工件和预编译结果通常共享存放在 ~/.julia depot 中
  • 项目开发时应尽量使用项目级环境,而不是长期堆在默认全局环境中