├── .envrc ├── .github └── workflows │ ├── ci.dhall │ ├── ci.dhall.frozen │ ├── ci.sh │ └── ci.yaml ├── .gitignore ├── LICENSE ├── README.md ├── cabal.project ├── cabal.project.local.ci ├── core-simple.png ├── default.nix ├── docs ├── 00-Prelude.org ├── 01-Contributors.org └── 02-Hacking.org ├── hie.yaml ├── hnix-store-core ├── CHANGELOG.md ├── LICENSE ├── README.md ├── hnix-store-core.cabal ├── src │ └── System │ │ └── Nix │ │ ├── Base.hs │ │ ├── Base32.hs │ │ ├── Build.hs │ │ ├── ContentAddress.hs │ │ ├── Derivation.hs │ │ ├── DerivedPath.hs │ │ ├── FileContentAddress.hs │ │ ├── Fingerprint.hs │ │ ├── Hash.hs │ │ ├── Hash │ │ └── Truncation.hs │ │ ├── OutputName.hs │ │ ├── Realisation.hs │ │ ├── Signature.hs │ │ ├── Store │ │ └── Types.hs │ │ ├── StorePath.hs │ │ └── StorePath │ │ └── Metadata.hs └── tests │ ├── Derivation.hs │ ├── Driver.hs │ ├── Fingerprint.hs │ ├── Hash.hs │ ├── Signature.hs │ ├── StorePath.hs │ └── samples │ ├── example0.actual │ ├── example0.drv │ ├── example1.actual │ └── example1.drv ├── hnix-store-db ├── CHANGELOG.md ├── LICENSE ├── README.lhs ├── README.md ├── apps │ └── Bench.hs ├── hnix-store-db.cabal ├── src │ └── System │ │ └── Nix │ │ └── Store │ │ ├── DB.hs │ │ └── DB │ │ ├── Instances.hs │ │ ├── Query.hs │ │ ├── Run.hs │ │ ├── Schema.hs │ │ └── Util.hs └── tests │ └── Smoke.hs ├── hnix-store-json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── hnix-store-json.cabal ├── src │ └── System │ │ └── Nix │ │ └── JSON.hs └── tests │ ├── JSONSpec.hs │ └── Spec.hs ├── hnix-store-nar ├── CHANGELOG.md ├── LICENSE ├── README.md ├── hnix-store-nar.cabal ├── src │ └── System │ │ └── Nix │ │ ├── Nar.hs │ │ └── Nar │ │ ├── Effects.hs │ │ ├── Options.hs │ │ ├── Parser.hs │ │ └── Streamer.hs └── tests │ ├── Driver.hs │ ├── NarFormat.hs │ └── fixtures │ ├── .gitignore │ ├── case-conflict.nar │ └── generate-fixtures.sh ├── hnix-store-readonly ├── CHANGELOG.md ├── LICENSE ├── README.md ├── hnix-store-readonly.cabal ├── src │ └── System │ │ └── Nix │ │ └── Store │ │ └── ReadOnly.hs └── tests │ ├── ReadOnlySpec.hs │ └── Spec.hs ├── hnix-store-remote ├── CHANGELOG.md ├── LICENSE ├── README.lhs ├── README.md ├── app │ └── BuildDerivation.hs ├── hnix-store-remote.cabal ├── src │ ├── Data │ │ ├── Serializer.hs │ │ └── Serializer │ │ │ └── Example.hs │ └── System │ │ └── Nix │ │ └── Store │ │ ├── Remote.hs │ │ └── Remote │ │ ├── Arbitrary.hs │ │ ├── Client.hs │ │ ├── Client │ │ └── Core.hs │ │ ├── Logger.hs │ │ ├── MonadStore.hs │ │ ├── Serializer.hs │ │ ├── Server.hs │ │ ├── Socket.hs │ │ ├── Types.hs │ │ └── Types │ │ ├── Activity.hs │ │ ├── CheckMode.hs │ │ ├── GC.hs │ │ ├── Handshake.hs │ │ ├── Logger.hs │ │ ├── NoReply.hs │ │ ├── ProtoVersion.hs │ │ ├── Query.hs │ │ ├── Query │ │ └── Missing.hs │ │ ├── StoreConfig.hs │ │ ├── StoreReply.hs │ │ ├── StoreRequest.hs │ │ ├── StoreText.hs │ │ ├── SubstituteMode.hs │ │ ├── SuccessCodeReply.hs │ │ ├── TrustedFlag.hs │ │ ├── Verbosity.hs │ │ ├── WorkerMagic.hs │ │ └── WorkerOp.hs ├── tests-io │ ├── DataSink.hs │ ├── Main.hs │ ├── NixDaemonSpec.hs │ └── SampleNar.hs └── tests │ ├── Data │ └── SerializerSpec.hs │ ├── Driver.hs │ ├── EnumSpec.hs │ └── NixSerializerSpec.hs ├── hnix-store-tests ├── CHANGELOG.md ├── LICENSE ├── README.md ├── hnix-store-tests.cabal ├── src │ ├── Data │ │ ├── ByteString │ │ │ └── Arbitrary.hs │ │ ├── HashSet │ │ │ └── Arbitrary.hs │ │ ├── Text │ │ │ └── Arbitrary.hs │ │ └── Vector │ │ │ └── Arbitrary.hs │ ├── System │ │ └── Nix │ │ │ ├── Arbitrary.hs │ │ │ └── Arbitrary │ │ │ ├── Base.hs │ │ │ ├── Build.hs │ │ │ ├── ContentAddress.hs │ │ │ ├── Derivation.hs │ │ │ ├── DerivedPath.hs │ │ │ ├── FileContentAddress.hs │ │ │ ├── Hash.hs │ │ │ ├── OutputName.hs │ │ │ ├── Realisation.hs │ │ │ ├── Signature.hs │ │ │ ├── Store │ │ │ └── Types.hs │ │ │ ├── StorePath.hs │ │ │ ├── StorePath │ │ │ └── Metadata.hs │ │ │ └── UTCTime.hs │ └── Test │ │ └── Hspec │ │ └── Nix.hs └── tests │ ├── BaseEncodingSpec.hs │ ├── ContentAddressSpec.hs │ ├── DerivationSpec.hs │ ├── DerivedPathSpec.hs │ ├── RealisationSpec.hs │ ├── SignatureSpec.hs │ ├── Spec.hs │ └── StorePathSpec.hs ├── matrix.nix ├── overlay.nix └── shell.nix /.envrc: -------------------------------------------------------------------------------- 1 | eval "$(lorri direnv)" 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.dhall: -------------------------------------------------------------------------------- 1 | let haskellCi = 2 | https://raw.githubusercontent.com/sorki/github-actions-dhall/main/haskell-ci.dhall 3 | 4 | let defSteps = haskellCi.defaultCabalSteps 5 | 6 | in haskellCi.generalCi 7 | ( haskellCi.withNix 8 | ( defSteps 9 | with docStep = None haskellCi.BuildStep 10 | with extraSteps.pre 11 | = 12 | defSteps.extraSteps.pre 13 | # [ haskellCi.installCachixStep "hnix-store" ] 14 | ) 15 | ) 16 | haskellCi.DhallMatrix::{ 17 | , ghc = [ haskellCi.GHC.GHC982, haskellCi.GHC.GHC966 ] 18 | , os = [ haskellCi.OS.Ubuntu, haskellCi.OS.MacOS ] 19 | } 20 | : haskellCi.CI.Type 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.dhall.frozen: -------------------------------------------------------------------------------- 1 | let haskellCi = 2 | https://raw.githubusercontent.com/sorki/github-actions-dhall/main/haskell-ci.dhall 3 | sha256:eff4d52e4243777fbe2c800778b24092ace8cd83165cf46b90d5c22e3ebfdd81 4 | 5 | let defSteps = haskellCi.defaultCabalSteps 6 | 7 | in haskellCi.generalCi 8 | ( haskellCi.withNix 9 | ( defSteps 10 | with docStep = None haskellCi.BuildStep 11 | with extraSteps.pre 12 | = 13 | defSteps.extraSteps.pre 14 | # [ haskellCi.installCachixStep "hnix-store" ] 15 | ) 16 | ) 17 | haskellCi.DhallMatrix::{ 18 | , ghc = [ haskellCi.GHC.GHC982, haskellCi.GHC.GHC966 ] 19 | , os = [ haskellCi.OS.Ubuntu, haskellCi.OS.MacOS ] 20 | } 21 | : haskellCi.CI.Type 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Script by @fisx 3 | 4 | set -eo pipefail 5 | cd "$( dirname "${BASH_SOURCE[0]}" )" 6 | 7 | which dhall || cabal install dhall 8 | which dhall-to-yaml || cabal install dhall-yaml 9 | 10 | echo "dhall format-ing ci.dhall" 11 | dhall format ci.dhall 12 | echo "cp haskellCi.dhall -> ci.dhall.frozen" 13 | cp ci.dhall ci.dhall.frozen 14 | echo "dhall freez-ing ci.dhall.frozen" 15 | dhall freeze ci.dhall.frozen 16 | echo "regenerating ci.yaml" 17 | dhall-to-yaml-ng --generated-comment --file ci.dhall > ci.yaml 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # Code generated by dhall-to-yaml. DO NOT EDIT. 2 | jobs: 3 | build: 4 | name: "GHC ${{ matrix.ghc }}, Cabal ${{ matrix.cabal }}, OS ${{ matrix.os }}" 5 | "runs-on": "${{ matrix.os }}" 6 | steps: 7 | - uses: "cachix/install-nix-action@v27" 8 | with: 9 | nix_path: "nixpkgs=channel:nixos-unstable" 10 | - uses: "cachix/cachix-action@v15" 11 | with: 12 | name: "hnix-store" 13 | signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}" 14 | - uses: "actions/checkout@v4" 15 | with: 16 | submodules: recursive 17 | - id: "setup-haskell-cabal" 18 | uses: "haskell-actions/setup@v2" 19 | with: 20 | "cabal-version": "${{ matrix.cabal }}" 21 | "ghc-version": "${{ matrix.ghc }}" 22 | - name: Update Hackage repository 23 | run: cabal update 24 | - name: cabal.project.local.ci 25 | run: | 26 | if [ -e cabal.project.local.ci ]; then 27 | cp cabal.project.local.ci cabal.project.local 28 | fi 29 | - name: freeze 30 | run: "cabal freeze --enable-tests --enable-benchmarks" 31 | - uses: "actions/cache@v4" 32 | with: 33 | key: "${{ matrix.os }}-${{ matrix.ghc }}-${{ matrix.cabal}}-${{ hashFiles('cabal.project.freeze') }}" 34 | path: | 35 | ${{ steps.setup-haskell-cabal.outputs.cabal-store }} 36 | dist-newstyle 37 | - name: Install dependencies 38 | run: "cabal build all --enable-tests --enable-benchmarks --only-dependencies" 39 | - name: build all 40 | run: "cabal build all --enable-tests --enable-benchmarks" 41 | - name: test all 42 | run: "cabal test all --enable-tests" 43 | - name: Build with Nix 44 | run: "nix-build --argstr compiler $(echo ghc${{ matrix.ghc }} | tr -d '.')" 45 | strategy: 46 | matrix: 47 | cabal: 48 | - '3.12' 49 | ghc: 50 | - '9.8.2' 51 | - '9.6.6' 52 | os: 53 | - "ubuntu-latest" 54 | - "macos-latest" 55 | name: Haskell CI 56 | 'on': 57 | pull_request: {} 58 | push: {} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-newstyle 3 | .ghc.environment* 4 | .ghci 5 | .ghci_history 6 | .direnv 7 | .envrc 8 | cabal.project.local~ 9 | cabal.project.local 10 | attic 11 | TAGS 12 | tags 13 | result 14 | result-* 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hnix-store 2 | 3 | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/haskell-nix/hnix-store/ci.yaml?branch=master)](https://github.com/haskell-nix/hnix-store/actions/workflows/ci.yaml) 4 | 5 | A Haskell interface to the [Nix] store. 6 | 7 | [Nix]: https://nixos.org/nix 8 | 9 | ## Rationale 10 | 11 | `Nix` can conceptually be broken up into two layers, both (perhaps 12 | unfortunately) named "Nix": The expression language and the store. 13 | The semantics of the expression language fundamentally depend on the 14 | store, but the store is independent of the language. The store 15 | semantics provide the basic building blocks of `Nix`: 16 | content-addressed files and directories, the drv file format and the 17 | semantics for building drvs, tracking references of store paths, 18 | copying files between stores (or to/from caches), distributed builds, 19 | etc. 20 | 21 | The goal of `hnix-store` is to provide a Haskell interface to the Nix 22 | store semantics, as well as various implementations of that interface. 23 | Though the current primary client is [hnix], an effort to reimplement 24 | the `Nix` expression language in Haskell, this project is meant to be 25 | generic and could be used for a number of other cases of interaction 26 | with the `Nix` store (e.g. a `shake` backend that emitted each build 27 | action as a store derivation). Currently, there are three 28 | implementations planned: 29 | 30 | * A `mock` store which performs no IO whatsoever, for unit testing. 31 | * A `readonly` store, which defers to another implementation for 32 | readonly effects (such as querying whether some path is valid in the 33 | store, or reading a file) but performs mutating effects in-memory 34 | only (for example, computing the store path a given directory would 35 | live at if added to the store, without actually modifying anything). 36 | * A `remote` store, which implements the client side of the `Nix` 37 | daemon Unix domain socket protocol, allowing full interaction with 38 | the store on a system with the C++ daemon installed. 39 | 40 | [hnix]: https://github.com/haskell-nix/hnix 41 | 42 | ## Packages 43 | 44 | In the interest of separating concerns, this project is split into 45 | several Haskell packages. 46 | 47 | ### [hnix-store-core] 48 | 49 | [![Hackage version](https://img.shields.io/hackage/v/hnix-store-core.svg?color=success)](https://hackage.haskell.org/package/hnix-store-core) 50 | [![Dependencies](https://img.shields.io/hackage-deps/v/hnix-store-core?label=Dependencies)](https://packdeps.haskellers.com/feed?needle=hnix-store-core) 51 | 52 | Contains the core types and 53 | fundamental operations, agnostic to any particular 54 | effectful implementation (e.g. in-memory, talking to the Nix daemon in 55 | IO, etc.), with the actual implementations in a different package. 56 | 57 | The intent is that core business logic for a project that needs to 58 | interact with the `Nix` store can simply depend on `hnix-store-core`, 59 | and only at the very edges of the system would it be necessary to 60 | bring in a specific implementation. 61 | 62 | ### [hnix-store-db] 63 | 64 | [![Hackage version](https://img.shields.io/hackage/v/hnix-store-db.svg?color=success)](https://hackage.haskell.org/package/hnix-store-db) 65 | [![Dependencies](https://img.shields.io/hackage-deps/v/hnix-store-db?label=Dependencies)](https://packdeps.haskellers.com/feed?needle=hnix-store-db) 66 | 67 | Implementation of the `Nix` store SQLite database. 68 | 69 | ### [hnix-store-json] 70 | 71 | [![Hackage version](https://img.shields.io/hackage/v/hnix-store-json.svg?color=success)](https://hackage.haskell.org/package/hnix-store-json) 72 | [![Dependencies](https://img.shields.io/hackage-deps/v/hnix-store-json?label=Dependencies)](https://packdeps.haskellers.com/feed?needle=hnix-store-json) 73 | 74 | `Aeson` instances for core types, required for remote store protocol. 75 | 76 | ### [hnix-store-nar] 77 | 78 | [![Hackage version](https://img.shields.io/hackage/v/hnix-store-nar.svg?color=success)](https://hackage.haskell.org/package/hnix-store-nar) 79 | [![Dependencies](https://img.shields.io/hackage-deps/v/hnix-store-nar?label=Dependencies)](https://packdeps.haskellers.com/feed?needle=hnix-store-nar) 80 | 81 | Packing and unpacking for NAR file format used by Nix. 82 | 83 | ### [hnix-store-readonly] 84 | 85 | [![Hackage version](https://img.shields.io/hackage/v/hnix-store-readonly.svg?color=success)](https://hackage.haskell.org/package/hnix-store-readonly) 86 | [![Dependencies](https://img.shields.io/hackage-deps/v/hnix-store-readonly?label=Dependencies)](https://packdeps.haskellers.com/feed?needle=hnix-store-readonly) 87 | 88 | Path computation without interaction with the actual `Nix` store 89 | 90 | ### [hnix-store-remote] 91 | 92 | [![Hackage version](https://img.shields.io/hackage/v/hnix-store-remote.svg?color=success)](https://hackage.haskell.org/package/hnix-store-remote) 93 | [![Dependencies](https://img.shields.io/hackage-deps/v/hnix-store-remote?label=Dependencies)](https://packdeps.haskellers.com/feed?needle=hnix-store-remote) 94 | 95 | [Nix] worker protocol implementation for interacting with remote Nix store 96 | via `nix-daemon`. 97 | 98 | ### [hnix-store-tests] 99 | 100 | [![Hackage version](https://img.shields.io/hackage/v/hnix-store-tests.svg?color=success)](https://hackage.haskell.org/package/hnix-store-tests) 101 | [![Dependencies](https://img.shields.io/hackage-deps/v/hnix-store-tests?label=Dependencies)](https://packdeps.haskellers.com/feed?needle=hnix-store-tests) 102 | 103 | Aribtrary instances and utilities for testing. 104 | 105 | [hnix-store-core]: ./hnix-store-core 106 | [hnix-store-db]: ./hnix-store-db 107 | [hnix-store-json]: ./hnix-store-json 108 | [hnix-store-nar]: ./hnix-store-nar 109 | [hnix-store-readonly]: ./hnix-store-readonly 110 | [hnix-store-remote]: ./hnix-store-remote 111 | [hnix-store-tests]: ./hnix-store-tests 112 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | tests: true 2 | benchmarks: true 3 | 4 | packages: 5 | ./hnix-store-core/hnix-store-core.cabal 6 | ./hnix-store-db/hnix-store-db.cabal 7 | ./hnix-store-json/hnix-store-json.cabal 8 | ./hnix-store-nar/hnix-store-nar.cabal 9 | ./hnix-store-readonly/hnix-store-readonly.cabal 10 | ./hnix-store-remote/hnix-store-remote.cabal 11 | ./hnix-store-tests/hnix-store-tests.cabal 12 | 13 | -- till https://github.com/obsidiansystems/dependent-sum/pull/80 14 | allow-newer: 15 | dependent-sum:some 16 | 17 | package hnix-store-db 18 | flags: +build-readme +build-bench 19 | 20 | package hnix-store-nar 21 | flags: +bounded_memory 22 | 23 | package hnix-store-remote 24 | flags: +build-derivation +build-readme +io-testsuite 25 | -------------------------------------------------------------------------------- /cabal.project.local.ci: -------------------------------------------------------------------------------- 1 | package hnix-store-core 2 | ghc-options: -Wunused-packages -Wall -Werror 3 | 4 | package hnix-store-db 5 | ghc-options: -Wunused-packages -Wall -Werror 6 | 7 | package hnix-store-json 8 | ghc-options: -Wunused-packages -Wall -Werror 9 | 10 | package hnix-store-nar 11 | ghc-options: -Wunused-packages -Wall -Werror 12 | 13 | package hnix-store-readonly 14 | ghc-options: -Wunused-packages -Wall -Werror 15 | 16 | package hnix-store-remote 17 | ghc-options: -Wunused-packages -Wall -Werror 18 | 19 | package hnix-store-tests 20 | ghc-options: -Wunused-packages -Wall -Werror 21 | -------------------------------------------------------------------------------- /core-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haskell-nix/hnix-store/7782e5bc6eec9d7054a86d07fae567ff3c928263/core-simple.png -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} 2 | , compiler ? null 3 | }: 4 | let 5 | lib = pkgs.lib; 6 | overlay = import ./overlay.nix pkgs compiler; 7 | overrideHaskellPackages = orig: { 8 | buildHaskellPackages = 9 | orig.buildHaskellPackages.override overrideHaskellPackages; 10 | overrides = if orig ? overrides 11 | then pkgs.lib.composeExtensions orig.overrides overlay 12 | else overlay; 13 | }; 14 | 15 | packageSet = 16 | if compiler == null 17 | then pkgs.haskellPackages 18 | else pkgs.haskell.packages.${compiler}; 19 | 20 | haskellPackages = packageSet.override overrideHaskellPackages; 21 | in { 22 | inherit (haskellPackages) 23 | hnix-store-core 24 | hnix-store-db 25 | hnix-store-json 26 | hnix-store-nar 27 | hnix-store-readonly 28 | hnix-store-remote 29 | hnix-store-tests; 30 | haskellPackages = lib.dontRecurseIntoAttrs haskellPackages; 31 | pkgs = lib.dontRecurseIntoAttrs pkgs; 32 | } 33 | -------------------------------------------------------------------------------- /docs/00-Prelude.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Prelude 2 | 3 | * Intro 4 | 5 | ~hnix-store~ project was founded in 2018 by Shea Levy as a companion 6 | to [[https://github.com/haskell-nix/hnix/][hnix]] project, which is its primary user. It lives its own life 7 | separate from ~hnix~ repository to prevent cross coupling 8 | between the two components which should allow for store to be used 9 | as a standalone interface to [[https://github.com/NixOS/nix][Nix]] stores. 10 | 11 | * Direct users 12 | 13 | Following projects are known to use ~hnix-store~ directly: 14 | 15 | ** [[https://github.com/haskell-nix/hnix/][hnix]] 16 | ** [[https://github.com/cachix/cachix/][cachix]] 17 | + NAR serialization 18 | ** [[https://git.sr.ht/~jack/nix-freeze-tree/][nix-freeze-tree]] 19 | + Digest computation 20 | + NAR serialization 21 | * Index 22 | 23 | List of packages that are part of this ad-hoc wiki 24 | ** [[./01-Contributors.org][01-Contributors]] 25 | ** [[./02-Hacking.org][02-Hacking]] 26 | -------------------------------------------------------------------------------- /docs/01-Contributors.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Contributors 2 | 3 | + Previous [[./00-Prelude.org][00-Prelude]] 4 | + Next [[./02-Hacking.org][02-Hacking]] 5 | 6 | Big thanks to our present and past contributors, 7 | in order of appearance: 8 | 9 | + Shea Levy @shlevy 10 | + Gabriella Gonzalez @Gabriella439 11 | + Alex Rozenshteyn @rpglover64 12 | + Greg Hale @imalsogreg 13 | + sorki @srk 14 | + Doug Beardsley @mightybyte 15 | + John Ericson @Ericson2314 16 | + Brian McKenna @puffnfresh 17 | + tv @473 18 | + Drew Hess @dhess 19 | + Guillaume Maudoux @layus 20 | + Anton Latukha @Anton-Latukha 21 | + Tom McLaughlin @thomasjm 22 | + Patrick @soulomoon 23 | + Domen Kožar @domenkozar 24 | + Andrei Borzenkov @s-and-witch 25 | + Sander @sandydoo 26 | + Cale Gibbard @cgibbard 27 | + Dylan Green @cidkidnix 28 | + Luigy Leon @luigy 29 | + squalus @squalus 30 | + Vaibhav Sagar @vaibhavsagar 31 | + Ryan Trinkle @ryantrinkle 32 | + Travis Whitaker @TravisWhitaker 33 | + Andrea Bedini @andreabedini 34 | + Dan Bornside @danbornside 35 | -------------------------------------------------------------------------------- /docs/02-Hacking.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Hacking 2 | 3 | + Previous [[./01-Contributors.org][01-Contributors]] 4 | + Next ~FIXME~ 5 | 6 | * Basic workflow 7 | 8 | Entering the development shell using ~nix-shell~ will pull 9 | in all the dependencies required by all sub-packages. 10 | 11 | Both ~default.nix~ and ~shell.nix~ simply import ~~ from your 12 | ~NIX_PATH~. If this is by chance broken, try using ~nixpkgs-unstable~ which 13 | is targeted by our CI, as it is also the basis for ~haskell-updates~ ~nixpkgs~ branch 14 | where new Hackage packages land. 15 | 16 | Not pinning the to specific version of ~nixpkgs~ is intentional so CI can roll 17 | with the ~unstable~ branch and detect breakage early. 18 | 19 | ** Using another version of GHC 20 | 21 | To use a different version of GHC to the one used 22 | by your ~pkgs.haskellPackages~, pass, for example ~--argstr compiler ghc963~ 23 | to either ~nix-shell~ or ~nix-build~ 24 | 25 | ** ~matrix.nix~ 26 | 27 | ~nix-build matrix-nix~ can be used to check that all our packages 28 | built against all supported GHC versions. It pulls the versions 29 | from [[../.github/workflows/ci.dhall][.github/workflows/ci.dhall]] so there is a single source of truth 30 | and the compilers don't need to be listed separately again. 31 | 32 | * Adding new packages 33 | 34 | When adding a new sub-package, following files need to be updated: 35 | + ~default.nix~ 36 | + ~shell.nix~ 37 | + ~cabal.project~ 38 | + ~cabal.project.local.ci~ (only if needed) 39 | + ~hie.yaml~ 40 | 41 | * CI 42 | 43 | The GitHub Actions CI uses [[https://github.com/sorki/github-actions-dhall][github-actions-dhall]] and [[../.github/workflows/ci.dhall][.github/workflows/ci.dhall]] 44 | to generate the [[../.github/workflows/ci.yaml][.github/workflows/ci.yaml]] workflow file 45 | which does a full matrix build of supported compilers both using ~cabal~ and ~nix~. 46 | 47 | ** Cachix cache 48 | 49 | The CI uses [[https://github.com/cachix/cachix/][cachix]] to not rebuild everything from scratch everytime it runs, it also 50 | feeds the cache available at https://app.cachix.org/cache/hnix-store which you 51 | can configure on your system. 52 | 53 | ** Updating 54 | 55 | To update the CI, edit [[../.github/workflows/ci.dhall][.github/workflows/ci.dhall]] and run [[../.github/workflows/ci.sh][.github/workflows/ci.sh]] 56 | to regenerate the ~yaml~ file. Don't forget to commit both files. 57 | 58 | ** ~cabal.project.local.ci~ 59 | 60 | The [[../cabal.project.local.ci][cabal.project.local.ci]] file is used by the CI workflow to alter ~ghc-options~, 61 | enabling e.g. ~-Werror~ and ~-Wunused-packages~. You can copy this to ~cabal.project.local~ 62 | to have the same build configuration: 63 | 64 | #+begin_src shell 65 | cp cabal.project.local.ci cabal.project.local 66 | #+end_src 67 | 68 | * Changelogs 69 | 70 | Since the packages are used by others as dependencies in production environments, 71 | make sure to add changelog entries for public interface breaking changes. These don't 72 | need to cover all changes if you are sure that the change only affects packages inside 73 | ~hnix-store~. 74 | 75 | * Readmes 76 | 77 | Some ~README.md~ files are symlinked to ~README.lhs~ files and also serve as a buildable executables. 78 | This makes sure they are always up to date and buildable. They are guarded by cabal flags 79 | and only available in development environment which enables all of them via [[../cabal.project][cabal.project]], which 80 | also causes them to be built by CI but not to propagate downstream (and polute ~$PATH~ of downstream users). 81 | 82 | * ~ghcid~ 83 | 84 | ~ghcid~ can be used as a lightweight IDE to assist with edit-compile-run-test cycle. You can obtain it quickly using 85 | ~nix-shell -p haskellPackages.ghcid~ 86 | 87 | ** Running 88 | *** For a single library 89 | 90 | #+begin_src shell 91 | ghcid -c 'cabal repl hnix-store-core' 92 | #+end_src 93 | 94 | *** For a testsuite or executable 95 | 96 | #+begin_src shell 97 | ghcid -c 'cabal repl test-suite:props' 98 | # or 99 | ghcid -c 'cabal repl exe:db-readme' 100 | #+end_src 101 | 102 | Often the specifier like ~exe~ or ~test-suite~ can be omitted when the name is unique. 103 | 104 | *** Running a testsuite after a build 105 | 106 | If working with the testsuite directly, you can invoke 107 | #+begin_src shell 108 | ghcid -c 'cabal repl test-suite:remote' --test 'hspec spec' 109 | #+end_src 110 | 111 | If you are editing a library or you need to run multiple testsuites, you can for example use 112 | 113 | #+begin_src shell 114 | ghcid -c 'cabal repl hnix-store-remote' --test ':! cabal test test-suite:remote && cabal test test-suite:remote-io' 115 | #+end_src 116 | 117 | *** With restarting 118 | 119 | ~ghcid~ can also restart itself so ~ghci~ picks up new or moved files, following incantation can be invoked 120 | when working with ~hnix-store-remote~ to combine all of the features 121 | 122 | #+begin_src shell 123 | ghcid -c 'cabal repl hnix-store-remote' \ 124 | --restart 'hnix-store-remote/hnix-store-remote.cabal' \ 125 | --test ':! cabal test test-suite:remote && cabal test test-suite:remote-io' 126 | #+end_src 127 | -------------------------------------------------------------------------------- /hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: "./hnix-store-core/src" 4 | component: "lib:hnix-store-core" 5 | 6 | - path: "./hnix-store-core/tests" 7 | component: "hnix-store-core:test:core" 8 | 9 | - path: "./hnix-store-db/src" 10 | component: "lib:hnix-store-db" 11 | 12 | - path: "./hnix-store-db/tests" 13 | component: "hnix-store-db:test:db" 14 | 15 | - path: "./hnix-store-json/src" 16 | component: "lib:hnix-store-json" 17 | 18 | - path: "./hnix-store-json/tests" 19 | component: "hnix-store-json:test:json" 20 | 21 | - path: "./hnix-store-nar/src" 22 | component: "lib:hnix-store-nar" 23 | 24 | - path: "./hnix-store-nar/tests" 25 | component: "hnix-store-nar:test:nar" 26 | 27 | - path: "./hnix-store-readonly/src" 28 | component: "lib:hnix-store-readonly" 29 | 30 | - path: "./hnix-store-readonly/tests" 31 | component: "hnix-store-readonly:test:readonly" 32 | 33 | - path: "./hnix-store-remote/src" 34 | component: "lib:hnix-store-remote" 35 | 36 | - path: "./hnix-store-remote/tests" 37 | component: "hnix-store-remote:test:remote" 38 | 39 | - path: "./hnix-store-remote/tests-io" 40 | component: "hnix-store-remote:test:remote-io" 41 | 42 | - path: "./hnix-store-tests/src" 43 | component: "lib:hnix-store-tests" 44 | 45 | - path: "./hnix-store-tests/tests" 46 | component: "hnix-store-tests:test:props" 47 | -------------------------------------------------------------------------------- /hnix-store-core/README.md: -------------------------------------------------------------------------------- 1 | # hnix-store-core 2 | 3 | Core types and functions for interacting with the Nix store. 4 | 5 | [System.Nix.StorePath]: ./src/System/Nix/StorePath.hs 6 | -------------------------------------------------------------------------------- /hnix-store-core/hnix-store-core.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: hnix-store-core 3 | version: 0.8.0.0 4 | synopsis: Core types used for interacting with the Nix store. 5 | description: 6 | This package contains types and functions needed to describe 7 | interactions with the Nix store. 8 | homepage: https://github.com/haskell-nix/hnix-store 9 | license: Apache-2.0 10 | license-file: LICENSE 11 | author: Shea Levy 12 | maintainer: srk@48.io 13 | copyright: 2018 Shea Levy 14 | category: Nix 15 | build-type: Simple 16 | extra-doc-files: 17 | CHANGELOG.md 18 | extra-source-files: 19 | README.md 20 | , tests/samples/example0.drv 21 | , tests/samples/example1.drv 22 | 23 | common commons 24 | ghc-options: -Wall 25 | default-extensions: 26 | ConstraintKinds 27 | , DataKinds 28 | , DeriveGeneric 29 | , DeriveDataTypeable 30 | , DeriveFunctor 31 | , DeriveFoldable 32 | , DeriveTraversable 33 | , DeriveLift 34 | , DerivingStrategies 35 | , DerivingVia 36 | , ExistentialQuantification 37 | , FlexibleContexts 38 | , FlexibleInstances 39 | , GADTs 40 | , ScopedTypeVariables 41 | , StandaloneDeriving 42 | , RecordWildCards 43 | , TypeApplications 44 | , TypeFamilies 45 | , TypeOperators 46 | , TypeSynonymInstances 47 | , InstanceSigs 48 | , KindSignatures 49 | , MultiParamTypeClasses 50 | , MultiWayIf 51 | , TupleSections 52 | , LambdaCase 53 | , BangPatterns 54 | , ViewPatterns 55 | default-language: Haskell2010 56 | 57 | library 58 | import: commons 59 | exposed-modules: 60 | System.Nix.Base 61 | , System.Nix.Base32 62 | , System.Nix.Build 63 | , System.Nix.ContentAddress 64 | , System.Nix.Derivation 65 | , System.Nix.DerivedPath 66 | , System.Nix.Fingerprint 67 | , System.Nix.FileContentAddress 68 | , System.Nix.Hash 69 | , System.Nix.Hash.Truncation 70 | , System.Nix.OutputName 71 | , System.Nix.Realisation 72 | , System.Nix.Signature 73 | , System.Nix.Store.Types 74 | , System.Nix.StorePath 75 | , System.Nix.StorePath.Metadata 76 | build-depends: 77 | base >=4.12 && <5 78 | , attoparsec 79 | , base16-bytestring >= 1.0 80 | , base64-bytestring >= 1.2.1 81 | , bytestring 82 | , containers 83 | , constraints-extras 84 | , crypton 85 | , data-default-class 86 | , dependent-sum > 0.7 87 | , dependent-sum-template >= 0.2.0.1 && < 0.3 88 | , filepath 89 | , hashable 90 | -- Required for crypton low-level type convertion 91 | , memory 92 | , nix-derivation >= 1.1.1 && <2 93 | , some > 1.0.5 && < 2 94 | , time 95 | , text 96 | , unordered-containers 97 | , vector 98 | hs-source-dirs: src 99 | 100 | test-suite core 101 | import: commons 102 | type: exitcode-stdio-1.0 103 | main-is: Driver.hs 104 | other-modules: 105 | Derivation 106 | Fingerprint 107 | Hash 108 | Signature 109 | StorePath 110 | hs-source-dirs: 111 | tests 112 | build-tool-depends: 113 | tasty-discover:tasty-discover 114 | build-depends: 115 | hnix-store-core 116 | , attoparsec 117 | , base 118 | , base16-bytestring 119 | , base64-bytestring 120 | , bytestring 121 | , containers 122 | , crypton 123 | , data-default-class 124 | , hspec 125 | , tasty 126 | , tasty-golden 127 | , tasty-hspec 128 | , text 129 | , time 130 | , unordered-containers 131 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Base.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Base 2 | ( BaseEncoding(Base16,NixBase32,Base64) 3 | , encodeWith 4 | , decodeWith 5 | ) where 6 | 7 | import Data.ByteString (ByteString) 8 | import Data.Text (Text) 9 | import GHC.Generics (Generic) 10 | 11 | import qualified Data.Text.Encoding 12 | import qualified Data.ByteString.Base16 13 | import qualified Data.ByteString.Base64 14 | 15 | import qualified System.Nix.Base32 -- Nix has own Base32 encoding 16 | 17 | -- | Constructors to indicate the base encodings 18 | data BaseEncoding 19 | = NixBase32 20 | -- | ^ Nix has a special map of Base32 encoding 21 | -- Placed first, since it determines Haskell optimizations of pattern matches, 22 | -- & NixBase seems be the most widely used in Nix. 23 | | Base16 24 | | Base64 25 | deriving (Bounded, Eq, Enum, Generic, Ord, Show) 26 | 27 | -- | Encode @ByteString@ with @Base@ encoding, produce @Text@. 28 | encodeWith :: BaseEncoding -> ByteString -> Text 29 | encodeWith Base16 = 30 | Data.Text.Encoding.decodeUtf8 31 | . Data.ByteString.Base16.encode 32 | encodeWith NixBase32 = System.Nix.Base32.encode 33 | encodeWith Base64 = 34 | Data.Text.Encoding.decodeUtf8 35 | . Data.ByteString.Base64.encode 36 | 37 | -- | Take the input & @Base@ encoding witness -> decode into @Text@. 38 | decodeWith :: BaseEncoding -> Text -> Either String ByteString 39 | decodeWith Base16 = 40 | Data.ByteString.Base16.decode 41 | . Data.Text.Encoding.encodeUtf8 42 | decodeWith NixBase32 = System.Nix.Base32.decode 43 | decodeWith Base64 = 44 | Data.ByteString.Base64.decode 45 | . Data.Text.Encoding.encodeUtf8 46 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Base32.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Description: Implementation of Nix's base32 encoding. 3 | -} 4 | module System.Nix.Base32 5 | ( encode 6 | , decode 7 | , digits32 8 | ) where 9 | 10 | import Data.ByteString (ByteString) 11 | import Data.Text (Text) 12 | import Data.Vector (Vector) 13 | import Data.Word (Word8) 14 | 15 | import qualified Data.Bits 16 | import qualified Data.Bool 17 | import qualified Data.ByteString 18 | import qualified Data.ByteString.Char8 19 | import qualified Data.List 20 | import qualified Data.Maybe 21 | import qualified Data.Text 22 | import qualified Data.Vector 23 | import qualified Numeric 24 | 25 | -- omitted: E O U T 26 | digits32 :: Vector Char 27 | digits32 = Data.Vector.fromList "0123456789abcdfghijklmnpqrsvwxyz" 28 | 29 | -- | Encode a 'BS.ByteString' in Nix's base32 encoding 30 | encode :: ByteString -> Text 31 | encode c = Data.Text.pack $ takeCharPosFromDict <$> [nChar - 1, nChar - 2 .. 0] 32 | where 33 | -- Each base32 character gives us 5 bits of information, while 34 | -- each byte gives is 8. Because 'div' rounds down, we need to add 35 | -- one extra character to the result, and because of that extra 1 36 | -- we need to subtract one from the number of bits in the 37 | -- bytestring to cover for the case where the number of bits is 38 | -- already a factor of 5. Thus, the + 1 outside of the 'div' and 39 | -- the - 1 inside of it. 40 | nChar = fromIntegral $ ((Data.ByteString.length c * 8 - 1) `div` 5) + 1 41 | 42 | byte = Data.ByteString.index c . fromIntegral 43 | 44 | -- May need to switch to a more efficient calculation at some 45 | -- point. 46 | bAsInteger :: Integer 47 | bAsInteger = 48 | sum 49 | [ fromIntegral (byte j) * (256 ^ j) 50 | | j <- [0 .. Data.ByteString.length c - 1] ] 51 | 52 | takeCharPosFromDict :: Integer -> Char 53 | takeCharPosFromDict i = digits32 Data.Vector.! digitInd 54 | where 55 | digitInd = 56 | fromIntegral $ 57 | bAsInteger `div` (32^i) `mod` 32 58 | 59 | -- | Decode Nix's base32 encoded text 60 | decode :: Text -> Either String ByteString 61 | decode what = 62 | Data.Bool.bool 63 | (Left "Invalid NixBase32 string") 64 | (unsafeDecode what) 65 | (Data.Text.all (`elem` digits32) what) 66 | 67 | -- | Decode Nix's base32 encoded text 68 | -- Doesn't check if all elements match `digits32` 69 | unsafeDecode :: Text -> Either String ByteString 70 | unsafeDecode what = 71 | case 72 | Numeric.readInt 73 | 32 74 | (`elem` digits32) 75 | (\c -> Data.Maybe.fromMaybe (error "character not in digits32") 76 | $ Data.Vector.findIndex (== c) digits32 77 | ) 78 | (Data.Text.unpack what) 79 | of 80 | [(i, _)] -> pure $ padded $ integerToBS i 81 | x -> Left $ "Can't decode: readInt returned " <> show x 82 | where 83 | padded x 84 | | Data.ByteString.length x < decLen = x `Data.ByteString.append` bstr 85 | | otherwise = x 86 | where 87 | bstr = Data.ByteString.Char8.pack $ take (decLen - Data.ByteString.length x) (cycle "\NUL") 88 | 89 | decLen = Data.Text.length what * 5 `div` 8 90 | 91 | -- | Encode an Integer to a bytestring 92 | -- Similar to Data.Base32String (integerToBS) without `reverse` 93 | integerToBS :: Integer -> ByteString 94 | integerToBS 0 = Data.ByteString.pack [0] 95 | integerToBS i 96 | | i > 0 = Data.ByteString.pack $ Data.List.unfoldr f i 97 | | otherwise = error "integerToBS not defined for negative values" 98 | where 99 | f 0 = Nothing 100 | f x = Just (fromInteger x :: Word8, x `Data.Bits.shiftR` 8) 101 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Build.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Description : Build related types 3 | Maintainer : srk 4 | |-} 5 | module System.Nix.Build 6 | ( BuildMode(..) 7 | , BuildStatus(..) 8 | , buildSuccess 9 | , BuildResult(..) 10 | ) where 11 | 12 | import Data.Map (Map) 13 | import Data.Time (UTCTime) 14 | import Data.Text (Text) 15 | import GHC.Generics (Generic) 16 | 17 | import System.Nix.OutputName (OutputName) 18 | import System.Nix.Realisation (DerivationOutput, Realisation) 19 | 20 | -- | Mode of the build operation 21 | -- Keep the order of these Enums to match enums from reference implementations 22 | -- src/libstore/store-api.hh 23 | data BuildMode 24 | = BuildMode_Normal -- ^ Perform normal build 25 | | BuildMode_Repair -- ^ Try to repair corrupted or missing paths by re-building or re-downloading them 26 | | BuildMode_Check -- ^ Check if the build is reproducible (rebuild and compare to previous build) 27 | deriving (Bounded, Eq, Generic, Ord, Enum, Show) 28 | 29 | -- | Build result status 30 | data BuildStatus = 31 | BuildStatus_Built -- ^ Build performed successfully 32 | | BuildStatus_Substituted -- ^ Path substituted from cache 33 | | BuildStatus_AlreadyValid -- ^ Path is already valid (available in local store) 34 | | BuildStatus_PermanentFailure 35 | | BuildStatus_InputRejected 36 | | BuildStatus_OutputRejected 37 | | BuildStatus_TransientFailure -- ^ Possibly transient build failure 38 | | BuildStatus_CachedFailure -- ^ Obsolete 39 | | BuildStatus_TimedOut -- ^ Build timed out 40 | | BuildStatus_MiscFailure 41 | | BuildStatus_DependencyFailed -- ^ Build dependency failed to build 42 | | BuildStatus_LogLimitExceeded 43 | | BuildStatus_NotDeterministic 44 | | BuildStatus_ResolvesToAlreadyValid 45 | | BuildStatus_NoSubstituters 46 | deriving (Bounded, Eq, Generic, Ord, Enum, Show) 47 | 48 | -- | Result of the build 49 | data BuildResult = BuildResult 50 | { buildResultStatus :: BuildStatus 51 | -- ^ Build status, MiscFailure should be the default 52 | , buildResultErrorMessage :: Maybe Text 53 | -- ^ Possible build error message 54 | , buildResultTimesBuilt :: Maybe Int 55 | -- ^ How many times this build was performed (since 1.29) 56 | , buildResultIsNonDeterministic :: Maybe Bool 57 | -- ^ If timesBuilt > 1, whether some builds did not produce the same result (since 1.29) 58 | , buildResultStartTime :: Maybe UTCTime 59 | -- ^ Start time of this build (since 1.29) 60 | , buildResultStopTime :: Maybe UTCTime 61 | -- ^ Stop time of this build (since 1.29) 62 | , buildResultBuiltOutputs :: Maybe (Map (DerivationOutput OutputName) Realisation) 63 | -- ^ Mapping of the output names to @Realisation@s (since 1.28) 64 | -- (paths with additional info and their dependencies) 65 | } 66 | deriving (Eq, Generic, Ord, Show) 67 | 68 | buildSuccess :: BuildStatus -> Bool 69 | buildSuccess x = 70 | x `elem` 71 | [ BuildStatus_Built 72 | , BuildStatus_Substituted 73 | , BuildStatus_AlreadyValid 74 | ] 75 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/ContentAddress.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module System.Nix.ContentAddress ( 4 | ContentAddress (..) 5 | , ContentAddressMethod (..) 6 | , contentAddressBuilder 7 | , contentAddressParser 8 | , buildContentAddress 9 | , parseContentAddress 10 | ) where 11 | 12 | import Control.Applicative 13 | import Crypto.Hash (Digest) 14 | import Data.Attoparsec.Text (Parser) 15 | import Data.Dependent.Sum (DSum) 16 | import Data.Text (Text) 17 | import Data.Text.Lazy.Builder (Builder) 18 | import GHC.Generics (Generic) 19 | import System.Nix.Hash (HashAlgo) 20 | 21 | import qualified Data.Attoparsec.Text 22 | import qualified Data.Text.Lazy 23 | import qualified Data.Text.Lazy.Builder 24 | import qualified System.Nix.Hash 25 | 26 | data ContentAddressMethod 27 | = ContentAddressMethod_Flat 28 | | ContentAddressMethod_NixArchive 29 | | ContentAddressMethod_Text 30 | -- ^ The path is a plain file added via makeTextPath or 31 | -- addTextToStore. It is addressed according to a sha256sum of the 32 | -- file contents. 33 | deriving (Eq, Generic, Ord, Show) 34 | 35 | -- | An address for a content-addressable store path, i.e. one whose 36 | -- store path hash is purely a function of its contents (as opposed to 37 | -- paths that are derivation outputs, whose hashes are a function of 38 | -- the contents of the derivation file instead). 39 | -- 40 | -- For backwards-compatibility reasons, the same information is 41 | -- encodable in multiple ways, depending on the method used to add the 42 | -- path to the store. These unfortunately result in separate store 43 | -- paths. 44 | data ContentAddress = ContentAddress 45 | ContentAddressMethod 46 | (DSum HashAlgo Digest) 47 | deriving (Eq, Generic, Ord, Show) 48 | 49 | -- | Marshall `ContentAddressableAddress` to `Text` 50 | -- in form suitable for remote protocol usage. 51 | buildContentAddress :: ContentAddress -> Text 52 | buildContentAddress = 53 | Data.Text.Lazy.toStrict 54 | . Data.Text.Lazy.Builder.toLazyText 55 | . contentAddressBuilder 56 | 57 | contentAddressBuilder :: ContentAddress -> Builder 58 | contentAddressBuilder (ContentAddress method digest) = 59 | (case method of 60 | ContentAddressMethod_Text -> "text" 61 | ContentAddressMethod_NixArchive -> "fixed:r" 62 | ContentAddressMethod_Flat -> "fixed" 63 | ) 64 | <> ":" 65 | <> System.Nix.Hash.algoDigestBuilder digest 66 | 67 | -- | Parse `ContentAddressableAddress` from `ByteString` 68 | parseContentAddress 69 | :: Text -> Either String ContentAddress 70 | parseContentAddress = 71 | Data.Attoparsec.Text.parseOnly contentAddressParser 72 | 73 | -- | Parser for content addressable field 74 | contentAddressParser :: Parser ContentAddress 75 | contentAddressParser = do 76 | method <- parseContentAddressMethod 77 | _ <- ":" 78 | digest <- parseTypedDigest 79 | case digest of 80 | Left e -> fail e 81 | Right x -> return $ ContentAddress method x 82 | 83 | parseContentAddressMethod :: Parser ContentAddressMethod 84 | parseContentAddressMethod = 85 | (ContentAddressMethod_Text <$ "text") 86 | <|> (ContentAddressMethod_NixArchive <$ "fixed:r") 87 | <|> (ContentAddressMethod_Flat <$ "fixed") 88 | 89 | parseTypedDigest :: Parser (Either String (DSum HashAlgo Digest)) 90 | parseTypedDigest = System.Nix.Hash.mkNamedDigest <$> parseHashType <*> parseHash 91 | where 92 | parseHashType :: Parser Text 93 | parseHashType = 94 | ("sha256" <|> "sha512" <|> "sha1" <|> "md5") <* (":" <|> "-") 95 | 96 | parseHash :: Parser Text 97 | parseHash = Data.Attoparsec.Text.takeWhile1 (/= ':') 98 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Derivation.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Derivation 2 | ( parseDerivation 3 | , buildDerivation 4 | -- Re-exports 5 | , Derivation(..) 6 | , DerivationOutput(..) 7 | ) where 8 | 9 | import Data.Attoparsec.Text.Lazy (Parser) 10 | import Data.Text (Text) 11 | import Data.Text.Lazy.Builder (Builder) 12 | 13 | import Nix.Derivation (Derivation(..), DerivationOutput(..)) 14 | import System.Nix.StorePath (StoreDir, StorePath) 15 | 16 | import qualified Data.Attoparsec.Text.Lazy 17 | import qualified Data.Text 18 | import qualified Data.Text.Lazy 19 | import qualified Data.Text.Lazy.Builder 20 | 21 | import qualified Nix.Derivation 22 | import qualified System.Nix.StorePath 23 | 24 | parseDerivation :: StoreDir -> Parser (Derivation StorePath Text) 25 | parseDerivation expectedRoot = 26 | Nix.Derivation.parseDerivationWith 27 | pathParser 28 | Nix.Derivation.textParser 29 | where 30 | pathParser = do 31 | text <- Nix.Derivation.textParser 32 | case Data.Attoparsec.Text.Lazy.parseOnly 33 | (System.Nix.StorePath.pathParser expectedRoot) 34 | (Data.Text.Lazy.fromStrict text) 35 | of 36 | Right p -> pure p 37 | Left e -> fail e 38 | 39 | buildDerivation :: StoreDir -> Derivation StorePath Text -> Builder 40 | buildDerivation storeDir = 41 | Nix.Derivation.buildDerivationWith 42 | (string . System.Nix.StorePath.storePathToText storeDir) 43 | string 44 | where 45 | string = Data.Text.Lazy.Builder.fromText . Data.Text.pack . show 46 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/DerivedPath.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module System.Nix.DerivedPath ( 4 | OutputsSpec(..) 5 | , DerivedPath(..) 6 | , ParseOutputsError(..) 7 | , parseOutputsSpec 8 | , outputsSpecToText 9 | , parseDerivedPath 10 | , derivedPathToText 11 | ) where 12 | 13 | import GHC.Generics (Generic) 14 | import Data.Set (Set) 15 | import Data.Text (Text) 16 | import System.Nix.OutputName (OutputName, InvalidNameError) 17 | import System.Nix.StorePath (StoreDir(..), StorePath, InvalidPathError) 18 | 19 | import qualified Data.Bifunctor 20 | import qualified Data.ByteString.Char8 21 | import qualified Data.Set 22 | import qualified Data.Text 23 | import qualified System.Nix.OutputName 24 | import qualified System.Nix.StorePath 25 | 26 | data OutputsSpec = 27 | OutputsSpec_All 28 | -- ^ Wildcard spec (^*) meaning all outputs 29 | | OutputsSpec_Names (Set OutputName) 30 | -- ^ Set of specific outputs 31 | deriving (Eq, Generic, Ord, Show) 32 | 33 | data DerivedPath = 34 | DerivedPath_Opaque StorePath 35 | -- ^ Fully evaluated store path that can't be built 36 | -- but can be fetched 37 | | DerivedPath_Built StorePath OutputsSpec 38 | -- ^ Derivation path and the outputs built from it 39 | deriving (Eq, Generic, Ord, Show) 40 | 41 | data ParseOutputsError = 42 | ParseOutputsError_InvalidPath InvalidPathError 43 | | ParseOutputsError_InvalidName InvalidNameError 44 | | ParseOutputsError_NoNames 45 | | ParseOutputsError_NoPrefix StoreDir Text 46 | deriving (Eq, Ord, Show) 47 | 48 | parseOutputsSpec :: Text -> Either ParseOutputsError OutputsSpec 49 | parseOutputsSpec t 50 | | t == "*" = Right OutputsSpec_All 51 | | otherwise = do 52 | names <- mapM 53 | ( Data.Bifunctor.first 54 | ParseOutputsError_InvalidName 55 | . System.Nix.OutputName.mkOutputName 56 | ) 57 | (Data.Text.splitOn "," t) 58 | if null names 59 | then Left ParseOutputsError_NoNames 60 | else Right $ OutputsSpec_Names (Data.Set.fromList names) 61 | 62 | outputsSpecToText :: OutputsSpec -> Text 63 | outputsSpecToText = \case 64 | OutputsSpec_All -> "*" 65 | OutputsSpec_Names ns -> 66 | Data.Text.intercalate 67 | "," 68 | (fmap System.Nix.OutputName.unOutputName 69 | (Data.Set.toList ns) 70 | ) 71 | 72 | parseDerivedPath 73 | :: StoreDir 74 | -> Text 75 | -> Either ParseOutputsError DerivedPath 76 | parseDerivedPath root@(StoreDir sd) path = 77 | let -- We need to do a bit more legwork for case 78 | -- when StoreDir contains '!' 79 | -- which is generated by its Arbitrary instance 80 | textRoot = Data.Text.pack 81 | $ Data.ByteString.Char8.unpack sd 82 | 83 | in case Data.Text.stripPrefix textRoot path of 84 | Nothing -> Left $ ParseOutputsError_NoPrefix root path 85 | Just woRoot -> 86 | case Data.Text.breakOn "!" woRoot of 87 | (pathNoPrefix, r) -> 88 | if Data.Text.null r 89 | then DerivedPath_Opaque 90 | <$> (convertError 91 | $ System.Nix.StorePath.parsePathFromText 92 | root 93 | path 94 | ) 95 | else DerivedPath_Built 96 | <$> (convertError 97 | $ System.Nix.StorePath.parsePathFromText 98 | root 99 | (textRoot <> pathNoPrefix) 100 | ) 101 | <*> parseOutputsSpec (Data.Text.drop (Data.Text.length "!") r) 102 | where 103 | convertError 104 | :: Either InvalidPathError a 105 | -> Either ParseOutputsError a 106 | convertError = Data.Bifunctor.first ParseOutputsError_InvalidPath 107 | 108 | derivedPathToText :: StoreDir -> DerivedPath -> Text 109 | derivedPathToText root = \case 110 | DerivedPath_Opaque p -> 111 | System.Nix.StorePath.storePathToText root p 112 | DerivedPath_Built p os -> 113 | System.Nix.StorePath.storePathToText root p 114 | <> "!" 115 | <> outputsSpecToText os 116 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/FileContentAddress.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.FileContentAddress 2 | ( FileIngestionMethod(..) 3 | ) where 4 | 5 | import GHC.Generics (Generic) 6 | 7 | data FileIngestionMethod 8 | = FileIngestionMethod_Flat 9 | | FileIngestionMethod_NixArchive 10 | deriving (Bounded, Eq, Generic, Enum, Ord, Show) 11 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Fingerprint.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | {-| 4 | Description : Fingerprint of Nix store path metadata used for signature verification 5 | -} 6 | module System.Nix.Fingerprint 7 | ( fingerprint 8 | , metadataFingerprint 9 | ) where 10 | 11 | import Crypto.Hash (Digest) 12 | import Data.Dependent.Sum (DSum) 13 | import Data.List (sort) 14 | import Data.Maybe (fromMaybe) 15 | import Data.Text (Text) 16 | import Data.Text.Lazy (toStrict) 17 | import Data.Text.Lazy.Builder (toLazyText) 18 | import Data.Word (Word64) 19 | import System.Nix.Hash (HashAlgo, algoDigestBuilder) 20 | import System.Nix.StorePath 21 | import System.Nix.StorePath.Metadata (Metadata(..)) 22 | 23 | import qualified Data.HashSet as HashSet 24 | import qualified Data.Text as Text 25 | 26 | -- | Produce the message signed by a NAR signature 27 | metadataFingerprint :: StoreDir -> StorePath -> Metadata StorePath -> Text 28 | metadataFingerprint storeDir storePath Metadata{..} = let 29 | narSize = fromMaybe 0 metadataNarBytes 30 | in fingerprint 31 | storeDir 32 | storePath 33 | metadataNarHash 34 | narSize 35 | (HashSet.toList metadataReferences) 36 | 37 | -- | Produce the message signed by a NAR signature 38 | fingerprint :: StoreDir 39 | -> StorePath 40 | -> DSum HashAlgo Digest -- ^ NAR hash 41 | -> Word64 -- ^ NAR size, in bytes 42 | -> [StorePath] -- ^ References 43 | -> Text 44 | fingerprint storeDir storePath narHash narSize refs = let 45 | encodedStorePath = storePathToText storeDir storePath 46 | encodedNarHash = (toStrict . toLazyText . algoDigestBuilder) narHash 47 | encodedNarSize = (Text.pack . show) narSize 48 | sortedRefs = sort (storePathToText storeDir <$> refs) 49 | encodedRefs = Text.intercalate "," sortedRefs 50 | in Text.intercalate ";" [ "1", encodedStorePath, encodedNarHash, encodedNarSize, encodedRefs] 51 | 52 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Hash/Truncation.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Hash.Truncation 2 | ( truncateInNixWay 3 | ) where 4 | 5 | import Data.Word (Word8) 6 | import Data.ByteString (ByteString) 7 | 8 | import qualified Data.ByteString 9 | import qualified Data.Bits 10 | import qualified Data.Bool 11 | import qualified Data.List 12 | 13 | -- | Bytewise truncation of a 'Digest'. 14 | -- 15 | -- When truncation length is greater than the length of the bytestring 16 | -- but less than twice the bytestring length, truncation splits the 17 | -- bytestring into a head part (truncation length) and tail part 18 | -- (leftover part), right-pads the leftovers with 0 to the truncation 19 | -- length, and combines the two strings bytewise with 'xor'. 20 | truncateInNixWay 21 | :: Int -> ByteString -> ByteString 22 | -- 2021-06-07: NOTE: Renamed function, since truncation can be done in a lot of ways, there is no practice of truncting hashes this way, moreover: 23 | -- 1. 24 | -- 2. 25 | truncateInNixWay n c = 26 | Data.ByteString.pack $ fmap truncOutputByte [0 .. n-1] 27 | where 28 | 29 | truncOutputByte :: Int -> Word8 30 | truncOutputByte i = Data.List.foldl' (aux i) 0 [0 .. Data.ByteString.length c - 1] 31 | 32 | inputByte :: Int -> Word8 33 | inputByte j = Data.ByteString.index c j 34 | 35 | aux :: Int -> Word8 -> Int -> Word8 36 | aux i x j = 37 | Data.Bool.bool 38 | id 39 | (`Data.Bits.xor` inputByte j) 40 | (j `mod` n == i) 41 | x 42 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/OutputName.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-| 3 | Description : Derived path output names 4 | -} 5 | 6 | module System.Nix.OutputName 7 | ( OutputName(..) 8 | , mkOutputName 9 | -- * Re-exports 10 | , System.Nix.StorePath.InvalidNameError(..) 11 | , System.Nix.StorePath.parseNameText 12 | ) where 13 | 14 | import Data.Hashable (Hashable) 15 | import Data.Text (Text) 16 | import GHC.Generics (Generic) 17 | import System.Nix.StorePath (InvalidNameError) 18 | 19 | import qualified System.Nix.StorePath 20 | 21 | -- | Name of the derived path output 22 | -- Typically used for "dev", "doc" sub-outputs 23 | newtype OutputName = OutputName 24 | { -- | Extract the contents of the name. 25 | unOutputName :: Text 26 | } deriving (Eq, Generic, Hashable, Ord, Show) 27 | 28 | mkOutputName :: Text -> Either InvalidNameError OutputName 29 | mkOutputName = fmap OutputName . System.Nix.StorePath.parseNameText 30 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Realisation.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Description : Derivation realisations 3 | -} 4 | 5 | module System.Nix.Realisation ( 6 | DerivationOutput(..) 7 | , DerivationOutputError(..) 8 | , derivationOutputBuilder 9 | , derivationOutputParser 10 | , Realisation(..) 11 | , RealisationWithId(..) 12 | ) where 13 | 14 | import Crypto.Hash (Digest) 15 | import Data.Map (Map) 16 | import Data.Set (Set) 17 | import Data.Text (Text) 18 | import Data.Text.Lazy.Builder (Builder) 19 | import Data.Dependent.Sum (DSum) 20 | import GHC.Generics (Generic) 21 | import System.Nix.Hash (HashAlgo) 22 | import System.Nix.OutputName (OutputName, InvalidNameError) 23 | import System.Nix.Signature (Signature) 24 | import System.Nix.StorePath (StorePath) 25 | 26 | import qualified Data.Bifunctor 27 | import qualified Data.Text 28 | import qualified Data.Text.Lazy.Builder 29 | import qualified System.Nix.Hash 30 | 31 | -- | Output of the derivation 32 | data DerivationOutput a = DerivationOutput 33 | { derivationOutputHash :: DSum HashAlgo Digest 34 | -- ^ Hash modulo of the derivation 35 | , derivationOutputOutput :: a 36 | -- ^ Output (either a OutputName or StorePatH) 37 | } deriving (Eq, Generic, Ord, Show) 38 | 39 | data DerivationOutputError 40 | = DerivationOutputError_Digest String 41 | | DerivationOutputError_Name InvalidNameError 42 | | DerivationOutputError_NoExclamationMark 43 | | DerivationOutputError_NoColon 44 | | DerivationOutputError_TooManyParts [Text] 45 | deriving (Eq, Ord, Show) 46 | 47 | derivationOutputParser 48 | :: (Text -> Either InvalidNameError outputName) 49 | -> Text 50 | -> Either DerivationOutputError (DerivationOutput outputName) 51 | derivationOutputParser outputName dOut = 52 | case Data.Text.splitOn (Data.Text.singleton '!') dOut of 53 | [] -> Left DerivationOutputError_NoColon 54 | [sriHash, oName] -> do 55 | hash <- 56 | case Data.Text.splitOn (Data.Text.singleton ':') sriHash of 57 | [] -> Left DerivationOutputError_NoColon 58 | [hashName, digest] -> 59 | Data.Bifunctor.first 60 | DerivationOutputError_Digest 61 | $ System.Nix.Hash.mkNamedDigest hashName digest 62 | x -> Left $ DerivationOutputError_TooManyParts x 63 | name <- 64 | Data.Bifunctor.first 65 | DerivationOutputError_Name 66 | $ outputName oName 67 | 68 | pure $ DerivationOutput hash name 69 | x -> Left $ DerivationOutputError_TooManyParts x 70 | 71 | derivationOutputBuilder 72 | :: (outputName -> Text) 73 | -> DerivationOutput outputName 74 | -> Builder 75 | derivationOutputBuilder outputName DerivationOutput{..} = 76 | System.Nix.Hash.algoDigestBuilder derivationOutputHash 77 | <> Data.Text.Lazy.Builder.singleton '!' 78 | <> Data.Text.Lazy.Builder.fromText (outputName derivationOutputOutput) 79 | 80 | -- | Build realisation context 81 | -- 82 | -- realisationId is ommited since it is a key 83 | -- of type @DerivationOutput OutputName@ so 84 | -- we will use @RealisationWithId@ newtype 85 | data Realisation = Realisation 86 | { realisationOutPath :: StorePath 87 | -- ^ Output path 88 | , realisationSignatures :: Set Signature 89 | -- ^ Signatures 90 | , realisationDependencies :: Map (DerivationOutput OutputName) StorePath 91 | -- ^ Dependent realisations required for this one to be valid 92 | } deriving (Eq, Generic, Ord, Show) 93 | 94 | -- | For wire protocol 95 | -- 96 | -- We store this normalized in @Build.buildResultBuiltOutputs@ 97 | -- as @Map (DerivationOutput OutputName) Realisation@ 98 | -- but wire protocol needs it de-normalized so we 99 | -- need a special (From|To)JSON instances for it 100 | newtype RealisationWithId = RealisationWithId 101 | { unRealisationWithId :: (DerivationOutput OutputName, Realisation) 102 | } 103 | deriving (Eq, Generic, Ord, Show) 104 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Signature.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE NamedFieldPuns #-} 3 | {-| 4 | Description : Nix-relevant interfaces to NaCl signatures. 5 | -} 6 | 7 | module System.Nix.Signature 8 | ( Signature(..) 9 | , signatureParser 10 | , parseSignature 11 | , signatureToText 12 | , NarSignature(..) 13 | , narSignatureParser 14 | , parseNarSignature 15 | , narSignatureToText 16 | ) where 17 | 18 | import Crypto.Error (CryptoFailable(..)) 19 | import Data.Attoparsec.Text (Parser) 20 | import Data.ByteString (ByteString) 21 | import Data.Text (Text) 22 | import GHC.Generics (Generic) 23 | import System.Nix.Base (decodeWith, encodeWith, BaseEncoding(Base64)) 24 | 25 | import qualified Crypto.PubKey.Ed25519 as Ed25519 26 | import qualified Data.Attoparsec.Text 27 | import qualified Data.ByteArray 28 | import qualified Data.Char 29 | import qualified Data.Text 30 | 31 | -- | An ed25519 signature. 32 | newtype Signature = Signature Ed25519.Signature 33 | deriving (Eq, Generic, Show) 34 | 35 | signatureParser :: Parser Signature 36 | signatureParser = do 37 | encodedSig <- 38 | Data.Attoparsec.Text.takeWhile1 39 | (\c -> Data.Char.isAlphaNum c || c == '+' || c == '/' || c == '=') 40 | decodedSig <- case decodeWith Base64 encodedSig of 41 | Left e -> fail e 42 | Right decodedSig -> pure decodedSig 43 | sig <- case Ed25519.signature decodedSig of 44 | CryptoFailed e -> (fail . show) e 45 | CryptoPassed sig -> pure sig 46 | pure $ Signature sig 47 | 48 | parseSignature :: Text -> Either String Signature 49 | parseSignature = Data.Attoparsec.Text.parseOnly signatureParser 50 | 51 | signatureToText :: Signature -> Text 52 | signatureToText (Signature sig) = 53 | encodeWith Base64 (Data.ByteArray.convert sig :: ByteString) 54 | 55 | -- | A detached signature attesting to a nix archive's validity. 56 | data NarSignature = NarSignature 57 | { -- | The name of the public key used to sign the archive. 58 | publicKey :: !Text 59 | , -- | The archive's signature. 60 | sig :: !Signature 61 | } 62 | deriving (Eq, Generic, Ord) 63 | 64 | instance Ord Signature where 65 | compare (Signature x) (Signature y) = let 66 | xBS = Data.ByteArray.convert x :: ByteString 67 | yBS = Data.ByteArray.convert y :: ByteString 68 | in compare xBS yBS 69 | 70 | narSignatureParser :: Parser NarSignature 71 | narSignatureParser = do 72 | publicKey <- Data.Attoparsec.Text.takeWhile1 (/= ':') 73 | _ <- Data.Attoparsec.Text.string ":" 74 | sig <- signatureParser 75 | pure $ NarSignature {..} 76 | 77 | parseNarSignature :: Text -> Either String NarSignature 78 | parseNarSignature = Data.Attoparsec.Text.parseOnly narSignatureParser 79 | 80 | narSignatureToText :: NarSignature -> Text 81 | narSignatureToText NarSignature {..} = 82 | mconcat [ publicKey, ":", signatureToText sig ] 83 | 84 | instance Show NarSignature where 85 | show narSig = Data.Text.unpack (narSignatureToText narSig) 86 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/Store/Types.hs: -------------------------------------------------------------------------------- 1 | -- | TODO rename module 2 | module System.Nix.Store.Types 3 | ( PathFilter(..) 4 | , RepairMode(..) 5 | ) where 6 | 7 | import GHC.Generics (Generic) 8 | 9 | -- | Path filtering function 10 | newtype PathFilter = PathFilter 11 | { pathFilterFunction :: FilePath -> Bool 12 | } 13 | 14 | -- | Repair mode 15 | data RepairMode 16 | = RepairMode_DoRepair 17 | | RepairMode_DontRepair 18 | deriving (Bounded, Eq, Generic, Enum, Ord, Show) 19 | -------------------------------------------------------------------------------- /hnix-store-core/src/System/Nix/StorePath/Metadata.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Description : Metadata about Nix store paths. 3 | -} 4 | module System.Nix.StorePath.Metadata 5 | ( Metadata(..) 6 | , StorePathTrust(..) 7 | ) where 8 | 9 | import Crypto.Hash (Digest) 10 | import Data.Dependent.Sum (DSum) 11 | import Data.HashSet (HashSet) 12 | import Data.Set (Set) 13 | import Data.Time (UTCTime) 14 | import Data.Word (Word64) 15 | import GHC.Generics (Generic) 16 | 17 | import System.Nix.Hash (HashAlgo) 18 | import System.Nix.Signature (NarSignature) 19 | import System.Nix.ContentAddress (ContentAddress) 20 | 21 | -- | How much do we trust the path, based on its provenance? 22 | -- This is called `Ultimate` in Nix, where Ultimate = True 23 | -- means that the path is ultimately trusted, which 24 | -- corresponds to our @BuiltLocally@ 25 | data StorePathTrust 26 | = -- | It was built locally and thus ultimately trusted 27 | BuiltLocally 28 | | -- | It was built elsewhere (and substituted or similar) and so 29 | -- is less trusted 30 | BuiltElsewhere 31 | deriving (Eq, Enum, Generic, Ord, Show) 32 | 33 | -- | Metadata (typically about a 'StorePath') 34 | -- This type corresponds to Nix-es `ValidPathInfo` 35 | data Metadata a = Metadata 36 | { -- | The path to the derivation file that built this path, if any 37 | -- and known. 38 | metadataDeriverPath :: !(Maybe a) 39 | , -- | The hash of the nar serialization of the path. 40 | metadataNarHash :: !(DSum HashAlgo Digest) 41 | , -- | The paths that this path directly references 42 | metadataReferences :: !(HashSet a) 43 | , -- | When was this path registered valid in the store? 44 | metadataRegistrationTime :: !UTCTime 45 | , -- | The size of the nar serialization of the path, in bytes. 46 | metadataNarBytes :: !(Maybe Word64) 47 | , -- | How much we trust this path. Nix-es ultimate 48 | metadataTrust :: !StorePathTrust 49 | , -- | A set of cryptographic attestations of this path's validity. 50 | -- 51 | -- There is no guarantee from this type alone that these 52 | -- signatures are valid. 53 | metadataSigs :: !(Set NarSignature) 54 | , -- | Whether and how this store path is content-addressable. 55 | -- 56 | -- There is no guarantee from this type alone that this address 57 | -- is actually correct for this store path. 58 | metadataContentAddress :: !(Maybe ContentAddress) 59 | } deriving (Eq, Generic, Ord, Show) 60 | -------------------------------------------------------------------------------- /hnix-store-core/tests/Derivation.hs: -------------------------------------------------------------------------------- 1 | 2 | module Derivation where 3 | 4 | import Test.Tasty (TestTree, testGroup) 5 | import Test.Tasty.Golden (goldenVsFile) 6 | 7 | import System.Nix.Derivation (parseDerivation, buildDerivation) 8 | 9 | import Data.Default.Class (Default(def)) 10 | import qualified Data.Attoparsec.Text.Lazy 11 | import qualified Data.Text.Lazy.IO 12 | import qualified Data.Text.Lazy.Builder 13 | 14 | processDerivation :: FilePath -> FilePath -> IO () 15 | processDerivation source dest = do 16 | contents <- Data.Text.Lazy.IO.readFile source 17 | either 18 | fail 19 | (Data.Text.Lazy.IO.writeFile dest 20 | . Data.Text.Lazy.Builder.toLazyText 21 | . buildDerivation def 22 | ) 23 | (Data.Attoparsec.Text.Lazy.parseOnly 24 | (parseDerivation def) 25 | contents 26 | ) 27 | 28 | test_derivation :: TestTree 29 | test_derivation = 30 | testGroup "golden" $ fmap mk [0 .. 1] 31 | where 32 | mk :: Int -> TestTree 33 | mk n = 34 | goldenVsFile 35 | ("derivation roundtrip of " <> drv) 36 | drv 37 | act 38 | (processDerivation drv act) 39 | where 40 | drv = fp <> show n <> ".drv" 41 | act = fp <> show n <> ".actual" 42 | fp = "tests/samples/example" 43 | -------------------------------------------------------------------------------- /hnix-store-core/tests/Driver.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF tasty-discover #-} 2 | -------------------------------------------------------------------------------- /hnix-store-core/tests/Fingerprint.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | -- Test case from https://code.tvl.fyi/commit/tvix/nix-compat/src/narinfo/fingerprint.rs?id=a834966efd64c1b2306241c3ef20f4258f6b9c4e 3 | 4 | module Fingerprint where 5 | 6 | import Crypto.Error (CryptoFailable(..)) 7 | import Data.Default.Class 8 | import System.Nix.Base (decodeWith, BaseEncoding(Base64)) 9 | import System.Nix.Fingerprint 10 | import System.Nix.Signature 11 | import System.Nix.StorePath 12 | import System.Nix.StorePath.Metadata 13 | import System.Nix.Hash (mkNamedDigest) 14 | import Data.Text (Text) 15 | import Data.Time.Clock (UTCTime(..)) 16 | import Data.Time.Calendar.OrdinalDate (fromOrdinalDate) 17 | import Test.Hspec 18 | 19 | import qualified Crypto.PubKey.Ed25519 as Ed25519 20 | import qualified Data.HashSet as HashSet 21 | import qualified Data.Set as Set 22 | import qualified Data.Text.Encoding as Text 23 | 24 | spec_fingerprint :: Spec 25 | spec_fingerprint = do 26 | 27 | describe "fingerprint" $ do 28 | 29 | it "is valid for example metadata" $ 30 | metadataFingerprint def exampleStorePath exampleMetadata `shouldBe` exampleFingerprint 31 | 32 | it "allows a successful signature verification" $ do 33 | let msg = Text.encodeUtf8 $ metadataFingerprint def exampleStorePath exampleMetadata 34 | Signature sig' = 35 | case 36 | sig 37 | <$> filter (\(NarSignature publicKey _) -> publicKey == "cache.nixos.org-1") 38 | (Set.toList (metadataSigs exampleMetadata)) 39 | of 40 | (x:_) -> x 41 | _ -> error "impossible" 42 | sig' `shouldSatisfy` Ed25519.verify pubkey msg 43 | 44 | exampleFingerprint :: Text 45 | exampleFingerprint = "1;/nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin;sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0;196040;/nix/store/0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0,/nix/store/6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115,/nix/store/j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12,/nix/store/yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n"; 46 | 47 | exampleStorePath :: StorePath 48 | exampleStorePath = forceRight $ parsePath def "/nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin" 49 | 50 | exampleMetadata :: Metadata StorePath 51 | exampleMetadata = Metadata 52 | { metadataDeriverPath = Just $ forceRight $ parsePath def "/nix/store/5rwxzi7pal3qhpsyfc16gzkh939q1np6-curl-7.82.0.drv" 53 | , metadataNarHash = forceRight $ mkNamedDigest "sha256" "1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0" 54 | , metadataReferences = HashSet.fromList $ forceRight . parsePath def <$> ["/nix/store/0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0","/nix/store/6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115","/nix/store/j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12","/nix/store/yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n"] 55 | , metadataRegistrationTime = UTCTime (fromOrdinalDate 0 0) 0 56 | , metadataNarBytes = Just 196040 57 | , metadataTrust = BuiltElsewhere 58 | , metadataSigs = Set.fromList $ forceRight . parseNarSignature <$> ["cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", "test1:519iiVLx/c4Rdt5DNt6Y2Jm6hcWE9+XY69ygiWSZCNGVcmOcyL64uVAJ3cV8vaTusIZdbTnYo9Y7vDNeTmmMBQ=="] 59 | , metadataContentAddress = Nothing 60 | } 61 | 62 | pubkey :: Ed25519.PublicKey 63 | pubkey = forceDecodeB64Pubkey "6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" 64 | 65 | forceDecodeB64Pubkey :: Text -> Ed25519.PublicKey 66 | forceDecodeB64Pubkey b64EncodedPubkey = let 67 | decoded = forceRight $ decodeWith Base64 b64EncodedPubkey 68 | in case Ed25519.publicKey decoded of 69 | CryptoFailed err -> (error . show) err 70 | CryptoPassed x -> x 71 | 72 | forceRight :: Either a b -> b 73 | forceRight = \case 74 | Right x -> x 75 | _ -> error "forceRight failed" 76 | 77 | -------------------------------------------------------------------------------- /hnix-store-core/tests/Hash.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module Hash where 5 | 6 | import Data.ByteString (ByteString) 7 | 8 | import Control.Monad (forM_) 9 | import Crypto.Hash (MD5, SHA1, SHA256, hash) 10 | import qualified Data.ByteString.Base16 as B16 11 | import qualified System.Nix.Base32 as B32 12 | import qualified Data.ByteString.Base64.Lazy as B64 13 | import qualified Data.ByteString.Lazy as BSL 14 | 15 | 16 | import System.Nix.Base 17 | import System.Nix.Hash 18 | import System.Nix.StorePath 19 | import Test.Hspec 20 | 21 | spec_hash :: Spec 22 | spec_hash = do 23 | 24 | describe "hashing parity with nix-store" $ do 25 | 26 | it "produces (base32 . sha256) of \"nix-output:foo\" the same as Nix does at the moment for placeholder \"foo\"" $ 27 | shouldBe (encodeDigestWith NixBase32 (hash @ByteString @SHA256 "nix-output:foo")) 28 | "1x0ymrsy7yr7i9wdsqy9khmzc1yy7nvxw6rdp72yzn50285s67j5" 29 | it "produces (base16 . md5) of \"Hello World\" the same as the thesis" $ 30 | shouldBe (encodeDigestWith Base16 (hash @ByteString @MD5 "Hello World")) 31 | "b10a8db164e0754105b7a99be72e3fe5" 32 | it "produces (base32 . sha1) of \"Hello World\" the same as the thesis" $ 33 | shouldBe (encodeDigestWith NixBase32 (hash @ByteString @SHA1 "Hello World")) 34 | "s23c9fs0v32pf6bhmcph5rbqsyl5ak8a" 35 | 36 | -- The example in question: 37 | -- https://nixos.org/nixos/nix-pills/nix-store-paths.html 38 | it "produces same base32 as nix pill flat file example" $ do 39 | shouldBe (encodeWith NixBase32 $ unStorePathHashPart $ mkStorePathHashPart @SHA256 "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3:/nix/store:myfile") 40 | "xv2iccirbrvklck36f1g7vldn5v58vck" 41 | 42 | -- | Hash encoding conversion ground-truth. 43 | -- Similiar to nix/tests/hash.sh 44 | spec_nixhash :: Spec 45 | spec_nixhash = do 46 | 47 | describe "hashing parity with nix-nash" $ do 48 | 49 | let 50 | samples = [ 51 | ( "800d59cfcd3c05e900cb4e214be48f6b886a08df" 52 | , "vw46m23bizj4n8afrc0fj19wrp7mj3c0" 53 | , "gA1Zz808BekAy04hS+SPa4hqCN8=" 54 | ) 55 | , ( "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" 56 | , "1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s" 57 | , "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=" 58 | ) 59 | , ( "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445" 60 | , "12k9jiq29iyqm03swfsgiw5mlqs173qazm3n7daz43infy12pyrcdf30fkk3qwv4yl2ick8yipc2mqnlh48xsvvxl60lbx8vp38yji0" 61 | , "IEqPxt2oLwoM7XvrjgikFlfBbvRosiioJ5vjMacDwzWW/RXBOxsH+aodO+pXeJygMa2Fx6cd1wNU7GMSOMo0RQ==" 62 | ) 63 | ] 64 | 65 | it "b16 encoded . b32 decoded should equal original b16" $ 66 | forM_ samples $ \(b16, b32, _b64) -> shouldBe (B16.encode <$> B32.decode b32) (pure b16) 67 | 68 | it "b64 encoded . b32 decoded should equal original b64" $ 69 | forM_ samples $ \(_b16, b32, b64) -> shouldBe (B64.encode . BSL.fromStrict <$> B32.decode b32) (pure b64) 70 | 71 | it "b32 encoded . b64 decoded should equal original b32" $ 72 | forM_ samples $ \(_b16, b32, b64) -> shouldBe (B32.encode . BSL.toStrict <$> B64.decode b64 ) (pure b32) 73 | 74 | it "b16 encoded . b64 decoded should equal original b16" $ 75 | forM_ samples $ \(b16, _b32, b64) -> shouldBe (B16.encode . BSL.toStrict <$> B64.decode b64 ) (pure b16) 76 | 77 | it "b32 encoded . b16 decoded should equal original b32" $ 78 | forM_ samples $ \(b16, b32, _b64) -> shouldBe (B32.encode 79 | #if MIN_VERSION_base16_bytestring(1,0,0) 80 | <$> B16.decode b16) (pure b32) 81 | #else 82 | $ fst $ B16.decode b16) (b32) 83 | #endif 84 | 85 | it "b64 encoded . b16 decoded should equal original b64" $ 86 | forM_ samples $ \(b16, _b32, b64) -> shouldBe (B64.encode . BSL.fromStrict 87 | #if MIN_VERSION_base16_bytestring(1,0,0) 88 | <$> B16.decode b16) (pure b64) 89 | #else 90 | $ fst $ B16.decode b16 ) (b64) 91 | #endif 92 | 93 | -------------------------------------------------------------------------------- /hnix-store-core/tests/Signature.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | -- Inspired by https://cl.tvl.fyi/c/depot/+/10081/1/tvix/nix-compat/src/narinfo/signature.rs 3 | -- and https://github.com/nix-community/go-nix/pull/93 4 | -- by @flokli and @zimbatm 5 | 6 | module Signature where 7 | 8 | import qualified Data.ByteString as BS 9 | import Test.Hspec 10 | import Data.Text (Text) 11 | import qualified Crypto.PubKey.Ed25519 12 | import qualified System.Nix.Base 13 | import System.Nix.Base (BaseEncoding(Base64)) 14 | import Crypto.Error (CryptoFailable(..)) 15 | 16 | import System.Nix.Signature 17 | 18 | spec_signature :: Spec 19 | spec_signature = do 20 | 21 | describe "signature parser" $ do 22 | 23 | it "parses names" $ do 24 | shouldParseName "cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==" "cache.nixos.org-1" 25 | 26 | it "fails on invalid signatures" $ do 27 | shouldNotParse "" 28 | shouldNotParse "asdf" 29 | shouldNotParse "cache.nixos.org-1:" 30 | shouldNotParse ":TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==" 31 | shouldNotParse "cache.nixos.org-1TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==" 32 | shouldNotParse "cache.nixos.org-1:sTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==" 33 | 34 | it "parses verifying signatures" $ do 35 | shouldVerify "cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==" pubkeyNixosOrg fingerprint 36 | shouldVerify "cache.nixos.org-2:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==" pubkeyNixosOrg fingerprint 37 | 38 | it "parses non-verifying signatures" $ do 39 | shouldNotVerify "cache.nixos.org-1:TsTTb000000000000000000000000ytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==" pubkeyNixosOrg fingerprint 40 | 41 | fingerprint :: BS.ByteString 42 | fingerprint = "1;/nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin;sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0;196040;/nix/store/0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0,/nix/store/6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115,/nix/store/j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12,/nix/store/yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n"; 43 | 44 | forceDecodeB64Pubkey :: Text -> Crypto.PubKey.Ed25519.PublicKey 45 | forceDecodeB64Pubkey b64EncodedPubkey = let 46 | decoded = case System.Nix.Base.decodeWith Base64 b64EncodedPubkey of 47 | Left err -> error err 48 | Right x -> x 49 | in case Crypto.PubKey.Ed25519.publicKey decoded of 50 | CryptoFailed err -> (error . show) err 51 | CryptoPassed x -> x 52 | 53 | pubkeyNixosOrg :: Crypto.PubKey.Ed25519.PublicKey 54 | pubkeyNixosOrg = forceDecodeB64Pubkey "6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" 55 | 56 | shouldNotParse :: Text -> Expectation 57 | shouldNotParse encoded = case parseNarSignature encoded of 58 | Left _ -> pure () 59 | Right _ -> expectationFailure "should not have parsed" 60 | 61 | shouldParseName :: Text -> Text -> Expectation 62 | shouldParseName encoded name = case parseNarSignature encoded of 63 | Left err -> expectationFailure err 64 | Right narSig -> shouldBe name (publicKey narSig) 65 | 66 | shouldVerify :: Text -> Crypto.PubKey.Ed25519.PublicKey -> BS.ByteString -> Expectation 67 | shouldVerify encoded pubkey msg = case parseNarSignature encoded of 68 | Left err -> expectationFailure err 69 | Right narSig -> let 70 | (Signature sig') = sig narSig 71 | in sig' `shouldSatisfy` Crypto.PubKey.Ed25519.verify pubkey msg 72 | 73 | shouldNotVerify :: Text -> Crypto.PubKey.Ed25519.PublicKey -> BS.ByteString -> Expectation 74 | shouldNotVerify encoded pubkey msg = case parseNarSignature encoded of 75 | Left err -> expectationFailure err 76 | Right narSig -> let 77 | (Signature sig') = sig narSig 78 | in sig' `shouldNotSatisfy` Crypto.PubKey.Ed25519.verify pubkey msg 79 | -------------------------------------------------------------------------------- /hnix-store-core/tests/StorePath.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module StorePath where 5 | 6 | import Test.Hspec (Spec, describe, it, shouldBe) 7 | import qualified Data.Text 8 | 9 | import System.Nix.StorePath (parseNameText, InvalidNameError(..)) 10 | 11 | spec_storePath :: Spec 12 | spec_storePath = do 13 | describe "parseNameText" $ do 14 | it "parses valid name" $ 15 | parseNameText "name-dev.dotok" 16 | `shouldBe` 17 | pure "name-dev.dotok" 18 | 19 | it "fails on empty" $ 20 | parseNameText mempty 21 | `shouldBe` 22 | Left EmptyName 23 | 24 | it "fails on too long" $ 25 | parseNameText (Data.Text.replicate 256 "n") 26 | `shouldBe` 27 | Left (NameTooLong 256) 28 | 29 | it "fails on leading dot" $ 30 | parseNameText ".ab" 31 | `shouldBe` 32 | Left LeadingDot 33 | 34 | it "fails on invalid characters" $ 35 | parseNameText "ab!cd#@" 36 | `shouldBe` 37 | Left (InvalidCharacters "!#@") 38 | -------------------------------------------------------------------------------- /hnix-store-core/tests/samples/example0.actual: -------------------------------------------------------------------------------- 1 | Derive([("devdoc","/nix/store/15x9ii8c3n5wb5lg80cm8x0yk6zy7rha-perl-MIME-Types-2.13-devdoc","",""),("out","/nix/store/93d75ghjyibmbxgfzwhh4b5zwsxzs44w-perl-MIME-Types-2.13","","")],[("/nix/store/cvdbbvnvg131bz9bwyyk97jpq1crclqr-MIME-Types-2.13.tar.gz.drv",["out"]),("/nix/store/p5g31bc5x92awghx9dlm065d7j773l0r-stdenv.drv",["out"]),("/nix/store/x50y5qihwsn0lfjhrf1s81b5hgb9w632-bash-4.4-p5.drv",["out"]),("/nix/store/57h2hjsdkdiwbzilcjqkn46138n1xb4a-perl-5.22.3.drv",["out"])],["/nix/store/cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh"],"x86_64-linux","/nix/store/fi3mbd2ml4pbgzyasrlnp0wyy6qi48fh-bash-4.4-p5/bin/bash",["-e","/nix/store/cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh"],[("AUTOMATED_TESTING","1"),("PERL_AUTOINSTALL","--skipdeps"),("buildInputs",""),("builder","/nix/store/fi3mbd2ml4pbgzyasrlnp0wyy6qi48fh-bash-4.4-p5/bin/bash"),("checkTarget","test"),("devdoc","/nix/store/15x9ii8c3n5wb5lg80cm8x0yk6zy7rha-perl-MIME-Types-2.13-devdoc"),("doCheck","1"),("installTargets","pure_install"),("name","perl-MIME-Types-2.13"),("nativeBuildInputs","/nix/store/nsa311yg8h93wfaacjk16c96a98bs09f-perl-5.22.3"),("out","/nix/store/93d75ghjyibmbxgfzwhh4b5zwsxzs44w-perl-MIME-Types-2.13"),("outputs","out devdoc"),("propagatedBuildInputs",""),("propagatedNativeBuildInputs",""),("src","/nix/store/5smhymz7viq8p47mc3jgyvqd003ab732-MIME-Types-2.13.tar.gz"),("stdenv","/nix/store/s3rlr45jzlzx0d6k2azlpxa5zwzr7xyy-stdenv"),("system","x86_64-linux")]) -------------------------------------------------------------------------------- /hnix-store-core/tests/samples/example0.drv: -------------------------------------------------------------------------------- 1 | Derive([("devdoc","/nix/store/15x9ii8c3n5wb5lg80cm8x0yk6zy7rha-perl-MIME-Types-2.13-devdoc","",""),("out","/nix/store/93d75ghjyibmbxgfzwhh4b5zwsxzs44w-perl-MIME-Types-2.13","","")],[("/nix/store/cvdbbvnvg131bz9bwyyk97jpq1crclqr-MIME-Types-2.13.tar.gz.drv",["out"]),("/nix/store/p5g31bc5x92awghx9dlm065d7j773l0r-stdenv.drv",["out"]),("/nix/store/x50y5qihwsn0lfjhrf1s81b5hgb9w632-bash-4.4-p5.drv",["out"]),("/nix/store/57h2hjsdkdiwbzilcjqkn46138n1xb4a-perl-5.22.3.drv",["out"])],["/nix/store/cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh"],"x86_64-linux","/nix/store/fi3mbd2ml4pbgzyasrlnp0wyy6qi48fh-bash-4.4-p5/bin/bash",["-e","/nix/store/cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh"],[("AUTOMATED_TESTING","1"),("PERL_AUTOINSTALL","--skipdeps"),("buildInputs",""),("builder","/nix/store/fi3mbd2ml4pbgzyasrlnp0wyy6qi48fh-bash-4.4-p5/bin/bash"),("checkTarget","test"),("devdoc","/nix/store/15x9ii8c3n5wb5lg80cm8x0yk6zy7rha-perl-MIME-Types-2.13-devdoc"),("doCheck","1"),("installTargets","pure_install"),("name","perl-MIME-Types-2.13"),("nativeBuildInputs","/nix/store/nsa311yg8h93wfaacjk16c96a98bs09f-perl-5.22.3"),("out","/nix/store/93d75ghjyibmbxgfzwhh4b5zwsxzs44w-perl-MIME-Types-2.13"),("outputs","out devdoc"),("propagatedBuildInputs",""),("propagatedNativeBuildInputs",""),("src","/nix/store/5smhymz7viq8p47mc3jgyvqd003ab732-MIME-Types-2.13.tar.gz"),("stdenv","/nix/store/s3rlr45jzlzx0d6k2azlpxa5zwzr7xyy-stdenv"),("system","x86_64-linux")]) -------------------------------------------------------------------------------- /hnix-store-db/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0.0 2024-07-31 2 | 3 | * Initial release 4 | 5 | --- 6 | 7 | `hnix-store-db` uses [PVP Versioning][1]. 8 | 9 | [1]: https://pvp.haskell.org 10 | -------------------------------------------------------------------------------- /hnix-store-db/README.lhs: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /hnix-store-db/README.md: -------------------------------------------------------------------------------- 1 | # hnix-store-db 2 | 3 | [Nix] SQLite database implementation. 4 | 5 | Only read-only functionality provided for 6 | database schema version `10`. 7 | 8 | [Nix]: https://nixos.org/nix 9 | 10 | ## API 11 | 12 | The interface is experimental and might change wildly. 13 | 14 | [System.Nix.Store.DB.Query]: ./src/System/Nix/Store/DB/Query.hs 15 | [System.Nix.Store.DB.Run]: ./src/System/Nix/Store/DB/Run.hs 16 | [System.Nix.Store.DB.Schema]: ./src/System/Nix/Store/DB/Schema.hs 17 | 18 | ## Example 19 | 20 | This example is runnable via `cabal run db-readme`. 21 | 22 | ```haskell 23 | {-# LANGUAGE OverloadedStrings #-} 24 | 25 | import Data.Default.Class (Default(def)) 26 | 27 | import qualified Control.Monad 28 | import qualified Control.Monad.IO.Class 29 | 30 | import qualified Database.Esqueleto.Experimental 31 | 32 | import qualified System.Nix.StorePath 33 | import qualified System.Nix.Store.DB.Run 34 | import qualified System.Nix.Store.DB.Schema 35 | 36 | import System.Nix.Store.DB.Query 37 | 38 | main :: IO () 39 | main = do 40 | System.Nix.Store.DB.Run.runSystemSqlite $ do 41 | (paths, refs, drvOuts) <- queryEverything 42 | 43 | Control.Monad.IO.Class.liftIO $ do 44 | putStrLn $ "Stats: " 45 | let stat name v = putStrLn $ "- " ++ name ++ ": " ++ show (length v) 46 | stat "ValidPath(s)" paths 47 | stat "Ref(s)" refs 48 | stat "DerivationOutput(s)" drvOuts 49 | 50 | maybeValidPath <- queryOneValidDerivationEntity 51 | case maybeValidPath of 52 | Nothing -> pure () 53 | Just validPathEntity -> do 54 | let pth = 55 | System.Nix.Store.DB.Schema.validPathPath 56 | $ Database.Esqueleto.Experimental.entityVal validPathEntity 57 | 58 | (same, samePath, references, referrers, validDerivers, outputs) <- (,,,,,) 59 | <$> queryPathInfo pth 60 | <*> queryPathFromHashPart def (System.Nix.StorePath.storePathHash pth) 61 | <*> queryReferences validPathEntity 62 | <*> queryReferrers pth 63 | <*> queryValidDerivers pth 64 | <*> queryDerivationOutputs validPathEntity 65 | 66 | Control.Monad.unless (same == Just (Database.Esqueleto.Experimental.entityVal validPathEntity)) 67 | $ error "queryPathInfo failed to roundtrip" 68 | Control.Monad.unless (samePath == Just pth) 69 | $ error "queryPathFromHashPart failed to roundtrip" 70 | 71 | Control.Monad.IO.Class.liftIO $ do 72 | putStrLn $ "References: " 73 | print references 74 | putStrLn $ "Referrers: " 75 | print referrers 76 | putStrLn $ "Valid derivers: " 77 | print validDerivers 78 | putStrLn $ "Derivation outputs: " 79 | print outputs 80 | 81 | pure () 82 | ``` 83 | -------------------------------------------------------------------------------- /hnix-store-db/apps/Bench.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import qualified System.Nix.Store.DB.Run 4 | 5 | main :: IO () 6 | main = System.Nix.Store.DB.Run.bench 7 | -------------------------------------------------------------------------------- /hnix-store-db/hnix-store-db.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: hnix-store-db 3 | version: 0.1.0.0 4 | synopsis: Nix store database support 5 | description: Implementation of the Nix store database 6 | homepage: https://github.com/haskell-nix/hnix-store 7 | license: Apache-2.0 8 | license-file: LICENSE 9 | author: Sorki 10 | maintainer: srk@48.io 11 | copyright: 2023 Sorki 12 | category: Nix 13 | build-type: Simple 14 | extra-doc-files: 15 | CHANGELOG.md 16 | extra-source-files: 17 | README.md 18 | , README.lhs 19 | 20 | flag build-bench 21 | default: 22 | False 23 | description: 24 | Build db-bench executable 25 | 26 | flag build-readme 27 | default: 28 | False 29 | description: 30 | Build README.lhs example 31 | 32 | common commons 33 | ghc-options: -Wall -Wunused-packages 34 | default-extensions: 35 | OverloadedStrings 36 | , DataKinds 37 | , DeriveGeneric 38 | , DeriveDataTypeable 39 | , DeriveFunctor 40 | , DeriveFoldable 41 | , DeriveTraversable 42 | , DeriveLift 43 | , DerivingStrategies 44 | , FlexibleContexts 45 | , FlexibleInstances 46 | , GADTs 47 | , GeneralizedNewtypeDeriving 48 | , RecordWildCards 49 | , ScopedTypeVariables 50 | , StandaloneDeriving 51 | , TypeApplications 52 | , TypeFamilies 53 | , TypeOperators 54 | , TypeSynonymInstances 55 | , InstanceSigs 56 | , MultiParamTypeClasses 57 | , TupleSections 58 | , LambdaCase 59 | , BangPatterns 60 | , ViewPatterns 61 | default-language: Haskell2010 62 | 63 | library 64 | import: commons 65 | hs-source-dirs: src 66 | exposed-modules: 67 | System.Nix.Store.DB 68 | , System.Nix.Store.DB.Query 69 | , System.Nix.Store.DB.Instances 70 | , System.Nix.Store.DB.Run 71 | , System.Nix.Store.DB.Schema 72 | , System.Nix.Store.DB.Util 73 | 74 | build-depends: 75 | base >=4.10 && <5 76 | , hnix-store-core >= 0.8 77 | , attoparsec 78 | , bytestring 79 | , bytestring 80 | , data-default-class 81 | , text 82 | , time 83 | , esqueleto >= 3.5.10 && < 3.6 84 | , persistent >= 2.14.5 && < 2.15 85 | , persistent-sqlite >= 2.13.1 && < 2.14 86 | , template-haskell 87 | , monad-logger 88 | , microlens 89 | , fast-logger 90 | , transformers 91 | , unliftio-core 92 | 93 | executable db-readme 94 | if !flag(build-readme) 95 | buildable: False 96 | build-depends: 97 | base >=4.12 && <5 98 | , data-default-class 99 | , esqueleto 100 | , hnix-store-core 101 | , hnix-store-db 102 | build-tool-depends: 103 | markdown-unlit:markdown-unlit 104 | default-language: Haskell2010 105 | main-is: README.lhs 106 | ghc-options: -pgmL markdown-unlit -Wall 107 | 108 | executable db-bench 109 | if !flag(build-bench) 110 | buildable: False 111 | build-depends: 112 | base >=4.12 && <5 113 | , hnix-store-db 114 | default-language: Haskell2010 115 | hs-source-dirs: apps 116 | main-is: Bench.hs 117 | ghc-options: -Wall 118 | 119 | test-suite db 120 | import: commons 121 | type: exitcode-stdio-1.0 122 | main-is: Smoke.hs 123 | hs-source-dirs: tests 124 | build-depends: 125 | base 126 | , hnix-store-db 127 | -------------------------------------------------------------------------------- /hnix-store-db/src/System/Nix/Store/DB.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.DB ( 2 | module System.Nix.Store.DB.Query 3 | , module System.Nix.Store.DB.Run 4 | , module System.Nix.Store.DB.Schema 5 | ) where 6 | 7 | import System.Nix.Store.DB.Query 8 | import System.Nix.Store.DB.Run 9 | import System.Nix.Store.DB.Schema 10 | -------------------------------------------------------------------------------- /hnix-store-db/src/System/Nix/Store/DB/Instances.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE TypeApplications #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# OPTIONS_GHC -fno-warn-orphans #-} 5 | 6 | module System.Nix.Store.DB.Instances where 7 | 8 | import Database.Persist (PersistField(..), PersistValue(..), SqlType(..)) 9 | import Database.Persist.Sql (PersistFieldSql(..)) 10 | 11 | import Data.Time (UTCTime) 12 | import Data.Default.Class (Default(def)) 13 | 14 | import System.Nix.ContentAddress (ContentAddress) 15 | import System.Nix.StorePath (StorePath) 16 | import System.Nix.StorePath.Metadata (StorePathTrust(..)) 17 | 18 | import qualified Data.Attoparsec.Text 19 | import qualified Data.Bifunctor 20 | import qualified Data.Text 21 | import qualified Data.Time.Clock.POSIX 22 | 23 | import qualified System.Nix.ContentAddress 24 | import qualified System.Nix.StorePath 25 | 26 | instance PersistField StorePath where 27 | toPersistValue = PersistText . System.Nix.StorePath.storePathToText def 28 | fromPersistValue (PersistText t) = either 29 | (Left . Data.Text.pack) 30 | Right 31 | $ Data.Attoparsec.Text.parseOnly 32 | (System.Nix.StorePath.pathParser def) 33 | t 34 | fromPersistValue wrongValue = Left 35 | $ "Received " 36 | <> (Data.Text.pack $ show wrongValue) 37 | <> " when a value of type PersistText was expected." 38 | 39 | instance PersistFieldSql StorePath where 40 | sqlType _ = SqlString 41 | 42 | instance PersistField StorePathTrust where 43 | toPersistValue BuiltLocally = PersistInt64 1 44 | toPersistValue BuiltElsewhere = PersistNull 45 | 46 | fromPersistValue (PersistInt64 1) = pure BuiltLocally 47 | fromPersistValue PersistNull = pure BuiltElsewhere 48 | fromPersistValue wrongValue = Left 49 | $ "Received " 50 | <> (Data.Text.pack $ show wrongValue) 51 | <> " when a value of type PersistNull" 52 | <> " or (PersistInt64 1) was expected." 53 | 54 | instance PersistFieldSql StorePathTrust where 55 | sqlType _ = SqlInt64 56 | 57 | newtype NixUTCTime = NixUTCTime UTCTime 58 | deriving (Eq, Show, Ord) 59 | 60 | instance PersistField NixUTCTime where 61 | toPersistValue (NixUTCTime u) = PersistInt64 62 | $ round $ Data.Time.Clock.POSIX.utcTimeToPOSIXSeconds u 63 | fromPersistValue (PersistInt64 i) = pure $ NixUTCTime 64 | $ Data.Time.Clock.POSIX.posixSecondsToUTCTime $ fromIntegral i 65 | fromPersistValue wrongValue = Left 66 | $ "Received " 67 | <> (Data.Text.pack $ show wrongValue) 68 | <> " when a value of (PersistInt64 _) was expected." 69 | 70 | instance PersistFieldSql NixUTCTime where 71 | sqlType _ = SqlInt64 72 | 73 | instance PersistField ContentAddress where 74 | toPersistValue = 75 | PersistText 76 | . System.Nix.ContentAddress.buildContentAddress 77 | 78 | fromPersistValue (PersistText t) = 79 | Data.Bifunctor.first (\e -> error $ show (e, t)) 80 | $ System.Nix.ContentAddress.parseContentAddress t 81 | fromPersistValue wrongValue = Left 82 | $ "Received " 83 | <> (Data.Text.pack $ show wrongValue) 84 | <> " when a value of type PersistText was expected." 85 | 86 | instance PersistFieldSql ContentAddress where 87 | sqlType _ = SqlString 88 | -------------------------------------------------------------------------------- /hnix-store-db/src/System/Nix/Store/DB/Schema.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes #-} 2 | {-# LANGUAGE TemplateHaskell #-} 3 | {-# LANGUAGE UndecidableInstances #-} 4 | 5 | module System.Nix.Store.DB.Schema where 6 | 7 | import Data.Text (Text) 8 | import Data.Word (Word64) 9 | 10 | import Database.Persist.TH ( mkMigrate 11 | , mkPersist 12 | , share 13 | , sqlSettings 14 | ) 15 | 16 | import System.Nix.ContentAddress (ContentAddress) 17 | import System.Nix.StorePath (StorePath) 18 | import System.Nix.StorePath.Metadata (StorePathTrust(..)) 19 | 20 | import System.Nix.Store.DB.Instances (NixUTCTime) 21 | import System.Nix.Store.DB.Util (persistLikeNix) 22 | 23 | -- shcema version 10 24 | -- cat /nix/var/nix/db/schema 25 | -- 10 26 | 27 | share [ mkPersist sqlSettings 28 | , mkMigrate "migrateAll" ] [persistLikeNix| 29 | ValidPath 30 | path StorePath 31 | hash Text 32 | regTime NixUTCTime sql=registrationTime 33 | deriver StorePath Maybe 34 | narBytes Word64 sql=narSize 35 | ultimate StorePathTrust Maybe 36 | -- ^ null is BuiltElsewhere, 1 is BuiltLocally 37 | sigs Text Maybe 38 | -- ^ space separated 39 | ca ContentAddress Maybe 40 | -- ^ if not null, an assertion that the path is content-addressed 41 | deriving Eq Show Ord 42 | 43 | Ref 44 | referrer ValidPathId 45 | reference ValidPathId 46 | 47 | Primary referrer reference 48 | 49 | Foreign ValidPath OnDeleteCascade fk_referrer referrer 50 | Foreign ValidPath OnDeleteRestrict fk_reference reference 51 | deriving Eq Show Ord 52 | 53 | DerivationOutput 54 | drv ValidPathId 55 | name Text sql=id 56 | -- ^ symbolic output id, usually "out" 57 | path StorePath 58 | 59 | Primary drv name 60 | 61 | Foreign ValidPath OnDeleteCascade fk_drv drv 62 | deriving Eq Show Ord 63 | |] 64 | -------------------------------------------------------------------------------- /hnix-store-db/src/System/Nix/Store/DB/Util.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module System.Nix.Store.DB.Util 3 | ( persistLikeNix 4 | , setWAL 5 | , enableWAL 6 | , disableWAL 7 | , setFK 8 | , enableFK 9 | , disableFK 10 | ) where 11 | 12 | import Language.Haskell.TH.Quote 13 | import Database.Persist.Quasi 14 | import Database.Persist.Sqlite (SqliteConnectionInfo) 15 | import Database.Persist.TH (persistWith) 16 | 17 | import qualified Database.Persist.Sqlite 18 | import qualified Lens.Micro 19 | 20 | -- | Coerce table names to their plural names 21 | -- i.e. ValidPath -> ValidPaths 22 | persistLikeNix :: QuasiQuoter 23 | persistLikeNix = persistWith $ 24 | setPsToDBName 25 | (coerce . (getPsToDBName upperCaseSettings)) 26 | upperCaseSettings 27 | where 28 | coerce x | x `elem` ["ValidPath", "Ref", "DerivationOutput"] = plural x 29 | coerce x = x 30 | 31 | plural x = x <> "s" 32 | 33 | -- * WAL and FK 34 | 35 | -- | Configure WAL (write ahead log) 36 | setWAL 37 | :: Bool 38 | -> SqliteConnectionInfo 39 | -> SqliteConnectionInfo 40 | setWAL v = Lens.Micro.over Database.Persist.Sqlite.walEnabled (const v) 41 | 42 | -- | Enable WAL (write ahead log) 43 | enableWAL 44 | :: SqliteConnectionInfo 45 | -> SqliteConnectionInfo 46 | enableWAL = setWAL True 47 | 48 | -- | Disable WAL (write ahead log) 49 | disableWAL 50 | :: SqliteConnectionInfo 51 | -> SqliteConnectionInfo 52 | disableWAL = setWAL False 53 | 54 | -- | Configure FK (foreign key constraints) 55 | setFK 56 | :: Bool 57 | -> SqliteConnectionInfo 58 | -> SqliteConnectionInfo 59 | setFK v = Lens.Micro.over Database.Persist.Sqlite.walEnabled (const v) 60 | 61 | -- | Enable foreign key constraint checking 62 | enableFK 63 | :: SqliteConnectionInfo 64 | -> SqliteConnectionInfo 65 | enableFK = setFK True 66 | 67 | -- | Disable foreign key constraint checking 68 | disableFK 69 | :: SqliteConnectionInfo 70 | -> SqliteConnectionInfo 71 | disableFK = setFK False 72 | -------------------------------------------------------------------------------- /hnix-store-db/tests/Smoke.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import qualified System.Nix.Store.DB.Run 4 | 5 | -- This only tests that database can be created 6 | -- in-memory using migrateAll and that queryEverything 7 | -- runs (with no data) 8 | -- 9 | -- For better test, we would need a populated nix-store 10 | main :: IO () 11 | main = System.Nix.Store.DB.Run.memTest 12 | -------------------------------------------------------------------------------- /hnix-store-json/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0.0 2024-07-31 2 | 3 | * Initial release 4 | 5 | --- 6 | 7 | `hnix-store-json` uses [PVP Versioning][1]. 8 | 9 | [1]: https://pvp.haskell.org 10 | -------------------------------------------------------------------------------- /hnix-store-json/README.md: -------------------------------------------------------------------------------- 1 | # hnix-store-json 2 | 3 | Aeson instances for core types. 4 | -------------------------------------------------------------------------------- /hnix-store-json/hnix-store-json.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: hnix-store-json 3 | version: 0.1.0.0 4 | synopsis: JSON serialization for core types 5 | description: 6 | Aeson instances for core types, required for remote store protocol 7 | homepage: https://github.com/haskell-nix/hnix-store 8 | license: Apache-2.0 9 | license-file: LICENSE 10 | author: Sorki 11 | maintainer: srk@48.io 12 | copyright: 2023 Sorki 13 | category: Nix 14 | build-type: Simple 15 | extra-doc-files: 16 | CHANGELOG.md 17 | extra-source-files: 18 | README.md 19 | 20 | common commons 21 | ghc-options: -Wall 22 | default-extensions: 23 | DataKinds 24 | , DeriveAnyClass 25 | , DeriveGeneric 26 | , DerivingVia 27 | , FlexibleInstances 28 | , LambdaCase 29 | , RecordWildCards 30 | , StandaloneDeriving 31 | , TypeApplications 32 | default-language: Haskell2010 33 | 34 | library 35 | import: commons 36 | exposed-modules: 37 | System.Nix.JSON 38 | build-depends: 39 | base >=4.12 && <5 40 | , hnix-store-core >= 0.8 41 | , aeson >= 2.0 && < 3.0 42 | , attoparsec 43 | , deriving-aeson >= 0.2 44 | , text 45 | hs-source-dirs: src 46 | 47 | test-suite json 48 | import: commons 49 | type: exitcode-stdio-1.0 50 | main-is: Spec.hs 51 | other-modules: 52 | JSONSpec 53 | hs-source-dirs: 54 | tests 55 | build-tool-depends: 56 | hspec-discover:hspec-discover 57 | build-depends: 58 | base 59 | , hnix-store-core 60 | , hnix-store-json 61 | , hnix-store-tests 62 | , aeson 63 | , containers 64 | , data-default-class 65 | , hspec 66 | -------------------------------------------------------------------------------- /hnix-store-json/src/System/Nix/JSON.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# OPTIONS_GHC -fno-warn-orphans #-} 3 | {-| 4 | Description : JSON serialization 5 | 6 | This module is mostly a stub for now 7 | providing (From|To)JSON for Realisation type 8 | which is required for `-remote`. 9 | -} 10 | module System.Nix.JSON where 11 | 12 | import Data.Aeson 13 | import Deriving.Aeson 14 | import System.Nix.Base (BaseEncoding(NixBase32)) 15 | import System.Nix.OutputName (OutputName) 16 | import System.Nix.Realisation (DerivationOutput, Realisation, RealisationWithId(..)) 17 | import System.Nix.Signature (Signature) 18 | import System.Nix.StorePath (StoreDir(..), StorePath, StorePathName, StorePathHashPart) 19 | 20 | import qualified Data.Aeson.KeyMap 21 | import qualified Data.Aeson.Types 22 | import qualified Data.Attoparsec.Text 23 | import qualified Data.Char 24 | import qualified Data.Text 25 | import qualified Data.Text.Lazy 26 | import qualified Data.Text.Lazy.Builder 27 | import qualified System.Nix.Base 28 | import qualified System.Nix.OutputName 29 | import qualified System.Nix.Realisation 30 | import qualified System.Nix.Signature 31 | import qualified System.Nix.StorePath 32 | 33 | instance ToJSON StorePathName where 34 | toJSON = toJSON . System.Nix.StorePath.unStorePathName 35 | toEncoding = toEncoding . System.Nix.StorePath.unStorePathName 36 | 37 | instance FromJSON StorePathName where 38 | parseJSON = 39 | withText "StorePathName" 40 | ( either (fail . show) pure 41 | . System.Nix.StorePath.mkStorePathName) 42 | 43 | instance ToJSON StorePathHashPart where 44 | toJSON = toJSON . System.Nix.StorePath.storePathHashPartToText 45 | toEncoding = toEncoding . System.Nix.StorePath.storePathHashPartToText 46 | 47 | instance FromJSON StorePathHashPart where 48 | parseJSON = 49 | withText "StorePathHashPart" 50 | ( either 51 | (fail . show) 52 | (pure . System.Nix.StorePath.unsafeMakeStorePathHashPart) 53 | . System.Nix.Base.decodeWith NixBase32 54 | ) 55 | 56 | instance ToJSON StorePath where 57 | toJSON = 58 | toJSON 59 | -- TODO: hacky, we need to stop requiring StoreDir for 60 | -- StorePath rendering and have a distinct 61 | -- types for rooted|unrooted paths 62 | . Data.Text.drop 1 63 | . System.Nix.StorePath.storePathToText (StoreDir mempty) 64 | 65 | toEncoding = 66 | toEncoding 67 | . Data.Text.drop 1 68 | . System.Nix.StorePath.storePathToText (StoreDir mempty) 69 | 70 | instance FromJSON StorePath where 71 | parseJSON = 72 | withText "StorePath" 73 | ( either 74 | (fail . show) 75 | pure 76 | . System.Nix.StorePath.parsePathFromText (StoreDir mempty) 77 | . Data.Text.cons '/' 78 | ) 79 | 80 | instance ToJSON (DerivationOutput OutputName) where 81 | toJSON = 82 | toJSON 83 | . Data.Text.Lazy.toStrict 84 | . Data.Text.Lazy.Builder.toLazyText 85 | . System.Nix.Realisation.derivationOutputBuilder 86 | System.Nix.OutputName.unOutputName 87 | 88 | toEncoding = 89 | toEncoding 90 | . Data.Text.Lazy.toStrict 91 | . Data.Text.Lazy.Builder.toLazyText 92 | . System.Nix.Realisation.derivationOutputBuilder 93 | System.Nix.OutputName.unOutputName 94 | 95 | instance ToJSONKey (DerivationOutput OutputName) where 96 | toJSONKey = 97 | Data.Aeson.Types.toJSONKeyText 98 | $ Data.Text.Lazy.toStrict 99 | . Data.Text.Lazy.Builder.toLazyText 100 | . System.Nix.Realisation.derivationOutputBuilder 101 | System.Nix.OutputName.unOutputName 102 | 103 | instance FromJSON (DerivationOutput OutputName) where 104 | parseJSON = 105 | withText "DerivationOutput OutputName" 106 | ( either 107 | (fail . show) 108 | pure 109 | . System.Nix.Realisation.derivationOutputParser 110 | System.Nix.OutputName.mkOutputName 111 | ) 112 | 113 | instance FromJSONKey (DerivationOutput OutputName) where 114 | fromJSONKey = 115 | FromJSONKeyTextParser 116 | ( either 117 | (fail . show) 118 | pure 119 | . System.Nix.Realisation.derivationOutputParser 120 | System.Nix.OutputName.mkOutputName 121 | ) 122 | 123 | instance ToJSON Signature where 124 | toJSON = toJSON . System.Nix.Signature.signatureToText 125 | toEncoding = toEncoding . System.Nix.Signature.signatureToText 126 | 127 | instance FromJSON Signature where 128 | parseJSON = 129 | withText "Signature" 130 | ( either 131 | (fail . show) 132 | pure 133 | . Data.Attoparsec.Text.parseOnly 134 | System.Nix.Signature.signatureParser 135 | ) 136 | 137 | data LowerLeading 138 | instance StringModifier LowerLeading where 139 | getStringModifier "" = "" 140 | getStringModifier (c:xs) = Data.Char.toLower c : xs 141 | 142 | deriving 143 | via CustomJSON 144 | '[FieldLabelModifier 145 | '[ StripPrefix "realisation" 146 | , LowerLeading 147 | , Rename "dependencies" "dependentRealisations" 148 | ] 149 | ] Realisation 150 | instance ToJSON Realisation 151 | deriving 152 | via CustomJSON 153 | '[FieldLabelModifier 154 | '[ StripPrefix "realisation" 155 | , LowerLeading 156 | , Rename "dependencies" "dependentRealisations" 157 | ] 158 | ] Realisation 159 | instance FromJSON Realisation 160 | 161 | -- For a keyed version of Realisation 162 | -- we use RealisationWithId (DerivationOutput OutputName, Realisation) 163 | -- instead of Realisation.id :: (DerivationOutput OutputName) 164 | -- field. 165 | instance ToJSON RealisationWithId where 166 | toJSON (RealisationWithId (drvOut, r)) = 167 | case toJSON r of 168 | Object o -> Object $ Data.Aeson.KeyMap.insert "id" (toJSON drvOut) o 169 | _ -> error "absurd" 170 | 171 | instance FromJSON RealisationWithId where 172 | parseJSON v@(Object o) = do 173 | r <- parseJSON @Realisation v 174 | drvOut <- o .: "id" 175 | pure (RealisationWithId (drvOut, r)) 176 | parseJSON x = fail $ "Expected Object but got " ++ show x 177 | -------------------------------------------------------------------------------- /hnix-store-json/tests/JSONSpec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module JSONSpec where 3 | 4 | import Data.Aeson (ToJSON, FromJSON, decode, encode) 5 | import Data.Default.Class (Default(def)) 6 | import Test.Hspec (Expectation, Spec, describe, it, shouldBe) 7 | import Test.Hspec.QuickCheck (prop) 8 | import Test.Hspec.Nix (forceRight, roundtrips) 9 | 10 | import System.Nix.Arbitrary () 11 | import System.Nix.JSON () 12 | import System.Nix.OutputName (OutputName) 13 | import System.Nix.Realisation (DerivationOutput(..), Realisation(..)) 14 | import System.Nix.Signature (Signature) 15 | import System.Nix.StorePath (StorePath, StorePathName, StorePathHashPart) 16 | 17 | import qualified Data.Map 18 | import qualified Data.Set 19 | import qualified System.Nix.Hash 20 | import qualified System.Nix.OutputName 21 | import qualified System.Nix.Signature 22 | import qualified System.Nix.StorePath 23 | 24 | roundtripsJSON 25 | :: ( Eq a 26 | , Show a 27 | , ToJSON a 28 | , FromJSON a 29 | ) 30 | => a 31 | -> Expectation 32 | roundtripsJSON = roundtrips encode decode 33 | 34 | sampleDerivationOutput :: DerivationOutput OutputName 35 | sampleDerivationOutput = DerivationOutput 36 | { derivationOutputHash = 37 | forceRight 38 | $ System.Nix.Hash.mkNamedDigest 39 | "sha256" 40 | "1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0" 41 | , derivationOutputOutput = 42 | forceRight 43 | $ System.Nix.OutputName.mkOutputName "foo" 44 | } 45 | 46 | sampleRealisation0 :: Realisation 47 | sampleRealisation0 = Realisation 48 | { realisationOutPath = 49 | forceRight 50 | $ System.Nix.StorePath.parsePath 51 | def 52 | "/nix/store/cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh" 53 | , realisationSignatures = mempty 54 | , realisationDependencies = mempty 55 | } 56 | 57 | sampleRealisation1 :: Realisation 58 | sampleRealisation1 = Realisation 59 | { realisationOutPath = 60 | forceRight 61 | $ System.Nix.StorePath.parsePath 62 | def 63 | "/nix/store/5rwxzi7pal3qhpsyfc16gzkh939q1np6-curl-7.82.0.drv" 64 | , realisationSignatures = 65 | Data.Set.fromList 66 | $ forceRight 67 | . System.Nix.Signature.parseSignature 68 | <$> [ "fW3iEMfyx6IZzGNswD54BjclfkXiYzh0xRXddrXfJ1rp1l8p1xTi9/0g2EibbwLFb6p83cwIJv5KtTGksC54CQ==" 69 | , "SMjnB3mPgXYjXacU+xN24BdzXlAgGAuFnYwPddU3bhjfHBeQus/OimdIPMgR/JMKFPHXORrk7pbjv68vecTEBA==" 70 | ] 71 | , realisationDependencies = 72 | Data.Map.fromList 73 | [ ( sampleDerivationOutput 74 | , forceRight 75 | $ System.Nix.StorePath.parsePathFromText 76 | def 77 | "/nix/store/9472ijanf79nlkb5n1yh57s7867p1930-testFixed" 78 | ) 79 | ] 80 | } 81 | 82 | spec :: Spec 83 | spec = do 84 | describe "JSON" $ do 85 | describe "roundtrips" $ do 86 | prop "StorePathName" $ roundtripsJSON @StorePathName 87 | prop "StorePathHashPart" $ roundtripsJSON @StorePathHashPart 88 | prop "StorePath" $ roundtripsJSON @StorePath 89 | prop "DerivationOutput OutputName" $ roundtripsJSON @(DerivationOutput OutputName) 90 | prop "Signature" $ roundtripsJSON @Signature 91 | prop "Realisation" $ roundtripsJSON @Realisation 92 | 93 | describe "ground truth" $ do 94 | it "sampleDerivationOutput matches preimage" $ 95 | encode sampleDerivationOutput `shouldBe` "\"sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0!foo\"" 96 | 97 | it "sampleRealisation0 matches preimage" $ 98 | encode sampleRealisation0 `shouldBe` "{\"outPath\":\"cdips4lakfk1qbf1x68fq18wnn3r5r14-builder.sh\",\"signatures\":[],\"dependentRealisations\":{}}" 99 | 100 | it "sampleRealisation1 matches preimage" $ 101 | encode sampleRealisation1 `shouldBe` "{\"outPath\":\"5rwxzi7pal3qhpsyfc16gzkh939q1np6-curl-7.82.0.drv\",\"signatures\":[\"SMjnB3mPgXYjXacU+xN24BdzXlAgGAuFnYwPddU3bhjfHBeQus/OimdIPMgR/JMKFPHXORrk7pbjv68vecTEBA==\",\"fW3iEMfyx6IZzGNswD54BjclfkXiYzh0xRXddrXfJ1rp1l8p1xTi9/0g2EibbwLFb6p83cwIJv5KtTGksC54CQ==\"],\"dependentRealisations\":{\"sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0!foo\":\"9472ijanf79nlkb5n1yh57s7867p1930-testFixed\"}}" 102 | -------------------------------------------------------------------------------- /hnix-store-json/tests/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /hnix-store-nar/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.1.1.0](https://github.com/haskell-nix/hnix-store/compare/nar-0.1.0.0...nar-0.1.1.0) 2024-10-09 2 | 3 | * Fix ordering of case-hacked paths on macOS [#286](https://github.com/haskell-nix/hnix-store/pull/286) 4 | 5 | # 0.1.0.0 2024-07-31 6 | 7 | * Initial release after a split from `hnix-store-core` 8 | 9 | --- 10 | 11 | `hnix-store-nar` uses [PVP Versioning][1]. 12 | 13 | [1]: https://pvp.haskell.org 14 | -------------------------------------------------------------------------------- /hnix-store-nar/README.md: -------------------------------------------------------------------------------- 1 | # hnix-store-nar 2 | 3 | `NAR` file format packing and unpacking. 4 | 5 | For a description of the NAR format, see [`Eelco's thesis`](https://nixos.org/~eelco/pubs/phd-thesis.pdf). 6 | 7 | [System.Nix.Nar]: ./src/System/Nix/Nar.hs 8 | [System.Nix.Nar.Effects]: ./src/System/Nix/Nar/Effects.hs 9 | -------------------------------------------------------------------------------- /hnix-store-nar/hnix-store-nar.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: hnix-store-nar 3 | version: 0.1.1.0 4 | synopsis: NAR file format 5 | description: 6 | Packing and unpacking for NAR file format used by Nix. 7 | homepage: https://github.com/haskell-nix/hnix-store 8 | license: Apache-2.0 9 | license-file: LICENSE 10 | author: Shea Levy 11 | maintainer: srk@48.io 12 | copyright: 2018 Shea Levy 13 | category: Nix 14 | build-type: Simple 15 | extra-doc-files: 16 | CHANGELOG.md 17 | extra-source-files: 18 | README.md 19 | , tests/fixtures/case-conflict.nar 20 | 21 | flag bounded_memory 22 | description: Run tests of constant memory use (requires +RTS -T) 23 | default: False 24 | manual: True 25 | 26 | common commons 27 | ghc-options: -Wall 28 | default-extensions: 29 | ConstraintKinds 30 | , DataKinds 31 | , DeriveGeneric 32 | , DeriveDataTypeable 33 | , DeriveFunctor 34 | , DeriveFoldable 35 | , DeriveTraversable 36 | , DeriveLift 37 | , DerivingStrategies 38 | , DerivingVia 39 | , ExistentialQuantification 40 | , FlexibleContexts 41 | , FlexibleInstances 42 | , GADTs 43 | , StandaloneDeriving 44 | , ScopedTypeVariables 45 | , StandaloneDeriving 46 | , RecordWildCards 47 | , TypeApplications 48 | , TypeFamilies 49 | , TypeOperators 50 | , TypeSynonymInstances 51 | , InstanceSigs 52 | , KindSignatures 53 | , MultiParamTypeClasses 54 | , MultiWayIf 55 | , TupleSections 56 | , LambdaCase 57 | , BangPatterns 58 | , ViewPatterns 59 | default-language: Haskell2010 60 | 61 | library 62 | import: commons 63 | exposed-modules: 64 | System.Nix.Nar 65 | , System.Nix.Nar.Parser 66 | , System.Nix.Nar.Streamer 67 | , System.Nix.Nar.Effects 68 | , System.Nix.Nar.Options 69 | build-depends: 70 | base >=4.12 && <5 71 | , algebraic-graphs >= 0.5 && < 0.8 72 | , bytestring 73 | , case-insensitive 74 | , cereal 75 | , containers 76 | , directory 77 | , filepath 78 | , lifted-base 79 | , monad-control 80 | , mtl 81 | , text 82 | , unix 83 | , unordered-containers 84 | hs-source-dirs: src 85 | 86 | test-suite nar 87 | import: commons 88 | if flag(bounded_memory) 89 | cpp-options: -DBOUNDED_MEMORY 90 | ghc-options: -rtsopts -fprof-auto "-with-rtsopts -T" 91 | type: exitcode-stdio-1.0 92 | main-is: Driver.hs 93 | other-modules: 94 | NarFormat 95 | hs-source-dirs: 96 | tests 97 | build-tool-depends: 98 | tasty-discover:tasty-discover 99 | build-depends: 100 | base 101 | , cryptonite 102 | , hnix-store-nar 103 | , base64-bytestring 104 | , cereal 105 | , bytestring 106 | , containers 107 | , directory 108 | , filepath 109 | , hspec 110 | , process 111 | , temporary 112 | , tasty 113 | , tasty-hspec 114 | , tasty-hunit 115 | , tasty-quickcheck 116 | , text 117 | , unix 118 | -------------------------------------------------------------------------------- /hnix-store-nar/src/System/Nix/Nar.hs: -------------------------------------------------------------------------------- 1 | {- 2 | Description : Generating and consuming NAR files 3 | Maintainer : Shea Levy 4 | -} 5 | 6 | module System.Nix.Nar 7 | ( 8 | -- * Encoding and Decoding NAR archives 9 | buildNarIO 10 | , unpackNarIO 11 | 12 | -- * Experimental 13 | , Nar.parseNar 14 | , Nar.testParser 15 | , Nar.testParser' 16 | 17 | -- * Filesystem capabilities used by NAR encoder/decoder 18 | , Nar.NarEffects(..) 19 | , Nar.narEffectsIO 20 | 21 | , Nar.NarOptions(..) 22 | , Nar.defaultNarOptions 23 | 24 | -- * Internal 25 | , Nar.streamNarIO 26 | , Nar.streamNarIOWithOptions 27 | , Nar.runParser 28 | , Nar.runParserWithOptions 29 | , Nar.dumpString 30 | , Nar.dumpPath 31 | 32 | -- * Type 33 | , Nar.NarSource 34 | ) where 35 | 36 | import qualified Control.Concurrent as Concurrent 37 | import qualified Data.ByteString as BS 38 | import qualified System.IO as IO 39 | 40 | import qualified System.Nix.Nar.Effects as Nar 41 | import qualified System.Nix.Nar.Options as Nar 42 | import qualified System.Nix.Nar.Parser as Nar 43 | import qualified System.Nix.Nar.Streamer as Nar 44 | 45 | -- For a description of the NAR format, see Eelco's thesis 46 | -- https://nixos.org/~eelco/pubs/phd-thesis.pdf 47 | 48 | -- | Pack the filesystem object at @FilePath@ into a NAR and stream it into the 49 | -- @IO.Handle@ 50 | -- The handle should aleady be open and in @WriteMode@. 51 | buildNarIO 52 | :: Nar.NarEffects IO 53 | -> FilePath 54 | -> IO.Handle 55 | -> IO () 56 | buildNarIO effs basePath outHandle = 57 | Nar.streamNarIO 58 | effs 59 | basePath 60 | (\chunk -> BS.hPut outHandle chunk >> Concurrent.threadDelay 10) 61 | 62 | -- | Read NAR formatted bytes from the @IO.Handle@ and unpack them into 63 | -- file system object(s) at the supplied @FilePath@ 64 | unpackNarIO 65 | :: Nar.NarEffects IO 66 | -> IO.Handle 67 | -> FilePath 68 | -> IO (Either String ()) 69 | unpackNarIO effs = Nar.runParser effs Nar.parseNar 70 | -------------------------------------------------------------------------------- /hnix-store-nar/src/System/Nix/Nar/Options.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module System.Nix.Nar.Options 3 | ( NarOptions(..) 4 | , defaultNarOptions 5 | , caseHackSuffix 6 | ) where 7 | 8 | import Data.Text (Text) 9 | import qualified System.Info 10 | 11 | -- | Options for configuring how NAR files are encoded and decoded. 12 | data NarOptions = NarOptions { 13 | optUseCaseHack :: Bool 14 | -- ^ Whether to enable a case hack to support case-insensitive filesystems. 15 | -- Equivalent to the 'use-case-hack' option in the Nix client. 16 | -- 17 | -- The case hack rewrites file names to avoid collisions on case-insensitive file systems, e.g. APFS and HFS+ on macOS. 18 | -- Enabled by default on macOS (Darwin). 19 | } 20 | 21 | defaultNarOptions :: NarOptions 22 | defaultNarOptions = NarOptions { 23 | optUseCaseHack = System.Info.os == "darwin" 24 | } 25 | 26 | caseHackSuffix :: Text 27 | caseHackSuffix = "~nix~case~hack~" 28 | -------------------------------------------------------------------------------- /hnix-store-nar/tests/Driver.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF tasty-discover #-} 2 | -------------------------------------------------------------------------------- /hnix-store-nar/tests/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.nar 3 | !*.* 4 | -------------------------------------------------------------------------------- /hnix-store-nar/tests/fixtures/case-conflict.nar: -------------------------------------------------------------------------------- 1 | nix-archive-1(type directoryentry(nameBaz.txtnode(typeregularcontents))entry(nameFoo.txtnode(typeregularcontents))entry(namebarnode(type directoryentry(namebaz.txtnode(typeregularcontents))))entry(namefoo.txtnode(typeregularcontents))) -------------------------------------------------------------------------------- /hnix-store-nar/tests/fixtures/generate-fixtures.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Generate a NAR file with case conflicts in the file names. 4 | mkdir -p case-conflict/bar 5 | touch case-conflict/{Foo.txt,foo.txt,Baz.txt,bar/baz.txt} 6 | 7 | storePath=$(nix-store --add ./case-conflict) 8 | nix-store --dump $storePath > case-conflict.nar 9 | -------------------------------------------------------------------------------- /hnix-store-readonly/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0.0 2024-07-31 2 | 3 | * Initial release 4 | 5 | --- 6 | 7 | `hnix-store-readonly` uses [PVP Versioning][1]. 8 | 9 | [1]: https://pvp.haskell.org 10 | -------------------------------------------------------------------------------- /hnix-store-readonly/README.md: -------------------------------------------------------------------------------- 1 | # hnix-store-readonly 2 | 3 | Read-only variant of Nix store. Only computes paths 4 | without interacting with the actual Nix store. 5 | 6 | [System.Nix.Store.ReadOnly]: ./src/System/Nix/Store/ReadOnly.hs 7 | -------------------------------------------------------------------------------- /hnix-store-readonly/hnix-store-readonly.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: hnix-store-readonly 3 | version: 0.1.0.0 4 | synopsis: Read-only Nix store 5 | description: 6 | Path computation without interaction with the actual Nix store 7 | homepage: https://github.com/haskell-nix/hnix-store 8 | license: Apache-2.0 9 | license-file: LICENSE 10 | author: Shea Levy 11 | maintainer: srk@48.io 12 | copyright: 2018 Shea Levy 13 | category: Nix 14 | build-type: Simple 15 | extra-doc-files: 16 | CHANGELOG.md 17 | extra-source-files: 18 | README.md 19 | 20 | common commons 21 | ghc-options: -Wall 22 | default-extensions: 23 | Rank2Types 24 | , ScopedTypeVariables 25 | , TypeApplications 26 | default-language: Haskell2010 27 | 28 | library 29 | import: commons 30 | exposed-modules: 31 | System.Nix.Store.ReadOnly 32 | build-depends: 33 | base >=4.12 && <5 34 | , hnix-store-core >= 0.8 35 | , hnix-store-nar >= 0.1 36 | , bytestring 37 | , constraints-extras 38 | , crypton 39 | , dependent-sum > 0.7 40 | , mtl 41 | , text 42 | , unordered-containers 43 | hs-source-dirs: src 44 | 45 | test-suite readonly 46 | import: commons 47 | type: exitcode-stdio-1.0 48 | main-is: Spec.hs 49 | other-modules: 50 | ReadOnlySpec 51 | hs-source-dirs: 52 | tests 53 | build-tool-depends: 54 | hspec-discover:hspec-discover 55 | build-depends: 56 | base 57 | , hnix-store-core 58 | , hnix-store-readonly 59 | , bytestring 60 | , crypton 61 | , data-default-class 62 | , dependent-sum 63 | , hspec 64 | , unordered-containers 65 | -------------------------------------------------------------------------------- /hnix-store-readonly/src/System/Nix/Store/ReadOnly.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GADTs #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module System.Nix.Store.ReadOnly 5 | ( References(..) 6 | , makeStorePath 7 | , makeFixedOutputPath 8 | , computeStorePathForPath 9 | ) where 10 | 11 | import Control.Monad.State (StateT, execStateT, modify) 12 | import Crypto.Hash (Context, Digest, SHA256, HashAlgorithm) 13 | import Data.ByteString (ByteString) 14 | import Data.Constraint.Extras (Has(has)) 15 | import Data.Dependent.Sum (DSum((:=>))) 16 | import Data.HashSet (HashSet) 17 | import Data.Some (Some(Some)) 18 | import System.Nix.ContentAddress (ContentAddressMethod (..)) 19 | import System.Nix.Hash (BaseEncoding(Base16), HashAlgo(..)) 20 | import System.Nix.Store.Types (PathFilter, RepairMode) 21 | import System.Nix.StorePath (StoreDir, StorePath, StorePathName) 22 | 23 | import qualified Crypto.Hash 24 | import qualified Data.ByteString.Char8 25 | import qualified Data.ByteString 26 | import qualified Data.HashSet 27 | import qualified Data.List 28 | import qualified Data.Text 29 | import qualified Data.Text.Encoding 30 | import qualified System.Nix.Hash 31 | import qualified System.Nix.Nar 32 | import qualified System.Nix.StorePath 33 | 34 | data References = References 35 | { references_others :: HashSet StorePath 36 | , references_self :: Bool 37 | } 38 | 39 | instance Semigroup References where 40 | a <> b = References 41 | { references_others = references_others a <> references_others b 42 | , references_self = references_self a || references_self b 43 | } 44 | 45 | instance Monoid References where 46 | mempty = References 47 | { references_others = mempty 48 | , references_self = False 49 | } 50 | 51 | makeStorePath 52 | :: StoreDir 53 | -> ByteString 54 | -> DSum HashAlgo Digest 55 | -> StorePathName 56 | -> StorePath 57 | makeStorePath storeDir ty (hashAlgo :=> (digest :: Digest a)) nm = 58 | System.Nix.StorePath.unsafeMakeStorePath storeHash nm 59 | where 60 | storeHash = has @HashAlgorithm hashAlgo $ System.Nix.StorePath.mkStorePathHashPart @a s 61 | s = 62 | Data.ByteString.intercalate ":" $ 63 | ty:fmap Data.Text.Encoding.encodeUtf8 64 | [ System.Nix.Hash.algoToText hashAlgo 65 | , System.Nix.Hash.encodeDigestWith Base16 digest 66 | , Data.Text.pack . Data.ByteString.Char8.unpack $ System.Nix.StorePath.unStoreDir storeDir 67 | , System.Nix.StorePath.unStorePathName nm 68 | ] 69 | 70 | makeType 71 | :: StoreDir 72 | -> ByteString 73 | -> References 74 | -> ByteString 75 | makeType storeDir ty refs = 76 | Data.ByteString.intercalate ":" $ ty : (others ++ self) 77 | where 78 | others = Data.List.sort 79 | $ fmap (System.Nix.StorePath.storePathToRawFilePath storeDir) 80 | $ Data.HashSet.toList 81 | $ references_others refs 82 | self = ["self" | references_self refs] 83 | 84 | makeFixedOutputPath 85 | :: StoreDir 86 | -> ContentAddressMethod 87 | -> DSum HashAlgo Digest 88 | -> References 89 | -> StorePathName 90 | -> StorePath 91 | makeFixedOutputPath storeDir method digest@(hashAlgo :=> h) refs = 92 | makeStorePath storeDir ty digest' 93 | where 94 | (ty, digest') = case method of 95 | ContentAddressMethod_Text -> 96 | case hashAlgo of 97 | HashAlgo_SHA256 98 | | references_self refs == False -> (makeType storeDir "text" refs, digest) 99 | _ -> error "unsupported" -- TODO do better; maybe we'll just remove this restriction too? 100 | _ -> 101 | if method == ContentAddressMethod_NixArchive 102 | && Some hashAlgo == Some HashAlgo_SHA256 103 | then (makeType storeDir "source" refs, digest) 104 | else let 105 | h' = 106 | Crypto.Hash.hash @ByteString @SHA256 107 | $ "fixed:out:" 108 | <> Data.Text.Encoding.encodeUtf8 (System.Nix.Hash.algoToText hashAlgo) 109 | <> (if method == ContentAddressMethod_NixArchive then ":r:" else ":") 110 | <> Data.Text.Encoding.encodeUtf8 (System.Nix.Hash.encodeDigestWith Base16 h) 111 | <> ":" 112 | in ("output:out", HashAlgo_SHA256 :=> h') 113 | 114 | digestPath 115 | :: FilePath -- ^ Local `FilePath` to add 116 | -> ContentAddressMethod -- ^ target directory method 117 | -> PathFilter -- ^ Path filter function 118 | -> RepairMode -- ^ Only used by local store backend 119 | -> IO (Digest SHA256) 120 | digestPath pth method _pathFilter _repair = 121 | case method of 122 | ContentAddressMethod_Flat -> flatContentHash 123 | ContentAddressMethod_NixArchive -> nixArchiveContentHash 124 | ContentAddressMethod_Text -> flatContentHash 125 | where 126 | nixArchiveContentHash :: IO (Digest SHA256) 127 | nixArchiveContentHash = 128 | Crypto.Hash.hashFinalize 129 | <$> execStateT streamNarUpdate (Crypto.Hash.hashInit @SHA256) 130 | 131 | streamNarUpdate :: StateT (Context SHA256) IO () 132 | streamNarUpdate = 133 | System.Nix.Nar.streamNarIO 134 | System.Nix.Nar.narEffectsIO 135 | pth 136 | (modify . flip (Crypto.Hash.hashUpdate @ByteString @SHA256)) 137 | 138 | flatContentHash :: IO (Digest SHA256) 139 | flatContentHash = 140 | Crypto.Hash.hashlazy 141 | <$> System.Nix.Nar.narReadFile 142 | System.Nix.Nar.narEffectsIO 143 | pth 144 | 145 | computeStorePathForPath 146 | :: StoreDir 147 | -> StorePathName -- ^ Name part of the newly created `StorePath` 148 | -> FilePath -- ^ Local `FilePath` to add 149 | -> ContentAddressMethod -- ^ Add target directory methodly 150 | -> PathFilter -- ^ Path filter function 151 | -> RepairMode -- ^ Only used by local store backend 152 | -> IO StorePath 153 | computeStorePathForPath storeDir name pth method pathFilter repair = do 154 | selectedHash <- digestPath pth method pathFilter repair 155 | pure $ makeFixedOutputPath storeDir method (HashAlgo_SHA256 :=> selectedHash) mempty name 156 | -------------------------------------------------------------------------------- /hnix-store-readonly/tests/ReadOnlySpec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module ReadOnlySpec where 4 | 5 | import Data.Default.Class (Default(def)) 6 | import Test.Hspec (Spec, describe, it, shouldBe, pendingWith) 7 | 8 | import Crypto.Hash (hash, Digest) 9 | import Data.ByteString (ByteString) 10 | import Data.Dependent.Sum (DSum(..)) 11 | import System.Nix.Hash (HashAlgo(..)) 12 | import System.Nix.StorePath (StorePath, StorePathName) 13 | import System.Nix.ContentAddress (ContentAddressMethod(..)) 14 | 15 | import qualified Data.HashSet 16 | import qualified System.Nix.StorePath 17 | 18 | import System.Nix.Store.ReadOnly 19 | 20 | testDigest :: DSum HashAlgo Digest 21 | testDigest = HashAlgo_SHA256 :=> Crypto.Hash.hash @ByteString "testDigest" 22 | 23 | testName :: StorePathName 24 | testName = 25 | either undefined id 26 | $ System.Nix.StorePath.mkStorePathName "testFixed" 27 | 28 | testPath :: StorePath 29 | testPath = 30 | either undefined id 31 | $ System.Nix.StorePath.parsePathFromText 32 | def 33 | "/nix/store/9472ijanf79nlkb5n1yh57s7867p1930-testFixed" 34 | 35 | testPath2 :: StorePath 36 | testPath2 = 37 | either undefined id 38 | $ System.Nix.StorePath.parsePathFromText 39 | def 40 | "/nix/store/iali40m5597kikibjxw8cx1ssxlqnii3-testFixed" 41 | 42 | spec :: Spec 43 | spec = do 44 | describe "ReadOnly" $ do 45 | it "makeStorePath computes correct StorePath" $ 46 | (pure 47 | $ makeStorePath 48 | def 49 | "test" 50 | testDigest 51 | testName 52 | ) 53 | `shouldBe` 54 | System.Nix.StorePath.parsePathFromText 55 | def 56 | "/nix/store/iali40m5597kikibjxw8cx1ssxlqnii3-testFixed" 57 | 58 | describe "makeTextPath" $ do 59 | it "computes correct StorePath for empty refs" $ 60 | (pure 61 | $ makeFixedOutputPath 62 | def 63 | ContentAddressMethod_Text 64 | testDigest 65 | mempty 66 | testName 67 | ) 68 | `shouldBe` 69 | System.Nix.StorePath.parsePathFromText 70 | def 71 | "/nix/store/ync87sfmahhaqwnykzwbk31q96drm9vn-testFixed" 72 | 73 | it "computes correct StorePath for nonempty refs" $ 74 | (pure 75 | $ makeFixedOutputPath 76 | def 77 | ContentAddressMethod_Text 78 | testDigest 79 | (References 80 | { references_others = Data.HashSet.fromList [ testPath, testPath2 ] 81 | , references_self = False 82 | }) 83 | testName 84 | ) 85 | `shouldBe` 86 | System.Nix.StorePath.parsePathFromText 87 | def 88 | "/nix/store/jcvh84zapqndh8hva515d4y41s07n2g8-testFixed" 89 | 90 | describe "makeFixedOuputPath" $ do 91 | it "computes correct StorePath, recursive" $ 92 | (pure 93 | $ makeFixedOutputPath 94 | def 95 | ContentAddressMethod_NixArchive 96 | testDigest 97 | mempty 98 | testName 99 | ) 100 | `shouldBe` 101 | System.Nix.StorePath.parsePathFromText 102 | def 103 | "/nix/store/c0cgdqy9i3smyh3as8c4s6fg6nvwdpzy-testFixed" 104 | 105 | it "computes correct StorePath, non-recursive" $ 106 | (pure 107 | $ makeFixedOutputPath 108 | def 109 | ContentAddressMethod_Flat 110 | testDigest 111 | mempty 112 | testName 113 | ) 114 | `shouldBe` 115 | System.Nix.StorePath.parsePathFromText 116 | def 117 | "/nix/store/9472ijanf79nlkb5n1yh57s7867p1930-testFixed" 118 | 119 | it "computeStorePathForText computes correct StorePath" $ 120 | (pure 121 | $ makeFixedOutputPath 122 | def 123 | ContentAddressMethod_Text 124 | (HashAlgo_SHA256 :=> Crypto.Hash.hash ("test" :: ByteString)) 125 | (References 126 | { references_others = Data.HashSet.fromList [ testPath ] 127 | , references_self = False 128 | }) 129 | testName 130 | ) 131 | `shouldBe` 132 | System.Nix.StorePath.parsePathFromText 133 | def 134 | "/nix/store/838lq5qh5a88wsalcjpmj33bcnmpz3pc-testFixed" 135 | 136 | describe "computeStorePathForForPath" $ do 137 | it "computes correct StorePath" $ 138 | pendingWith "needs IO and a sample directory to add" 139 | -------------------------------------------------------------------------------- /hnix-store-readonly/tests/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /hnix-store-remote/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.7.0.0](https://github.com/haskell-nix/hnix-store/compare/remote-0.6.0.0...remote-0.7.0.0) 2024-07-31 2 | 3 | * Changes: 4 | * `StorePath` no longer carries `storePathRoot` field and we 5 | have a stand-alone `StoreDir` type instead to be used instead of `FilePath` 6 | when store root directory is needed as a context. 7 | Fore `-remote`, this affects `runStoreOpts` and its variants [#216](https://github.com/haskell-nix/hnix-store/pull/216) 8 | * The old `MonadStore` is now deprecated and aliased to `RemoteStoreT IO` 9 | * All store operations now use `MonadRemoteStore` typeclass, which `RemoteStoreT` is an instance of 10 | * Couple of `Bool` parameters switched to enums 11 | 12 | * Additions 13 | * `addToStoreNAR` store operation [#277](https://github.com/haskell-nix/hnix-store/pull/277) 14 | * `narFromPath` store operation [#279](https://github.com/haskell-nix/hnix-store/pull/279) 15 | * Initial server-side support with proxy daemon acting as MITM proxy, 16 | which allows for testing of both sides of the remote store protocol 17 | 18 | The library stability is not quite there yet and should be considered experimental. 19 | 20 | # [0.6.0.0](https://github.com/haskell-nix/hnix-store/compare/remote-0.5.0.0...remote-0.6.0.0) 2021-06-06 21 | 22 | * Changes: 23 | * `System.Nix.Store.Remote` [#179](https://github.com/haskell-nix/hnix-store/pull/179) 24 | * `addToStore` no longer accepts `FilePath` as its second argument but uses 25 | more generic `NarSource` [(NarSource PR)](https://github.com/haskell-nix/hnix-store/pull/177) 26 | 27 | # [0.5.0.0](https://github.com/haskell-nix/hnix-store/compare/0.4.3.0...remote-0.5.0.0) 2021-06-11 28 | 29 | * Changes: 30 | * `System.Nix.Store.Remote` [#161](https://github.com/haskell-nix/hnix-store/pull/161) 31 | * `addToStore`: constraint of `ValidAlgo a` removed in favour of constraint on `cryptonite: HashAlgorithm a` through constraint `NamedAlgo a`. 32 | * `queryPathFromHashPart`: 1st arg changed from `Digest StorePathHashAlgo` to `StorePathHashPart`, for details: [hnix-store-core 0.5.0.0 ChangeLog](https://hackage.haskell.org/package/hnix-store-core-0.5.0.0/changelog). 33 | 34 | # [0.4.3.0](https://github.com/haskell-nix/hnix-store/compare/0.4.2.0...0.4.3.0) 2021-05-30 35 | 36 | Nothing (it is tandem `hnix-store-core` fix release) 37 | 38 | # [0.4.2.0](https://github.com/haskell-nix/hnix-store/compare/0.4.1.0...0.4.2.0) 2021-03-12 39 | 40 | * Additions: 41 | * Cabal now properly states `tasty-discover` as `build-tool-depends` [#130](https://github.com/haskell-nix/hnix-store/pull/130) 42 | * added explicit `hie.yml` cradle description for `cabal` to help Haskell Language Server to work with monorepo. [#132](https://github.com/haskell-nix/hnix-store/pull/132) 43 | * Nix dev env: removed GHC 8.6.5 support, afaik it is not even in Nixpkgs anymore [#136](https://github.com/haskell-nix/hnix-store/pull/136) 44 | 45 | # [0.4.1.0](https://github.com/haskell-nix/hnix-store/compare/0.4.0.0...0.4.1.0) 2021-01-16 46 | 47 | * `System.Nix.Store.Remote`: module API now re-exports `System.Nix.Store.Remote.Types` API 48 | * Big clean-up of dependencies. 49 | 50 | # [0.4.0.0](https://github.com/haskell-nix/hnix-store/compare/0.3.0.0...0.4.0.0) 2020-12-30 51 | 52 | * `hnix-store-core` compatibility 53 | 54 | # 0.3.0.0 -- 2020-11-29 55 | 56 | * Restored most store API functions except `addToStoreNar` 57 | * Added `buildDerivation` 58 | 59 | # 0.2.0.0 -- skipped 60 | 61 | * `hnix-store-core` release only 62 | 63 | # 0.1.0.0 -- 2019-03-18 64 | 65 | * First version. 66 | 67 | --- 68 | 69 | `hnix-store-remote` uses [PVP Versioning][1]. 70 | 71 | [1]: https://pvp.haskell.org 72 | -------------------------------------------------------------------------------- /hnix-store-remote/README.lhs: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /hnix-store-remote/README.md: -------------------------------------------------------------------------------- 1 | # hnix-store-remote 2 | 3 | [Nix] worker protocol implementation for interacting with remote Nix store 4 | via `nix-daemon`. 5 | 6 | [Nix]: https://nixos.org/nix 7 | 8 | ## API 9 | 10 | [System.Nix.Store.Remote]: ./src/System/Nix/Store/Remote.hs 11 | 12 | ## Example 13 | 14 | ```haskell 15 | {-# LANGUAGE OverloadedStrings #-} 16 | 17 | import System.Nix.StorePath (mkStorePathName) 18 | import System.Nix.Store.Remote 19 | 20 | main :: IO () 21 | main = do 22 | runStore $ do 23 | syncWithGC 24 | roots <- findRoots 25 | 26 | res <- case mkStorePathName "hnix-store" of 27 | Left e -> error (show e) 28 | Right name -> 29 | addTextToStore 30 | (StoreText name "Hello World!") 31 | mempty 32 | RepairMode_DontRepair 33 | 34 | pure (roots, res) 35 | >>= print 36 | ``` 37 | -------------------------------------------------------------------------------- /hnix-store-remote/app/BuildDerivation.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | module Main where 3 | 4 | import Data.Default.Class (Default(def)) 5 | 6 | import qualified Data.Text 7 | import qualified System.Environment 8 | import qualified System.Nix.Build 9 | import qualified System.Nix.StorePath 10 | import qualified System.Nix.Store.Remote 11 | 12 | main :: IO () 13 | main = System.Environment.getArgs >>= \case 14 | [filename] -> do 15 | case System.Nix.StorePath.parsePathFromText def (Data.Text.pack filename) of 16 | Left e -> error $ show e 17 | Right p -> do 18 | out <- 19 | System.Nix.Store.Remote.runStore 20 | $ System.Nix.Store.Remote.buildDerivation 21 | p 22 | System.Nix.Build.BuildMode_Normal 23 | print out 24 | _ -> error "No input derivation file" 25 | 26 | -------------------------------------------------------------------------------- /hnix-store-remote/src/Data/Serializer.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 3 | {-| 4 | Description : Serializer data type 5 | Copyright : (c) John Ericson, 2023 6 | Sorki, 2023 7 | Stability : experimental 8 | 9 | @Serializer@ ties @Get@ and @PutM@ monads 10 | into a single datatype and allows 11 | transforming both monads with a monad transformer 12 | for adding extra layers like @ExceptT@ 13 | (for example when @putS@ can fail due to unsupported 14 | version of a protocol) or @ReaderT@ (when we need 15 | to serialize a data type based differently based 16 | on a protocol version). 17 | 18 | See "Data.Serializer.Example" 19 | -} 20 | 21 | module Data.Serializer 22 | ( 23 | -- * Serializer 24 | Serializer(..) 25 | -- ** Runners 26 | , runGetS 27 | , runPutS 28 | -- * Simple serializer 29 | , SimpleSerializer 30 | -- ** Simple runners 31 | , runGetSimple 32 | , runPutSimple 33 | -- * From Get/Put, Serialize 34 | , lift2 35 | , liftSerialize 36 | -- * Combinators 37 | , mapIsoSerializer 38 | , mapPrismSerializer 39 | , tup 40 | -- * Utility 41 | , GetSerializerError(..) 42 | , transformGetError 43 | , transformPutError 44 | -- * Re-exports 45 | , Get 46 | , PutM 47 | ) where 48 | 49 | #if !MIN_VERSION_base(4,18,0) 50 | import Control.Applicative (liftA2) 51 | #endif 52 | import Control.Monad.Except (MonadError, throwError) 53 | import Control.Monad.Trans (lift) 54 | import Control.Monad.Trans (MonadTrans) 55 | import Control.Monad.Trans.Identity (IdentityT, runIdentityT) 56 | import Data.ByteString (ByteString) 57 | import Data.Serialize (Serialize) 58 | import Data.Serialize.Get (Get, runGet) 59 | import Data.Serialize.Put (Putter, PutM, runPutM) 60 | 61 | import qualified Data.Serialize 62 | 63 | -- * Serializer 64 | 65 | -- | @Serializer@ ties @Get@ and @PutM@ monads 66 | -- into a single datatype and allows 67 | -- transforming the monads with a monad transformer 68 | -- for e.g. adding @ExceptT@ or @ReaderT@ layers. 69 | data Serializer t a = Serializer 70 | { getS :: t Get a 71 | , putS :: a -> t PutM () 72 | } 73 | 74 | -- ** Runners 75 | 76 | -- | Runner for putS of @Serializer@ 77 | runPutS 78 | :: ( Monad (t PutM) 79 | , MonadTrans t 80 | ) 81 | => Serializer t a -- ^ Serializer 82 | -> (t PutM () -> PutM b) -- ^ Tranformer runner 83 | -> a -- ^ Value to (out)put 84 | -> (b, ByteString) 85 | runPutS s run a = runPutM $ run $ (putS s) a 86 | 87 | -- | Runner for getS of @Serializer@ 88 | runGetS 89 | :: ( Monad (t Get) 90 | , MonadTrans t 91 | ) 92 | => Serializer t a -- ^ Serializer 93 | -> (t Get a -> Get b) -- ^ Tranformer runner 94 | -> ByteString -- ^ ByteString to parse 95 | -> Either String b 96 | runGetS s run b = runGet (run (getS s)) b 97 | 98 | -- * Simple serializer 99 | 100 | -- | Simple @Serializer@ 101 | type SimpleSerializer a = Serializer IdentityT a 102 | 103 | -- ** Simple runners 104 | 105 | -- | Runner for getS of @SimpleSerializer@ 106 | runGetSimple 107 | :: SimpleSerializer a 108 | -> ByteString 109 | -> Either String a 110 | runGetSimple s b = 111 | runGetS s (runIdentityT) b 112 | 113 | -- | Runner for putS of @SimpleSerializer@ 114 | runPutSimple 115 | :: SimpleSerializer a 116 | -> a 117 | -> ByteString 118 | runPutSimple s = 119 | snd 120 | . runPutS s runIdentityT 121 | 122 | -- * From Get/Put, Serialize 123 | 124 | -- | Lift @Get a@ and @Putter a@ into @Serializer@ 125 | lift2 126 | :: forall a t 127 | . MonadTrans t 128 | => Get a 129 | -> Putter a 130 | -> Serializer t a 131 | lift2 f g = Serializer 132 | { getS = lift f 133 | , putS = lift . g 134 | } 135 | 136 | -- | Lift @Serialize a@ instance into @Serializer@ 137 | liftSerialize 138 | :: ( Serialize a 139 | , MonadTrans t 140 | ) 141 | => Serializer t a 142 | liftSerialize = 143 | lift2 144 | Data.Serialize.get 145 | Data.Serialize.put 146 | 147 | -- * Combinators 148 | 149 | -- | Map over @Serializer@ 150 | mapIsoSerializer 151 | :: Functor (t Get) 152 | => (a -> b) -- ^ Map over @getS@ 153 | -> (b -> a) -- ^ Map over @putS@ 154 | -> Serializer t a 155 | -> Serializer t b 156 | mapIsoSerializer f g s = Serializer 157 | { getS = f <$> getS s 158 | , putS = putS s . g 159 | } 160 | 161 | -- | Map over @Serializer@ where @getS@ 162 | -- can return @Either@ 163 | mapPrismSerializer 164 | :: MonadError eGet (t Get) 165 | => (a -> Either eGet b) -- ^ Map over @getS@ 166 | -> (b -> a) -- ^ Map over @putS@ 167 | -> Serializer t a 168 | -> Serializer t b 169 | mapPrismSerializer f g s = Serializer 170 | { getS = either throwError pure . f =<< getS s 171 | , putS = putS s . g 172 | } 173 | 174 | -- | Tuple combinator 175 | tup 176 | :: ( Applicative (t Get) 177 | , Monad (t PutM) 178 | ) 179 | => Serializer t a 180 | -> Serializer t b 181 | -> Serializer t (a, b) 182 | tup a b = Serializer 183 | { getS = liftA2 (,) (getS a) (getS b) 184 | , putS = \(x, y) -> do 185 | putS a x 186 | putS b y 187 | } 188 | 189 | -- * Utilities 190 | 191 | -- | Wrapper for both GetS errors 192 | -- 193 | -- * the one that occurs when @fail@ is called 194 | -- * custom one when @ExceptT@ is used 195 | data GetSerializerError customGetError 196 | = SerializerError_GetFail String 197 | | SerializerError_Get customGetError 198 | deriving (Eq, Ord, Show) 199 | 200 | -- | Helper for transforming nested Eithers 201 | -- into @GetSerializerError@ wrapper 202 | transformGetError 203 | :: Either String (Either customGetError b) 204 | -> Either (GetSerializerError customGetError) b 205 | transformGetError = \case 206 | Left stringyRunGetError -> Left (SerializerError_GetFail stringyRunGetError) 207 | Right (Left myGetError) -> Left (SerializerError_Get myGetError) 208 | Right (Right res) -> Right res 209 | 210 | -- | Helper for transforming @runPutM@ result 211 | transformPutError 212 | :: (Either customPutError (), ByteString) 213 | -> Either customPutError ByteString 214 | transformPutError (e, r) = either Left (pure $ Right r) e 215 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Arbitrary.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Store.Remote.Arbitrary where 5 | 6 | import Data.Some (Some(Some)) 7 | import System.Nix.Arbitrary () 8 | import System.Nix.Store.Types (RepairMode(..)) 9 | import System.Nix.Store.Remote.Types 10 | 11 | import Test.QuickCheck (Arbitrary(..), oneof, suchThat) 12 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 13 | 14 | deriving via GenericArbitrary CheckMode 15 | instance Arbitrary CheckMode 16 | 17 | deriving via GenericArbitrary SubstituteMode 18 | instance Arbitrary SubstituteMode 19 | 20 | deriving via GenericArbitrary ProtoStoreConfig 21 | instance Arbitrary ProtoStoreConfig 22 | 23 | deriving via GenericArbitrary ProtoVersion 24 | instance Arbitrary ProtoVersion 25 | 26 | deriving via GenericArbitrary StoreText 27 | instance Arbitrary StoreText 28 | 29 | -- * Logger 30 | 31 | deriving via GenericArbitrary Activity 32 | instance Arbitrary Activity 33 | 34 | deriving via GenericArbitrary ActivityID 35 | instance Arbitrary ActivityID 36 | 37 | deriving via GenericArbitrary ActivityResult 38 | instance Arbitrary ActivityResult 39 | 40 | deriving via GenericArbitrary Field 41 | instance Arbitrary Field 42 | 43 | instance Arbitrary Trace where 44 | arbitrary = do 45 | -- we encode 0 position as Nothing 46 | tracePosition <- arbitrary `suchThat` (/= Just 0) 47 | traceHint <- arbitrary 48 | 49 | pure Trace{..} 50 | 51 | deriving via GenericArbitrary BasicError 52 | instance Arbitrary BasicError 53 | 54 | instance Arbitrary ErrorInfo where 55 | arbitrary = do 56 | errorInfoLevel <- arbitrary 57 | errorInfoMessage <- arbitrary 58 | -- we encode 0 position as Nothing 59 | errorInfoPosition <- arbitrary `suchThat` (/= Just 0) 60 | errorInfoTraces <- arbitrary 61 | 62 | pure ErrorInfo{..} 63 | 64 | deriving via GenericArbitrary LoggerOpCode 65 | instance Arbitrary LoggerOpCode 66 | 67 | deriving via GenericArbitrary Logger 68 | instance Arbitrary Logger 69 | 70 | deriving via GenericArbitrary Verbosity 71 | instance Arbitrary Verbosity 72 | 73 | -- * GC 74 | 75 | deriving via GenericArbitrary GCAction 76 | instance Arbitrary GCAction 77 | 78 | deriving via GenericArbitrary GCOptions 79 | instance Arbitrary GCOptions 80 | 81 | -- * Handshake 82 | 83 | deriving via GenericArbitrary WorkerMagic 84 | instance Arbitrary WorkerMagic 85 | 86 | deriving via GenericArbitrary TrustedFlag 87 | instance Arbitrary TrustedFlag 88 | 89 | -- * Worker protocol 90 | 91 | deriving via GenericArbitrary WorkerOp 92 | instance Arbitrary WorkerOp 93 | 94 | -- ** Request 95 | 96 | instance Arbitrary (Some StoreRequest) where 97 | arbitrary = oneof 98 | [ Some <$> (AddToStore <$> arbitrary <*> arbitrary <*> arbitrary <*> pure RepairMode_DontRepair) 99 | , Some <$> (AddTextToStore <$> arbitrary <*> arbitrary <*> pure RepairMode_DontRepair) 100 | , Some <$> (AddSignatures <$> arbitrary <*> arbitrary) 101 | , Some . AddIndirectRoot <$> arbitrary 102 | , Some . AddTempRoot <$> arbitrary 103 | , Some <$> (BuildPaths <$> arbitrary <*> arbitrary) 104 | , Some <$> (BuildDerivation <$> arbitrary <*> arbitrary <*> arbitrary) 105 | , Some . CollectGarbage <$> arbitrary 106 | , Some . EnsurePath <$> arbitrary 107 | , pure $ Some FindRoots 108 | , Some . IsValidPath <$> arbitrary 109 | , Some . NarFromPath <$> arbitrary 110 | , Some <$> (QueryValidPaths <$> arbitrary <*> arbitrary) 111 | , pure $ Some QueryAllValidPaths 112 | , Some . QuerySubstitutablePaths <$> arbitrary 113 | , Some . QueryPathInfo <$> arbitrary 114 | , Some . QueryReferrers <$> arbitrary 115 | , Some . QueryValidDerivers <$> arbitrary 116 | , Some . QueryDerivationOutputs <$> arbitrary 117 | , Some . QueryDerivationOutputNames <$> arbitrary 118 | , Some . QueryPathFromHashPart <$> arbitrary 119 | , Some . QueryMissing <$> arbitrary 120 | , pure $ Some OptimiseStore 121 | , pure $ Some SyncWithGC 122 | , Some <$> (VerifyStore <$> arbitrary <*> arbitrary) 123 | ] 124 | 125 | -- ** Reply 126 | 127 | deriving via GenericArbitrary SuccessCodeReply 128 | instance Arbitrary SuccessCodeReply 129 | 130 | deriving via GenericArbitrary GCResult 131 | instance Arbitrary GCResult 132 | 133 | deriving via GenericArbitrary GCRoot 134 | instance Arbitrary GCRoot 135 | 136 | deriving via GenericArbitrary Missing 137 | instance Arbitrary Missing 138 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Logger.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Logger 2 | ( processOutput 3 | ) where 4 | 5 | import Control.Monad.Except (throwError) 6 | import Control.Monad.IO.Class (liftIO) 7 | import Data.ByteString (ByteString) 8 | import Data.Serialize (Result(..)) 9 | import System.Nix.Store.Remote.Serializer (LoggerSError, logger, runSerialT) 10 | import System.Nix.Store.Remote.Socket (sockGet8) 11 | import System.Nix.Store.Remote.MonadStore (MonadRemoteStore, RemoteStoreError(..), appendLog, getDataSource, getDataSink, getStoreSocket, getProtoVersion) 12 | import System.Nix.Store.Remote.Types.Logger (Logger(..)) 13 | import System.Nix.Store.Remote.Types.ProtoVersion (ProtoVersion) 14 | 15 | import qualified Control.Monad 16 | import qualified Data.Serialize.Get 17 | import qualified Data.Serializer 18 | import qualified Network.Socket.ByteString 19 | 20 | processOutput 21 | :: MonadRemoteStore m 22 | => m () 23 | processOutput = do 24 | protoVersion <- getProtoVersion 25 | sockGet8 >>= go . (decoder protoVersion) 26 | where 27 | decoder 28 | :: ProtoVersion 29 | -> ByteString 30 | -> Result (Either LoggerSError Logger) 31 | decoder protoVersion = 32 | Data.Serialize.Get.runGetPartial 33 | (runSerialT protoVersion $ Data.Serializer.getS logger) 34 | 35 | go 36 | :: MonadRemoteStore m 37 | => Result (Either LoggerSError Logger) 38 | -> m () 39 | go (Done ectrl leftover) = do 40 | let loop = do 41 | protoVersion <- getProtoVersion 42 | sockGet8 >>= go . (decoder protoVersion) 43 | 44 | Control.Monad.unless (leftover == mempty) $ 45 | throwError 46 | $ RemoteStoreError_LoggerLeftovers 47 | (show ectrl) 48 | leftover 49 | 50 | case ectrl of 51 | Left e -> throwError $ RemoteStoreError_SerializerLogger e 52 | Right ctrl -> do 53 | case ctrl of 54 | -- These two terminate the logger loop 55 | Logger_Error e -> throwError $ RemoteStoreError_LoggerError e 56 | Logger_Last -> appendLog Logger_Last 57 | 58 | -- Read data from source 59 | Logger_Read size -> do 60 | mSource <- getDataSource 61 | case mSource of 62 | Nothing -> 63 | throwError RemoteStoreError_NoDataSourceProvided 64 | Just source -> do 65 | mChunk <- liftIO $ source size 66 | case mChunk of 67 | Nothing -> throwError RemoteStoreError_DataSourceExhausted 68 | Just chunk -> do 69 | sock <- getStoreSocket 70 | liftIO $ Network.Socket.ByteString.sendAll sock chunk 71 | 72 | loop 73 | 74 | -- Write data to sink 75 | Logger_Write out -> do 76 | mSink <- getDataSink 77 | case mSink of 78 | Nothing -> 79 | throwError RemoteStoreError_NoDataSinkProvided 80 | Just sink -> do 81 | liftIO $ sink out 82 | 83 | loop 84 | 85 | -- Following we just append and loop 86 | -- but listed here explicitely for posterity 87 | x@(Logger_Next _) -> appendLog x >> loop 88 | x@(Logger_StartActivity {}) -> appendLog x >> loop 89 | x@(Logger_StopActivity {}) -> appendLog x >> loop 90 | x@(Logger_Result {}) -> appendLog x >> loop 91 | 92 | go (Partial k) = do 93 | chunk <- sockGet8 94 | go (k chunk) 95 | 96 | go (Fail msg leftover) = 97 | throwError 98 | $ RemoteStoreError_LoggerParserFail 99 | msg 100 | leftover 101 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Socket.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Socket where 2 | 3 | import Control.Monad.Except (MonadError, throwError) 4 | import Control.Monad.IO.Class (MonadIO(..)) 5 | import Data.ByteString (ByteString) 6 | import Data.Serialize.Get (Get, Result(..)) 7 | import Data.Serialize.Put (Put, runPut) 8 | import Network.Socket.ByteString (recv, sendAll) 9 | import System.Nix.Store.Remote.MonadStore (MonadRemoteStore(..), RemoteStoreError(..)) 10 | import System.Nix.Store.Remote.Serializer (NixSerializer, runP, runSerialT) 11 | import System.Nix.Store.Remote.Types (ProtoStoreConfig) 12 | 13 | import qualified Control.Exception 14 | import qualified Data.ByteString 15 | import qualified Data.Serializer 16 | import qualified Data.Serialize.Get 17 | 18 | genericIncremental 19 | :: ( MonadIO m 20 | , MonadError RemoteStoreError m 21 | , Show a 22 | ) 23 | => m ByteString 24 | -> Get a 25 | -> m a 26 | genericIncremental getsome parser = do 27 | getsome >>= go . decoder 28 | where 29 | decoder = Data.Serialize.Get.runGetPartial parser 30 | go (Done x leftover) | leftover /= mempty = 31 | throwError 32 | $ RemoteStoreError_GenericIncrementalLeftovers 33 | (show x) 34 | leftover 35 | 36 | go (Done x _leftover) = pure x 37 | 38 | go (Partial k) = do 39 | chunk <- getsome 40 | go (k chunk) 41 | 42 | go (Fail msg leftover) = 43 | throwError 44 | $ RemoteStoreError_GenericIncrementalFail 45 | msg 46 | leftover 47 | 48 | sockGet8 49 | :: MonadRemoteStore m 50 | => m ByteString 51 | sockGet8 = do 52 | soc <- getStoreSocket 53 | eresult <- liftIO $ Control.Exception.try $ recv soc 8 54 | case eresult of 55 | Left e -> 56 | throwError $ RemoteStoreError_IOException e 57 | 58 | Right result | Data.ByteString.length result == 0 -> 59 | throwError RemoteStoreError_Disconnected 60 | 61 | Right result | otherwise -> 62 | pure result 63 | 64 | sockPut 65 | :: MonadRemoteStore m 66 | => Put 67 | -> m () 68 | sockPut p = do 69 | soc <- getStoreSocket 70 | liftIO $ sendAll soc $ runPut p 71 | 72 | sockPutS 73 | :: ( MonadRemoteStore m 74 | , MonadError e m 75 | ) 76 | => NixSerializer ProtoStoreConfig e a 77 | -> a 78 | -> m () 79 | sockPutS s a = do 80 | cfg <- getConfig 81 | sock <- getStoreSocket 82 | case runP s cfg a of 83 | Right x -> liftIO $ sendAll sock x 84 | Left e -> throwError e 85 | 86 | sockGetS 87 | :: ( MonadRemoteStore m 88 | , MonadError e m 89 | , Show a 90 | , Show e 91 | ) 92 | => NixSerializer ProtoStoreConfig e a 93 | -> m a 94 | sockGetS s = do 95 | cfg <- getConfig 96 | res <- genericIncremental sockGet8 97 | $ runSerialT cfg $ Data.Serializer.getS s 98 | 99 | case res of 100 | Right x -> pure x 101 | Left e -> throwError e 102 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types 2 | ( module System.Nix.Store.Remote.Types.Activity 3 | , module System.Nix.Store.Remote.Types.GC 4 | , module System.Nix.Store.Remote.Types.CheckMode 5 | , module System.Nix.Store.Remote.Types.Logger 6 | , module System.Nix.Store.Remote.Types.ProtoVersion 7 | , module System.Nix.Store.Remote.Types.Query 8 | , module System.Nix.Store.Remote.Types.StoreConfig 9 | , module System.Nix.Store.Remote.Types.StoreRequest 10 | , module System.Nix.Store.Remote.Types.StoreText 11 | , module System.Nix.Store.Remote.Types.SubstituteMode 12 | , module System.Nix.Store.Remote.Types.SuccessCodeReply 13 | , module System.Nix.Store.Remote.Types.TrustedFlag 14 | , module System.Nix.Store.Remote.Types.Verbosity 15 | , module System.Nix.Store.Remote.Types.WorkerMagic 16 | , module System.Nix.Store.Remote.Types.WorkerOp 17 | ) where 18 | 19 | import System.Nix.Store.Remote.Types.Activity 20 | import System.Nix.Store.Remote.Types.GC 21 | import System.Nix.Store.Remote.Types.CheckMode 22 | import System.Nix.Store.Remote.Types.Logger 23 | import System.Nix.Store.Remote.Types.ProtoVersion 24 | import System.Nix.Store.Remote.Types.Query 25 | import System.Nix.Store.Remote.Types.StoreConfig 26 | import System.Nix.Store.Remote.Types.StoreRequest 27 | import System.Nix.Store.Remote.Types.StoreText 28 | import System.Nix.Store.Remote.Types.SubstituteMode 29 | import System.Nix.Store.Remote.Types.SuccessCodeReply 30 | import System.Nix.Store.Remote.Types.TrustedFlag 31 | import System.Nix.Store.Remote.Types.Verbosity 32 | import System.Nix.Store.Remote.Types.WorkerMagic 33 | import System.Nix.Store.Remote.Types.WorkerOp 34 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/Activity.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.Activity 2 | ( Activity(..) 3 | , ActivityID(..) 4 | , ActivityResult(..) 5 | ) where 6 | 7 | import GHC.Generics 8 | 9 | -- | Type of the activity 10 | -- 11 | -- We don't have Activity_Unknown here 12 | -- as we can do @Maybe Activity@ and @Nothing@ 13 | -- corresponding to Unknown (which has 0 value) 14 | -- 15 | -- Rest of the values are offset by @(+100)@ 16 | -- on the wire, i.e.: 17 | -- 18 | -- * @Activity_CopyPath = 100@ 19 | -- * @Activity_BuildWaiting = 111@ 20 | data Activity 21 | = Activity_CopyPath 22 | | Activity_FileTransfer 23 | | Activity_Realise 24 | | Activity_CopyPaths 25 | | Activity_Builds 26 | | Activity_Build 27 | | Activity_OptimiseStore 28 | | Activity_VerifyPaths 29 | | Activity_Substitute 30 | | Activity_QueryPathInfo 31 | | Activity_PostBuildHook 32 | | Activity_BuildWaiting 33 | deriving (Bounded, Eq, Enum, Generic, Ord, Show) 34 | 35 | -- | Numeric ID of the activity 36 | newtype ActivityID = ActivityID { unActivityID :: Int } 37 | deriving (Eq, Generic, Ord, Show) 38 | 39 | -- | Result of some activity 40 | -- 41 | -- The values are offset by @(+100)@ 42 | -- on the wire, i.e.: 43 | -- 44 | -- * @ActivityResult_FileLinked = 100@ 45 | -- * @ActivityResult_PostBuildLogLine = 107@ 46 | data ActivityResult 47 | = ActivityResult_FileLinked 48 | | ActivityResult_BuildLogLine 49 | | ActivityResult_UnstrustedPath 50 | | ActivityResult_CorruptedPath 51 | | ActivityResult_SetPhase 52 | | ActivityResult_Progress 53 | | ActivityResult_SetExpected 54 | | ActivityResult_PostBuildLogLine 55 | deriving (Bounded, Eq, Enum, Generic, Ord, Show) 56 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/CheckMode.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.CheckMode 2 | ( CheckMode(..) 3 | ) where 4 | 5 | import GHC.Generics 6 | 7 | -- | Check mode, used by @verifyStore@ 8 | data CheckMode 9 | = CheckMode_DoCheck 10 | | CheckMode_DontCheck 11 | deriving (Bounded, Eq, Generic, Enum, Ord, Show) 12 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/GC.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Description : Garbage collection actions / options 3 | Maintainer : srk 4 | |-} 5 | module System.Nix.Store.Remote.Types.GC ( 6 | GCAction(..) 7 | , GCOptions(..) 8 | , GCResult(..) 9 | , GCRoot(..) 10 | ) where 11 | 12 | import Data.HashSet (HashSet) 13 | import Data.Word (Word64) 14 | import GHC.Generics (Generic) 15 | import System.Nix.StorePath (StorePath) 16 | import System.Posix.ByteString (RawFilePath) 17 | 18 | -- | Garbage collection action 19 | data GCAction 20 | = GCAction_ReturnLive -- ^ Return the set of paths reachable from roots (closure) 21 | | GCAction_ReturnDead -- ^ Return unreachable paths 22 | | GCAction_DeleteDead -- ^ Delete unreachable paths 23 | | GCAction_DeleteSpecific -- ^ Delete specified paths 24 | deriving (Bounded, Eq, Enum, Generic, Ord, Show) 25 | 26 | -- | Garbage collector operation options 27 | data GCOptions = GCOptions 28 | { -- | Operation 29 | gcOptionsOperation :: GCAction 30 | -- | If set, then reachability from the roots is ignored (unused) 31 | , gcOptionsIgnoreLiveness :: Bool 32 | -- | Paths to delete for @GCAction_DeleteSpecific@ 33 | , gcOptionsPathsToDelete :: HashSet StorePath 34 | -- | Stop after `gcOptions_maxFreed` bytes have been freed 35 | , gcOptionsMaxFreed :: Word64 36 | } deriving (Eq, Generic, Ord, Show) 37 | 38 | -- | Result of the garbage collection operation 39 | data GCResult = GCResult 40 | { -- | Depending on the action, the GC roots, 41 | -- or the paths that would be or have been deleted 42 | gcResultDeletedPaths :: HashSet StorePath 43 | -- | The number of bytes that would be or was freed for 44 | -- 45 | -- - @GCAction_ReturnDead@ 46 | -- - @GCAction_DeleteDead@ 47 | -- - @GCAction_DeleteSpecific@ 48 | , gcResultBytesFreed :: Word64 49 | } deriving (Eq, Generic, Ord, Show) 50 | 51 | -- | Used as a part of the result of @FindRoots@ operation 52 | data GCRoot 53 | = GCRoot_Censored -- ^ Source path is censored since the user is not trusted 54 | | GCRoot_Path RawFilePath -- ^ Raw source path 55 | deriving (Eq, Generic, Ord, Show) 56 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/Handshake.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.Handshake 2 | ( ClientHandshakeInput(..) 3 | , ClientHandshakeOutput(..) 4 | , ServerHandshakeInput(..) 5 | , ServerHandshakeOutput(..) 6 | ) where 7 | 8 | import Data.Text (Text) 9 | import GHC.Generics (Generic) 10 | import System.Nix.Store.Remote.Types.ProtoVersion (ProtoVersion) 11 | import System.Nix.Store.Remote.Types.TrustedFlag (TrustedFlag) 12 | 13 | -- | Data sent by the client during initial protocol handshake 14 | data ClientHandshakeInput = ClientHandshakeInput 15 | { clientHandshakeInputOurVersion :: ProtoVersion -- ^ Our protocol version (that we advertise to the server) 16 | } deriving (Eq, Generic, Ord, Show) 17 | 18 | -- | Data received by the client via initial protocol handshake 19 | data ClientHandshakeOutput = ClientHandshakeOutput 20 | { clientHandshakeOutputNixVersion :: Maybe Text -- ^ Textual version, since 1.33 21 | , clientHandshakeOutputTrust :: Maybe TrustedFlag -- ^ Whether remote side trusts us 22 | , clientHandshakeOutputLeastCommonVersion :: ProtoVersion -- ^ Minimum protocol version supported by both sides 23 | , clientHandshakeOutputServerVersion :: ProtoVersion -- ^ Protocol version supported by the server 24 | } deriving (Eq, Generic, Ord, Show) 25 | 26 | -- | Data sent by the server during initial protocol handshake 27 | data ServerHandshakeInput = ServerHandshakeInput 28 | { serverHandshakeInputNixVersion :: Text -- ^ Textual version, since 1.33 29 | , serverHandshakeInputOurVersion :: ProtoVersion -- ^ Our protocol version (that we advertise to the client) 30 | , serverHandshakeInputTrust :: Maybe TrustedFlag -- ^ Whether client should trusts us 31 | } deriving (Eq, Generic, Ord, Show) 32 | 33 | -- | Data received by the server during initial protocol handshake 34 | data ServerHandshakeOutput = ServerHandshakeOutput 35 | { serverHandshakeOutputLeastCommonVersion :: ProtoVersion -- ^ Minimum protocol version supported by both sides 36 | , serverHandshakeOutputClientVersion :: ProtoVersion -- ^ Protocol version supported by the client 37 | } deriving (Eq, Generic, Ord, Show) 38 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/Logger.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.Logger 2 | ( Field(..) 3 | , Trace(..) 4 | , BasicError(..) 5 | , ErrorInfo(..) 6 | , Logger(..) 7 | , LoggerOpCode(..) 8 | , loggerOpCodeToWord64 9 | , word64ToLoggerOpCode 10 | , isError 11 | ) where 12 | 13 | import Data.ByteString (ByteString) 14 | import Data.Text (Text) 15 | import Data.Word (Word64) 16 | import GHC.Generics 17 | import System.Nix.Store.Remote.Types.Activity (Activity, ActivityID, ActivityResult) 18 | import System.Nix.Store.Remote.Types.Verbosity (Verbosity) 19 | 20 | data Field 21 | = Field_LogStr Text 22 | | Field_LogInt Int 23 | deriving (Eq, Generic, Ord, Show) 24 | 25 | -- | Error trace 26 | data Trace = Trace 27 | { tracePosition :: Maybe Int -- Error position, Nix always writes 0 here 28 | , traceHint :: Text 29 | } 30 | deriving (Eq, Generic, Ord, Show) 31 | 32 | data BasicError = BasicError 33 | { basicErrorExitStatus :: Int 34 | , basicErrorMessage :: Text 35 | } 36 | deriving (Eq, Generic, Ord, Show) 37 | 38 | -- | Extended error info 39 | -- available for protoVersion_minor >= 26 40 | data ErrorInfo = ErrorInfo 41 | { errorInfoLevel :: Verbosity 42 | , errorInfoMessage :: Text 43 | , errorInfoPosition :: Maybe Int -- Error position, Nix always writes 0 here 44 | , errorInfoTraces :: [Trace] 45 | } 46 | deriving (Eq, Generic, Ord, Show) 47 | 48 | data LoggerOpCode 49 | = LoggerOpCode_Next 50 | | LoggerOpCode_Read 51 | | LoggerOpCode_Write 52 | | LoggerOpCode_Last 53 | | LoggerOpCode_Error 54 | | LoggerOpCode_StartActivity 55 | | LoggerOpCode_StopActivity 56 | | LoggerOpCode_Result 57 | deriving (Eq, Generic, Ord, Show) 58 | 59 | loggerOpCodeToWord64 :: LoggerOpCode -> Word64 60 | loggerOpCodeToWord64 = \case 61 | LoggerOpCode_Next -> 0x6f6c6d67 62 | LoggerOpCode_Read -> 0x64617461 63 | LoggerOpCode_Write -> 0x64617416 64 | LoggerOpCode_Last -> 0x616c7473 65 | LoggerOpCode_Error -> 0x63787470 66 | LoggerOpCode_StartActivity -> 0x53545254 67 | LoggerOpCode_StopActivity -> 0x53544f50 68 | LoggerOpCode_Result -> 0x52534c54 69 | 70 | word64ToLoggerOpCode :: Word64 -> Either String LoggerOpCode 71 | word64ToLoggerOpCode = \case 72 | 0x6f6c6d67 -> Right LoggerOpCode_Next 73 | 0x64617461 -> Right LoggerOpCode_Read 74 | 0x64617416 -> Right LoggerOpCode_Write 75 | 0x616c7473 -> Right LoggerOpCode_Last 76 | 0x63787470 -> Right LoggerOpCode_Error 77 | 0x53545254 -> Right LoggerOpCode_StartActivity 78 | 0x53544f50 -> Right LoggerOpCode_StopActivity 79 | 0x52534c54 -> Right LoggerOpCode_Result 80 | x -> Left $ "Invalid LoggerOpCode: " ++ show x 81 | 82 | data Logger 83 | = Logger_Next Text 84 | | Logger_Read Word64 -- data needed from source 85 | | Logger_Write ByteString -- data for sink 86 | | Logger_Last 87 | | Logger_Error (Either BasicError ErrorInfo) 88 | | Logger_StartActivity 89 | { startActivityID :: ActivityID 90 | , startActivityVerbosity :: Verbosity 91 | , startActivityType :: Maybe Activity 92 | , startActivityString :: ByteString 93 | , startActivityFields :: [Field] 94 | , startActivityParentID :: ActivityID 95 | } 96 | | Logger_StopActivity 97 | { stopActivityID :: ActivityID 98 | } 99 | | Logger_Result 100 | { resultActivityID :: ActivityID 101 | , resultType :: ActivityResult 102 | , resultFields :: [Field] 103 | } 104 | deriving (Eq, Generic, Ord, Show) 105 | 106 | isError :: Logger -> Bool 107 | isError Logger_Error {} = True 108 | isError _ = False 109 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/NoReply.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.NoReply 2 | ( NoReply(..) 3 | ) where 4 | 5 | -- | Reply type for the case where the server does not reply 6 | data NoReply = NoReply 7 | deriving (Show) 8 | 9 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/ProtoVersion.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.ProtoVersion 2 | ( ProtoVersion(..) 3 | , HasProtoVersion(..) 4 | ) where 5 | 6 | import Data.Default.Class (Default(def)) 7 | import Data.Word (Word8, Word16) 8 | import GHC.Generics (Generic) 9 | 10 | data ProtoVersion = ProtoVersion 11 | { protoVersion_major :: Word16 12 | , protoVersion_minor :: Word8 13 | } 14 | deriving (Eq, Generic, Ord, Show) 15 | 16 | -- | The protocol version we support 17 | instance Default ProtoVersion where 18 | def = ProtoVersion 19 | { protoVersion_major = 1 20 | , protoVersion_minor = 24 21 | } 22 | 23 | class HasProtoVersion r where 24 | hasProtoVersion :: r -> ProtoVersion 25 | 26 | instance HasProtoVersion ProtoVersion where 27 | hasProtoVersion = id 28 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/Query.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.Query 2 | ( module System.Nix.Store.Remote.Types.Query.Missing 3 | ) where 4 | 5 | import System.Nix.Store.Remote.Types.Query.Missing 6 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/Query/Missing.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.Query.Missing 2 | ( Missing(..) 3 | ) where 4 | 5 | import Data.HashSet (HashSet) 6 | import Data.Word (Word64) 7 | import GHC.Generics (Generic) 8 | import System.Nix.StorePath (StorePath) 9 | 10 | -- | Result of @QueryMissing@ @StoreRequest@ 11 | data Missing = Missing 12 | { missingWillBuild :: HashSet StorePath -- ^ Paths that will be built 13 | , missingWillSubstitute :: HashSet StorePath -- ^ Paths that can be substituted from cache 14 | , missingUnknownPaths :: HashSet StorePath -- ^ Path w/o any information 15 | , missingDownloadSize :: Word64 -- ^ Total size of packed NARs to download 16 | , missingNarSize :: Word64 -- ^ Total size of NARs after unpacking 17 | } 18 | deriving (Eq, Generic, Ord, Show) 19 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/StoreConfig.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-orphans #-} 2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 3 | module System.Nix.Store.Remote.Types.StoreConfig 4 | ( ProtoStoreConfig(..) 5 | , StoreSocketPath(..) 6 | , StoreTCP(..) 7 | , StoreConnection(..) 8 | , HasStoreSocket(..) 9 | ) where 10 | 11 | import Data.Default.Class (Default(def)) 12 | import Data.String (IsString) 13 | import GHC.Generics (Generic) 14 | import Network.Socket (Socket) 15 | import System.Nix.StorePath (HasStoreDir(..), StoreDir) 16 | import System.Nix.Store.Remote.Types.ProtoVersion (HasProtoVersion(..), ProtoVersion) 17 | 18 | class HasStoreSocket r where 19 | hasStoreSocket :: r -> Socket 20 | 21 | instance HasStoreSocket Socket where 22 | hasStoreSocket = id 23 | 24 | data ProtoStoreConfig = ProtoStoreConfig 25 | { protoStoreConfigDir :: StoreDir 26 | , protoStoreConfigProtoVersion :: ProtoVersion 27 | } deriving (Eq, Generic, Ord, Show) 28 | 29 | instance Default ProtoStoreConfig where 30 | def = ProtoStoreConfig def def 31 | 32 | instance HasStoreDir StoreDir where 33 | hasStoreDir = id 34 | 35 | instance HasStoreDir ProtoStoreConfig where 36 | hasStoreDir = protoStoreConfigDir 37 | 38 | instance HasProtoVersion ProtoStoreConfig where 39 | hasProtoVersion = protoStoreConfigProtoVersion 40 | 41 | newtype StoreSocketPath = StoreSocketPath 42 | { unStoreSocketPath :: FilePath 43 | } 44 | deriving newtype (IsString) 45 | deriving stock (Eq, Generic, Ord, Show) 46 | 47 | instance Default StoreSocketPath where 48 | def = StoreSocketPath "/nix/var/nix/daemon-socket/socket" 49 | 50 | data StoreTCP = StoreTCP 51 | { storeTCPHost :: String 52 | , storeTCPPort :: Int 53 | } deriving (Eq, Generic, Ord, Show) 54 | 55 | data StoreConnection 56 | = StoreConnection_Socket StoreSocketPath 57 | | StoreConnection_TCP StoreTCP 58 | deriving (Eq, Generic, Ord, Show) 59 | 60 | instance Default StoreConnection where 61 | def = StoreConnection_Socket def 62 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/StoreReply.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.StoreReply 2 | ( StoreReply(..) 3 | ) where 4 | 5 | import Data.HashSet (HashSet) 6 | import Data.Map (Map) 7 | import System.Nix.Build (BuildResult) 8 | import System.Nix.StorePath (StorePath, StorePathName) 9 | import System.Nix.StorePath.Metadata (Metadata) 10 | import System.Nix.Store.Remote.Serializer 11 | import System.Nix.Store.Remote.Types.NoReply (NoReply(..)) 12 | import System.Nix.Store.Remote.Types.SuccessCodeReply (SuccessCodeReply) 13 | import System.Nix.Store.Remote.Types.GC (GCResult, GCRoot) 14 | import System.Nix.Store.Remote.Types.Query.Missing (Missing) 15 | import System.Nix.Store.Remote.Types.StoreConfig (ProtoStoreConfig) 16 | 17 | -- | Get @NixSerializer@ for some type @a@ 18 | -- This could also be generalized for every type 19 | -- we have a serializer for but we mostly need 20 | -- this for replies and it would make look serializers 21 | -- quite hodor, like @a <- getS get; b <- getS get@ 22 | class StoreReply a where 23 | getReplyS :: NixSerializer ProtoStoreConfig ReplySError a 24 | 25 | instance StoreReply SuccessCodeReply where 26 | getReplyS = opSuccess 27 | 28 | instance StoreReply NoReply where 29 | getReplyS = noop NoReply 30 | 31 | instance StoreReply Bool where 32 | getReplyS = mapPrimE bool 33 | 34 | instance StoreReply BuildResult where 35 | getReplyS = buildResult 36 | 37 | instance StoreReply GCResult where 38 | getReplyS = gcResult 39 | 40 | instance StoreReply (Map GCRoot StorePath) where 41 | getReplyS = mapS gcRoot (mapPrimE storePath) 42 | 43 | instance StoreReply Missing where 44 | getReplyS = missing 45 | 46 | instance StoreReply (Maybe (Metadata StorePath)) where 47 | getReplyS = maybePathMetadata 48 | 49 | instance StoreReply StorePath where 50 | getReplyS = mapPrimE storePath 51 | 52 | instance StoreReply (HashSet StorePath) where 53 | getReplyS = mapPrimE (hashSet storePath) 54 | 55 | instance StoreReply (HashSet StorePathName) where 56 | getReplyS = mapPrimE (hashSet storePathName) 57 | 58 | mapPrimE 59 | :: NixSerializer r SError a 60 | -> NixSerializer r ReplySError a 61 | mapPrimE = mapErrorS ReplySError_PrimGet 62 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/StoreText.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.StoreText 2 | ( StoreText(..) 3 | ) where 4 | 5 | import Data.Text (Text) 6 | import GHC.Generics (Generic) 7 | import System.Nix.StorePath (StorePathName) 8 | 9 | data StoreText = StoreText 10 | { storeTextName :: StorePathName 11 | , storeTextText :: Text 12 | } deriving (Eq, Generic, Ord, Show) 13 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/SubstituteMode.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.SubstituteMode 2 | ( SubstituteMode(..) 3 | ) where 4 | 5 | import GHC.Generics 6 | 7 | -- | Path substitution mode, used by @queryValidPaths@ 8 | data SubstituteMode 9 | = SubstituteMode_DoSubstitute 10 | | SubstituteMode_DontSubstitute 11 | deriving (Bounded, Eq, Generic, Enum, Ord, Show) 12 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/SuccessCodeReply.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.SuccessCodeReply 2 | ( SuccessCodeReply(..) 3 | ) where 4 | 5 | import GHC.Generics (Generic) 6 | 7 | -- | Reply that checks an int success return value 8 | data SuccessCodeReply = SuccessCodeReply 9 | deriving (Eq, Show, Generic) 10 | 11 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/TrustedFlag.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.TrustedFlag 2 | ( TrustedFlag(..) 3 | ) where 4 | 5 | import GHC.Generics (Generic) 6 | 7 | -- | Whether remote side trust us 8 | data TrustedFlag 9 | = TrustedFlag_Trusted 10 | | TrustedFlag_NotTrusted 11 | deriving (Bounded, Eq, Generic, Enum, Ord, Show) 12 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/Verbosity.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.Verbosity 2 | ( Verbosity(..) 3 | ) where 4 | 5 | import GHC.Generics 6 | 7 | -- | Logging verbosity 8 | data Verbosity 9 | = Verbosity_Error 10 | | Verbosity_Warn 11 | | Verbosity_Notice 12 | | Verbosity_Info 13 | | Verbosity_Talkative 14 | | Verbosity_Chatty 15 | | Verbosity_Debug 16 | | Verbosity_Vomit 17 | deriving (Bounded, Eq, Enum, Generic, Ord, Show) 18 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/WorkerMagic.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.WorkerMagic 2 | ( WorkerMagic(..) 3 | , workerMagicToWord64 4 | , word64ToWorkerMagic 5 | ) where 6 | 7 | import Data.Word (Word64) 8 | import GHC.Generics (Generic) 9 | 10 | -- | WorkerMagic 11 | -- 12 | -- Magic numbers exchange during handshake 13 | data WorkerMagic 14 | = WorkerMagic_One 15 | | WorkerMagic_Two 16 | deriving (Eq, Generic, Ord, Show) 17 | 18 | workerMagicToWord64 :: WorkerMagic -> Word64 19 | workerMagicToWord64 = \case 20 | WorkerMagic_One -> 0x6e697863 21 | WorkerMagic_Two -> 0x6478696f 22 | 23 | word64ToWorkerMagic :: Word64 -> Either String WorkerMagic 24 | word64ToWorkerMagic = \case 25 | 0x6e697863 -> Right WorkerMagic_One 26 | 0x6478696f -> Right WorkerMagic_Two 27 | x -> Left $ "Invalid WorkerMagic: " ++ show x 28 | -------------------------------------------------------------------------------- /hnix-store-remote/src/System/Nix/Store/Remote/Types/WorkerOp.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Store.Remote.Types.WorkerOp 2 | ( WorkerOp(..) 3 | ) where 4 | 5 | import GHC.Generics (Generic) 6 | 7 | -- | Worker opcode 8 | -- 9 | -- This type has gaps filled in so that the GHC builtin 10 | -- Enum instance lands on the right values. 11 | data WorkerOp 12 | = WorkerOp_Reserved_0__ -- 0 13 | | WorkerOp_IsValidPath -- 1 14 | | WorkerOp_Reserved_2__ -- 2 15 | | WorkerOp_HasSubstitutes -- 3 16 | | WorkerOp_QueryPathHash -- 4 // obsolete 17 | | WorkerOp_QueryReferences -- 5 // obsolete 18 | | WorkerOp_QueryReferrers -- 6 19 | | WorkerOp_AddToStore -- 7 20 | | WorkerOp_AddTextToStore -- 8 // obsolete since 1.25, Nix 3.0. Use wopAddToStore 21 | | WorkerOp_BuildPaths -- 9 22 | | WorkerOp_EnsurePath -- 10 0xa 23 | | WorkerOp_AddTempRoot -- 11 0xb 24 | | WorkerOp_AddIndirectRoot -- 12 0xc 25 | | WorkerOp_SyncWithGC -- 13 0xd 26 | | WorkerOp_FindRoots -- 14 0xe 27 | | WorkerOp_Reserved_15__ -- 15 0xf 28 | | WorkerOp_ExportPath -- 16 0x10 // obsolete 29 | | WorkerOp_Reserved_17__ -- 17 0x11 30 | | WorkerOp_QueryDeriver -- 18 0x12 // obsolete 31 | | WorkerOp_SetOptions -- 19 0x13 32 | | WorkerOp_CollectGarbage -- 20 0x14 33 | | WorkerOp_QuerySubstitutablePathInfo -- 21 0x15 34 | | WorkerOp_QueryDerivationOutputs -- 22 0x16 // obsolete 35 | | WorkerOp_QueryAllValidPaths -- 23 0x17 36 | | WorkerOp_QueryFailedPaths -- 24 0x18 37 | | WorkerOp_ClearFailedPaths -- 25 0x19 38 | | WorkerOp_QueryPathInfo -- 26 0x1a 39 | | WorkerOp_ImportPaths -- 27 0x1b // obsolete 40 | | WorkerOp_QueryDerivationOutputNames -- 28 0x1c // obsolete 41 | | WorkerOp_QueryPathFromHashPart -- 29 0x1d 42 | | WorkerOp_QuerySubstitutablePathInfos -- 30 0x1e 43 | | WorkerOp_QueryValidPaths -- 31 0x1f 44 | | WorkerOp_QuerySubstitutablePaths -- 32 0x20 45 | | WorkerOp_QueryValidDerivers -- 33 0x21 46 | | WorkerOp_OptimiseStore -- 34 0x22 47 | | WorkerOp_VerifyStore -- 35 0x23 48 | | WorkerOp_BuildDerivation -- 36 0x24 49 | | WorkerOp_AddSignatures -- 37 0x25 50 | | WorkerOp_NarFromPath -- 38 0x26 51 | | WorkerOp_AddToStoreNar -- 39 0x27 52 | | WorkerOp_QueryMissing -- 40 0x28 53 | | WorkerOp_QueryDerivationOutputMap -- 41 0x29 54 | | WorkerOp_RegisterDrvOutput -- 42 0x2a 55 | | WorkerOp_QueryRealisation -- 43 0x2b 56 | | WorkerOp_AddMultipleToStore -- 44 0x2c 57 | | WorkerOp_AddBuildLog -- 45 0x2d 58 | | WorkerOp_BuildPathsWithResults -- 46 0x2e 59 | deriving (Bounded, Eq, Enum, Generic, Ord, Show, Read) 60 | -------------------------------------------------------------------------------- /hnix-store-remote/tests-io/DataSink.hs: -------------------------------------------------------------------------------- 1 | module DataSink 2 | 3 | ( DataSink(..) 4 | , dataSinkResult 5 | , dataSinkWriter 6 | , newDataSink 7 | ) 8 | 9 | where 10 | 11 | import Data.ByteString (ByteString) 12 | 13 | import Control.Monad.ST 14 | import Data.STRef 15 | 16 | -- | Basic data sink for testing 17 | newtype DataSink = DataSink (STRef RealWorld ByteString) 18 | 19 | newDataSink :: IO DataSink 20 | newDataSink = DataSink <$> (stToIO . newSTRef) mempty 21 | 22 | dataSinkWriter :: DataSink -> (ByteString -> IO()) 23 | dataSinkWriter (DataSink stref) chunk = stToIO (modifySTRef stref (chunk <>)) 24 | 25 | dataSinkResult :: DataSink -> IO ByteString 26 | dataSinkResult (DataSink stref) = (stToIO . readSTRef) stref 27 | -------------------------------------------------------------------------------- /hnix-store-remote/tests-io/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import qualified Test.Hspec 4 | import qualified NixDaemonSpec 5 | 6 | -- we run remote tests in 7 | -- Linux namespaces to avoid interacting with systems store 8 | main :: IO () 9 | main = do 10 | NixDaemonSpec.enterNamespaces 11 | Test.Hspec.hspec 12 | NixDaemonSpec.spec 13 | -------------------------------------------------------------------------------- /hnix-store-remote/tests-io/SampleNar.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE NamedFieldPuns #-} 3 | 4 | module SampleNar 5 | 6 | ( SampleNar(..) 7 | , buildDataSource 8 | , sampleNar0 9 | , encodeNar 10 | ) 11 | 12 | where 13 | 14 | import Crypto.Hash ( Digest, SHA256, hashInit, hashUpdate, hashFinalize ) 15 | import Data.ByteString (ByteString) 16 | import Data.Dependent.Sum (DSum((:=>))) 17 | import Data.Time.Calendar.OrdinalDate (fromOrdinalDate) 18 | import Data.Time.Clock (UTCTime(..)) 19 | import System.Nix.Hash (HashAlgo(HashAlgo_SHA256)) 20 | import System.Nix.StorePath (StorePath, parsePath) 21 | import System.Nix.StorePath.Metadata (Metadata(..), StorePathTrust(..)) 22 | 23 | import Control.Monad.ST 24 | import Data.Default.Class 25 | import Data.STRef 26 | import Data.Word 27 | 28 | import qualified Data.ByteString 29 | import qualified System.Nix.Nar 30 | 31 | -- | Sample data for an AddToStoreNar operation 32 | data SampleNar 33 | = SampleNar 34 | { sampleNar_fileData :: !ByteString -- ^ Contents of the file to be put in the store 35 | , sampleNar_narData :: !ByteString -- ^ The file data serialized as a NAR 36 | , sampleNar_storePath :: !StorePath 37 | , sampleNar_metadata :: !(Metadata StorePath) 38 | } 39 | 40 | sampleNar0 :: IO SampleNar 41 | sampleNar0 = do 42 | let sampleNar_fileData = "hello" 43 | sampleNar_narData <- encodeNar sampleNar_fileData 44 | let sampleNar_metadata = Metadata 45 | { metadataDeriverPath = Just $ forceParsePath "/nix/store/g2mxdrkwr1hck4y5479dww7m56d1x81v-hello-2.12.1.drv" 46 | , metadataNarHash = sha256 sampleNar_narData 47 | , metadataReferences = mempty 48 | , metadataRegistrationTime = UTCTime (fromOrdinalDate 1980 1) 0 49 | , metadataNarBytes = Just ((fromIntegral . Data.ByteString.length) sampleNar_narData) 50 | , metadataTrust = BuiltElsewhere 51 | , metadataSigs = mempty 52 | , metadataContentAddress = Nothing 53 | } 54 | sampleNar_storePath = forceParsePath "/nix/store/00000lj3clbkc0aqvjjzfa6slp4zdvlj-hello-2.12.1" 55 | pure SampleNar{..} 56 | 57 | buildDataSource :: SampleNar -> IO(Word64 -> IO (Maybe ByteString)) 58 | buildDataSource SampleNar{sampleNar_narData} = dataSourceFromByteString sampleNar_narData 59 | 60 | dataSourceFromByteString :: ByteString -> IO (Word64 -> IO(Maybe ByteString)) 61 | dataSourceFromByteString bs = do 62 | posRef <- stToIO $ newSTRef (0::Word64) 63 | let len = fromIntegral $ Data.ByteString.length bs 64 | outFn chunkSize = do 65 | pos <- stToIO $ readSTRef posRef 66 | if pos >= len then pure Nothing 67 | else do 68 | let bs' = Data.ByteString.drop (fromIntegral pos) bs 69 | bs'' = Data.ByteString.take (fromIntegral chunkSize) bs' 70 | takenLen = fromIntegral $ Data.ByteString.length bs'' 71 | stToIO $ modifySTRef posRef (takenLen +) 72 | pure $ Just bs'' 73 | pure outFn 74 | 75 | forceParsePath :: ByteString -> StorePath 76 | forceParsePath path = case parsePath def path of 77 | Left err -> error $ mconcat [ "forceParsePath failed: ", show err ] 78 | Right x -> x 79 | sha256 :: ByteString -> DSum HashAlgo Digest 80 | sha256 bs = HashAlgo_SHA256 :=> hashFinalize (hashUpdate (hashInit @SHA256) bs) 81 | 82 | encodeNar :: ByteString -> IO ByteString 83 | encodeNar bytes = do 84 | ref <- stToIO $ newSTRef mempty 85 | let accumFn chunk = do 86 | stToIO $ modifySTRef ref (<> chunk) 87 | System.Nix.Nar.dumpString bytes accumFn 88 | stToIO $ readSTRef ref 89 | 90 | -------------------------------------------------------------------------------- /hnix-store-remote/tests/Data/SerializerSpec.hs: -------------------------------------------------------------------------------- 1 | module Data.SerializerSpec (spec) where 2 | 3 | import Data.Some 4 | import Data.Serializer 5 | import Data.Serializer.Example 6 | 7 | import Test.Hspec (Spec, describe, it, shouldBe) 8 | import Test.Hspec.QuickCheck (prop) 9 | 10 | spec :: Spec 11 | spec = describe "Serializer" $ do 12 | prop "Roundtrips GADT protocol" $ \someCmd -> 13 | (runG cmdS 14 | <$> (runP cmdS someCmd)) 15 | `shouldBe` 16 | ((pure $ pure someCmd) :: 17 | Either MyPutError 18 | (Either (GetSerializerError MyGetError) 19 | (Some Cmd))) 20 | 21 | it "Handles putS error" $ 22 | runP cmdSPutError (Some (Cmd_Bool True)) 23 | `shouldBe` 24 | Left MyPutError_NoLongerSupported 25 | 26 | it "Handles getS error" $ 27 | runG cmdSGetError (runPutSimple cmdS (Some (Cmd_Bool True))) 28 | `shouldBe` 29 | Left (SerializerError_Get MyGetError_Example) 30 | 31 | it "Handles getS fail" $ 32 | runG cmdSGetFail (runPutSimple cmdS (Some (Cmd_Bool True))) 33 | `shouldBe` 34 | Left (SerializerError_GetFail @MyGetError "Failed reading: no parse\nEmpty call stack\n") 35 | 36 | prop "Roundtrips elaborate example" $ \someCmd readerBool -> 37 | (runGRest cmdSRest readerBool 0 38 | <$> (runPRest cmdSRest readerBool 0 someCmd)) 39 | `shouldBe` 40 | ((pure $ pure $ someCmd) :: 41 | Either MyPutError 42 | (Either (GetSerializerError MyGetError) 43 | (Some Cmd))) 44 | -------------------------------------------------------------------------------- /hnix-store-remote/tests/Driver.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /hnix-store-remote/tests/EnumSpec.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module EnumSpec (spec) where 4 | 5 | import Test.Hspec (SpecWith, Spec, describe, it, shouldBe) 6 | 7 | import Data.ByteString (ByteString) 8 | import Data.Word (Word64) 9 | import System.Nix.Build (BuildMode(..), BuildStatus(..)) 10 | import System.Nix.Store.Remote.Serializer 11 | ( activity 12 | , activityResult 13 | , enum 14 | , int 15 | , loggerOpCode 16 | , runP 17 | , LoggerSError 18 | , NixSerializer 19 | , SError 20 | ) 21 | import System.Nix.Store.Remote.Types 22 | 23 | spec :: Spec 24 | spec = do 25 | let 26 | itE 27 | :: ( Bounded a 28 | , Enum a 29 | , Show a 30 | ) 31 | => String 32 | -> a 33 | -> Word64 34 | -> SpecWith () 35 | itE name constr value = 36 | it name 37 | $ ((runP enum () constr) :: Either SError ByteString) 38 | `shouldBe` 39 | (runP (int @Word64) () value) 40 | 41 | itE' 42 | :: Show a 43 | => NixSerializer () LoggerSError a 44 | -> String 45 | -> a 46 | -> Word64 47 | -> SpecWith () 48 | itE' s name constr value = 49 | it name 50 | $ ((runP s () constr) :: Either LoggerSError ByteString) 51 | `shouldBe` 52 | (runP (int @Word64) () (value)) 53 | 54 | describe "Enums" $ do 55 | describe "BuildMode enum order matches Nix" $ do 56 | itE "Normal" BuildMode_Normal 0 57 | itE "Repair" BuildMode_Repair 1 58 | itE "Check" BuildMode_Check 2 59 | 60 | describe "BuildStatus enum order matches Nix" $ do 61 | itE "Built" BuildStatus_Built 0 62 | itE "Substituted" BuildStatus_Substituted 1 63 | itE "AlreadyValid" BuildStatus_AlreadyValid 2 64 | itE "PermanentFailure" BuildStatus_PermanentFailure 3 65 | itE "InputRejected" BuildStatus_InputRejected 4 66 | itE "OutputRejected" BuildStatus_OutputRejected 5 67 | itE "TransientFailure" BuildStatus_TransientFailure 6 68 | itE "CachedFailure" BuildStatus_CachedFailure 7 69 | itE "TimedOut" BuildStatus_TimedOut 8 70 | itE "MiscFailure" BuildStatus_MiscFailure 9 71 | itE "DependencyFailed" BuildStatus_DependencyFailed 10 72 | itE "LogLimitExceeded" BuildStatus_LogLimitExceeded 11 73 | itE "NotDeterministic" BuildStatus_NotDeterministic 12 74 | itE "ResolvesToAlreadyValid" BuildStatus_ResolvesToAlreadyValid 13 75 | itE "NoSubstituters" BuildStatus_NoSubstituters 14 76 | 77 | describe "GCAction enum order matches Nix" $ do 78 | itE "ReturnLive" GCAction_ReturnLive 0 79 | itE "ReturnDead" GCAction_ReturnDead 1 80 | itE "DeleteDead" GCAction_DeleteDead 2 81 | itE "DeleteSpecific" GCAction_DeleteSpecific 3 82 | 83 | describe "Logger" $ do 84 | let itA = itE' activity 85 | describe "Activity enum order matches Nix" $ do 86 | itA "CopyPath" Activity_CopyPath 100 87 | itA "FileTransfer" Activity_FileTransfer 101 88 | itA "Realise" Activity_Realise 102 89 | itA "CopyPaths" Activity_CopyPaths 103 90 | itA "Builds" Activity_Builds 104 91 | itA "Build" Activity_Build 105 92 | itA "OptimiseStore" Activity_OptimiseStore 106 93 | itA "VerifyPaths" Activity_VerifyPaths 107 94 | itA "Substitute" Activity_Substitute 108 95 | itA "QueryPathInfo" Activity_QueryPathInfo 109 96 | itA "PostBuildHook" Activity_PostBuildHook 110 97 | itA "BuildWaiting" Activity_BuildWaiting 111 98 | 99 | let itR = itE' activityResult 100 | describe "ActivityResult enum order matches Nix" $ do 101 | itR "FileLinked" ActivityResult_FileLinked 100 102 | itR "BuildLogLine" ActivityResult_BuildLogLine 101 103 | itR "UnstrustedPath" ActivityResult_UnstrustedPath 102 104 | itR "CorruptedPath" ActivityResult_CorruptedPath 103 105 | itR "SetPhase" ActivityResult_SetPhase 104 106 | itR "Progress" ActivityResult_Progress 105 107 | itR "SetExpected" ActivityResult_SetExpected 106 108 | itR "PostBuildLogLine" ActivityResult_PostBuildLogLine 107 109 | 110 | 111 | let itL = itE' loggerOpCode 112 | describe "LoggerOpCode matches Nix" $ do 113 | itL "Next" LoggerOpCode_Next 0x6f6c6d67 114 | itL "Read" LoggerOpCode_Read 0x64617461 115 | itL "Write" LoggerOpCode_Write 0x64617416 116 | itL "Last" LoggerOpCode_Last 0x616c7473 117 | itL "Error" LoggerOpCode_Error 0x63787470 118 | itL "StartActivity" LoggerOpCode_StartActivity 0x53545254 119 | itL "StopActivity" LoggerOpCode_StopActivity 0x53544f50 120 | itL "Result" LoggerOpCode_Result 0x52534c54 121 | 122 | describe "Verbosity enum order matches Nix" $ do 123 | itE "Error" Verbosity_Error 0 124 | itE "Warn" Verbosity_Warn 1 125 | itE "Notice" Verbosity_Notice 2 126 | itE "Info" Verbosity_Info 3 127 | itE "Talkative" Verbosity_Talkative 4 128 | itE "Chatty" Verbosity_Chatty 5 129 | itE "Debug" Verbosity_Debug 6 130 | itE "Vomit" Verbosity_Vomit 7 131 | 132 | describe "WorkerOp enum order matches Nix" $ do 133 | itE "IsValidPath" WorkerOp_IsValidPath 1 134 | itE "BuildPathsWithResults" WorkerOp_BuildPathsWithResults 46 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /hnix-store-tests/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0.0 2024-07-31 2 | 3 | * Initial release 4 | 5 | --- 6 | 7 | `hnix-store-tests` uses [PVP Versioning][1]. 8 | 9 | [1]: https://pvp.haskell.org 10 | -------------------------------------------------------------------------------- /hnix-store-tests/README.md: -------------------------------------------------------------------------------- 1 | # hnix-store-tests 2 | 3 | Arbitrary instances for core types, roundtrip property tests, utility functions 4 | and a harness for running tests that require nix-store 5 | with nix-daemon. 6 | 7 | [Test.Hspec.Nix]: ./src/Test/Hspec/Nix.hs 8 | -------------------------------------------------------------------------------- /hnix-store-tests/hnix-store-tests.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: hnix-store-tests 3 | version: 0.1.0.0 4 | synopsis: Test utilities and instances 5 | description: 6 | This package contains Arbitrary instances for core 7 | types, roundtrip property tests, utility functions 8 | and a harness for running tests that require nix-store 9 | with nix-daemon. 10 | homepage: https://github.com/haskell-nix/hnix-store 11 | license: Apache-2.0 12 | license-file: LICENSE 13 | author: Sorki 14 | maintainer: srk@48.io 15 | copyright: 2023 Sorki 16 | category: Nix 17 | build-type: Simple 18 | extra-doc-files: 19 | CHANGELOG.md 20 | extra-source-files: 21 | README.md 22 | 23 | common commons 24 | ghc-options: -Wall 25 | default-extensions: 26 | DerivingStrategies 27 | , DerivingVia 28 | , FlexibleInstances 29 | , ScopedTypeVariables 30 | , StandaloneDeriving 31 | , RecordWildCards 32 | , TypeApplications 33 | , LambdaCase 34 | default-language: Haskell2010 35 | 36 | library 37 | import: commons 38 | exposed-modules: 39 | Data.ByteString.Arbitrary 40 | , Data.HashSet.Arbitrary 41 | , Data.Text.Arbitrary 42 | , Data.Vector.Arbitrary 43 | , System.Nix.Arbitrary 44 | , System.Nix.Arbitrary.Base 45 | , System.Nix.Arbitrary.Build 46 | , System.Nix.Arbitrary.ContentAddress 47 | , System.Nix.Arbitrary.Derivation 48 | , System.Nix.Arbitrary.DerivedPath 49 | , System.Nix.Arbitrary.FileContentAddress 50 | , System.Nix.Arbitrary.Hash 51 | , System.Nix.Arbitrary.OutputName 52 | , System.Nix.Arbitrary.Realisation 53 | , System.Nix.Arbitrary.Signature 54 | , System.Nix.Arbitrary.Store.Types 55 | , System.Nix.Arbitrary.StorePath 56 | , System.Nix.Arbitrary.StorePath.Metadata 57 | , System.Nix.Arbitrary.UTCTime 58 | , Test.Hspec.Nix 59 | build-depends: 60 | base >=4.12 && <5 61 | , hnix-store-core >= 0.8 62 | , bytestring 63 | , containers 64 | , crypton 65 | , dependent-sum > 0.7 66 | , generic-arbitrary < 1.1 67 | , hashable 68 | , hspec 69 | , QuickCheck 70 | , text 71 | , time 72 | , unordered-containers 73 | , vector 74 | hs-source-dirs: src 75 | 76 | test-suite props 77 | import: commons 78 | type: exitcode-stdio-1.0 79 | main-is: Spec.hs 80 | other-modules: 81 | BaseEncodingSpec 82 | ContentAddressSpec 83 | DerivationSpec 84 | DerivedPathSpec 85 | RealisationSpec 86 | StorePathSpec 87 | SignatureSpec 88 | hs-source-dirs: 89 | tests 90 | build-tool-depends: 91 | hspec-discover:hspec-discover 92 | build-depends: 93 | base 94 | , hnix-store-core 95 | , hnix-store-tests 96 | , attoparsec 97 | , text 98 | , hspec 99 | -------------------------------------------------------------------------------- /hnix-store-tests/src/Data/ByteString/Arbitrary.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-orphans #-} 2 | module Data.ByteString.Arbitrary () where 3 | 4 | import Data.ByteString (ByteString) 5 | import Test.QuickCheck (Arbitrary(..)) 6 | import qualified Data.ByteString.Char8 7 | 8 | instance Arbitrary ByteString where 9 | arbitrary = Data.ByteString.Char8.pack <$> arbitrary 10 | shrink xs = Data.ByteString.Char8.pack <$> shrink (Data.ByteString.Char8.unpack xs) 11 | -------------------------------------------------------------------------------- /hnix-store-tests/src/Data/HashSet/Arbitrary.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-orphans #-} 2 | module Data.HashSet.Arbitrary where 3 | 4 | import Data.Hashable (Hashable) 5 | import Data.HashSet (HashSet) 6 | import Test.QuickCheck (Arbitrary(..)) 7 | import qualified Data.HashSet 8 | 9 | instance (Hashable a, Eq a, Arbitrary a) => Arbitrary (HashSet a) where 10 | arbitrary = Data.HashSet.fromList <$> arbitrary 11 | shrink hashset = Data.HashSet.fromList <$> shrink (Data.HashSet.toList hashset) 12 | -------------------------------------------------------------------------------- /hnix-store-tests/src/Data/Text/Arbitrary.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-orphans #-} 2 | module Data.Text.Arbitrary () where 3 | 4 | import Data.Text (Text) 5 | import Test.QuickCheck (Arbitrary(..), frequency, suchThat) 6 | import qualified Data.Text 7 | 8 | instance Arbitrary Text where 9 | arbitrary = Data.Text.pack <$> arbitrary 10 | shrink xs = Data.Text.pack <$> shrink (Data.Text.unpack xs) 11 | 12 | instance {-# OVERLAPPING #-} Arbitrary (Maybe Text) where 13 | arbitrary = frequency 14 | [ (1, pure Nothing) 15 | , (3, Just <$> arbitrary `suchThat` (/= mempty)) 16 | ] 17 | -------------------------------------------------------------------------------- /hnix-store-tests/src/Data/Vector/Arbitrary.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-orphans #-} 2 | -- Stolen from quickcheck-instances (BSD-3) 3 | module Data.Vector.Arbitrary () where 4 | 5 | import Data.Vector (Vector) 6 | import Test.QuickCheck (Arbitrary(..), Arbitrary1(..), arbitrary1, shrink1) 7 | import qualified Data.Vector 8 | 9 | instance Arbitrary1 Vector where 10 | liftArbitrary = 11 | fmap Data.Vector.fromList 12 | . liftArbitrary 13 | liftShrink shr = 14 | fmap Data.Vector.fromList 15 | . liftShrink shr 16 | . Data.Vector.toList 17 | 18 | instance Arbitrary a => Arbitrary (Vector a) where 19 | arbitrary = arbitrary1 20 | shrink = shrink1 21 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary.hs: -------------------------------------------------------------------------------- 1 | module System.Nix.Arbitrary where 2 | 3 | import Data.ByteString.Arbitrary () 4 | import Data.HashSet.Arbitrary () 5 | import Data.Text.Arbitrary () 6 | import Data.Vector.Arbitrary () 7 | 8 | import System.Nix.Arbitrary.Base () 9 | import System.Nix.Arbitrary.Build () 10 | import System.Nix.Arbitrary.ContentAddress () 11 | import System.Nix.Arbitrary.Derivation () 12 | import System.Nix.Arbitrary.DerivedPath () 13 | import System.Nix.Arbitrary.FileContentAddress () 14 | import System.Nix.Arbitrary.Hash () 15 | import System.Nix.Arbitrary.OutputName () 16 | import System.Nix.Arbitrary.Realisation () 17 | import System.Nix.Arbitrary.Signature () 18 | import System.Nix.Arbitrary.Store.Types () 19 | import System.Nix.Arbitrary.StorePath () 20 | import System.Nix.Arbitrary.StorePath.Metadata () 21 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/Base.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Arbitrary.Base where 5 | 6 | import System.Nix.Base 7 | 8 | import Test.QuickCheck (Arbitrary(..)) 9 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 10 | 11 | deriving via GenericArbitrary BaseEncoding 12 | instance Arbitrary BaseEncoding 13 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/Build.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Arbitrary.Build where 5 | 6 | import Data.Time (UTCTime) 7 | import Data.Text.Arbitrary () 8 | import Test.QuickCheck (Arbitrary(..), scale, suchThat) 9 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 10 | import System.Nix.Arbitrary.OutputName () 11 | import System.Nix.Arbitrary.Realisation () 12 | import System.Nix.Arbitrary.UTCTime () 13 | 14 | import System.Nix.Build 15 | 16 | import qualified Data.Time.Clock.POSIX 17 | 18 | deriving via GenericArbitrary BuildMode 19 | instance Arbitrary BuildMode 20 | 21 | deriving via GenericArbitrary BuildStatus 22 | instance Arbitrary BuildStatus 23 | 24 | instance Arbitrary BuildResult where 25 | arbitrary = do 26 | buildResultStatus <- arbitrary 27 | buildResultErrorMessage <- arbitrary 28 | buildResultTimesBuilt <- arbitrary `suchThat` (/= Just 0) 29 | buildResultIsNonDeterministic <- arbitrary `suchThat` (/= Nothing) 30 | buildResultStartTime <- arbitrary `suchThat` (/= Just t0) 31 | buildResultStopTime <- arbitrary `suchThat` (/= Just t0) 32 | buildResultBuiltOutputs <- scale (`div` 10) (arbitrary `suchThat` (/= Nothing)) 33 | 34 | pure BuildResult{..} 35 | where 36 | t0 :: UTCTime 37 | t0 = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0 38 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/ContentAddress.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Arbitrary.ContentAddress where 5 | 6 | import System.Nix.Arbitrary.Hash () 7 | import System.Nix.Arbitrary.Store.Types () 8 | import System.Nix.ContentAddress (ContentAddress, ContentAddressMethod) 9 | 10 | import Test.QuickCheck (Arbitrary(..)) 11 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 12 | 13 | deriving via GenericArbitrary ContentAddressMethod 14 | instance Arbitrary ContentAddressMethod 15 | 16 | deriving via GenericArbitrary ContentAddress 17 | instance Arbitrary ContentAddress 18 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/Derivation.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Arbitrary.Derivation where 5 | 6 | import Data.Text (Text) 7 | import Data.Text.Arbitrary () 8 | import Data.Vector.Arbitrary () 9 | import System.Nix.Derivation 10 | import System.Nix.StorePath (StorePath) 11 | 12 | import Test.QuickCheck (Arbitrary(..)) 13 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 14 | import System.Nix.Arbitrary.StorePath () 15 | 16 | deriving via GenericArbitrary (Derivation StorePath Text) 17 | instance Arbitrary (Derivation StorePath Text) 18 | deriving via GenericArbitrary (DerivationOutput StorePath Text) 19 | instance Arbitrary (DerivationOutput StorePath Text) 20 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/DerivedPath.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Arbitrary.DerivedPath where 5 | 6 | import qualified Data.Set 7 | import Test.QuickCheck (Arbitrary(..), oneof) 8 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 9 | import System.Nix.Arbitrary.OutputName () 10 | import System.Nix.Arbitrary.StorePath () 11 | import System.Nix.DerivedPath (DerivedPath, OutputsSpec(..)) 12 | 13 | instance Arbitrary OutputsSpec where 14 | arbitrary = oneof 15 | [ pure OutputsSpec_All 16 | , OutputsSpec_Names 17 | . Data.Set.fromList 18 | <$> ((:) <$> arbitrary <*> arbitrary) 19 | ] 20 | 21 | deriving via GenericArbitrary DerivedPath 22 | instance Arbitrary DerivedPath 23 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/FileContentAddress.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Arbitrary.FileContentAddress where 5 | 6 | import System.Nix.FileContentAddress 7 | 8 | import Test.QuickCheck (Arbitrary(..)) 9 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 10 | 11 | deriving via GenericArbitrary FileIngestionMethod 12 | instance Arbitrary FileIngestionMethod 13 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/Hash.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Arbitrary.Hash where 5 | 6 | import Data.ByteString (ByteString) 7 | import Data.ByteString.Arbitrary () 8 | import Crypto.Hash (Digest, MD5(..), SHA1(..), SHA256(..), SHA512(..)) 9 | import Data.Dependent.Sum (DSum((:=>))) 10 | import Data.Some (Some(Some)) 11 | import System.Nix.Hash (HashAlgo(..)) 12 | 13 | import Test.QuickCheck (Arbitrary(arbitrary), oneof) 14 | 15 | import qualified Crypto.Hash 16 | 17 | -- * Arbitrary @Digest@s 18 | 19 | instance Arbitrary (Digest MD5) where 20 | arbitrary = Crypto.Hash.hash @ByteString <$> arbitrary 21 | 22 | instance Arbitrary (Digest SHA1) where 23 | arbitrary = Crypto.Hash.hash @ByteString <$> arbitrary 24 | 25 | instance Arbitrary (Digest SHA256) where 26 | arbitrary = Crypto.Hash.hash @ByteString <$> arbitrary 27 | 28 | instance Arbitrary (Digest SHA512) where 29 | arbitrary = Crypto.Hash.hash @ByteString <$> arbitrary 30 | 31 | -- * Arbitrary @DSum HashAlgo Digest@s 32 | 33 | instance Arbitrary (DSum HashAlgo Digest) where 34 | arbitrary = oneof 35 | [ (HashAlgo_MD5 :=>) <$> arbitrary 36 | , (HashAlgo_SHA1 :=>) <$> arbitrary 37 | , (HashAlgo_SHA256 :=>) <$> arbitrary 38 | , (HashAlgo_SHA512 :=>) <$> arbitrary 39 | ] 40 | 41 | instance Arbitrary (Some HashAlgo) where 42 | arbitrary = 43 | oneof 44 | $ pure 45 | <$> [ 46 | Some HashAlgo_MD5 47 | , Some HashAlgo_SHA1 48 | , Some HashAlgo_SHA256 49 | , Some HashAlgo_SHA512 50 | ] 51 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/OutputName.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# OPTIONS_GHC -Wno-orphans #-} 3 | module System.Nix.Arbitrary.OutputName where 4 | 5 | import System.Nix.OutputName (OutputName) 6 | import qualified Data.Text 7 | import qualified System.Nix.OutputName 8 | 9 | import Test.QuickCheck (Arbitrary(arbitrary), choose, elements, vectorOf) 10 | 11 | instance Arbitrary OutputName where 12 | arbitrary = 13 | either (error . show) id 14 | . System.Nix.OutputName.mkOutputName 15 | . Data.Text.pack <$> ((:) <$> s1 <*> limited sn) 16 | where 17 | alphanum = ['a' .. 'z'] <> ['A' .. 'Z'] <> ['0' .. '9'] 18 | s1 = elements $ alphanum <> "+-_?=" 19 | sn = elements $ alphanum <> "+-._?=" 20 | limited n = do 21 | k <- choose (0, 210) 22 | vectorOf k n 23 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/Realisation.hs: -------------------------------------------------------------------------------- 1 | -- due to Illegal equational constraint Test.QuickCheck.Arbitrary.Generic.TypesDiffer 2 | {-# LANGUAGE TypeFamilies #-} 3 | -- due to recent generic-arbitrary 4 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 5 | {-# OPTIONS_GHC -Wno-orphans #-} 6 | module System.Nix.Arbitrary.Realisation where 7 | 8 | import System.Nix.Arbitrary.Hash () 9 | import System.Nix.Arbitrary.OutputName () 10 | import System.Nix.Arbitrary.Signature () 11 | import System.Nix.Arbitrary.StorePath () 12 | import System.Nix.Realisation (DerivationOutput, Realisation) 13 | 14 | import Test.QuickCheck (Arbitrary(..)) 15 | import Test.QuickCheck.Arbitrary.Generic (Arg, GenericArbitrary(..), genericArbitrary, genericShrink) 16 | 17 | instance 18 | ( Arg (DerivationOutput outputName) outputName 19 | , Arbitrary outputName 20 | ) => 21 | Arbitrary (DerivationOutput outputName) 22 | where 23 | arbitrary = genericArbitrary 24 | shrink = genericShrink 25 | 26 | deriving via GenericArbitrary Realisation 27 | instance Arbitrary Realisation 28 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/Signature.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | module System.Nix.Arbitrary.Signature where 6 | 7 | import qualified Crypto.PubKey.Ed25519 8 | import Crypto.Random (drgNewTest, withDRG) 9 | import qualified Data.ByteString as BS 10 | import qualified Data.Text as Text 11 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 12 | import Test.QuickCheck 13 | 14 | import System.Nix.Signature 15 | 16 | instance Arbitrary Crypto.PubKey.Ed25519.Signature where 17 | arbitrary = do 18 | seeds <- (,,,,) <$> arbitraryBoundedRandom <*> arbitraryBoundedRandom <*> arbitraryBoundedRandom <*> arbitraryBoundedRandom <*> arbitraryBoundedRandom 19 | let drg = drgNewTest seeds 20 | (secretKey, _) = withDRG drg Crypto.PubKey.Ed25519.generateSecretKey 21 | publicKey = Crypto.PubKey.Ed25519.toPublic secretKey 22 | msg :: BS.ByteString = "msg" 23 | pure $ Crypto.PubKey.Ed25519.sign secretKey publicKey msg 24 | 25 | deriving via GenericArbitrary Signature 26 | instance Arbitrary Signature 27 | 28 | instance Arbitrary NarSignature where 29 | arbitrary = do 30 | name <- Text.pack . getPrintableString <$> suchThat arbitrary (\(PrintableString str) -> validName str) 31 | NarSignature name <$> arbitrary 32 | 33 | validName :: String -> Bool 34 | validName txt = not (null txt) && not (elem ':' txt) 35 | 36 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/Store/Types.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 3 | {-# OPTIONS_GHC -Wno-orphans #-} 4 | module System.Nix.Arbitrary.Store.Types where 5 | 6 | import System.Nix.Store.Types 7 | 8 | import Test.QuickCheck (Arbitrary(..)) 9 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 10 | 11 | deriving via GenericArbitrary RepairMode 12 | instance Arbitrary RepairMode 13 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/StorePath.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# OPTIONS_GHC -Wno-orphans #-} 3 | module System.Nix.Arbitrary.StorePath where 4 | 5 | #if !MIN_VERSION_base(4,18,0) 6 | import Control.Applicative (liftA2) 7 | #endif 8 | import Crypto.Hash (MD5, SHA1, SHA256, SHA512) 9 | import qualified Data.ByteString.Char8 10 | import qualified Data.Text 11 | import System.Nix.StorePath (StoreDir(..) 12 | , StorePath 13 | , StorePathName 14 | , StorePathHashPart 15 | ) 16 | import qualified System.Nix.StorePath 17 | 18 | import Test.QuickCheck (Arbitrary(arbitrary), choose, elements, oneof, vectorOf) 19 | 20 | instance Arbitrary StoreDir where 21 | arbitrary = 22 | StoreDir 23 | . (Data.ByteString.Char8.singleton '/' <>) -- TODO(srk): nasty, see #237 24 | . Data.ByteString.Char8.pack <$> arbitrary 25 | 26 | instance Arbitrary StorePath where 27 | arbitrary = 28 | liftA2 System.Nix.StorePath.unsafeMakeStorePath 29 | arbitrary 30 | arbitrary 31 | 32 | instance Arbitrary StorePathName where 33 | arbitrary = 34 | either undefined id 35 | . System.Nix.StorePath.mkStorePathName 36 | . Data.Text.pack <$> ((:) <$> s1 <*> limited sn) 37 | where 38 | alphanum = ['a' .. 'z'] <> ['A' .. 'Z'] <> ['0' .. '9'] 39 | s1 = elements $ alphanum <> "+-_?=" 40 | sn = elements $ alphanum <> "+-._?=" 41 | limited n = do 42 | k <- choose (0, 210) 43 | vectorOf k n 44 | 45 | instance Arbitrary StorePathHashPart where 46 | arbitrary = 47 | oneof 48 | [ System.Nix.StorePath.mkStorePathHashPart @MD5 49 | . Data.ByteString.Char8.pack <$> arbitrary 50 | , System.Nix.StorePath.mkStorePathHashPart @SHA1 51 | . Data.ByteString.Char8.pack <$> arbitrary 52 | , System.Nix.StorePath.mkStorePathHashPart @SHA256 53 | . Data.ByteString.Char8.pack <$> arbitrary 54 | , System.Nix.StorePath.mkStorePathHashPart @SHA512 55 | . Data.ByteString.Char8.pack <$> arbitrary 56 | ] 57 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/StorePath/Metadata.hs: -------------------------------------------------------------------------------- 1 | -- due to recent generic-arbitrary 2 | {-# LANGUAGE CPP #-} 3 | {-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} 4 | {-# OPTIONS_GHC -Wno-orphans #-} 5 | module System.Nix.Arbitrary.StorePath.Metadata where 6 | 7 | import Data.Dependent.Sum (DSum((:=>))) 8 | import Data.HashSet.Arbitrary () 9 | import System.Nix.Arbitrary.ContentAddress () 10 | import System.Nix.Arbitrary.Hash () 11 | import System.Nix.Arbitrary.Signature () 12 | import System.Nix.Arbitrary.StorePath () 13 | import System.Nix.Arbitrary.UTCTime () 14 | import System.Nix.StorePath (StorePath) 15 | import System.Nix.StorePath.Metadata (Metadata(..), StorePathTrust) 16 | 17 | import qualified System.Nix.Hash 18 | 19 | import Test.QuickCheck (Arbitrary(..), suchThat) 20 | import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) 21 | 22 | deriving via GenericArbitrary StorePathTrust 23 | instance Arbitrary StorePathTrust 24 | 25 | instance Arbitrary (Metadata StorePath) where 26 | arbitrary = do 27 | metadataDeriverPath <- arbitrary 28 | metadataNarHash <- (System.Nix.Hash.HashAlgo_SHA256 :=>) <$> arbitrary 29 | metadataReferences <- arbitrary 30 | metadataRegistrationTime <- arbitrary 31 | metadataNarBytes <- arbitrary `suchThat` (/= Just 0) 32 | metadataTrust <- arbitrary 33 | metadataSigs <- arbitrary 34 | metadataContentAddress <- arbitrary 35 | pure Metadata{..} 36 | -------------------------------------------------------------------------------- /hnix-store-tests/src/System/Nix/Arbitrary/UTCTime.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -Wno-orphans #-} 2 | -- Stolen from quickcheck-instances (BSD-3) 3 | -- UTCTime/DiffTime slightly modified to produce 4 | -- values rounded to whole seconds 5 | module System.Nix.Arbitrary.UTCTime where 6 | 7 | import Data.Time (Day(..), DiffTime, UTCTime(..)) 8 | import Test.QuickCheck (Arbitrary(..)) 9 | 10 | instance Arbitrary Day where 11 | arbitrary = ModifiedJulianDay <$> (2000 +) <$> arbitrary 12 | shrink = (ModifiedJulianDay <$>) . shrink . Data.Time.toModifiedJulianDay 13 | 14 | instance Arbitrary DiffTime where 15 | -- without abs something weird happens, try it 16 | arbitrary = fromInteger . abs <$> arbitrary 17 | 18 | instance Arbitrary UTCTime where 19 | arbitrary = 20 | UTCTime 21 | <$> arbitrary 22 | <*> arbitrary 23 | shrink ut@(UTCTime day dayTime) = 24 | [ ut { Data.Time.utctDay = d' } | d' <- shrink day ] 25 | ++ [ ut { Data.Time.utctDayTime = t' } | t' <- shrink dayTime ] 26 | 27 | -------------------------------------------------------------------------------- /hnix-store-tests/src/Test/Hspec/Nix.hs: -------------------------------------------------------------------------------- 1 | module Test.Hspec.Nix 2 | ( forceRight 3 | , roundtrips 4 | ) where 5 | 6 | import Test.Hspec (Expectation, shouldBe) 7 | 8 | roundtrips 9 | :: forall a b f 10 | . ( Applicative f 11 | , Eq (f a) 12 | , Show a 13 | , Show b 14 | , Show (f a) 15 | ) 16 | => (a -> b) -- ^ Encode 17 | -> (b -> f a) -- ^ Decode 18 | -> a 19 | -> Expectation 20 | roundtrips encode decode x = 21 | decode (encode x) `shouldBe` pure x 22 | 23 | forceRight 24 | :: Show a 25 | => Either a b 26 | -> b 27 | forceRight = \case 28 | Right x -> x 29 | Left e -> error $ "forceRight failed: " ++ show e 30 | -------------------------------------------------------------------------------- /hnix-store-tests/tests/BaseEncodingSpec.hs: -------------------------------------------------------------------------------- 1 | module BaseEncodingSpec where 2 | 3 | import Test.Hspec (Spec, describe) 4 | import Test.Hspec.QuickCheck (prop) 5 | import Test.Hspec.Nix (roundtrips) 6 | 7 | import System.Nix.Base 8 | import System.Nix.Arbitrary () 9 | import System.Nix.StorePath (StorePathHashPart(..)) 10 | 11 | spec :: Spec 12 | spec = do 13 | describe "Hash" $ do 14 | prop "Base16 roundtrips" $ 15 | roundtrips 16 | (encodeWith Base16) 17 | (decodeWith Base16) 18 | . unStorePathHashPart 19 | 20 | prop "Nix-like Base32 roundtrips" $ 21 | roundtrips 22 | (encodeWith NixBase32) 23 | (decodeWith NixBase32) 24 | . unStorePathHashPart 25 | 26 | prop "Base64 roundtrips" $ 27 | roundtrips 28 | (encodeWith Base64) 29 | (decodeWith Base64) 30 | . unStorePathHashPart 31 | -------------------------------------------------------------------------------- /hnix-store-tests/tests/ContentAddressSpec.hs: -------------------------------------------------------------------------------- 1 | module ContentAddressSpec where 2 | 3 | import Test.Hspec (Spec, describe) 4 | import Test.Hspec.QuickCheck (prop) 5 | import Test.Hspec.Nix (roundtrips) 6 | import System.Nix.Arbitrary () 7 | 8 | import qualified System.Nix.ContentAddress 9 | 10 | spec :: Spec 11 | spec = do 12 | describe "ContentAddress" $ do 13 | prop "roundtrips" $ 14 | roundtrips 15 | System.Nix.ContentAddress.buildContentAddress 16 | System.Nix.ContentAddress.parseContentAddress 17 | -------------------------------------------------------------------------------- /hnix-store-tests/tests/DerivationSpec.hs: -------------------------------------------------------------------------------- 1 | module DerivationSpec where 2 | 3 | import Test.Hspec (Spec, describe) 4 | import Test.Hspec.QuickCheck (xprop) 5 | import Test.Hspec.Nix (roundtrips) 6 | 7 | import System.Nix.Arbitrary () 8 | import System.Nix.Derivation (parseDerivation, buildDerivation) 9 | 10 | import qualified Data.Attoparsec.Text 11 | import qualified Data.Text.Lazy 12 | import qualified Data.Text.Lazy.Builder 13 | 14 | -- TODO(srk): this won't roundtrip as Arbitrary Text 15 | -- contains wild stuff like control characters and UTF8 sequences. 16 | -- Either fix in nix-derivation or use wrapper type 17 | -- (but we use Nix.Derivation.textParser so we need Text for now) 18 | spec :: Spec 19 | spec = do 20 | describe "Derivation" $ do 21 | xprop "roundtrips via Text" $ \sd -> 22 | roundtrips 23 | ( Data.Text.Lazy.toStrict 24 | . Data.Text.Lazy.Builder.toLazyText 25 | . buildDerivation sd 26 | ) 27 | (Data.Attoparsec.Text.parseOnly (parseDerivation sd)) 28 | -------------------------------------------------------------------------------- /hnix-store-tests/tests/DerivedPathSpec.hs: -------------------------------------------------------------------------------- 1 | module DerivedPathSpec where 2 | 3 | import Test.Hspec (Spec, describe) 4 | import Test.Hspec.QuickCheck (prop) 5 | import Test.Hspec.Nix (roundtrips) 6 | 7 | import System.Nix.Arbitrary () 8 | 9 | import qualified System.Nix.DerivedPath 10 | 11 | spec :: Spec 12 | spec = do 13 | describe "DerivedPath" $ do 14 | prop "roundtrips" $ \sd -> 15 | roundtrips 16 | (System.Nix.DerivedPath.derivedPathToText sd) 17 | (System.Nix.DerivedPath.parseDerivedPath sd) 18 | -------------------------------------------------------------------------------- /hnix-store-tests/tests/RealisationSpec.hs: -------------------------------------------------------------------------------- 1 | module RealisationSpec where 2 | 3 | import Test.Hspec (Spec, describe) 4 | import Test.Hspec.QuickCheck (prop) 5 | import Test.Hspec.Nix (roundtrips) 6 | 7 | import System.Nix.Arbitrary () 8 | 9 | import qualified Data.Text.Lazy 10 | import qualified Data.Text.Lazy.Builder 11 | import qualified System.Nix.OutputName 12 | import qualified System.Nix.Realisation 13 | 14 | spec :: Spec 15 | spec = do 16 | describe "DerivationOutput" $ do 17 | prop "roundtrips" $ 18 | roundtrips 19 | ( Data.Text.Lazy.toStrict 20 | . Data.Text.Lazy.Builder.toLazyText 21 | . System.Nix.Realisation.derivationOutputBuilder 22 | System.Nix.OutputName.unOutputName 23 | ) 24 | ( System.Nix.Realisation.derivationOutputParser 25 | System.Nix.OutputName.mkOutputName 26 | ) 27 | -------------------------------------------------------------------------------- /hnix-store-tests/tests/SignatureSpec.hs: -------------------------------------------------------------------------------- 1 | module SignatureSpec where 2 | 3 | import Test.Hspec (Spec, describe) 4 | import Test.Hspec.Nix (roundtrips) 5 | import Test.Hspec.QuickCheck (prop) 6 | 7 | import System.Nix.Signature (signatureToText, parseSignature, narSignatureToText, parseNarSignature) 8 | import System.Nix.Arbitrary () 9 | 10 | spec :: Spec 11 | spec = do 12 | describe "Signature" $ do 13 | prop "roundtrips" $ roundtrips signatureToText parseSignature 14 | describe "NarSignature" $ do 15 | prop "roundtrips" $ roundtrips narSignatureToText parseNarSignature 16 | -------------------------------------------------------------------------------- /hnix-store-tests/tests/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} 2 | -------------------------------------------------------------------------------- /hnix-store-tests/tests/StorePathSpec.hs: -------------------------------------------------------------------------------- 1 | module StorePathSpec where 2 | 3 | import Test.Hspec (Spec, describe) 4 | import Test.Hspec.QuickCheck (prop) 5 | import Test.Hspec.Nix (roundtrips) 6 | 7 | import System.Nix.Arbitrary () 8 | import System.Nix.StorePath 9 | 10 | import qualified Data.Attoparsec.Text 11 | 12 | spec :: Spec 13 | spec = do 14 | describe "StorePath" $ do 15 | prop "roundtrips using parsePath . storePathToRawFilePath" $ 16 | \storeDir -> 17 | roundtrips 18 | (storePathToRawFilePath storeDir) 19 | (parsePath storeDir) 20 | 21 | prop "roundtrips using parsePathFromText . storePathToText" $ 22 | \storeDir -> 23 | roundtrips 24 | (storePathToText storeDir) 25 | (parsePathFromText storeDir) 26 | 27 | prop "roundtrips using pathParser . storePathToText" $ 28 | \storeDir -> 29 | roundtrips 30 | (storePathToText storeDir) 31 | (Data.Attoparsec.Text.parseOnly $ pathParser storeDir) 32 | -------------------------------------------------------------------------------- /matrix.nix: -------------------------------------------------------------------------------- 1 | # Matrix build for all packages and compilers 2 | # 3 | # Uses ./.github/workflows/ci.dhall as a single source of 4 | # truth for a list of supported compilers 5 | # 6 | # The dhall expression needs to be frozen via dhall freeze 7 | # which is done automatically when we update it via ./.github/workflows/ci.sh 8 | # and dhallDirectoryToNix uses ci.dhall.frozen instead 9 | { pkgs ? import {} 10 | }: 11 | let 12 | lib = pkgs.lib; 13 | ciDhallNix = pkgs.dhallDirectoryToNix { src = ./.github/workflows; file = "ci.dhall.frozen"; }; 14 | ciCompilers = ciDhallNix.jobs.build.strategy.matrix.ghc; 15 | 16 | # from e.g. 9.6.3 to ghc963 17 | convertCompilers = cs: 18 | map (x: "ghc${lib.strings.replaceStrings ["."] [""] x}") cs; 19 | 20 | compilers = 21 | lib.traceValFn (cs: 22 | let 23 | prettyCs = lib.concatMapStringsSep "\n" (c: "- ${c}") cs; 24 | in 25 | "building for compilers:\n${prettyCs}") 26 | (convertCompilers ciCompilers); 27 | in 28 | pkgs.lib.recurseIntoAttrs 29 | (pkgs.lib.genAttrs compilers (compiler: 30 | pkgs.lib.recurseIntoAttrs 31 | (import ./. { inherit pkgs compiler; }) 32 | ) 33 | ) 34 | -------------------------------------------------------------------------------- /overlay.nix: -------------------------------------------------------------------------------- 1 | pkgs: compiler: hself: hsuper: 2 | let 3 | lib = pkgs.lib; 4 | haskellLib = pkgs.haskell.lib; 5 | 6 | fetchGitHubPR = { url, sha256 ? throw "sha256 required", ...}@x: 7 | let 8 | m = builtins.match "https://github.com/(.+)/(.+)/pull/([0-9]+)" url; 9 | parts = if m != null then m else throw "Failed to match PR URL"; 10 | in 11 | pkgs.fetchFromGitHub ({ 12 | owner = builtins.elemAt parts 0; 13 | repo = builtins.elemAt parts 1; 14 | rev = "refs/pull/${builtins.elemAt parts 2}/head"; 15 | } // (lib.filterAttrs (n: v: n != "url") x)); 16 | in 17 | { 18 | # srk 2024-07-28: allow template-haskell 2.22 (GHC 9.8) 19 | # https://github.com/obsidiansystems/dependent-sum-template/pull/13 20 | dependent-sum-template = 21 | haskellLib.doJailbreak 22 | hsuper.dependent-sum-template_0_2_0_1; 23 | 24 | hnix-store-core = 25 | lib.pipe 26 | (hself.callCabal2nix "hnix-store-core" ./hnix-store-core {}) 27 | [ 28 | haskellLib.compose.buildFromSdist 29 | ]; 30 | hnix-store-db = 31 | lib.pipe 32 | (hself.callCabal2nix "hnix-store-db" ./hnix-store-db {}) 33 | [ 34 | haskellLib.compose.buildFromSdist 35 | ]; 36 | hnix-store-json = 37 | lib.pipe 38 | (hself.callCabal2nix "hnix-store-json" ./hnix-store-json {}) 39 | [ 40 | haskellLib.compose.buildFromSdist 41 | ]; 42 | hnix-store-nar = 43 | lib.pipe 44 | (hself.callCabal2nix "hnix-store-nar" ./hnix-store-nar {}) 45 | [ 46 | haskellLib.compose.buildFromSdist 47 | ]; 48 | hnix-store-readonly = 49 | lib.pipe 50 | (hself.callCabal2nix "hnix-store-readonly" ./hnix-store-readonly {}) 51 | [ 52 | haskellLib.compose.buildFromSdist 53 | ]; 54 | hnix-store-remote = 55 | lib.pipe 56 | # enable -fio-testsuite for Linux systems as 57 | # it requires linux-namespaces 58 | # NOTE: we cannot use haskellLib.compose.enableCabalFlag 59 | # as the testsuite deps won't get picked up 60 | # after cabal2nix step 61 | ( 62 | if pkgs.stdenv.isDarwin 63 | then hself.callCabal2nix "hnix-store-remote" ./hnix-store-remote {} 64 | else hself.callCabal2nixWithOptions "hnix-store-remote" ./hnix-store-remote "-fio-testsuite" {} 65 | ) 66 | [ 67 | haskellLib.compose.buildFromSdist 68 | (pkg: pkg.overrideAttrs (attrs: { 69 | buildInputs = attrs.buildInputs ++ [ 70 | pkgs.nix 71 | ]; 72 | })) 73 | ]; 74 | hnix-store-tests = 75 | lib.pipe 76 | (hself.callCabal2nix "hnix-store-tests" ./hnix-store-tests {}) 77 | [ 78 | haskellLib.compose.buildFromSdist 79 | ]; 80 | } 81 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | attrs@{...}: 2 | let 3 | inherit (import ./. attrs) pkgs haskellPackages; 4 | hlib = pkgs.haskell.lib; 5 | 6 | packages = [ 7 | "hnix-store-core" 8 | "hnix-store-db" 9 | "hnix-store-json" 10 | "hnix-store-nar" 11 | "hnix-store-readonly" 12 | "hnix-store-remote" 13 | "hnix-store-tests" 14 | ]; 15 | extract-external-inputs = p: 16 | builtins.filter 17 | (dep: !(builtins.elem dep packages)) 18 | (map 19 | (x: x.pname) 20 | (hlib.getHaskellBuildInputs haskellPackages.${p})); 21 | external-inputs = 22 | map 23 | (x: haskellPackages.${x}) 24 | (builtins.concatLists 25 | (map 26 | extract-external-inputs 27 | packages)); 28 | metaPackage = 29 | haskellPackages.mkDerivation 30 | { pname = "hnix-store-shell"; 31 | version = "0.0.0.0"; 32 | libraryHaskellDepends = external-inputs; 33 | license = pkgs.stdenv.lib.licenses.asl20;}; 34 | 35 | package-envs = 36 | builtins.listToAttrs 37 | (map 38 | (p: 39 | { name = p; 40 | value = haskellPackages.${p}.env;}) 41 | packages); 42 | 43 | in 44 | 45 | metaPackage.env // package-envs 46 | --------------------------------------------------------------------------------