Skip to contents

Julia’s General registry is a git repository of TOML files: an index of every registered package, and four small files per package. This article reads all of them with zutoml and asks what they say, in particular which packages carry code compiled from C, C++ or Fortran.

Fetch the registry

The registry is downloaded once, as a tarball of a pinned commit (about 12 MB), and unpacked into a temporary directory.

commit <- "19314165a0b3aaf26cba8eb458327ec2f90dce4d"
url <- paste0("https://codeload.github.com/JuliaRegistries/General/tar.gz/", commit)
tarball <- tempfile(fileext = ".tar.gz")
withr::with_options(list(timeout = 300), download.file(url, tarball, quiet = TRUE, mode = "wb"))
dir <- tempfile("General")
untar(tarball, exdir = dir)
root <- list.files(dir, full.names = TRUE)

The index

Registry.toml names the registry and lists every package by UUID, each an inline table with its name and the directory holding its files:

toml_read(file.path(root, "Registry.toml"), select = "repo")
#> [1] "https://github.com/JuliaRegistries/General.git"
index <- toml_read(file.path(root, "Registry.toml"), select = "packages")
length(index)
#> [1] 14524
index[1:2]
#> $`00000000-0000-4000-b000-202512210000`
#> $`00000000-0000-4000-b000-202512210000`$name
#> [1] "Swizzling"
#> 
#> $`00000000-0000-4000-b000-202512210000`$path
#> [1] "S/Swizzling"
#> 
#> 
#> $`00000000-1111-2222-3333-444444444444`
#> $`00000000-1111-2222-3333-444444444444`$name
#> [1] "REPLTreeViews"
#> 
#> $`00000000-1111-2222-3333-444444444444`$path
#> [1] "R/REPLTreeViews"

A data frame is easier to work with:

pkgs <- data.frame(
  uuid = names(index),
  name = vapply(index, `[[`, "", "name"),
  path = vapply(index, `[[`, "", "path"),
  row.names = NULL
)
pkgs$jll <- endsWith(pkgs$name, "_jll")
table(pkgs$jll)
#> 
#> FALSE  TRUE 
#> 12662  1862

Packages whose names end in _jll are binary wrappers. Each is built by BinaryBuilder from a recipe in Yggdrasil and ships a compiled library or program, usually written in C, C++ or Fortran.

One package: Xsum

Xsum.jl gives exactly rounded sums through Radford Neal’s xsum library, which is written in C. Its directory has four files:

xsum <- file.path(root, pkgs$path[pkgs$name == "Xsum"])
list.files(xsum)
#> [1] "Compat.toml"   "Deps.toml"     "Package.toml"  "Versions.toml"

Package.toml says where the source lives:

str(toml_read(file.path(xsum, "Package.toml")))
#> List of 3
#>  $ name: chr "Xsum"
#>  $ uuid: chr "1fd64df0-a4d2-11e9-360d-2f03868f4cf5"
#>  $ repo: chr "https://github.com/JuliaMath/Xsum.jl.git"

Versions.toml has one table per release, keyed by version number and holding the git tree hash of that release:

names(toml_read(file.path(xsum, "Versions.toml")))
#> [1] "0.1.0" "1.0.0" "1.1.0" "1.2.0" "1.3.0"

Deps.toml and Compat.toml are keyed by version ranges: [0] covers every 0.x release, [1] every 1.x release, ["1.3-1"] releases from 1.3 up to the end of 1.x.

str(toml_read(file.path(xsum, "Deps.toml")))
#> List of 2
#>  $ 0:List of 2
#>   ..$ BinaryProvider: chr "b99e7846-7c00-51b0-8f62-c81ae34c0232"
#>   ..$ Libdl         : chr "8f399da3-3557-5675-b5ff-fb832c97cbdb"
#>  $ 1:List of 1
#>   ..$ xsum_jll: chr "e979a739-315a-50ee-b437-b496a9503be1"

This is as far as the registry goes. Xsum 0.x downloaded its binary with BinaryProvider; Xsum 1.x depends on xsum_jll, the compiled xsum library. So the registry shows that Xsum calls compiled code. It cannot say the code is C: xsum_jll’s own files name only its wrapper repository.

str(toml_read(file.path(root, pkgs$path[pkgs$name == "xsum_jll"], "Package.toml")))
#> List of 3
#>  $ name: chr "xsum_jll"
#>  $ uuid: chr "e979a739-315a-50ee-b437-b496a9503be1"
#>  $ repo: chr "https://github.com/JuliaBinaryWrappers/xsum_jll.jl.git"

The binary itself can say more: see Inside the binaries.

Every package

Reading Package.toml, Versions.toml and Deps.toml for every package parses some 44,000 small documents.

read_all <- function(file) {
  lapply(file.path(root, pkgs$path, file), function(f) {
    if (file.exists(f)) toml_read(f) else list()
  })
}
info <- read_all("Package.toml")
versions <- read_all("Versions.toml")
deps <- read_all("Deps.toml")

Almost every package is on GitHub. subdir marks a package that lives in a subdirectory of a larger repository:

pkgs$repo <- vapply(info, `[[`, "", "repo")
pkgs$subdir <- vapply(info, function(x) !is.null(x$subdir), NA)
sort(table(sub("^https?://([^/]+)/.*$", "\\1", pkgs$repo)), decreasing = TRUE)
#> 
#>     github.com     gitlab.com   codeberg.org gitlab.gwdg.de      git.sr.ht 
#>          14435             73              9              4              2 
#> gitlab.emse.fr 
#>              1
sum(pkgs$subdir)
#> [1] 619

The most released packages, and how many releases were later yanked:

pkgs$n_versions <- lengths(versions)
yanked <- function(v) sum(vapply(v, function(e) isTRUE(e$yanked), NA))
pkgs$n_yanked <- vapply(versions, yanked, 0L)
head(pkgs[order(-pkgs$n_versions), c("name", "n_versions", "n_yanked")], 10)
#>                  name n_versions n_yanked
#> 2466       DiffEqBase        631        3
#> 666         SciMLBase        578        3
#> 8572  ModelingToolkit        550        1
#> 94       Reactant_jll        443        0
#> 13455          Meshes        428        0
#> 9030     Oceananigans        424        0
#> 1723   OrdinaryDiffEq        414        4
#> 9580            Trixi        353        0
#> 7303      LinearSolve        332        1
#> 7217           Enzyme        327        0
sum(pkgs$n_yanked)
#> [1] 359

Dependencies of the latest release

To ask what a package depends on now, take its latest release and keep the Deps.toml sections whose range contains it. A range is one version or two joined by -; a short bound such as 1 stands for every 1.x.y. Pre-releases (1.0.0-rc1) are skipped and build numbers (+0) ignored.

version_parts <- function(v) {
  as.integer(strsplit(sub("[-+].*$", "", v), ".", fixed = TRUE)[[1]])
}

latest <- function(v) {
  v <- names(v)[!grepl("-", names(v))]
  if (!length(v)) return(NA_character_)
  v[order(numeric_version(sub("\\+.*$", "", v)), decreasing = TRUE)][1]
}

# -1, 0 or 1 as version v is below, within or above a (possibly short) bound
compare <- function(v, bound) {
  b <- version_parts(bound)
  d <- which(v[seq_along(b)] != b)
  if (length(d)) sign(v[d[1]] - b[d[1]]) else 0
}

in_range <- function(v, range) {
  bounds <- trimws(strsplit(range, "-", fixed = TRUE)[[1]])
  lo <- bounds[1]
  hi <- bounds[length(bounds)]
  (lo == "*" || compare(v, lo) >= 0) && (hi == "*" || compare(v, hi) <= 0)
}

deps_at <- function(d, v) {
  if (is.na(v)) return(character())
  v <- version_parts(v)
  hit <- Filter(function(r) in_range(v, r), names(d))
  sort(unique(as.character(unlist(lapply(d[hit], names)))))
}

pkgs$latest <- vapply(versions, latest, "")
pkgs$deps <- Map(deps_at, deps, pkgs$latest)
pkgs$deps[[which(pkgs$name == "Xsum")]]
#> [1] "xsum_jll"

The most depended-on packages at their latest releases. Standard libraries such as LinearAlgebra are not registered, so they are left out:

used <- table(unlist(pkgs$deps))
head(sort(used[names(used) %in% pkgs$name], decreasing = TRUE), 15)
#> 
#>          Statistics           Artifacts         JLLWrappers        StaticArrays 
#>                2086                1910                1795                1373 
#>       Distributions          DataFrames            Reexport           StatsBase 
#>                1277                1272                1157                1155 
#>     PrecompileTools                JSON DocStringExtensions    SpecialFunctions 
#>                 822                 821                 791                 697 
#>      DataStructures                 CSV       ProgressMeter 
#>                 685                 680                 617

Packages with compiled code

The registry records metadata, not source, so it cannot say which language a package is written in. Dependencies come close: a _jll package is compiled code, a package that depends on one calls compiled code, and a package that depends on CxxWrap wraps C++.

pkgs$calls_jll <- vapply(pkgs$deps, function(d) any(endsWith(d, "_jll")), NA)
pkgs$cxxwrap <- vapply(pkgs$deps, function(d) "CxxWrap" %in% d, NA)
callers <- pkgs[!pkgs$jll & pkgs$calls_jll, ]
c(packages = sum(!pkgs$jll), call_a_jll = nrow(callers), cxxwrap = sum(callers$cxxwrap))
#>   packages call_a_jll    cxxwrap 
#>      12662        856         50

Inside the binaries

Each _jll package has an Artifacts.toml, one entry per platform it was built for. BinaryBuilder adds two tags to that platform when the binary needs them, and both name a language:

  • libgfortran_version: the binary links GCC’s Fortran runtime, because it has Fortran code or Fortran bindings;
  • cxxstring_abi: the binary passes C++ std::strings across its interface, so it is C++.

xsum_jll has neither; Arpack_jll links libgfortran:

[[Arpack]]
arch = "aarch64"
git-tree-sha1 = "d626d3e001367ad1c01b8c5580939702be031ee1"
libgfortran_version = "5.0.0"
os = "macos"

Artifacts.toml is in the package, not in the registry. Julia’s package server hands out any registered version as a tarball, named by the package’s UUID and the git-tree-sha1 that Versions.toml records, so the files read are exactly those of the pinned registry. download.file() fetches the tarballs of every _jll the callers use, about 8 kB each, in parallel.

jlls <- pkgs[pkgs$jll & pkgs$name %in% unlist(callers$deps), ]
nrow(jlls)
#> [1] 780
jlls$tree <- mapply(
  function(v, latest) v[[latest]][["git-tree-sha1"]],
  versions[match(jlls$name, pkgs$name)], jlls$latest
)
jll_dir <- tempfile("jll")
dir.create(jll_dir)
tarballs <- file.path(jll_dir, paste0(jlls$name, ".tar.gz"))
withr::with_options(list(timeout = 600), download.file(
  sprintf("https://pkg.julialang.org/package/%s/%s", jlls$uuid, jlls$tree),
  tarballs,
  method = "libcurl", quiet = TRUE, mode = "wb"
))

Every platform entry carries the same tags, so the first one is enough, and select reads only that. An artifact is usually named after its package (xsum in xsum_jll); when it is not, select raises zutoml_missing_key and the whole file is read instead.

first_artifact <- function(name, tarball) {
  exdir <- file.path(jll_dir, name)
  untar(tarball, files = "Artifacts.toml", exdir = exdir)
  file <- file.path(exdir, "Artifacts.toml")
  tryCatch(
    toml_read(file, select = paste0(sub("_jll$", "", name), "[1]")),
    zutoml_missing_key = function(e) {
      artifact <- toml_read(file)[[1]]
      if (is.null(names(artifact))) artifact[[1]] else artifact
    }
  )
}
artifacts <- Map(first_artifact, jlls$name, tarballs)
jlls$fortran <- vapply(artifacts, function(a) !is.null(a$libgfortran_version), NA)
jlls$cxx <- vapply(artifacts, function(a) !is.null(a$cxxstring_abi), NA)
table(fortran = jlls$fortran, cxx = jlls$cxx)
#>        cxx
#> fortran FALSE TRUE
#>   FALSE   640   60
#>   TRUE     80    0

A package is marked with every language its binaries show. CompilerSupportLibraries_jll is left out: it is GCC’s runtime, for Fortran, C++ and OpenMP together, so depending on it does not say which.

any_of <- function(flag) {
  tagged <- setdiff(jlls$name[flag], "CompilerSupportLibraries_jll")
  vapply(callers$deps, function(d) any(d %in% tagged), NA)
}
callers$fortran <- any_of(jlls$fortran)
callers$cxx <- any_of(jlls$cxx) | callers$cxxwrap
c(fortran = sum(callers$fortran), cxx = sum(callers$cxx), neither = sum(!callers$fortran & !callers$cxx))
#> fortran     cxx neither 
#>     106      94     660

The packages that call Fortran, with the binaries that show it:

shown_by <- function(flag, rows, also = character(), n = 20) {
  tagged <- c(setdiff(jlls$name[flag], "CompilerSupportLibraries_jll"), also)
  via <- vapply(callers$deps[rows], function(d) {
    paste(intersect(d, tagged), collapse = ", ")
  }, "")
  keep <- via != ""
  head(data.frame(package = callers$name[rows][keep], via = via[keep]), n)
}
shown_by(jlls$fortran, callers$fortran)
#>                package                           via
#> 1       FormationTemps                      HDF5_jll
#> 2          TDEPWrapper                      TDEP_jll
#> 3    NLPModelsAlgencan  Algencan_jll, OpenBLAS32_jll
#> 4        MolSimToolkit                 MDLovoFit_jll
#> 5          PETScDiffEq                     PETSc_jll
#> 6                PRIMA                     PRIMA_jll
#> 7            cuNumeric                OpenBLAS32_jll
#> 8            PetscCall                     PETSc_jll
#> 9           Population               OpenSpecFun_jll
#> 10 FastPolynomialRoots                   LibAMVW_jll
#> 11               DASKR                     DASKR_jll
#> 12               Xfoil               xfoil_light_jll
#> 13              MAiNGO                    MAiNGO_jll
#> 14              CUTEst     CUTEst_jll, SIFDecode_jll
#> 15           UnoSolver       OpenBLAS32_jll, Uno_jll
#> 16               FMM3D                     FMM3D_jll
#> 17           StaticMPI                     MPICH_jll
#> 18       QRupdatesFast               QRupdate_ng_jll
#> 19            PinCFlow           HDF5_jll, MPICH_jll
#> 20              MadNLP MUMPS_seq_jll, OpenBLAS32_jll

The packages that call C++, through a tagged binary or through CxxWrap:

shown_by(jlls$cxx, callers$cxx, also = "CxxWrap")
#>          package                                                via
#> 1        XGBoost                       XGBoost_GPU_jll, XGBoost_jll
#> 2  ClangCompiler                                     libclangex_jll
#> 3         OpenCL                                    SPIRV_Tools_jll
#> 4      JuliaChem                                            CxxWrap
#> 5         Algoim                                            CxxWrap
#> 6      cuNumeric cunumeric_jl_wrapper_jll, cupynumeric_jll, CxxWrap
#> 7          Gecko                                 CxxWrap, gecko_jll
#> 8         Legate         CxxWrap, legate_jl_wrapper_jll, legate_jll
#> 9    LEMONGraphs                                            CxxWrap
#> 10          CGAL                                            CxxWrap
#> 11        XRootD                                            CxxWrap
#> 12          ROOT                                            CxxWrap
#> 13        JSBSim                                            CxxWrap
#> 14       Whisper                                    whisper_cpp_jll
#> 15      QWTWPlot                       marble_jll, Qt_jll, qwtw_jll
#> 16          Xyce                                            CxxWrap
#> 17        AMDGPU                            AMDGPU_LLVM_Backend_jll
#> 18            GR                             GR_jll, Qt6Wayland_jll
#> 19    FastNoise2                                     FastNoise2_jll
#> 20           QML                            CxxWrap, Qt6Wayland_jll

No tag says C. The closest the TOML comes is the packages whose binaries carry neither tag, Xsum among them. Most of them are C, but not all: goaws_jll is Go.

neither <- !callers$fortran & !callers$cxx
shown_by(!jlls$fortran & !jlls$cxx, neither)
#>                  package                           via
#> 1               LevelDB2                   LevelDB_jll
#> 2                 LibSSH Kerberos_krb5_jll, libssh_jll
#> 3                  GoAWS                     goaws_jll
#> 4               cuTENSOR                  CUTENSOR_jll
#> 5                  SpaSM                     Spasm_jll
#> 6  LatticeDiracOperators                  AlgRemez_jll
#> 7                  NOMAD                     NOMAD_jll
#> 8                  cuDNN                     CUDNN_jll
#> 9                WGPUgfx                      GLFW_jll
#> 10              ImPlot3D                 CImPlot3D_jll
#> 11                   VSL                       MKL_jll
#> 12                GitOut                       Git_jll
#> 13                Blosc2                    Blosc2_jll
#> 14                LibIIO                    libiio_jll
#> 15        FastTransforms            FastTransforms_jll
#> 16                Kissat                    Kissat_jll
#> 17             ViennaRNA                 ViennaRNA_jll
#> 18             NomnomlJS                   Librsvg_jll
#> 19                Hygese                   HGSCVRP_jll
#> 20                    Z3                        z3_jll
"Xsum" %in% callers$name[neither]
#> [1] TRUE

The tags show what a binary needs, which is a lower bound. Much C++ passes no strings across its interface and is not tagged, and a binary with neither tag may be C, C++, Rust or Go. MPICH_jll and HDF5_jll are tagged for their Fortran bindings, though they are mostly C. The build recipes in Yggdrasil are the full answer; the TOML has taken us a long way towards it.