pixqc

2772 build.zig.zon Files Analyzed

I built ziglist.org. It's a directory of Zig packages and projects. Here are some interesting bits I found after analyzing the database.

As of 2024-08-13T03:00:00.002Z:

Ziglist

Top 10 most depended-on packages: raylib, raylib_zig, clap, mach_glfw, zigimg, glfw, zap, zmath, zgui, sdl.

Top 5 most depended-on packages by path: zmath, zgui, system_sdk, zglfw, zstbi. All from zig-gamedev/zig-gamedev monorepo.

Top 5 most depended-on packages by URL: raylib, raylib_zig, clap, mach_glfw, zigimg.

Check out ziglist.org/dependencies for more.

543 C/C++ repos have a build.zig file. The most notable ones are ggerganov/ggml and cryptocode/terminal-doom. Most are Zig bindings: vrischmann/zig-sqlite, floooh/sokol-zig, and ikskuh/SDL.zig.

Zero-dependency Zig projects: tigerbeetle/tigerbeetle and mitchellh/libxev.

Repos created pre-0.11 Zig that has build.zig.zon: capy-ui/capy, fairyglade/ly, riverwm/river, hexops/mach, mitchellh/libxev, and much more. build.zig.zon was introduced in Zig 0.11.

Zig projects with dependencies but no build.zig.zon:

Repos with build.zig.zon but no build.zig: AidanWelch/zig-translate, z4nsh1n/zig-flap, yellowflash/zig-timestamp, PiotrCembrowski/zig-pro, zacwoll/learning_zig.

Ziglist has a zon file parser:

import { z } from "zod";

const zon2json = (zon) => {
  return zon
    .replace(/(?<!:)\/\/.*$/gm, "") // Remove comments
    .replace(/\.\{""}/g, ".{}") // Handle empty objects
    .replace(/\.{/g, "{") // Replace leading dots before curly braces
    .replace(/\.@"([^"]+)"?\s*=\s*/g, '"$1": ') // Handle .@"key" = value
    .replace(/\.(\w+)\s*=\s*/g, '"$1": ') // Handle .key = value
    .replace(/("paths"\s*:\s*){([^}]*)}/g, "$1[$2]") // Convert paths to array
    .replace(/,(\s*[}\]])/g, "$1") // Remove trailing commas
    .replace(/"url"\s*:\s*"([^"]+)"/g, (_, p1) => {
      // Special handling for URL to preserve '?' and '#'
      return `"url": "${p1.replace(/"/g, '\\"')}"`;
    });
};

const SchemaZon = z.object({
  name: z.string(),
  version: z.string(),
  minimum_zig_version: z.string().optional(),
  paths: z.array(z.string()).optional(),
  dependencies: z
    .record(
      z.union([
        z.object({
          url: z.string(),
          hash: z.string(),
          lazy: z.boolean().optional(),
        }),
        z.object({
          path: z.string(),
          lazy: z.boolean().optional(),
        }),
      ]),
    )
    .optional(),
});

const url =
  "https://raw.githubusercontent.com/ziglang/zig/master/build.zig.zon";
const response = await fetch(url);
console.log(SchemaZon.parse(JSON.parse(zon2json(await response.text()))));

// {
//   name: "zig",
//   version: "0.0.0",
//   paths: [],
//   dependencies: {
//     standalone_test_cases: { path: "test/standalone" },
//     link_test_cases: { path: "test/link" }
//   }
// }

Out of ~2.7k zon files, zons that fail to parse:

Download Ziglist's SQLite database here (last updated: 2024-08-13T03:00:00.002Z; size: 6MB).

Appendix contains queries to calculate the all stats above.

· · ·

"What if pkg.go.dev, crates.io, and Zig had a baby?" This is the question that birthed Ziglist.

The word "directory" is deliberate when describing Ziglist. Zig has no official package repository. If crates.io is an Amazon warehouse, Ziglist is a mere yellow pages. Every go install and cargo add is logged. That's how pkg.go.dev and crates.io have granular download data and dependency graphs. (For go install specifically, read more here, here, and here.)

"I can just fetch all build.zig from GitHub's code search API and paginate til the end." The problem is GitHub only returns 1k items for a given query.

curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <token>" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  "https://api.github.com/search/code?q=filename%3Abuild.zig&per_page=100&page=11"

Returns:

{
  "message": "Cannot access beyond the first 1000 results",
  "documentation_url": "https://docs.github.com/rest/search/search#search-code",
  "status": "422"
}

Bummer. I xeeted out that I'm 422'd by GitHub and @selfhostedmind suggested using the repo search API with &created_at field. Example URL:

https://api.github.com/search/repositories?q=in%3Aname%2Cdescrip
tion%2Ctopics%20zig%20created%3A2015-07-04T00%3A00%3A00Z..2017-0
9-02T00%3A00%3A00Z&per_page=100&page=1

And this became the bread and butter of Ziglist. Thanks @selfhostedmind! The code goes into more details on how it works. See here.

Visual aesthetic: I shamelessly copied the design of openalternative.co. I dislike the tech stack. But I like the visual aesthetic. So I recreated everything from scratch. All visual aesthetic credit goes to them.

Code aesthetics: I really like the aesthetic of sokol_gfx.h and ggml.c. One giant C file. That's badass. npx create-next-app@latest has the exact opposite aesthetic, which we will not discuss because I don't want to have nightmares and heart palpitations. This is why Ziglist is just a single main.jsx file.

Dependencies: Tailwind, SQLite, Cloudflare R2, Zod, Hono, and Deno. I was curious about Deno around the time I started Ziglist, especially jsr.io. So I naturally ran deno init and went from there. I enjoy Bun's native SQLite support. I miss it a lot when I'm working with Deno.

Check out ziglist.org!

· · ·

Appendix. Here are all the queries used to calculate the stats above:

-- high-level stats
WITH repo_counts AS (
  SELECT
    COUNT(*) total_repos,
    SUM(CASE WHEN build_zig_exists = 1 THEN 1 ELSE 0 END) build_zig_count,
    SUM(CASE WHEN build_zig_zon_exists = 1 THEN 1 ELSE 0 END) build_zig_zon_count,
    COUNT(min_zig_version) min_zig_version_count
  FROM zig_repos
),
dependency_stats AS (
  SELECT
    COUNT(DISTINCT REPLACE(name, '-', '_')) unique_dependencies,
    COUNT(*) total_dependencies,
    SUM(CASE WHEN dependency_type = 'path' THEN 1 ELSE 0 END) path_dependencies,
    SUM(CASE WHEN dependency_type = 'url' THEN 1 ELSE 0 END) url_dependencies
  FROM zig_repo_dependencies
)
SELECT
  rc.total_repos,
  rc.build_zig_count,
  rc.build_zig_zon_count,
  ds.unique_dependencies,
  ds.total_dependencies,
  ds.path_dependencies,
  ds.url_dependencies,
  rc.min_zig_version_count
FROM repo_counts rc, dependency_stats ds;
-- weekly Zig repo created
WITH RECURSIVE
  weeks(week_start) AS (
    SELECT date('2015-08-06', 'weekday 0', '-7 days')  -- Start from the Sunday before 2015-08-06
    UNION ALL
    SELECT date(week_start, '+7 days')
    FROM weeks
    WHERE week_start < date('now', 'weekday 0', '-7 days')
  ),
  repo_counts AS (
    SELECT
      date(created_at, 'unixepoch', 'weekday 0', '-7 days') as week_start,
      COUNT(*) as repo_count
    FROM zig_repos
    WHERE language = 'Zig'
    GROUP BY week_start
  )
SELECT
  weeks.week_start,
  COALESCE(repo_counts.repo_count, 0) as repo_count
FROM weeks
LEFT JOIN repo_counts ON weeks.week_start = repo_counts.week_start
ORDER BY weeks.week_start;
-- top 10 most depended-on packages
WITH url_dependencies AS (
  SELECT
    REPLACE(name, '-', '_') as normalized_name,
    COUNT(*) as url_dependency_count,
    COUNT(DISTINCT url_dependency_hash) as hash_version_count
  FROM zig_repo_dependencies
  WHERE dependency_type = 'url'
  GROUP BY normalized_name
),
path_dependencies AS (
  SELECT
    REPLACE(name, '-', '_') as normalized_name,
    COUNT(*) as path_dependency_count
  FROM zig_repo_dependencies
  WHERE dependency_type = 'path'
  GROUP BY normalized_name
),
top_10_dependencies AS (
  SELECT
    COALESCE(u.normalized_name, p.normalized_name) as normalized_name,
    COALESCE(u.url_dependency_count, 0) + COALESCE(p.path_dependency_count, 0) as total_dependency_count
  FROM url_dependencies u
  FULL OUTER JOIN path_dependencies p ON u.normalized_name = p.normalized_name
  ORDER BY total_dependency_count DESC
  LIMIT 10
)
SELECT
  json_array(
    json_group_array(normalized_name)
  ) as json_names
FROM top_10_dependencies;
-- top 5 most depended-on packages by path and URL
WITH normalized_dependencies AS (
  SELECT
    REPLACE(name, '-', '_') AS normalized_name,
    dependency_type,
    full_name
  FROM zig_repo_dependencies
),
top_path_dependencies AS (
  SELECT
    normalized_name,
    COUNT(*) AS dependency_count
  FROM normalized_dependencies
  WHERE dependency_type = 'path'
  GROUP BY normalized_name
  ORDER BY dependency_count DESC
  LIMIT 5
),
top_url_dependencies AS (
  SELECT
    normalized_name,
    COUNT(*) AS dependency_count
  FROM normalized_dependencies
  WHERE dependency_type = 'url'
  GROUP BY normalized_name
  ORDER BY dependency_count DESC
  LIMIT 5
)
SELECT 'Path' AS dependency_type, normalized_name, dependency_count
FROM top_path_dependencies
UNION ALL
SELECT 'URL' AS dependency_type, normalized_name, dependency_count
FROM top_url_dependencies
ORDER BY dependency_type, dependency_count DESC;
-- C/C++ repos with build.zig
SELECT full_name
FROM zig_repos
WHERE language IN ('C++', 'C') AND build_zig_exists = 1
ORDER BY stars DESC
LIMIT 20;
-- zero-dependency Zig projects: manually checked
-- because some projects don't list dependencies on their build.zig.zon
-- repos created before Zig 0.11 with build.zig.zon
SELECT
  full_name
FROM zig_repos
WHERE
  created_at < 1691107200 -- 2023-08-04
  AND build_zig_zon_exists = 1
ORDER BY stars DESC
LIMIT 20;
-- zig projects with dependencies but no build.zig.zon: manually checked
-- repos with build.zig.zon but no build.zig
SELECT full_name
FROM zig_repos
WHERE build_zig_exists = 0
  AND build_zig_zon_exists = 1;

Zon files that fail to parse: I found that out by grepping Ziglist's logs :)