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 +4/−0
- NgxExport/Distribution.hs +74/−50
- ngx-export-distribution.cabal +19/−1
- nhm-tool.hs +526/−0
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"+ ]+