├── hie.yaml ├── shell.nix ├── test └── KudzuTest.hs ├── app └── Main.hs ├── scripts.nix ├── CHANGELOG.md ├── .gitignore ├── nix ├── sources.json └── sources.nix ├── flake.lock ├── default.nix ├── .github └── workflows │ └── build.yml ├── LICENSE ├── flake.nix ├── README.md ├── src └── Kudzu.hs └── kudzu.cabal /hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import ./default.nix { }).shell 2 | -------------------------------------------------------------------------------- /test/KudzuTest.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | main :: IO () 4 | main = putStrLn "Test suite not yet implemented." 5 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = do 5 | print "Hello World, Kudzu will cover your code?" 6 | -------------------------------------------------------------------------------- /scripts.nix: -------------------------------------------------------------------------------- 1 | {s}: 2 | { 3 | ghcidScript = s "dev" "ghcid --command 'cabal new-repl lib:kudzu' --allow-eval --warnings"; 4 | testScript = s "test" "cabal run test:kudzu-tests"; 5 | hoogleScript = s "hgl" "hoogle serve"; 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for kudzu 2 | 3 | ## 0.1.0.0 -- 2022-07-01 4 | 5 | * First version. Released on an unsuspecting world. Good Luck World. 6 | Kinda does the thing, yay? 7 | 8 | 9 | ## 0.1.1.0 -- 2024-04-27 10 | 11 | * refactored grabUntilNSame 12 | * updated dependencies 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.hie 7 | *.chi 8 | *.chs.h 9 | *.dyn_o 10 | *.dyn_hi 11 | .hpc 12 | .hsenv 13 | .cabal-sandbox/ 14 | cabal.sandbox.config 15 | *.prof 16 | *.aux 17 | *.hp 18 | *.eventlog 19 | .stack-work/ 20 | cabal.project.local 21 | cabal.project.local~ 22 | .HTF/ 23 | .ghc.environment.* 24 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "nixpkgs": { 3 | "branch": "nixpkgs-unstable", 4 | "description": "Nix Packages collection", 5 | "homepage": null, 6 | "owner": "nixos", 7 | "repo": "nixpkgs", 8 | "rev": "2fdb6f2e08e7989b03a2a1aa8538d99e3eeea881", 9 | "sha256": "12wfjn35j9k28jgp8ihg96c90lqnplfm5r2v5y02pbics58lcrbw", 10 | "type": "tarball", 11 | "url": "https://github.com/nixos/nixpkgs/archive/2fdb6f2e08e7989b03a2a1aa8538d99e3eeea881.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1713725259, 6 | "narHash": "sha256-9ZR/Rbx5/Z/JZf5ehVNMoz/s5xjpP0a22tL6qNvLt5E=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "a5e4bbcb4780c63c79c87d29ea409abf097de3f7", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixos-23.11", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { compiler ? "ghc8107" }: 2 | 3 | let 4 | sources = import ./nix/sources.nix; 5 | pkgs = import sources.nixpkgs { }; 6 | 7 | gitignore = pkgs.nix-gitignore.gitignoreSourcePure [ ./.gitignore ]; 8 | 9 | myHaskellPackages = pkgs.haskell.packages.${compiler}.override { 10 | overrides = hself: hsuper: { 11 | "kudzu" = hself.callCabal2nix "kudzu" (gitignore ./.) { }; 12 | }; 13 | }; 14 | 15 | shell = myHaskellPackages.shellFor { 16 | packages = p: [ p."kudzu" ]; 17 | buildInputs = [ 18 | myHaskellPackages.haskell-language-server 19 | pkgs.haskellPackages.cabal-install 20 | pkgs.haskellPackages.ghcid 21 | pkgs.haskellPackages.ormolu 22 | pkgs.haskellPackages.hlint 23 | pkgs.haskellPackages.hasktags 24 | pkgs.niv 25 | pkgs.nixpkgs-fmt 26 | ]; 27 | withHoogle = true; 28 | }; 29 | 30 | exe = pkgs.haskell.lib.justStaticExecutables (myHaskellPackages."kudzu"); 31 | 32 | docker = pkgs.dockerTools.buildImage { 33 | name = "kudzu"; 34 | config.Cmd = [ "${exe}/bin/kudzu" ]; 35 | }; 36 | in { 37 | inherit shell; 38 | inherit exe; 39 | inherit docker; 40 | inherit myHaskellPackages; 41 | "kudzu" = myHaskellPackages."kudzu"; 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Builds and tests this Haskell project on "GitHub Actions" 2 | # 3 | # some docs: https://github.com/haskell/actions/tree/main/setup 4 | 5 | name: build 6 | on: [push] 7 | jobs: 8 | build-and-test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Cache ~/.cache/cabal/packages 13 | uses: actions/cache@v3 14 | with: 15 | path: ~/.cache/cabal/packages 16 | key: v1-${{ runner.os }}-cabal-packages-${{ hashFiles('*.cabal') }} 17 | restore-keys: v1-${{ runner.os }}-cabal-packages- 18 | 19 | - name: Cache ~/.local/state/cabal 20 | uses: actions/cache@v3 21 | with: 22 | path: ~/.local/state/cabal 23 | key: v1-${{ runner.os }}-cabal-state-${{ hashFiles('*.cabal') }} 24 | restore-keys: v1-${{ runner.os }}-cabal-state-${{ hashFiles('*.cabal') }} 25 | 26 | - run: du -hd3 ~/.cache/cabal/packages ~/.local/state/cabal || true 27 | 28 | - run: haddock --version 29 | - run: ghc --version 30 | - run: cabal --version 31 | - run: haddock --version 32 | - run: ghc-pkg list 33 | 34 | - name: Check out repository 35 | uses: actions/checkout@v3 36 | 37 | - run: cabal update 38 | - run: cabal configure --enable-tests --enable-benchmarks --enable-coverage --enable-documentation 39 | - run: cabal build 40 | - run: cabal test 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, Shae Erisson 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Shae Erisson nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "kudzu"; 3 | 4 | inputs = { 5 | # Nix Inputs 6 | nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11"; 7 | }; 8 | 9 | outputs = { 10 | self, 11 | nixpkgs, 12 | }: 13 | let 14 | forAllSystems = function: 15 | nixpkgs.lib.genAttrs [ 16 | "x86_64-linux" 17 | "aarch64-linux" 18 | "x86_64-darwin" 19 | "aarch64-darwin" 20 | ] (system: function rec { 21 | inherit system; 22 | compilerVersion = "ghc963"; 23 | pkgs = nixpkgs.legacyPackages.${system}; 24 | hsPkgs = pkgs.haskell.packages.${compilerVersion}.override { 25 | overrides = hfinal: hprev: { 26 | kudzu = hfinal.callCabal2nix "kudzu" ./. {}; 27 | }; 28 | }; 29 | }); 30 | in 31 | { 32 | # nix fmt 33 | formatter = forAllSystems ({pkgs, ...}: pkgs.alejandra); 34 | 35 | # nix develop 36 | devShell = forAllSystems ({hsPkgs, pkgs, ...}: 37 | hsPkgs.shellFor { 38 | # withHoogle = true; 39 | packages = p: [ 40 | p.kudzu 41 | ]; 42 | buildInputs = with pkgs; 43 | [ 44 | hsPkgs.haskell-language-server 45 | haskellPackages.cabal-install 46 | cabal2nix 47 | haskellPackages.ghcid 48 | haskellPackages.fourmolu 49 | haskellPackages.cabal-fmt 50 | ] 51 | ++ (builtins.attrValues (import ./scripts.nix {s = pkgs.writeShellScriptBin;})); 52 | }); 53 | 54 | # nix build 55 | packages = forAllSystems ({hsPkgs, ...}: { 56 | kudzu = hsPkgs.kudzu; 57 | default = hsPkgs.kudzu; 58 | }); 59 | 60 | # You can't build the kudzu package as a check because of IFD in cabal2nix 61 | checks = {}; 62 | 63 | # nix run 64 | apps = forAllSystems ({system, ...}: { 65 | kudzu = { 66 | type = "app"; 67 | program = "${self.packages.${system}.kudzu}/bin/kudzu"; 68 | }; 69 | default = self.apps.${system}.kudzu; 70 | }); 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KUDZU will slowly grow to cover all of your code 2 | 3 | Kudzu is a library that throws test cases at your property tests until the code coverage no longer increases. 4 | 5 | # WHY? 6 | 7 | Property testing has no feedback loop, you randomly choose a number of test cases and hope for the best. 8 | 9 | How do you know if your property tests were any good? The best feedback I know is to use [hpc](https://wiki.haskell.org/Haskell_program_coverage) and look at the pretty colored HTML output to see what code was exercised. 10 | 11 | But wait, why do *I* have to look at the output? Isn't that why we have computers? 12 | 13 | # HOW? 14 | 15 | In Haskell, you can get [code coverage results](https://hackage.haskell.org/package/hpc/docs/Trace-Hpc-Reflect.html#v:examineTix) while your program is running! 16 | 17 | # WHAT FEEDBACK LOOP? 18 | 19 | The simplest feedback loop is to keep running random tests until new code coverage stops increasing. 20 | 21 | # HOW DO I MAKE IT GO? 22 | 23 | 1. add kudzu to your test-suite depends, 24 | 2. import the [`Kudzu`] module 25 | 3. call the testUntil function of your choice 26 | 1. [`testUntilSameQCMany`] or [`testUntilSameQC`] 27 | 2. [`testUntilSameHHMany`] or [`testUntilSameHH`] 28 | 3. [`testUntilSameLCMany`] or [`testUntilsameLC`] 29 | 4. run with `cabal test --enable-coverage` 30 | 31 | # TELL ME MORE 32 | 33 | The best write up of this idea is [Random Test Generation, Coverage Based](https://danluu.com/testing/). 34 | 35 | # TODO 36 | 37 | - [x] support HedgeHog 38 | - [x] support QuickCheck 39 | - [x] support LeanCheck 40 | - [ ] figure out how to use Kudzu on Kudzu without looping forever 41 | 42 | # EXAMPLE 43 | 44 | You can see kudzu in use in the [tests](https://github.com/shapr/takedouble/blob/main/test/Main.hs) for [takedouble](https://github.com/shapr/takedouble/) 45 | 46 | [`Kudzu`]: https://hackage.haskell.org/package/kudzu/docs/Kudzu.html 47 | [`testUntilSameQC`]: https://hackage.haskell.org/package/kudzu/docs/Kudzu.html#v:testUntilSameQC 48 | [`testUntilSameQCMany`]: https://hackage.haskell.org/package/kudzu/docs/Kudzu.html#v:testUntilSameQCMany 49 | [`testUntilSameHH`]: https://hackage.haskell.org/package/kudzu/docs/Kudzu.html#v:testUntilSameHH 50 | [`testUntilSameHHMany`]: https://hackage.haskell.org/package/kudzu/docs/Kudzu.html#v:testUntilSameHHMany 51 | [`testUntilSameLC`]: https://hackage.haskell.org/package/kudzu/docs/Kudzu.html#v:testUntilSameLC 52 | [`testUntilSameLCMany`]: https://hackage.haskell.org/package/kudzu/docs/Kudzu.html#v:testUntilSameLCMany 53 | -------------------------------------------------------------------------------- /src/Kudzu.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Kudzu where 4 | 5 | import Control.Monad (unless) 6 | import qualified Hedgehog as HH 7 | import qualified Test.LeanCheck as LC 8 | import qualified Test.QuickCheck as QC 9 | import qualified Test.QuickCheck.Random as QC 10 | import Trace.Hpc.Reflect (examineTix) 11 | import Trace.Hpc.Tix (Tix (..), TixModule (..)) 12 | 13 | testUntilSameQCMany :: (Traversable t, QC.Testable a) => Int -> t a -> IO (t (KudzuResult Integer)) 14 | testUntilSameQCMany howMany ts = do 15 | mapM (testUntilSameQC howMany) ts 16 | 17 | -- | QuickCheck 18 | testUntilSameQC :: (QC.Testable a) => Int -> a -> IO (KudzuResult Integer) 19 | testUntilSameQC n testable = do 20 | let rs = map (examineAndCount' testable) [0 .. n] 21 | grabUntilNSame n rs 22 | 23 | examineAndCount' :: (QC.Testable prop) => prop -> Int -> IO Integer 24 | examineAndCount' v size = do 25 | qcg <- QC.newQCGen 26 | QC.quickCheckWith (QC.stdArgs{QC.replay = Just (qcg, size)}) (QC.withMaxSuccess 1 v) 27 | tixModuleCount <$> examineTix 28 | 29 | -- | Hedgehog 30 | testUntilSameHHMany :: (Traversable t) => Int -> t HH.Property -> IO (t (KudzuResult Integer)) 31 | testUntilSameHHMany howMany ps = do 32 | mapM (testUntilSameHH howMany) ps 33 | 34 | testUntilSameHH :: Int -> HH.Property -> IO (KudzuResult Integer) 35 | testUntilSameHH n prop = grabUntilNSame n $ examineAndCountHH <$> repeat prop 36 | 37 | examineAndCountHH :: HH.Property -> IO Integer 38 | examineAndCountHH prop = do 39 | passed <- HH.check . HH.withTests 1 $ prop 40 | unless passed $ error "property failed" 41 | tixModuleCount <$> examineTix 42 | 43 | -- | LeanCheck 44 | testUntilSameLCMany :: (Traversable t, LC.Testable a) => Int -> t a -> IO (t (KudzuResult Integer)) 45 | testUntilSameLCMany howMany ts = do 46 | mapM (testUntilSameLC howMany) ts 47 | 48 | testUntilSameLC :: (LC.Testable a) => Int -> a -> IO (KudzuResult Integer) 49 | testUntilSameLC n testable = grabUntilNSame n $ examineAndCount <$> LC.results testable 50 | 51 | examineAndCount :: ([String], Bool) -> IO Integer 52 | examineAndCount v = unless (snd v) (error $ unwords ("test failed with:" : fst v)) >> tixModuleCount <$> examineTix 53 | 54 | data KudzuResult a = KFail Int | KSuccess Int a deriving (Show, Eq, Ord) 55 | 56 | -- | Keep running property tests until the "amount" of code coverage is the same for N iterations of one test. 57 | grabUntilNSame :: 58 | (Monad m, Eq a) => 59 | -- | How many iterations must be the same? 60 | Int -> 61 | -- | a lazy list of iterations 62 | [m a] -> 63 | m (KudzuResult a) 64 | grabUntilNSame _ [] = pure $ KFail 0 65 | grabUntilNSame orig (a : as) = do 66 | a' <- a -- run the first iteration of the test 67 | go 0 orig as a' 68 | where 69 | go c 0 _ z = pure $ KSuccess c z -- we reached the desired window size 70 | go c _ [] _ = pure $ KFail c -- if we run out of list elements for test results, we're done 71 | go c n (b : bs) z = do 72 | a' <- b 73 | if a' == z 74 | then go (c + 1) (n - 1) bs z 75 | else go (c + 1) orig as a' 76 | 77 | -- | How many regions were executed at least once for this module? 78 | tixCount :: TixModule -> Integer 79 | tixCount (TixModule _ _ _ regions) = sum $ 1 <$ filter (> 0) regions 80 | 81 | -- | How many regions were executed at least once for all these modules? 82 | tixModuleCount :: Tix -> Integer 83 | tixModuleCount (Tix ms) = sum $ map tixCount ms 84 | -------------------------------------------------------------------------------- /kudzu.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | 3 | -- Initial package description 'kudzu.cabal' generated by 4 | -- 'cabal init'. For further documentation, see: 5 | -- http://haskell.org/cabal/users-guide/ 6 | -- 7 | -- The name of the package. 8 | name: kudzu 9 | 10 | -- The package version. 11 | -- See the Haskell package versioning policy (PVP) for standards 12 | -- guiding when and how versions should be incremented. 13 | -- https://pvp.haskell.org 14 | -- PVP summary: +-+------- breaking API changes 15 | -- | | +----- non-breaking API additions 16 | -- | | | +--- code changes with no API change 17 | version: 0.1.1.0 18 | 19 | -- A short (one-line) description of the package. 20 | synopsis: coverage driven random testing framework 21 | 22 | -- A longer description of the package. 23 | -- description: 24 | 25 | -- URL for the project homepage or repository. 26 | homepage: https://github.com/shapr/kudzu 27 | 28 | -- A URL where users can report bugs. 29 | -- bug-reports: 30 | 31 | -- The license under which the package is released. 32 | license: BSD-3-Clause 33 | 34 | -- The file containing the license text. 35 | license-file: LICENSE 36 | 37 | -- The package author(s). 38 | author: Shae Erisson 39 | 40 | -- An email address to which users can send suggestions, bug reports, and patches. 41 | maintainer: shae@scannedinavian.com 42 | 43 | -- A copyright notice. 44 | -- copyright: 45 | category: Testing 46 | 47 | -- Extra files to be distributed with the package, such as examples or a README. 48 | extra-source-files: README.md 49 | LICENSE 50 | extra-doc-files: CHANGELOG.md 51 | description: Kudzu is a coverage driven random testing framework 52 | source-repository head 53 | type: git 54 | location: https://github.com/shapr/kudzu.git 55 | 56 | 57 | library 58 | -- Modules exported by the library. 59 | exposed-modules: Kudzu 60 | 61 | -- Modules included in this library but not exported. 62 | -- other-modules: 63 | 64 | -- LANGUAGE extensions used by modules in this package. 65 | -- other-extensions: 66 | 67 | -- Other library packages from which modules are imported. 68 | build-depends: base >=4.14 && < 4.20 69 | , QuickCheck >= 2.12 && < 2.16 70 | , hedgehog >=1.2 && < 1.5 71 | , hpc >= 0.6.0 && < 0.8.0 72 | , leancheck >= 1.0.0 && < 1.2 73 | 74 | 75 | -- Directories containing source files. 76 | hs-source-dirs: src 77 | 78 | -- Base language which the package is written in. 79 | default-language: Haskell2010 80 | ghc-options: -Wall 81 | 82 | executable kudzu 83 | -- .hs or .lhs file containing the Main module. 84 | main-is: Main.hs 85 | 86 | -- Modules included in this executable, other than Main. 87 | -- other-modules: 88 | 89 | -- LANGUAGE extensions used by modules in this package. 90 | -- other-extensions: 91 | 92 | -- Other library packages from which modules are imported. 93 | build-depends: 94 | base >=4.14 && < 4.20, 95 | kudzu 96 | 97 | -- Directories containing source files. 98 | hs-source-dirs: app 99 | 100 | -- Base language which the package is written in. 101 | default-language: Haskell2010 102 | 103 | test-suite kudzu-test 104 | -- Base language which the package is written in. 105 | default-language: Haskell2010 106 | 107 | -- The interface type and version of the test suite. 108 | type: exitcode-stdio-1.0 109 | 110 | -- Directories containing source files. 111 | hs-source-dirs: test 112 | 113 | -- The entrypoint to the test suite. 114 | main-is: KudzuTest.hs 115 | 116 | -- Test dependencies. 117 | build-depends: base >=4.14 118 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: spec: 10 | if spec.builtin or true then 11 | builtins_fetchurl { inherit (spec) url sha256; } 12 | else 13 | pkgs.fetchurl { inherit (spec) url sha256; }; 14 | 15 | fetch_tarball = pkgs: name: spec: 16 | let 17 | ok = str: ! builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str); 18 | # sanitize the name, though nix will still fail if name starts with period 19 | name' = stringAsChars (x: if ! ok x then "-" else x) "${name}-src"; 20 | in 21 | if spec.builtin or true then 22 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 23 | else 24 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 25 | 26 | fetch_git = spec: 27 | builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; 28 | 29 | fetch_builtin-tarball = name: throw 30 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 31 | $ niv modify ${name} -a type=tarball -a builtin=true''; 32 | 33 | fetch_builtin-url = name: throw 34 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 35 | $ niv modify ${name} -a type=file -a builtin=true''; 36 | 37 | # 38 | # Various helpers 39 | # 40 | 41 | # The set of packages used when specs are fetched using non-builtins. 42 | mkPkgs = sources: 43 | let 44 | sourcesNixpkgs = 45 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; 46 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 47 | hasThisAsNixpkgsPath = == ./.; 48 | in 49 | if builtins.hasAttr "nixpkgs" sources 50 | then sourcesNixpkgs 51 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 52 | import {} 53 | else 54 | abort 55 | '' 56 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 57 | add a package called "nixpkgs" to your sources.json. 58 | ''; 59 | 60 | # The actual fetching function. 61 | fetch = pkgs: name: spec: 62 | 63 | if ! builtins.hasAttr "type" spec then 64 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 65 | else if spec.type == "file" then fetch_file pkgs spec 66 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 67 | else if spec.type == "git" then fetch_git spec 68 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 69 | else if spec.type == "builtin-url" then fetch_builtin-url name 70 | else 71 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 72 | 73 | # Ports of functions for older nix versions 74 | 75 | # a Nix version of mapAttrs if the built-in doesn't exist 76 | mapAttrs = builtins.mapAttrs or ( 77 | f: set: with builtins; 78 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 79 | ); 80 | 81 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 82 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 83 | 84 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 85 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 86 | 87 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 88 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 89 | concatStrings = builtins.concatStringsSep ""; 90 | 91 | # fetchTarball version that is compatible between all the versions of Nix 92 | builtins_fetchTarball = { url, name, sha256 }@attrs: 93 | let 94 | inherit (builtins) lessThan nixVersion fetchTarball; 95 | in 96 | if lessThan nixVersion "1.12" then 97 | fetchTarball { inherit name url; } 98 | else 99 | fetchTarball attrs; 100 | 101 | # fetchurl version that is compatible between all the versions of Nix 102 | builtins_fetchurl = { url, sha256 }@attrs: 103 | let 104 | inherit (builtins) lessThan nixVersion fetchurl; 105 | in 106 | if lessThan nixVersion "1.12" then 107 | fetchurl { inherit url; } 108 | else 109 | fetchurl attrs; 110 | 111 | # Create the final "sources" from the config 112 | mkSources = config: 113 | mapAttrs ( 114 | name: spec: 115 | if builtins.hasAttr "outPath" spec 116 | then abort 117 | "The values in sources.json should not have an 'outPath' attribute" 118 | else 119 | spec // { outPath = fetch config.pkgs name spec; } 120 | ) config.sources; 121 | 122 | # The "config" used by the fetchers 123 | mkConfig = 124 | { sourcesFile ? ./sources.json 125 | , sources ? builtins.fromJSON (builtins.readFile sourcesFile) 126 | , pkgs ? mkPkgs sources 127 | }: rec { 128 | # The sources, i.e. the attribute set of spec name to spec 129 | inherit sources; 130 | 131 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 132 | inherit pkgs; 133 | }; 134 | in 135 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 136 | --------------------------------------------------------------------------------