funflow-nix (empty) → 0.1.0.0
raw patch · 9 files changed
+380/−0 lines, 9 filesdep +asyncdep +basedep +containerssetup-changed
Dependencies added: async, base, containers, data-default, directory, filepath, funflow, funflow-nix, modern-uri, path, path-io, process, random, safe-exceptions, tasty, tasty-hunit, temporary, text, unix
Files
- ChangeLog.md +5/−0
- LICENSE +30/−0
- README.md +36/−0
- Setup.hs +2/−0
- examples/Simple.hs +56/−0
- funflow-nix.cabal +75/−0
- src/Control/Funflow/External/Nix.hs +73/−0
- tests/Test.hs +10/−0
- tests/TestFlows.hs +93/−0
+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for funflow-nix++## 0.1.0.0 -- 2018-11-16++* Initial Release
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2018, Matthew Pickering++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Matthew Pickering nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,36 @@+`funflow-nix` provides functions for creating flows which run in a nix+environment.++The library exposes the `NixConfig` data type which allows you to specify the+environment and command to run. This is then turned into a flow using `nix`.++A complete example can be seen in `examples/Simple.hs`.++We can pin the version of nixpkgs we want to use by specifying a tarball to+use as the source.++```+tarballSource :: NixpkgsSource+tarballSource = NixpkgsTarball [uri|https://github.com/NixOS/nixpkgs/archive/a19357241973538212b5cb435dde84ad25cbe337.tar.gz|]++nixConfig :: Environment -> NixConfig+nixConfig senv =+ NixShellConfig {+ environment = senv+ , command = "jq"+ , args = [ParamText "--version"]+ , env = []+ , stdout = StdOutCapture+ , nixpkgsSource = tarballSource+ }+```++Once the config has been specified. It can be turned into a flow by using the+`nix` function.+++```+jqVersionPkg :: SimpleFlow () String+jqVersionPkg = readString_ <<< nix (\() -> nixConfig (PackageList ["jq"]))+```+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ examples/Simple.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE Arrows #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+module Main(main) where++import Control.Arrow+import Control.Funflow+import Control.Funflow.ContentHashable+import Path+import Path.IO+import Control.Funflow.External.Nix+import Text.URI.QQ++nixConfig :: (NixpkgsSource, Environment) -> NixConfig+nixConfig (nps, senv) =+ NixShellConfig {+ environment = senv+ , command = "jq"+ , args = [ParamText "--version"]+ , env = []+ , stdout = StdOutCapture+ , nixpkgsSource = nps+ }++jqVersion :: SimpleFlow NixpkgsSource String+jqVersion = proc np -> do+ cwd <- stepIO (const getCurrentDir) -< ()+ shellScript <- copyFileToStore+ -< (FileContent (cwd </> [relfile|data/shell.nix|])+ ,[relfile|data/shell.nix|])+ readString_ <<< nix nixConfig -< (np, ShellFile shellScript)++jqVersionPkg :: SimpleFlow NixpkgsSource String+jqVersionPkg = readString_ <<< nix (\np -> nixConfig (np, PackageList ["jq"]))++tarballSource :: NixpkgsSource+tarballSource = NixpkgsTarball [uri|https://github.com/NixOS/nixpkgs/archive/a19357241973538212b5cb435dde84ad25cbe337.tar.gz|]++mainFlow :: SimpleFlow () (String, String)+mainFlow = proc _ -> do+ s1 <- jqVersion -< tarballSource+ s2 <- jqVersionPkg -< NIX_PATH+ returnA -< (s1, s2)++main :: IO ()+main = do+ cwd <- getCurrentDir+ r <- withSimpleLocalRunner (cwd </> [reldir|funflow-example/store|]) $ \run ->+ run mainFlow ()+ case r of+ Left err ->+ putStrLn $ "FAILED" ++ show err+ Right out -> do+ putStrLn $ "SUCCESS"+ print out+
+ funflow-nix.cabal view
@@ -0,0 +1,75 @@+-- Initial funflow-nix.cabal generated by cabal init. For further+-- documentation, see http://haskell.org/cabal/users-guide/++name: funflow-nix+version: 0.1.0.0+synopsis: Utility functions for using funflow with nix+description: This library provides functions to create flows which run commands+ in environments created by nix commands.+ It is designed to be like the docker integration but the environments+ are created by nix rather than in a container.+license: BSD3+license-file: LICENSE+author: Matthew Pickering+maintainer: matthewtpickering@gmail.com+-- copyright:+-- category:+build-type: Simple+extra-source-files: ChangeLog.md, README.md+cabal-version: >=1.10+tested-with: GHC==8.2.2++source-repository head+ type: git+ location: https://github.com/mpickering/funflow-nix.git++flag example+ Description: Build the example executable+ Default: False+ Manual: True++library+ exposed-modules: Control.Funflow.External.Nix+ -- other-modules:+ -- other-extensions:+ build-depends: base >=4.10 && < 5, funflow >= 1.4, text, path, modern-uri+ hs-source-dirs: src+ ghc-options: -Wall+ default-language: Haskell2010+++executable example+ if flag(example)+ buildable: True+ else+ buildable: False+ main-is: examples/Simple.hs+ build-depends: funflow, funflow-nix, path, path-io, modern-uri, base < 5+ ghc-options: -Wall+ default-language: Haskell2010++Test-suite unit-tests+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: tests+ main-is: Test.hs+ other-modules: TestFlows+ ghc-options: -Wall -threaded+ build-depends: base < 5+ , async+ , containers+ , data-default >= 0.7+ , directory+ , filepath+ , funflow+ , funflow-nix+ , path+ , path-io+ , process+ , random+ , safe-exceptions+ , tasty+ , tasty-hunit+ , temporary+ , unix+ , modern-uri
+ src/Control/Funflow/External/Nix.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE DeriveGeneric #-}+module Control.Funflow.External.Nix+ ( NixConfig(..)+ , NixpkgsSource(..)+ , Environment(..)+ , nix )where++import Control.Funflow+import Path+import Control.Funflow.ContentHashable+import Control.Funflow.ContentStore as CS+import Control.Funflow.External as E+import Data.Semigroup ((<>))+import qualified Data.Text as T+import GHC.Generics (Generic)+import Data.List+import Text.URI (URI)+import qualified Text.URI as URI++data NixpkgsSource = NIX_PATH -- ^ Inherit the @NIX_PATH@ from the environment+ | NixpkgsTarball URI -- ^ The 'URI' pointing to a nixpkgs tarball+ deriving Generic+++data NixConfig =+ NixShellConfig {+ environment :: Environment, -- ^ Specification of the nix environment+ nixpkgsSource :: NixpkgsSource, -- ^ Which version of nixpkgs to use+ command :: T.Text, -- ^ The command to run in the environment+ args :: [ParamField], -- ^ Arguments to pass to the command+ env :: [(T.Text, Param)], -- ^ Environmental variables which are set in the environment+ stdout :: OutputCapture+ }++data Environment = ShellFile (Content File) -- ^ Path to a shell.nix file+ | PackageList [T.Text] -- ^ A list of packages that+ -- will be passed by @-p@.+ deriving Generic++instance ContentHashable IO Environment where++instance ContentHashable IO NixpkgsSource where+ contentHashUpdate ctx NIX_PATH = contentHashUpdate_fingerprint ctx NIX_PATH+ contentHashUpdate ctx (NixpkgsTarball s) = contentHashUpdate ctx (URI.render s)++nixpkgsSourceToParam :: NixpkgsSource -> Param+nixpkgsSourceToParam NIX_PATH = envParam "NIX_PATH"+nixpkgsSourceToParam (NixpkgsTarball uri) = textParam ("nixpkgs=" <> URI.render uri)++toExternal :: NixConfig -> ExternalTask+toExternal NixShellConfig{..} = ExternalTask+ { _etCommand = "nix-shell"+ , _etParams =+ [ "--run"+ , Param [ParamCmd (Param (intersperse (ParamText " ") (ParamText command : args)))]+ ] ++ packageSpec environment+ , _etEnv = E.EnvExplicit $ ("NIX_PATH", nixpkgsSourceToParam nixpkgsSource ) : env+ , _etWriteToStdOut = stdout+ }+ where+ packageSpec :: Environment -> [Param]+ packageSpec (ShellFile ip) = [contentParam ip]+ packageSpec (PackageList ps) = [textParam ("-p " <> p) | p <- ps]+++-- | Constructor a flow from a @NixConfig@+nix :: (ContentHashable IO a, ArrowFlow eff ex arr) => (a -> NixConfig)+ -> arr a CS.Item+nix f = external $ toExternal . f
+ tests/Test.hs view
@@ -0,0 +1,10 @@+import qualified TestFlows+import Test.Tasty++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests = testGroup "Unit Tests"+ [ TestFlows.tests+ ]
+ tests/TestFlows.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE Arrows #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}++module TestFlows where++import Control.Arrow+import Control.Concurrent.Async (withAsync)+import Control.Exception.Safe hiding (catch)+import Control.Funflow+import Control.Funflow.ContentHashable+import qualified Control.Funflow.ContentStore as CS+import Control.Funflow.External.Coordinator.Memory+import Control.Funflow.External.Executor (executeLoop)+import Path+import Path.IO+import Test.Tasty+import Test.Tasty.HUnit+import Control.Funflow.External.Nix+import Text.URI.QQ++data FlowAssertion where+ FlowAssertion :: (Eq b, Show b)+ => String -- test name+ -> a -- input+ -> SimpleFlow a b -- the flow to test+ -> Maybe b --expected output - Nothing for expected failure+ -> IO () -- test setup action+ -> FlowAssertion++mkError :: String -> SomeException+mkError = toException . userError++nixConfig :: (NixpkgsSource, Environment) -> NixConfig+nixConfig (nps, senv) =+ NixShellConfig {+ environment = senv+ , command = "jq"+ , args = [ParamText "--version"]+ , env = []+ , stdout = StdOutCapture+ , nixpkgsSource = nps+ }++-- | Test that we can merge directories within the content store.+jqVersion :: SimpleFlow NixpkgsSource String+jqVersion = proc np -> do+ cwd <- stepIO (const getCurrentDir) -< ()+ shellScript <- copyFileToStore+ -< (FileContent (cwd </> [relfile|data/shell.nix|]),[relfile|data/shell.nix|])+ readString_ <<< nix nixConfig -< (np, ShellFile shellScript)++jqVersionPkg :: SimpleFlow NixpkgsSource String+jqVersionPkg = readString_ <<< nix (\np -> nixConfig (np, PackageList ["jq"]))++tarballSource :: NixpkgsSource+tarballSource = NixpkgsTarball [uri|https://github.com/NixOS/nixpkgs/archive/a19357241973538212b5cb435dde84ad25cbe337.tar.gz|]+++flowAssertions :: [FlowAssertion]+flowAssertions =+ [+ FlowAssertion "shell 1" NIX_PATH jqVersion (Just "jq-1.6\n") (return ())+ , FlowAssertion "shell 2" NIX_PATH jqVersionPkg (Just "jq-1.6\n") (return ())++ , FlowAssertion "shell tarball 1" tarballSource jqVersion (Just "jq-1.5\n") (return ())+ , FlowAssertion "shell tarball 2" tarballSource jqVersionPkg (Just "jq-1.5\n") (return ())+ ]++testFlowAssertion :: FlowAssertion -> TestTree+testFlowAssertion (FlowAssertion nm x flw expect before) =+ testCase nm $+ withSystemTempDir "test_output_" $ \storeDir ->+ CS.withStore storeDir $ \store -> do+ hook <- createMemoryCoordinator+ before+ res <- withAsync (executeLoop MemoryCoordinator hook store) $ \_ ->+ runSimpleFlow MemoryCoordinator hook store flw x+ assertFlowResult expect res++assertFlowResult :: (Eq a, Show ex, Show a) => Maybe a -> Either ex a -> Assertion+assertFlowResult expect res =+ case (expect, res) of+ (Nothing, Left _) -> return ()+ (Just xr, Right y) -> assertEqual "flow results" xr y+ (Nothing, Right y) -> assertFailure $ "expected flow failure, got success" ++ show y+ (Just xr, Left err) -> assertFailure $ "expected success "++ show xr++", got error" ++ show err++tests :: TestTree+tests = testGroup "Flow Assertions" $ map testFlowAssertion flowAssertions