Merge pull request #255 from haskell-nix/srk/daemon

cereal remote, server side integration
This commit is contained in:
Richard Marko 2023-11-30 08:37:45 +01:00 committed by GitHub
commit 70eb0d35fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2806 additions and 680 deletions

View File

@ -1,6 +1,6 @@
let haskellCi =
https://raw.githubusercontent.com/sorki/github-actions-dhall/main/haskell-ci.dhall
sha256:5d7058a7684fd5315467b562853bd1c4a43da691c09293d3715ee739dfa26e08
sha256:a39801f73d93c6e0f91942755ef8ae4c50947e9a9b180b6724957229470f7b8d
let defSteps = haskellCi.defaultCabalSteps

View File

@ -59,7 +59,6 @@ jobs:
- "macos-latest"
name: Haskell CI
'on':
pull_request: {}
push: {}
schedule:
- cron: "4 20 10 * *"

View File

@ -20,4 +20,4 @@ package hnix-store-nar
flags: +bounded_memory
package hnix-store-remote
flags: +build-readme +io-testsuite
flags: +build-derivation +build-readme +io-testsuite

View File

@ -6,6 +6,7 @@ module System.Nix.Base
import Data.ByteString (ByteString)
import Data.Text (Text)
import GHC.Generics (Generic)
import qualified Data.Text.Encoding
import qualified Data.ByteString.Base16
@ -21,6 +22,7 @@ data BaseEncoding
-- & NixBase seems be the most widely used in Nix.
| Base16
| Base64
deriving (Bounded, Eq, Enum, Generic, Ord, Show)
-- | Encode @ByteString@ with @Base@ encoding, produce @Text@.
encodeWith :: BaseEncoding -> ByteString -> Text

View File

@ -15,25 +15,28 @@ import GHC.Generics (Generic)
-- keep the order of these Enums to match enums from reference implementations
-- src/libstore/store-api.hh
data BuildMode = Normal | Repair | Check
data BuildMode
= BuildMode_Normal
| BuildMode_Repair
| BuildMode_Check
deriving (Eq, Generic, Ord, Enum, Show)
data BuildStatus =
Built
| Substituted
| AlreadyValid
| PermanentFailure
| InputRejected
| OutputRejected
| TransientFailure -- possibly transient
| CachedFailure -- no longer used
| TimedOut
| MiscFailure
| DependencyFailed
| LogLimitExceeded
| NotDeterministic
| ResolvesToAlreadyValid
| NoSubstituters
BuildStatus_Built
| BuildStatus_Substituted
| BuildStatus_AlreadyValid
| BuildStatus_PermanentFailure
| BuildStatus_InputRejected
| BuildStatus_OutputRejected
| BuildStatus_TransientFailure -- possibly transient
| BuildStatus_CachedFailure -- no longer used
| BuildStatus_TimedOut
| BuildStatus_MiscFailure
| BuildStatus_DependencyFailed
| BuildStatus_LogLimitExceeded
| BuildStatus_NotDeterministic
| BuildStatus_ResolvesToAlreadyValid
| BuildStatus_NoSubstituters
deriving (Eq, Generic, Ord, Enum, Show)
-- | Result of the build
@ -55,4 +58,8 @@ data BuildResult = BuildResult
buildSuccess :: BuildResult -> Bool
buildSuccess BuildResult {..} =
status `elem` [Built, Substituted, AlreadyValid]
status `elem`
[ BuildStatus_Built
, BuildStatus_Substituted
, BuildStatus_AlreadyValid
]

View File

@ -167,7 +167,7 @@ decodeDigestWith b x =
let
toEither =
maybeToRight
("Cryptonite was not able to convert '(ByteString -> Digest a)' for: '" <> show bs <>"'.")
("Crypton was not able to convert '(ByteString -> Digest a)' for: '" <> show bs <>"'.")
(toEither . Crypto.Hash.digestFromByteString) bs
where
-- To not depend on @extra@

View File

@ -34,6 +34,7 @@ module System.Nix.StorePath
, pathParser
-- * Utilities for tests
, unsafeMakeStorePath
, unsafeMakeStorePathHashPart
) where
import Crypto.Hash (HashAlgorithm)
@ -307,3 +308,11 @@ unsafeMakeStorePath
-> StorePathName
-> StorePath
unsafeMakeStorePath = StorePath
-- | Path hash parts rarely need to be constructed directly.
-- Prefer @mkStorePathHashPart@
-- Used by remote store in wire protocol
unsafeMakeStorePathHashPart
:: ByteString
-> StorePathHashPart
unsafeMakeStorePathHashPart = StorePathHashPart

View File

@ -0,0 +1,41 @@
{-# LANGUAGE LambdaCase #-}
module Main where
import Data.Default.Class (Default(def))
import Data.Text (Text)
import System.Nix.Derivation (Derivation)
import System.Nix.StorePath (StorePath)
import qualified Data.Text
import qualified Data.Text.IO
import qualified Data.Attoparsec.Text
import qualified System.Environment
import qualified System.Nix.Build
import qualified System.Nix.Derivation
import qualified System.Nix.StorePath
import qualified System.Nix.Store.Remote
parseDerivation :: FilePath -> IO (Derivation StorePath Text)
parseDerivation source = do
contents <- Data.Text.IO.readFile source
case Data.Attoparsec.Text.parseOnly
(System.Nix.Derivation.parseDerivation def) contents of
Left e -> error e
Right drv -> pure drv
main :: IO ()
main = System.Environment.getArgs >>= \case
[filename] -> do
case System.Nix.StorePath.parsePathFromText def (Data.Text.pack filename) of
Left e -> error $ show e
Right p -> do
d <- parseDerivation filename
out <-
System.Nix.Store.Remote.runStore
$ System.Nix.Store.Remote.buildDerivation
p
d
System.Nix.Build.BuildMode_Normal
print out
_ -> error "No input derivation file"

View File

@ -26,14 +26,17 @@ common commons
, DeriveFoldable
, DeriveTraversable
, DeriveLift
, DerivingVia
, FlexibleContexts
, FlexibleInstances
, GADTs
, RecordWildCards
, ScopedTypeVariables
, StandaloneDeriving
, TypeApplications
, TypeSynonymInstances
, InstanceSigs
, KindSignatures
, MultiParamTypeClasses
, TupleSections
, LambdaCase
@ -53,6 +56,12 @@ flag io-testsuite
Enable testsuite, which requires external
binaries and Linux namespace support.
flag build-derivation
default:
False
description:
Build build-derivation executable
flag build-readme
default:
False
@ -62,37 +71,68 @@ flag build-readme
library
import: commons
exposed-modules:
System.Nix.Store.Remote
, System.Nix.Store.Remote.Binary
Data.Serializer
, Data.Serializer.Example
, System.Nix.Store.Remote
, System.Nix.Store.Remote.Arbitrary
, System.Nix.Store.Remote.Logger
, System.Nix.Store.Remote.MonadStore
, System.Nix.Store.Remote.Protocol
, System.Nix.Store.Remote.Serialize
, System.Nix.Store.Remote.Serialize.Prim
, System.Nix.Store.Remote.Logger
, System.Nix.Store.Remote.Protocol
, System.Nix.Store.Remote.Serializer
, System.Nix.Store.Remote.Socket
, System.Nix.Store.Remote.Types
, System.Nix.Store.Remote.Util
, System.Nix.Store.Remote.Types.Activity
, System.Nix.Store.Remote.Types.CheckMode
, System.Nix.Store.Remote.Types.GC
, System.Nix.Store.Remote.Types.Logger
, System.Nix.Store.Remote.Types.ProtoVersion
, System.Nix.Store.Remote.Types.StoreConfig
, System.Nix.Store.Remote.Types.SubstituteMode
, System.Nix.Store.Remote.Types.Verbosity
, System.Nix.Store.Remote.Types.WorkerOp
build-depends:
base >=4.12 && <5
, hnix-store-core >= 0.8 && <0.9
, hnix-store-nar >= 0.1
, attoparsec
, binary
, bytestring
, cereal
, containers
, crypton
, data-default-class
, dependent-sum > 0.7 && < 1
, generic-arbitrary < 1.1
, hashable
, text
, time
, transformers
, network
, mtl
, QuickCheck
, quickcheck-instances
, unordered-containers
, transformers
, vector
hs-source-dirs: src
ghc-options: -Wall
executable build-derivation
if !flag(build-derivation)
buildable: False
build-depends:
base >=4.12 && <5
, attoparsec
, hnix-store-core
, hnix-store-remote
, data-default-class
, text
default-language: Haskell2010
main-is: BuildDerivation.hs
hs-source-dirs: app
ghc-options: -Wall -threaded -rtsopts "-with-rtsopts -N"
executable remote-readme
if !flag(build-readme)
buildable: False
@ -110,8 +150,10 @@ test-suite remote
type: exitcode-stdio-1.0
main-is: Driver.hs
hs-source-dirs: tests
ghc-options: -Wall
ghc-options: -Wall -threaded -rtsopts "-with-rtsopts -N"
other-modules:
Data.SerializerSpec
NixSerializerSpec
SerializeSpec
build-tool-depends:
hspec-discover:hspec-discover
@ -121,6 +163,9 @@ test-suite remote
, hnix-store-remote
, hnix-store-tests
, cereal
, crypton
, dependent-sum > 0.7 && < 1
, some > 1.0.5 && < 2
, text
, time
, hspec
@ -153,14 +198,14 @@ test-suite remote-io
, containers
, crypton
, directory
, process
, filepath
, hspec-expectations-lifted
, text
, tasty
, hspec
, tasty-hspec
, hspec-expectations-lifted
, linux-namespaces
, process
, tasty
, tasty-hspec
, temporary
, text
, unix
, unordered-containers

View File

@ -0,0 +1,212 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-|
Description : Serializer data type
Copyright : (c) John Ericson, 2023
Richard Marko, 2023
Stability : experimental
@Serializer@ ties @Get@ and @PutM@ monads
into a single datatype and allows
transforming both monads with a monad transformer
for adding extra layers like @ExceptT@
(for example when @putS@ can fail due to unsupported
version of a protocol) or @ReaderT@ (when we need
to serialize a data type based differently based
on a protocol version).
See "Data.Serializer.Example"
-}
module Data.Serializer
(
-- * Serializer
Serializer(..)
-- ** Runners
, runGetS
, runPutS
-- * Simple serializer
, SimpleSerializer
-- ** Simple runners
, runGetSimple
, runPutSimple
-- * From Get/Put, Serialize
, lift2
, liftSerialize
-- * Combinators
, mapIsoSerializer
, mapPrismSerializer
, tup
-- * Utility
, GetSerializerError(..)
, transformGetError
, transformPutError
-- * Re-exports
, Get
, PutM
) where
#if !MIN_VERSION_base(4,18,0)
import Control.Applicative (liftA2)
#endif
import Control.Monad.Except (MonadError, throwError)
import Control.Monad.Trans (lift)
import Control.Monad.Trans (MonadTrans)
import Control.Monad.Trans.Identity (IdentityT, runIdentityT)
import Data.ByteString (ByteString)
import Data.Serialize (Serialize)
import Data.Serialize.Get (Get, runGet)
import Data.Serialize.Put (Putter, PutM, runPutM)
import qualified Data.Serialize
-- * Serializer
-- | @Serializer@ ties @Get@ and @PutM@ monads
-- into a single datatype and allows
-- transforming the monads with a monad transformer
-- for e.g. adding @ExceptT@ or @ReaderT@ layers.
data Serializer t a = Serializer
{ getS :: t Get a
, putS :: a -> t PutM ()
}
-- ** Runners
-- | Runner for putS of @Serializer@
runPutS
:: ( Monad (t PutM)
, MonadTrans t
)
=> Serializer t a -- ^ Serializer
-> (t PutM () -> PutM b) -- ^ Tranformer runner
-> a -- ^ Value to (out)put
-> (b, ByteString)
runPutS s run a = runPutM $ run $ (putS s) a
-- | Runner for getS of @Serializer@
runGetS
:: ( Monad (t Get)
, MonadTrans t
)
=> Serializer t a -- ^ Serializer
-> (t Get a -> Get b) -- ^ Tranformer runner
-> ByteString -- ^ ByteString to parse
-> Either String b
runGetS s run b = runGet (run (getS s)) b
-- * Simple serializer
-- | Simple @Serializer@
type SimpleSerializer a = Serializer IdentityT a
-- ** Simple runners
-- | Runner for getS of @SimpleSerializer@
runGetSimple
:: SimpleSerializer a
-> ByteString
-> Either String a
runGetSimple s b =
runGetS s (runIdentityT) b
-- | Runner for putS of @SimpleSerializer@
runPutSimple
:: SimpleSerializer a
-> a
-> ByteString
runPutSimple s =
snd
. runPutS s runIdentityT
-- * From Get/Put, Serialize
-- | Lift @Get a@ and @Putter a@ into @Serializer@
lift2
:: forall a t
. MonadTrans t
=> Get a
-> Putter a
-> Serializer t a
lift2 f g = Serializer
{ getS = lift f
, putS = lift . g
}
-- | Lift @Serialize a@ instance into @Serializer@
liftSerialize
:: ( Serialize a
, MonadTrans t
)
=> Serializer t a
liftSerialize =
lift2
Data.Serialize.get
Data.Serialize.put
-- * Combinators
-- | Map over @Serializer@
mapIsoSerializer
:: Functor (t Get)
=> (a -> b) -- ^ Map over @getS@
-> (b -> a) -- ^ Map over @putS@
-> (Serializer t a -> Serializer t b)
mapIsoSerializer f g s = Serializer
{ getS = f <$> getS s
, putS = putS s . g
}
-- | Map over @Serializer@ where @getS@
-- can return @Either@
mapPrismSerializer
:: MonadError eGet (t Get)
=> (a -> Either eGet b) -- ^ Map over @getS@
-> (b -> a) -- ^ Map over @putS@
-> (Serializer t a -> Serializer t b)
mapPrismSerializer f g s = Serializer
{ getS = either throwError pure . f =<< getS s
, putS = putS s . g
}
-- | Tuple combinator
tup
:: ( Applicative (t Get)
, Monad (t PutM)
)
=> Serializer t a
-> Serializer t b
-> Serializer t (a, b)
tup a b = Serializer
{ getS = liftA2 (,) (getS a) (getS b)
, putS = \(x, y) -> do
putS a x
putS b y
}
-- * Utilities
-- | Wrapper for both GetS errors
--
-- * the one that occurs when @fail@ is called
-- * custom one when @ExceptT@ is used
data GetSerializerError customGetError
= SerializerError_GetFail String
| SerializerError_Get customGetError
deriving (Eq, Show)
-- | Helper for transforming nested Eithers
-- into @GetSerializerError@ wrapper
transformGetError
:: Either String (Either customGetError b)
-> Either (GetSerializerError customGetError) b
transformGetError = \case
Left stringyRunGetError -> Left (SerializerError_GetFail stringyRunGetError)
Right (Left myGetError) -> Left (SerializerError_Get myGetError)
Right (Right res) -> Right res
-- | Helper for transforming @runPutM@ result
transformPutError
:: (Either customPutError (), ByteString)
-> Either customPutError ByteString
transformPutError (e, r) = either Left (pure $ Right r) e

View File

@ -0,0 +1,276 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.Serializer.Example
(
-- * Simple protocol
OpCode(..)
, Cmd(..)
-- * Cmd Serializer
, cmdS
-- * Runners
, runG
, runP
-- * Custom errors
, MyGetError(..)
, MyPutError(..)
-- ** Erroring variants of cmdS
-- *** putS with throwError and MyPutError
, cmdSPutError
-- *** getS with throwError and MyGetError
, cmdSGetError
-- *** getS with fail
, cmdSGetFail
-- *** putS with fail
, cmdSPutFail
-- * Elaborate
, cmdSRest
, runGRest
, runPRest
) where
import Control.Monad.Except (MonadError, throwError)
import Control.Monad.Reader (MonadReader, ask)
import Control.Monad.State (MonadState)
import Control.Monad.Trans (MonadTrans, lift)
import Control.Monad.Trans.Reader (ReaderT, runReaderT)
import Control.Monad.Trans.Except (ExceptT, runExceptT)
import Control.Monad.Trans.State (StateT, runStateT)
import Data.Bifunctor (first, second)
import Data.ByteString (ByteString)
import Data.Int (Int8)
import Data.GADT.Show (GShow(..), defaultGshowsPrec)
import Data.Kind (Type)
import Data.Type.Equality
import Data.Serialize.Get (getInt8)
import Data.Serialize.Put (putInt8)
import Data.Serializer
import Data.Some (Some(..))
import GHC.Generics
import System.Nix.Store.Remote.Serialize.Prim (getBool, putBool, getEnum, putEnum)
import Test.QuickCheck (Arbitrary(..), oneof)
-- * Simple protocol
-- | OpCode used to differentiate between operations
data OpCode = OpCode_Int | OpCode_Bool
deriving (Bounded, Eq, Enum, Generic, Ord, Show)
-- | Protocol operations
data Cmd :: Type -> Type where
Cmd_Int :: Int8 -> Cmd Int8
Cmd_Bool :: Bool -> Cmd Bool
deriving instance Eq (Cmd a)
deriving instance Show (Cmd a)
instance GShow Cmd where
gshowsPrec = defaultGshowsPrec
instance TestEquality Cmd where
testEquality (Cmd_Int _) (Cmd_Int _) = Just Refl
testEquality (Cmd_Bool _) (Cmd_Bool _) = Just Refl
testEquality _ _ = Nothing
-- constructors only
-- import Data.GADT.Compare
-- instance GEq Cmd where
-- geq = testEquality
instance {-# OVERLAPPING #-} Eq (Some Cmd) where
Some (Cmd_Int a) == Some (Cmd_Int b) = a == b
Some (Cmd_Bool a) == Some (Cmd_Bool b) = a == b
_ == _ = False
instance Arbitrary (Some Cmd) where
arbitrary = oneof
[ Some . Cmd_Int <$> arbitrary
, Some . Cmd_Bool <$> arbitrary
]
-- | @OpCode@ @Serializer@
opcode :: MonadTrans t => Serializer t OpCode
opcode = Serializer
{ getS = lift getEnum
, putS = lift . putEnum
}
-- * Cmd Serializer
-- | @Cmd@ @Serializer@
cmdS
:: forall t . ( MonadTrans t
, Monad (t Get)
, Monad (t PutM)
)
=> Serializer t (Some Cmd)
cmdS = Serializer
{ getS = getS opcode >>= \case
OpCode_Int -> Some . Cmd_Int <$> lift getInt8
OpCode_Bool -> Some . Cmd_Bool <$> lift getBool
, putS = \case
Some (Cmd_Int i) -> putS opcode OpCode_Int >> lift (putInt8 i)
Some (Cmd_Bool b) -> putS opcode OpCode_Bool >> lift (putBool b)
}
-- * Runners
-- | @runGetS@ specialized to @ExceptT e@
runG
:: Serializer (ExceptT e) a
-> ByteString
-> Either (GetSerializerError e) a
runG s =
transformGetError
. runGetS s runExceptT
-- | @runPutS@ specialized to @ExceptT e@
runP
:: Serializer (ExceptT e) a
-> a
-> Either e ByteString
runP s =
(\(e, r) -> either Left (pure $ Right r) e)
. runPutS s runExceptT
-- * Custom errors
data MyGetError
= MyGetError_Example
deriving (Eq, Show)
data MyPutError
= MyPutError_NoLongerSupported -- no longer supported protocol version
deriving (Eq, Show)
-- ** Erroring variants of cmdS
-- *** putS with throwError and MyPutError
cmdSPutError :: Serializer (ExceptT MyPutError) (Some Cmd)
cmdSPutError = Serializer
{ getS = getS cmdS
, putS = \case
Some (Cmd_Int i) -> putS opcode OpCode_Int >> lift (putInt8 i)
Some (Cmd_Bool _b) -> throwError MyPutError_NoLongerSupported
}
-- *** getS with throwError and MyGetError
cmdSGetError :: Serializer (ExceptT MyGetError) (Some Cmd)
cmdSGetError = Serializer
{ getS = getS opcode >>= \case
OpCode_Int -> Some . Cmd_Int <$> lift getInt8
OpCode_Bool -> throwError MyGetError_Example
, putS = putS cmdS
}
-- *** getS with fail
cmdSGetFail
:: ( MonadTrans t
, MonadFail (t Get)
, Monad (t PutM)
)
=> Serializer t (Some Cmd)
cmdSGetFail = Serializer
{ getS = getS opcode >>= \case
OpCode_Int -> Some . Cmd_Int <$> lift getInt8
OpCode_Bool -> fail "no parse"
, putS = putS cmdS
}
-- *** putS with fail
-- | Unused as PutM doesn't have @MonadFail@
-- >>> serializerPutFail = cmdPutFail @(ExceptT MyGetError)
-- No instance for (MonadFail PutM)
-- as expected
cmdSPutFail
:: ( MonadTrans t
, MonadFail (t PutM)
, Monad (t Get)
)
=> Serializer t (Some Cmd)
cmdSPutFail = Serializer
{ getS = getS cmdS
, putS = \case
Some (Cmd_Int i) -> putS opcode OpCode_Int >> lift (putInt8 i)
Some (Cmd_Bool _b) -> fail "can't"
}
-- * Elaborate
-- | Transformer for @Serializer@
newtype REST r e s m a = REST
{ _unREST :: ExceptT e (StateT s (ReaderT r m)) a }
deriving
( Applicative
, Functor
, Monad
, MonadError e
, MonadReader r
, MonadState s
, MonadFail
)
instance MonadTrans (REST r e s) where
lift = REST . lift . lift . lift
-- | Runner for @REST@
restRunner
:: Monad m
=> r
-> s
-> REST r e s m a
-> m ((Either e a), s)
restRunner r s =
(`runReaderT` r)
. (`runStateT` s)
. runExceptT
. _unREST
runGRest
:: Serializer (REST r e s) a
-> r
-> s
-> ByteString
-> Either (GetSerializerError e) a
runGRest serializer r s =
transformGetError
. second fst
. runGetS
serializer
(restRunner r s)
runPRest
:: Serializer (REST r e s) a
-> r
-> s
-> a
-> Either e ByteString
runPRest serializer r s =
transformPutError
. first fst
. runPutS
serializer
(restRunner r s)
cmdSRest
:: Serializer (REST Bool e Int) (Some Cmd)
cmdSRest = Serializer
{ getS = getS opcode >>= \case
OpCode_Int -> do
isTrue <- ask
if isTrue
then Some . Cmd_Int . (+1) <$> lift getInt8
else Some . Cmd_Int <$> lift getInt8
OpCode_Bool -> Some . Cmd_Bool <$> lift getBool
, putS = \case
Some (Cmd_Int i) -> do
putS opcode OpCode_Int
isTrue <- ask
if isTrue
then lift (putInt8 (i - 1))
else lift (putInt8 i)
Some (Cmd_Bool b) -> putS opcode OpCode_Bool >> lift (putBool b)
}

View File

@ -10,6 +10,7 @@ module System.Nix.Store.Remote
, addTempRoot
, buildPaths
, buildDerivation
, deleteSpecific
, ensurePath
, findRoots
, isValidPathUncached
@ -28,54 +29,46 @@ module System.Nix.Store.Remote
, syncWithGC
, verifyStore
, module System.Nix.Store.Types
, module System.Nix.Store.Remote.MonadStore
, module System.Nix.Store.Remote.Types
) where
import Crypto.Hash (SHA256)
import Data.ByteString (ByteString)
import Data.Dependent.Sum (DSum((:=>)))
import Data.HashSet (HashSet)
import Data.Map (Map)
import Data.Text (Text)
import Data.Word (Word64)
import System.Nix.Nar (NarSource)
import System.Nix.Derivation (Derivation)
import System.Nix.Store.Types (FileIngestionMethod(..), RepairMode(..))
import System.Nix.Build (BuildMode, BuildResult)
import System.Nix.Hash (NamedAlgo(..), BaseEncoding(Base16), decodeDigestWith)
import System.Nix.StorePath (StorePath, StorePathName, StorePathHashPart, InvalidPathError)
import System.Nix.StorePath.Metadata (Metadata(..), StorePathTrust(..))
import qualified Data.Text
import qualified Control.Monad
import qualified Data.Attoparsec.Text
import qualified Data.Text.Encoding
import qualified System.Nix.Hash
--
import qualified Data.ByteString.Lazy as BSL
import System.Nix.Derivation (Derivation)
import System.Nix.Store.Types (FileIngestionMethod(..), RepairMode(..))
import System.Nix.Build ( BuildMode
, BuildResult
)
import System.Nix.Hash ( NamedAlgo(..)
, BaseEncoding(Base16)
, decodeDigestWith
)
import System.Nix.StorePath ( StorePath
, StorePathName
, StorePathHashPart
, InvalidPathError
)
import System.Nix.StorePath.Metadata ( Metadata(..)
, StorePathTrust(..)
)
import qualified Data.Binary.Put
import qualified Data.Map.Strict
import qualified Data.Serialize.Put
import qualified Data.Set
import qualified System.Nix.ContentAddress
import qualified System.Nix.Hash
import qualified System.Nix.Signature
import qualified System.Nix.StorePath
import System.Nix.Store.Remote.Binary
import System.Nix.Store.Remote.Types
import System.Nix.Store.Remote.Protocol
import System.Nix.Store.Remote.Util
import qualified System.Nix.Signature
import Crypto.Hash ( SHA256 )
import System.Nix.Nar ( NarSource )
import System.Nix.Store.Remote.MonadStore
import System.Nix.Store.Remote.Protocol
import System.Nix.Store.Remote.Socket
import System.Nix.Store.Remote.Types
import Data.Serialize (get)
import System.Nix.Store.Remote.Serialize
import System.Nix.Store.Remote.Serialize.Prim
-- | Pack `Nar` and add it to the store.
addToStore
@ -91,7 +84,7 @@ addToStore name source recursive repair = do
$ error "repairing is not supported when building through the Nix daemon"
runOpArgsIO AddToStore $ \yield -> do
yield $ BSL.toStrict $ Data.Binary.Put.runPut $ do
yield $ Data.Serialize.Put.runPut $ do
putText $ System.Nix.StorePath.unStorePathName name
putBool
$ not
@ -124,7 +117,7 @@ addTextToStore name text references' repair = do
putPaths storeDir references'
sockGetPath
addSignatures :: StorePath -> [BSL.ByteString] -> MonadStore ()
addSignatures :: StorePath -> [ByteString] -> MonadStore ()
addSignatures p signatures = do
storeDir <- getStoreDir
Control.Monad.void $ simpleOpArgs AddSignatures $ do
@ -169,9 +162,30 @@ buildDerivation p drv buildMode = do
-- but without it protocol just hangs waiting for
-- more data. Needs investigation.
-- Intentionally the only warning that should pop-up.
putInt (0 :: Integer)
putInt (0 :: Int)
getSocketIncremental getBuildResult
getSocketIncremental get
-- | Delete store paths
deleteSpecific
:: HashSet StorePath -- ^ Paths to delete
-> MonadStore GCResult
deleteSpecific paths = do
storeDir <- getStoreDir
runOpArgs CollectGarbage $ do
putEnum GCAction_DeleteSpecific
putPaths storeDir paths
putBool False -- ignoreLiveness
putInt (maxBound :: Word64) -- maxFreedBytes
putInt (0::Int)
putInt (0::Int)
putInt (0::Int)
getSocketIncremental $ do
gcResult_deletedPaths <- getPathsOrFail storeDir
gcResult_bytesFreed <- getInt
-- TODO: who knows
_ :: Int <- getInt
pure GCResult{..}
ensurePath :: StorePath -> MonadStore ()
ensurePath pn = do
@ -179,7 +193,7 @@ ensurePath pn = do
Control.Monad.void $ simpleOpArgs EnsurePath $ putPath storeDir pn
-- | Find garbage collector roots.
findRoots :: MonadStore (Map BSL.ByteString StorePath)
findRoots :: MonadStore (Map ByteString StorePath)
findRoots = do
runOp FindRoots
sd <- getStoreDir
@ -187,7 +201,7 @@ findRoots = do
getSocketIncremental
$ getMany
$ (,)
<$> (BSL.fromStrict <$> getByteStringLen)
<$> getByteString
<*> getPath sd
r <- catRights res
@ -208,13 +222,13 @@ isValidPathUncached p = do
-- | Query valid paths from set, optionally try to use substitutes.
queryValidPaths
:: HashSet StorePath -- ^ Set of `StorePath`s to query
-> SubstituteFlag -- ^ Try substituting missing paths when `True`
-> SubstituteMode -- ^ Try substituting missing paths when `True`
-> MonadStore (HashSet StorePath)
queryValidPaths ps substitute = do
storeDir <- getStoreDir
runOpArgs QueryValidPaths $ do
putPaths storeDir ps
putBool (unSubstituteFlag substitute)
putBool $ substitute == SubstituteMode_DoSubstitute
sockGetPaths
queryAllValidPaths :: MonadStore (HashSet StorePath)
@ -253,8 +267,8 @@ queryPathInfoUncached path = do
narBytes <- Just <$> sockGetInt
ultimate <- sockGetBool
sigStrings <- fmap bsToText <$> sockGetStrings
caString <- bsToText <$> sockGetStr
sigStrings <- fmap Data.Text.Encoding.decodeUtf8 <$> sockGetStrings
caString <- Data.Text.Encoding.decodeUtf8 <$> sockGetStr
let
sigs = case
@ -335,7 +349,7 @@ syncWithGC :: MonadStore ()
syncWithGC = Control.Monad.void $ simpleOp SyncWithGC
-- returns True on errors
verifyStore :: CheckFlag -> RepairMode -> MonadStore Bool
verifyStore :: CheckMode -> RepairMode -> MonadStore Bool
verifyStore check repair = simpleOpArgs VerifyStore $ do
putBool $ unCheckFlag check
putBool $ check == CheckMode_DoCheck
putBool $ repair == RepairMode_DoRepair

View File

@ -0,0 +1,45 @@
-- due to recent generic-arbitrary
{-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module System.Nix.Store.Remote.Arbitrary where
import System.Nix.Store.Remote.Types
import Test.QuickCheck (Arbitrary(..))
import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..))
import Test.QuickCheck.Instances ()
deriving via GenericArbitrary ProtoVersion
instance Arbitrary ProtoVersion
-- * Logger
deriving via GenericArbitrary Activity
instance Arbitrary Activity
deriving via GenericArbitrary ActivityID
instance Arbitrary ActivityID
deriving via GenericArbitrary ActivityResult
instance Arbitrary ActivityResult
deriving via GenericArbitrary Field
instance Arbitrary Field
deriving via GenericArbitrary Trace
instance Arbitrary Trace
deriving via GenericArbitrary BasicError
instance Arbitrary BasicError
deriving via GenericArbitrary ErrorInfo
instance Arbitrary ErrorInfo
deriving via GenericArbitrary LoggerOpCode
instance Arbitrary LoggerOpCode
deriving via GenericArbitrary Logger
instance Arbitrary Logger
deriving via GenericArbitrary Verbosity
instance Arbitrary Verbosity

View File

@ -1,54 +0,0 @@
{-|
Description : Utilities for packing stuff
Maintainer : srk <srk@48.io>
|-}
module System.Nix.Store.Remote.Binary where
import Control.Monad
import Data.ByteString (ByteString)
import Data.Binary.Get
import Data.Binary.Put
import qualified Data.ByteString.Lazy as BSL
putInt :: Integral a => a -> Put
putInt = putWord64le . fromIntegral
getInt :: Integral a => Get a
getInt = fromIntegral <$> getWord64le
putMany :: Foldable t => (a -> Put) -> t a -> Put
putMany printer xs = do
putInt (length xs)
mapM_ printer xs
getMany :: Get a -> Get [a]
getMany parser = do
count <- getInt
replicateM count parser
-- length prefixed string packing with padding to 8 bytes
putByteStringLen :: BSL.ByteString -> Put
putByteStringLen x = do
putInt len
putLazyByteString x
when (len `mod` 8 /= 0) $ pad $ 8 - (len `mod` 8)
where
len :: Int
len = fromIntegral $ BSL.length x
pad count = replicateM_ count (putWord8 0)
putByteStrings :: Foldable t => t BSL.ByteString -> Put
putByteStrings = putMany putByteStringLen
getByteStringLen :: Get ByteString
getByteStringLen = do
len <- getInt
st <- getLazyByteString len
when (len `mod` 8 /= 0) $ do
pads <- unpad $ fromIntegral $ 8 - (len `mod` 8)
unless (all (== 0) pads) $ fail $ "No zeroes" <> show (st, len, pads)
pure $ BSL.toStrict st
where unpad x = replicateM x getWord8
getByteStrings :: Get [ByteString]
getByteStrings = getMany getByteStringLen

View File

@ -1,87 +1,67 @@
module System.Nix.Store.Remote.Logger
( Logger(..)
, Field(..)
, processOutput
( processOutput
) where
import Control.Monad.Except ( throwError )
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (asks)
import Control.Monad.State.Strict (get)
import Data.Binary.Get
import Network.Socket.ByteString ( recv )
import System.Nix.Store.Remote.Binary
import System.Nix.Store.Remote.Types
import System.Nix.Store.Remote.Util
import Control.Monad.Except (throwError)
import Data.ByteString (ByteString)
import Data.Serialize (Result(..))
import System.Nix.Store.Remote.Serialize.Prim (putByteString)
import System.Nix.Store.Remote.Serializer (LoggerSError, logger, runSerialT)
import System.Nix.Store.Remote.Socket (sockGet8, sockPut)
import System.Nix.Store.Remote.MonadStore (MonadStore, clearData)
import System.Nix.Store.Remote.Types (Logger(..), ProtoVersion, hasProtoVersion)
import qualified Control.Monad
controlParser :: Get Logger
controlParser = do
ctrl <- getInt
case (ctrl :: Int) of
0x6f6c6d67 -> Next <$> getByteStringLen
0x64617461 -> Read <$> getInt
0x64617416 -> Write <$> getByteStringLen
0x616c7473 -> pure Last
0x63787470 -> flip Error <$> getByteStringLen
<*> getInt
0x53545254 -> StartActivity <$> getInt
<*> getInt
<*> getInt
<*> getByteStringLen
<*> getFields
<*> getInt
0x53544f50 -> StopActivity <$> getInt
0x52534c54 -> Result <$> getInt
<*> getInt
<*> getFields
x -> fail $ "Invalid control message received:" <> show x
import qualified Control.Monad.Reader
import qualified Control.Monad.State.Strict
import qualified Data.Serialize.Get
import qualified Data.Serializer
processOutput :: MonadStore [Logger]
processOutput = go decoder
processOutput = do
protoVersion <- Control.Monad.Reader.asks hasProtoVersion
sockGet8 >>= go . (decoder protoVersion)
where
decoder = runGetIncremental controlParser
go :: Decoder Logger -> MonadStore [Logger]
go (Done _leftover _consumed ctrl) = do
case ctrl of
e@(Error _ _) -> pure [e]
Last -> pure [Last]
Read _n -> do
(mdata, _) <- get
case mdata of
Nothing -> throwError "No data to read provided"
Just part -> do
-- XXX: we should check/assert part size against n of (Read n)
sockPut $ putByteStringLen part
clearData
decoder
:: ProtoVersion
-> ByteString
-> Result (Either LoggerSError Logger)
decoder protoVersion =
Data.Serialize.Get.runGetPartial
(runSerialT protoVersion $ Data.Serializer.getS logger)
go decoder
go :: Result (Either LoggerSError Logger) -> MonadStore [Logger]
go (Done ectrl leftover) = do
-- we should probably handle Read here as well
x -> do
next <- go decoder
pure $ x : next
Control.Monad.unless (leftover == mempty) $
-- TODO: throwError
error $ "Leftovers detected: '" ++ show leftover ++ "'"
protoVersion <- Control.Monad.Reader.asks hasProtoVersion
case ectrl of
-- TODO: tie this with throwError and better error type
Left e -> error $ show e
Right ctrl -> do
case ctrl of
e@(Logger_Error _) -> pure [e]
Logger_Last -> pure [Logger_Last]
Logger_Read _n -> do
(mdata, _) <- Control.Monad.State.Strict.get
case mdata of
Nothing -> throwError "No data to read provided"
Just part -> do
-- XXX: we should check/assert part size against n of (Read n)
sockPut $ putByteString part
clearData
sockGet8 >>= go . (decoder protoVersion)
-- we should probably handle Read here as well
x -> do
next <- sockGet8 >>= go . (decoder protoVersion)
pure $ x : next
go (Partial k) = do
soc <- asks storeSocket
chunk <- liftIO (Just <$> recv soc 8)
chunk <- sockGet8
go (k chunk)
go (Fail _leftover _consumed msg) = error msg
getFields :: Get [Field]
getFields = do
cnt <- getInt
Control.Monad.replicateM cnt getField
getField :: Get Field
getField = do
typ <- getInt
case (typ :: Int) of
0 -> LogInt <$> getInt
1 -> LogStr <$> getByteStringLen
x -> fail $ "Unknown log type: " <> show x
go (Fail msg _leftover) = error msg

View File

@ -0,0 +1,58 @@
module System.Nix.Store.Remote.MonadStore
( MonadStore
, mapStoreDir
, getStoreDir
, getLog
, flushLog
, gotError
, getErrors
, setData
, clearData
) where
import Control.Monad.Except (ExceptT)
import Control.Monad.Reader (ReaderT, asks)
import Control.Monad.Reader.Class (MonadReader)
import Control.Monad.State.Strict (StateT, gets, modify)
import Data.ByteString (ByteString)
import Control.Monad.Trans.State.Strict (mapStateT)
import Control.Monad.Trans.Except (mapExceptT)
import Control.Monad.Trans.Reader (withReaderT)
import System.Nix.StorePath (HasStoreDir(..), StoreDir)
import System.Nix.Store.Remote.Types.Logger (Logger, isError)
import System.Nix.Store.Remote.Types.StoreConfig (StoreConfig(..))
-- | Ask for a @StoreDir@
getStoreDir :: (HasStoreDir r, MonadReader r m) => m StoreDir
getStoreDir = asks hasStoreDir
type MonadStore a
= ExceptT
String
(StateT (Maybe ByteString, [Logger]) (ReaderT StoreConfig IO))
a
-- | For lying about the store dir in tests
mapStoreDir :: (StoreDir -> StoreDir) -> (MonadStore a -> MonadStore a)
mapStoreDir f = mapExceptT . mapStateT . withReaderT
$ \c@StoreConfig { storeConfig_dir = sd } -> c { storeConfig_dir = f sd }
gotError :: MonadStore Bool
gotError = gets (any isError . snd)
getErrors :: MonadStore [Logger]
getErrors = gets (filter isError . snd)
getLog :: MonadStore [Logger]
getLog = gets snd
flushLog :: MonadStore ()
flushLog = modify (\(a, _b) -> (a, []))
setData :: ByteString -> MonadStore ()
setData x = modify (\(_, b) -> (Just x, b))
clearData :: MonadStore ()
clearData = modify (\(_, b) -> (Nothing, b))

View File

@ -11,39 +11,39 @@ module System.Nix.Store.Remote.Protocol
, runStoreOpts
, runStoreOptsTCP
, runStoreOpts'
, ourProtoVersion
, GCAction(..)
) where
import qualified Control.Monad
import Control.Exception ( bracket )
import Control.Monad.Except
import Control.Monad.Reader (asks, runReaderT)
import Control.Monad.State.Strict
import Control.Exception ( bracket )
import Control.Monad.Except
import Control.Monad.Reader (asks, runReaderT)
import Control.Monad.State.Strict
import Data.Default.Class (Default(def))
import qualified Data.Bool
import Data.Binary.Get
import Data.Binary.Put
import Data.Serialize.Get
import Data.Serialize.Put
import qualified Data.ByteString
import qualified Data.ByteString.Char8
import qualified Data.ByteString.Lazy
import Network.Socket ( SockAddr(SockAddrUnix) )
import qualified Network.Socket as S
import Network.Socket.ByteString ( recv
, sendAll
)
import Network.Socket (SockAddr(SockAddrUnix))
import qualified Network.Socket as S
import Network.Socket.ByteString (recv, sendAll)
import System.Nix.StorePath ( StoreDir(..) )
import System.Nix.Store.Remote.Binary
import System.Nix.Store.Remote.Logger
import System.Nix.Store.Remote.Types
import System.Nix.Store.Remote.Util
import System.Nix.StorePath (StoreDir(..))
import System.Nix.Store.Remote.Serialize.Prim
import System.Nix.Store.Remote.Logger
import System.Nix.Store.Remote.MonadStore
import System.Nix.Store.Remote.Socket
import System.Nix.Store.Remote.Serializer (protoVersion)
import System.Nix.Store.Remote.Types
protoVersion :: Int
protoVersion = 0x115
-- major protoVersion & 0xFF00
-- minor .. & 0x00FF
ourProtoVersion :: ProtoVersion
ourProtoVersion = ProtoVersion
{ protoVersion_major = 1
, protoVersion_minor = 21
}
workerMagic1 :: Int
workerMagic1 = 0x6e697863
@ -53,76 +53,6 @@ workerMagic2 = 0x6478696f
defaultSockPath :: String
defaultSockPath = "/nix/var/nix/daemon-socket/socket"
data WorkerOp =
IsValidPath
| HasSubstitutes
| QueryReferrers
| AddToStore
| AddTextToStore
| BuildPaths
| EnsurePath
| AddTempRoot
| AddIndirectRoot
| SyncWithGC
| FindRoots
| SetOptions
| CollectGarbage
| QuerySubstitutablePathInfo
| QueryDerivationOutputs
| QueryAllValidPaths
| QueryFailedPaths
| ClearFailedPaths
| QueryPathInfo
| QueryDerivationOutputNames
| QueryPathFromHashPart
| QuerySubstitutablePathInfos
| QueryValidPaths
| QuerySubstitutablePaths
| QueryValidDerivers
| OptimiseStore
| VerifyStore
| BuildDerivation
| AddSignatures
| NarFromPath
| AddToStoreNar
| QueryMissing
deriving (Eq, Ord, Show)
opNum :: WorkerOp -> Int
opNum IsValidPath = 1
opNum HasSubstitutes = 3
opNum QueryReferrers = 6
opNum AddToStore = 7
opNum AddTextToStore = 8
opNum BuildPaths = 9
opNum EnsurePath = 10
opNum AddTempRoot = 11
opNum AddIndirectRoot = 12
opNum SyncWithGC = 13
opNum FindRoots = 14
opNum SetOptions = 19
opNum CollectGarbage = 20
opNum QuerySubstitutablePathInfo = 21
opNum QueryDerivationOutputs = 22
opNum QueryAllValidPaths = 23
opNum QueryFailedPaths = 24
opNum ClearFailedPaths = 25
opNum QueryPathInfo = 26
opNum QueryDerivationOutputNames = 28
opNum QueryPathFromHashPart = 29
opNum QuerySubstitutablePathInfos = 30
opNum QueryValidPaths = 31
opNum QuerySubstitutablePaths = 32
opNum QueryValidDerivers = 33
opNum OptimiseStore = 34
opNum VerifyStore = 35
opNum BuildDerivation = 36
opNum AddSignatures = 37
opNum NarFromPath = 38
opNum AddToStoreNar = 39
opNum QueryMissing = 40
simpleOp :: WorkerOp -> MonadStore Bool
simpleOp op = simpleOpArgs op $ pure ()
@ -133,8 +63,8 @@ simpleOpArgs op args = do
Data.Bool.bool
sockGetBool
(do
Error _num msg <- head <$> getError
throwError $ Data.ByteString.Char8.unpack msg
-- TODO: don't use show
getErrors >>= throwError . show
)
err
@ -145,7 +75,7 @@ runOpArgs :: WorkerOp -> Put -> MonadStore ()
runOpArgs op args =
runOpArgsIO
op
(\encode -> encode $ Data.ByteString.Lazy.toStrict $ runPut args)
(\encode -> encode $ runPut args)
runOpArgsIO
:: WorkerOp
@ -153,17 +83,17 @@ runOpArgsIO
-> MonadStore ()
runOpArgsIO op encoder = do
sockPut $ putInt $ opNum op
sockPut $ putEnum op
soc <- asks storeSocket
soc <- asks storeConfig_socket
encoder (liftIO . sendAll soc)
out <- processOutput
modify (\(a, b) -> (a, b <> out))
err <- gotError
Control.Monad.when err $ do
Error _num msg <- head <$> getError
throwError $ Data.ByteString.Char8.unpack msg
-- TODO: don't use show
getErrors >>= throwError . show
runStore :: MonadStore a -> IO (Either String a, [Logger])
runStore = runStoreOpts defaultSockPath def
@ -182,30 +112,36 @@ runStoreOptsTCP host port storeRootDir code = do
runStoreOpts'
:: S.Family -> S.SockAddr -> StoreDir -> MonadStore a -> IO (Either String a, [Logger])
runStoreOpts' sockFamily sockAddr storeRootDir code =
bracket open (S.close . storeSocket) run
bracket open (S.close . storeConfig_socket) run
where
open = do
soc <- S.socket sockFamily S.Stream 0
S.connect soc sockAddr
pure StoreConfig
{ storeSocket = soc
, storeDir = storeRootDir
{ storeConfig_dir = storeRootDir
, storeConfig_protoVersion = ourProtoVersion
, storeConfig_socket = soc
}
greet = do
sockPut $ putInt workerMagic1
soc <- asks storeSocket
soc <- asks hasStoreSocket
vermagic <- liftIO $ recv soc 16
let
(magic2, _daemonProtoVersion) =
flip runGet (Data.ByteString.Lazy.fromStrict vermagic)
eres =
flip runGet vermagic
$ (,)
<$> (getInt :: Get Int)
<*> (getInt :: Get Int)
Control.Monad.unless (magic2 == workerMagic2) $ error "Worker magic 2 mismatch"
sockPut $ putInt protoVersion -- clientVersion
case eres of
Left err -> error $ "Error parsing vermagic " ++ err
Right (magic2, _daemonProtoVersion) -> do
Control.Monad.unless (magic2 == workerMagic2) $ error "Worker magic 2 mismatch"
pv <- asks hasProtoVersion
sockPutS @() protoVersion pv -- clientVersion
sockPut $ putInt (0 :: Int) -- affinity
sockPut $ putInt (0 :: Int) -- obsolete reserveSpace

View File

@ -9,7 +9,10 @@ import Data.Serialize (Serialize(..))
import Data.Serialize.Get (Get)
import Data.Serialize.Put (Putter)
import Data.Text (Text)
import Data.Word (Word8, Word32)
import qualified Control.Monad
import qualified Data.Bits
import qualified Data.Bool
import qualified Data.Map
import qualified Data.Set
@ -20,11 +23,14 @@ import System.Nix.Derivation (Derivation(..), DerivationOutput(..))
import System.Nix.Build (BuildMode(..), BuildStatus(..), BuildResult(..))
import System.Nix.StorePath (StoreDir, StorePath)
import System.Nix.Store.Remote.Serialize.Prim
import System.Nix.Store.Remote.Types
instance Serialize Text where
get = getText
put = putText
-- * BuildResult
instance Serialize BuildMode where
get = getEnum
put = putEnum
@ -37,7 +43,7 @@ instance Serialize BuildResult where
get = do
status <- get
errorMessage <-
(\em -> Data.Bool.bool (Just em) Nothing (Data.Text.null em))
(\em -> Data.Bool.bool (Just em) Nothing (Data.Text.null em))
<$> get
timesBuilt <- getInt
isNonDeterministic <- getBool
@ -55,6 +61,30 @@ instance Serialize BuildResult where
putTime startTime
putTime stopTime
-- * GCAction
--
instance Serialize GCAction where
get = getEnum
put = putEnum
-- * ProtoVersion
-- protoVersion_major & 0xFF00
-- protoVersion_minor & 0x00FF
instance Serialize ProtoVersion where
get = do
v <- getInt @Word32
pure ProtoVersion
{ protoVersion_major = fromIntegral $ Data.Bits.shiftR v 8
, protoVersion_minor = fromIntegral $ v Data.Bits..&. 0x00FF
}
put p =
putInt @Word32
$ ((Data.Bits.shiftL (fromIntegral $ protoVersion_major p :: Word32) 8)
Data.Bits..|. fromIntegral (protoVersion_minor p))
-- * Derivation
getDerivation
:: StoreDir
-> Get (Derivation StorePath Text)
@ -104,3 +134,75 @@ putDerivation storeDir Derivation{..} = do
flip putMany (Data.Map.toList env)
$ \(a1, a2) -> putText a1 *> putText a2
-- * Logger
instance Serialize Activity where
get =
toEnumCheckBounds . (+(-100)) <$> getInt
>>= either fail pure
put = putInt . (+100) . fromEnum
instance Serialize ActivityID where
get = ActivityID <$> getInt
put (ActivityID aid) = putInt aid
instance Serialize ActivityResult where
get =
toEnumCheckBounds . (+(-100)) <$> getInt
>>= either fail pure
put = putInt . (+100) . fromEnum
instance Serialize Field where
get = (getInt :: Get Word8) >>= \case
0 -> Field_LogInt <$> getInt
1 -> Field_LogStr <$> getText
x -> fail $ "Unknown log field type: " <> show x
put (Field_LogInt x) = putInt (0 :: Word8) >> putInt x
put (Field_LogStr x) = putInt (1 :: Word8) >> putText x
instance Serialize Trace where
get = do
tracePosition <- (\case 0 -> Nothing; x -> Just x) <$> getInt @Int
traceHint <- get
pure Trace{..}
put Trace{..} = do
maybe (putInt @Int 0) putInt $ tracePosition
put traceHint
instance Serialize BasicError where
get = do
basicErrorMessage <- get
basicErrorExitStatus <- getInt
pure BasicError{..}
put BasicError{..} = do
put basicErrorMessage
putInt basicErrorExitStatus
instance Serialize ErrorInfo where
get = do
etyp <- get @Text
Control.Monad.unless (etyp == Data.Text.pack "Error")
$ fail
$ "get ErrorInfo: received unknown error type" ++ show etyp
errorInfoLevel <- get
_name <- get @Text -- removed error name
errorInfoMessage <- get
errorInfoPosition <- (\case 0 -> Nothing; x -> Just x) <$> getInt @Int
errorInfoTraces <- getMany get
pure ErrorInfo{..}
put ErrorInfo{..} = do
put $ Data.Text.pack "Error"
put errorInfoLevel
put $ Data.Text.pack "Error" -- removed error name
put errorInfoMessage
maybe (putInt @Int 0) putInt $ errorInfoPosition
putMany put errorInfoTraces
instance Serialize LoggerOpCode where
get = getInt @Int >>= either fail pure . intToLoggerOpCode
put = putInt @Int . loggerOpCodeToInt
instance Serialize Verbosity where
get = getEnum
put = putEnum

View File

@ -11,9 +11,11 @@ import Data.Serialize.Get (Get)
import Data.Serialize.Put (Putter)
import Data.Text (Text)
import Data.Time (NominalDiffTime, UTCTime)
import Data.Word (Word8)
import System.Nix.StorePath (StoreDir, StorePath, InvalidPathError)
import qualified Control.Monad
import qualified Data.Either
import qualified Data.HashSet
import qualified Data.Serialize.Get
import qualified Data.Serialize.Put
@ -25,18 +27,21 @@ import qualified System.Nix.StorePath
-- * Int
-- | Deserialize Nix like integer
getInt :: Get Int
getInt :: Integral a => Get a
getInt = fromIntegral <$> Data.Serialize.Get.getWord64le
-- | Serialize Nix like integer
putInt :: Putter Int
putInt :: Integral a => Putter a
putInt = Data.Serialize.Put.putWord64le . fromIntegral
-- * Bool
-- | Deserialize @Bool@ from integer
getBool :: Get Bool
getBool = (== 1) <$> (getInt :: Get Int)
getBool = (getInt :: Get Word8) >>= \case
0 -> pure False
1 -> pure True
x -> fail $ "illegal bool value " ++ show x
-- | Serialize @Bool@ into integer
putBool :: Putter Bool
@ -45,9 +50,18 @@ putBool False = putInt (0 :: Int)
-- * Enum
-- | Utility toEnum version checking bounds using Bounded class
toEnumCheckBounds :: Enum a => Int -> Either String a
toEnumCheckBounds = \case
x | x < minBound -> Left $ "enum out of min bound " ++ show x
x | x > maxBound -> Left $ "enum out of max bound " ++ show x
x | otherwise -> Right $ toEnum x
-- | Deserialize @Enum@ to integer
getEnum :: Enum a => Get a
getEnum = toEnum <$> getInt
getEnum =
toEnumCheckBounds <$> getInt
>>= either fail pure
-- | Serialize @Enum@ to integer
putEnum :: Enum a => Putter a
@ -179,6 +193,19 @@ getPaths sd =
. fmap (System.Nix.StorePath.parsePath sd)
<$> getByteStrings
-- | Deserialize @StorePath@, checking
-- that @StoreDir@ matches expected value
getPathsOrFail :: StoreDir -> Get (HashSet StorePath)
getPathsOrFail sd = do
eps <-
fmap (System.Nix.StorePath.parsePath sd)
<$> getByteStrings
Control.Monad.when (any Data.Either.isLeft eps)
$ fail
$ show
$ Data.Either.lefts eps
pure $ Data.HashSet.fromList $ Data.Either.rights eps
-- | Serialize a @HashSet@ of @StorePath@s
putPaths :: StoreDir -> Putter (HashSet StorePath)
putPaths storeDir =

View File

@ -0,0 +1,874 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-|
Description : NixSerializer
Copyright : (c) John Ericson, 2023
Richard Marko, 2023
|-}
module System.Nix.Store.Remote.Serializer
(
-- * NixSerializer
NixSerializer
-- * Errors
, SError(..)
-- ** Runners
, runSerialT
, runG
, runP
-- * Primitives
, int
, bool
, byteString
, enum
, text
, maybeText
-- * UTCTime
, time
-- * Combinators
, list
, set
, hashSet
, mapS
-- * ProtoVersion
, protoVersion
-- * StorePath
, storePath
, storePathHashPart
, storePathName
-- * Metadata
, pathMetadata
-- * Some HashAlgo
, someHashAlgo
-- * Digest
, digest
-- * Derivation
, derivation
-- * Derivation
, derivedPath
-- * Build
, buildMode
, buildResult
-- * Logger
, LoggerSError(..)
, activityID
, maybeActivity
, activityResult
, field
, trace
, basicError
, errorInfo
, loggerOpCode
, logger
, verbosity
) where
import Control.Monad.Except (MonadError, throwError, withExceptT)
import Control.Monad.Reader (MonadReader)
import Control.Monad.Trans (MonadTrans, lift)
import Control.Monad.Trans.Reader (ReaderT, runReaderT)
import Control.Monad.Trans.Except (ExceptT, runExceptT)
import Crypto.Hash (Digest, HashAlgorithm, SHA256)
import Data.ByteString (ByteString)
import Data.Dependent.Sum (DSum((:=>)))
import Data.Fixed (Uni)
import Data.Hashable (Hashable)
import Data.HashSet (HashSet)
import Data.Map (Map)
import Data.Set (Set)
import Data.Some (Some)
import Data.Text (Text)
import Data.Time (NominalDiffTime, UTCTime)
import Data.Vector (Vector)
import Data.Word (Word8, Word32, Word64)
import GHC.Generics (Generic)
import qualified Control.Monad
import qualified Control.Monad.Reader
import qualified Data.Attoparsec.Text
import qualified Data.Bits
import qualified Data.ByteString
import qualified Data.HashSet
import qualified Data.Map.Strict
import qualified Data.Serialize.Get
import qualified Data.Serialize.Put
import qualified Data.Set
import qualified Data.Text
import qualified Data.Text.Encoding
import qualified Data.Time.Clock.POSIX
import qualified Data.Vector
import Data.Serializer
import System.Nix.Base (BaseEncoding(NixBase32))
import System.Nix.Build (BuildMode, BuildResult(..))
import System.Nix.ContentAddress (ContentAddress)
import System.Nix.Derivation (Derivation(..), DerivationOutput(..))
import System.Nix.DerivedPath (DerivedPath, ParseOutputsError)
import System.Nix.Hash (HashAlgo)
import System.Nix.Signature (NarSignature)
import System.Nix.StorePath (HasStoreDir(..), InvalidPathError, StorePath, StorePathHashPart, StorePathName)
import System.Nix.StorePath.Metadata (Metadata(..), StorePathTrust(..))
import System.Nix.Store.Remote.Types
import qualified Data.Coerce
import qualified Data.Bifunctor
import qualified Data.Some
import qualified System.Nix.Base
import qualified System.Nix.ContentAddress
import qualified System.Nix.DerivedPath
import qualified System.Nix.Hash
import qualified System.Nix.Signature
import qualified System.Nix.StorePath
-- | Transformer for @Serializer@
newtype SerialT r e m a = SerialT
{ _unSerialT :: ExceptT e (ReaderT r m) a }
deriving
( Applicative
, Functor
, Monad
, MonadError e
, MonadReader r
, MonadFail
)
instance MonadTrans (SerialT r e) where
lift = SerialT . lift . lift
-- | Runner for @SerialT@
runSerialT
:: Monad m
=> r
-> SerialT r e m a
-> m (Either e a)
runSerialT r =
(`runReaderT` r)
. runExceptT
. _unSerialT
mapError
:: Functor m
=> (e -> e')
-> SerialT r e m a
-> SerialT r e' m a
mapError f =
SerialT
. withExceptT f
. _unSerialT
-- * NixSerializer
type NixSerializer r e = Serializer (SerialT r e)
-- * Errors
data SError
= SError
| SError_BadPadding
{ badPaddingStr :: ByteString
, badPaddingLen :: Int
, badPaddingPads :: [Word8]
}
| SError_ContentAddress String
| SError_DerivedPath ParseOutputsError
| SError_Digest String
| SError_EnumOutOfMinBound Int
| SError_EnumOutOfMaxBound Int
| SError_HashAlgo String
| SError_IllegalBool Word64
| SError_InvalidNixBase32
| SError_NarHashMustBeSHA256
| SError_NotYetImplemented String (ForPV ProtoVersion)
| SError_Path InvalidPathError
| SError_Signature String
deriving (Eq, Ord, Generic, Show)
data ForPV a
= ForPV_Newer a
| ForPV_Older a
deriving (Eq, Ord, Generic, Show)
-- ** Runners
runG
:: NixSerializer r e a
-> r
-> ByteString
-> Either (GetSerializerError e) a
runG serializer r =
transformGetError
. runGetS
serializer
(runSerialT r)
runP
:: NixSerializer r e a
-> r
-> a
-> Either e ByteString
runP serializer r =
transformPutError
. runPutS
serializer
(runSerialT r)
-- * Primitives
int :: Integral a => NixSerializer r e a
int = Serializer
{ getS = fromIntegral <$> lift Data.Serialize.Get.getWord64le
, putS = lift . Data.Serialize.Put.putWord64le . fromIntegral
}
bool :: NixSerializer r SError Bool
bool = Serializer
{ getS = getS (int @Word64) >>= \case
0 -> pure False
1 -> pure True
x -> throwError $ SError_IllegalBool x
, putS = \case
False -> putS (int @Word8) 0
True -> putS (int @Word8) 1
}
byteString :: NixSerializer r SError ByteString
byteString = Serializer
{ getS = do
len <- getS int
st <- lift $ Data.Serialize.Get.getByteString len
Control.Monad.when (len `mod` 8 /= 0) $ do
pads <- unpad $ fromIntegral $ 8 - (len `mod` 8)
Control.Monad.unless
(all (== 0) pads)
$ throwError
$ SError_BadPadding st len pads
pure st
, putS = \x -> do
let len = Data.ByteString.length x
putS int len
lift $ Data.Serialize.Put.putByteString x
Control.Monad.when
(len `mod` 8 /= 0)
$ pad $ 8 - (len `mod` 8)
}
where
unpad count =
Control.Monad.replicateM
count
(lift Data.Serialize.Get.getWord8)
pad count =
Control.Monad.replicateM_
count
(lift $ Data.Serialize.Put.putWord8 0)
-- | Utility toEnum version checking bounds using Bounded class
toEnumCheckBoundsM
:: ( Enum a
, MonadError SError m
)
=> Int
-> m a
toEnumCheckBoundsM = \case
x | x < minBound -> throwError $ SError_EnumOutOfMinBound x
x | x > maxBound -> throwError $ SError_EnumOutOfMaxBound x
x | otherwise -> pure $ toEnum x
enum :: Enum a => NixSerializer r SError a
enum = Serializer
{ getS = getS int >>= toEnumCheckBoundsM
, putS = putS int . fromEnum
}
text :: NixSerializer r SError Text
text = mapIsoSerializer
Data.Text.Encoding.decodeUtf8
Data.Text.Encoding.encodeUtf8
byteString
maybeText :: NixSerializer r SError (Maybe Text)
maybeText = mapIsoSerializer
(\case
t | Data.Text.null t -> Nothing
t | otherwise -> Just t
)
(maybe mempty id)
text
-- * UTCTime
time :: NixSerializer r e UTCTime
time = Serializer
{ getS =
Data.Time.Clock.POSIX.posixSecondsToUTCTime
. toPicoSeconds
<$> getS int
, putS =
putS int
. fromPicoSeconds
. Data.Time.Clock.POSIX.utcTimeToPOSIXSeconds
}
where
-- fancy (*10^12), from Int to Uni to Pico(seconds)
toPicoSeconds :: Int -> NominalDiffTime
toPicoSeconds n = realToFrac (toEnum n :: Uni)
-- fancy (`div`10^12), from Pico to Uni to Int
fromPicoSeconds :: NominalDiffTime -> Int
fromPicoSeconds = (fromEnum :: Uni -> Int) . realToFrac
-- * Combinators
list
:: NixSerializer r e a
-> NixSerializer r e [a]
list s = Serializer
{ getS = do
count <- getS int
Control.Monad.replicateM count (getS s)
, putS = \xs -> do
putS int (length xs)
mapM_ (putS s) xs
}
set
:: Ord a
=> NixSerializer r e a
-> NixSerializer r e (Set a)
set =
mapIsoSerializer
Data.Set.fromList
Data.Set.toList
. list
hashSet
:: ( Eq a
, Hashable a
)
=> NixSerializer r e a
-> NixSerializer r e (HashSet a)
hashSet =
mapIsoSerializer
Data.HashSet.fromList
Data.HashSet.toList
. list
mapS
:: Ord k
=> NixSerializer r e k
-> NixSerializer r e v
-> NixSerializer r e (Map k v)
mapS k v =
mapIsoSerializer
Data.Map.Strict.fromList
Data.Map.Strict.toList
$ list
$ tup k v
vector
:: Ord a
=> NixSerializer r e a
-> NixSerializer r e (Vector a)
vector =
mapIsoSerializer
Data.Vector.fromList
Data.Vector.toList
. list
-- * ProtoVersion
-- protoVersion_major & 0xFF00
-- protoVersion_minor & 0x00FF
protoVersion :: NixSerializer r e ProtoVersion
protoVersion = Serializer
{ getS = do
v <- getS (int @Word32)
pure ProtoVersion
{ protoVersion_major = fromIntegral $ Data.Bits.shiftR v 8
, protoVersion_minor = fromIntegral $ v Data.Bits..&. 0x00FF
}
, putS = \p ->
putS (int @Word32)
$ ((Data.Bits.shiftL (fromIntegral $ protoVersion_major p :: Word32) 8)
Data.Bits..|. fromIntegral (protoVersion_minor p))
}
-- * StorePath
storePath :: HasStoreDir r => NixSerializer r SError StorePath
storePath = Serializer
{ getS = do
sd <- Control.Monad.Reader.asks hasStoreDir
System.Nix.StorePath.parsePath sd <$> getS byteString
>>=
either
(throwError . SError_Path)
pure
, putS = \p -> do
sd <- Control.Monad.Reader.asks hasStoreDir
putS
byteString
$ System.Nix.StorePath.storePathToRawFilePath sd p
}
storePathHashPart :: NixSerializer r SError StorePathHashPart
storePathHashPart =
mapIsoSerializer
System.Nix.StorePath.unsafeMakeStorePathHashPart
System.Nix.StorePath.unStorePathHashPart
$ mapPrismSerializer
(Data.Bifunctor.first (pure SError_InvalidNixBase32)
. System.Nix.Base.decodeWith NixBase32)
(System.Nix.Base.encodeWith NixBase32)
text
storePathName :: NixSerializer r SError StorePathName
storePathName =
mapPrismSerializer
(Data.Bifunctor.first SError_Path
. System.Nix.StorePath.makeStorePathName)
System.Nix.StorePath.unStorePathName
text
pathMetadata
:: HasStoreDir r
=> NixSerializer r SError (Metadata StorePath)
pathMetadata = Serializer
{ getS = do
deriverPath <- getS maybePath
digest' <- getS $ digest NixBase32
let narHash = System.Nix.Hash.HashAlgo_SHA256 :=> digest'
references <- getS $ hashSet storePath
registrationTime <- getS time
narBytes <- (\case
0 -> Nothing
size -> Just size) <$> getS int
trust <- getS storePathTrust
sigs <- getS $ set signature
contentAddress <- getS maybeContentAddress
pure $ Metadata{..}
, putS = \Metadata{..} -> do
putS maybePath deriverPath
let putNarHash
:: DSum HashAlgo Digest
-> SerialT r SError PutM ()
putNarHash = \case
System.Nix.Hash.HashAlgo_SHA256 :=> d
-> putS (digest @SHA256 NixBase32) d
_ -> throwError SError_NarHashMustBeSHA256
putNarHash narHash
putS (hashSet storePath) references
putS time registrationTime
putS int $ Prelude.maybe 0 id $ narBytes
putS storePathTrust trust
putS (set signature) sigs
putS maybeContentAddress contentAddress
}
where
maybeContentAddress
:: NixSerializer r SError (Maybe ContentAddress)
maybeContentAddress =
mapPrismSerializer
(maybe
(pure Nothing)
$ Data.Bifunctor.bimap
SError_ContentAddress
Just
. System.Nix.ContentAddress.parseContentAddress
)
(fmap System.Nix.ContentAddress.buildContentAddress)
maybeText
maybePath
:: HasStoreDir r
=> NixSerializer r SError (Maybe StorePath)
maybePath = Serializer
{ getS = do
getS maybeText >>= \case
Nothing -> pure Nothing
Just t -> do
sd <- Control.Monad.Reader.asks hasStoreDir
either
(throwError . SError_Path)
(pure . pure)
$ System.Nix.StorePath.parsePathFromText sd t
, putS = \case
Nothing -> putS maybeText Nothing
Just p -> do
sd <- Control.Monad.Reader.asks hasStoreDir
putS text $ System.Nix.StorePath.storePathToText sd p
}
storePathTrust
:: NixSerializer r SError StorePathTrust
storePathTrust =
mapIsoSerializer
(\case False -> BuiltElsewhere; True -> BuiltLocally)
(\case BuiltElsewhere -> False; BuiltLocally -> True)
bool
signature
:: NixSerializer r SError NarSignature
signature =
mapPrismSerializer
(Data.Bifunctor.first SError_Signature
. Data.Attoparsec.Text.parseOnly
System.Nix.Signature.signatureParser)
(System.Nix.Signature.signatureToText)
text
-- * Some HashAlgo
someHashAlgo :: NixSerializer r SError (Some HashAlgo)
someHashAlgo =
mapPrismSerializer
(Data.Bifunctor.first SError_HashAlgo
. System.Nix.Hash.textToAlgo)
(Data.Some.foldSome System.Nix.Hash.algoToText)
text
-- * Digest
digest
:: forall a r
. HashAlgorithm a
=> BaseEncoding
-> NixSerializer r SError (Digest a)
digest base =
mapIsoSerializer
Data.Coerce.coerce
Data.Coerce.coerce
$ mapPrismSerializer
(Data.Bifunctor.first SError_Digest
. System.Nix.Hash.decodeDigestWith @a base)
(System.Nix.Hash.encodeDigestWith base)
$ text
derivationOutput
:: HasStoreDir r
=> NixSerializer r SError (DerivationOutput StorePath Text)
derivationOutput = Serializer
{ getS = do
path <- getS storePath
hashAlgo <- getS text
hash <- getS text
pure DerivationOutput{..}
, putS = \DerivationOutput{..} -> do
putS storePath path
putS text hashAlgo
putS text hash
}
-- * Derivation
derivation
:: HasStoreDir r
=> NixSerializer r SError (Derivation StorePath Text)
derivation = Serializer
{ getS = do
outputs <- getS (mapS text derivationOutput)
-- Our type is Derivation, but in Nix
-- the type sent over the wire is BasicDerivation
-- which omits inputDrvs
inputDrvs <- pure mempty
inputSrcs <- getS (set storePath)
platform <- getS text
builder <- getS text
args <- getS (vector text)
env <- getS (mapS text text)
pure Derivation{..}
, putS = \Derivation{..} -> do
putS (mapS text derivationOutput) outputs
putS (set storePath) inputSrcs
putS text platform
putS text builder
putS (vector text) args
putS (mapS text text) env
}
-- * DerivedPath
derivedPathNew
:: HasStoreDir r
=> NixSerializer r SError DerivedPath
derivedPathNew = Serializer
{ getS = do
root <- Control.Monad.Reader.asks hasStoreDir
p <- getS text
case System.Nix.DerivedPath.parseDerivedPath root p of
Left err -> throwError $ SError_DerivedPath err
Right x -> pure x
, putS = \d -> do
root <- Control.Monad.Reader.asks hasStoreDir
putS text (System.Nix.DerivedPath.derivedPathToText root d)
}
derivedPath
:: ( HasProtoVersion r
, HasStoreDir r
)
=> NixSerializer r SError DerivedPath
derivedPath = Serializer
{ getS = do
pv <- Control.Monad.Reader.asks hasProtoVersion
if pv < ProtoVersion 1 30
then
throwError
$ SError_NotYetImplemented
"DerivedPath"
(ForPV_Older pv)
else getS derivedPathNew
, putS = \d -> do
pv <- Control.Monad.Reader.asks hasProtoVersion
if pv < ProtoVersion 1 30
then
throwError
$ SError_NotYetImplemented
"DerivedPath"
(ForPV_Older pv)
else putS derivedPathNew d
}
-- * Build
buildMode :: NixSerializer r SError BuildMode
buildMode = enum
buildResult :: NixSerializer r SError BuildResult
buildResult = Serializer
{ getS = do
status <- getS enum
errorMessage <- getS maybeText
timesBuilt <- getS int
isNonDeterministic <- getS bool
startTime <- getS time
stopTime <- getS time
pure $ BuildResult{..}
, putS = \BuildResult{..} -> do
putS enum status
putS maybeText errorMessage
putS int timesBuilt
putS bool isNonDeterministic
putS time startTime
putS time stopTime
}
-- * Logger
data LoggerSError
= LoggerSError_Prim SError
| LoggerSError_InvalidOpCode Int
| LoggerSError_TooOldForErrorInfo
| LoggerSError_TooNewForBasicError
| LoggerSError_UnknownLogFieldType Word8
deriving (Eq, Ord, Generic, Show)
mapPrimE
:: Functor m
=> SerialT r SError m a
-> SerialT r LoggerSError m a
mapPrimE = mapError LoggerSError_Prim
maybeActivity :: NixSerializer r LoggerSError (Maybe Activity)
maybeActivity = Serializer
{ getS = getS (int @Int) >>= \case
0 -> pure Nothing
x -> mapPrimE $ toEnumCheckBoundsM (x - 100) >>= pure . Just
, putS = \case
Nothing -> putS (int @Int) 0
Just act -> putS activity act
}
where
activity :: NixSerializer r LoggerSError Activity
activity = Serializer
{ getS = mapPrimE $ getS int >>= toEnumCheckBoundsM . (+(-100))
, putS = putS int . (+100) . fromEnum
}
activityID :: NixSerializer r LoggerSError ActivityID
activityID = mapIsoSerializer ActivityID unActivityID int
activityResult :: NixSerializer r LoggerSError ActivityResult
activityResult = Serializer
{ getS = mapPrimE $ getS int >>= toEnumCheckBoundsM . (+(-100))
, putS = putS int . (+100) . fromEnum
}
field :: NixSerializer r LoggerSError Field
field = Serializer
{ getS = getS (int @Word8) >>= \case
0 -> Field_LogInt <$> getS int
1 -> Field_LogStr <$> mapPrimE (getS text)
x -> throwError $ LoggerSError_UnknownLogFieldType x
, putS = \case
Field_LogInt x -> putS int (0 :: Word8) >> putS int x
Field_LogStr x -> putS int (1 :: Word8) >> mapPrimE (putS text x)
}
trace :: NixSerializer r LoggerSError Trace
trace = Serializer
{ getS = do
tracePosition <- (\case 0 -> Nothing; x -> Just x) <$> getS (int @Int)
traceHint <- mapPrimE $ getS text
pure Trace{..}
, putS = \Trace{..} -> do
maybe (putS (int @Int) 0) (putS int) $ tracePosition
mapPrimE $ putS text traceHint
}
basicError :: NixSerializer r LoggerSError BasicError
basicError = Serializer
{ getS = do
basicErrorMessage <- mapPrimE $ getS text
basicErrorExitStatus <- getS int
pure BasicError{..}
, putS = \BasicError{..} -> do
mapPrimE $ putS text basicErrorMessage
putS int basicErrorExitStatus
}
errorInfo :: NixSerializer r LoggerSError ErrorInfo
errorInfo = Serializer
{ getS = do
etyp <- mapPrimE $ getS text
Control.Monad.unless (etyp == Data.Text.pack "Error")
$ fail
$ "get ErrorInfo: received unknown error type" ++ show etyp
errorInfoLevel <- getS verbosity
_name <- mapPrimE $ getS text -- removed error name
errorInfoMessage <- mapPrimE $ getS text
errorInfoPosition <- (\case 0 -> Nothing; x -> Just x) <$> getS int
errorInfoTraces <- getS (list trace)
pure ErrorInfo{..}
, putS = \ErrorInfo{..} -> do
mapPrimE $ do
putS text $ Data.Text.pack "Error"
putS verbosity errorInfoLevel
mapPrimE $ do
putS text $ Data.Text.pack "Error" -- removed error name
putS text errorInfoMessage
maybe (putS (int @Word8) 0) (putS int) errorInfoPosition
putS (list trace) errorInfoTraces
}
loggerOpCode :: NixSerializer r LoggerSError LoggerOpCode
loggerOpCode = Serializer
{ getS = do
c <- getS int
either
(pure $ throwError (LoggerSError_InvalidOpCode c))
pure
$ intToLoggerOpCode c
, putS = putS int . loggerOpCodeToInt
}
logger
:: HasProtoVersion r
=> NixSerializer r LoggerSError Logger
logger = Serializer
{ getS = getS loggerOpCode >>= \case
LoggerOpCode_Next ->
mapPrimE $
Logger_Next <$> getS text
LoggerOpCode_Read ->
Logger_Read <$> getS int
LoggerOpCode_Write ->
mapPrimE $
Logger_Write <$> getS byteString
LoggerOpCode_Last ->
pure Logger_Last
LoggerOpCode_Error -> do
pv <- Control.Monad.Reader.asks hasProtoVersion
Logger_Error <$>
if protoVersion_minor pv >= 26
then Right <$> getS errorInfo
else Left <$> getS basicError
LoggerOpCode_StartActivity -> do
startActivityID <- getS activityID
startActivityVerbosity <- getS verbosity
startActivityType <- getS maybeActivity
startActivityString <- mapPrimE $ getS byteString
startActivityFields <- getS (list field)
startActivityParentID <- getS activityID
pure Logger_StartActivity{..}
LoggerOpCode_StopActivity -> do
stopActivityID <- getS activityID
pure Logger_StopActivity{..}
LoggerOpCode_Result -> do
resultActivityID <- getS activityID
resultType <- getS activityResult
resultFields <- getS (list field)
pure Logger_Result {..}
, putS = \case
Logger_Next s -> do
putS loggerOpCode LoggerOpCode_Next
mapError LoggerSError_Prim $
putS text s
Logger_Read i -> do
putS loggerOpCode LoggerOpCode_Read
putS int i
Logger_Write s -> do
putS loggerOpCode LoggerOpCode_Write
mapPrimE $ putS byteString s
Logger_Last ->
putS loggerOpCode LoggerOpCode_Last
Logger_Error basicOrInfo -> do
putS loggerOpCode LoggerOpCode_Error
minor <- protoVersion_minor <$> Control.Monad.Reader.asks hasProtoVersion
case basicOrInfo of
Left _ | minor >= 26 -> throwError $ LoggerSError_TooNewForBasicError
Left e | otherwise -> putS basicError e
Right _ | minor < 26 -> throwError $ LoggerSError_TooOldForErrorInfo
Right e -> putS errorInfo e
Logger_StartActivity{..} -> do
putS loggerOpCode LoggerOpCode_StartActivity
putS activityID startActivityID
putS verbosity startActivityVerbosity
putS maybeActivity startActivityType
mapPrimE $
putS byteString startActivityString
putS (list field) startActivityFields
putS activityID startActivityParentID
Logger_StopActivity{..} -> do
putS loggerOpCode LoggerOpCode_StopActivity
putS activityID stopActivityID
Logger_Result{..} -> do
putS loggerOpCode LoggerOpCode_Result
putS activityID resultActivityID
putS activityResult resultType
putS (list field) resultFields
}
verbosity :: NixSerializer r LoggerSError Verbosity
verbosity = Serializer
{ getS = mapPrimE $ getS enum
, putS = mapPrimE . putS enum
}

View File

@ -0,0 +1,97 @@
module System.Nix.Store.Remote.Socket where
import Control.Monad.Except (throwError)
import Control.Monad.Reader (asks)
import Control.Monad.IO.Class (MonadIO(..))
import Data.ByteString (ByteString)
import Data.HashSet (HashSet)
import Data.Serialize.Get (Get, Result(..))
import Data.Serialize.Put
import Network.Socket.ByteString (recv, sendAll)
import System.Nix.StorePath (StorePath)
import System.Nix.Store.Remote.MonadStore
import System.Nix.Store.Remote.Serializer (NixSerializer, runP)
import System.Nix.Store.Remote.Serialize.Prim
import System.Nix.Store.Remote.Types
import qualified Data.Serialize.Get
genericIncremental
:: MonadIO m
=> m ByteString
-> Get a
-> m a
genericIncremental getsome parser = do
getsome >>= go . decoder
where
decoder = Data.Serialize.Get.runGetPartial parser
go (Done x _leftover) = pure x
go (Partial k) = do
chunk <- getsome
go (k chunk)
go (Fail msg _leftover) = error msg
getSocketIncremental :: Get a -> MonadStore a
getSocketIncremental = genericIncremental sockGet8
sockGet8 :: MonadStore ByteString
sockGet8 = do
soc <- asks hasStoreSocket
liftIO $ recv soc 8
sockPut :: Put -> MonadStore ()
sockPut p = do
soc <- asks hasStoreSocket
liftIO $ sendAll soc $ runPut p
sockPutS
:: Show e
=> NixSerializer ProtoVersion e a
-> a
-> MonadStore ()
sockPutS s a = do
soc <- asks hasStoreSocket
pv <- asks hasProtoVersion
case runP s pv a of
Right x -> liftIO $ sendAll soc x
-- TODO: errors
Left e -> throwError $ show e
sockGet :: Get a -> MonadStore a
sockGet = getSocketIncremental
sockGetInt :: Integral a => MonadStore a
sockGetInt = getSocketIncremental getInt
sockGetBool :: MonadStore Bool
sockGetBool = (== (1 :: Int)) <$> sockGetInt
sockGetStr :: MonadStore ByteString
sockGetStr = getSocketIncremental getByteString
sockGetStrings :: MonadStore [ByteString]
sockGetStrings = getSocketIncremental getByteStrings
sockGetPath :: MonadStore StorePath
sockGetPath = do
sd <- getStoreDir
pth <- getSocketIncremental (getPath sd)
either
(throwError . show)
pure
pth
sockGetPathMay :: MonadStore (Maybe StorePath)
sockGetPathMay = do
sd <- getStoreDir
pth <- getSocketIncremental (getPath sd)
pure $
either
(const Nothing)
Just
pth
sockGetPaths :: MonadStore (HashSet StorePath)
sockGetPaths = do
sd <- getStoreDir
getSocketIncremental (getPathsOrFail sd)

View File

@ -1,119 +1,21 @@
module System.Nix.Store.Remote.Types
( MonadStore
, StoreConfig(..)
, CheckFlag
, doCheck
, dontCheck
, unCheckFlag
, SubstituteFlag
, doSubstitute
, dontSubstitute
, unSubstituteFlag
, Logger(..)
, Field(..)
, mapStoreDir
, getStoreDir
, getStoreDir'
, getLog
, flushLog
, gotError
, getError
, setData
, clearData
( module System.Nix.Store.Remote.Types.Activity
, module System.Nix.Store.Remote.Types.GC
, module System.Nix.Store.Remote.Types.CheckMode
, module System.Nix.Store.Remote.Types.Logger
, module System.Nix.Store.Remote.Types.ProtoVersion
, module System.Nix.Store.Remote.Types.StoreConfig
, module System.Nix.Store.Remote.Types.SubstituteMode
, module System.Nix.Store.Remote.Types.Verbosity
, module System.Nix.Store.Remote.Types.WorkerOp
) where
import Control.Monad.Except (ExceptT)
import Control.Monad.Reader (ReaderT, asks)
import Control.Monad.Reader.Class (MonadReader)
import Control.Monad.State.Strict (StateT, gets, modify)
import Data.ByteString (ByteString)
import Network.Socket (Socket)
import qualified Data.ByteString.Lazy as BSL
import Control.Monad.Trans.State.Strict (mapStateT)
import Control.Monad.Trans.Except (mapExceptT)
import Control.Monad.Trans.Reader (withReaderT)
import System.Nix.StorePath (HasStoreDir(..), StoreDir)
data StoreConfig = StoreConfig
{ storeDir :: StoreDir
, storeSocket :: Socket
}
-- | Check flag, used by @verifyStore@
newtype CheckFlag = CheckFlag { unCheckFlag :: Bool }
deriving (Eq, Ord, Show)
doCheck, dontCheck :: CheckFlag
doCheck = CheckFlag True
dontCheck = CheckFlag False
-- | Substitute flag, used by @queryValidPaths@
newtype SubstituteFlag = SubstituteFlag { unSubstituteFlag :: Bool }
deriving (Eq, Ord, Show)
doSubstitute, dontSubstitute :: SubstituteFlag
doSubstitute = SubstituteFlag True
dontSubstitute = SubstituteFlag False
-- | Ask for a @StoreDir@
getStoreDir' :: (HasStoreDir r, MonadReader r m) => m StoreDir
getStoreDir' = asks hasStoreDir
type MonadStore a
= ExceptT
String
(StateT (Maybe BSL.ByteString, [Logger]) (ReaderT StoreConfig IO))
a
-- | For lying about the store dir in tests
mapStoreDir :: (StoreDir -> StoreDir) -> (MonadStore a -> MonadStore a)
mapStoreDir f = mapExceptT . mapStateT . withReaderT $ \c@StoreConfig { storeDir = sd } -> c { storeDir = f sd }
type ActivityID = Int
type ActivityParentID = Int
type ActivityType = Int
type Verbosity = Int
type ResultType = Int
data Field = LogStr ByteString | LogInt Int
deriving (Eq, Ord, Show)
data Logger =
Next ByteString
| Read Int -- data needed from source
| Write ByteString -- data for sink
| Last
| Error Int ByteString
| StartActivity ActivityID Verbosity ActivityType ByteString [Field] ActivityParentID
| StopActivity ActivityID
| Result ActivityID ResultType [Field]
deriving (Eq, Ord, Show)
isError :: Logger -> Bool
isError (Error _ _) = True
isError _ = False
gotError :: MonadStore Bool
gotError = gets (any isError . snd)
getError :: MonadStore [Logger]
getError = gets (filter isError . snd)
getLog :: MonadStore [Logger]
getLog = gets snd
flushLog :: MonadStore ()
flushLog = modify (\(a, _b) -> (a, []))
setData :: BSL.ByteString -> MonadStore ()
setData x = modify (\(_, b) -> (Just x, b))
clearData :: MonadStore ()
clearData = modify (\(_, b) -> (Nothing, b))
getStoreDir :: MonadStore StoreDir
getStoreDir = asks storeDir
import System.Nix.Store.Remote.Types.Activity
import System.Nix.Store.Remote.Types.GC
import System.Nix.Store.Remote.Types.CheckMode
import System.Nix.Store.Remote.Types.Logger
import System.Nix.Store.Remote.Types.ProtoVersion
import System.Nix.Store.Remote.Types.StoreConfig
import System.Nix.Store.Remote.Types.SubstituteMode
import System.Nix.Store.Remote.Types.Verbosity
import System.Nix.Store.Remote.Types.WorkerOp

View File

@ -0,0 +1,55 @@
module System.Nix.Store.Remote.Types.Activity
( Activity(..)
, ActivityID(..)
, ActivityResult(..)
) where
import GHC.Generics
-- | Type of the activity
--
-- We don't have Activity_Unknown here
-- as we can do @Maybe Activity@ and @Nothing@
-- corresponding to Unknown (which has 0 value)
--
-- Rest of the values are offset by @(+100)@
-- on the wire, i.e.:
--
-- * @Activity_CopyPath = 100@
-- * @Activity_BuildWaiting = 111@
data Activity
= Activity_CopyPath
| Activity_FileTransfer
| Activity_Realise
| Activity_CopyPaths
| Activity_Builds
| Activity_Build
| Activity_OptimiseStore
| Activity_VerifyPaths
| Activity_Substitute
| Activity_QueryPathInfo
| Activity_PostBuildHook
| Activity_BuildWaiting
deriving (Bounded, Eq, Enum, Generic, Ord, Show)
-- | Numeric ID of the activity
newtype ActivityID = ActivityID { unActivityID :: Int }
deriving (Eq, Generic, Ord, Show)
-- | Result of some activity
--
-- The values are offset by @(+100)@
-- on the wire, i.e.:
--
-- * @ActivityResult_FileLinked = 100@
-- * @ActivityResult_PostBuildLogLine = 107@
data ActivityResult
= ActivityResult_FileLinked
| ActivityResult_BuildLogLine
| ActivityResult_UnstrustedPath
| ActivityResult_CorruptedPath
| ActivityResult_SetPhase
| ActivityResult_Progress
| ActivityResult_SetExpected
| ActivityResult_PostBuildLogLine
deriving (Bounded, Eq, Enum, Generic, Ord, Show)

View File

@ -0,0 +1,11 @@
module System.Nix.Store.Remote.Types.CheckMode
( CheckMode(..)
) where
import GHC.Generics
-- | Check mode, used by @verifyStore@
data CheckMode
= CheckMode_DoCheck
| CheckMode_DontCheck
deriving (Bounded, Eq, Generic, Enum, Ord, Show)

View File

@ -0,0 +1,47 @@
{-|
Description : Garbage collection actions / options
Maintainer : srk <srk@48.io>
|-}
module System.Nix.Store.Remote.Types.GC (
GCAction(..)
, GCOptions(..)
, GCResult(..)
) where
import Data.HashSet (HashSet)
import Data.Word (Word64)
import GHC.Generics (Generic)
import System.Nix.StorePath (StorePath)
-- | Garbage collection action
data GCAction
= GCAction_ReturnLive -- ^ Return the set of paths reachable from roots (closure)
| GCAction_ReturnDead -- ^ Return unreachable paths
| GCAction_DeleteDead -- ^ Delete unreachable paths
| GCAction_DeleteSpecific -- ^ Delete specified paths
deriving (Bounded, Eq, Enum, Generic, Ord, Show)
-- | Garbage collector operation options
data GCOptions = GCOptions
{ -- | Operation
gcOptions_operation :: GCAction
-- | If set, then reachability from the roots is ignored (unused)
, gcOptions_ignoreLiveness :: Bool
-- | Paths to delete for @GCAction_DeleteSpecific@
, gcOptions_pathsToDelete :: HashSet StorePath
-- | Stop after `gcOptions_maxFreed` bytes have been freed
, gcOptions_maxFreed :: Integer
} deriving (Eq, Generic, Ord, Show)
-- | Result of the garbage collection operation
data GCResult = GCResult
{ -- | Depending on the action, the GC roots,
-- or the paths that would be or have been deleted
gcResult_deletedPaths :: HashSet StorePath
-- | The number of bytes that would be or was freed for
--
-- - @GCAction_ReturnDead@
-- - @GCAction_DeleteDead@
-- - @GCAction_DeleteSpecific@
, gcResult_bytesFreed :: Word64
} deriving (Eq, Generic, Ord, Show)

View File

@ -0,0 +1,107 @@
module System.Nix.Store.Remote.Types.Logger
( Field(..)
, Trace(..)
, BasicError(..)
, ErrorInfo(..)
, Logger(..)
, LoggerOpCode(..)
, loggerOpCodeToInt
, intToLoggerOpCode
, isError
) where
import Data.ByteString (ByteString)
import Data.Text (Text)
import GHC.Generics
import System.Nix.Store.Remote.Types.Activity (Activity, ActivityID, ActivityResult)
import System.Nix.Store.Remote.Types.Verbosity (Verbosity)
data Field
= Field_LogStr Text
| Field_LogInt Int
deriving (Eq, Generic, Ord, Show)
-- | Error trace
data Trace = Trace
{ tracePosition :: Maybe Int -- Error position, Nix always writes 0 here
, traceHint :: Text
}
deriving (Eq, Generic, Ord, Show)
data BasicError = BasicError
{ basicErrorExitStatus :: Int
, basicErrorMessage :: Text
}
deriving (Eq, Generic, Ord, Show)
-- | Extended error info
-- available for protoVersion_minor >= 26
data ErrorInfo = ErrorInfo
{ errorInfoLevel :: Verbosity
, errorInfoMessage :: Text
, errorInfoPosition :: Maybe Int -- Error position, Nix always writes 0 here
, errorInfoTraces :: [Trace]
}
deriving (Eq, Generic, Ord, Show)
data LoggerOpCode
= LoggerOpCode_Next
| LoggerOpCode_Read
| LoggerOpCode_Write
| LoggerOpCode_Last
| LoggerOpCode_Error
| LoggerOpCode_StartActivity
| LoggerOpCode_StopActivity
| LoggerOpCode_Result
deriving (Eq, Generic, Ord, Show)
loggerOpCodeToInt :: LoggerOpCode -> Int
loggerOpCodeToInt = \case
LoggerOpCode_Next -> 0x6f6c6d67
LoggerOpCode_Read -> 0x64617461
LoggerOpCode_Write -> 0x64617416
LoggerOpCode_Last -> 0x616c7473
LoggerOpCode_Error -> 0x63787470
LoggerOpCode_StartActivity -> 0x53545254
LoggerOpCode_StopActivity -> 0x53544f50
LoggerOpCode_Result -> 0x52534c54
intToLoggerOpCode :: Int -> Either String LoggerOpCode
intToLoggerOpCode = \case
0x6f6c6d67 -> Right LoggerOpCode_Next
0x64617461 -> Right LoggerOpCode_Read
0x64617416 -> Right LoggerOpCode_Write
0x616c7473 -> Right LoggerOpCode_Last
0x63787470 -> Right LoggerOpCode_Error
0x53545254 -> Right LoggerOpCode_StartActivity
0x53544f50 -> Right LoggerOpCode_StopActivity
0x52534c54 -> Right LoggerOpCode_Result
x -> Left $ "Invalid LoggerOpCode: " ++ show x
data Logger
= Logger_Next Text
| Logger_Read Int -- data needed from source
| Logger_Write ByteString -- data for sink
| Logger_Last
| Logger_Error (Either BasicError ErrorInfo)
| Logger_StartActivity
{ startActivityID :: ActivityID
, startActivityVerbosity :: Verbosity
, startActivityType :: Maybe Activity
, startActivityString :: ByteString
, startActivityFields :: [Field]
, startActivityParentID :: ActivityID
}
| Logger_StopActivity
{ stopActivityID :: ActivityID
}
| Logger_Result
{ resultActivityID :: ActivityID
, resultType :: ActivityResult
, resultFields :: [Field]
}
deriving (Eq, Generic, Ord, Show)
isError :: Logger -> Bool
isError Logger_Error {} = True
isError _ = False

View File

@ -0,0 +1,19 @@
module System.Nix.Store.Remote.Types.ProtoVersion
( ProtoVersion(..)
, HasProtoVersion(..)
) where
import Data.Word (Word8, Word16)
import GHC.Generics
data ProtoVersion = ProtoVersion
{ protoVersion_major :: Word16
, protoVersion_minor :: Word8
}
deriving (Eq, Generic, Ord, Show)
class HasProtoVersion r where
hasProtoVersion :: r -> ProtoVersion
instance HasProtoVersion ProtoVersion where
hasProtoVersion = id

View File

@ -0,0 +1,45 @@
{-# OPTIONS_GHC -Wno-orphans #-}
module System.Nix.Store.Remote.Types.StoreConfig
( PreStoreConfig(..)
, StoreConfig(..)
, HasStoreSocket(..)
) where
import Network.Socket (Socket)
import System.Nix.StorePath (HasStoreDir(..), StoreDir)
import System.Nix.Store.Remote.Types.ProtoVersion (HasProtoVersion(..), ProtoVersion)
data PreStoreConfig = PreStoreConfig
{ preStoreConfig_dir :: StoreDir
, preStoreConfig_socket :: Socket
}
instance HasStoreDir PreStoreConfig where
hasStoreDir = preStoreConfig_dir
class HasStoreSocket r where
hasStoreSocket :: r -> Socket
instance HasStoreSocket Socket where
hasStoreSocket = id
instance HasStoreSocket PreStoreConfig where
hasStoreSocket = preStoreConfig_socket
data StoreConfig = StoreConfig
{ storeConfig_dir :: StoreDir
, storeConfig_protoVersion :: ProtoVersion
, storeConfig_socket :: Socket
}
instance HasStoreDir StoreDir where
hasStoreDir = id
instance HasStoreDir StoreConfig where
hasStoreDir = storeConfig_dir
instance HasProtoVersion StoreConfig where
hasProtoVersion = storeConfig_protoVersion
instance HasStoreSocket StoreConfig where
hasStoreSocket = storeConfig_socket

View File

@ -0,0 +1,11 @@
module System.Nix.Store.Remote.Types.SubstituteMode
( SubstituteMode(..)
) where
import GHC.Generics
-- | Path substitution mode, used by @queryValidPaths@
data SubstituteMode
= SubstituteMode_DoSubstitute
| SubstituteMode_DontSubstitute
deriving (Bounded, Eq, Generic, Enum, Ord, Show)

View File

@ -0,0 +1,17 @@
module System.Nix.Store.Remote.Types.Verbosity
( Verbosity(..)
) where
import GHC.Generics
-- | Logging verbosity
data Verbosity
= Verbosity_Error
| Verbosity_Warn
| Verbosity_Notice
| Verbosity_Info
| Verbosity_Talkative
| Verbosity_Chatty
| Verbosity_Debug
| Verbosity_Vomit
deriving (Bounded, Eq, Enum, Generic, Ord, Show)

View File

@ -0,0 +1,57 @@
module System.Nix.Store.Remote.Types.WorkerOp
( WorkerOp(..)
) where
-- | Worker opcode
--
-- This type has gaps filled in so that the GHC builtin
-- Enum instance lands on the right values.
data WorkerOp
= Reserved_0__ -- 0
| IsValidPath -- 1
| Reserved_2__ -- 2
| HasSubstitutes -- 3
| QueryPathHash -- 4 // obsolete
| QueryReferences -- 5 // obsolete
| QueryReferrers -- 6
| AddToStore -- 7
| AddTextToStore -- 8 // obsolete since 1.25, Nix 3.0. Use wopAddToStore
| BuildPaths -- 9
| EnsurePath -- 10 0xa
| AddTempRoot -- 11 0xb
| AddIndirectRoot -- 12 0xc
| SyncWithGC -- 13 0xd
| FindRoots -- 14 0xe
| Reserved_15__ -- 15 0xf
| ExportPath -- 16 0x10 // obsolete
| Reserved_17__ -- 17 0x11
| QueryDeriver -- 18 0x12 // obsolete
| SetOptions -- 19 0x13
| CollectGarbage -- 20 0x14
| QuerySubstitutablePathInfo -- 21 0x15
| QueryDerivationOutputs -- 22 0x16 // obsolete
| QueryAllValidPaths -- 23 0x17
| QueryFailedPaths -- 24 0x18
| ClearFailedPaths -- 25 0x19
| QueryPathInfo -- 26 0x1a
| ImportPaths -- 27 0x1b // obsolete
| QueryDerivationOutputNames -- 28 0x1c // obsolete
| QueryPathFromHashPart -- 29 0x1d
| QuerySubstitutablePathInfos -- 30 0x1e
| QueryValidPaths -- 31 0x1f
| QuerySubstitutablePaths -- 32 0x20
| QueryValidDerivers -- 33 0x21
| OptimiseStore -- 34 0x22
| VerifyStore -- 35 0x23
| BuildDerivation -- 36 0x24
| AddSignatures -- 37 0x25
| NarFromPath -- 38 0x26
| AddToStoreNar -- 39 0x27
| QueryMissing -- 40 0x28
| QueryDerivationOutputMap -- 41 0x29
| RegisterDrvOutput -- 42 0x2a
| QueryRealisation -- 43 0x2b
| AddMultipleToStore -- 44 0x2c
| AddBuildLog -- 45 0x2d
| BuildPathsWithResults -- 46 0x2e
deriving (Bounded, Eq, Enum, Ord, Show, Read)

View File

@ -1,172 +0,0 @@
module System.Nix.Store.Remote.Util where
import Control.Monad.Except (throwError)
import Control.Monad.Reader (asks)
import Control.Monad.IO.Class (MonadIO(..))
import Data.ByteString (ByteString)
import Data.HashSet (HashSet)
import Data.Text (Text)
import Data.Either (rights)
import Data.Binary.Get
import Data.Binary.Put
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import Data.Time
import Data.Time.Clock.POSIX
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Lazy as BSL
import Network.Socket.ByteString ( recv
, sendAll
)
import System.Nix.Build
import System.Nix.Derivation
import System.Nix.StorePath (StoreDir, StorePath, InvalidPathError, parsePath, storePathToRawFilePath)
import System.Nix.Store.Remote.Binary
import System.Nix.Store.Remote.Types
import qualified Data.HashSet
import qualified Data.Map
genericIncremental :: (MonadIO m) => m (Maybe ByteString) -> Get a -> m a
genericIncremental getsome parser = go decoder
where
decoder = runGetIncremental parser
go (Done _leftover _consumed x ) = pure x
go (Partial k ) = do
chunk <- getsome
go (k chunk)
go (Fail _leftover _consumed msg) = error msg
getSocketIncremental :: Get a -> MonadStore a
getSocketIncremental = genericIncremental sockGet8
where
sockGet8 :: MonadStore (Maybe BSC.ByteString)
sockGet8 = do
soc <- asks storeSocket
liftIO $ Just <$> recv soc 8
sockPut :: Put -> MonadStore ()
sockPut p = do
soc <- asks storeSocket
liftIO $ sendAll soc $ BSL.toStrict $ runPut p
sockGet :: Get a -> MonadStore a
sockGet = getSocketIncremental
sockGetInt :: Integral a => MonadStore a
sockGetInt = getSocketIncremental getInt
sockGetBool :: MonadStore Bool
sockGetBool = (== (1 :: Int)) <$> sockGetInt
sockGetStr :: MonadStore ByteString
sockGetStr = getSocketIncremental getByteStringLen
sockGetStrings :: MonadStore [ByteString]
sockGetStrings = getSocketIncremental getByteStrings
sockGetPath :: MonadStore StorePath
sockGetPath = do
sd <- getStoreDir
pth <- getSocketIncremental (getPath sd)
either
(throwError . show)
pure
pth
sockGetPathMay :: MonadStore (Maybe StorePath)
sockGetPathMay = do
sd <- getStoreDir
pth <- getSocketIncremental (getPath sd)
pure $
either
(const Nothing)
Just
pth
sockGetPaths :: MonadStore (HashSet StorePath)
sockGetPaths = do
sd <- getStoreDir
getSocketIncremental (getPaths sd)
bsToText :: ByteString -> Text
bsToText = T.decodeUtf8
textToBS :: Text -> ByteString
textToBS = T.encodeUtf8
bslToText :: BSL.ByteString -> Text
bslToText = TL.toStrict . TL.decodeUtf8
textToBSL :: Text -> BSL.ByteString
textToBSL = TL.encodeUtf8 . TL.fromStrict
putText :: Text -> Put
putText = putByteStringLen . textToBSL
putTexts :: [Text] -> Put
putTexts = putByteStrings . fmap textToBSL
getPath :: StoreDir -> Get (Either InvalidPathError StorePath)
getPath sd = parsePath sd <$> getByteStringLen
getPaths :: StoreDir -> Get (HashSet StorePath)
getPaths sd =
Data.HashSet.fromList . rights . fmap (parsePath sd) <$> getByteStrings
putPath :: StoreDir -> StorePath -> Put
putPath storeDir = putByteStringLen . BSL.fromStrict . storePathToRawFilePath storeDir
putPaths :: StoreDir -> HashSet StorePath -> Put
putPaths storeDir = putByteStrings . Data.HashSet.toList . Data.HashSet.map
(BSL.fromStrict . storePathToRawFilePath storeDir)
putBool :: Bool -> Put
putBool True = putInt (1 :: Int)
putBool False = putInt (0 :: Int)
getBool :: Get Bool
getBool = (== 1) <$> (getInt :: Get Int)
putEnum :: (Enum a) => a -> Put
putEnum = putInt . fromEnum
getEnum :: (Enum a) => Get a
getEnum = toEnum <$> getInt
putTime :: UTCTime -> Put
putTime = (putInt :: Int -> Put) . round . utcTimeToPOSIXSeconds
getTime :: Get UTCTime
getTime = posixSecondsToUTCTime <$> getEnum
getBuildResult :: Get BuildResult
getBuildResult =
BuildResult
<$> getEnum
<*> (Just . bsToText <$> getByteStringLen)
<*> getInt
<*> getBool
<*> getTime
<*> getTime
putDerivation :: StoreDir -> Derivation StorePath Text -> Put
putDerivation storeDir Derivation{..} = do
flip putMany (Data.Map.toList outputs)
$ \(outputName, DerivationOutput{..}) -> do
putText outputName
putPath storeDir path
putText hashAlgo
putText hash
putMany (putPath storeDir) inputSrcs
putText platform
putText builder
putMany putText args
flip putMany (Data.Map.toList env)
$ \(a1, a2) -> putText a1 *> putText a2

View File

@ -3,42 +3,36 @@
module NixDaemon where
import Data.Text (Text)
import Data.Either ( isRight
, isLeft
)
import Data.Bool ( bool )
import Control.Monad ( void )
import Control.Monad.IO.Class ( liftIO )
import Data.Either (isRight, isLeft)
import Data.Bool (bool)
import Control.Monad (forM_, void)
import Control.Monad.IO.Class (liftIO)
import qualified System.Environment
import Control.Exception ( bracket )
import Control.Concurrent ( threadDelay )
import qualified Data.ByteString.Char8 as BSC
import Control.Exception (bracket)
import Control.Concurrent (threadDelay)
import qualified Data.ByteString.Char8 as BSC
import qualified Data.Either
import qualified Data.HashSet as HS
import qualified Data.Map.Strict as M
import System.Directory
import System.IO.Temp
import qualified System.Process as P
import System.Posix.User as U
import System.Linux.Namespaces as NS
import Test.Hspec ( Spec
, describe
, context
)
import qualified Test.Hspec as Hspec
import Test.Hspec.Expectations.Lifted
import qualified Data.HashSet as HS
import qualified Data.Map.Strict as M
import qualified Data.Text
import qualified Data.Text.Encoding
import System.Directory
import System.IO.Temp
import qualified System.Process as P
import System.Posix.User as U
import System.Linux.Namespaces as NS
import Test.Hspec (Spec, describe, context)
import qualified Test.Hspec as Hspec
import Test.Hspec.Expectations.Lifted
import System.FilePath
import System.Nix.Build
import System.Nix.StorePath
import System.Nix.StorePath.Metadata
import System.Nix.Store.Remote
import System.Nix.Store.Remote.Protocol
import System.FilePath
import System.Nix.Build
import System.Nix.StorePath
import System.Nix.StorePath.Metadata
import System.Nix.Store.Remote
import System.Nix.Store.Remote.Protocol
import Crypto.Hash ( SHA256 )
import System.Nix.Nar ( dumpPath )
import Crypto.Hash (SHA256)
import System.Nix.Nar (dumpPath)
createProcessEnv :: FilePath -> String -> [String] -> IO P.ProcessHandle
createProcessEnv fp proc args = do
@ -194,14 +188,23 @@ spec_protocol = Hspec.around withNixDaemon $
context "verifyStore" $ do
itRights "check=False repair=False" $
verifyStore dontCheck RepairMode_DontRepair `shouldReturn` False
verifyStore
CheckMode_DontCheck
RepairMode_DontRepair
`shouldReturn` False
itRights "check=True repair=False" $
verifyStore doCheck RepairMode_DontRepair `shouldReturn` False
verifyStore
CheckMode_DoCheck
RepairMode_DontRepair
`shouldReturn` False
--privileged
itRights "check=True repair=True" $
verifyStore doCheck RepairMode_DoRepair `shouldReturn` False
verifyStore
CheckMode_DoCheck
RepairMode_DoRepair
`shouldReturn` False
context "addTextToStore" $
itRights "adds text to store" $ withPath pure
@ -234,15 +237,15 @@ spec_protocol = Hspec.around withNixDaemon $
context "buildPaths" $ do
itRights "build Normal" $ withPath $ \path -> do
let pathSet = HS.fromList [path]
buildPaths pathSet Normal
buildPaths pathSet BuildMode_Normal
itRights "build Check" $ withPath $ \path -> do
let pathSet = HS.fromList [path]
buildPaths pathSet Check
buildPaths pathSet BuildMode_Check
itLefts "build Repair" $ withPath $ \path -> do
let pathSet = HS.fromList [path]
buildPaths pathSet Repair
buildPaths pathSet BuildMode_Repair
context "roots" $ context "findRoots" $ do
itRights "empty roots" (findRoots `shouldReturn` M.empty)
@ -272,3 +275,17 @@ spec_protocol = Hspec.around withNixDaemon $
path <- dummy
liftIO $ print path
isValidPathUncached path `shouldReturn` True
context "deleteSpecific" $
itRights "delete a path from the store" $ withPath $ \path -> do
-- clear temp gc roots so the delete works. restarting the nix daemon should also do this...
storeDir <- getStoreDir
let tempRootsDir = Data.Text.unpack $ mconcat [ Data.Text.Encoding.decodeUtf8 (unStoreDir storeDir), "/../var/nix/temproots/" ]
tempRootList <- liftIO $ listDirectory tempRootsDir
liftIO $ forM_ tempRootList $ \entry -> do
removeFile $ mconcat [ tempRootsDir, "/", entry ]
GCResult{..} <- deleteSpecific (HS.fromList [path])
gcResult_deletedPaths `shouldBe` HS.fromList [path]
gcResult_bytesFreed `shouldBe` 4

View File

@ -0,0 +1,43 @@
module Data.SerializerSpec (spec) where
import Data.Some
import Data.Serializer
import Data.Serializer.Example
import Test.Hspec (Spec, describe, it, shouldBe)
import Test.Hspec.QuickCheck (prop)
spec :: Spec
spec = describe "Serializer" $ do
prop "Roundtrips GADT protocol" $ \someCmd ->
(runG cmdS
<$> (runP cmdS someCmd))
`shouldBe`
((pure $ pure someCmd) ::
Either MyPutError
(Either (GetSerializerError MyGetError)
(Some Cmd)))
it "Handles putS error" $
runP cmdSPutError (Some (Cmd_Bool True))
`shouldBe`
Left MyPutError_NoLongerSupported
it "Handles getS error" $
runG cmdSGetError (runPutSimple cmdS (Some (Cmd_Bool True)))
`shouldBe`
Left (SerializerError_Get MyGetError_Example)
it "Handles getS fail" $
runG cmdSGetFail (runPutSimple cmdS (Some (Cmd_Bool True)))
`shouldBe`
Left (SerializerError_GetFail @MyGetError "Failed reading: no parse\nEmpty call stack\n")
prop "Roundtrips elaborate example" $ \someCmd readerBool ->
(runGRest cmdSRest readerBool 0
<$> (runPRest cmdSRest readerBool 0 someCmd))
`shouldBe`
((pure $ pure $ someCmd) ::
Either MyPutError
(Either (GetSerializerError MyGetError)
(Some Cmd)))

View File

@ -0,0 +1,164 @@
{-# LANGUAGE OverloadedStrings #-}
module NixSerializerSpec (spec) where
import Crypto.Hash (MD5, SHA1, SHA256, SHA512)
import Data.Dependent.Sum (DSum((:=>)))
import Data.Fixed (Uni)
import Data.Time (NominalDiffTime)
import Test.Hspec (Expectation, Spec, describe, parallel, shouldBe)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (Gen, arbitrary, forAll, suchThat)
import Test.QuickCheck.Instances ()
import qualified Data.Time.Clock.POSIX
import qualified Data.Serializer
import qualified System.Nix.Build
import qualified System.Nix.Hash
import System.Nix.Arbitrary ()
import System.Nix.Build (BuildResult)
import System.Nix.Derivation (Derivation(inputDrvs))
import System.Nix.StorePath (StoreDir)
import System.Nix.StorePath.Metadata (Metadata(..))
import System.Nix.Store.Remote.Arbitrary ()
import System.Nix.Store.Remote.Serializer
import System.Nix.Store.Remote.Types (ErrorInfo(..), Logger(..), ProtoVersion(..), Trace(..))
-- | Test for roundtrip using @NixSerializer@
roundtripSReader
:: forall r e a
. ( Eq a
, Show a
, Eq e
, Show e
)
=> NixSerializer r e a
-> r
-> a
-> Expectation
roundtripSReader serializer readerVal a =
(runG serializer readerVal
<$> runP serializer readerVal a)
`shouldBe` (pure $ pure a)
roundtripS
:: ( Eq a
, Show a
, Eq e
, Show e
)
=> NixSerializer () e a
-> a
-> Expectation
roundtripS serializer = roundtripSReader serializer ()
spec :: Spec
spec = parallel $ do
describe "Prim" $ do
prop "Int" $ roundtripS @Int @() int
prop "Bool" $ roundtripS bool
prop "ByteString" $ roundtripS byteString
prop "Text" $ roundtripS text
prop "Maybe Text"
$ forAll (arbitrary `suchThat` (/= Just ""))
$ roundtripS maybeText
prop "UTCTime" $ do
let
-- scale to seconds and back
toSeconds :: Int -> NominalDiffTime
toSeconds n = realToFrac (toEnum n :: Uni)
fromSeconds :: NominalDiffTime -> Int
fromSeconds = (fromEnum :: Uni -> Int) . realToFrac
roundtripS @Int @() $
Data.Serializer.mapIsoSerializer
(fromSeconds . Data.Time.Clock.POSIX.utcTimeToPOSIXSeconds)
(Data.Time.Clock.POSIX.posixSecondsToUTCTime . toSeconds)
time
describe "Combinators" $ do
prop "list" $ roundtripS @[Int] @() (list int)
prop "set" $ roundtripS (set byteString)
prop "hashSet" $ roundtripS (hashSet byteString)
prop "mapS" $ roundtripS (mapS (int @Int) byteString)
describe "Complex" $ do
prop "BuildResult"
$ forAll (arbitrary `suchThat` ((/= Just "") . System.Nix.Build.errorMessage))
$ \br ->
roundtripS @BuildResult buildResult
-- fix time to 0 as we test UTCTime above
$ br { System.Nix.Build.startTime = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
, System.Nix.Build.stopTime = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
}
prop "StorePath" $
roundtripSReader @StoreDir storePath
prop "StorePathHashPart" $
roundtripS storePathHashPart
prop "StorePathName" $
roundtripS storePathName
let narHashIsSHA256 Metadata{..} =
case narHash of
(System.Nix.Hash.HashAlgo_SHA256 :=> _) -> True
_ -> False
prop "Metadata (StorePath)"
$ \sd -> forAll (arbitrary `suchThat` (\m -> narHashIsSHA256 m && narBytes m /= Just 0))
$ roundtripSReader @StoreDir pathMetadata sd
. (\m -> m
{ registrationTime = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
})
prop "Some HashAlgo" $
roundtripS someHashAlgo
describe "Digest" $ do
prop "MD5" $ roundtripS . digest @MD5
prop "SHA1" $ roundtripS . digest @SHA1
prop "SHA256" $ roundtripS . digest @SHA256
prop "SHA512" $ roundtripS . digest @SHA512
prop "Derivation" $ \sd ->
roundtripSReader @StoreDir derivation sd
. (\drv -> drv { inputDrvs = mempty })
prop "ProtoVersion" $ roundtripS @ProtoVersion @() protoVersion
describe "Logger" $ do
prop "ActivityID" $ roundtripS activityID
prop "Maybe Activity" $ roundtripS maybeActivity
prop "ActivityResult" $ roundtripS activityResult
prop "Field" $ roundtripS field
prop "Trace"
$ forAll (arbitrary `suchThat` ((/= Just 0) . tracePosition))
$ roundtripS trace
prop "BasicError" $ roundtripS basicError
prop "ErrorInfo"
$ forAll (arbitrary
`suchThat`
(\ErrorInfo{..}
-> errorInfoPosition /= Just 0
&& all ((/= Just 0) . tracePosition) errorInfoTraces
)
)
$ roundtripS errorInfo
prop "LoggerOpCode" $ roundtripS loggerOpCode
prop "Verbosity" $ roundtripS verbosity
prop "Logger"
$ forAll (arbitrary :: Gen ProtoVersion)
$ \pv ->
forAll (arbitrary `suchThat` errorInfoIf (protoVersion_minor pv >= 26))
$ roundtripSReader logger pv
where
errorInfoIf True (Logger_Error (Right x)) = noJust0s x
errorInfoIf False (Logger_Error (Left _)) = True
errorInfoIf _ (Logger_Error _) = False
errorInfoIf _ _ = True
noJust0s ErrorInfo{..} =
errorInfoPosition /= Just 0
&& all ((/= Just 0) . tracePosition) errorInfoTraces

View File

@ -1,4 +1,3 @@
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
module SerializeSpec (spec) where
@ -9,7 +8,7 @@ import Data.Serialize.Get (Get, runGet)
import Data.Serialize.Put (Putter, runPut)
import Data.Text (Text)
import Data.Time (NominalDiffTime)
import Test.Hspec (Expectation, Spec, describe, it, shouldBe)
import Test.Hspec (Expectation, Spec, describe, it, parallel, shouldBe)
import Test.Hspec.QuickCheck (prop)
import Test.Hspec.Nix (roundtrips)
import Test.QuickCheck (arbitrary, forAll, suchThat)
@ -21,10 +20,12 @@ import qualified Data.Time.Clock.POSIX
import qualified System.Nix.Build
import System.Nix.Arbitrary ()
import System.Nix.Build (BuildMode, BuildStatus)
import System.Nix.Derivation (Derivation(..))
import System.Nix.Build (BuildMode(..), BuildStatus(..))
import System.Nix.Derivation (Derivation(inputDrvs))
import System.Nix.Store.Remote.Arbitrary ()
import System.Nix.Store.Remote.Serialize (getDerivation, putDerivation)
import System.Nix.Store.Remote.Serialize.Prim
import System.Nix.Store.Remote.Types
-- | Test for roundtrip using @Putter@ and @Get@ functions
roundtrips2
@ -54,10 +55,11 @@ roundtripS =
(runGet get)
spec :: Spec
spec = do
spec = parallel $ do
describe "Prim" $ do
prop "Int" $ roundtrips2 putInt getInt
prop "Int" $ roundtrips2 putInt (getInt @Int)
prop "Bool" $ roundtrips2 putBool getBool
prop "ByteString" $ roundtrips2 putByteString getByteString
prop "UTCTime" $ do
let
@ -72,8 +74,7 @@ spec = do
(fromSeconds . Data.Time.Clock.POSIX.utcTimeToPOSIXSeconds <$> getTime)
describe "Combinators" $ do
prop "Many" $ roundtrips2 (putMany putInt) (getMany getInt)
prop "ByteString" $ roundtrips2 putByteString getByteString
prop "Many" $ roundtrips2 (putMany putInt) (getMany (getInt @Int))
prop "[ByteString]" $ roundtrips2 putByteStrings getByteStrings
prop "Text" $ roundtrips2 putText getText
prop "[Text]" $ roundtrips2 putTexts getTexts
@ -101,6 +102,8 @@ spec = do
, System.Nix.Build.stopTime = Data.Time.Clock.POSIX.posixSecondsToUTCTime 0
}
prop "ProtoVersion" $ roundtripS @ProtoVersion
prop "Derivation StorePath Text" $ \sd ->
roundtrips2
(putDerivation sd)
@ -108,25 +111,98 @@ spec = do
-- inputDrvs is not used in remote protocol serialization
. (\drv -> drv { inputDrvs = mempty })
let it' name constr value = it name $ runPut (put constr) `shouldBe` runPut (putInt value)
describe "Build enum order matches Nix" $ do
it' "Normal" System.Nix.Build.Normal 0
it' "Repair" System.Nix.Build.Repair 1
it' "Check" System.Nix.Build.Check 2
describe "Logger" $ do
prop "Activity" $ roundtripS @Activity
prop "ActivityID" $ roundtripS @ActivityID
prop "Activity" $ roundtripS @Activity
prop "Field" $ roundtripS @Field
prop "Trace"
$ forAll (arbitrary `suchThat` ((/= Just 0) . tracePosition))
$ roundtripS @Trace
prop "BasicError" $ roundtripS @BasicError
prop "ErrorInfo"
$ forAll (arbitrary
`suchThat`
(\ErrorInfo{..}
-> errorInfoPosition /= Just 0
&& all ((/= Just 0) . tracePosition) errorInfoTraces
)
)
$ roundtripS @ErrorInfo
prop "LoggerOpCode" $ roundtripS @LoggerOpCode
prop "Verbosity" $ roundtripS @Verbosity
describe "BuildStatus enum order matches Nix" $ do
it' "Built" System.Nix.Build.Built 0
it' "Substituted" System.Nix.Build.Substituted 1
it' "AlreadyValid" System.Nix.Build.AlreadyValid 2
it' "PermanentFailure" System.Nix.Build.PermanentFailure 3
it' "InputRejected" System.Nix.Build.InputRejected 4
it' "OutputRejected" System.Nix.Build.OutputRejected 5
it' "TransientFailure" System.Nix.Build.TransientFailure 6
it' "CachedFailure" System.Nix.Build.CachedFailure 7
it' "TimedOut" System.Nix.Build.TimedOut 8
it' "MiscFailure" System.Nix.Build.MiscFailure 9
it' "DependencyFailed" System.Nix.Build.DependencyFailed 10
it' "LogLimitExceeded" System.Nix.Build.LogLimitExceeded 11
it' "NotDeterministic" System.Nix.Build.NotDeterministic 12
it' "ResolvesToAlreadyValid" System.Nix.Build.ResolvesToAlreadyValid 13
it' "NoSubstituters" System.Nix.Build.NoSubstituters 14
describe "Enums" $ do
let it' name constr value = it name $ runPut (put constr) `shouldBe` runPut (putInt @Int value)
describe "BuildMode enum order matches Nix" $ do
it' "Normal" BuildMode_Normal 0
it' "Repair" BuildMode_Repair 1
it' "Check" BuildMode_Check 2
describe "BuildStatus enum order matches Nix" $ do
it' "Built" BuildStatus_Built 0
it' "Substituted" BuildStatus_Substituted 1
it' "AlreadyValid" BuildStatus_AlreadyValid 2
it' "PermanentFailure" BuildStatus_PermanentFailure 3
it' "InputRejected" BuildStatus_InputRejected 4
it' "OutputRejected" BuildStatus_OutputRejected 5
it' "TransientFailure" BuildStatus_TransientFailure 6
it' "CachedFailure" BuildStatus_CachedFailure 7
it' "TimedOut" BuildStatus_TimedOut 8
it' "MiscFailure" BuildStatus_MiscFailure 9
it' "DependencyFailed" BuildStatus_DependencyFailed 10
it' "LogLimitExceeded" BuildStatus_LogLimitExceeded 11
it' "NotDeterministic" BuildStatus_NotDeterministic 12
it' "ResolvesToAlreadyValid" BuildStatus_ResolvesToAlreadyValid 13
it' "NoSubstituters" BuildStatus_NoSubstituters 14
describe "GCAction enum order matches Nix" $ do
it' "ReturnLive" GCAction_ReturnLive 0
it' "ReturnDead" GCAction_ReturnDead 1
it' "DeleteDead" GCAction_DeleteDead 2
it' "DeleteSpecific" GCAction_DeleteSpecific 3
describe "Logger" $ do
describe "Activity enum order matches Nix" $ do
it' "CopyPath" Activity_CopyPath 100
it' "FileTransfer" Activity_FileTransfer 101
it' "Realise" Activity_Realise 102
it' "CopyPaths" Activity_CopyPaths 103
it' "Builds" Activity_Builds 104
it' "Build" Activity_Build 105
it' "OptimiseStore" Activity_OptimiseStore 106
it' "VerifyPaths" Activity_VerifyPaths 107
it' "Substitute" Activity_Substitute 108
it' "QueryPathInfo" Activity_QueryPathInfo 109
it' "PostBuildHook" Activity_PostBuildHook 110
it' "BuildWaiting" Activity_BuildWaiting 111
describe "ActivityResult enum order matches Nix" $ do
it' "FileLinked" ActivityResult_FileLinked 100
it' "BuildLogLine" ActivityResult_BuildLogLine 101
it' "UnstrustedPath" ActivityResult_UnstrustedPath 102
it' "CorruptedPath" ActivityResult_CorruptedPath 103
it' "SetPhase" ActivityResult_SetPhase 104
it' "Progress" ActivityResult_Progress 105
it' "SetExpected" ActivityResult_SetExpected 106
it' "PostBuildLogLine" ActivityResult_PostBuildLogLine 107
describe "LoggerOpCode matches Nix" $ do
it' "Next" LoggerOpCode_Next 0x6f6c6d67
it' "Read" LoggerOpCode_Read 0x64617461
it' "Write" LoggerOpCode_Write 0x64617416
it' "Last" LoggerOpCode_Last 0x616c7473
it' "Error" LoggerOpCode_Error 0x63787470
it' "StartActivity" LoggerOpCode_StartActivity 0x53545254
it' "StopActivity" LoggerOpCode_StopActivity 0x53544f50
it' "Result" LoggerOpCode_Result 0x52534c54
describe "Verbosity enum order matches Nix" $ do
it' "Error" Verbosity_Error 0
it' "Warn" Verbosity_Warn 1
it' "Notice" Verbosity_Notice 2
it' "Info" Verbosity_Info 3
it' "Talkative" Verbosity_Talkative 4
it' "Chatty" Verbosity_Chatty 5
it' "Debug" Verbosity_Debug 6
it' "Vomit" Verbosity_Vomit 7

View File

@ -36,6 +36,7 @@ library
import: commons
exposed-modules:
System.Nix.Arbitrary
, System.Nix.Arbitrary.Base
, System.Nix.Arbitrary.Build
, System.Nix.Arbitrary.ContentAddress
, System.Nix.Arbitrary.Derivation
@ -44,6 +45,7 @@ library
, System.Nix.Arbitrary.Signature
, System.Nix.Arbitrary.Store.Types
, System.Nix.Arbitrary.StorePath
, System.Nix.Arbitrary.StorePath.Metadata
, Test.Hspec.Nix
build-depends:
base >=4.12 && <5

View File

@ -1,5 +1,6 @@
module System.Nix.Arbitrary where
import System.Nix.Arbitrary.Base ()
import System.Nix.Arbitrary.Build ()
import System.Nix.Arbitrary.ContentAddress ()
import System.Nix.Arbitrary.Derivation ()
@ -8,3 +9,4 @@ import System.Nix.Arbitrary.Hash ()
import System.Nix.Arbitrary.Signature ()
import System.Nix.Arbitrary.Store.Types ()
import System.Nix.Arbitrary.StorePath ()
import System.Nix.Arbitrary.StorePath.Metadata ()

View File

@ -0,0 +1,13 @@
-- due to recent generic-arbitrary
{-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module System.Nix.Arbitrary.Base where
import System.Nix.Base
import Test.QuickCheck (Arbitrary(..))
import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..))
import Test.QuickCheck.Instances ()
deriving via GenericArbitrary BaseEncoding
instance Arbitrary BaseEncoding

View File

@ -6,6 +6,7 @@ module System.Nix.Arbitrary.Hash where
import Data.ByteString (ByteString)
import Crypto.Hash (Digest, MD5(..), SHA1(..), SHA256(..), SHA512(..))
import Data.Dependent.Sum (DSum((:=>)))
import Data.Some (Some(Some))
import System.Nix.Hash (HashAlgo(..))
import Test.QuickCheck (Arbitrary(arbitrary), oneof)
@ -36,3 +37,14 @@ instance Arbitrary (DSum HashAlgo Digest) where
, (HashAlgo_SHA256 :=>) <$> arbitrary
, (HashAlgo_SHA512 :=>) <$> arbitrary
]
instance Arbitrary (Some HashAlgo) where
arbitrary =
oneof
$ pure
<$> [
Some HashAlgo_MD5
, Some HashAlgo_SHA1
, Some HashAlgo_SHA256
, Some HashAlgo_SHA512
]

View File

@ -1,13 +1,11 @@
-- due to recent generic-arbitrary
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module System.Nix.Arbitrary.StorePath where
#if !MIN_VERSION_base(4,18,0)
import Control.Applicative (liftA2)
#endif
import Crypto.Hash (SHA256)
import Crypto.Hash (MD5, SHA1, SHA256, SHA512)
import qualified Data.ByteString.Char8
import qualified Data.Text
import System.Nix.StorePath (StoreDir(..)
@ -17,7 +15,7 @@ import System.Nix.StorePath (StoreDir(..)
)
import qualified System.Nix.StorePath
import Test.QuickCheck (Arbitrary(arbitrary), listOf, elements)
import Test.QuickCheck (Arbitrary(arbitrary), elements, listOf, oneof)
instance Arbitrary StoreDir where
arbitrary =
@ -43,6 +41,13 @@ instance Arbitrary StorePathName where
instance Arbitrary StorePathHashPart where
arbitrary =
-- TODO(srk): other hashes
System.Nix.StorePath.mkStorePathHashPart @SHA256
. Data.ByteString.Char8.pack <$> arbitrary
oneof
[ System.Nix.StorePath.mkStorePathHashPart @MD5
. Data.ByteString.Char8.pack <$> arbitrary
, System.Nix.StorePath.mkStorePathHashPart @SHA1
. Data.ByteString.Char8.pack <$> arbitrary
, System.Nix.StorePath.mkStorePathHashPart @SHA256
. Data.ByteString.Char8.pack <$> arbitrary
, System.Nix.StorePath.mkStorePathHashPart @SHA512
. Data.ByteString.Char8.pack <$> arbitrary
]

View File

@ -0,0 +1,23 @@
-- due to recent generic-arbitrary
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module System.Nix.Arbitrary.StorePath.Metadata where
import System.Nix.Arbitrary.ContentAddress ()
import System.Nix.Arbitrary.Hash ()
import System.Nix.Arbitrary.Signature ()
import System.Nix.Arbitrary.StorePath ()
import System.Nix.StorePath (StorePath)
import System.Nix.StorePath.Metadata (Metadata, StorePathTrust)
import Test.QuickCheck (Arbitrary(..))
import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..))
import Test.QuickCheck.Instances ()
deriving via GenericArbitrary StorePathTrust
instance Arbitrary StorePathTrust
deriving via GenericArbitrary (Metadata StorePath)
instance Arbitrary (Metadata StorePath)