mirror of
https://github.com/nmattia/snack.git
synced 2024-10-26 12:38:49 +03:00
Better HPack support
This commit is contained in:
parent
451e0d86da
commit
7788e6a39d
188
bin/Snack.hs
188
bin/Snack.hs
@ -1,36 +1,33 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE DerivingStrategies #-}
|
||||
{-# LANGUAGE MonadFailDesugaring #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
module Main where
|
||||
|
||||
import Control.Applicative
|
||||
import Control.Monad
|
||||
import Control.Monad.IO.Class
|
||||
import Data.Aeson ((.:))
|
||||
import Data.Aeson (FromJSON, (.:), (.:?))
|
||||
import Data.FileEmbed (embedStringFile)
|
||||
import Data.List (intercalate)
|
||||
import Data.Semigroup ((<>))
|
||||
import Data.String (fromString)
|
||||
import Data.String.Interpolate
|
||||
import Shelly (Sh)
|
||||
import System.Posix.Process (executeFile)
|
||||
import UnliftIO.Exception
|
||||
import qualified Data.Aeson as Aeson
|
||||
import qualified Data.Aeson.Types as Aeson
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Text as T
|
||||
import qualified Options.Applicative as Opts
|
||||
import qualified Shelly as S
|
||||
|
||||
{-
|
||||
|
||||
TODO:
|
||||
Mode
|
||||
= HPack HPackPah
|
||||
| Standalone SnackNix
|
||||
| Discovery (Either HPackPath SnackNix)
|
||||
-}
|
||||
|
||||
data Mode
|
||||
= Standalone SnackNix -- Reads a snack.nix file
|
||||
| HPack PackageYaml
|
||||
@ -63,15 +60,6 @@ data Options = Options
|
||||
, command :: Command
|
||||
}
|
||||
|
||||
data Project = Project
|
||||
{ outPath :: FilePath
|
||||
, exePath :: FilePath
|
||||
}
|
||||
|
||||
instance Aeson.FromJSON Project where
|
||||
parseJSON = Aeson.withObject "project" $ \o ->
|
||||
Project <$> o .: "out_path" <*> o .: "exe_path"
|
||||
|
||||
parseMode :: Opts.Parser Mode
|
||||
parseMode =
|
||||
((Standalone . mkSnackNix) <$>
|
||||
@ -93,10 +81,65 @@ parseMode =
|
||||
options :: Opts.Parser Options
|
||||
options = Options <$> parseMode <*> parseCommand
|
||||
|
||||
snackGhci :: SnackNix -> Sh ()
|
||||
snackGhci snackNix = do
|
||||
path <- S.print_stdout False $ exePath <$> snackBuildGhci snackNix
|
||||
S.print_stdout True $ run_ (fromString path) []
|
||||
newtype ModuleName = ModuleName { unModuleName :: T.Text }
|
||||
deriving newtype (Ord, Eq, Aeson.FromJSONKey)
|
||||
deriving stock Show
|
||||
|
||||
data BuildResult
|
||||
= BuiltLibrary LibraryBuild
|
||||
| BuiltExecutable ExecutableBuild
|
||||
| BuiltMulti MultiBuild
|
||||
| BuiltGhci GhciBuild
|
||||
deriving Show
|
||||
|
||||
instance Aeson.FromJSON BuildResult where
|
||||
parseJSON v =
|
||||
BuiltLibrary <$> (guardBuildType "library" v)
|
||||
<|> BuiltExecutable <$> (guardBuildType "executable" v)
|
||||
<|> BuiltMulti <$> (guardBuildType "multi" v)
|
||||
<|> BuiltGhci <$> (guardBuildType "ghci" v)
|
||||
where
|
||||
guardBuildType :: FromJSON a => T.Text -> Aeson.Value -> Aeson.Parser a
|
||||
guardBuildType ty = Aeson.withObject "build result" $ \o -> do
|
||||
bty <- o .: "build_type"
|
||||
guard (bty == ty)
|
||||
Aeson.parseJSON =<< o .: "result"
|
||||
|
||||
newtype GhciBuild = GhciBuild
|
||||
{ ghciExePath :: NixPath
|
||||
}
|
||||
deriving stock Show
|
||||
|
||||
instance FromJSON GhciBuild where
|
||||
parseJSON = Aeson.withObject "ghci build" $ \o ->
|
||||
GhciBuild <$> o .: "ghci_path"
|
||||
|
||||
-- The kinds of builds: library, executable, or a mix of both (currently only
|
||||
-- for HPack)
|
||||
newtype LibraryBuild = LibraryBuild
|
||||
{ unLibraryBuild :: Map.Map ModuleName NixPath }
|
||||
deriving newtype FromJSON
|
||||
deriving stock Show
|
||||
|
||||
newtype ExecutableBuild = ExecutableBuild
|
||||
{ exePath :: NixPath }
|
||||
deriving stock Show
|
||||
|
||||
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
|
||||
}
|
||||
deriving stock Show
|
||||
|
||||
instance Aeson.FromJSON MultiBuild where
|
||||
parseJSON = Aeson.withObject "multi build" $ \o ->
|
||||
MultiBuild
|
||||
<$> o .:? "library"
|
||||
<*> o .: "executables"
|
||||
|
||||
data NixArg = NixArg
|
||||
{ argType :: NixArgType
|
||||
@ -111,9 +154,17 @@ data NixArgType
|
||||
newtype NixExpr = NixExpr { unNixExpr :: T.Text }
|
||||
|
||||
newtype NixPath = NixPath { unNixPath :: T.Text }
|
||||
deriving newtype FromJSON
|
||||
deriving stock Show
|
||||
|
||||
snackBuildWith :: [NixArg] -> NixExpr -> Sh NixPath
|
||||
snackBuildWith extraNixArgs nixExpr =
|
||||
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 :: [NixArg] -> NixExpr -> Sh NixPath
|
||||
nixBuild extraNixArgs nixExpr =
|
||||
NixPath <$> runStdin1
|
||||
(T.pack [i|
|
||||
{ #{ intercalate "," funArgs } }:
|
||||
@ -159,65 +210,79 @@ snackBuildWith extraNixArgs nixExpr =
|
||||
{ Arg -> "--arg"; ArgStr -> "--argstr" }
|
||||
: [ argName narg , argValue narg ]
|
||||
|
||||
snackBuild :: SnackNix -> Sh Project
|
||||
snackBuild :: SnackNix -> Sh BuildResult
|
||||
snackBuild snackNix = do
|
||||
NixPath out <- snackBuildWith
|
||||
NixPath out <- nixBuild
|
||||
[ NixArg
|
||||
{ argName = "snackNix"
|
||||
, argValue = T.pack $ unSnackNix snackNix
|
||||
, argType = Arg
|
||||
}
|
||||
]
|
||||
$ NixExpr "(snack.executable (import snackNix)).build.json"
|
||||
json <- liftIO $ BS.readFile (T.unpack out)
|
||||
let Just proj = Aeson.decodeStrict' json
|
||||
pure proj
|
||||
$ NixExpr "snack.inferSnackBuild snackNix"
|
||||
decodeOrFail =<< liftIO (BS.readFile $ T.unpack out)
|
||||
|
||||
snackBuildGhci :: SnackNix -> Sh Project
|
||||
snackBuildGhci snackNix = do
|
||||
NixPath out <- snackBuildWith
|
||||
snackGhci :: SnackNix -> Sh GhciBuild
|
||||
snackGhci snackNix = do
|
||||
NixPath out <- nixBuild
|
||||
[ NixArg
|
||||
{ argName = "snackNix"
|
||||
, argValue = T.pack $ unSnackNix snackNix
|
||||
, argType = Arg
|
||||
}
|
||||
]
|
||||
$ NixExpr "(snack.executable (import snackNix)).ghci.json"
|
||||
json <- liftIO $ BS.readFile (T.unpack out)
|
||||
let Just proj = Aeson.decodeStrict' json
|
||||
pure proj
|
||||
$ NixExpr "snack.inferSnackGhci snackNix"
|
||||
liftIO (BS.readFile (T.unpack out)) >>= decodeOrFail >>= \case
|
||||
BuiltGhci g -> pure g
|
||||
b -> throwIO $ userError $ "Expected GHCi build, got " <> show b
|
||||
|
||||
snackBuildHPack :: PackageYaml -> Sh Project
|
||||
snackBuildHPack :: PackageYaml -> Sh BuildResult
|
||||
snackBuildHPack packageYaml = do
|
||||
NixPath out <- snackBuildWith
|
||||
NixPath out <- nixBuild
|
||||
[ NixArg
|
||||
{ argName = "packageYaml"
|
||||
, argValue = T.pack $ unPackageYaml packageYaml
|
||||
, argType = Arg
|
||||
}
|
||||
]
|
||||
$ NixExpr "(snack.packageYaml packageYaml).library.build.json"
|
||||
json <- liftIO $ BS.readFile (T.unpack out)
|
||||
let Just proj = Aeson.decodeStrict' json
|
||||
pure proj
|
||||
$ NixExpr "snack.inferHPackBuild packageYaml"
|
||||
decodeOrFail =<< liftIO (BS.readFile (T.unpack out))
|
||||
|
||||
snackGhciHPack :: PackageYaml -> Sh GhciBuild
|
||||
snackGhciHPack packageYaml = do
|
||||
NixPath out <- nixBuild
|
||||
[ 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 :: Mode -> Command -> IO ()
|
||||
runCommand (Standalone snackNix) = \case
|
||||
Build -> S.shelly $ void $ snackBuild snackNix
|
||||
Run -> snackRun snackBuild
|
||||
Ghci -> snackRun snackBuildGhci
|
||||
where
|
||||
snackRun build = do
|
||||
fp <- S.shelly $ S.print_stdout False $ exePath <$> build snackNix
|
||||
executeFile fp True [] Nothing
|
||||
Run -> quiet (snackBuild snackNix) >>= runBuildResult
|
||||
Ghci -> runExe =<< ghciExePath <$> (quiet $ snackGhci snackNix)
|
||||
runCommand (HPack packageYaml) = \case
|
||||
Build -> S.shelly $ void $ snackBuildHPack packageYaml
|
||||
Run -> snackRun snackBuildHPack
|
||||
Ghci -> undefined -- snackRun snackBuildHPackGhci
|
||||
where
|
||||
snackRun build = do
|
||||
fp <- S.shelly $ S.print_stdout False $ exePath <$> build packageYaml
|
||||
executeFile fp True [] Nothing
|
||||
Run -> quiet (snackBuildHPack packageYaml) >>= runBuildResult
|
||||
Ghci -> runExe =<< ghciExePath <$> (quiet $ snackGhciHPack packageYaml)
|
||||
|
||||
runBuildResult :: BuildResult -> IO ()
|
||||
runBuildResult = \case
|
||||
BuiltExecutable (ExecutableBuild p) -> runExe p
|
||||
BuiltMulti b
|
||||
| [ExecutableBuild exe] <- Map.elems (executableBuilds b) -> runExe exe
|
||||
b -> fail $ "Unexpected build type: " <> show b
|
||||
|
||||
quiet :: Sh a -> IO a
|
||||
quiet = S.shelly . S.print_stdout False
|
||||
runExe :: NixPath -> IO ()
|
||||
runExe (NixPath fp) = executeFile (T.unpack fp) True [] Nothing
|
||||
|
||||
parseCommand :: Opts.Parser Command
|
||||
parseCommand =
|
||||
@ -233,8 +298,9 @@ run p args = T.lines <$> S.run p args
|
||||
runStdin1 :: T.Text -> S.FilePath -> [T.Text] -> Sh T.Text
|
||||
runStdin1 stin p args = do
|
||||
S.setStdin stin
|
||||
[out] <- run p args
|
||||
pure out
|
||||
run p args >>= \case
|
||||
[out] -> pure out
|
||||
xs -> throwIO $ userError $ "unexpected output: " <> show xs
|
||||
|
||||
run_ :: S.FilePath -> [T.Text] -> Sh ()
|
||||
run_ p args = void $ run p args
|
||||
|
@ -1,11 +1,11 @@
|
||||
{ runCommand, writeTextFile, symlinkJoin }:
|
||||
let
|
||||
specJson = writeTextFile
|
||||
pkgs = import ../nix {};
|
||||
specJson = pkgs.writeTextFile
|
||||
{ name = "spec-json";
|
||||
text = builtins.readFile ../nix/nixpkgs/nixpkgs-src.json;
|
||||
destination = "/spec.json";
|
||||
};
|
||||
lib64 = runCommand "lib64" {}
|
||||
lib64 = pkgs.runCommand "lib64" {}
|
||||
''
|
||||
tar -czf lib.tar.gz -C ${../snack-lib} .
|
||||
mkdir -p $out
|
||||
@ -23,6 +23,7 @@ in
|
||||
"shelly"
|
||||
"text"
|
||||
"unix"
|
||||
"unliftio"
|
||||
];
|
||||
ghcOpts = [ "-Werror" "-Wall" ] ;
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
_: pkgs: rec {
|
||||
snack-lib = pkgs.callPackage ../snack-lib/default.nix { };
|
||||
snack-exe = (snack-lib.executable (import ../bin/snack.nix { inherit (pkgs) writeTextFile symlinkJoin runCommand;})).build.out;
|
||||
snack-exe =
|
||||
(snack-lib.buildAsExecutable (snack-lib.snackSpec ../bin/snack.nix)).out;
|
||||
}
|
||||
|
@ -22,87 +22,145 @@ with (callPackage ./package-spec.nix {});
|
||||
with (callPackage ./hpack.nix {});
|
||||
|
||||
let
|
||||
ghcWith = deps: haskellPackages.ghcWithPackages
|
||||
(ps: map (p: ps.${p}) deps);
|
||||
|
||||
# TODO: "executable" is a bad name
|
||||
executable = pkgDescr:
|
||||
# Assumes the package description describes an executable
|
||||
withMainModSpec = pkgDescr: act:
|
||||
let
|
||||
moduleSpecFold' = modSpecFoldFromPackageSpec topPkgSpec;
|
||||
topPkgSpec = mkPackageSpec pkgDescr;
|
||||
ghcWith = deps: haskellPackages.ghcWithPackages
|
||||
(ps: map (p: ps.${p}) deps);
|
||||
mainModName = pkgDescr.packageMain;
|
||||
mainModSpec =
|
||||
let
|
||||
fld = moduleSpecFold' modSpecs;
|
||||
modSpecs = foldDAG fld [mainModName];
|
||||
in modSpecs.${mainModName};
|
||||
drv = linkMainModule ghcWith mainModSpec;
|
||||
in
|
||||
if builtins.isNull topPkgSpec.packageMain
|
||||
then
|
||||
let
|
||||
modNames = listModulesInDir topPkgSpec.packageBase;
|
||||
fld = moduleSpecFold' modSpecs';
|
||||
modSpecs' = foldDAG fld modNames;
|
||||
modSpecs = builtins.attrValues modSpecs';
|
||||
in
|
||||
{
|
||||
build =
|
||||
# This json is a bit different than the other ones, because it's
|
||||
# a map of modules to object files (rather than out_path +
|
||||
# exe_path)
|
||||
{ json = writeText "build_output"
|
||||
(builtins.toJSON (buildLibrary ghcWith modSpecs));
|
||||
};
|
||||
ghci =
|
||||
let
|
||||
drv = ghciWithModules ghcWith modSpecs;
|
||||
json =
|
||||
{ out_path = "${drv.out}";
|
||||
exe_path = "${drv.out}/bin/ghci-with-files";
|
||||
};
|
||||
in
|
||||
{ out = drv.out;
|
||||
json = writeText "ghci_output" (builtins.toJSON json);
|
||||
};
|
||||
}
|
||||
else
|
||||
let
|
||||
mainModName = topPkgSpec.packageMain;
|
||||
mainModSpec =
|
||||
let
|
||||
fld = moduleSpecFold' modSpecs;
|
||||
modSpecs = foldDAG fld [mainModName];
|
||||
in modSpecs.${mainModName};
|
||||
in
|
||||
{ build =
|
||||
let
|
||||
drv = linkMainModule ghcWith mainModSpec;
|
||||
json =
|
||||
{ out_path = "${drv.out}";
|
||||
exe_path = "${drv.out}/${drv.relExePath}";
|
||||
};
|
||||
in
|
||||
{ out = drv.out;
|
||||
json = writeText "build_output" (builtins.toJSON json);
|
||||
};
|
||||
{ out = drv.out;
|
||||
outPath = "${drv.out}";
|
||||
exePath = "${drv.out}/${drv.relExePath}";
|
||||
};
|
||||
|
||||
ghci =
|
||||
let
|
||||
drv = ghciWithMain ghcWith mainModSpec;
|
||||
json =
|
||||
{ out_path = "${drv.out}";
|
||||
exe_path = "${drv.out}/bin/ghci-with-files";
|
||||
};
|
||||
in
|
||||
{ out = drv.out;
|
||||
json = writeText "ghci_output" (builtins.toJSON json);
|
||||
};
|
||||
};
|
||||
packageYaml = pyam:
|
||||
libraryModSpecs = pkgSpec:
|
||||
let
|
||||
moduleSpecFold' = modSpecFoldFromPackageSpec pkgSpec;
|
||||
modNames = listModulesInDir pkgSpec.packageBase;
|
||||
fld = moduleSpecFold' modSpecs';
|
||||
modSpecs' = foldDAG fld modNames;
|
||||
modSpecs = builtins.attrValues modSpecs';
|
||||
in modSpecs;
|
||||
|
||||
executableMainModSpec = pkgSpec:
|
||||
let
|
||||
moduleSpecFold' = modSpecFoldFromPackageSpec pkgSpec;
|
||||
mainModName = pkgSpec.packageMain;
|
||||
mainModSpec =
|
||||
let
|
||||
project = snackNixFromHPack pyam;
|
||||
in
|
||||
{ library = executable project.library;
|
||||
executables = lib.attrsets.mapAttrs (_: v: executable v) project.executables;
|
||||
fld = moduleSpecFold' modSpecs;
|
||||
modSpecs = foldDAG fld [mainModName];
|
||||
in modSpecs.${mainModName};
|
||||
in mainModSpec;
|
||||
|
||||
buildAsLibrary = pkgSpec:
|
||||
buildLibrary ghcWith (libraryModSpecs pkgSpec);
|
||||
|
||||
buildAsExecutable = pkgSpec:
|
||||
let drv = linkMainModule ghcWith (executableMainModSpec pkgSpec);
|
||||
in
|
||||
{ out = drv.out;
|
||||
exe_path = "${drv.out}/${drv.relExePath}";
|
||||
};
|
||||
|
||||
inferSnackBuild = snackNix: writeText "snack-build-json"
|
||||
( builtins.toJSON (
|
||||
let
|
||||
pkgSpec = mkPackageSpec (import snackNix);
|
||||
in
|
||||
if builtins.isNull pkgSpec.packageMain
|
||||
then
|
||||
{ "build_type" = "library";
|
||||
"result" = buildAsLibrary pkgSpec;
|
||||
}
|
||||
else
|
||||
{ "build_type" = "executable";
|
||||
"result" = buildAsExecutable pkgSpec;
|
||||
}
|
||||
));
|
||||
|
||||
inferSnackGhci = snackNix: writeText "snack-ghci-json"
|
||||
( builtins.toJSON (
|
||||
let
|
||||
pkgSpec = mkPackageSpec (import snackNix);
|
||||
drv =
|
||||
if builtins.isNull pkgSpec.packageMain
|
||||
then ghciWithModules ghcWith (libraryModSpecs pkgSpec)
|
||||
else ghciWithMain ghcWith (executableMainModSpec pkgSpec);
|
||||
in
|
||||
{ build_type = "ghci";
|
||||
result = {
|
||||
"ghci_path" = "${drv.out}/bin/ghci-with-files";
|
||||
};
|
||||
}
|
||||
));
|
||||
|
||||
inferHPackBuild = packageYaml: writeText "hpack-build-json"
|
||||
( builtins.toJSON (
|
||||
let pkgSpecs = hpackSpecs packageYaml;
|
||||
in
|
||||
{ build_type = "multi";
|
||||
result =
|
||||
{ library =
|
||||
if builtins.isNull pkgSpecs.library
|
||||
then null
|
||||
else buildAsLibrary (pkgSpecs.library);
|
||||
executables = lib.attrsets.mapAttrs (k: v: buildAsExecutable v) pkgSpecs.executables;
|
||||
};
|
||||
}
|
||||
));
|
||||
|
||||
inferHPackGhci = packageYaml: writeText "hpack-ghci-json"
|
||||
( builtins.toJSON (
|
||||
let
|
||||
pkgSpecs = hpackSpecs packageYaml;
|
||||
pkgSpec = mkPackageSpec (import snackNix);
|
||||
drv =
|
||||
let exeSpecs = builtins.attrValues pkgSpecs.executables;
|
||||
in
|
||||
if lib.lists.length exeSpecs == 1
|
||||
then ghciWithMain ghcWith (executableMainModSpec (lib.lists.head exeSpecs))
|
||||
else
|
||||
if builtins.isNull pkgSpecs.library
|
||||
then abort "GHCi: needs either a single executable or a library"
|
||||
else ghciWithModules ghcWith (libraryModSpecs pkgSpecs.library);
|
||||
in
|
||||
{ build_type = "ghci";
|
||||
result = {
|
||||
"ghci_path" = "${drv.out}/bin/ghci-with-files";
|
||||
};
|
||||
}
|
||||
));
|
||||
|
||||
snackSpec = snackNix: mkPackageSpec (import snackNix);
|
||||
hpackSpecs = packageYaml:
|
||||
let
|
||||
descrs = pkgDescrsFromHPack packageYaml;
|
||||
in
|
||||
{ library = withAttr descrs "library" null
|
||||
(comp: if builtins.isNull comp then null else mkPackageSpec comp);
|
||||
executables =
|
||||
lib.attrsets.mapAttrs (k: v: mkPackageSpec v) descrs.executables;
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit
|
||||
executable
|
||||
inferSnackBuild
|
||||
inferSnackGhci
|
||||
inferHPackBuild
|
||||
inferHPackGhci
|
||||
packageYaml
|
||||
buildAsExecutable
|
||||
buildAsLibrary
|
||||
snackSpec
|
||||
hpackSpec
|
||||
;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
{ lib, glibcLocales, callPackage, writeText, runCommand, haskellPackages }:
|
||||
|
||||
with (callPackage ./lib.nix {});
|
||||
with (callPackage ./modules.nix {});
|
||||
|
||||
let
|
||||
@ -20,49 +21,44 @@ let
|
||||
in builtins.fromJSON json;
|
||||
in
|
||||
{
|
||||
snackNixFromHPack = packageYaml:
|
||||
# Returns an attribute set with two fields:
|
||||
# - library: a package spec
|
||||
# - executable: an attr set of executable name to package spec
|
||||
pkgDescrsFromHPack = packageYaml:
|
||||
let
|
||||
package = fromYAML (builtins.readFile packageYaml);
|
||||
topDeps =
|
||||
# this drops the version bounds
|
||||
map (x: lib.lists.head (lib.strings.splitString " " x))
|
||||
package.dependencies;
|
||||
extensions = package.default-extensions;
|
||||
packageLib =
|
||||
let component = package.library;
|
||||
in
|
||||
topExtensions = optAttr package "default-extensions" [];
|
||||
packageLib = withAttr package "library" null (component:
|
||||
{ src =
|
||||
let base = builtins.dirOf packageYaml;
|
||||
in builtins.toPath "${builtins.toString base}/${component.source-dirs}";
|
||||
dependencies = topDeps ++
|
||||
(if builtins.hasAttr "dependencies" component
|
||||
then component.dependencies
|
||||
else []);
|
||||
|
||||
inherit extensions;
|
||||
};
|
||||
dependencies = topDeps ++ (optAttr component "dependencies" []);
|
||||
extensions = topExtensions ++ (optAttr component "extensions" []);
|
||||
}
|
||||
);
|
||||
|
||||
exes =
|
||||
if builtins.hasAttr "executables" package
|
||||
then lib.mapAttrs (k: v: mkExe v) package.executables
|
||||
else {};
|
||||
withAttr package "executables" {} (lib.mapAttrs (k: v: mkExe v)) //
|
||||
withAttr package "executable" {} (comp: { ${package.name} = mkExe comp; });
|
||||
mkExe = component:
|
||||
let
|
||||
depOrPack =
|
||||
lib.lists.partition
|
||||
(x: x == package.name)
|
||||
(if builtins.hasAttr "dependencies" component
|
||||
then component.dependencies
|
||||
else []);
|
||||
packages = map (_: packageLib) depOrPack.right;
|
||||
dependencies = topDeps ++ depOrPack.wrong;
|
||||
(optAttr component "dependencies" []);
|
||||
in
|
||||
{ main = fileToModule component.main;
|
||||
src =
|
||||
let
|
||||
base = builtins.dirOf packageYaml;
|
||||
in builtins.toPath "${builtins.toString base}/${component.source-dirs}";
|
||||
inherit packages dependencies extensions;
|
||||
dependencies = topDeps ++ depOrPack.wrong;
|
||||
extensions = topExtensions ++ (optAttr component "extensions" []);
|
||||
packages = map (_: packageLib) depOrPack.right;
|
||||
};
|
||||
in
|
||||
{ library = packageLib;
|
||||
|
@ -43,4 +43,11 @@ foldDAGRec =
|
||||
};
|
||||
in foldDAGRec fld acc' children;
|
||||
in lib.foldl insert acc0 roots;
|
||||
|
||||
withAttr = obj: attrName: def: f:
|
||||
if builtins.hasAttr attrName obj then f (obj.${attrName}) else def;
|
||||
|
||||
optAttr = obj: attrName: def:
|
||||
if builtins.hasAttr attrName obj then obj.${attrName} else def;
|
||||
|
||||
}
|
||||
|
8
tests/extensions/package.yaml
Normal file
8
tests/extensions/package.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
name: snack-extensions-test
|
||||
dependencies:
|
||||
- text
|
||||
executable:
|
||||
main: Main.hs
|
||||
source-dirs: .
|
||||
default-extensions:
|
||||
- OverloadedStrings
|
@ -3,12 +3,18 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
snack build
|
||||
snack run | diff golden -
|
||||
test() {
|
||||
$SNACK build
|
||||
$SNACK run | diff golden -
|
||||
|
||||
TMP_FILE=$(mktemp)
|
||||
TMP_FILE=$(mktemp)
|
||||
|
||||
capture_io "$TMP_FILE" main | snack ghci
|
||||
capture_io "$TMP_FILE" main | $SNACK ghci
|
||||
|
||||
diff golden $TMP_FILE
|
||||
rm $TMP_FILE
|
||||
diff golden $TMP_FILE
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack" test
|
||||
SNACK="snack -s ./snack.nix" test
|
||||
SNACK="snack --package-yaml ./package.yaml" test
|
||||
|
@ -3,13 +3,18 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TMP_FILE=$(mktemp)
|
||||
test() {
|
||||
TMP_FILE=$(mktemp)
|
||||
|
||||
cat $(snack build) | jq -M 'keys' > $TMP_FILE
|
||||
cat $($SNACK build) | jq -M '.result | keys' > $TMP_FILE
|
||||
|
||||
diff golden.jq $TMP_FILE
|
||||
diff golden.jq $TMP_FILE
|
||||
|
||||
capture_io "$TMP_FILE" main | snack ghci
|
||||
diff golden $TMP_FILE
|
||||
capture_io "$TMP_FILE" main | $SNACK ghci
|
||||
diff golden $TMP_FILE
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
rm $TMP_FILE
|
||||
SNACK="snack" test
|
||||
SNACK="snack -s ./snack.nix" test
|
||||
# Note: no HPack test, because HPack doesn't support multi library
|
||||
|
13
tests/library/package.yaml
Normal file
13
tests/library/package.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
name: snack-library-test
|
||||
|
||||
dependencies:
|
||||
- conduit
|
||||
|
||||
library:
|
||||
source-dirs: src
|
||||
|
||||
executable:
|
||||
main: Foo.hs
|
||||
source-dirs: app
|
||||
dependencies:
|
||||
- snack-library-test
|
@ -3,12 +3,18 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
snack build
|
||||
snack run | diff golden -
|
||||
test() {
|
||||
$SNACK build
|
||||
$SNACK run | diff golden -
|
||||
|
||||
TMP_FILE=$(mktemp)
|
||||
TMP_FILE=$(mktemp)
|
||||
|
||||
capture_io "$TMP_FILE" main | snack ghci
|
||||
capture_io "$TMP_FILE" main | $SNACK ghci
|
||||
|
||||
diff golden $TMP_FILE
|
||||
rm $TMP_FILE
|
||||
diff golden $TMP_FILE
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
SNACK="snack" test
|
||||
SNACK="snack -s ./snack.nix" test
|
||||
SNACK="snack --package-yaml ./package.yaml" test
|
||||
|
@ -10,22 +10,12 @@ test() {
|
||||
|
||||
TMP_FILE=$(mktemp)
|
||||
|
||||
capture_io "$TMP_FILE" main | snack ghci
|
||||
capture_io "$TMP_FILE" main | $SNACK ghci
|
||||
|
||||
diff golden $TMP_FILE
|
||||
rm $TMP_FILE
|
||||
}
|
||||
|
||||
|
||||
SNACK="snack" test
|
||||
SNACK="snack -s ./snack.nix" test
|
||||
#SNACK="snack --package-yaml ./package.yaml" test
|
||||
#snack build
|
||||
#snack run | diff golden -
|
||||
|
||||
#TMP_FILE=$(mktemp)
|
||||
|
||||
#capture_io "$TMP_FILE" main | snack ghci
|
||||
|
||||
#diff golden $TMP_FILE
|
||||
#rm $TMP_FILE
|
||||
SNACK="snack --package-yaml ./package.yaml" test
|
||||
|
Loading…
Reference in New Issue
Block a user