{-# LANGUAGE RecordWildCards, LambdaCase, MultiWayIf, ViewPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Text.Parsec hiding (uncons)
import Distribution.Simple.Program
import Distribution.Verbosity
import Cabal.Plan
import Control.Monad
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Text (Text)
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Maybe
import Data.Char
import Data.List
import System.Console.ANSI
import System.Environment
import System.Directory
import System.FilePath
import System.Exit
import System.IO
import Data.Version (showVersion)
import Paths_ngx_export_distribution (version)
data DistData = DistData { distDataDir :: String
, distDataArchive :: String
, distDataTargetDir :: String
, distDataPatchOnly :: Bool
, distDataTargetLib :: String
, distDataOwnVerbosity :: Verbosity
, distDataOtherVerbosity :: Verbosity
, distDataWaitArg :: Maybe String
, distDataHelp :: Bool
}
defaultDistDataDir :: String
defaultDistDataDir = ".hslibs"
data PlanData = PlanData { planDataBuilddir :: String
, planDataQuery :: Maybe PlanDataQuery
, planDataWaitArg :: Maybe String
, planDataWaitProjectName :: Bool
, planDataHelp :: Bool
}
data PlanDataQuery = PlanDataQueryDeps String
| PlanDataQueryAbi
data InitData = InitData { initDataPrefix :: String
, initDataNoThreaded :: Bool
, initDataForce :: Bool
, initDataToStdout :: Bool
, initDataProject :: String
, initDataGhcOptions :: [String]
, initDataWaitArg :: Maybe String
, initDataHelp :: Bool
}
defaultInitDataPrefix :: String
defaultInitDataPrefix = "/var/lib/nginx"
data LddRec = LibHS String (Maybe FilePath)
| LibFFI String (Maybe FilePath)
| LibOther String (Maybe FilePath)
deriving Show
data HelpSection = HelpDist | HelpPlan | HelpDeps | HelpInit deriving Eq
replaceChar :: Char -> Char -> String -> String
replaceChar from to = map $ \v -> if v == from then to else v
progVersion :: String
progVersion = "nhm-tool " ++ showVersion version
usage :: Maybe HelpSection -> Bool -> IO ()
usage section success = do
when (isNothing section) $
putStrLn $ progVersion ++
": help building custom Haskell handlers for Nginx,\n\
\this is a tool from \
\https://github.com/lyokha/nginx-haskell-module\n"
putStrLn "Usage:"
when (isNothing section || section == Just HelpDist) $
T.putStrLn $ T.concat ["\n\
\ * nhm-tool dist [-d dir -a ar | -p] [-t target_dir] [-v | -vv] \
\lib\n\n\
\ collect Haskell libraries on which 'lib' depends,\n\
\ patch 'lib' to enable loading dependent libraries from \
\'target_dir'\n\n\
\ 'dir' is a directory where dependent libraries will be collected\n\
\ (default is ", T.pack defaultDistDataDir, ")\n\
\ 'target_dir' is a directory where dependent libraries will be\n\
\ installed (no default, 'lib' will not be patched if omitted)\n\
\ 'ar' is the base name of the archive to contain 'lib' and\n\
\ 'dir' (no default, the archive will not be created if omitted)\n\
\ if '-p' (patch-only) is specified then dependent libraries will\n\
\ neither be collected nor archived\n\
\ use options '-v' and '-vv' to increase verbosity level\n\
\ special value '-' for 'dir', 'target_dir', and 'ar' resets them\n\
\ (with -d- being equivalent to -p)"
]
when (isNothing section || section `elem` map Just [HelpPlan, HelpDeps]) $
T.putStrLn "\n\
\ * nhm-tool plan [-d dir] [deps project-name | abi]\n\n\
\ print build plan or its derivatives:\n\
\ 'deps' print all direct dependencies of 'project-name',\n\
\ the output is compatible with the format of GHC environment \
\files\n\
\ 'abi' print 'Arch-Os-CompilerId' bundle in GHC style\n\n\
\ 'dir' is the cabal build directory where the build plan is \
\located\n\
\ (default is dist-newstyle)"
when (isNothing section || section == Just HelpDeps) $
T.putStrLn "\n\
\ * nhm-tool deps [-d dir] project-name\n\n\
\ synonym for 'nhm-tool plan [-d dir] deps project-name'"
when (isNothing section || section == Just HelpInit) $
T.putStrLn $ T.concat ["\n\
\ * nhm-tool init [-p dir] [-no-threaded] [-f | -to-stdout] \
\project-name\n\n\
\ bootstrap environment to build custom Haskell handlers\n\n\
\ '-p' prefix for install dir, default is ",
T.pack defaultInitDataPrefix, "\n\
\ '-no-threaded' use base RTS library\n\
\ '-f' force re-writing of existing files (except the source file)\n\
\ '-to-stdout' do not write files but dump what will be written"
]
when (isNothing section) $
T.putStrLn "\n\
\ * nhm-tool [-h | -help | --help | -v | -version | --version]\n\n\
\ show this help message or version and exit,\n\
\ help options are applicable in sub-commands as well"
if success
then exitSuccess
else exitFailure
main :: IO ()
main = do
args <- getArgs
case args of
"dist" : args' -> do
let distData = foldl parseDistArg (Just defaultArgs) args'
defaultArgs = DistData defaultDistDataDir "" "" False ""
normal normal Nothing False
case distData of
Nothing -> usage (Just HelpDist) False
Just (normalizeDistData -> distData'@DistData {..}) ->
if | distDataHelp ->
usage (Just HelpDist) $ length args' == 1
| isJust distDataWaitArg
|| null distDataTargetLib ->
usage (Just HelpDist) False
| otherwise -> cmdDist distData'
"plan" : args' -> do
let planData = foldl parsePlanArg (Just defaultArgs) args'
defaultArgs = PlanData "" Nothing Nothing False False
case planData of
Nothing -> usage (Just HelpPlan) False
Just planData'@PlanData {..} ->
if | planDataHelp ->
usage (Just HelpPlan) $ length args' == 1
| isJust planDataWaitArg
|| planDataWaitProjectName ->
usage (Just HelpPlan) False
| otherwise -> cmdPlan planData'
"deps" : args' -> do
let planData = foldl parsePlanArg (Just defaultArgs) args'
defaultArgs = PlanData "" Nothing Nothing True False
case planData of
Nothing -> usage (Just HelpDeps) False
Just planData'@PlanData {..} ->
if | planDataHelp ->
usage (Just HelpDeps) $ length args' == 1
| isJust planDataWaitArg
|| planDataWaitProjectName ->
usage (Just HelpDeps) False
| otherwise -> cmdPlan planData'
"init" : args' -> do
let initData = foldl parseInitArg (Just defaultArgs) args'
defaultArgs = InitData defaultInitDataPrefix False False False
"" ["-Wall", "-O2"] Nothing False
case initData of
Nothing -> usage (Just HelpInit) False
Just initData'@InitData {..} ->
if | initDataHelp ->
usage (Just HelpInit) $ length args' == 1
| isJust initDataWaitArg
|| null initDataProject
|| initDataForce && initDataToStdout ->
usage (Just HelpInit) False
| otherwise -> cmdInit initData'
[arg] | arg `elem` ["-h", "-help", "--help"] ->
usage Nothing True
| arg `elem` ["-v", "-version", "--version"] ->
putStrLn progVersion >> exitSuccess
args' -> usage Nothing $ null args'
where normalizeDistData dist@DistData { distDataDir = "-" } =
normalizeDistData dist { distDataDir = defaultDistDataDir
, distDataPatchOnly = True
}
normalizeDistData dist@DistData { distDataArchive = "-" } =
normalizeDistData dist { distDataArchive = "" }
normalizeDistData dist@DistData { distDataTargetDir = "-" } =
normalizeDistData dist { distDataTargetDir = "" }
normalizeDistData dist@DistData { distDataDir = "" } =
normalizeDistData dist { distDataDir = defaultDistDataDir }
normalizeDistData dist = dist
parseDistArg :: Maybe DistData -> String -> Maybe DistData
parseDistArg Nothing _ = Nothing
parseDistArg (Just dist@DistData {..}) arg =
case distDataWaitArg of
Nothing ->
let (opt, value) = splitAt 2 arg
in if | "-d" == opt ->
Just $ if null value
then dist { distDataWaitArg = Just opt }
else dist' { distDataDir = value }
| "-t" == opt ->
Just $ if null value
then dist { distDataWaitArg = Just opt }
else dist' { distDataTargetDir = value }
| "-a" == opt ->
Just $ if null value
then dist { distDataWaitArg = Just opt }
else dist' { distDataArchive = value }
| "-p" == arg ->
Just dist' { distDataPatchOnly = True }
| "-v" == arg ->
Just dist' { distDataOwnVerbosity = verbose }
| "-vv" == arg ->
Just dist' { distDataOwnVerbosity = verbose
, distDataOtherVerbosity = verbose
}
| (`elem` ["-h", "-help", "--help"]) arg ->
Just dist' { distDataHelp = True }
| Just '-' == (fst <$> uncons arg) ->
Nothing
| null distDataTargetLib ->
Just dist' { distDataTargetLib = arg }
| otherwise ->
Nothing
Just "-d" -> Just dist' { distDataDir = arg }
Just "-t" -> Just dist' { distDataTargetDir = arg }
Just "-a" -> Just dist' { distDataArchive = arg }
Just _ -> undefined
where dist' = dist { distDataWaitArg = Nothing }
parsePlanArg :: Maybe PlanData -> String -> Maybe PlanData
parsePlanArg Nothing _ = Nothing
parsePlanArg (Just plan@PlanData {..}) arg =
case planDataWaitArg of
Nothing ->
let (opt, value) = splitAt 2 arg
in if | "deps" == arg && not hasDataQuery ->
Just plan' { planDataWaitProjectName = True }
| "abi" == arg && not hasDataQuery ->
Just plan' { planDataQuery = Just PlanDataQueryAbi }
| "-d" == opt ->
Just $ if null value
then plan { planDataWaitArg = Just opt }
else plan' { planDataBuilddir = value }
| (`elem` ["-h", "-help", "--help"]) arg ->
Just plan' { planDataHelp = True }
| Just '-' == (fst <$> uncons arg) ->
Nothing
| planDataWaitProjectName ->
Just plan'
{ planDataQuery = Just $ PlanDataQueryDeps arg
, planDataWaitProjectName = False
}
| otherwise ->
Nothing
Just "-d" -> Just plan' { planDataBuilddir = arg }
Just _ -> undefined
where plan' = plan { planDataWaitArg = Nothing }
hasDataQuery = isJust planDataQuery || planDataWaitProjectName
parseInitArg :: Maybe InitData -> String -> Maybe InitData
parseInitArg Nothing _ = Nothing
parseInitArg (Just init'@InitData {..}) arg =
case initDataWaitArg of
Nothing ->
let (opt, value) = splitAt 2 arg
in if | "-p" == opt ->
Just $ if null value
then init' { initDataWaitArg = Just opt }
else init'' { initDataPrefix = value }
| "-no-threaded" == arg ->
Just init'' { initDataNoThreaded = True }
| "-f" == arg ->
Just init'' { initDataForce = True }
| "-to-stdout" == arg ->
Just init'' { initDataToStdout = True }
| (`elem` ["-h", "-help", "--help"]) arg ->
Just init'' { initDataHelp = True }
| Just '-' == (fst <$> uncons arg) ->
Nothing
| null initDataProject ->
Just init'' { initDataProject = arg }
| otherwise ->
Nothing
Just "-p" -> Just init'' { initDataPrefix = arg }
Just _ -> undefined
where init'' = init' { initDataWaitArg = Nothing }
cmdDist :: DistData -> IO ()
cmdDist DistData {..} = do
patchelf <- requireProgram' $ simpleProgram "patchelf"
if distDataPatchOnly
then patchTargetLib patchelf
else do
ldd <- requireProgram' $ simpleProgram "ldd"
putStrLn' "---> Collecting libraries"
lddOut <- getProgramOutput' ldd [distDataTargetLib]
case parseLddOutput lddOut of
Left err -> do
hPutStrLn stderr $ show err ++ " in\n" ++ lddOut
exitFailure
Right recs -> do
tar' <- requireProgram' tarProgram
collectLibs recs lddOut
unless (null distDataTargetDir) $
putStrLn' "" >> patchTargetLib patchelf
unless (null distDataArchive) $
putStrLn' "" >> archiveLibs tar'
where patchTargetLib patchelf = unless (null distDataTargetDir) $ do
putStrLn' $ "---> Patching " ++ distDataTargetLib
let printRpath = getProgramOutput'
patchelf ["--print-rpath", distDataTargetLib]
patchelfOut <- printRpath
case parsePatchelfRpathOutput patchelfOut of
Left err -> do
hPutStrLn stderr $ show err ++ " in\n" ++ patchelfOut
exitFailure
Right paths -> do
unless (distDataTargetDir `elem` paths) $ runProgram'
patchelf ["--set-rpath"
,distDataTargetDir ++ ':' : patchelfOut
,distDataTargetLib
]
printRpath >>= putStrLnTrim
collectLibs recs lddOut = do
let recsLibHS = M.fromList $
mapMaybe (\case
LibHS name path -> Just (name, path)
LibFFI name path -> Just (name, path)
_ -> Nothing
) recs
if M.null recsLibHS
then do
hPutStrLn stderr $
"No Haskell libraries were collected in\n" ++ lddOut
exitFailure
else do
let (M.mapMaybe id -> recsLibHS', recsLibHSNotFound) =
M.partition isJust recsLibHS
if M.null recsLibHSNotFound
then do
createDirectoryIfMissing False distDataDir
forM_ (M.elems recsLibHS') $ \path -> do
let dst = distDataDir </> takeFileName path
putStrLn' $ path ++ " -> " ++ dst
copyFile path dst
else do
hPutStrLn stderr $
"Haskell libraries " ++
show (M.keys recsLibHSNotFound) ++
" were not found in\n" ++ lddOut
exitFailure
archiveLibs tar' = unless (null distDataArchive) $ do
putStrLn' "---> Archiving artifacts"
tarOut <- getProgramOutput'
tar' ["czvf"
,distDataArchive <.> ".tar.gz"
,distDataTargetLib
,distDataDir
]
putStrLnTrim tarOut
requireProgram' = fmap fst .
flip (requireProgram distDataOtherVerbosity) emptyProgramDb
getProgramOutput' = getProgramOutput distDataOtherVerbosity
runProgram' = runProgram distDataOtherVerbosity
putStrLn' = when (distDataOwnVerbosity == verbose) . putStrLn
putStrLnTrim = putStrLn' . dropWhileEnd (== '\n')
parsePatchelfRpathOutput :: String -> Either ParseError [String]
parsePatchelfRpathOutput =
parse (many (satisfy (/= colon)) `sepBy` char colon) "patchelf_rpath"
where colon = ':'
parseLddOutput :: String -> Either ParseError [LddRec]
parseLddOutput = flip parse "ldd" $ manyLines $
try (toLddRec <$>
anyCharsTill arrow <*>
((Just .) . (:) <$> char '/' <*> right
<|> Nothing <$ string "not found"
)
)
<|> (`LibOther` Nothing) <$> (try right <|> anyCharsTill (arrow *> addr))
where manyLines = many . (spaces *>) . (<* newline)
toLddRec lib | "libHS" `isPrefixOf` lib = LibHS lib
| "libffi.so" `isPrefixOf` lib = LibFFI lib
| otherwise = LibOther lib
anyCharsTill = manyTill $ satisfy (/= '\n')
right = anyCharsTill $ spaces1 *> addr
addr = string "(0x" *> many1 hexDigit *> char ')'
arrow = spaces1 *> string "=>" *> spaces1
spaces1 = skipMany1 space
cmdPlan :: PlanData -> IO ()
cmdPlan PlanData {..} = do
let buildDir | null planDataBuilddir = ProjectRelativeToDir "."
| otherwise = InBuildDir planDataBuilddir
path <- findPlanJson buildDir
case planDataQuery of
Nothing ->
readFile path >>= putStrLn
Just (PlanDataQueryDeps proj) -> do
units <- pjUnits <$> decodePlanJson path
let comps = [ uComps
| Unit {..} <- M.elems units
, uType == UnitTypeLocal
, let uPkgName = (\(PkgId (PkgName pkg) _) -> pkg) uPId
, uPkgName == T.pack proj
]
when (null comps) $ do
hPutStrLn stderr $ "Failed to find plan for " ++ proj
exitFailure
let deps = foldl (\a curComps ->
let libComps = M.filterWithKey
(const . (== CompNameLib)) curComps
in M.foldr S.union a $
M.map ciLibDeps libComps
) S.empty comps
forM_ (S.toList deps) $ \(UnitId unit) ->
putStrLn $ "package-id " ++ T.unpack unit
Just PlanDataQueryAbi -> do
PlanJson {..} <- decodePlanJson path
T.putStrLn $
T.intercalate "-" [pjArch, pjOs, dispPkgId pjCompilerId]
cmdInit :: InitData -> IO ()
cmdInit init'@InitData {..} = do
let files = [("cabal.project", cabalProject init', True)
,("Setup.hs", setupHs init', True)
,(initDataProject ++ ".cabal", projectCabal init', True)
,("Makefile", makefile init', True)
,(replaceChar '-' '_' initDataProject ++ ".hs"
,projectHs init'
,False
)
,("hie.yaml", hieYaml init', True)
]
forM_ files $ \(name, file, overridable) ->
if initDataToStdout
then printHeader (name ++ "\n") >> T.putStrLn file
else do
exists <- doesFileExist name
if exists
then if initDataForce && overridable
then T.writeFile name file
else hPutStrLn stderr $
if overridable
then useForceMsg name
else existsMsg name
else T.writeFile name file
where printHeader header = do
isANSITerm <- hSupportsANSI stdout
if isANSITerm
then do
setSGR [SetColor Foreground Dull Blue
,SetUnderlining SingleUnderline
]
putStrLn header
setSGR [Reset]
else putStrLn $ " ~~~ " ++ header
existsMsg name = "File " ++ name ++ " exists"
useForceMsg name = existsMsg name ++ ", use option -f to override it"
cabalProject :: InitData -> Text
cabalProject InitData {..} = T.concat
["packages: ", T.pack initDataProject, ".cabal\n"]
setupHs :: InitData -> Text
setupHs = const
"import NgxExport.Distribution\n\
\main = defaultMain\n"
projectCabal :: InitData -> Text
projectCabal InitData {..} = T.concat
["name: ", T.pack initDataProject, "\n\
\version: 0.1.0.0\n\
\build-type: Custom\n\
\cabal-version: 2.0\n\
\\n\
\custom-setup\n\
\ setup-depends: base >= 4.8 && < 5\n\
\ , ngx-export-distribution\n\
\\n\
\library\n\
\ default-language: Haskell2010\n\
\ build-tool-depends: ngx-export-distribution:nhm-tool\n\
\ build-depends: base >= 4.8 && < 5\n\
\ , ngx-export\n\
\\n\
\ ghc-options: ", T.pack $ unwords initDataGhcOptions, "\n"
,if initDataNoThreaded
then ""
else
"\n\
\ if impl(ghc >= 9.0.1)\n\
\ ghc-options: -threaded\n"
]
makefile :: InitData -> Text
makefile InitData {..} = T.concat
["NAME := ", T.pack $ replaceChar '-' '_' initDataProject, "\n\
\PKGNAME := $(subst _,-,$(NAME))\n\
\PKGVER := 0.1.0.0\n\
\\n\
\PREFIX := ", T.pack initDataPrefix, "\n\
\MACHINE := $(shell uname -m)\n\
\KERNEL := $(shell uname -s | tr A-Z a-z)\n\
\\n\
\PKGDISTR := ngx-export-distribution\n\
\NHMTOOL := nhm-tool\n\
\\n\
\SRC := $(NAME).hs\n\
\LIB := $(NAME).so\n\
\DISTR := $(PKGNAME)-$(PKGVER).tar.gz\n\
\\n\
\override OBJS := $(SRC:.hs=.o)\n\
\override HIOBJS := $(SRC:.hs=.hi)\n\
\override DYNOBJS := $(SRC:.hs=.dyn_o)\n\
\override DYNHIOBJS := $(SRC:.hs=.dyn_hi)\n\
\override STUBS := $(SRC:.hs=_stub.h)\n\
\\n\
\GHC := ghc\n\
\CABAL := cabal\n\
\GHCVER := $(shell $(GHC) --numeric-version)\n\
\CABALVER := $(shell $(CABAL) --numeric-version)\n\
\GHCPUID := $(shell \\\n\
\ if printf '3.12\\n$(CABALVER)' | sort -VC && \\\n\
\ printf '9.8\\n$(GHCVER)' | sort -VC; \\\n\
\ then ghcpuid=`$(GHC) --info | sed -n \\\n\
\ 's/^.*\\x28\"Project Unit Id\",\"\\(.*\\)\"\\x29.*$$/\
\\\1/ip'`; \\\n\
\ if test -z \"$$ghcpuid\"; \\\n\
\ then echo \"ghc-$(GHCVER)\"; \\\n\
\ else echo \"$$ghcpuid\"; \\\n\
\ fi; \\\n\
\ else echo \"ghc-$(GHCVER)\"; \\\n\
\ fi)\n\
\GHCENV := .ghc.environment.$(MACHINE)-$(KERNEL)-$(GHCVER)\n\
\GHCENVLNK := ", ghcEnvLnk, "\n\
\DEPLIBS := $(MACHINE)-$(KERNEL)-$(GHCPUID)\n\
\BUILDDIR := dist-nhm\n\
\SETUPCONFIG := $(BUILDDIR)/setup-config\n\
\\n\
\.PHONY: all env config install clean clean-all\n\
\\n\
\all: $(DISTR)\n\
\\n\
\env: $(GHCENVLNK)\n\
\\n\
\config: $(SETUPCONFIG)\n\
\\n\
\$(GHCENVLNK): cabal.project $(PKGNAME).cabal\n\
\\trm -f \"$(GHCENVLNK)\"\n\
\\t$(CABAL) install --builddir=\"$(BUILDDIR)\" --lib --only-dependencies \
\\\\n\
\\t --package-env .\n\
\\tsed -i 's/\\(^package-id \\)/--\\1/' \"$(GHCENV)\"\n",
updatePath,
"\t$(NHMTOOL) deps \"$(PKGNAME)\" -d \"$(BUILDDIR)\" >> \"$(GHCENV)\"\n\
\\tln -sf \"$(GHCENV)\" \"$(GHCENVLNK)\"\n\
\\n\
\$(SETUPCONFIG): $(GHCENVLNK)\n",
updatePath,
"\trunhaskell --ghc-arg=-package=base \\\n\
\\t --ghc-arg=-package=\"$(PKGDISTR)\" Setup.hs configure \\\n\
\\t --builddir=\"$(BUILDDIR)\" \\\n\
\\t --package-db=clear --package-db=global \\\n\
\\t $$(sed -n 's/^\\(package-db\\)\\s\\+/--\\1=/p' \"$(GHCENV)\") \\\n\
\\t $$(sed -n 's/^package-id\\s\\+\\(.*\\)'` \\\n\
\\t `'\\(-\\([0-9]\\+\\.\\)*[0-9]\\+\\($$\\|-.*\\)\\)/'` \\\n\
\\t `'--dependency=\\1=\\1\\2/p' \\\n\
\\t \"$(GHCENV)\") \\\n\
\\t --prefix=\"$(PREFIX)\"\n\
\\n\
\$(DISTR): $(SETUPCONFIG) $(SRC)\n",
updatePath,
"\trunhaskell --ghc-arg=-package=base \\\n\
\\t --ghc-arg=-package=\"$(PKGDISTR)\" Setup.hs build \\\n\
\\t --builddir=\"$(BUILDDIR)\" \\\n\
\\t --ghc-options=\"$(SRC) -o $(LIB) -package-env $(GHCENVLNK) \
\$(LINKRTS)\"\n\
\\n\
\install: $(DISTR)\n\
\\tinstall -d \"$(PREFIX)\"\n\
\\ttar xf \"$(DISTR)\" -C \"$(PREFIX)\" --no-same-owner\n\
\\n\
\clean:\n\
\\trm -rf \"$(DEPLIBS)\"\n\
\\trm -f $(OBJS) $(HIOBJS) $(DYNOBJS) $(DYNHIOBJS) $(STUBS)\n\
\\trm -f \"$(LIB)\"\n\
\\n\
\clean-all: clean\n\
\\trm -rf \"$(BUILDDIR)\"\n\
\\trm -f \"$(GHCENV)\" \"$(GHCENVLNK)\" \"$(DISTR)\"\n"
]
where updatePath =
"\tif test \"$(NHMTOOL)\" = nhm-tool && ! command -v nhm-tool \
\>/dev/null; \\\n\
\\tthen \\\n\
\\t PATH=$$(dirname $$($(CABAL) list-bin \"$(PKGDISTR)\" \\\n\
\\t --builddir=\"$(BUILDDIR)\")):$$PATH; \\\n\
\\tfi; \\\n"
projectHs :: InitData -> Text
projectHs InitData {..} = T.concat
["{-# LANGUAGE TemplateHaskell #-}\n\n\
\module "
,T.pack $ foldr (\v -> if v == '-' || v == '_'
then maybe [] (uncurry $ (:) . toUpper) . uncons
else (v :)
) "" $ '_' : initDataProject
," where\n\n"
]
hieYaml :: InitData -> Text
hieYaml InitData {..} = T.concat
["cradle:\n\
\ bios:\n\
\ shell: |\n\
\ if [ -L ", ghcEnvLnk, " ]\n\
\ then\n\
\ printf \""
,T.replace "-" "\\055" $ T.pack $ intercalate "\\n" initDataGhcOptions
,"\\n\" > \"$HIE_BIOS_OUTPUT\"\n\
\ find . -name '[A-Z]*.hs' -exec sh -c '\n\
\ file=$(echo \"$1\" | cut -c 3-)\n\
\ if grep -q \"^\\s*module $(echo \"$file\" |\n\
\ sed '\\''s/^\\(.*\\)\\..*/\\1/;s/\\//./g'\\'')\" \
\\"$file\"\n\
\ then\n\
\ echo \"$file\" >> \"$HIE_BIOS_OUTPUT\"\n\
\ fi\n\
\ ' sh {} \\;\n\
\ else\n\
\ echo \"Ghc environment file wasn't found, \"`\n\
\ `\"run \\\"make env\\\" and restart \
\language server.\" >&2\n\
\ exit 1\n\
\ fi\n"
]
ghcEnvLnk :: Text
ghcEnvLnk = ".ghc.environment.lnk"