Whats the best way and/or what would you recommend as a solution/direction for resolving dependencies in a package manager?

Both run dependencies (libs) and build dependencies. I would prefer a simple,
solution that is fast and scales well if it’s possible.

To give some context, I thought it would be fun to write a package manager to replace homebrew. In its current state it’s at a ‘Hello World’ level, both figuratively and literally as it can build GNU Hello.
Homebrew uses Ruby both for the package manager and manifests(formulas),
which isn’t exactly the fastest language in the world, I (obviously) chose to use zig to code
the package manager and then lua for the manifests becasue its easy to embed and I like it.

As of the time of writing this the manifests has the following look,

return pkg {
  name     = "hello",
  version  = "2.12.0",
  desc     = "Program providing model for GNU coding standards and practices",
  homepage = "https://www.gnu.org/software/hello/",
  url      = "https://ftp.gnu.org/gnu/hello/hello-2.12.tar.gz",
  hash     = "ce20127416c48b9e6a7025ea9e7ced637802b6c96262aa59e6ebb7e673a00374",
  license  = "GNU GPLv3 or later",
  build    = function(b)
    if b.os == "macos" then
      assert(b.env.set("LDFLAGS", "-liconv"))
    end

    assert(b.run("./configure",
      "--disable-dependency-tracking",
      "--disable-silent-rules",
      "--prefix=" .. b.prefix
    ))

    assert(b.run("make", "install"))

    local license_path = b.prefix .. "/share/licenses"
    assert(b.run("mkdir", "-p", license_path))
    assert(b.run("cp", "COPYING", license_path))
  end
}

which is more or less a blatant ripoff of the corresponding homebrew formula.
Prebuilt binaries will be hosted on a simple server where each binary is signed
using minisign.

1 Like

I wrote a bunch about dependencies in What Is a dependency?

I do find build.zig.zon approach interesting:

  • A specific version of dependency is identified by the checksum of its content (you can download dependencies without trusting the distributor, transparent caching is possible, the class of errors where there are several “versions” of the same dependency goes away)
  • Dependency declaration “calls” dependency by its checksum, and is itself hashed to define current package checksum (you get cryptographic dependency pining without lock files)
  • Dependency declaration also includes a suggested list of URL you can use to fetch dependency (this obviates the need for central server, while checksums still allow centralized caching and decentralized vending).
  • There’s also a notion of dependency name/id and a version, which allows authors to express compatibility intention across versions (although all dependencies are also pinned, there’s dependency deduplication across graph of dependencies. If A uses C 1.0.0, and B uses C 1.1.0, that gets deduped just to C 1.1.0. There is semver version resolution, but it is scoped to the set of versions transitively reachable from the root)

I personally use nix on both linux and mac. Though if you are making zig projects, I prefer to not rely on system packages at all. Doing that makes both cross-compiling simpler and your builds easily repeatable by anyone. Even if you link to system libraries in the end, I still prefer providing stubs or dlopen/dlsym wrappers just to keep compilation simple.

Thank you! Will take a closer look on how zig does it and see where I end up. Also I always come back having learned something new from reading your blog, great resource!

I use nixos which I really like since I don’t daily drive it and it’s nice to be able to come back once in a while and see what the hell and how I installed stuff. Tried it on mac but it never stuck for me as it was a bit more of a hurdle but yeah nix is awesome. For zig projects I only depend on the zig pm, awesome work by the team.