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:
commit
51c56ad3a3
@ -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
|
||||
described through `package.nix` (i.e. to migrate: rename `snack.nix` to
|
||||
`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
|
||||
- The module import parsing when the CPP extension is enabled.
|
||||
|
35
README.md
35
README.md
@ -81,14 +81,24 @@ The _snack_ executable is now in your `PATH`:
|
||||
|
||||
``` shell
|
||||
$ snack --help
|
||||
Usage: snack [-l|--lib DIR] [-b|--snack-nix PATH] [-j|--cores INT]
|
||||
([-s|--package-nix PATH] | [-p|--package-yaml PATH]) COMMAND
|
||||
Usage: <interactive> [-l|--lib DIR] ([-s|--snack-nix PATH] | [--no-snack-nix])
|
||||
[-j|--cores INT] [-p|--package-file PATH] (COMMAND |
|
||||
COMMAND)
|
||||
|
||||
Available options:
|
||||
-l,--lib DIR Path to the directory to use as the Nix library
|
||||
instead of the default one bundled with the snack
|
||||
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
|
||||
-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
|
||||
|
||||
Available commands:
|
||||
@ -106,13 +116,10 @@ suite its own package description.
|
||||
|
||||
## Usage
|
||||
|
||||
There are two ways to tell snack about a package;
|
||||
* Use [`--package-nix`](#nix) if you need more control over your build.
|
||||
* Use [`--package-yaml`](#hpack) for simple builds or if you already have
|
||||
There are two ways to describe a package:
|
||||
* Use [`package.yaml` file](#hpack) for simple builds or if you already have
|
||||
a `package.yaml` file.
|
||||
|
||||
If a package option is not supplied then snack will run as if
|
||||
`--package-nix=package.nix` was given as the package option.
|
||||
* Use a [`package.nix` file](#nix) if you need more control over your build.
|
||||
|
||||
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
|
||||
@ -190,19 +197,19 @@ default-extensions:
|
||||
This command will build the project and display the top-rated post's title:
|
||||
|
||||
``` shell
|
||||
$ snack run --package-yaml ./package.yaml
|
||||
$ snack run
|
||||
```
|
||||
|
||||
You can also build without executing:
|
||||
|
||||
``` shell
|
||||
$ snack build --package-yaml ./package.yaml
|
||||
$ snack build
|
||||
```
|
||||
|
||||
Alternatively you can load up the project in `ghci`:
|
||||
|
||||
``` shell
|
||||
$ snack ghci --package-yaml ./package.yaml
|
||||
$ snack ghci
|
||||
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 )
|
||||
[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
|
||||
|
||||
``` 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,
|
||||
@ -310,7 +317,7 @@ If you are hacking on the _snack_ executable, just start _snack_ in a GHCi
|
||||
session:
|
||||
|
||||
``` 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...
|
||||
done.
|
||||
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):
|
||||
|
||||
``` 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
|
||||
[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 )
|
||||
|
412
bin/Snack.hs
412
bin/Snack.hs
@ -15,13 +15,13 @@ module Main (main) where
|
||||
import Control.Applicative
|
||||
import Control.Monad
|
||||
import Control.Monad.IO.Class
|
||||
import Data.Aeson (FromJSON, (.:), (.:?))
|
||||
import Data.Aeson (FromJSON, (.:))
|
||||
import Data.FileEmbed (embedStringFile)
|
||||
import Data.List (intercalate)
|
||||
import Data.Semigroup ((<>))
|
||||
import Data.String.Interpolate
|
||||
import Shelly (Sh)
|
||||
import System.Directory (canonicalizePath)
|
||||
import System.Directory (doesFileExist, doesPathExist, canonicalizePath)
|
||||
import System.Posix.Process (executeFile)
|
||||
import UnliftIO.Exception
|
||||
import qualified Data.Aeson as Aeson
|
||||
@ -32,7 +32,17 @@ import qualified Data.Text as T
|
||||
import qualified Options.Applicative as Opts
|
||||
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
|
||||
|
||||
data ConfigStage
|
||||
@ -43,99 +53,126 @@ type family Config (c :: ConfigStage) ty1 ty2 where
|
||||
Config 'ConfigRaw ty1 _ = ty1
|
||||
Config 'ConfigReady _ ty2 = ty2
|
||||
|
||||
---
|
||||
--- 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
|
||||
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 = 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
|
||||
newtype SnackLib = SnackLib FilePath
|
||||
|
||||
mkSnackLib :: FilePath -> IO SnackLib
|
||||
mkSnackLib = fmap SnackLib . canonicalizePath
|
||||
mkSnackLib = fmap SnackLib . mkDirPath
|
||||
|
||||
--- Package description (@package.yaml@, @package.nix@)
|
||||
|
||||
-- | Like a FilePath, but Nix friendly
|
||||
newtype PackageYaml = PackageYaml { unPackageYaml :: FilePath }
|
||||
newtype PackageFile = PackageFile { unPackageFile :: FilePath }
|
||||
|
||||
mkPackageYaml :: FilePath -> IO PackageYaml
|
||||
mkPackageYaml = fmap PackageYaml . canonicalizePath
|
||||
-- | What package description (@package.yaml@, @package.nix@) to use
|
||||
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@
|
||||
newtype NixConfig = NixConfig
|
||||
{ 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 =
|
||||
(NixConfig <$>
|
||||
@ -147,6 +184,25 @@ parseNixConfig =
|
||||
<> 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 = SnackConfig <$> Opts.optional
|
||||
(Opts.strOption
|
||||
@ -160,40 +216,56 @@ parseSnackConfig = SnackConfig <$> Opts.optional
|
||||
]
|
||||
)
|
||||
)
|
||||
) <*>
|
||||
Opts.optional (
|
||||
Opts.strOption
|
||||
(Opts.long "snack-nix"
|
||||
<> Opts.short 'b'
|
||||
<> Opts.metavar "PATH")
|
||||
) <*>
|
||||
) <*> parseSnackNixConfig <*>
|
||||
parseNixConfig
|
||||
|
||||
parseMode :: Opts.Parser ModeRaw
|
||||
parseMode =
|
||||
(Standalone <$>
|
||||
Opts.strOption
|
||||
(Opts.long "package-nix"
|
||||
<> Opts.short 's'
|
||||
<> Opts.value "./package.nix"
|
||||
<> Opts.metavar "PATH")
|
||||
)
|
||||
<|>
|
||||
(HPack <$>
|
||||
Opts.strOption
|
||||
(Opts.long "package-yaml"
|
||||
<> Opts.value "./package.yaml"
|
||||
<> Opts.short 'p'
|
||||
<> Opts.metavar "PATH")
|
||||
)
|
||||
-- | What command to execute
|
||||
data Command
|
||||
= Build
|
||||
| Run [String] -- Run with extra args
|
||||
| Ghci
|
||||
| Test
|
||||
|
||||
parseCommand :: Opts.Parser Command
|
||||
parseCommand =
|
||||
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:"
|
||||
)
|
||||
|
||||
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 =
|
||||
Options <$>
|
||||
parseSnackConfig <*>
|
||||
parseMode <*>
|
||||
parsePackageFileConfig <*>
|
||||
parseCommand
|
||||
|
||||
--- Build related types used when interfacing with Nix
|
||||
|
||||
newtype ModuleName = ModuleName T.Text
|
||||
deriving newtype (Ord, Eq, Aeson.FromJSONKey)
|
||||
deriving stock Show
|
||||
@ -240,17 +312,16 @@ instance FromJSON ExecutableBuild where
|
||||
parseJSON = Aeson.withObject "executable build" $ \o ->
|
||||
ExecutableBuild <$> o .: "exe_path"
|
||||
|
||||
data MultiBuild = MultiBuild
|
||||
{ _librayBuild :: Maybe LibraryBuild
|
||||
, executableBuilds :: Map.Map T.Text ExecutableBuild
|
||||
newtype MultiBuild = MultiBuild
|
||||
{ executableBuilds :: Map.Map T.Text ExecutableBuild
|
||||
}
|
||||
deriving stock Show
|
||||
|
||||
instance Aeson.FromJSON MultiBuild where
|
||||
parseJSON = Aeson.withObject "multi build" $ \o ->
|
||||
MultiBuild
|
||||
<$> o .:? "library"
|
||||
<*> o .: "executables"
|
||||
MultiBuild <$> o .: "executables"
|
||||
|
||||
--- Type-helpers for passing arguments to Nix
|
||||
|
||||
data NixArg = NixArg
|
||||
{ argType :: NixArgType
|
||||
@ -268,12 +339,6 @@ newtype NixPath = NixPath T.Text
|
||||
deriving newtype FromJSON
|
||||
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 snackCfg extraNixArgs nixExpr =
|
||||
NixPath <$> runStdin1
|
||||
@ -343,71 +408,38 @@ nixBuild snackCfg extraNixArgs nixExpr =
|
||||
: [ argName narg , argValue narg ]
|
||||
nixCfg = snackNixCfg snackCfg
|
||||
|
||||
snackBuild :: SnackConfig -> PackageNix -> Sh BuildResult
|
||||
snackBuild snackCfg packageNix = do
|
||||
snackBuild :: SnackConfig -> PackageFile -> Sh BuildResult
|
||||
snackBuild snackCfg packageFile = do
|
||||
NixPath out <- nixBuild snackCfg
|
||||
[ NixArg
|
||||
{ argName = "packageNix"
|
||||
, argValue = T.pack $ unPackageNix packageNix
|
||||
{ argName = "packageFile"
|
||||
, argValue = T.pack $ unPackageFile packageFile
|
||||
, argType = Arg
|
||||
}
|
||||
]
|
||||
$ NixExpr "snack.inferSnackBuild packageNix"
|
||||
$ NixExpr "snack.inferBuild packageFile"
|
||||
decodeOrFail =<< liftIO (BS.readFile $ T.unpack out)
|
||||
|
||||
snackGhci :: SnackConfig -> PackageNix -> Sh GhciBuild
|
||||
snackGhci snackCfg packageNix = do
|
||||
snackGhci :: SnackConfig -> PackageFile -> Sh GhciBuild
|
||||
snackGhci snackCfg packageFile = do
|
||||
NixPath out <- nixBuild snackCfg
|
||||
[ NixArg
|
||||
{ argName = "packageNix"
|
||||
, argValue = T.pack $ unPackageNix packageNix
|
||||
{ argName = "packageFile"
|
||||
, argValue = T.pack $ unPackageFile packageFile
|
||||
, argType = Arg
|
||||
}
|
||||
]
|
||||
$ NixExpr "snack.inferSnackGhci packageNix"
|
||||
$ NixExpr "snack.inferGhci packageFile"
|
||||
liftIO (BS.readFile (T.unpack out)) >>= decodeOrFail >>= \case
|
||||
BuiltGhci g -> pure g
|
||||
b -> throwIO $ userError $ "Expected GHCi build, got " <> show b
|
||||
|
||||
snackBuildHPack :: SnackConfig -> PackageYaml -> Sh BuildResult
|
||||
snackBuildHPack snackCfg packageYaml = do
|
||||
NixPath out <- nixBuild snackCfg
|
||||
[ NixArg
|
||||
{ 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
|
||||
runCommand :: SnackConfig -> PackageFile -> Command -> IO ()
|
||||
runCommand snackCfg packageFile = \case
|
||||
Build -> S.shelly $ void $ snackBuild snackCfg packageFile
|
||||
Run args -> quiet (snackBuild snackCfg packageFile) >>= runBuildResult args
|
||||
Ghci -> flip runExe [] =<<
|
||||
ghciExePath <$> (quiet (snackGhci snackCfg packageNix))
|
||||
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)
|
||||
ghciExePath <$> (quiet (snackGhci snackCfg packageFile))
|
||||
Test -> noTest
|
||||
|
||||
noTest :: IO a
|
||||
@ -421,29 +453,46 @@ runBuildResult args = \case
|
||||
runExe exe args
|
||||
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 fp) args = executeFile (T.unpack fp) True args Nothing
|
||||
|
||||
parseCommand :: Opts.Parser Command
|
||||
parseCommand =
|
||||
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:"
|
||||
)
|
||||
specJson :: T.Text
|
||||
specJson = $(embedStringFile "spec.json")
|
||||
|
||||
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 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 stin p args = do
|
||||
S.setStdin stin
|
||||
@ -451,8 +500,5 @@ runStdin1 stin p args = do
|
||||
[out] -> pure out
|
||||
xs -> throwIO $ userError $ "unexpected output: " <> show xs
|
||||
|
||||
specJson :: T.Text
|
||||
specJson = $(embedStringFile "spec.json")
|
||||
|
||||
libb64 :: T.Text
|
||||
libb64 = $(embedStringFile "lib.tar.gz.b64")
|
||||
quiet :: Sh a -> IO a
|
||||
quiet = S.shelly . S.print_stdout False
|
||||
|
@ -68,7 +68,7 @@ for t in $SNACK_TESTS; do
|
||||
done
|
||||
|
||||
for t in $SNACK_TESTS; do
|
||||
banner "Test $name"
|
||||
banner "Test $t"
|
||||
pushd "tests/$t"
|
||||
./test
|
||||
popd
|
||||
|
@ -1,6 +1,7 @@
|
||||
# This is the entry point of the library, and badly needs documentation.
|
||||
# TODO: currently single out derivations prepend the PWD to the path
|
||||
# TODO: make sure that filters for "base" are airtight
|
||||
# TODO: document the sh*t out of these functions
|
||||
{ pkgs
|
||||
, ghc-version ? "ghc822"
|
||||
, ghcWithPackages ? pkgs.haskell.packages.${ghc-version}.ghcWithPackages
|
||||
@ -67,6 +68,37 @@ let
|
||||
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);
|
||||
|
||||
inferSnackGhci = packageNix: writeText "snack-ghci-json"
|
||||
@ -152,6 +184,8 @@ let
|
||||
in
|
||||
{
|
||||
inherit
|
||||
inferBuild
|
||||
inferGhci
|
||||
inferSnackBuild
|
||||
inferSnackGhci
|
||||
inferHPackBuild
|
||||
@ -160,7 +194,7 @@ in
|
||||
buildAsExecutable
|
||||
buildAsLibrary
|
||||
snackSpec
|
||||
hpackSpec
|
||||
hpackSpecs
|
||||
mkPackage
|
||||
;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ test() {
|
||||
$SNACK run
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
|
||||
SNACK="snack -j4 --package-file ./package.yaml" test
|
||||
|
@ -1,7 +0,0 @@
|
||||
{ main = "Foo";
|
||||
src = ./src;
|
||||
dependencies = [
|
||||
"conduit"
|
||||
"something-that-doesnt-exist"
|
||||
];
|
||||
}
|
@ -16,6 +16,5 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack -j4 --snack-nix snack.nix --package-nix package.nix" test
|
||||
SNACK="snack -j4 --snack-nix snack.nix" test
|
||||
SNACK="snack -j4 --snack-nix snack.nix --package-yaml package.yaml" test
|
||||
SNACK="snack -j4 --snack-nix snack.nix --package-file package.yaml" test
|
||||
SNACK="snack -j4" test
|
||||
|
@ -15,7 +15,6 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
|
||||
SNACK="snack -j4 --package-file ./package.yaml" test
|
||||
|
@ -8,6 +8,8 @@ TMP_DIR=$(mktemp -d)
|
||||
git clone http://github.com/nmattia/pboy.git $TMP_DIR
|
||||
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
|
||||
|
@ -16,6 +16,6 @@ test() {
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
|
||||
# Note: no HPack test, because HPack doesn't support multi library
|
||||
|
@ -16,6 +16,6 @@ test() {
|
||||
}
|
||||
|
||||
SNACK="snack" test
|
||||
SNACK="snack -s ./package.nix" test
|
||||
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test
|
||||
SNACK="snack --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
|
||||
# Note: no HPack test, because HPack doesn't support multi library
|
||||
|
@ -15,6 +15,5 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.yaml" test
|
||||
|
@ -15,6 +15,5 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.yaml" test
|
||||
|
@ -16,6 +16,5 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.yaml" test
|
||||
|
@ -16,6 +16,5 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.yaml" test
|
||||
|
@ -8,7 +8,6 @@ test() {
|
||||
$SNACK run
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 -s ./package.nix -l ../../snack-lib" test
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.nix -l ../../snack-lib" test
|
||||
SNACK="snack -j4 --package-file ./package.yaml" test
|
||||
|
@ -8,4 +8,4 @@ test() {
|
||||
$SNACK run
|
||||
}
|
||||
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4" test
|
||||
|
@ -16,10 +16,5 @@ test() {
|
||||
}
|
||||
|
||||
|
||||
SNACK="snack -j4" 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
|
||||
SNACK="snack -j4 -p ./package.nix" test
|
||||
SNACK="snack -j4 -p ./package.yaml" test
|
||||
|
@ -15,6 +15,5 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack -j4" test
|
||||
SNACK="snack -j4 -s ./package.nix" test
|
||||
SNACK="snack -j4 --package-yaml ./package.yaml" test
|
||||
SNACK="snack -j4 --package-file ./package.nix" test
|
||||
SNACK="snack -j4 --package-file ./package.yaml" test
|
||||
|
@ -3,12 +3,12 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
snack -j4 build -s code/package.nix
|
||||
snack -j4 run -s code/package.nix | diff golden -
|
||||
snack -j4 build --package-file code/package.nix
|
||||
snack -j4 run --package-file code/package.nix | diff golden -
|
||||
|
||||
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
|
||||
rm $TMP_FILE
|
||||
|
@ -16,5 +16,5 @@ 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
|
||||
|
@ -15,6 +15,5 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack" test
|
||||
SNACK="snack -s ./package.nix" test
|
||||
SNACK="snack --package-yaml ./package.yaml" test
|
||||
SNACK="snack --package-file ./package.nix" test
|
||||
SNACK="snack --package-file ./package.yaml" test
|
||||
|
@ -15,6 +15,5 @@ test() {
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack" test
|
||||
SNACK="snack -s ./package.nix" test
|
||||
SNACK="snack --package-yaml ./package.yaml" test
|
||||
SNACK="snack --package-file ./package.nix" test
|
||||
SNACK="snack --package-file ./package.yaml" test
|
||||
|
Loading…
Reference in New Issue
Block a user