Keter.hs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. {-# LANGUAGE OverloadedStrings #-}
  2. module Keter
  3. ( keter
  4. ) where
  5. import Data.Yaml
  6. import qualified Data.HashMap.Strict as Map
  7. import qualified Data.Text as T
  8. import System.Environment (getEnvironment)
  9. import System.Exit
  10. import System.Process
  11. import Control.Monad
  12. import System.Directory hiding (findFiles)
  13. import Data.Maybe (mapMaybe,isJust,maybeToList)
  14. import Data.Monoid
  15. import System.FilePath ((</>))
  16. import qualified Codec.Archive.Tar as Tar
  17. import Control.Exception
  18. import qualified Data.ByteString.Lazy as L
  19. import Codec.Compression.GZip (compress)
  20. import qualified Data.Foldable as Fold
  21. import Control.Monad.Trans.Writer (tell, execWriter)
  22. run :: String -> [String] -> IO ()
  23. run a b = do
  24. ec <- rawSystem a b
  25. unless (ec == ExitSuccess) $ exitWith ec
  26. keter :: String -- ^ cabal command
  27. -> Bool -- ^ no build?
  28. -> Bool -- ^ no copy to?
  29. -> [String] -- ^ build args
  30. -> IO ()
  31. keter cabal noBuild noCopyTo buildArgs = do
  32. ketercfg <- keterConfig
  33. mvalue <- decodeFile ketercfg
  34. value <-
  35. case mvalue of
  36. Nothing -> error "No config/keter.yaml found"
  37. Just (Object value) ->
  38. case Map.lookup "host" value of
  39. Just (String s) | "<<" `T.isPrefixOf` s ->
  40. error $ "Please set your hostname in " ++ ketercfg
  41. _ ->
  42. case Map.lookup "user-edited" value of
  43. Just (Bool False) ->
  44. error $ "Please edit your Keter config file at "
  45. ++ ketercfg
  46. _ -> return value
  47. Just _ -> error $ ketercfg ++ " is not an object"
  48. env' <- getEnvironment
  49. cwd' <- getCurrentDirectory
  50. files <- getDirectoryContents "."
  51. project <-
  52. case mapMaybe (T.stripSuffix ".cabal" . T.pack) files of
  53. [x] -> return x
  54. [] -> error "No cabal file found"
  55. _ -> error "Too many cabal files found"
  56. let findFiles (Object v) =
  57. mapM_ go $ Map.toList v
  58. where
  59. go ("exec", String s) = tellFile s
  60. go ("extraFiles", Array a) = Fold.mapM_ tellExtra a
  61. go (_, v') = findFiles v'
  62. tellFile s = tell [collapse $ "config" </> T.unpack s]
  63. tellExtra (String s) = tellFile s
  64. tellExtra _ = error "extraFiles should be a flat array"
  65. findFiles (Array v) = Fold.mapM_ findFiles v
  66. findFiles _ = return ()
  67. bundleFiles = execWriter $ findFiles $ Object value
  68. collapse = T.unpack . T.intercalate "/" . collapse' . T.splitOn "/" . T.pack
  69. collapse' (_:"..":rest) = collapse' rest
  70. collapse' (".":xs) = collapse' xs
  71. collapse' (x:xs) = x : collapse' xs
  72. collapse' [] = []
  73. unless noBuild $ do
  74. stackQueryRunSuccess <- do
  75. eres <- try $ readProcessWithExitCode "stack" ["query"] "" :: IO (Either IOException (ExitCode, String, String))
  76. return $ either (\_ -> False) (\(ec, _, _) -> (ec == ExitSuccess)) eres
  77. let inStackExec = isJust $ lookup "STACK_EXE" env'
  78. mStackYaml = lookup "STACK_YAML" env'
  79. useStack = inStackExec || isJust mStackYaml || stackQueryRunSuccess
  80. if useStack
  81. then do let stackYaml = maybeToList $ fmap ("--stack-yaml="<>) mStackYaml
  82. localBinPath = cwd' </> "dist/bin"
  83. run "stack" $ stackYaml <> ["clean"]
  84. createDirectoryIfMissing True localBinPath
  85. run "stack"
  86. (stackYaml
  87. <> ["--local-bin-path",localBinPath,"build","--copy-bins"]
  88. <> buildArgs)
  89. else do run cabal ["clean"]
  90. run cabal ["configure"]
  91. run cabal ("build" : buildArgs)
  92. _ <- try' $ removeDirectoryRecursive "static/tmp"
  93. archive <- Tar.pack "" $
  94. "config" : "static" : bundleFiles
  95. let fp = T.unpack project ++ ".keter"
  96. L.writeFile fp $ compress $ Tar.write archive
  97. unless noCopyTo $ case Map.lookup "copy-to" value of
  98. Just (String s) ->
  99. let baseArgs = [fp, T.unpack s] :: [String]
  100. scpArgs =
  101. case parseMaybe (.: "copy-to-args") value of
  102. Just as -> as ++ baseArgs
  103. Nothing -> baseArgs
  104. args =
  105. case parseMaybe (.: "copy-to-port") value of
  106. Just i -> "-P" : show (i :: Int) : scpArgs
  107. Nothing -> scpArgs
  108. in run "scp" args
  109. _ -> return ()
  110. where
  111. -- Test for alternative config file extension (yaml or yml).
  112. keterConfig = do
  113. let yml = "config/keter.yml"
  114. ymlExists <- doesFileExist yml
  115. return $ if ymlExists then yml else "config/keter.yaml"
  116. try' :: IO a -> IO (Either SomeException a)
  117. try' = try