Skip to content

Commit

Permalink
Drop snapshot download-from-remote codepath
Browse files Browse the repository at this point in the history
The new one from parsing elm.json should be sufficient.

Also, add the ability to customise the paths for elm.json and
registry.dat. Inspired by me trying to test using `cabal run`, and not
knowing how to do that outside the repo working directory (where my
elm.json is).
  • Loading branch information
bmillwood committed Sep 22, 2024
1 parent 63e4b3e commit 9c488c2
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 75 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Convert an [Elm](http://elm-lang.org/) project into
It consists of multiple commands:
- `elm2nix convert`: Given `elm.json` in current directory, all dependencies are
parsed and their sha256sum calculated
- `elm2nix snapshot`: Downloads snapshot of https://package.elm-lang.org/all-packages json and converts into binary `registry.dat` used by [elm-compiler](https://github.com/elm/compiler/blob/047d5026fe6547c842db65f7196fed3f0b4743ee/builder/src/Stuff.hs#L147) as a cache
- `elm2nix snapshot`: Reads packages from `elm.json` and writes them to binary cache file `registry.dat` used by [elm-compiler](https://github.com/elm/compiler/blob/047d5026fe6547c842db65f7196fed3f0b4743ee/builder/src/Stuff.hs#L147).
- `elm2nix init`: Generates `default.nix` that glues everything together

## Assumptions
Expand Down
6 changes: 0 additions & 6 deletions elm2nix.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,8 @@ library
, bytestring
, binary
, containers
, data-default
, directory
, filepath
, here
, mtl
, process
, req
, text
, transformers
, unordered-containers
Expand All @@ -68,7 +63,6 @@ executable elm2nix
build-depends:
base >= 4.7 && < 5
, elm2nix
, directory
, optparse-applicative
, here
, ansi-wl-pprint
Expand Down
14 changes: 10 additions & 4 deletions elm2nix/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import qualified Text.PrettyPrint.ANSI.Leijen as PP
data Command
= Init
| Convert
| Snapshot { fromElmJson :: Bool }
| Snapshot { elmJson :: FilePath, writeTo :: FilePath }

main :: IO ()
main = do
Expand All @@ -26,7 +26,7 @@ main = do
case cmd of
Convert -> Elm2Nix.convert
Init -> Elm2Nix.initialize
Snapshot { fromElmJson } -> Elm2Nix.snapshot fromElmJson
Snapshot { elmJson, writeTo } -> Elm2Nix.snapshot elmJson writeTo

getOpts :: IO Command
getOpts = customExecParser p (infoH opts rest)
Expand All @@ -45,14 +45,20 @@ getOpts = customExecParser p (infoH opts rest)

$ elm2nix init > default.nix
$ elm2nix convert > elm-srcs.nix
$ elm2nix snapshot --from-elm-json
$ elm2nix snapshot
$ nix-build

Note: You have to run elm2nix from top-level directory of an Elm project.
|]

snapshotOpts :: Parser Command
snapshotOpts = Snapshot <$> switch (long "from-elm-json")
snapshotOpts =
Snapshot
<$> (arg "elm-json" <|> pure "elm.json")
<*> (arg "write-to" <|> pure "registry.dat")
where
arg name = strOption $
long name <> metavar "FILENAME" <> completer (bashCompleter "file")

opts :: Parser Command
opts = subparser
Expand Down
5 changes: 2 additions & 3 deletions src/Elm2Nix.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE CPP #-}

module Elm2Nix
( convert
Expand All @@ -20,7 +19,7 @@ import System.IO (hPutStrLn, stderr)

import qualified Data.Text as Text

import Elm2Nix.ElmJson (Dep, Elm2NixError(..), readElmJson, toErrorMessage)
import Elm2Nix.ElmJson (Elm2NixError(..), readElmJson, toErrorMessage)
import Elm2Nix.FixedOutput (FixedDerivation(..), prefetch)
import Elm2Nix.PackagesSnapshot (snapshot)

Expand All @@ -40,7 +39,7 @@ convert :: IO ()
convert = runCLI $ do
liftIO (hPutStrLn stderr "Resolving elm.json dependencies into Nix ...")

deps <- either throwErr return =<< liftIO readElmJson
deps <- either throwErr return =<< liftIO (readElmJson "elm.json")
liftIO (hPutStrLn stderr "Prefetching tarballs and computing sha256 hashes ...")

sources <- liftIO (mapConcurrently (uncurry prefetch) deps)
Expand Down
6 changes: 3 additions & 3 deletions src/Elm2Nix/ElmJson.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ toErrorMessage err =
ElmJsonReadError s -> "Error reading json: " ++ s
KeyNotFound key -> "Key not found in json: " ++ Text.unpack key

readElmJson :: IO (Either Elm2NixError [Dep])
readElmJson = do
res <- Json.eitherDecode <$> LBS.readFile "elm.json"
readElmJson :: FilePath -> IO (Either Elm2NixError [Dep])
readElmJson path = do
res <- Json.eitherDecode <$> LBS.readFile path
pure $
either
(Left . ElmJsonReadError)
Expand Down
66 changes: 8 additions & 58 deletions src/Elm2Nix/PackagesSnapshot.hs
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
{- Downloads binary serialized https://package.elm-lang.org/all-packages
as Elm compiler expects it to parse.
{- Writes a binary serialized package registry for the Elm compiler to consume.
Takes Elm upstream code from:
- https://github.com/elm/compiler/blob/master/builder/src/Deps/Cache.hs
- https://github.com/elm/compiler/blob/master/builder/src/Deps/Website.hs
- https://github.com/elm/compiler/blob/master/builder/src/Deps/Registry.hs
- https://github.com/elm/compiler/blob/master/compiler/src/Elm/Package.hs
-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-}
module Elm2Nix.PackagesSnapshot
( snapshot
) where

import Control.Monad (liftM2, liftM3)
import qualified Data.Aeson as Aeson
import qualified Data.Binary as Binary
import Data.Binary (Binary, put, get, putWord8, getWord8)
import Data.Binary.Put (putBuilder)
import Data.Binary.Get.Internal (readN)
import qualified Data.Map as Map
#if MIN_VERSION_req(2,0,0)
#else
import Data.Default (def)
#endif
import Data.Map (Map)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Data.Word (Word16)
import qualified Network.HTTP.Req as Req
import qualified Data.List as List
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BS
Expand Down Expand Up @@ -147,48 +137,23 @@ instance Binary Registry where
get = liftM2 Registry get get
put (Registry a b) = put a >> put b

#if MIN_VERSION_req(2,0,0)
defHttpConfig = Req.defaultHttpConfig
#else
defHttpConfig = def
#endif

getFromElmJson :: IO Packages
getFromElmJson = do
deps <- either (error . toErrorMessage) id <$> readElmJson
snapshot :: FilePath -> FilePath -> IO ()
snapshot elmJson writeTo = do
deps <- either (error . toErrorMessage) id <$> readElmJson elmJson
let
parseDep (k, v) = do
name <- parseName (Text.pack k)
version <- parseVersion (Text.pack v)
pure (name, KnownVersions version [])
Packages . Map.fromList <$> mapM parseDep deps

getFromPackageServer :: IO Packages
getFromPackageServer = do
r <- Req.runReq defHttpConfig $
Req.req
Req.POST
(Req.https "package.elm-lang.org" Req./: "all-packages")
Req.NoReqBody
Req.jsonResponse
mempty
case Aeson.fromJSON (Req.responseBody r) of
Aeson.Error s -> error s
Aeson.Success val -> pure val

snapshot :: Bool -> IO ()
snapshot fromElmJson = do
packages <- unwrap <$> if fromElmJson then getFromElmJson else getFromPackageServer
pure (name, [version])
packages <- toKnownVersions . Map.fromListWith (<>) <$> mapM parseDep deps
let size = Map.foldr' addEntry 0 packages
registry = Registry size packages

addEntry :: KnownVersions -> Int -> Int
addEntry (KnownVersions _ vs) count =
count + 1 + length vs

Binary.encodeFile "registry.dat" registry

newtype Packages = Packages { unwrap :: Map.Map Name KnownVersions }
Binary.encodeFile writeTo registry

toKnownVersions :: Map.Map Name [Version] -> Map.Map Name KnownVersions
toKnownVersions =
Expand All @@ -197,18 +162,3 @@ toKnownVersions =
v:vs -> KnownVersions v vs
[] -> undefined
)

instance Aeson.FromJSON Packages where
parseJSON v = Packages <$> fmap toKnownVersions (Aeson.parseJSON v)


instance Aeson.FromJSON Version where
parseJSON = Aeson.withText "string" parseVersion


instance Aeson.FromJSON Name where
parseJSON = Aeson.withText "string" parseName


instance Aeson.FromJSONKey Name where
fromJSONKey = Aeson.FromJSONKeyTextParser parseName

0 comments on commit 9c488c2

Please sign in to comment.