├── .gitignore ├── example-cabal-library ├── app │ └── Main.hs └── example-cabal-library.cabal ├── purescript-parser-combinator ├── spago.dhall ├── test │ └── Main.purs └── src │ ├── NixBuiltins.nix │ ├── NixBuiltins.purs │ └── Parsec.purs ├── purescript-cabal-parser ├── test │ └── Main.purs ├── spago.dhall └── src │ ├── Main.nix │ └── Main.purs ├── packages.dhall ├── flake.nix ├── LICENSE ├── flake.lock ├── README.md └── test.nix /.gitignore: -------------------------------------------------------------------------------- 1 | # nix result files 2 | /result* 3 | 4 | # PureScript stuff 5 | output/ 6 | .spago/ 7 | -------------------------------------------------------------------------------- /example-cabal-library/app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /purescript-parser-combinator/spago.dhall: -------------------------------------------------------------------------------- 1 | { name = "parser-combinator" 2 | , dependencies = [ "control", "either", "maybe", "prelude", "tuples" ] 3 | , backend = "purenix" 4 | , packages = ../packages.dhall 5 | , sources = [ "src/**/*.purs" ] 6 | } 7 | -------------------------------------------------------------------------------- /purescript-cabal-parser/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /purescript-parser-combinator/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | -------------------------------------------------------------------------------- /purescript-cabal-parser/spago.dhall: -------------------------------------------------------------------------------- 1 | { name = "cabal-parser" 2 | , dependencies = [ "arrays", "control", "either", "maybe", "parser-combinator", "prelude", "tuples", "unsafe-coerce" ] 3 | , packages = ../packages.dhall 4 | , backend = "purenix" 5 | , sources = [ "src/**/*.purs" ] 6 | } 7 | -------------------------------------------------------------------------------- /example-cabal-library/example-cabal-library.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: example-cabal-library 3 | version: 0.1.0.0 4 | license: BSD-3-Clause 5 | 6 | executable example-cabal-library 7 | main-is: Main.hs 8 | hs-source-dirs: app 9 | default-language: Haskell2010 10 | build-depends: base ^>=4.14.1.0, aeson 11 | -------------------------------------------------------------------------------- /packages.dhall: -------------------------------------------------------------------------------- 1 | let upstream = 2 | https://raw.githubusercontent.com/purenix-org/temp-package-set/main/packages.dhall sha256:35a43b497d0e850f2ea201726d267132b1e66149d7f80d43a64fc8318ca783b9 3 | 4 | in upstream 5 | with cabal-parser = ./purescript-cabal-parser/spago.dhall as Location 6 | with parser-combinator = 7 | ./purescript-parser-combinator/spago.dhall as Location 8 | -------------------------------------------------------------------------------- /purescript-cabal-parser/src/Main.nix: -------------------------------------------------------------------------------- 1 | 2 | { # This Nix file ends up in 3 | # cabal2nixWithoutIFD/purescript-cabal-parser/output/Main/foreign.nix. The cabal file 4 | # is located at cabal2nixWithoutIFD/example-cabal-library/example-cabal-library.cabal. 5 | cabalFilePath = ./../../../example-cabal-library/example-cabal-library.cabal; 6 | 7 | haskellPackagePath = ./../../../example-cabal-library/.; 8 | 9 | # :: forall a. Array String -> (AttrSet -> a) -> FunctionWithArgs 10 | mkFunctionWithArgs = argList: f: 11 | { __functor = _: f; 12 | __functionArgs = builtins.listToAttrs (map (k: { name = k; value = false; }) argList); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "cabal2nixWithoutIFD"; 3 | 4 | inputs.purenix.url = "github:purenix-org/purenix"; 5 | inputs.nixpkgs.follows = "purenix/nixpkgs"; 6 | inputs.flake-utils.url = "github:numtide/flake-utils"; 7 | 8 | outputs = { self, nixpkgs, flake-utils, purenix }: 9 | flake-utils.lib.eachDefaultSystem (system: 10 | let 11 | overlay = self: _: { 12 | }; 13 | pkgs = import nixpkgs { 14 | inherit system; 15 | overlays = [ 16 | overlay 17 | ]; 18 | }; 19 | in 20 | { 21 | devShell = purenix.devShells.${system}.use-purenix; 22 | } 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /purescript-parser-combinator/src/NixBuiltins.nix: -------------------------------------------------------------------------------- 1 | 2 | rec { 3 | abort = builtins.abort; 4 | 5 | # This is taken from lib/attrsets.nix in Nixpkgs. 6 | # 7 | # :: forall a. Array String -> a -> AttrSet -> a 8 | attrByPath = attrPath: default: e: 9 | let 10 | attr = builtins.head attrPath; 11 | in 12 | if attrPath == [] then 13 | e 14 | else 15 | if e ? ${attr} then 16 | attrByPath (builtins.tail attrPath) default e.${attr} 17 | else 18 | default; 19 | 20 | attrUpdate = a: b: a // b; 21 | 22 | attrUpdate' = a: b: a // b; 23 | 24 | concatStringsSep = builtins.concatStringsSep; 25 | 26 | getAttr = builtins.getAttr; 27 | 28 | # This is taken from lib/attrsets.nix in Nixpkgs. 29 | # 30 | # :: forall a. Array String -> AttrSet -> a 31 | getAttrFromPath = attrPath: set: 32 | let errorMsg = "cannot find attribute `" + builtins.concatStringsSep "." attrPath + "'"; 33 | in attrByPath attrPath (abort errorMsg) set; 34 | 35 | readFile = builtins.readFile; 36 | 37 | stringLength = builtins.stringLength; 38 | 39 | substring = builtins.substring; 40 | 41 | trace = builtins.trace; 42 | 43 | unsafeAdd = a: b: a + b; 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Dennis Gosnell, Jonas Carpay 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /purescript-parser-combinator/src/NixBuiltins.purs: -------------------------------------------------------------------------------- 1 | 2 | module NixBuiltins where 3 | 4 | import Unsafe.Coerce (unsafeCoerce) 5 | 6 | -- | A Nix attribute set that can have any types as values. This is generally 7 | -- | used from unsafe functions like `getAttr`. 8 | foreign import data AttrSet :: Type 9 | 10 | foreign import data Derivation :: Type 11 | 12 | foreign import data Path :: Type 13 | 14 | foreign import abort :: forall a. String -> a 15 | 16 | foreign import attrByPath :: forall a. Array String -> a -> AttrSet -> a 17 | 18 | foreign import attrUpdate :: AttrSet -> AttrSet -> AttrSet 19 | infixr 5 attrUpdate as // 20 | 21 | foreign import attrUpdate' :: forall r. AttrSet -> { | r } -> AttrSet 22 | infixr 5 attrUpdate' as //! 23 | 24 | foreign import concatStringsSep :: String -> Array String -> String 25 | 26 | foreign import getAttr :: forall a. String -> AttrSet -> a 27 | 28 | foreign import getAttrFromPath :: forall a. Array String -> AttrSet -> a 29 | 30 | foreign import readFile :: Path -> String 31 | 32 | foreign import stringLength :: String -> Int 33 | 34 | foreign import substring :: Int -> Int -> String -> String 35 | 36 | foreign import trace :: forall a. String -> a -> a 37 | 38 | foreign import unsafeAdd :: forall a b c. a -> b -> c 39 | 40 | appendPath :: Path -> String -> Path 41 | appendPath = unsafeAdd 42 | 43 | charToStr :: Char -> String 44 | charToStr = unsafeCoerce 45 | 46 | charArrayToStrArray :: Array Char -> Array String 47 | charArrayToStrArray = unsafeCoerce 48 | 49 | concatChars :: Array Char -> String 50 | concatChars = unsafeCoerce concatStrs 51 | 52 | concatStrs :: Array String -> String 53 | concatStrs = concatStringsSep "" 54 | 55 | -- | A flipped version of `getAttr`. 56 | getAttrFlip :: forall a. AttrSet -> String -> a 57 | getAttrFlip attrSet key = getAttr key attrSet 58 | infixl 9 getAttrFlip as !. 59 | 60 | toAttrSet :: forall r. {|r} -> AttrSet 61 | toAttrSet = unsafeCoerce 62 | 63 | unsafeStrToChar :: String -> Char 64 | unsafeStrToChar = unsafeCoerce 65 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1638122382, 6 | "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "74f7e4319258e287b0f9cb95426c9853b282730b", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "flake-utils_2": { 19 | "locked": { 20 | "lastModified": 1634851050, 21 | "narHash": "sha256-N83GlSGPJJdcqhUxSCS/WwW5pksYf3VP1M13cDRTSVA=", 22 | "owner": "numtide", 23 | "repo": "flake-utils", 24 | "rev": "c91f3de5adaf1de973b797ef7485e441a65b8935", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "type": "github" 31 | } 32 | }, 33 | "nixpkgs": { 34 | "locked": { 35 | "lastModified": 1636886446, 36 | "narHash": "sha256-4xsVM2H8CG3d/3V+GqDDLDOmb3kdrugbqKVyrg8Q/zc=", 37 | "owner": "NixOS", 38 | "repo": "nixpkgs", 39 | "rev": "5cb226a06c49f7a2d02863d0b5786a310599df6b", 40 | "type": "github" 41 | }, 42 | "original": { 43 | "owner": "NixOS", 44 | "ref": "nixpkgs-unstable", 45 | "repo": "nixpkgs", 46 | "type": "github" 47 | } 48 | }, 49 | "purenix": { 50 | "inputs": { 51 | "flake-utils": "flake-utils_2", 52 | "nixpkgs": "nixpkgs" 53 | }, 54 | "locked": { 55 | "lastModified": 1636973632, 56 | "narHash": "sha256-wuXIw7H/FJ7sja8ZOIBBSo/na+j6tThlQKuYeH4r2q4=", 57 | "owner": "purenix-org", 58 | "repo": "purenix", 59 | "rev": "4dd6ec3913825a66cc2c8a82219428d812c0a203", 60 | "type": "github" 61 | }, 62 | "original": { 63 | "owner": "purenix-org", 64 | "repo": "purenix", 65 | "type": "github" 66 | } 67 | }, 68 | "root": { 69 | "inputs": { 70 | "flake-utils": "flake-utils", 71 | "nixpkgs": [ 72 | "purenix", 73 | "nixpkgs" 74 | ], 75 | "purenix": "purenix" 76 | } 77 | } 78 | }, 79 | "root": "root", 80 | "version": 7 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cabal2nixWithoutIFD 2 | 3 | This repo contains a proof-of-concept for a 4 | [`callCabal2nix`](https://github.com/NixOS/nixpkgs/blob/d263feb6f6392939c4c5e0a2608a450f65417d18/pkgs/development/haskell-modules/make-package-set.nix#L220) 5 | function written in Nix. `callCabal2nix` uses 6 | [Import From Derivation (IFD)](https://blog.hercules-ci.com/2019/08/30/native-support-for-import-for-derivation/) 7 | and a Haskell program [cabal2nix](https://github.com/NixOS/cabal2nix) to take 8 | a Haskell `.cabal` file (like 9 | [`example-cabal-library.cabal`](`./example-cabal-library/example-cabal-library.cabal`)) 10 | and translate it into a derivation that can be built with Nix. 11 | 12 | One unfortunate part of `callCabal2nix` is that `cabal2nix` is written in 13 | Haskell, so IFD must be used to build the resulting derivation. IFD can often 14 | be slower than just doing things with native Nix, and it is not allowed 15 | in Nixpkgs. 16 | 17 | This repo provides a `callCabal2nixWithoutIFD` function that is written in Nix, 18 | so it doesn't have either of the above downsides. Cabal files are parsed in 19 | Nix directly, so IFD is not needed. 20 | 21 | Internally, this `callCabal2nixWithoutIFD` function is written in PureScript 22 | and transpiled to Nix using [PureNix](https://github.com/purenix-org/purenix). 23 | Writing a parser for a complicated format like a Cabal file is much more 24 | reasonable in a language like PureScript than Nix itself. 25 | 26 | While this repo is just a proof-of-concept, this approach of writing a 27 | complicated parser in PureNix could reasonably be extended to write a full 28 | parser for `.cabal` files. Writing a similar parser directly in raw Nix would 29 | be considerably more difficult. 30 | 31 | ## Compiling PureScript Code to Nix 32 | 33 | Compiling `purescript-cabal-parser` to Nix can be done with the following 34 | steps. 35 | 36 | First, get into a Nix devShell: 37 | 38 | ```console 39 | $ nix develop 40 | ``` 41 | 42 | The, change to `./purescript-cabal-parser` directory and run `spago build`: 43 | 44 | ```console 45 | $ cd ./purescript-cabal-parser/ 46 | $ spago build 47 | ``` 48 | 49 | This transpiles the PureScript code to Nix. The transpiled Nix code is placed 50 | in `./purescript-cabal-parser/output/`. 51 | 52 | ## Running Transpiled PureScript Code 53 | 54 | The transpiled PureScript code can be tested by using the [`test.nix`](./test.nix) 55 | script. 56 | 57 | Here's a derivation for a Haskell package that was built using 58 | `callCabal2nixWithoutIFD`: 59 | 60 | ```console 61 | $ nix-build ./test.nix -A exampleHaskellPackage 62 | ... 63 | /nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0 64 | ``` 65 | 66 | Here's a derivation for a Haskell package that was built using 67 | the normal `callCabal2nix`: 68 | 69 | ```console 70 | $ nix-build ./test.nix -A exampleHaskellPackageWithIFD 71 | ... 72 | /nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0 73 | ``` 74 | 75 | You can disable IFD in Nix and see that the Haskell derivation built with 76 | `callCabal2nixWithoutIFD` still builds successfully: 77 | 78 | ```console 79 | $ nix-build --option allow-import-from-derivation false ./test.nix -A exampleHaskellPackage 80 | ... 81 | /nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0 82 | $ nix-build --option allow-import-from-derivation false ./test.nix -A exampleHaskellPackageWithIFD 83 | error: cannot build '/nix/store/q5hq65ynrqnnnjs3kl1ggsx2pkwlw702-cabal2nix-example-cabal-library.drv' 84 | during evaluation because the option 'allow-import-from-derivation' is disabled 85 | ``` 86 | 87 | I recommend reading through the [`test.nix`](./test.nix) file to see what other 88 | derivations have been defined that you can try building. The file is heavily 89 | commented. 90 | 91 | ## Playing around in the Nix REPL 92 | 93 | You may be interested in taking a look at the PureScript code that defines the 94 | above derivations. Most of the interesting code is in 95 | [`purescript-cabal-parser/src/Main.purs`](./purescript-cabal-parser/src/Main.purs). 96 | 97 | You can load the transpiled PureScript code in a Nix REPL to play around with 98 | it: 99 | 100 | ```console 101 | $ cd ./purescript-cabal-parser/ 102 | $ nix repl ./purescript-cabal-parser/output/Main 103 | ``` 104 | 105 | This puts you in a Nix REPL with all the functions and datatypes from the 106 | [`Main`](./purescript-cabal-parser/src/Main.purs) module available. 107 | 108 | For instance, `Main` defines a function called `cabalParser`: 109 | 110 | ```purescript 111 | cabalParser :: String -> Either (Array String) CabalFile 112 | ``` 113 | 114 | This takes the raw text of a Cabal file and parses it into a `CabalFile` 115 | datatype: 116 | 117 | ```purescript 118 | type CabalFile = 119 | { name :: String 120 | , version :: String 121 | , license :: License 122 | , executable :: Executable 123 | } 124 | 125 | data License = LicenseBSD3 126 | 127 | type Executable = 128 | { name :: String 129 | , buildDepends :: Array String 130 | } 131 | ``` 132 | 133 | Let's try calling `cabalParser` from the Nix REPL: 134 | 135 | ```nix 136 | nix-repl> rawCabalFile = builtins.readFile ./ 137 | nix-repl> :p cabalParser rawCabalFile 138 | { __field0 = { executable = { buildDepends = [ "base" "aeson" ]; name = "example-cabal-library"; }; license = { __tag = "LicenseBSD3"; }; name = "example-cabal-library"; version = "0.1.0.0"; }; __tag = "Right"; } 139 | ``` 140 | 141 | If you squint, you can roughly see this attrset corresponds to the `CabalFile` 142 | datatype. 143 | 144 | There are lots of comments in the 145 | [`Main.purs`](./purescript-cabal-parser/src/Main.purs) file, so you may want to 146 | skim through it to see what else is available. 147 | 148 | ## Caveats 149 | 150 | The cabal parser implemented in `./purescript-cabal-parser/` is completely a 151 | proof-of-concept. It only parses the absolute simplest of `.cabal` files. 152 | I wouldn't recommend using it any arbitrary Haskell package. 153 | 154 | However, I hope the PureScript code in this repository shows the potential of 155 | PureNix for writing things that would be difficult in raw Nix. 156 | -------------------------------------------------------------------------------- /test.nix: -------------------------------------------------------------------------------- 1 | # This file is used for testing the transpiled PureScript code. 2 | # Check the documentation below on each overlay to see exactly what attributes 3 | # each overlay defines, and how to build them. 4 | # 5 | # Before using this file, you must build the PureScript library 6 | # `./purescript-cabal-parser`. See the README.md for instructions on how to do 7 | # this. 8 | 9 | let 10 | 11 | flake-lock = builtins.fromJSON (builtins.readFile ./flake.lock); 12 | 13 | nixpkgs-src = builtins.fetchTarball { 14 | url = "https://github.com/NixOS/nixpkgs/archive/${flake-lock.nodes.nixpkgs.locked.rev}.tar.gz"; 15 | sha256 = flake-lock.nodes.nixpkgs.locked.narHash; 16 | }; 17 | 18 | # This is the compiled output of the 19 | # `./purescript-cabal-parser/src/Main.purs` file. Note that PureNix takes 20 | # PureScript files and transpiles them to Nix files as a record set, with 21 | # each attribute name being a PureScript function name. See the call to 22 | # `rawCabalFileToPkgDef` in `haskellPkgDrv` below for an example of using 23 | # a function from `purenixMain`. 24 | # 25 | # Try importing this `./purescript-cabal-parser/output/Main/default.nix` file 26 | # in the Nix repl and playing around with it. 27 | # 28 | # It feels pretty neat being able to call functions written in PureScript 29 | # from Nix. 30 | purenix-Main = import ./purescript-cabal-parser/output/Main; 31 | 32 | overlays = [ 33 | # This overlay adds a top-level package called `exampleHaskellPackageFromNix`. 34 | # 35 | # This top-level Haskell package is built by parsing the 36 | # `example-haskell-package.cabal` file using the PureScript function 37 | # `rawCabalFileToPkgDef`. 38 | # 39 | # This overlay is similar to the `exampleNixpkgsOverlay`, except it is 40 | # defined in Nix instead of PureScript. Check the 41 | # `./purescript-cabal-parser/src/Main.purs` file to compare how the 42 | # `exampleNixpkgsOverlay` function is written in PureScript. 43 | # 44 | # You can build `exampleHakellPackageFromNix` like the following: 45 | # 46 | # ``` 47 | # $ nix-build ./test.nix -A exampleHaskellPackageFromNix 48 | # /nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0 49 | # ``` 50 | (final: prev: { 51 | exampleHaskellPackageFromNix = 52 | let 53 | # `rawCabalFile` is just a string that contains the raw contents of the 54 | # cabal file for our example-cabal-library Haskell package. 55 | rawCabalFile = 56 | builtins.readFile ./example-cabal-library/example-cabal-library.cabal; 57 | 58 | # This uses the PureScript function `rawCabalFileToPkgDef` here from Nix. 59 | # This function parses a raw Cabal file, and turns it into something 60 | # that you can pass to `haskellPackages.callPackage`. 61 | # 62 | # `haskellPkgDrv` will become a function similar to this: 63 | # 64 | # ``` 65 | # { mkDerivation, aeson, base, lib }: 66 | # mkDerivation { 67 | # pname = "example-cabal-library"; 68 | # version = "0.1.0.0"; 69 | # src = ./example-cabal-library; 70 | # isLibrary = false; 71 | # isExecutable = true; 72 | # executableHaskellDepends = [ aeson base ]; 73 | # license = lib.licenses.bsd3; 74 | # } 75 | # ``` 76 | haskellPkgDrv = 77 | purenix-Main.rawCabalFileToPkgDef rawCabalFile ./example-cabal-library; 78 | in 79 | final.haskellPackages.callPackage haskellPkgDrv {}; 80 | }) 81 | 82 | # This overlay adds a top-level package called `exampleHaskellPackage`. 83 | # 84 | # This is exactly the same as the previous overlay, except it is defined in 85 | # PureScript. Check `./purescript-cabal-parser/src/Main.purs` to see how 86 | # this overlay is defined. 87 | # 88 | # You can build `exampleHakellPackage` like the following: 89 | # 90 | # ``` 91 | # $ nix-build ./test.nix -A exampleHaskellPackage 92 | # /nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0 93 | # ``` 94 | purenix-Main.exampleNixpkgsOverlay 95 | 96 | # This overlay is similar to `exampleNixpkgsOverlay`, but instead of 97 | # adding a top-level package, it adds a Haskell package to `haskellPackages`. 98 | # Adding a Haskell package is significantly more involved than adding a 99 | # top-level package. Check out the source in 100 | # `./purescript-cabal-parser/src/Main.purs` to see how this is defined. 101 | # 102 | # You can build this Haskell package like the following: 103 | # 104 | # ``` 105 | # $ nix-build ./test.nix -A haskellPackages.exampleHaskellPackage 106 | # /nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0 107 | # ``` 108 | purenix-Main.exampleNixpkgsHaskellOverlay 109 | 110 | # This overlay is almost the exact same as `exampleNixpkgsHaskellOverlay`, 111 | # but the PureScript code defining it is strongly typed. 112 | # `exampleNixpkgsHaskellOverlay` uses mostly dynamically-typed Nix values 113 | # like `AttrSet` and unsafe getters. But `exampleNixpkgsHaskellOverlayTyped` 114 | # uses realistic types for each attribute set. This is possible because of 115 | # PureScripts row types. 116 | # 117 | # See `./purescript-cabal-parser/src/Main.purs` for all the gory details. 118 | # 119 | # You can build the Haskell package added in this overlay like the following: 120 | # 121 | # ``` 122 | # $ nix-build ./test.nix -A haskellPackages.exampleHaskellPackageTyped 123 | # /nix/store/xb0yxkpzdz3zjqmcxwa9z6r6r98yfizz-example-cabal-library-0.1.0.0 124 | # ``` 125 | purenix-Main.exampleNixpkgsHaskellOverlayTyped 126 | 127 | # This is an example overlay that shows how the normal `callCabal2nix` 128 | # function is used to build a Haskell package. 129 | # 130 | # This is useful because you can tell Nix to disallow IFD and confirm 131 | # this call fails: 132 | # 133 | # ``` 134 | # $ nix-build --option allow-import-from-derivation false ./test.nix -A exampleHaskellPackageWithIFD 135 | # error: cannot build '/nix/store/q5hq65ynrqnnnjs3kl1ggsx2pkwlw702-cabal2nix-example-cabal-library.drv' 136 | # during evaluation because the option 'allow-import-from-derivation' is disabled 137 | # ``` 138 | # 139 | # Now, you can go back and try building all the above packages with 140 | # `allow-import-from-derivation` disabled. You'll see that they all 141 | # still successfully build, because they are not using IFD. Instead, 142 | # the `.cabal` file is parsed using the Nix code produced from PureNix. 143 | (final: prev: { 144 | exampleHaskellPackageWithIFD = 145 | final.haskellPackages.callCabal2nix "example-cabal-library" ./example-cabal-library { }; 146 | }) 147 | ]; 148 | 149 | pkgs = import nixpkgs-src { inherit overlays; }; 150 | 151 | in 152 | 153 | pkgs 154 | -------------------------------------------------------------------------------- /purescript-parser-combinator/src/Parsec.purs: -------------------------------------------------------------------------------- 1 | module Parsec 2 | -- ( Parser, 3 | -- runParser, 4 | -- token, 5 | -- throwAt, 6 | -- ) 7 | where 8 | 9 | import Prelude 10 | 11 | import Control.Alt (class Alt, alt) 12 | import Control.Alternative (class Alternative, (<|>)) 13 | import Control.MonadPlus (class MonadPlus) 14 | import Control.Lazy (class Lazy) 15 | import Control.Plus (class Plus, empty) 16 | import Data.Array (foldr, many, notElem, replicate, some, (:)) 17 | import Data.Either (Either(..)) 18 | import Data.Maybe (Maybe(..)) 19 | import Data.Traversable (sequence) 20 | import Data.Tuple.Nested ((/\), type (/\)) 21 | import NixBuiltins (charToStr, concatChars, stringLength, substring, unsafeStrToChar) 22 | 23 | data Err' e = Err Int e 24 | 25 | instance semigroupErr :: Semigroup e => Semigroup (Err' e) where 26 | append (Err il el) (Err ir er) = case compare il ir of 27 | LT -> Err ir er 28 | EQ -> Err ir (append el er) 29 | GT -> Err il el 30 | 31 | instance monoidErr :: Monoid e => Monoid (Err' e) where 32 | mempty = Err 0 mempty 33 | 34 | type Err = Err' (Array String) 35 | 36 | -- | A simple backtracking parser. 37 | -- Maintains the error message of whatever branch managed to consume the most input. 38 | newtype Parser a = Parser 39 | ( forall b. 40 | String -> 41 | Int -> -- Offset 42 | Err -> -- error set 43 | (a -> Int -> Err -> b) -> -- Success continuation 44 | (Err -> b) -> -- Error continuation 45 | b 46 | ) 47 | 48 | unParser 49 | :: forall b a 50 | . Parser a -> 51 | String -> 52 | Int -> -- Offset 53 | Err -> -- error set 54 | (a -> Int -> Err -> b) -> -- Success continuation 55 | (Err -> b) -> -- Error continuation 56 | b 57 | unParser (Parser p) = p 58 | 59 | runParser :: 60 | forall a. 61 | String -> 62 | Parser a -> 63 | Either (Int /\ Array String) (Int /\ a) 64 | runParser s (Parser p) = 65 | p 66 | s 67 | 0 68 | mempty 69 | (\a i _ -> Right (i /\ a)) 70 | (\(Err i e) -> Left (i /\ e)) 71 | 72 | instance functorParser :: Functor Parser where 73 | map f (Parser p) = Parser \s i e ok err -> 74 | p s i e (ok <<< f) err 75 | 76 | instance applyParser :: Apply Parser where 77 | apply :: forall a b. Parser (a -> b) -> Parser a -> Parser b 78 | apply (Parser pf) (Parser pa) = Parser \t i e ok ng -> 79 | pf t i e (\f i' e' -> pa t i' e' (ok <<< f) ng) ng 80 | 81 | instance applicativeParser :: Applicative Parser where 82 | pure a = Parser \_ i e ok _ -> ok a i e 83 | 84 | instance bindParser :: Bind Parser where 85 | bind :: forall a b. Parser a -> (a -> Parser b) -> Parser b 86 | bind (Parser k) f = Parser \t i e ok ng -> 87 | k t i e (\a i' e' -> unParser (f a) t i' e' ok ng) ng 88 | 89 | instance monadParser :: Monad Parser 90 | 91 | instance altParser :: Alt Parser where 92 | alt (Parser pl) (Parser pr) = Parser \t i e ok ng -> 93 | pl t i e ok \e' -> pr t i e' ok ng 94 | 95 | instance plusParser :: Plus Parser where 96 | empty = Parser \_ _ e _ ng -> ng e 97 | 98 | instance alternativeParser :: Alternative Parser 99 | 100 | instance monadPlusParser :: MonadPlus Parser 101 | 102 | -- This isn't actually needed, since PureNix is already Lazy, but some things in 103 | -- the PureScript stdlib are using Lazy. 104 | instance lazyParser :: Lazy (Parser a) where 105 | defer :: (Unit -> Parser a) -> Parser a 106 | defer f = Parser \t i e ok ng -> unParser (f unit) t i e ok ng 107 | 108 | ---------------- 109 | -- Primitives -- 110 | ---------------- 111 | 112 | chars :: Int -> Parser String 113 | chars n = Parser \str i err ok ng -> 114 | let endOffset = i + n 115 | in 116 | if stringLength str >= endOffset then 117 | ok (substring i n str) endOffset err 118 | else 119 | let errMsg = "not enough characters left in Parser to parse " <> show n <> " chars" 120 | in 121 | ng (err <> Err i [errMsg]) 122 | 123 | eof :: Parser Unit 124 | eof = Parser \str i err ok ng -> 125 | if i >= stringLength str then 126 | ok unit i err 127 | else 128 | ng (err <> Err i ["not at eof"]) 129 | 130 | -- | Enter a context with a function to throw an error at the start of the context. 131 | -- | A simple motivating example is `expect`: 132 | -- | 133 | -- | ```purescript 134 | -- | expect :: forall a. Error -> (String -> Maybe a) -> Parser a 135 | -- | expect err f = 136 | -- | P.throwAt \throw -> 137 | -- | P.token >>= (maybe (throw err) pure <<< f) 138 | -- | ``` 139 | -- | 140 | -- | In this example, we first consume a token, and then see if it matches our 141 | -- | expectation. However since consuming a token advanced the parser past the 142 | -- | token, simply throwing an error in place reports the error after the token. 143 | -- | 144 | -- | By having `throwAt` be the only way to throw errors, it's always clear and 145 | -- | explicit where you are reporting an error. 146 | throwAt 147 | :: forall r 148 | . ((forall a. String -> Parser a) -> Parser r) 149 | -> Parser r 150 | throwAt k = Parser \str i err ok ng -> 151 | let throw' :: forall x. String -> Parser x 152 | throw' e = Parser \_ _ e' _ ng' -> ng' (e' <> Err i [e]) 153 | in unParser (k throw') str i err ok ng 154 | 155 | ----------------- 156 | -- Combinators -- 157 | ----------------- 158 | 159 | char :: Char -> Parser Unit 160 | char c = 161 | void $ 162 | satisfyNote 163 | (_ == c) 164 | (\parsedChar -> 165 | "expected character '" <> charToStr c <> 166 | "', but got '" <> charToStr parsedChar <> "'" 167 | ) 168 | 169 | notChar :: Char -> Parser Char 170 | notChar c = 171 | satisfyNote 172 | (_ /= c) 173 | (\parsedChar -> 174 | "expected any character but '" <> charToStr c <> 175 | "', but got '" <> charToStr parsedChar 176 | ) 177 | 178 | notChars :: Array Char -> Parser Char 179 | notChars cs = 180 | satisfyNote 181 | (\c -> notElem c cs) 182 | (\parsedChar -> 183 | "expected any character but '" <> show cs <> 184 | "', but got '" <> charToStr parsedChar 185 | ) 186 | 187 | satisfyNote :: (Char -> Boolean) -> (Char -> String) -> Parser Char 188 | satisfyNote f errMsg = 189 | throwAt \throw -> do 190 | parsedChar <- map unsafeStrToChar (chars 1) 191 | if f parsedChar 192 | then pure parsedChar 193 | else throw (errMsg parsedChar) 194 | 195 | isAlphaNum :: Char -> Boolean 196 | isAlphaNum = 197 | case _ of 198 | 'A' -> true 199 | 'B' -> true 200 | 'C' -> true 201 | 'D' -> true 202 | 'E' -> true 203 | 'F' -> true 204 | 'G' -> true 205 | 'H' -> true 206 | 'I' -> true 207 | 'J' -> true 208 | 'K' -> true 209 | 'L' -> true 210 | 'M' -> true 211 | 'N' -> true 212 | 'O' -> true 213 | 'P' -> true 214 | 'Q' -> true 215 | 'R' -> true 216 | 'S' -> true 217 | 'T' -> true 218 | 'U' -> true 219 | 'V' -> true 220 | 'W' -> true 221 | 'X' -> true 222 | 'Y' -> true 223 | 'Z' -> true 224 | 'a' -> true 225 | 'b' -> true 226 | 'c' -> true 227 | 'd' -> true 228 | 'e' -> true 229 | 'f' -> true 230 | 'g' -> true 231 | 'h' -> true 232 | 'i' -> true 233 | 'j' -> true 234 | 'k' -> true 235 | 'l' -> true 236 | 'm' -> true 237 | 'n' -> true 238 | 'o' -> true 239 | 'p' -> true 240 | 'q' -> true 241 | 'r' -> true 242 | 's' -> true 243 | 't' -> true 244 | 'u' -> true 245 | 'v' -> true 246 | 'w' -> true 247 | 'x' -> true 248 | 'y' -> true 249 | 'z' -> true 250 | '0' -> true 251 | '1' -> true 252 | '2' -> true 253 | '3' -> true 254 | '4' -> true 255 | '5' -> true 256 | '6' -> true 257 | '7' -> true 258 | '8' -> true 259 | '9' -> true 260 | _ -> false 261 | 262 | alphaNum :: Parser Char 263 | alphaNum = 264 | satisfyNote 265 | isAlphaNum 266 | (\c -> 267 | "Trying to parse A-Z or a-z or 0-9, but got character '" <> charToStr c <> "'" 268 | ) 269 | 270 | alphaNums :: Parser String 271 | alphaNums = map concatChars (some alphaNum) 272 | 273 | space :: Parser Unit 274 | space = char ' ' <|> char '\t' 275 | 276 | spaces :: Parser Unit 277 | spaces = void $ some space 278 | 279 | count :: forall a. Int -> Parser a -> Parser (Array a) 280 | count i p = sequence $ replicate i p 281 | 282 | string :: String -> Parser String 283 | string s = 284 | throwAt \throw -> do 285 | parsedChars <- chars (stringLength s) 286 | if s == parsedChars 287 | then pure s 288 | else throw $ "expected string '" <> s <> "', but got string '" <> parsedChars <> "'" 289 | 290 | oneOf :: forall a. Array (Parser a) -> Parser a 291 | oneOf parsers = foldr alt empty parsers 292 | 293 | sepBy1 :: forall a sep. Parser a -> Parser sep -> Parser (Array a) 294 | sepBy1 p sep = do 295 | fst <- p 296 | rest <- many (sep *> p) 297 | pure (fst : rest) 298 | 299 | optional :: forall a. Parser a -> Parser (Maybe a) 300 | optional v = map Just v <|> pure Nothing 301 | -------------------------------------------------------------------------------- /purescript-cabal-parser/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Control.Alt ((<|>)) 6 | import Data.Array (findMap, many, some, (:)) 7 | import Data.Either (Either(Left, Right)) 8 | import Data.Generic.Rep (class Generic) 9 | import Data.Maybe (Maybe(Just, Nothing)) 10 | import Data.Show.Generic (genericShow) 11 | import Data.Tuple.Nested ((/\), type (/\)) 12 | import NixBuiltins (AttrSet, Derivation, Path, abort, appendPath, concatChars, getAttr, getAttrFromPath, readFile, toAttrSet, trace, (!.), (//!)) 13 | import Parsec (Parser, alphaNums, char, count, eof, notChar, notChars, oneOf, optional, runParser, sepBy1, space, string) 14 | import Unsafe.Coerce (unsafeCoerce) 15 | 16 | -- This is useful for "type-driven design", but shouldn't be used in an actual 17 | -- function. 18 | -- undefined :: forall a. a 19 | -- undefined = unsafeCoerce unit 20 | 21 | -- | This represents an `executable` section in a Cabal file. 22 | type Executable = 23 | { name :: String 24 | , buildDepends :: Array String 25 | } 26 | 27 | -- | A datatype representing the `license` in a Cabal file. 28 | -- | This should be expanded to cover more licenses. 29 | data License = LicenseBSD3 30 | 31 | derive instance genericLicense :: Generic License _ 32 | 33 | instance showLicense :: Show License where 34 | show = genericShow 35 | 36 | -- | This represents a parsed Cabal file. Note that for now 37 | -- | we only parse the fields that are required for the Nix derivation. 38 | -- | This includes things like `buildDepends`, but not things like 39 | -- | `default-language`. 40 | type CabalFile = 41 | { name :: String 42 | , version :: String 43 | , license :: License 44 | , executable :: Executable 45 | } 46 | 47 | type IndentAmount = Int 48 | 49 | -- | This represents a raw property in a `.cabal` file. 50 | -- | For instance, Cabal files have a `name` property. 51 | -- | This `name` property would be created as a `SimpleRawProp`: 52 | -- | 53 | -- | ```purescript 54 | -- | SimpleRawProp "name" "example-cabal-library" 55 | -- | ``` 56 | -- | 57 | -- | A `RecursiveRawProp` is used for something like an `executable` section. 58 | data RawProp = SimpleRawProp String String | RecursiveRawProp String String (Array RawProp) 59 | 60 | derive instance genericRawProp :: Generic RawProp _ 61 | 62 | instance showRawProp :: Show RawProp where 63 | show rawProp = genericShow rawProp 64 | 65 | -- | A `RawCabalFile` is an array of `RawProp`s. 66 | data RawCabalFile = RawCabalFile (Array RawProp) 67 | 68 | derive instance genericRawCabalFile :: Generic RawCabalFile _ 69 | 70 | instance showRawCabalFile :: Show RawCabalFile where 71 | show = genericShow 72 | 73 | parseRawPropKey :: Parser String 74 | parseRawPropKey = map concatChars (some (notChar ':')) 75 | 76 | parseRawPropVal :: Parser String 77 | parseRawPropVal = do 78 | val <- map concatChars (some (notChar '\n')) 79 | char '\n' 80 | pure val 81 | 82 | parseSimpleRawProp :: Parser RawProp 83 | parseSimpleRawProp = do 84 | key <- parseRawPropKey 85 | char ':' 86 | void $ some space 87 | val <- parseRawPropVal 88 | pure $ SimpleRawProp key val 89 | 90 | parseRecursiveRawPropKey :: Parser String 91 | parseRecursiveRawPropKey = alphaNums 92 | 93 | parseRecursiveRawProp :: IndentAmount -> Parser RawProp 94 | parseRecursiveRawProp indentAmount = do 95 | key <- parseRecursiveRawPropKey 96 | void $ some space 97 | val <- parseRawPropVal 98 | recursiveProps <- parseRawProps (indentAmount + 2) 99 | pure $ RecursiveRawProp key val recursiveProps 100 | 101 | parseRawProp :: IndentAmount -> Parser RawProp 102 | parseRawProp indentAmount = do 103 | void $ many (char '\n') 104 | void $ count indentAmount space 105 | parseRecursiveRawProp indentAmount <|> parseSimpleRawProp 106 | 107 | parseRawProps :: IndentAmount -> Parser (Array RawProp) 108 | parseRawProps indentAmount = some (parseRawProp indentAmount) 109 | 110 | parseRawCabalFile :: Parser RawCabalFile 111 | parseRawCabalFile = do 112 | res <- map RawCabalFile (parseRawProps 0) 113 | eof 114 | pure res 115 | 116 | -- | This is the Nix `Path` of the `example-cabal-library.cabal` file. 117 | -- | This is convenient for being able to embed things like `rawCabalFileStr`, 118 | -- | but this should really be taken as an argument everywhere that needs it. 119 | foreign import cabalFilePath :: Path 120 | 121 | rawCabalFileStr :: String 122 | rawCabalFileStr = readFile cabalFilePath 123 | 124 | licenseParser :: Parser License 125 | licenseParser = 126 | oneOf [ string "BSD-3-Clause" $> LicenseBSD3 ] 127 | 128 | buildDependParser :: Parser String 129 | buildDependParser = do 130 | libName <- alphaNums 131 | void $ many (notChars [',', '\n']) 132 | pure libName 133 | 134 | buildDependsParser :: Parser (Array String) 135 | buildDependsParser = 136 | sepBy1 buildDependParser (char ',' *> optional space) 137 | 138 | parsedRawCabalFile :: String -> Either (Int /\ Array String) (Int /\ RawCabalFile) 139 | parsedRawCabalFile rawCabalFileString = runParser rawCabalFileString parseRawCabalFile 140 | 141 | getSimpleProp :: String -> Array RawProp -> Either (Array String) String 142 | getSimpleProp key rawProps = 143 | case findMap pred rawProps of 144 | Nothing -> Left [ "could not find key " <> key ] 145 | Just val -> Right val 146 | where 147 | pred :: RawProp -> Maybe String 148 | pred = 149 | case _ of 150 | SimpleRawProp key' val | key' == key -> Just val 151 | _ -> Nothing 152 | 153 | getSimplePropParse 154 | :: forall a. Parser a -> String -> Array RawProp -> Either (Array String) a 155 | getSimplePropParse parser key rawProps = do 156 | rawVal <- getSimpleProp key rawProps 157 | case runParser rawVal parser of 158 | Left (_ /\ err) -> Left err 159 | Right (_ /\ parsedVal) -> Right parsedVal 160 | 161 | getRecursiveProp 162 | :: String -> Array RawProp -> Either (Array String) (String /\ Array RawProp) 163 | getRecursiveProp key rawProps = do 164 | case findMap pred rawProps of 165 | Nothing -> Left [ "could not find key " <> key ] 166 | Just r -> Right r 167 | where 168 | pred :: RawProp -> Maybe (String /\ Array RawProp) 169 | pred = 170 | case _ of 171 | RecursiveRawProp key' val inner | key' == key -> Just (val /\ inner) 172 | _ -> Nothing 173 | 174 | cabalFileFromRaw :: RawCabalFile -> Either (Array String) CabalFile 175 | cabalFileFromRaw (RawCabalFile rawProps) = do 176 | name <- getSimpleProp "name" rawProps 177 | version <- getSimpleProp "version" rawProps 178 | license <- getSimplePropParse licenseParser "license" rawProps 179 | executableName /\ executableProps <- getRecursiveProp "executable" rawProps 180 | buildDepends <- getSimplePropParse buildDependsParser "build-depends" executableProps 181 | pure 182 | { name 183 | , version 184 | , license 185 | , executable: 186 | { name: executableName 187 | , buildDepends 188 | } 189 | } 190 | 191 | -- | Parse a raw `.cabal` file into a `CabalFile`. This function takes a 192 | -- | `String` of the text of a `.cabal` file. 193 | -- | 194 | -- | This function starts by parsing the raw `.cabal` file into key-val mapping 195 | -- | `RawCabalFile`, and then parses all the required fields from this 196 | -- | `RawCabalFile` into a `CabalFile`. 197 | cabalParser :: String -> Either (Array String) CabalFile 198 | cabalParser rawCabalFileString = do 199 | case parsedRawCabalFile rawCabalFileString of 200 | Left (_ /\ err) -> Left err 201 | Right (_ /\ rawCabalFile) -> cabalFileFromRaw rawCabalFile 202 | 203 | licenseToAttrPath :: License -> Array String 204 | licenseToAttrPath = 205 | case _ of 206 | LicenseBSD3 -> ["lib", "licenses", "bsd3"] 207 | 208 | -- | This datatype represents a Nix function that takes an attribute set. 209 | -- | 210 | -- | For instance, imagine the following Nix function: 211 | -- | 212 | -- | ```nix 213 | -- | { a, b }: a + b 214 | -- | ``` 215 | -- | 216 | -- | This could be constructed from PureScript with `mkFunctionWithArgs`: 217 | -- | 218 | -- | ```purescript 219 | -- | mkFunctionWithArgs ["a", "b"] (\args -> (args !. "a") + (args !. "b")) 220 | -- | ``` 221 | foreign import data FunctionWithArgs :: Type 222 | 223 | foreign import haskellPackagePath :: Path 224 | 225 | -- | See the docs on `FunctionWithArgs`. 226 | foreign import mkFunctionWithArgs 227 | :: forall a. Array String -> (AttrSet -> a) -> FunctionWithArgs 228 | 229 | -- | Take an input `CabalFile` and convert it to a Nix function that is 230 | -- | compatible with `callPackage`. 231 | -- | 232 | -- | The Nix function this produces will look similar to the following: 233 | -- | 234 | -- | ```nix 235 | -- | { mkDerivation, aeson, base, lib }: 236 | -- | mkDerivation { 237 | -- | pname = "example-cabal-library"; 238 | -- | version = "0.1.0.0"; 239 | -- | src = src; 240 | -- | isLibrary = false; 241 | -- | isExecutable = true; 242 | -- | executableHaskellDepends = [ aeson base ]; 243 | -- | license = lib.licenses.bsd3; 244 | -- | } 245 | -- | ``` 246 | cabalFileToPkgDef :: CabalFile -> Path -> FunctionWithArgs 247 | cabalFileToPkgDef { name, version, license, executable } src = 248 | mkFunctionWithArgs 249 | ("mkDerivation" : "lib" : executable.buildDepends) 250 | \args -> 251 | (getAttr "mkDerivation" args) 252 | { pname: name 253 | , version 254 | , src 255 | , isLibrary: false 256 | , isExecutable: true 257 | , executableHaskellDepends: map (\buildDepend -> getAttr buildDepend args) executable.buildDepends 258 | , license: getAttrFromPath (licenseToAttrPath license) args 259 | } 260 | 261 | 262 | -- | Take a raw Cabal file as String, parse it, and turn it into a `FunctionWithArgs`. 263 | -- | Call `cabalFileToPkgDef` with the `CabalFile` produced by 264 | -- | `cabalParser`. Throw an error if `cabalParser` returns `Left`. 265 | -- | 266 | -- | Check the documentation on `cabalFileToPkgDef` to see what 267 | -- | the `FunctionWithArgs` this produces looks like. 268 | -- | 269 | -- | This can be built in the Nix repl like the following: 270 | -- | 271 | -- | ```nix 272 | -- | nix-repl> haskellPackages.callPackage (rawCabalFileToPkgDef rawCabalFileStr haskellPackagePath) {} 273 | -- | «derivation /nix/store/p8ws0vwn26rhnhrqvjqavykbigm1wvla-example-cabal-library-0.1.0.0.drv» 274 | -- | ``` 275 | -- | 276 | -- | This is assuming you're in a Nix REPL with everything from Nixpkgs 277 | -- | available. 278 | rawCabalFileToPkgDef :: String -> Path -> FunctionWithArgs 279 | rawCabalFileToPkgDef rawCabalFileString packageSrc = 280 | case cabalParser rawCabalFileString of 281 | Left err -> abort $ "Could not parse cabal file: " <> show err 282 | Right cabalFile -> 283 | -- trace (show cabalFile) $ 284 | cabalFileToPkgDef cabalFile packageSrc 285 | 286 | -- | Take Nixpkgs as an argument and produce a `Derivation` 287 | -- | for `rawCabalFileToPkgDef`. 288 | -- | 289 | -- | The derivation this produces is similar to the derivation produced from 290 | -- | `haskellPackages.callPackage (rawCabalFileToPkgDef rawCabalFileStr haskellPackagePath) {}`. 291 | -- | 292 | -- | ```nix 293 | -- | nix-repl> examplePackage pkgs 294 | -- | «derivation /nix/store/p8ws0vwn26rhnhrqvjqavykbigm1wvla-example-cabal-library-0.1.0.0.drv» 295 | -- | ``` 296 | -- | 297 | -- | This is assuming you're in a Nix REPL with nixpkgs available as `pkgs`. 298 | examplePackage :: AttrSet -> Derivation 299 | examplePackage nixpkgs = 300 | (nixpkgs !. "haskellPackages" !. "callPackage") 301 | (rawCabalFileToPkgDef rawCabalFileStr haskellPackagePath) 302 | {} 303 | 304 | -- | A normal Nixpkgs overlay that adds a top-level derivation called 305 | -- | `exampleHaskellPackage`. 306 | -- | 307 | -- | This `exampleHaskellPackage` can be used like the following. 308 | -- | Check the `../../test.nix` file for another example of how this is used: 309 | -- | 310 | -- | ```nix 311 | -- | nix-repl> nixpkgs = import nixpkgs-src { overlays = [ (import ./output/Main).exampleNixpkgsOverlay ]; } 312 | -- | nix-repl> nixpkgs.exampleHaskellPackage 313 | -- | «derivation /nix/store/p8ws0vwn26rhnhrqvjqavykbigm1wvla-example-cabal-library-0.1.0.0.drv» 314 | -- | ``` 315 | -- | 316 | -- | This is assuming that `nixpkgs-src` is a `Path` to Nixpkgs`, or has been 317 | -- | retrieved with something like `builtins.fetchTarball`. 318 | -- | 319 | -- | `exampleNixpkgsOverlay` is a simple example of an overlay. It is similar 320 | -- | a Nixpkgs overlay that looks like the following: 321 | -- | 322 | -- | ```nix 323 | -- | final: _prev: { 324 | -- | exampleHaskellPackage = examplePackage final; 325 | -- | } 326 | -- | ``` 327 | exampleNixpkgsOverlay :: AttrSet -> AttrSet -> { exampleHaskellPackage :: Derivation } 328 | exampleNixpkgsOverlay final _prev = 329 | { exampleHaskellPackage: examplePackage final 330 | } 331 | 332 | -- | This is a Nixpkgs overlay that updates the top-level `haskell` attribute 333 | -- | in order to add a new Haskell package, as well as the `callCabal2nixWithoutIFD` 334 | -- | function. 335 | -- | 336 | -- | Here is how you could actually use this overlay. 337 | -- | 338 | -- | ```nix 339 | -- | nix-repl> nixpkgs = import nixpkgs-src { overlays = [ (import ./output/Main).exampleNixpkgsHaskellOverlay ]; } 340 | -- | nix-repl> nixpkgs.haskellPackages.exampleHaskellPackage 341 | -- | «derivation /nix/store/p8ws0vwn26rhnhrqvjqavykbigm1wvla-example-cabal-library-0.1.0.0.drv» 342 | -- | ``` 343 | -- | 344 | -- | See the docs on the `exampleNixpkgsOverlay` for more information. 345 | -- | 346 | -- | This overlay is basically the same as the following Nix code: 347 | -- | 348 | -- | ```nix 349 | -- | final: prev: { 350 | -- | haskell = prev.haskell // { 351 | -- | packageOverrides = hfinal: hprev: 352 | -- | prev.haskell.packageOverrides hfinal hprev // { 353 | -- | exampleHaskellPackage = 354 | -- | hfinal.callCabal2nixWithoutIFD "example-cabal-library" haskellPackagePath { }; 355 | -- | callCabal2nixWithoutIFD = callCabal2nixWithoutIFD hfinal.callPackage 356 | -- | }; 357 | -- | }; 358 | -- | } 359 | -- | ``` 360 | exampleNixpkgsHaskellOverlay :: AttrSet -> AttrSet -> AttrSet 361 | exampleNixpkgsHaskellOverlay _final prev = 362 | toAttrSet 363 | { haskell: 364 | (prev !. "haskell") //! 365 | { packageOverrides: \hfinal hprev -> 366 | (prev !. "haskell" !. "packageOverrides") hfinal hprev //! 367 | { exampleHaskellPackage: 368 | (hfinal !. "callCabal2nixWithoutIFD") 369 | "example-cabal-library" 370 | haskellPackagePath 371 | {} 372 | , callCabal2nixWithoutIFD: 373 | callCabal2nixWithoutIFD (hfinal !. "callPackage") 374 | } 375 | } 376 | } 377 | 378 | -- | This is just like the Nix function `callCabal2nix`, but uses all the 379 | -- | machinery defined in this file. 380 | -- | 381 | -- | The first argument is the Nix function `haskellPackages.callPackage`, which 382 | -- | is used internally. 383 | callCabal2nixWithoutIFD 384 | :: (FunctionWithArgs -> AttrSet -> Derivation) 385 | -> String -> Path -> AttrSet -> Derivation 386 | callCabal2nixWithoutIFD haskellCallPackage pkgName src overrides = 387 | let (cabalFilePath :: Path) = appendPath src ("/" <> pkgName <> ".cabal") 388 | (rawCabalFileString :: String) = readFile cabalFilePath 389 | (haskellPkgFunc :: FunctionWithArgs) = rawCabalFileToPkgDef rawCabalFileString src 390 | in 391 | haskellCallPackage haskellPkgFunc overrides 392 | 393 | -- | This is a Row type that corresponds to the previous Haskell 394 | -- | packages overlay (`hprev`) in the above Nix code. 395 | -- | 396 | -- | The only thing we need to pull out of `hprev` is `callPackage`, 397 | -- | but this overlay contains other things, so this is an 398 | -- | extensible row type. 399 | type PrevHaskellPackagesRow a = 400 | ( callPackage :: FunctionWithArgs -> AttrSet -> Derivation 401 | | a 402 | ) 403 | 404 | -- | Turn `PrevHaskellPackagesRow` into a `Record`. 405 | type PrevHaskellPackages s = Record (PrevHaskellPackagesRow s) 406 | 407 | -- | This is the final Haskell packages overlay. It corresponds 408 | -- | to `hfinal` in the above Nix code. 409 | -- | 410 | -- | This should contain everything in `PrevHaskellPackagesRow`, 411 | -- | plus the function `callCabal2nixWithoutIFDTyped`, plus 412 | -- | `exampleHaskellPackageType`. 413 | -- | 414 | -- | Note that there may be more overlays on top of this, so 415 | -- | in general when typing an overly, `PrevHaskellPackagesRow` 416 | -- | should get a separate type variable from `FinalHaskellPackagesRow`. 417 | type FinalHaskellPackagesRow b = 418 | ( callCabal2nixWithoutIFDTyped 419 | :: String -> Path -> AttrSet -> Derivation 420 | , exampleHaskellPackageTyped :: Derivation 421 | | PrevHaskellPackagesRow b 422 | ) 423 | 424 | -- | Turn `FinalHaskellPackagesRow` into a `Record`. 425 | type FinalHaskellPackages s = Record (FinalHaskellPackagesRow s) 426 | 427 | -- | The previous Nixpkgs overlay. 428 | -- | 429 | -- | Note that the only thing we pull out of the top-level is the 430 | -- | `haskell` attribute (and `packageOverrides` inside that), so 431 | -- | it is the only thing we have to type here. 432 | -- | 433 | -- | The `r` parameter represents all the additional packages that 434 | -- | live in the Nixpkgs top-level. We don't directly use any of 435 | -- | them here, but `r` represents their existence. 436 | -- | 437 | -- | The `h` parameter represents all the additional attributes that 438 | -- | live in the `haskell` attrset. The only thing we use from this set 439 | -- | is `packageOverrides`, but `h` represents the existence of all 440 | -- | the other attributes. 441 | -- | 442 | -- | `s` and `t` represent all the Haskell packages and other functions 443 | -- | that live in a Haskell package set (like Nixpkgs `haskellPackages`). 444 | -- | Note that `s` and `t` are different because the final Haskell package 445 | -- | set may have additional packages that the previous package set doesn't 446 | -- | have. See the docs for `FinalHaskellPackagesRow` and `PrevHaskellPackagesRow`. 447 | type PrevOverlayRow r h s t = 448 | ( haskell :: 449 | { packageOverrides 450 | :: FinalHaskellPackages s 451 | -> PrevHaskellPackages t 452 | -> FinalHaskellPackages s 453 | | h 454 | } 455 | | r 456 | ) 457 | 458 | -- | Turn `PrevOverlayRow` into a `Record`. 459 | type PrevOverlay r h s t = 460 | Record (PrevOverlayRow r h s t) 461 | 462 | -- | This is similar to `PrevOverlay` but due to how Nixpkgs 463 | -- | overlays work, we are allowed to return an attrset with 464 | -- | only a single attribute, and only that single attribute 465 | -- | will be updated. 466 | type ResultOverlay h s t = 467 | Record (PrevOverlayRow () h s t) 468 | 469 | -- | This is a Nixpkgs overlay that updates the top-level `haskell` attribute 470 | -- | in order to add a new Haskell package, as well as the 471 | -- | `callCabal2nixWithoutIFDTyped` function. This is the exact same as 472 | -- | `exampleNixpkgsHaskellOverlay`, but fully typed. 473 | -- | 474 | -- | Here is how you could actually use this overlay. 475 | -- | 476 | -- | ```nix 477 | -- | nix-repl> nixpkgs = import nixpkgs-src { overlays = [ (import ./output/Main).exampleNixpkgsHaskellOverlayTyped ]; } 478 | -- | nix-repl> nixpkgs.haskellPackages.exampleHaskellPackageTyped 479 | -- | «derivation /nix/store/p8ws0vwn26rhnhrqvjqavykbigm1wvla-example-cabal-library-0.1.0.0.drv» 480 | -- | ``` 481 | -- | 482 | -- | See the docs on the `exampleNixpkgsOverlay` for more information. 483 | -- | 484 | -- | This overlay is basically the same as the following Nix code: 485 | -- | 486 | -- | ```nix 487 | -- | final: prev: { 488 | -- | haskell = prev.haskell // { 489 | -- | packageOverrides = hfinal: hprev: 490 | -- | prev.haskell.packageOverrides hfinal hprev // { 491 | -- | exampleHaskellPackageTyped = 492 | -- | hfinal.callCabal2nixWithoutIFD 493 | -- | "example-cabal-library" 494 | -- | haskellPackagePath 495 | -- | { }; 496 | -- | callCabal2nixWithoutIFDTyped = callCabal2nixWithoutIFD hfinal.callPackage 497 | -- | }; 498 | -- | }; 499 | -- | } 500 | -- | ``` 501 | -- | 502 | -- | Adding types to this function is quite annoying and tricky to figure out, 503 | -- | but the resulting PureScript code looks almost identical to the untyped 504 | -- | Nix code. 505 | exampleNixpkgsHaskellOverlayTyped 506 | :: forall p r h s t 507 | . Record p 508 | -> PrevOverlay r h s t 509 | -> ResultOverlay h s t 510 | exampleNixpkgsHaskellOverlayTyped _final prev = 511 | { haskell: 512 | prev.haskell 513 | { packageOverrides = \hfinal hprev -> 514 | (prev.haskell.packageOverrides hfinal hprev) 515 | { exampleHaskellPackageTyped = 516 | hfinal.callCabal2nixWithoutIFDTyped 517 | "example-cabal-library" 518 | haskellPackagePath 519 | (toAttrSet {}) 520 | , callCabal2nixWithoutIFDTyped = 521 | callCabal2nixWithoutIFD hfinal.callPackage 522 | } 523 | } 524 | } 525 | --------------------------------------------------------------------------------