8. Artifacts
Pkg 可以安装和管理数据容器,它不是 Julia 包。这些容器可以包含特定于平台的二进制文件、数据集、文本或任何其他类型的数据,这些数据可以方便地放置在不可变的、具有生命周期的数据存储中。这些容器(称为“Artifacts”)可以在本地创建、托管在任何地方,并在安装 Julia 包时自动下载和解包。此机制还为使用BinaryBuilder.jl 进行包构建提供二进制依赖。
基本用法
Pkg artifacts 在Artifacts.toml文件中声明,该文件可以放在当前目录或包的根目录中。目前,Pkg 支持从 URL 下载 tar 文件(可以压缩)。以下是一个最小 Artifacts.toml 文件,它允许从 github.com 下载 socrates.tar.gz 文件。在此示例中,定义了一个名为 socrates 的 artifact。
# a simple Artifacts.toml file
[socrates]
git-tree-sha1 = "43563e7631a7eafae1f9f8d9d332e3de44ad7239"
    [[socrates.download]]
    url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.gz"
    sha256 = "e65d2f13f2085f2c279830e863292312a72930fee5ba3c792b14c33ce5c5cc58"如果 Artifacts.toml 文件位于您的当前目录中,则 socrates.tar.gz 可以下载、解压缩,并以 artifact"socrates" 方式使用。由于这个 tar 压缩包包含一个 bin 文件夹和一个名为 socrates 的文本文件,我们可以按如下方式访问该文件的内容。
using Pkg.Artifacts
rootpath = artifact"socrates"
open(joinpath(rootpath, "bin", "socrates")) do file
    println(read(file, String))
end如果您有一个可通过 url 访问的现有 tar 压缩包,也可以以这种方式访问它。要创建 Artifacts.toml 您必须计算两个哈希值:下载文件的 sha256 哈希值和解压内容的 git-tree-sha1 哈希值。这些可以计算如下:
using Tar, Inflate, SHA
filename = "socrates.tar.gz"
println("sha256: ", bytes2hex(open(sha256, filename)))
println("git-tree-sha1: ", Tar.tree_hash(IOBuffer(inflate_gzip(filename))))要从您创建的包中访问此 artifact,请将 Artifacts.toml 放在包的根目录中,和 Project.toml 相邻。然后,确保在您的 deps 节中添加了 Pkg,以及在 compat 节中设置 julia = "1.3" 或更高版本。
Artifacts.toml 文件
Pkg 提供了用于使用 artifacts 的 API,以及用于记录软件包中 artifacts 使用情况的 TOML 文件格式,并在软件包安装时自动下载 artifacts。Artifacts 始终可以通过内容哈希来引用,但通常通过绑定到项目源树中 Artifacts.toml 文件的内容哈希的名称来访问。
可以使用替代名称 JuliaArtifacts.toml,类似于分别使用 JuliaProject.toml 和 JuliaManifest.toml 代替 Project.toml 和 Manifest.toml。 It is possible to use the alternate name JuliaArtifacts.toml, similar to how it is possible to use JuliaProject.toml and JuliaManifest.toml instead of Project.toml and Manifest.toml, respectively.
此处显示了一个 Artifacts.toml 文件示例:
# Example Artifacts.toml file
[socrates]
git-tree-sha1 = "43563e7631a7eafae1f9f8d9d332e3de44ad7239"
lazy = true
    [[socrates.download]]
    url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.gz"
    sha256 = "e65d2f13f2085f2c279830e863292312a72930fee5ba3c792b14c33ce5c5cc58"
    [[socrates.download]]
    url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.bz2"
    sha256 = "13fc17b97be41763b02cbb80e9d048302cec3bd3d446c2ed6e8210bddcd3ac76"
[[c_simple]]
arch = "x86_64"
git-tree-sha1 = "4bdf4556050cb55b67b211d4e78009aaec378cbc"
libc = "musl"
os = "linux"
    [[c_simple.download]]
    sha256 = "411d6befd49942826ea1e59041bddf7dbb72fb871bb03165bf4e164b13ab5130"
    url = "https://github.com/JuliaBinaryWrappers/c_simple_jll.jl/releases/download/c_simple+v1.2.3+0/c_simple.v1.2.3.x86_64-linux-musl.tar.gz"
[[c_simple]]
arch = "x86_64"
git-tree-sha1 = "51264dbc770cd38aeb15f93536c29dc38c727e4c"
os = "macos"
    [[c_simple.download]]
    sha256 = "6c17d9e1dc95ba86ec7462637824afe7a25b8509cc51453f0eb86eda03ed4dc3"
    url = "https://github.com/JuliaBinaryWrappers/c_simple_jll.jl/releases/download/c_simple+v1.2.3+0/c_simple.v1.2.3.x86_64-apple-darwin14.tar.gz"
[processed_output]
git-tree-sha1 = "1c223e66f1a8e0fae1f9fcb9d3f2e3ce48a82200"此 Artifacts.toml 绑定了三个 artifacts:一个命名为 socrates,一个命名为 c_simple,一个命名为 processed_output。artifacts 唯一需要的信息是它的 git-tree-sha1。因为 artifacts 仅通过其内容哈希寻址,所以 Artifacts.toml 文件的目的是提供有关这些 artifacts 的元数据,例如绑定人类可读的名称到内容哈希,提供有关可以从何处下载 artifacts 的信息,甚至绑定单个名称到多个哈希,由特定于平台的约束(例如操作系统或 libgfortran 的版本)键控(keyed)。
Artifact 类型和属性
在上面的示例中,socrates artifact 展示了具有多个下载位置的独立于平台的 artifact。下载和安装 socrates artifact 时,将按顺序尝试 URL 直到成功。该socrates artifact 被标记为 lazy,这意味着它不会在安装包含的包时自动下载,而是在包第一次尝试使用它时按需下载。
c_simple artifact 展示了一个依赖于平台的 artifact,其中 c_simple 数组中的每个条目都包含键,键帮助调用包根据主机的详细信息选择适当的下载地址。请注意,每个 artifact 都包含对应下载条目的 git-tree-sha1 和 sha256。这是为了确保下载的 tar 压缩包在尝试解压缩之前是安全的,并强制所有 tar 压缩包必须扩展为同样的整体 git 树哈希。
processed_output artifact不包含 download 节,因此无法被安装。像这样的 artifact 是先前运行的代码的结果,生成一个新的 artifact 并将生成的哈希绑定到该项目中的名称上。
使用 Artifacts
可以使用从 Pkg.Artifacts 命名空间公开的便捷 API 来操作 artifact 。作为一个激励性的例子,让我们想象我们正在编写一个需要加载 Iris 机器学习数据集的包。虽然我们可以在构建步骤中将数据集下载到包目录中,并且目前许多包都这样做,但这有一些明显的缺点:
首先,它修改了包目录,使包安装变得具有状态了,这是我们要避免的。将来,我们希望能够以完全只读的方式安装包,而不是在安装后能够自行修改。
其次,下载的数据不会在包的不同版本之间共享。如果我们安装了三个不同版本的包以供不同项目使用,那么我们需要三个不同的数据副本,即使这些版本之间没有更改。此外,每次我们升级或降级软件包时,除非我们做了一些聪明的事情(而且可能很脆弱),否则我们必须再次下载数据。
对于 artifacts,我们将检查 iris artifacts 是否已经存在于磁盘上,只有不存在时我们才会下载并安装它,之后我们可以将结果绑定到我们的 Artifacts.toml 文件中:
using Pkg.Artifacts
# This is the path to the Artifacts.toml we will manipulate
artifact_toml = joinpath(@__DIR__, "Artifacts.toml")
# Query the `Artifacts.toml` file for the hash bound to the name "iris"
# (returns `nothing` if no such binding exists)
iris_hash = artifact_hash("iris", artifact_toml)
# If the name was not bound, or the hash it was bound to does not exist, create it!
if iris_hash == nothing || !artifact_exists(iris_hash)
    # create_artifact() returns the content-hash of the artifact directory once we're finished creating it
    iris_hash = create_artifact() do artifact_dir
        # We create the artifact by simply downloading a few files into the new artifact directory
        iris_url_base = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris"
        download("$(iris_url_base)/iris.data", joinpath(artifact_dir, "iris.csv"))
        download("$(iris_url_base)/bezdekIris.data", joinpath(artifact_dir, "bezdekIris.csv"))
        download("$(iris_url_base)/iris.names", joinpath(artifact_dir, "iris.names"))
    end
    # Now bind that hash within our `Artifacts.toml`.  `force = true` means that if it already exists,
    # just overwrite with the new content-hash.  Unless the source files change, we do not expect
    # the content hash to change, so this should not cause unnecessary version control churn.
    bind_artifact!(artifact_toml, "iris", iris_hash)
end
# Get the path of the iris dataset, either newly created or previously generated.
# this should be something like `~/.julia/artifacts/dbd04e28be047a54fbe9bf67e934be5b5e0d357a`
iris_dataset_path = artifact_path(iris_hash)对于使用先前绑定的 artifacts 的特定用例,我们有一个速记符号 artifact"name",它将自动搜索当前包中的 Artifacts.toml 文件,按名称查找给定的 artifacts,如果尚未安装,则安装它,然后返回该给定 artifacts 的路径。下面给出了这个速记符号的一个例子:
using Pkg.Artifacts
# For this to work, an `Artifacts.toml` file must be in the current working directory
# (or in the root of the current package) and must define a mapping for the "iris"
# artifact.  If it does not exist on-disk, it will be downloaded.
iris_dataset_path = artifact"iris"Pkg.Artifacts API
Artifacts API 分为三个级别:哈希感知函数、名称感知函数和实用函数。 The Artifacts API is broken up into three levels: hash-aware functions, name-aware functions and utility functions.
哈希感知函数只处理内容哈希。这些方法允许您查询 artifact 是否存在、其路径是什么、验证 artifact 是否满足其在磁盘上的内容哈希等。哈希感知函数包括:
artifact_exists()、artifact_path()、remove_artifact()、verify_artifact()和archive_artifact()。请注意,通常您不应该使用remove_artifact(),而应该使用Pkg.gc()来清理安装的 artifact。名称感知函数处理
Artifacts.toml文件中的绑定名称,因此,通常需要Artifacts.toml文件路径和 artifact 名称。名称感知函数包括:artifact_meta()、artifact_hash()、bind_artifact!()、unbind_artifact!()、download_artifact()和ensure_artifact_installed()。实用函数处理 artifact 生命周期的各种方面,例如
create_artifact()、ensure_all_artifacts_installed(),甚至是@artifact_str字符串宏。
有关文档字符串和方法的完整列表,请参阅 Artifacts Reference章节。
覆盖 artifact 位置
有时需要能够覆盖 artifact 的位置和内容。一个常见的例子是计算环境,其中必须使用某些版本的二进制依赖项,而不管包是使用哪个版本的依赖项发布的。虽然典型的 Julia 配置会下载、解包并链接到通用库,但系统管理员可能希望禁用此功能,而使用已安装在本地计算机上的库。Pkg 通过在 artifacts depot 目录中放置一个 Overrides.toml 文件启用对此功能的支持(例如对于默认用户仓库是 ~/.julia/artifacts/Overrides.toml 文件),它可以通过内容哈希,或包的 UUID 和绑定到 artifact 的名称覆盖 artifact 的位置。此外,目标位置可以是绝对路径,也可以是 artifact 内容哈希。这允许系统管理员创建他们自己的 artifacts,然后他们可以通过覆盖其他包来使用新 artifact。
# Override single hash to an absolute path
78f35e74ff113f02274ce60dab6e92b4546ef806 = "/path/to/replacement"
# Override single hash to new artifact content-hash
683942669b4639019be7631caa28c38f3e1924fe = "d826e316b6c0d29d9ad0875af6ca63bf67ed38c3"
# Override package bindings by specifying the package UUID and bound artifact name
# For demonstration purposes we assume this package is called `Foo`
[d57dbccd-ca19-4d82-b9b8-9d660942965b]
libfoo = "/path/to/libfoo"
libbar = "683942669b4639019be7631caa28c38f3e1924fe"由于Pkg depot 的分层特性,多个 Overrides.toml 文件可能同时生效。这允许“内部” Overrides.toml 文件覆盖放在“外部” Overrides.toml 文件中的覆盖规则。要删除覆盖并重新启用 artifact 的默认位置逻辑,请将条目映射插入到空字符串中:
78f35e74ff113f02274ce60dab6e92b4546ef806 = "/path/to/new/replacement"
683942669b4639019be7631caa28c38f3e1924fe = ""
[d57dbccd-ca19-4d82-b9b8-9d660942965b]
libfoo = ""如果上面给出的两个 Overrides.toml 片段相互叠加,最终结果将映射内容哈希 78f35e74ff113f02274ce60dab6e92b4546ef806 到 "/path/to/new/replacement",并映射 Foo.libbar 到由内容哈希标识的 artifact 683942669b4639019be7631caa28c38f3e1924fe。请注意,虽然该哈希之前已被覆盖,但它不再被覆盖,因此 Foo.libbar 将直接查看 ~/.julia/artifacts/683942669b4639019be7631caa28c38f3e1924fe 位置。
大多数受覆盖影响的方法可以通过设置它们的 honor_overrides=false 关键字参数来忽略覆盖。要使基于 UUID/名称 的覆盖起作用,Artifacts.toml 文件必须在知道加载包的 UUID 的情况下被加载。这是由 artifacts"" 字符串宏自动推导出来的,但是,如果您出于某种原因在包中手动使用 Pkg.Artifacts API 并且希望尊重覆盖,则必须通过关键字参数将包的 UUID 提供给 API 调用,类似于 artifact_meta() 和 ensure_artifact_installed() 使用 pkg_uuid 关键字参数。
扩展平台选择
Pkg 的扩展平台选择至少需要 Julia 1.7,并且被认为是实验性的。
Julia 1.6 中的新增功能,Platform 对象可以应用扩展属性,允许使用诸如 CUDA 驱动程序版本兼容性、微架构兼容性、julia 版本兼容性等扩展属性标记 artifacts!请注意,此功能被认为是实验性的,将来可能会更改。如果您作为包开发人员发现自己需要此功能,请与我们联系,以便它可以为整个生态系统的利益而发展。为了在 Pkg.add() 时支持 artifact 选择,Pkg 将运行特殊命名的文件 <project_root>/.pkg/select_artifacts.jl,传递当前平台三元组作为第一个参数。这个 artifact 选择脚本应该打印一个 TOML - 表示此包根据给定平台需要的 artifacts 的序列化字典,如果给定平台三元组未明确提供平台功能,则根据需要对系统进行任何检查,以自动检测平台功能。字典的格式应该与从 Artifacts.select_downloadable_artifacts() 返回的匹配,实际上大多数包应该简单地用一个增强的 Platform 对象调用此函数。artifact 选择 hook 的定义的示例,可能如下所示,分为两个文件:
# .pkg/platform_augmentation.jl
using Libdl, Base.BinaryPlatforms
function augment_platform!(p::Platform)
    # If this platform object already has a `cuda` tag set, don't augment
    if haskey(p, "cuda")
        return p
    end
    # Open libcuda explicitly, so it gets `dlclose()`'ed after we're done
    dlopen("libcuda") do lib
        # find symbol to ask for driver version; if we can't find it, just silently continue
        cuDriverGetVersion = dlsym(lib, "cuDriverGetVersion"; throw_error=false)
        if cuDriverGetVersion !== nothing
            # Interrogate CUDA driver for driver version:
            driverVersion = Ref{Cint}()
            ccall(cuDriverGetVersion, UInt32, (Ptr{Cint},), driverVersion)
            # Store only the major version
            p["cuda"] = div(driverVersion, 1000)
        end
    end
    # Return possibly-altered `Platform` object
    return p
endusing TOML, Artifacts, Base.BinaryPlatforms
include("./platform_augmentation.jl")
artifacts_toml = joinpath(dirname(@__DIR__), "Artifacts.toml")
# Get "target triplet" from ARGS, if given (defaulting to the host triplet otherwise)
target_triplet = get(ARGS, 1, Base.BinaryPlatforms.host_triplet())
# Augment this platform object with any special tags we require
platform = augment_platform!(HostPlatform(parse(Platform, target_triplet)))
# Select all downloadable artifacts that match that platform
artifacts = select_downloadable_artifacts(artifacts_toml; platform)
# Output the result to `stdout` as a TOML dictionary
TOML.print(stdout, artifacts)在此 hook 定义中,我们的 platformaugmentation 例程打开一个系统库(libcuda),搜索 CUDA 驱动的版本符号,然后将版本号中的主版本号嵌入到我们要增强的 Platform 对象的 cuda 属性中。虽然实际尝试关闭已加载的库对于此代码而言并不重要(因为它很可能会在包操作完成后,立即被 CUDA 包再次打开),但最佳实践是使 hook 尽可能轻量级和透明,因为它们将来可能会被其他 Pkg 实用程序使用。在您自己的包中,您也应该在使用 `@artifactstr` 宏时使用增强的平台对象,如下所示:
include("../.pkg/platform_augmentation.jl")
function __init__()
    p = augment_platform!(HostPlatform())
    global my_artifact_dir = @artifact_str("MyArtifact", p)
end这可确保您的代码使用与 Pkg 尝试安装的 artifact 相同的 artifact。
Artifact 选择 hook 仅允许使用Base、Artifacts、Libdl 和 TOML 包。不允许使用任何其他标准库,也不允许使用任何包(包括他们所属的包)。