packages feed

ngx-export-distribution 0.4.1.0 → 0.5.0.0

raw patch · 4 files changed

+623/−51 lines, 4 filesdep +cabal-plandep +containersdep +extranew-component:exe:nhm-toolPVP ok

version bump matches the API change (PVP)

Dependencies added: cabal-plan, containers, extra, parsec, prettyprinter, prettyprinter-ansi-terminal, text

API changes (from Hackage documentation)

Files

Changelog.md view
@@ -1,3 +1,7 @@+### 0.5.0.0++- Build executable *nhm-tool* to replace utilities *hslibdeps* and *nhm-init*.+ ### 0.4.1.0  - Test GHC version to figure out whether to use option *-flink-rts* in run-time.
NgxExport/Distribution.hs view
@@ -33,6 +33,9 @@     -- *** Building dependencies with cabal v2-build     -- $deps-cabal-v2 +    -- *** Bootstrapping a new project+    -- $bootstrapping-project+     -- *** Drawbacks     -- $drawbacks @@ -114,14 +117,23 @@ -- main = 'defaultMain' -- @ ----- The configuration step requires that utilities /patchelf/ and--- <https://github.com/lyokha/nginx-haskell-module/blob/master/utils/hslibdeps hslibdeps>--- were found in the paths of environment variable /$PATH/.+-- The configuration step requires that utilities /nhm-tool/ and /patchelf/+-- were found in the paths of environment variable /$PATH/. The /nhm-tool/ is+-- packaged with this module and can be installed by running --+-- > $ cabal install+--+-- from the root directory of the module or by running+--+-- > $ cabal install ngx-export-distribution+--+-- from any other directory, in which case it will be installed from /Hackage/.+-- -- Building is a bit cumbersome: it expects explicit option /--prefix/ at the -- configuration step (which will be interpreted as the prefix part of the--- /rpath/ by utility /hslibdeps/) and explicit ghc option /-o/ at the build--- step which is as well used by /hslibdeps/ as the name of the target library.+-- /rpath/ by  /nhm-tool dist/) and explicit ghc option /-o/ at the build+-- step which is as well used by /nhm-tool dist/ as the name of the target+-- library.  -- $cabal-v1 --@@ -194,15 +206,18 @@  -- $setup-hs ----- For building custom artifacts, options of /hslibdeps/ must be accessed+-- For building custom artifacts, options of /nhm-tool dist/ must be accessed -- directly. For this, commands /runhaskell Setup.hs configure \/ build/ can be -- used instead of /cabal v1-configure \/ v1-build/. Let's change the names of -- the directory with dependent libraries and the tar-file to /deps\// and -- /deps.tar.gz/ respectively, and also define the /rpath/ directory without -- using option /--prefix/. ----- > $ runhaskell Setup.hs configure --user --hslibdeps-options="-t/var/lib/nginx/deps -ddeps -adeps"+-- > $ runhaskell Setup.hs configure --user --nhm-tool-options="-t/var/lib/nginx/deps -ddeps -adeps" --+--  Note that despite the name /--nhm-tool-options/, the specified options are+--  passed internally into a sub-command /nhm-tool dist/.+-- -- > $ runhaskell Setup.hs build --ghc-options="ngx_distribution_test.hs -o ngx_distribution_test.so -threaded"  -- $deps-cabal-v2@@ -213,13 +228,7 @@ -- build dependencies and put them in a package environment in the current -- working directory. ----- > $ cabal v2-install --lib --only-dependencies --package-env .------ > $ cabal v2-build ngx-export-distribution------ The second command is probably no-op because /ngx-export-distribution/ should--- have been installed by the first command. At least in Cabal /3.10/, this is--- the case.+-- > $ cabal install --lib --only-dependencies --package-env . -- -- > $ sed -i 's/\(^package-id \)/--\1/' .ghc.environment.x86_64-linux-$(ghc --numeric-version) --@@ -243,10 +252,10 @@ -- [cabal-plan](https://hackage.haskell.org/package/cabal-plan), finding the -- direct dependencies in the /cabal plan/ can be done automatically. ----- > $ hslibdeps -e >> .ghc.environment.x86_64-linux-$(ghc --numeric-version)+-- > $ nhm-tool deps ngx-distribution-test >> .ghc.environment.x86_64-linux-$(ghc --numeric-version) ----- Command /hslibdeps -e/ is a convenient wrapper around /cabal-plan/. After--- running this, four lines looking similar to+-- Command /nhm-tool deps/ builds around the code of the /cabal-plan/ library.+-- After running this, four lines looking similar to -- -- > package-id aeson-2.1.0.0-9b19e87ee2a82567866c50e13806427068fd4bcc78cedb01ecad7389791f6761 -- > package-id base-4.17.0.0@@ -262,38 +271,54 @@ -- Haskell libraries found in the global package database and the Cabal package -- store. +-- $bootstrapping-project+--+-- All the steps listed in the previous section can be automated. This is+-- exactly what /nhm-tool init/ does. Running+--+-- > $ nhm-tool init project-name+--+-- produces files /cabal.project/, /Setup.hs/, /project-name.cabal/, /Makefile/,+-- and /project_name.hs/. If any of the former four files exist, add option /-f/+-- to override them.+--+-- Note that the root Haskell source file is /project_name.hs/ where+-- /project_name/ is /project-name/ with all dashes replaced by underscores. If+-- the source code will depend on packages other than /base/ and /ngx-export/,+-- add them into /project-name.cabal/ manually.+--+-- By default, the target library will be linked against the threaded Haskell+-- RTS library. To link against the base RTS library, add option /-no-threaded/.+--+-- The target library will be installed in directory /\/var\/lib\/nginx/. Use+-- option /-p prefix/ to override the install directory.+--+-- Build and install the target library.+--+-- > $ make+-- > $ sudo make install+--+-- With ghc older than /8.10.6/, build with+--+-- > $ make LINKRTS=-lHSrts_thr-ghc$(ghc --numeric-version)+ -- $drawbacks -- -- With all the building approaches shown above, the following list of drawbacks -- must be taken into account. ----- - Utility /hslibdeps/ collects only libraries prefixed with /libHS/,+-- - Utility /nhm-tool/ collects only libraries prefixed with /libHS/, -- - clean commands such as /cabal v1-clean/ do not delete build artifacts in --   the current working directory, -- - behavior of Cabal commands other than /configure/, /build/ and /clean/ is --   not well defined. -hslibdeps :: Program-hslibdeps = simpleProgram "hslibdeps"+nhmTool :: Program+nhmTool = simpleProgram "nhm-tool"  patchelf :: Program patchelf = simpleProgram "patchelf" -requireHslibdeps :: Verbosity -> ProgramDb -> IO (ConfiguredProgram, ProgramDb)-requireHslibdeps verbosity progdb = do-    mres <- needProgram verbosity prog progdb-    case mres of-        Nothing -> die' verbosity notFound-        Just res -> return res-    where prog = hslibdeps-          progName = programName prog-          notFound = "The program '" ++ progName ++-              "' is required but it could not be found." ++-              "\nNote: Get '" ++ progName ++ "' from " ++ url ++-              " and put it into a directory listed in the PATH."-          url = "https://github.com/lyokha/nginx-haskell-module\-              \/blob/master/utils/hslibdeps"- -- | Builds a shared library. -- -- Runs /ghc/ compiler with the following arguments.@@ -332,8 +357,8 @@                           map (second pure) configGhcOptions         (lib', extraGhcOptions) =             maybe (let name = unPackageName $ pkgName $ package desc-                       nameSo = addExtension name "so"-                   in (nameSo, [addExtension name "hs", "-o", nameSo])+                       nameSo = name <.> "so"+                   in (nameSo, [name <.> "hs", "-o", nameSo])                   ) (, []) lib     unless (null extraGhcOptions) $ do         let extraSourceFile = head extraGhcOptions@@ -357,17 +382,16 @@ -- -- - Collects all dependent Haskell libraries in a directory with the name equal --   to the value of /$abi/ which normally expands to /$arch-$os-$compiler/ (or---   with that overridden in option /--hslibdeps-options/),+--   with that overridden in option /--nhm-tool-options/), -- - adds value /$prefix\/$abi/ (or that overridden in option---   /--hslibdeps-options/) in the beginning of the list of /rpath/ in the+--   /--nhm-tool-options/) in the beginning of the list of /rpath/ in the --   shared library, -- - archives the shared library and the directory with the collected dependent --   libraries in a /tar.gz/ file. ----- All steps are performed by utility--- <https://github.com/lyokha/nginx-haskell-module/blob/master/utils/hslibdeps hslibdeps>.--- It collects all libraries with prefix /libHS/ from the list returned by--- command /ldd/ applied to the shared library.+-- All steps are performed by utility /nhm-tool/. It collects all libraries+-- with prefix /libHS/ from the list returned by command /ldd/ applied to the+-- shared library. patchAndCollectDependentLibs :: Verbosity           -- ^ Verbosity level                              -> FilePath            -- ^ Path to the library                              -> PackageDescription  -- ^ Package description@@ -380,16 +404,16 @@         rpathArg = maybe [] (("-t" :) . pure . (</> dir) . fromPathTemplate) $             flagToMaybe $ prefix $ configInstallDirs $ configFlags lbi         archiveArg = "-a" : [prettyShow $ package desc]-    hslibdepsP <- fst <$> requireHslibdeps verbosity (withPrograms lbi)-    let hslibdepsR = programInvocation hslibdepsP $-            lib : rpathArg ++ dirArg ++ archiveArg-    runProgramInvocation verbosity hslibdepsR+    nhmToolP <- fst <$> requireProgram verbosity nhmTool (withPrograms lbi)+    let nhmToolR = programInvocation nhmToolP $+            "dist" : lib : "-v" : rpathArg ++ dirArg ++ archiveArg+    runProgramInvocation verbosity nhmToolR  -- | Build hooks. -- -- Based on 'simpleUserHooks'. Overrides ----- - 'confHook' by configuring programs /hslibdeps/ and /patchelf/ and then+-- - 'confHook' by configuring programs /nhm-tool/ and /patchelf/ and then --   running the original /confHook/ from /simpleUserHooks/, -- - 'buildHook' by running in sequence 'buildSharedLib' and --   'patchAndCollectDependentLibs'.@@ -398,11 +422,11 @@ -- neither tested nor recommended. ngxExportHooks :: UserHooks ngxExportHooks =-    simpleUserHooks { hookedPrograms = [hslibdeps]+    simpleUserHooks { hookedPrograms = [nhmTool]                     , confHook = \desc flags -> do                         let verbosity = toVerbosity $ configVerbosity flags                             pdb = configPrograms flags-                        _ <- requireHslibdeps verbosity pdb >>=+                        _ <- requireProgram verbosity nhmTool pdb >>=                                  requireProgram verbosity patchelf . snd                         confHook simpleUserHooks desc flags                     , buildHook = \desc lbi _ flags -> do
ngx-export-distribution.cabal view
@@ -1,5 +1,5 @@ name:                       ngx-export-distribution-version:                    0.4.1.0+version:                    0.5.0.0 synopsis:                   Build custom libraries for Nginx haskell module description:                Build custom libraries for         <https://github.com/lyokha/nginx-haskell-module Nginx haskell module>.@@ -23,6 +23,24 @@                           , filepath    exposed-modules:          NgxExport.Distribution++  ghc-options:             -Wall++executable nhm-tool+  default-language:         Haskell2010+  build-depends:            base >= 4.8 && < 5+                          , Cabal >= 3.0.0.0+                          , directory+                          , filepath+                          , text+                          , containers+                          , prettyprinter+                          , prettyprinter-ansi-terminal+                          , cabal-plan+                          , parsec+                          , extra++  main-is:                  nhm-tool.hs    ghc-options:             -Wall 
+ nhm-tool.hs view
@@ -0,0 +1,526 @@+{-# LANGUAGE RecordWildCards, LambdaCase, MultiWayIf, ViewPatterns #-}+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Text.Parsec+import Distribution.Simple.Program.Types+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.Functor+import Data.Maybe+import Data.Char+import Data.List+import Data.List.Extra+import Prettyprinter (pretty, annotate)+import Prettyprinter.Render.Terminal+import System.Environment+import System.Directory+import System.FilePath+import System.Exit+import System.IO+import GHC.IO.Device+import qualified GHC.IO.FD as FD++data DistData = DistData { distDataDir :: String+                         , distDataArchive :: String+                         , distDataTargetDir :: String+                         , distDataPatchOnly :: Bool+                         , distDataTargetLib :: String+                         , distDataWait :: Maybe ArgWait+                         , distDataOwnVerbosity :: Verbosity+                         , distDataOtherVerbosity :: Verbosity+                         , distDataHelp :: Bool+                         }++defaultDistDataDir :: String+defaultDistDataDir = ".hslibs"++data DepsData = DepsData { depsDataProject :: String+                         , depsDataHelp :: Bool+                         }++data InitData = InitData { initDataPrefix :: String+                         , initDataNoThreaded :: Bool+                         , initDataForce :: Bool+                         , initDataToStdout :: Bool+                         , initDataProject :: String+                         , initDataWait :: Maybe ArgWait+                         , initDataHelp :: Bool+                         }++defaultInitDataPrefix :: String+defaultInitDataPrefix = "/var/lib/nginx"++data ArgWait = ArgWaitA | ArgWaitD | ArgWaitT | ArgWaitP++data LddRec = LibHS String (Maybe FilePath)+            | LibOther String (Maybe FilePath)+            deriving Show++data HelpSection = HelpDist | HelpDeps | HelpInit deriving Eq++usage :: Maybe HelpSection -> Bool -> IO ()+usage section success = do+    when (isNothing section) $+        putStrLn "nhm-tool: 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] \+        \hslib\n\n\+        \    'dir' is a directory where dependent libraries will be\n\+        \      collected (default is", T.pack defaultDistDataDir, ")\n\+        \    'target_dir' is a directory where dependent libraries will be\n\+        \      installed (no default, 'hslib' will not be patched if omitted)\n\+        \    'ar' is the base name of the archive to contain 'hslib' 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 == Just HelpDeps) $+        T.putStrLn "\n\+        \  * nhm-tool deps project-name\n\n\+        \    print all direct dependencies of the given project,\n\+        \    the output is compatible with the format of GHC environment files"+    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\+        \    '-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]\n\n\+        \    show this help message and exit, applicable in sub-commands \+        \as well"+    if success+        then exitSuccess+        else exitFailure++main :: IO ()+main = do+    args <- getArgs+    when (null args) $ usage Nothing True+    case head args of+        "dist" -> do+            let distData = foldl parseDistArgs (Just defaultArgs) $ tail args+                defaultArgs = DistData defaultDistDataDir "" "" False ""+                    Nothing normal normal False+            case distData of+                Nothing -> usage (Just HelpDist) False+                Just (normalizeDistData -> distData'@DistData {..}) ->+                    if distDataHelp+                        then usage (Just HelpDist) True+                        else if isJust distDataWait || null distDataTargetLib+                                 then usage (Just HelpDist) False+                                 else cmdDist distData'+        "deps" -> do+            let depsData = foldl parseDepsArgs (Just defaultArgs) $ tail args+                defaultArgs = DepsData "" False+            case depsData of+                Nothing -> usage (Just HelpDeps) False+                Just depsData'@DepsData {..} ->+                    if depsDataHelp+                        then usage (Just HelpDeps) True+                        else if null depsDataProject+                                 then usage (Just HelpDeps) False+                                 else cmdDeps depsData'+        "init" -> do+            let initData = foldl parseInitArgs (Just defaultArgs) $ tail args+                defaultArgs = InitData defaultInitDataPrefix False False False+                    "" Nothing False+            case initData of+                Nothing -> usage (Just HelpInit) False+                Just initData'@InitData {..} ->+                    if initDataHelp+                        then usage (Just HelpInit) True+                        else if isJust initDataWait || null initDataProject ||+                                    initDataForce && initDataToStdout+                                 then usage (Just HelpInit) False+                                 else cmdInit initData'+        "-h" -> usage Nothing True+        "-help" -> usage Nothing True+        "--help" -> usage Nothing True+        _ -> usage Nothing False+    where normalizeDistData dist@DistData {..} =+              dist { distDataTargetDir =+                         if distDataTargetDir == "-"+                             then ""+                             else distDataTargetDir+                   , distDataArchive =+                         if distDataArchive == "-"+                             then ""+                             else distDataArchive+                   , distDataPatchOnly =+                         distDataDir == "-" || distDataPatchOnly+                   }++parseDistArgs :: Maybe DistData -> String -> Maybe DistData+parseDistArgs Nothing _ = Nothing+parseDistArgs (Just dist@DistData {..}) arg =+    case distDataWait of+        Nothing ->+            let optLong = length arg > 2+                optValue = drop 2 arg+            in if | "-d" `isPrefixOf` arg ->+                        if optLong+                            then Just dist' { distDataDir = optValue }+                            else Just dist { distDataWait = Just ArgWaitD }+                  | "-t" `isPrefixOf` arg ->+                        if optLong+                            then Just dist' { distDataTargetDir = optValue }+                            else Just dist { distDataWait = Just ArgWaitT }+                  | "-a" `isPrefixOf` arg ->+                        if optLong+                            then Just dist' { distDataArchive = optValue }+                            else Just dist { distDataWait = Just ArgWaitA }+                  | "-p" == arg ->+                        Just dist' { distDataPatchOnly = True }+                  | "-v" == arg ->+                        Just dist' { distDataOwnVerbosity = verbose }+                  | "-vv" == arg ->+                        Just dist' { distDataOwnVerbosity = verbose+                                   , distDataOtherVerbosity = verbose+                                   }+                  | "-h" == arg || "-help" == arg || "--help" == arg ->+                        Just dist' { distDataHelp = True }+                  | "-" `isPrefixOf` arg -> Nothing+                  | otherwise ->+                         if null distDataTargetLib+                             then Just dist' { distDataTargetLib = arg }+                             else Nothing+        Just ArgWaitD -> Just dist' { distDataDir = arg }+        Just ArgWaitT -> Just dist' { distDataTargetDir = arg }+        Just ArgWaitA -> Just dist' { distDataArchive = arg }+        Just _ -> undefined+        where dist' = dist { distDataWait = Nothing }++parseDepsArgs :: Maybe DepsData -> String -> Maybe DepsData+parseDepsArgs Nothing _ = Nothing+parseDepsArgs (Just deps@DepsData {..}) arg =+    if | "-h" == arg || "-help" == arg || "--help" == arg ->+             Just deps { depsDataHelp = True }+       | "-" `isPrefixOf` arg -> Nothing+       | otherwise ->+              if null depsDataProject+                  then Just deps { depsDataProject = arg }+                  else Nothing++parseInitArgs :: Maybe InitData -> String -> Maybe InitData+parseInitArgs Nothing _ = Nothing+parseInitArgs (Just init'@InitData {..}) arg =+    case initDataWait of+        Nothing ->+            let optLong = length arg > 2+                optValue = drop 2 arg+            in if | "-p" `isPrefixOf` arg ->+                        if optLong+                            then Just init'' { initDataPrefix = optValue }+                            else Just init' { initDataWait = Just ArgWaitP }+                  | "-no-threaded" == arg ->+                        Just init'' { initDataNoThreaded = True }+                  | "-f" == arg ->+                        Just init'' { initDataForce = True }+                  | "-to-stdout" == arg ->+                        Just init'' { initDataToStdout = True }+                  | "-h" == arg || "-help" == arg || "--help" == arg ->+                        Just init'' { initDataHelp = True }+                  | "-" `isPrefixOf` arg -> Nothing+                  | otherwise ->+                         if null initDataProject+                             then Just init'' { initDataProject = arg }+                             else Nothing+        Just ArgWaitP -> Just init'' { initDataPrefix = arg }+        Just _ -> undefined+        where init'' = init' { initDataWait = Nothing }++cmdDist :: DistData -> IO ()+cmdDist DistData {..} = do+    let pdb = emptyProgramDb+        patchelf = simpleProgram "patchelf"+        ldd = simpleProgram "ldd"+    (patchelf', pdb') <- requireProgram distDataOtherVerbosity patchelf pdb+    if distDataPatchOnly+        then patchTargetLib patchelf'+        else do+            (ldd', pdb'') <- requireProgram distDataOtherVerbosity ldd pdb'+            putStrLn' "---> Collecting libraries"+            lddOut <- getProgramOutput distDataOtherVerbosity+                ldd' [distDataTargetLib]+            case parseLddOutput lddOut of+                Left msg -> do+                    hPrint stderr msg+                    exitFailure+                Right recs -> do+                    (tar', _) <- requireProgram distDataOtherVerbosity+                        tarProgram pdb''+                    collectLibs recs lddOut+                    unless (null distDataTargetDir) $ putStrLn' ""+                    patchTargetLib patchelf'+                    putStrLn' ""+                    archiveLibs tar'+    where patchTargetLib patchelf' =+              unless (null distDataTargetDir) $ do+                  putStrLn' $ "---> Patching " ++ distDataTargetLib+                  patchelfOut <- getProgramOutput distDataOtherVerbosity+                      patchelf' ["--print-rpath", distDataTargetLib]+                  case parsePatchelfRpathOutput patchelfOut of+                      Left msg -> do+                          hPrint stderr msg+                          exitFailure+                      Right paths -> do+                          unless (distDataTargetDir `elem` paths) $+                              runProgram distDataOtherVerbosity patchelf'+                                  ["--set-rpath"+                                  ,distDataTargetDir ++ ':' : patchelfOut+                                  ,distDataTargetLib+                                  ]+                          patchelfOut' <- getProgramOutput+                              distDataOtherVerbosity+                                  patchelf' ["--print-rpath"+                                            ,distDataTargetLib+                                            ]+                          putStrLnTrim patchelfOut'+          collectLibs recs lddOut = do+              let recsLibHS = filter (\case+                                          LibHS _ _ -> True+                                          _ -> False+                                     ) recs+              if null recsLibHS+                  then do+                      hPutStrLn stderr+                          "There were no Haskell libraries collected"+                      exitFailure+                  else do+                      let recsLibHSNotFound =+                              filter (\case+                                          LibHS _ Nothing -> True+                                          _ -> False+                                     ) recsLibHS+                      if null recsLibHSNotFound+                          then do+                              createDirectoryIfMissing False distDataDir+                              forM_ recsLibHS $ \case+                                  LibHS _ (Just path) -> do+                                      let dst = distDataDir </>+                                                    takeFileName path+                                      putStrLn' $ path ++ " -> " ++ dst+                                      copyFile path dst+                                  _ -> undefined+                          else do+                              hPutStrLn stderr $+                                 "There were missing Haskell libraries:\n" +++                                     lddOut+                              exitFailure+          archiveLibs tar' =+              unless (null distDataArchive) $ do+                  putStrLn' "---> Archiving artifacts"+                  tarOut <- getProgramOutput distDataOtherVerbosity tar'+                      ["czvf"+                      ,distDataArchive <.> ".tar.gz"+                      ,distDataTargetLib+                      ,distDataDir+                      ]+                  putStrLnTrim tarOut+          putStrLn' = when (distDataOwnVerbosity == verbose) . putStrLn+          putStrLnTrim = putStrLn' . fst . spanEnd (== '\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" $ many $+    spaces >>+    (try (do+              lib <- manyTill anyChar' sep+              path <- (char bs >> right <&> Just . (bs :))+                      <|> (string "not found" >> return Nothing)+              return $ if "libHS" `isPrefixOf` lib+                           then LibHS lib path+                           else LibOther lib path+         )+     <|> (right <&> (`LibOther` Nothing))+    )+    where right = manyTill anyChar' (try $ spaces1 >> addr >> newline)+          addr = string "(0x" >> many1 hexDigit >> char ')'+          anyChar' = satisfy (/= '\n')+          sep = spaces1 >> string "=>" >> spaces1+          spaces1 = skipMany1 space+          bs = '/'++cmdDeps :: DepsData -> IO ()+cmdDeps DepsData {..} = do+    units <- pjUnits <$> findAndDecodePlanJson (ProjectRelativeToDir ".")+    let locals = [ Unit {..}+                 | Unit {..} <- M.elems units+                 , uType == UnitTypeLocal+                 , M.member CompNameLib uComps+                 , toPkgName uPId == T.pack depsDataProject+                 ]+    when (null locals) $ do+        hPutStrLn stderr $ "Failed to find plan for " ++ depsDataProject+        exitFailure+    let locals' = foldl (\a (Unit {..}) ->+                            let comps = M.filterWithKey+                                    (const . (== CompNameLib)) uComps+                                deps = M.map ciLibDeps comps+                            in M.foldr S.union a deps+                        ) S.empty locals+    forM_ (S.toList locals') $ \(UnitId local) ->+        putStrLn $ "package-id " ++ T.unpack local+    where toPkgName (PkgId (PkgName name) _) = name++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)+                ,(replace "-" "_" initDataProject ++ ".hs"+                  ,projectHs init'+                  ,False+                 )+                ]+    forM_ files $+        if initDataToStdout+            then \(name, file, _) ->+                printHeader name >> T.putStrLn file+            else \(name, file, overridable) -> 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+              isTerm <- isTerminal FD.stdout+              if isTerm+                  then putDoc $ annotate (color Blue <> underlined) $+                      pretty $ header ++ "\n\n"+                  else putStrLn $ " ~~~ " ++ header ++ "\n"+          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:              1.24\n\+     \\n\+     \custom-setup\n\+     \  setup-depends:            base >= 4.8 && < 5\n\+     \                          , ngx-export-distribution\n\+     \\n\+     \library\n\+     \  default-language:         Haskell2010\n\+     \  build-depends:            base >= 4.8 && < 5\n\+     \                          , ngx-export\n\+     \\n\+     \  ghc-options:             -Wall -O2\n"+    ,if initDataNoThreaded+         then ""+         else+     "\n\+     \  if impl(ghc >= 8.10.6)\n\+     \    ghc-options:           -threaded\n"+    ]++makefile :: InitData -> Text+makefile InitData {..} = T.concat+    ["NAME := ", T.replace "-" "_" $ T.pack 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\+     \\n\+     \SRC := $(NAME).hs\n\+     \LIB := $(NAME).so\n\+     \STUB := $(NAME)_stub.h\n\+     \DISTR := $(PKGNAME)-$(PKGVER).tar.gz\n\+     \\n\+     \GHCVER := $(shell ghc --numeric-version)\n\+     \GHCENV := .ghc.environment.$(MACHINE)-$(KERNEL)-$(GHCVER)\n\+     \DEPLIBS := $(MACHINE)-$(KERNEL)-ghc-$(GHCVER)\n\+     \DISTDIR := dist\n\+     \DISTV2DIR := dist-newstyle\n\+     \\n\+     \all: $(DISTR)\n\+     \\n\+     \$(DISTR): $(SRC)\n\+     \\tcabal install --lib --only-dependencies --package-env .\n\+     \\tsed -i 's/\\(^package-id \\)/--\\1/' $(GHCENV)\n\+     \\trunhaskell --ghc-arg=-package=base \\\n\+     \\t --ghc-arg=-package=$(PKGDISTR) Setup.hs configure \\\n\+     \\t --package-db=clear --package-db=global \\\n\+     \\t $$(sed -n 's/^\\(package-db\\)\\s\\+/--\\1=/p' $(GHCENV)) \\\n\+     \\t --prefix=$(PREFIX)\n\+     \\tnhm-tool deps $(PKGNAME) >> $(GHCENV)\n\+     \\trunhaskell --ghc-arg=-package=base \\\n\+     \\t --ghc-arg=-package=$(PKGDISTR) Setup.hs build \\\n\+     \\t --ghc-options=\"$(SRC) -o $(LIB) $(LINKRTS)\"\n\+     \\n\+     \install: $(DISTR)\n\+     \\tinstall -d $(PREFIX)\n\+     \\ttar xf $(DISTR) -C $(PREFIX) --no-same-owner\n\+     \\n\+     \.PHONY: clean\n\+     \\n\+     \clean:\n\+     \\trm -rf $(DISTDIR) $(DISTV2DIR) $(DEPLIBS)\n\+     \\trm -f $(GHCENV) $(STUB) $(NAME).hi $(NAME).o\n\+     \\trm -f $(LIB)\n\+     \\n\+     \clean-all: clean\n\+     \\trm -f $(DISTR)\n"+    ]++projectHs :: InitData -> Text+projectHs InitData {..} = T.concat+    ["{-# LANGUAGE TemplateHaskell #-}\n\n\+     \module "+    ,T.pack $ fst $+        foldl (\(a, tr) v -> if v == '-' || v == '_'+                                 then (a, True)+                                 else if tr || null a+                                          then (a ++ [toUpper v], False)+                                          else (a ++ [v], False)+              ) ("", False) initDataProject+    , " where\n\n"+    ]+