Skip to content

Commit 555d57e

Browse files
authored
Use nix instead of json in output of hackage-to-nix (#118)
We have been using `readFile` and `fromJSON` to read a `.json` a 60MB .json file. This change puts the information contained in the json file into `.nix` files that are imported when needed (rather than all at once). This seems to save around 1s at eval time. Tested with: ``` time nix-instantiate -E '(import ./. {}).pkgs-unstable.haskell-nix.tool "ghc8107" "hello" {}' time nix-instantiate -E '(import ./. {}).pkgs-unstable.haskell-nix.tool "ghc8107" "haskell-language-server" {}' ``` See also #1574
1 parent 8a754bd commit 555d57e

File tree

1 file changed

+52
-34
lines changed

1 file changed

+52
-34
lines changed

hackage2nix/Main.hs

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
{-# LANGUAGE OverloadedStrings #-}
22
{-# LANGUAGE NamedFieldPuns #-}
3+
{-# LANGUAGE LambdaCase #-}
34

45
module Main where
56

67
import Cabal2Nix
8+
import Cabal2Nix.Util ( quoted )
79
import Control.Applicative ( liftA2 )
810
import Control.Monad.Trans.State.Strict
911
import Crypto.Hash.SHA256 ( hash )
10-
import Data.Aeson
11-
import Data.Aeson.Types ( Pair )
12-
import Data.Aeson.Encode.Pretty
1312
import qualified Data.ByteString.Base16 as Base16
1413
import qualified Data.ByteString.Char8 as BS
15-
import qualified Data.ByteString.Lazy as BL
1614
import Data.Foldable ( toList
1715
, for_
1816
)
@@ -25,6 +23,7 @@ import qualified Data.Sequence as Seq
2523
import Data.String ( IsString(fromString)
2624
)
2725
import Data.Text ( Text )
26+
import qualified Data.Text as T ( pack )
2827
import Data.Text.Encoding ( decodeUtf8 )
2928
import Distribution.Hackage.DB ( hackageTarball )
3029
import qualified Distribution.Hackage.DB.Parsed
@@ -36,13 +35,30 @@ import Distribution.Pretty ( prettyShow
3635
)
3736
import Distribution.Types.PackageName ( PackageName )
3837
import Distribution.Types.Version ( Version )
38+
import Nix ( (@@)
39+
, mkSym
40+
, mkInt
41+
, mkStr
42+
, NExpr
43+
, ($=)
44+
, mkNonRecSet
45+
)
3946
import Nix.Pretty ( prettyNix )
4047
import System.Directory ( createDirectoryIfMissing
4148
)
4249
import System.Environment ( getArgs )
4350
import System.FilePath ( (</>)
4451
, (<.>)
4552
)
53+
import Data.Char (isUpper)
54+
55+
-- Avoid issues with case insensitive file systems by escaping upper case
56+
-- characters with a leading _ character.
57+
escapeUpperCase :: String -> String
58+
escapeUpperCase = (>>= (\case
59+
'_' -> "__"
60+
c | isUpper c -> ['_', c]
61+
| otherwise -> [c]))
4662

4763
main :: IO ()
4864
main = do
@@ -53,23 +69,25 @@ main = do
5369

5470
db <- U.readTarball Nothing inp
5571

56-
let (defaultJson, cabalFiles) =
57-
runState (fmap (object . toList . (Seq.sortOn fst)) $ foldMapWithKeyA package2json db) mempty
72+
let (nixFiles, cabalFiles) =
73+
runState (fmap (toList . (Seq.sortOn fst)) $ foldMapWithKeyA package2nix db) mempty
5874
createDirectoryIfMissing False out
59-
BL.writeFile (out </> "default.nix") $
75+
writeFile (out </> "default.nix") $
6076
"with builtins; mapAttrs (_: mapAttrs (_: data: rec {\n\
6177
\ inherit (data) sha256;\n\
62-
\ revisions = (mapAttrs (rev: rdata: {\n\
63-
\ inherit (rdata) revNum sha256;\n\
64-
\ outPath = ./. + \"/hackage/${rdata.outPath}\";\n\
65-
\ }) data.revisions) // {\n\
78+
\ revisions = data.revisions // {\n\
6679
\ default = revisions.\"${data.revisions.default}\";\n\
6780
\ };\n\
68-
\})) (fromJSON (readFile ./hackage.json))\n"
81+
\})) {\n"
82+
-- Import all the per package nix files
83+
<> mconcat (map (\(pname, _) ->
84+
" " <> quoted pname <> " = import ./nix/" <> escapeUpperCase pname <> ".nix;\n") nixFiles)
85+
<> "}\n"
86+
87+
createDirectoryIfMissing False (out </> "nix")
88+
for_ nixFiles $ \(pname, nix) ->
89+
writeFile (out </> "nix" </> escapeUpperCase pname <.> "nix") $ show $ prettyNix nix
6990

70-
BL.writeFile (out </> "hackage.json") $ encodePretty'
71-
(defConfig {confCompare = compare, confIndent = Spaces 1})
72-
defaultJson
7391
createDirectoryIfMissing False (out </> "hackage")
7492

7593
for_ cabalFiles $ \(cabalFile, pname, path) -> do
@@ -93,32 +111,32 @@ foldMapWithKeyA f =
93111
fromPretty :: (Pretty a, IsString b) => a -> b
94112
fromPretty = fromString . prettyShow
95113

96-
package2json :: PackageName -> U.PackageData -> GPDWriter (Seq Pair)
97-
package2json pname (U.PackageData { U.versions }) = do
98-
versionBindings <- foldMapWithKeyA (version2json pname) versions
99-
return $ Seq.singleton $ fromPretty pname .= (object . toList $ Seq.sortOn fst $ versionBindings)
114+
package2nix :: PackageName -> U.PackageData -> GPDWriter (Seq (String, NExpr))
115+
package2nix pname (U.PackageData { U.versions }) = do
116+
versionBindings <- foldMapWithKeyA (version2nix pname) versions
117+
return $ Seq.singleton (fromPretty pname, (mkNonRecSet . map (uncurry ($=)) . toList $ Seq.sortOn fst $ versionBindings))
100118

101-
version2json
102-
:: PackageName -> Version -> U.VersionData -> GPDWriter (Seq (Pair))
103-
version2json pname vnum (U.VersionData { U.cabalFileRevisions, U.metaFile }) =
119+
version2nix
120+
:: PackageName -> Version -> U.VersionData -> GPDWriter (Seq (Text, NExpr))
121+
version2nix pname vnum (U.VersionData { U.cabalFileRevisions, U.metaFile }) =
104122
do
105123
revisionBindings <- sequenceA
106124
$ zipWith (revBindingJson pname vnum) cabalFileRevisions [0 ..]
107125
let hash = decodeUtf8 $ fromString $ P.parseMetaData pname vnum metaFile Map.! "sha256"
108-
return $ Seq.singleton $ fromPretty vnum .= object
109-
[ "sha256" .= hash
110-
, "revisions" .= object
111-
( revisionBindings
112-
++ ["default" .= fst (last revisionBindings)]
126+
return $ Seq.singleton (quoted (fromPretty vnum), mkNonRecSet
127+
[ "sha256" $= mkStr hash
128+
, "revisions" $= mkNonRecSet
129+
( map (uncurry ($=)) revisionBindings
130+
++ ["default" $= mkStr (fst (last revisionBindings))]
113131
)
114-
]
132+
])
115133

116134
revBindingJson
117135
:: PackageName
118136
-> Version
119137
-> BS.ByteString
120138
-> Integer
121-
-> GPDWriter (Key, Value)
139+
-> GPDWriter (Text, NExpr)
122140
revBindingJson pname vnum cabalFile revNum = do
123141
let qualifiedName = mconcat $ intersperse
124142
"-"
@@ -130,8 +148,8 @@ revBindingJson pname vnum cabalFile revNum = do
130148
cabalHash = Base16.encode $ hash cabalFile
131149
modify' $ mappend $ Seq.singleton
132150
(cabalFile, prettyPname ++ ".cabal", revPath)
133-
return $ revName .= object
134-
[ "outPath" .= (qualifiedName <> ".nix")
135-
, "revNum" .= revNum
136-
, "sha256" .= decodeUtf8 cabalHash
137-
]
151+
return (revName, mkNonRecSet
152+
[ "nix" $= mkSym "import" @@ mkSym (T.pack ("../hackage/" <> qualifiedName <> ".nix"))
153+
, "revNum" $= mkInt revNum
154+
, "sha256" $= mkStr (decodeUtf8 cabalHash)
155+
])

0 commit comments

Comments
 (0)