on master help sign in
command: Parse preproc correctly; stop using StaticPtr; run ghcid in nix-shell
commit4e81096008c98af52b7483cb617d134e935b4b70 authorElliot Cameron <eacameron@gmail.com> dateFri 17 Jan 2020, 17:07 (7 years ago) parent9d1c728
4 files changed+42−76
lib/command/obelisk-command.cabal+8−7 lib/command/src/Obelisk/Command.hs+9−30 lib/command/src/Obelisk/Command/Run.hs+25−34 lib/command/src/Obelisk/Command/Utils.hs−5
lib/command/obelisk-command.cabal+8−7
@@ -7,35 +7,36 @@ library
77 hs-source-dirs: src
88 build-depends:
99 aeson
10− , ansi-terminal
11− , Cabal
1210 , aeson-pretty
11+ , ansi-terminal
1312 , base
1413 , base16-bytestring
1514 , binary
1615 , bytestring
17− , obelisk-cliapp
16+ , Cabal
1817 , containers
1918 , data-default
2019 , directory
2120 , either
2221 , errors
22+ , exceptions
23+ , extra
2324 , filepath
25+ , git
2426 , github
2527 , here
26− , git
2728 , hnix
2829 , hpack
2930 , io-streams
30− , exceptions
31− , logging-effect
3231 , lens
32+ , logging-effect
33+ , megaparsec
3334 , modern-uri
3435 , monad-loops
3536 , mtl
36− , megaparsec
3737 , network
3838 , network-uri
39+ , obelisk-cliapp
3940 , optparse-applicative
4041 , placeholders
4142 , process
lib/command/src/Obelisk/Command.hs+9−30
@@ -15,7 +15,6 @@ import qualified Data.ByteString.Base16 as Base16
1515import qualified Data.ByteString.Lazy as LBS
1616import Data.Foldable (for_)
1717import Data.List
18−import Data.Maybe (catMaybes)
1918import qualified Data.Text as T
2019import Data.Text.Encoding
2120import Data.Text.Encoding.Error (lenientDecode)
@@ -36,7 +35,6 @@ import Obelisk.Command.Deploy
3635import Obelisk.Command.Project
3736import Obelisk.Command.Run
3837import Obelisk.Command.Thunk
39−import Obelisk.Command.Utils
4038import qualified Obelisk.Command.VmBuilder as VmBuilder
4139import qualified Obelisk.Command.Preprocessor as Preprocessor
4240
@@ -103,29 +101,11 @@ data ObCommand
103101data ObInternal
104102 = ObInternal_RunStaticIO StaticKey
105103 | ObInternal_CLIDemo
106− -- the "apply-packages" preprocessor argument syntax is also handled outside
104+ -- the preprocessor argument syntax is also handled outside
107105 -- optparse-applicative, but it shouldn't ever conflict with another syntax
108106 | ObInternal_ApplyPackages String String String [String]
109107 deriving Show
110108
111−inNixShell' :: MonadObelisk m => StaticPtr (ObeliskT IO ()) -> m ()
112−inNixShell' p = withProjectRoot "." $ \root -> do
113− cmd <- liftIO $ unwords <$> mkCmd -- TODO: shell escape instead of unwords
114− projectShell root True "ghc" (Just cmd)
115− where
116− mkCmd = do
117− argsCfg <- getArgsConfig
118− myArgs <- getArgs
119− obArgs <- parseCLIArgs argsCfg myArgs
120− progName <- getObeliskExe
121− return $ progName : catMaybes
122− [ Just "--no-handoff"
123− , bool Nothing (Just "--verbose") $ _args_verbose obArgs
124− , Just "internal"
125− , Just "run-static-io"
126− , Just $ encodeStaticKey $ staticKey p
127− ]
128−
129109obCommand :: ArgsConfig -> Parser ObCommand
130110obCommand cfg = hsubparser
131111 (mconcat
@@ -280,11 +260,7 @@ parserPrefs = defaultPrefs
280260 }
281261
282262parseCLIArgs :: ArgsConfig -> [String] -> IO Args
283−parseCLIArgs cfg = \case
284− (origPath:inPath:outPath:"apply-packages":packagePaths)
285− | any (\c -> c == '.' || c == '/') origPath ->
286− pure $ Args False False $ ObCommand_Internal $ ObInternal_ApplyPackages origPath inPath outPath packagePaths
287− as -> pure as >>= handleParseResult . execParserPure parserPrefs (argsInfo cfg)
263+parseCLIArgs cfg = handleParseResult . execParserPure parserPrefs (argsInfo cfg)
288264
289265-- | Create an Obelisk config for the current process.
290266mkObeliskConfig :: IO Obelisk
@@ -357,6 +333,9 @@ main' argsCfg = do
357333 liftIO $ executeFile impl False ("--no-handoff" : myArgs) Nothing
358334 case myArgs of
359335 "--no-handoff" : as -> go as -- If we've been told not to hand off, don't hand off
336+ origPath:inPath:outPath:preprocessorName:packagePaths
337+ | preprocessorName == preprocessorIdentifier && any (\c -> c == '.' || c == pathSeparator) origPath ->
338+ ob $ ObCommand_Internal $ ObInternal_ApplyPackages origPath inPath outPath packagePaths
360339 a:as -- Otherwise bash completion would always hand-off even if the user isn't trying to
361340 | "--bash-completion" `isPrefixOf` a
362341 && "--no-handoff" `elem` as -> go (a:as)
@@ -397,24 +376,24 @@ ob = \case
397376 Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
398377 DeployCommand_Update -> deployUpdate "."
399378 DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs
400− ObCommand_Run -> inNixShell' $ static run
379+ ObCommand_Run -> run
401380 ObCommand_Thunk tc -> case tc of
402381 ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config)
403382 ThunkCommand_Unpack thunks -> for_ thunks unpackThunk
404383 ThunkCommand_Pack thunks config -> for_ thunks (packThunk config)
405384 ObCommand_Repl -> runRepl
406− ObCommand_Watch -> inNixShell' $ static runWatch
385+ ObCommand_Watch -> runWatch
407386 ObCommand_Shell so -> withProjectRoot "." $ \root ->
408387 projectShell root False (_shellOpts_shell so) (_shellOpts_command so)
409388 ObCommand_Doc shell' pkgs -> withProjectRoot "." $ \root ->
410389 projectShell root False shell' (Just $ haddockCommand pkgs)
411390 ObCommand_Internal icmd -> case icmd of
412− ObInternal_RunStaticIO k -> liftIO (unsafeLookupStaticPtr @(ObeliskT IO ()) k) >>= \case
391+ ObInternal_RunStaticIO k -> liftIO (unsafeLookupStaticPtr @(ObeliskT IO ()) k) >>= \case -- TODO: DELETE
413392 Nothing -> failWith $ "ObInternal_RunStaticIO: no such StaticKey: " <> T.pack (show k)
414393 Just p -> do
415394 c <- getObelisk
416395 liftIO $ runObelisk c $ deRefStaticPtr p
417− ObInternal_CLIDemo -> cliDemo
396+ ObInternal_CLIDemo -> cliDemo -- TODO: DELETE
418397 ObInternal_ApplyPackages origPath inPath outPath packagePaths -> do
419398 liftIO $ Preprocessor.applyPackages origPath inPath outPath packagePaths
420399
lib/command/src/Obelisk/Command/Run.hs+25−34
@@ -3,7 +3,6 @@
33{-# LANGUAGE MultiParamTypeClasses #-}
44{-# LANGUAGE OverloadedStrings #-}
55{-# LANGUAGE ScopedTypeVariables #-}
6−{-# LANGUAGE TemplateHaskell #-}
76{-# LANGUAGE TupleSections #-}
87module Obelisk.Command.Run where
98
@@ -15,7 +14,7 @@ import Control.Monad.Reader (MonadIO)
1514import Data.Coerce (coerce)
1615import Data.Either
1716import Data.Foldable (for_)
18−import qualified Data.List as L
17+import Data.List.Extra (dropPrefix)
1918import qualified Data.List.NonEmpty as NE
2019import Data.Maybe
2120import qualified Data.Text as T
@@ -24,7 +23,6 @@ import Debug.Trace (trace)
2423import Distribution.Compiler (CompilerFlavor(..))
2524import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
2625import Distribution.Parsec.ParseResult (runParseResult)
27−import Distribution.Pretty
2826import qualified Distribution.System as Dist
2927import Distribution.Types.BuildInfo
3028import Distribution.Types.CondTree
@@ -38,13 +36,14 @@ import qualified Hpack.Yaml as Hpack
3836import Language.Haskell.Extension
3937import Network.Socket hiding (Debug)
4038import System.Directory
39+import System.Environment (getExecutablePath)
4140import System.FilePath
4241import qualified System.Info
4342import System.IO.Temp (withSystemTempDirectory)
4443
45−import Obelisk.App (MonadObelisk, ObeliskT)
46−import Obelisk.CliApp (Severity (..) , callCommand, failWith, putLog, proc, readCreateProcessWithExitCode, readProcessAndLogStderr)
47−import Obelisk.Command.Project (inProjectShell, withProjectRoot)
44+import Obelisk.App (MonadObelisk)
45+import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, readProcessAndLogStderr)
46+import Obelisk.Command.Project (inProjectShell, obeliskDirName, toObeliskDir, withProjectRoot)
4847import Obelisk.Command.Utils (findExePath, ghcidExePath, nixBuildExePath, nixExePath)
4948
5049data CabalPackageInfo = CabalPackageInfo
@@ -61,11 +60,12 @@ data CabalPackageInfo = CabalPackageInfo
6160 -- ^ List of compiler-specific options (e.g., the "ghc-options" field of the cabal file)
6261 }
6362
64−-- NOTE: `run` is not polymorphic like the rest because we use StaticPtr to invoke it.
65−run :: ObeliskT IO ()
63+-- | Used to signal to obelisk that it's being invoked as a preprocessor
64+preprocessorIdentifier :: String
65+preprocessorIdentifier = "__preprocessor-apply-packages"
66+
67+run :: MonadObelisk m => m ()
6668run = withProjectRoot "." $ \root -> do
67− let nixPath = $(staticWhich "nix")
68− nixBuildPath = $(staticWhich "nix-build")
6969 pkgs <- getLocalPkgs root
7070 withGhciScript pkgs $ \dotGhciPath -> do
7171 freePort <- getFreePort
@@ -124,20 +124,15 @@ runWatch = do
124124-- to the Nix @project@ function.
125125getLocalPkgs :: (MonadObelisk m, MonadIO m) => FilePath -> m [FilePath]
126126getLocalPkgs root = do
127− rootAbs <- liftIO $ makeAbsolute root
127+ (_exitCode, out, err) <- readCreateProcessWithExitCode $
128128 proc findExePath ["-L", root, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
129129 putLog Debug $ T.strip $ T.pack err
130130
131− let packagePaths = filter (not . isIgnored) $ T.lines $ T.strip $ T.pack out
132− obeliskImplDir = ".obelisk/impl/"
133− rootObelisk = T.pack (rootAbs </> T.unpack obeliskImplDir)
134− isIgnored path =
135− obeliskImplDir `T.isInfixOf` path
136− && case T.stripPrefix rootObelisk path of
137− Nothing -> True
138− Just pathSuffix -> obeliskImplDir `T.isInfixOf` pathSuffix
139−
140− pure $ map T.unpack packagePaths
131+ let
132+ -- We ignore any path that has ".obelisk" in it, but keep the root ".obelisk" paths
133+ packagePaths = filter (not . isIgnored) $ map T.unpack $ T.lines $ T.strip $ T.pack out
134+ isIgnored path = obeliskDirName `elem` dropPrefix (splitPath $ toObeliskDir root) (splitPath path)
135+ pure packagePaths
141136
142137data GuessPackageFileError = GuessPackageFileError_Ambiguous [FilePath] | GuessPackageFileError_NotFound
143138 deriving (Eq, Ord, Show)
@@ -255,15 +250,13 @@ withGhciScript dirs f = do
255250 unless (null pkgDirErrs) $
256251 putLog Warning $ T.pack $ "Failed to find packages in " <> intercalate ", " pkgDirErrs
257252
253+ selfExe <- liftIO getExecutablePath
258254 let
259255 packageNames = map _cabalPackageInfo_packageName packageInfos
260− languageFromPkgs = L.nub $ mapMaybe _cabalPackageInfo_defaultLanguage packageInfos
261− -- NOTE when no default-language is present cabal sets Haskell98
262− language = NE.toList $ fromMaybe (Haskell98 NE.:| []) $ NE.nonEmpty languageFromPkgs
263256 dotGhci = unlines
264− [ ":set -pgmF ob -optF apply-packages " <> unwords (map (("-optF " <>) . _cabalPackageInfo_packageRoot) packageInfos)
257+ -- TODO: Shell escape
258+ [ ":set -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . _cabalPackageInfo_packageRoot) packageInfos)
265259 , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
266− , ":set " <> unwords (("-X" <>) . prettyShow <$> language)
267260 , ":load Backend Frontend"
268261 -- We need to also load 'Obelisk.Run' if it's part of our ghci session.
269262 <> if "obelisk-run" `elem` packageNames then " Obelisk.Run" else ""
@@ -271,7 +264,6 @@ withGhciScript dirs f = do
271264 , "import qualified Frontend"
272265 , "import qualified Backend"
273266 ]
274− warnDifferentLanguages language
275267 withSystemTempDirectory "ob-ghci" $ \fp -> do
276268 let dotGhciPath = fp </> ".ghci"
277269 liftIO $ writeFile dotGhciPath dotGhci
@@ -281,16 +273,15 @@ withGhciScript dirs f = do
281273 rootedSourceDirs pkg = NE.toList $
282274 (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
283275
284−warnDifferentLanguages :: MonadObelisk m => [Language] -> m ()
285−warnDifferentLanguages (_:_:_) = putLog Warning "Different languages detected across packages which may result in errors when loading the repl"
286−warnDifferentLanguages _ = return ()
287−
288276-- | Run ghci repl
289277runGhciRepl
290278 :: MonadObelisk m
291279 => FilePath -- ^ Path to .ghci
292280 -> m ()
293−runGhciRepl dotGhci = inProjectShell "ghc" $ "ghci " <> makeBaseGhciOptions dotGhci
281+runGhciRepl dotGhci =
282+ -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
283+ -- ghc that is provided by the shell in the user's project.
284+ inProjectShell "ghc" $ "ghci " <> makeBaseGhciOptions dotGhci
294285
295286-- | Run ghcid
296287runGhcid
@@ -298,7 +289,7 @@ runGhcid
298289 => FilePath -- ^ Path to .ghci
299290 -> Maybe String -- ^ Optional command to run at every reload
300291 -> m ()
301−runGhcid dotGhci mcmd = callCommand $ unwords $ $(staticWhich "ghcid") : opts
292+runGhcid dotGhci mcmd = inProjectShell "ghc" $ unwords $ ghcidExePath : opts -- TODO: Shell escape
302293 where
303294 opts =
304295 [ "-W"
@@ -307,7 +298,7 @@ runGhcid dotGhci mcmd = callCommand $ unwords $ $(staticWhich "ghcid") : opts
307298 , "--reload=config"
308299 , "--outputfile=ghcid-output.txt"
309300 ] <> testCmd
310− testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'")
301+ testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'") -- TODO: Shell escape
311302
312303makeBaseGhciOptions :: FilePath -> String
313304makeBaseGhciOptions dotGhci =
lib/command/src/Obelisk/Command/Utils.hs−5
@@ -21,8 +21,6 @@ import Data.Maybe (maybeToList)
2121import Data.Text (Text)
2222import qualified Data.Text as T
2323import Data.Void (Void)
24−import System.Directory (canonicalizePath)
25−import System.Environment (getExecutablePath)
2624import System.Exit (ExitCode)
2725import System.Which (staticWhich)
2826import Text.Megaparsec as MP
@@ -31,9 +29,6 @@ import Text.Megaparsec.Char as MP
3129import Obelisk.App (MonadObelisk)
3230import Obelisk.CliApp
3331
34−getObeliskExe :: IO FilePath
35−getObeliskExe = getExecutablePath >>= canonicalizePath
36−
3732cp :: FilePath
3833cp = $(staticWhich "cp")
3934