1
1
mirror of https://github.com/nmattia/snack.git synced 2024-12-01 03:03:55 +03:00

Merge pull request #99 from nmattia/nm-discovery

Implement discovery mode for package and snack.nix files
This commit is contained in:
Nicolas Mattia 2019-01-19 18:30:30 +01:00 committed by GitHub
commit 51c56ad3a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 330 additions and 259 deletions

View File

@ -19,6 +19,11 @@ Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
- The `snack.nix` now describes the build environment and packages are - The `snack.nix` now describes the build environment and packages are
described through `package.nix` (i.e. to migrate: rename `snack.nix` to described through `package.nix` (i.e. to migrate: rename `snack.nix` to
`package.nix`). `package.nix`).
- The same flag (`-p`) is used for specifying both a YAML or Nix file. When
none is provided snack tries to use either `./package.yaml` or
`./package.nix`.
- The flag `-s` is used to specify a `snack.nix`. By default `./snack.nix` is
used.
### Fixed ### Fixed
- The module import parsing when the CPP extension is enabled. - The module import parsing when the CPP extension is enabled.

View File

@ -81,14 +81,24 @@ The _snack_ executable is now in your `PATH`:
``` shell ``` shell
$ snack --help $ snack --help
Usage: snack [-l|--lib DIR] [-b|--snack-nix PATH] [-j|--cores INT] Usage: <interactive> [-l|--lib DIR] ([-s|--snack-nix PATH] | [--no-snack-nix])
([-s|--package-nix PATH] | [-p|--package-yaml PATH]) COMMAND [-j|--cores INT] [-p|--package-file PATH] (COMMAND |
COMMAND)
Available options: Available options:
-l,--lib DIR Path to the directory to use as the Nix library -l,--lib DIR Path to the directory to use as the Nix library
instead of the default one bundled with the snack instead of the default one bundled with the snack
executable. executable.
-s,--snack-nix PATH Use the specified environment (snack.nix) file. When
none is provided ./snack.nix is used (if it exists).
(Use --no-snack-nix to disable this behavior)
--no-snack-nix Don't use ./snack.nix as the environment (snack.nix)
file.
-j,--cores INT How many cores to use during the build -j,--cores INT How many cores to use during the build
-p,--package-file PATH Specifies a YAML or Nix file to use as package
description. If not provided, snack looks for either
'package.yaml' or 'package.nix' in the current
directory.
-h,--help Show this help text -h,--help Show this help text
Available commands: Available commands:
@ -106,13 +116,10 @@ suite its own package description.
## Usage ## Usage
There are two ways to tell snack about a package; There are two ways to describe a package:
* Use [`--package-nix`](#nix) if you need more control over your build. * Use [`package.yaml` file](#hpack) for simple builds or if you already have
* Use [`--package-yaml`](#hpack) for simple builds or if you already have
a `package.yaml` file. a `package.yaml` file.
* Use a [`package.nix` file](#nix) if you need more control over your build.
If a package option is not supplied then snack will run as if
`--package-nix=package.nix` was given as the package option.
The next two sections show an example config for each option. They use the The next two sections show an example config for each option. They use the
following example project which displays the title of the top-rated post on the following example project which displays the title of the top-rated post on the
@ -190,19 +197,19 @@ default-extensions:
This command will build the project and display the top-rated post's title: This command will build the project and display the top-rated post's title:
``` shell ``` shell
$ snack run --package-yaml ./package.yaml $ snack run
``` ```
You can also build without executing: You can also build without executing:
``` shell ``` shell
$ snack build --package-yaml ./package.yaml $ snack build
``` ```
Alternatively you can load up the project in `ghci`: Alternatively you can load up the project in `ghci`:
``` shell ``` shell
$ snack ghci --package-yaml ./package.yaml $ snack ghci
GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help
[1 of 2] Compiling Lib ( /home/nicolas/projects/nmattia/snack/tests/readme/src/Lib.hs, interpreted ) [1 of 2] Compiling Lib ( /home/nicolas/projects/nmattia/snack/tests/readme/src/Lib.hs, interpreted )
[2 of 2] Compiling Main ( /home/nicolas/projects/nmattia/snack/tests/readme/app/Main.hs, interpreted ) [2 of 2] Compiling Main ( /home/nicolas/projects/nmattia/snack/tests/readme/app/Main.hs, interpreted )
@ -233,7 +240,7 @@ in
Building and running the project is as simple as Building and running the project is as simple as
``` shell ``` shell
$ snack run # looks for a file called package.nix by default $ snack run
``` ```
Alternatively, use `$ snack build` or `$ snack ghci` if you only want to build, Alternatively, use `$ snack build` or `$ snack ghci` if you only want to build,
@ -310,7 +317,7 @@ If you are hacking on the _snack_ executable, just start _snack_ in a GHCi
session: session:
``` shell ``` shell
$ snack ghci -s ./bin/package.nix $ snack ghci -p ./bin/package.nix
Temporarily symlinking /nix/store/j1x5vkxjr2ibabddfkdih4sm4kwinfda-spec-json/spec.json to spec.json... Temporarily symlinking /nix/store/j1x5vkxjr2ibabddfkdih4sm4kwinfda-spec-json/spec.json to spec.json...
done. done.
Temporarily symlinking /nix/store/w42y6dzgfmli9r8kmgh8akqk6kyda31x-lib64/lib.tar.gz.b64 to lib.tar.gz.b64... Temporarily symlinking /nix/store/w42y6dzgfmli9r8kmgh8akqk6kyda31x-lib64/lib.tar.gz.b64 to lib.tar.gz.b64...
@ -325,7 +332,7 @@ If you are hacking on the library, specify `-l/--lib` when running snack (this
works in GHCi too): works in GHCi too):
``` shell ``` shell
*Main> :main ghci -l ./snack-lib/ -s ./tests/readme/package.nix *Main> :main ghci -l ./snack-lib/ -p ./tests/readme/package.nix
GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help
[1 of 2] Compiling Lib ( /home/nicolas/projects/nmattia/snack/tests/readme/src/Lib.hs, interpreted ) [1 of 2] Compiling Lib ( /home/nicolas/projects/nmattia/snack/tests/readme/src/Lib.hs, interpreted )
[2 of 2] Compiling Main ( /home/nicolas/projects/nmattia/snack/tests/readme/app/Main.hs, interpreted ) [2 of 2] Compiling Main ( /home/nicolas/projects/nmattia/snack/tests/readme/app/Main.hs, interpreted )

View File

@ -15,13 +15,13 @@ module Main (main) where
import Control.Applicative import Control.Applicative
import Control.Monad import Control.Monad
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Data.Aeson (FromJSON, (.:), (.:?)) import Data.Aeson (FromJSON, (.:))
import Data.FileEmbed (embedStringFile) import Data.FileEmbed (embedStringFile)
import Data.List (intercalate) import Data.List (intercalate)
import Data.Semigroup ((<>)) import Data.Semigroup ((<>))
import Data.String.Interpolate import Data.String.Interpolate
import Shelly (Sh) import Shelly (Sh)
import System.Directory (canonicalizePath) import System.Directory (doesFileExist, doesPathExist, canonicalizePath)
import System.Posix.Process (executeFile) import System.Posix.Process (executeFile)
import UnliftIO.Exception import UnliftIO.Exception
import qualified Data.Aeson as Aeson import qualified Data.Aeson as Aeson
@ -32,7 +32,17 @@ import qualified Data.Text as T
import qualified Options.Applicative as Opts import qualified Options.Applicative as Opts
import qualified Shelly as S import qualified Shelly as S
--- main :: IO ()
main = do
opts <-
prepareOptions =<<
Opts.execParser (Opts.info (parseOptions <**> Opts.helper) mempty)
runCommand (snackConfig opts) (package opts) (command opts)
-------------------------------------------------------------------------------
-- Configuration
-------------------------------------------------------------------------------
--- Some config helpers --- Some config helpers
data ConfigStage data ConfigStage
@ -43,99 +53,126 @@ type family Config (c :: ConfigStage) ty1 ty2 where
Config 'ConfigRaw ty1 _ = ty1 Config 'ConfigRaw ty1 _ = ty1
Config 'ConfigReady _ ty2 = ty2 Config 'ConfigReady _ ty2 = ty2
---
--- Configuration proper --- Configuration proper
type Mode = Mode_ 'ConfigReady
type ModeRaw = Mode_ 'ConfigRaw
data Mode_ c
= Standalone (Config c FilePath PackageNix) -- Reads a package.nix file
| HPack (Config c FilePath PackageYaml) -- Reads a package.yaml
prepareMode :: ModeRaw -> IO Mode
prepareMode = \case
Standalone fp -> Standalone <$> mkPackageNix fp
HPack fp -> HPack <$> mkPackageYaml fp
-- | Like a FilePath, but Nix friendly
newtype PackageNix = PackageNix { unPackageNix :: FilePath }
mkPackageNix :: FilePath -> IO PackageNix
mkPackageNix = fmap PackageNix . canonicalizePath
-- | Like a FilePath, but Nix friendly -- | Like a FilePath, but Nix friendly
newtype SnackNix = SnackNix FilePath newtype SnackNix = SnackNix FilePath
data SnackNixConfig
= SnackNixSpecific FilePath
| SnackNixDiscovery
| SnackNixNone
parseSnackNixConfig :: Opts.Parser SnackNixConfig
parseSnackNixConfig =
SnackNixSpecific <$> Opts.strOption
(Opts.long "snack-nix"
<> Opts.short 's'
<> Opts.metavar "PATH"
<> Opts.help (unlines
[ "Use the specified environment (snack.nix) file."
, "When none is provided ./snack.nix is used (if it exists)."
, "(Use --no-snack-nix to disable this behavior)"
])
) <|>
Opts.flag'
SnackNixNone
(Opts.long "no-snack-nix"
<> Opts.help "Don't use ./snack.nix as the environment (snack.nix) file."
) <|>
pure SnackNixDiscovery
prepareSnackNix :: SnackNixConfig -> IO (Maybe SnackNix)
prepareSnackNix = \case
SnackNixNone -> pure Nothing
SnackNixSpecific fp -> Just <$> mkSnackNix fp
SnackNixDiscovery -> discoverSnackNix
discoverSnackNix :: IO (Maybe SnackNix)
discoverSnackNix = do
eSNix <- mkSnackNixEither "snack.nix"
case eSNix of
Left{} -> pure Nothing
Right sn -> pure (Just sn)
mkSnackNix :: FilePath -> IO SnackNix mkSnackNix :: FilePath -> IO SnackNix
mkSnackNix = fmap SnackNix . canonicalizePath mkSnackNix = fmap SnackNix . mkFilePath
mkSnackNixEither :: FilePath -> IO (Either String SnackNix)
mkSnackNixEither fp = fmap SnackNix <$> mkFilePathEither fp
-- | Like a FilePath, but Nix friendly -- | Like a FilePath, but Nix friendly
newtype SnackLib = SnackLib FilePath newtype SnackLib = SnackLib FilePath
mkSnackLib :: FilePath -> IO SnackLib mkSnackLib :: FilePath -> IO SnackLib
mkSnackLib = fmap SnackLib . canonicalizePath mkSnackLib = fmap SnackLib . mkDirPath
--- Package description (@package.yaml@, @package.nix@)
-- | Like a FilePath, but Nix friendly -- | Like a FilePath, but Nix friendly
newtype PackageYaml = PackageYaml { unPackageYaml :: FilePath } newtype PackageFile = PackageFile { unPackageFile :: FilePath }
mkPackageYaml :: FilePath -> IO PackageYaml -- | What package description (@package.yaml@, @package.nix@) to use
mkPackageYaml = fmap PackageYaml . canonicalizePath data PackageFileConfig
= PackageFileSpecific FilePath
-- ^ Use the specified file as package description
| PackageFileDiscovery
-- ^ Find a suitable package description
parsePackageFileConfig :: Opts.Parser PackageFileConfig
parsePackageFileConfig =
(PackageFileSpecific <$>
Opts.strOption
(Opts.long "package-file"
<> Opts.short 'p'
<> Opts.help (unlines
[ "Specifies a YAML or Nix file to use as package description."
, "If not provided, snack looks for either 'package.yaml' or 'package.nix' in the current directory."
]
)
<> Opts.metavar "PATH")
) <|> pure PackageFileDiscovery
-- Finding the package descriptions
mkPackageFileEither :: FilePath -> IO (Either String PackageFile)
mkPackageFileEither = fmap (fmap PackageFile) . mkFilePathEither
mkPackageFile :: FilePath -> IO PackageFile
mkPackageFile = fmap PackageFile . mkFilePath
preparePackage :: PackageFileConfig -> IO PackageFile
preparePackage = \case
PackageFileSpecific fp -> mkPackageFile fp
PackageFileDiscovery -> discoverPackageFile
-- | Tries to find a package description.
discoverPackageFile :: IO PackageFile
discoverPackageFile = do
eYaml <- mkPackageFileEither "package.yaml"
eNix <- mkPackageFileEither "package.nix"
case (eYaml, eNix) of
(Right (PackageFile yaml), Right (PackageFile nix)) ->
throwIO $ userError $ unlines
[ "Please specify which package file to use, e.g.: "
, " snack -p " <> yaml, "or"
, " snack -p " <> nix
]
(Right yaml, Left{}) -> pure yaml
(Left{}, Right nix) -> pure nix
(Left e1, Left e2) -> throwIO $ userError $ unlines
[ "Could not discover package file:"
, e1, e2
, "Please specify one with e.g.:"
, " snack -p <path-to-yaml-or-nix>"
]
--- Nix configuration
-- | How to call @nix-build@ -- | How to call @nix-build@
newtype NixConfig = NixConfig newtype NixConfig = NixConfig
{ nixNCores :: Int } { nixNCores :: Int }
type SnackConfig = SnackConfig_ 'ConfigReady
type SnackConfigRaw = SnackConfig_ 'ConfigRaw
-- | Extra configuration for snack
data SnackConfig_ c = SnackConfig
{ snackLib :: Config c (Maybe FilePath) (Maybe SnackLib)
, snackNix :: Maybe (Config c FilePath SnackNix)
, snackNixCfg :: NixConfig
}
data Command
= Build
| Run [String] -- Run with extra args
| Ghci
| Test
main :: IO ()
main = do
opts <-
prepareOptions =<<
Opts.execParser (Opts.info (parseOptions <**> Opts.helper) mempty)
runCommand (snackConfig opts) (mode opts) (command opts)
type OptionsRaw = Options_ 'ConfigRaw
type Options = Options_ 'ConfigReady
data Options_ c = Options
{ snackConfig :: SnackConfig_ c
, mode :: Mode_ c
, command :: Command
}
prepareOptions :: OptionsRaw -> IO Options
prepareOptions raw =
Options <$>
prepareSnackConfig (snackConfig raw) <*>
prepareMode (mode raw) <*>
pure (command raw)
prepareSnackConfig :: SnackConfigRaw -> IO SnackConfig
prepareSnackConfig raw =
SnackConfig <$>
(case snackLib raw of
Nothing -> pure Nothing
Just fp -> Just <$> mkSnackLib fp
) <*>
forM (snackNix raw) mkSnackNix <*>
pure (snackNixCfg raw)
parseNixConfig :: Opts.Parser NixConfig parseNixConfig :: Opts.Parser NixConfig
parseNixConfig = parseNixConfig =
(NixConfig <$> (NixConfig <$>
@ -147,6 +184,25 @@ parseNixConfig =
<> Opts.help "How many cores to use during the build") <> Opts.help "How many cores to use during the build")
) )
--- Snack configuration (unrelated to packages)
type SnackConfig = SnackConfig_ 'ConfigReady
type SnackConfigRaw = SnackConfig_ 'ConfigRaw
-- | Extra configuration for snack
data SnackConfig_ c = SnackConfig
{ snackLib :: Maybe (Config c FilePath SnackLib)
, snackNix :: Config c SnackNixConfig (Maybe SnackNix)
, snackNixCfg :: NixConfig
}
prepareSnackConfig :: SnackConfigRaw -> IO SnackConfig
prepareSnackConfig raw =
SnackConfig <$>
forM (snackLib raw) mkSnackLib <*>
prepareSnackNix (snackNix raw) <*>
pure (snackNixCfg raw)
parseSnackConfig :: Opts.Parser SnackConfigRaw parseSnackConfig :: Opts.Parser SnackConfigRaw
parseSnackConfig = SnackConfig <$> Opts.optional parseSnackConfig = SnackConfig <$> Opts.optional
(Opts.strOption (Opts.strOption
@ -160,40 +216,56 @@ parseSnackConfig = SnackConfig <$> Opts.optional
] ]
) )
) )
) <*> ) <*> parseSnackNixConfig <*>
Opts.optional (
Opts.strOption
(Opts.long "snack-nix"
<> Opts.short 'b'
<> Opts.metavar "PATH")
) <*>
parseNixConfig parseNixConfig
parseMode :: Opts.Parser ModeRaw -- | What command to execute
parseMode = data Command
(Standalone <$> = Build
Opts.strOption | Run [String] -- Run with extra args
(Opts.long "package-nix" | Ghci
<> Opts.short 's' | Test
<> Opts.value "./package.nix"
<> Opts.metavar "PATH") parseCommand :: Opts.Parser Command
) parseCommand =
<|> Opts.hsubparser
(HPack <$> ( Opts.command "build" (Opts.info (pure Build) mempty)
Opts.strOption <> Opts.command "run" (Opts.info
(Opts.long "package-yaml" ( Run <$> Opts.many (Opts.argument Opts.str (Opts.metavar "ARG"))
<> Opts.value "./package.yaml" ) mempty)
<> Opts.short 'p' <> Opts.command "ghci" (Opts.info (pure Ghci) mempty)
<> Opts.metavar "PATH") )
) <|> Opts.hsubparser
( Opts.command "test" (Opts.info (pure Test) (Opts.progDesc "Use build, run or ghci commands with test suites."))
<> Opts.commandGroup "Unavailable commands:"
)
type OptionsRaw = Options_ 'ConfigRaw
type Options = Options_ 'ConfigReady
-- | The whole set of CLI options
data Options_ c = Options
{ snackConfig :: SnackConfig_ c
, package :: Config c PackageFileConfig PackageFile
, command :: Command
}
prepareOptions :: OptionsRaw -> IO Options
prepareOptions raw =
Options <$>
prepareSnackConfig (snackConfig raw) <*>
preparePackage (package raw) <*>
pure (command raw)
parseOptions :: Opts.Parser OptionsRaw parseOptions :: Opts.Parser OptionsRaw
parseOptions = parseOptions =
Options <$> Options <$>
parseSnackConfig <*> parseSnackConfig <*>
parseMode <*> parsePackageFileConfig <*>
parseCommand parseCommand
--- Build related types used when interfacing with Nix
newtype ModuleName = ModuleName T.Text newtype ModuleName = ModuleName T.Text
deriving newtype (Ord, Eq, Aeson.FromJSONKey) deriving newtype (Ord, Eq, Aeson.FromJSONKey)
deriving stock Show deriving stock Show
@ -240,17 +312,16 @@ instance FromJSON ExecutableBuild where
parseJSON = Aeson.withObject "executable build" $ \o -> parseJSON = Aeson.withObject "executable build" $ \o ->
ExecutableBuild <$> o .: "exe_path" ExecutableBuild <$> o .: "exe_path"
data MultiBuild = MultiBuild newtype MultiBuild = MultiBuild
{ _librayBuild :: Maybe LibraryBuild { executableBuilds :: Map.Map T.Text ExecutableBuild
, executableBuilds :: Map.Map T.Text ExecutableBuild
} }
deriving stock Show deriving stock Show
instance Aeson.FromJSON MultiBuild where instance Aeson.FromJSON MultiBuild where
parseJSON = Aeson.withObject "multi build" $ \o -> parseJSON = Aeson.withObject "multi build" $ \o ->
MultiBuild MultiBuild <$> o .: "executables"
<$> o .:? "library"
<*> o .: "executables" --- Type-helpers for passing arguments to Nix
data NixArg = NixArg data NixArg = NixArg
{ argType :: NixArgType { argType :: NixArgType
@ -268,12 +339,6 @@ newtype NixPath = NixPath T.Text
deriving newtype FromJSON deriving newtype FromJSON
deriving stock Show deriving stock Show
decodeOrFail :: FromJSON a => BS.ByteString -> Sh a
decodeOrFail bs = case Aeson.decodeStrict' bs of
Just foo -> pure foo
Nothing -> throwIO $ userError $ unlines
[ "could not decode " <> show bs ]
nixBuild :: SnackConfig -> [NixArg] -> NixExpr -> Sh NixPath nixBuild :: SnackConfig -> [NixArg] -> NixExpr -> Sh NixPath
nixBuild snackCfg extraNixArgs nixExpr = nixBuild snackCfg extraNixArgs nixExpr =
NixPath <$> runStdin1 NixPath <$> runStdin1
@ -343,71 +408,38 @@ nixBuild snackCfg extraNixArgs nixExpr =
: [ argName narg , argValue narg ] : [ argName narg , argValue narg ]
nixCfg = snackNixCfg snackCfg nixCfg = snackNixCfg snackCfg
snackBuild :: SnackConfig -> PackageNix -> Sh BuildResult snackBuild :: SnackConfig -> PackageFile -> Sh BuildResult
snackBuild snackCfg packageNix = do snackBuild snackCfg packageFile = do
NixPath out <- nixBuild snackCfg NixPath out <- nixBuild snackCfg
[ NixArg [ NixArg
{ argName = "packageNix" { argName = "packageFile"
, argValue = T.pack $ unPackageNix packageNix , argValue = T.pack $ unPackageFile packageFile
, argType = Arg , argType = Arg
} }
] ]
$ NixExpr "snack.inferSnackBuild packageNix" $ NixExpr "snack.inferBuild packageFile"
decodeOrFail =<< liftIO (BS.readFile $ T.unpack out) decodeOrFail =<< liftIO (BS.readFile $ T.unpack out)
snackGhci :: SnackConfig -> PackageNix -> Sh GhciBuild snackGhci :: SnackConfig -> PackageFile -> Sh GhciBuild
snackGhci snackCfg packageNix = do snackGhci snackCfg packageFile = do
NixPath out <- nixBuild snackCfg NixPath out <- nixBuild snackCfg
[ NixArg [ NixArg
{ argName = "packageNix" { argName = "packageFile"
, argValue = T.pack $ unPackageNix packageNix , argValue = T.pack $ unPackageFile packageFile
, argType = Arg , argType = Arg
} }
] ]
$ NixExpr "snack.inferSnackGhci packageNix" $ NixExpr "snack.inferGhci packageFile"
liftIO (BS.readFile (T.unpack out)) >>= decodeOrFail >>= \case liftIO (BS.readFile (T.unpack out)) >>= decodeOrFail >>= \case
BuiltGhci g -> pure g BuiltGhci g -> pure g
b -> throwIO $ userError $ "Expected GHCi build, got " <> show b b -> throwIO $ userError $ "Expected GHCi build, got " <> show b
snackBuildHPack :: SnackConfig -> PackageYaml -> Sh BuildResult runCommand :: SnackConfig -> PackageFile -> Command -> IO ()
snackBuildHPack snackCfg packageYaml = do runCommand snackCfg packageFile = \case
NixPath out <- nixBuild snackCfg Build -> S.shelly $ void $ snackBuild snackCfg packageFile
[ NixArg Run args -> quiet (snackBuild snackCfg packageFile) >>= runBuildResult args
{ argName = "packageYaml"
, argValue = T.pack $ unPackageYaml packageYaml
, argType = Arg
}
]
$ NixExpr "snack.inferHPackBuild packageYaml"
decodeOrFail =<< liftIO (BS.readFile (T.unpack out))
snackGhciHPack :: SnackConfig -> PackageYaml -> Sh GhciBuild
snackGhciHPack snackCfg packageYaml = do
NixPath out <- nixBuild snackCfg
[ NixArg
{ argName = "packageYaml"
, argValue = T.pack $ unPackageYaml packageYaml
, argType = Arg
}
]
$ NixExpr "snack.inferHPackGhci packageYaml"
liftIO (BS.readFile (T.unpack out)) >>= decodeOrFail >>= \case
BuiltGhci g -> pure g
b -> throwIO $ userError $ "Expected GHCi build, got " <> show b
runCommand :: SnackConfig -> Mode -> Command -> IO ()
runCommand snackCfg (Standalone packageNix) = \case
Build -> S.shelly $ void $ snackBuild snackCfg packageNix
Run args -> quiet (snackBuild snackCfg packageNix) >>= runBuildResult args
Ghci -> flip runExe [] =<< Ghci -> flip runExe [] =<<
ghciExePath <$> (quiet (snackGhci snackCfg packageNix)) ghciExePath <$> (quiet (snackGhci snackCfg packageFile))
Test -> noTest
runCommand snackCfg (HPack packageYaml) = \case
Build -> S.shelly $ void $ snackBuildHPack snackCfg packageYaml
Run args ->
quiet (snackBuildHPack snackCfg packageYaml) >>= runBuildResult args
Ghci -> flip runExe [] =<<
ghciExePath <$> quiet (snackGhciHPack snackCfg packageYaml)
Test -> noTest Test -> noTest
noTest :: IO a noTest :: IO a
@ -421,29 +453,46 @@ runBuildResult args = \case
runExe exe args runExe exe args
b -> fail $ "Unexpected build type: " <> show b b -> fail $ "Unexpected build type: " <> show b
quiet :: Sh a -> IO a
quiet = S.shelly . S.print_stdout False
runExe :: NixPath -> [String] -> IO () runExe :: NixPath -> [String] -> IO ()
runExe (NixPath fp) args = executeFile (T.unpack fp) True args Nothing runExe (NixPath fp) args = executeFile (T.unpack fp) True args Nothing
parseCommand :: Opts.Parser Command specJson :: T.Text
parseCommand = specJson = $(embedStringFile "spec.json")
Opts.hsubparser
( Opts.command "build" (Opts.info (pure Build) mempty)
<> Opts.command "run" (Opts.info
( Run <$> Opts.many (Opts.argument Opts.str (Opts.metavar "ARG"))
) mempty)
<> Opts.command "ghci" (Opts.info (pure Ghci) mempty)
)
<|> Opts.hsubparser
( Opts.command "test" (Opts.info (pure Test) (Opts.progDesc "Use build, run or ghci commands with test suites."))
<> Opts.commandGroup "Unavailable commands:"
)
libb64 :: T.Text
libb64 = $(embedStringFile "lib.tar.gz.b64")
--- Auxiliary
mkDirPath :: FilePath -> IO FilePath
mkDirPath fp = doesPathExist fp >>= \case
True -> doesFileExist fp >>= \case
True -> throwIO $ userError $ fp <> " is a file"
False -> canonicalizePath fp
False -> throwIO $ userError $ fp <> " does not exist"
mkFilePath :: FilePath -> IO FilePath
mkFilePath fp =
mkFilePathEither fp >>= either (throwIO . userError) pure
mkFilePathEither :: FilePath -> IO (Either String FilePath)
mkFilePathEither fp = doesFileExist fp >>= \case
True -> Right <$> canonicalizePath fp
False -> doesPathExist fp >>= \case
True -> pure (Left (fp <> " is a directory"))
False -> pure (Left (fp <> " does not exist"))
decodeOrFail :: FromJSON a => BS.ByteString -> Sh a
decodeOrFail bs = case Aeson.decodeStrict' bs of
Just foo -> pure foo
Nothing -> throwIO $ userError $ unlines
[ "could not decode " <> show bs ]
-- | Run the executable with given arguments
run :: S.FilePath -> [T.Text] -> Sh [T.Text] run :: S.FilePath -> [T.Text] -> Sh [T.Text]
run p args = T.lines <$> S.run p args run p args = T.lines <$> S.run p args
-- | Run the executable with given arguments, assuming a single line of output
runStdin1 :: T.Text -> S.FilePath -> [T.Text] -> Sh T.Text runStdin1 :: T.Text -> S.FilePath -> [T.Text] -> Sh T.Text
runStdin1 stin p args = do runStdin1 stin p args = do
S.setStdin stin S.setStdin stin
@ -451,8 +500,5 @@ runStdin1 stin p args = do
[out] -> pure out [out] -> pure out
xs -> throwIO $ userError $ "unexpected output: " <> show xs xs -> throwIO $ userError $ "unexpected output: " <> show xs
specJson :: T.Text quiet :: Sh a -> IO a
specJson = $(embedStringFile "spec.json") quiet = S.shelly . S.print_stdout False
libb64 :: T.Text
libb64 = $(embedStringFile "lib.tar.gz.b64")

View File

@ -68,7 +68,7 @@ for t in $SNACK_TESTS; do
done done
for t in $SNACK_TESTS; do for t in $SNACK_TESTS; do
banner "Test $name" banner "Test $t"
pushd "tests/$t" pushd "tests/$t"
./test ./test
popd popd

View File

@ -1,6 +1,7 @@
# This is the entry point of the library, and badly needs documentation. # This is the entry point of the library, and badly needs documentation.
# TODO: currently single out derivations prepend the PWD to the path # TODO: currently single out derivations prepend the PWD to the path
# TODO: make sure that filters for "base" are airtight # TODO: make sure that filters for "base" are airtight
# TODO: document the sh*t out of these functions
{ pkgs { pkgs
, ghc-version ? "ghc822" , ghc-version ? "ghc822"
, ghcWithPackages ? pkgs.haskell.packages.${ghc-version}.ghcWithPackages , ghcWithPackages ? pkgs.haskell.packages.${ghc-version}.ghcWithPackages
@ -67,6 +68,37 @@ let
exe_path = "${drv.out}/${drv.relExePath}"; exe_path = "${drv.out}/${drv.relExePath}";
}; };
# TODO: deduplicate extensions + update README with --package-file
inferBuild = packageFile:
let
basename = builtins.baseNameOf packageFile;
components = pkgs.lib.strings.splitString "." basename;
ext =
if pkgs.lib.length components <= 1
then abort ("File " ++ packageFile ++ " does not have an extension")
else pkgs.lib.last components;
build =
if ext == "nix" then inferSnackBuild
else if ext == "yaml" then inferHPackBuild
else if ext == "yml" then inferHPackBuild
else abort ("Unknown extension " ++ ext ++ " of file " ++ packageFile);
in build packageFile;
inferGhci = packageFile:
let
basename = builtins.baseNameOf packageFile;
components = pkgs.lib.strings.splitString "." basename;
ext =
if pkgs.lib.length components <= 1
then abort ("File " ++ packageFile ++ " does not have an extension")
else pkgs.lib.last components;
ghci =
if ext == "nix" then inferSnackGhci
else if ext == "yaml" then inferHPackGhci
else if ext == "yml" then inferHPackGhci
else abort ("Unknown extension " ++ ext ++ " of file " ++ packageFile);
in ghci packageFile;
inferSnackBuild = packageNix: mkPackage (import packageNix); inferSnackBuild = packageNix: mkPackage (import packageNix);
inferSnackGhci = packageNix: writeText "snack-ghci-json" inferSnackGhci = packageNix: writeText "snack-ghci-json"
@ -152,6 +184,8 @@ let
in in
{ {
inherit inherit
inferBuild
inferGhci
inferSnackBuild inferSnackBuild
inferSnackGhci inferSnackGhci
inferHPackBuild inferHPackBuild
@ -160,7 +194,7 @@ in
buildAsExecutable buildAsExecutable
buildAsLibrary buildAsLibrary
snackSpec snackSpec
hpackSpec hpackSpecs
mkPackage mkPackage
; ;
} }

View File

@ -8,7 +8,6 @@ test() {
$SNACK run $SNACK run
} }
SNACK="snack -j4" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test SNACK="snack -j4 --package-file ./package.yaml" test
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -1,7 +0,0 @@
{ main = "Foo";
src = ./src;
dependencies = [
"conduit"
"something-that-doesnt-exist"
];
}

View File

@ -16,6 +16,5 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack -j4 --snack-nix snack.nix --package-nix package.nix" test SNACK="snack -j4 --snack-nix snack.nix --package-file package.yaml" test
SNACK="snack -j4 --snack-nix snack.nix" test SNACK="snack -j4" test
SNACK="snack -j4 --snack-nix snack.nix --package-yaml package.yaml" test

View File

@ -15,7 +15,6 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack -j4" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test SNACK="snack -j4 --package-file ./package.yaml" test
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -8,6 +8,8 @@ TMP_DIR=$(mktemp -d)
git clone http://github.com/nmattia/pboy.git $TMP_DIR git clone http://github.com/nmattia/pboy.git $TMP_DIR
git -C $TMP_DIR reset --hard a2458d6984930a33a3b1972cb6d5c167d2511b06 git -C $TMP_DIR reset --hard a2458d6984930a33a3b1972cb6d5c167d2511b06
snack -j4 build --package-yaml $TMP_DIR/package.yaml pushd $TMP_DIR
snack -j4 build
popd
rm -rf $TMP_DIR rm -rf $TMP_DIR

View File

@ -16,6 +16,6 @@ test() {
} }
SNACK="snack -j4" test SNACK="snack -j4" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
# Note: no HPack test, because HPack doesn't support multi library # Note: no HPack test, because HPack doesn't support multi library

View File

@ -16,6 +16,6 @@ test() {
} }
SNACK="snack" test SNACK="snack" test
SNACK="snack -s ./package.nix" test SNACK="snack --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
# Note: no HPack test, because HPack doesn't support multi library # Note: no HPack test, because HPack doesn't support multi library

View File

@ -15,6 +15,5 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack -j4" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.yaml" test
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -15,6 +15,5 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack -j4" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.yaml" test
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -16,6 +16,5 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack -j4" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.yaml" test
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -16,6 +16,5 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack -j4" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.yaml" test
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -8,7 +8,6 @@ test() {
$SNACK run $SNACK run
} }
SNACK="snack -j4" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test SNACK="snack -j4 --package-file ./package.yaml" test
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -8,4 +8,4 @@ test() {
$SNACK run $SNACK run
} }
SNACK="snack -j4 --package-yaml ./package.yaml" test SNACK="snack -j4" test

View File

@ -16,10 +16,5 @@ test() {
} }
SNACK="snack -j4" test SNACK="snack -j4 -p ./package.nix" test
SNACK="snack -j4 -p ./package.yaml" test
# TODO: Fix cannot coerce a list to a string, at /...-snack-lib/files.nix:66:12
SNACK="snack -j4 -s ./package.nix" test
# TODO: Fix cannot coerce a list to a string, at /...-snack-lib/hpack.nix:60:37
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -15,6 +15,5 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack -j4" test SNACK="snack -j4 --package-file ./package.nix" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.yaml" test
SNACK="snack -j4 --package-yaml ./package.yaml" test

View File

@ -3,12 +3,12 @@
set -euo pipefail set -euo pipefail
snack -j4 build -s code/package.nix snack -j4 build --package-file code/package.nix
snack -j4 run -s code/package.nix | diff golden - snack -j4 run --package-file code/package.nix | diff golden -
TMP_FILE=$(mktemp) TMP_FILE=$(mktemp)
capture_io "$TMP_FILE" main | snack -j4 -s code/package.nix ghci capture_io "$TMP_FILE" main | snack -j4 --package-file code/package.nix ghci
diff golden $TMP_FILE diff golden $TMP_FILE
rm $TMP_FILE rm $TMP_FILE

View File

@ -16,5 +16,5 @@ test() {
} }
SNACK="snack -j4" test SNACK="snack -j4" test
SNACK="snack -j4 -s ./package.nix" test SNACK="snack -j4 --package-file ./package.nix" test
# Note: no HPack test, because HPack doesn't support multi library # Note: no HPack test, because HPack doesn't support multi library

View File

@ -15,6 +15,5 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack" test SNACK="snack --package-file ./package.nix" test
SNACK="snack -s ./package.nix" test SNACK="snack --package-file ./package.yaml" test
SNACK="snack --package-yaml ./package.yaml" test

View File

@ -15,6 +15,5 @@ test() {
rm $TMP_FILE rm $TMP_FILE
} }
SNACK="snack" test SNACK="snack --package-file ./package.nix" test
SNACK="snack -s ./package.nix" test SNACK="snack --package-file ./package.yaml" test
SNACK="snack --package-yaml ./package.yaml" test