Can we get a metadata file with a specific revision or hash?
I'm currently trying to create a Nixified Haskell package set using plan.json. This is the approach I'm taking.
I need to make the process of fetching packages from Hackage using plan.json reproducible. However, there are currently these issues:
The cabal file of the network-uri package fetched through the URI like https://hackage.haskell.org/package/network-uri-2.4.2.2/network-uri-2.4.2.2.tar.gz is the originally uploaded version, not the latest revision. This is fine since the pkg-src-sha256 in plan.json also points to the hash of the original version.
I discovered that I can fetch the latest revision via https://hackage.haskell.org/package/network-uri-2.4.2.2/network-uri.cabal. The hash of that cabal file matches the pkg-cabal-sha256 in plan.json.
To make builds perfectly reproducible, I need to be able to fetch a specific version of the .cabal file rather than just the latest revision. Does Hackage currently provide a way to do this?
Yes, look e.g. at https://hackage.haskell.org/package/network-uri-2.6.4.2/revisions/
I don't think there's url to fetch using a hash, only using a revision number.
I was aware of that page. Then, the best approach possible by now is fetching that page and parsing HTML to find out the matching revision?
I think it would be better if we have a more machine-readable format provided also.
Search the api page for “revision” and you’ll see the data is available in json if the request specifies the content type. there’s some other revision resources in json there too: https://hackage.haskell.org/api
FWIW, the 01-index.tar locally on your machine is the most machine readable format you can have. You don't need to access Hackage at all (other than to get actual source tarballs). E.g. https://hackage-content.haskell.org/package/cabal-install-parsers-0.6.3/docs/Cabal-Index.html#v:indexMetadata constructs the latest view of the package index. PackageInfo has the revision number, so if you want you can use that to construct URLs to fetch .cabal files, alternatively you can simply embed them in the derivations.
I'm trying to emulate the cabal fetch behavior only with Nix, so I think the best approach available now is fetching /package/:pacakge/revision in JSON to get the index from the hash of the revision and applying to the .tar.gz extraction of /package/:package/:pacakge.tar.gz
I'm trying to emulate the cabal fetch
cabal fetch doesn't not fetch .cabal files from Hackage. They come from the 01-index.tar file:
% cabal get -v assoc
Reading available packages of hackage.haskell.org...
Using most recent state specified from most recent cabal update
index-state(hackage.haskell.org) = 2025-08-02T19:59:02Z
assoc-1.1.1 has already been downloaded.
Unpacking to assoc-1.1.1/
Updating assoc-1.1.1/assoc.cabal with the latest revision from the index.
Ah, I should say cabal get. I mean, the fetch from Hackage server behavior or cabal.
I realized that I can't fetch the revisions JSON file reliably with a hash. I mean. There's no hash for revisions JSON provided in plan.json. This makes it impossible to fetch revisions JSON from Nix's fetchurl.
How about allowing downloading a metadata file with a specific revision with its hash? like /package/network-uri-2.6.4.2/network-uri.cabal?sha256=....? If I create a PR for this, do you think it's acceptable?
@bglgwyng your aims seem roughly similar to what Stackages people has been doing with https://github.com/commercialhaskell/all-cabal-tool and related repos (esp all-cabal-hashes). Could that help you?
I don't think that we would want to extend hackage with hash-based downloads -- it already provides a number of mechanisms for retrieving packages and cabal revisions both, as detailed above. Further, we know that nix can make use of these mechanisms effectively, e.g. in overrideCabal and callHackageDirect both: https://magnus.therning.org/2024-04-20-update-to-hackage-revisions-in-nix.html
As such, modified versions of these functions could certainly be used to fetch but not build, without the need of changes on the hackage server.
@ulysses4ever @gbaz Thanks for sharing! From my understanding, both methods are based on the pre-fetched Hackage metadata before evaluating the Nix script. Aren't they?
https://github.com/bglgwyng/plan2flake/blob/main/flake.nix#L48-L82 This is my approach, and it doesn't require any pre-fetched metadata. But it has problems I described above.
I'm not sure what you're getting at with pre-fetched metadata? Do you mean via use of cabal itself?
I think a good approach could be just using the sdistTarball function from here: https://ryantm.github.io/nixpkgs/languages-frameworks/haskell/ -- that should handle revisions and versions etc for you in a fairly robust and well supported way?
Sorry, I misunderstood callHacakgeDirectly. However, it requires rev.revision to determine the metadata revision, which is missing from plan.json. Hmm... Should plan.json provide revision then, and the problem is in cabal?
when cabal generates a plan.json it will always use the latest metadata revision (unless it is explicitly passed timestamp telling it to do otherwise), which is also what callHackageDirectly will use, and which is available by fetching the cabal file from hackage directly. So if your code assumes the plan.json is generated in proximity (timewise) to when your code is run, then it should be safe for you to always assume the latest revision as well.
Yes. I agree. In most cases, it doesn't matter, and re-generating plan.json is a working solution. However, I want to utilize plan.json as Cargo.lock in Rust, so I'm looking for a way to fetch the specific revision in Nix.