Skip to content

Commit e7b9153

Browse files
authored
Merge pull request #5 from OxfordAbstracts/opts
Opts
2 parents 294f3f7 + fb1c02a commit e7b9153

35 files changed

+1451
-135
lines changed

app/Command/Bundle.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
-- | Bundles compiled PureScript modules for the browser.
2-
module Command.Bundle (command) where
2+
module Command.Bundle (command, initSqlite) where
33

44
import Prelude
55

66
import System.Exit (exitFailure)
77
import System.IO (stderr, hPutStrLn)
88
import Options.Applicative qualified as Opts
9+
import Language.PureScript.Make.IdeCache (sqliteInit)
910

1011
app :: IO ()
1112
app = do
@@ -21,3 +22,9 @@ command :: Opts.Parser (IO ())
2122
command = run <$> (Opts.helper <*> pure ()) where
2223
run :: () -> IO ()
2324
run _ = app
25+
26+
initSqlite :: Opts.Parser (IO ())
27+
initSqlite = run <$> (Opts.helper <*> pure ()) where
28+
run :: () -> IO ()
29+
run _ = do
30+
sqliteInit "output"

app/Command/Compile.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import System.Exit (exitSuccess, exitFailure)
2424
import System.Directory (getCurrentDirectory)
2525
import System.IO (hPutStr, stderr, stdout)
2626
import System.IO.UTF8 (readUTF8FilesT)
27+
import Language.PureScript.Make.IdeCache (sqliteInit)
2728

2829
data PSCMakeOptions = PSCMakeOptions
2930
{ pscmInput :: [FilePath]
@@ -56,6 +57,7 @@ printWarningsAndErrors verbose True files warnings errors = do
5657

5758
compile :: PSCMakeOptions -> IO ()
5859
compile PSCMakeOptions{..} = do
60+
sqliteInit "output"
5961
input <- toInputGlobs $ PSCGlobs
6062
{ pscInputGlobs = pscmInput
6163
, pscInputGlobsFromFile = pscmInputFromFile

app/Command/Ide.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import System.Directory (doesDirectoryExist, getCurrentDirectory, setCurrentDire
4040
import System.FilePath ((</>))
4141
import System.IO (BufferMode(..), hClose, hFlush, hSetBuffering, hSetEncoding, utf8)
4242
import System.IO.Error (isEOFError)
43+
import Database.SQLite.Simple qualified as SQLite
44+
import Protolude qualified as D
4345

4446
listenOnLocalhost :: Network.PortNumber -> IO Network.Socket
4547
listenOnLocalhost port = do
@@ -138,6 +140,7 @@ command = Opts.helper <*> subcommands where
138140
conf = IdeConfiguration
139141
{ confLogLevel = logLevel
140142
, confOutputPath = outputPath
143+
, sqliteFilePath = outputPath </> "cache.db"
141144
, confGlobs = globs
142145
, confGlobsFromFile = globsFromFile
143146
, confGlobsExclude = globsExcluded
@@ -148,6 +151,11 @@ command = Opts.helper <*> subcommands where
148151
{ ideStateVar = ideState
149152
, ideConfiguration = conf
150153
, ideCacheDbTimestamp = ts
154+
, query = \q -> SQLite.withConnection (outputPath </> "cache.db")
155+
(\conn -> do
156+
SQLite.execute_ conn "pragma busy_timeout = 30000;"
157+
SQLite.query_ conn $ SQLite.Query q
158+
)
151159
}
152160
startServer port env
153161

@@ -189,6 +197,7 @@ startServer port env = Network.withSocketsDo $ do
189197
case accepted of
190198
Left err -> $(logError) err
191199
Right (cmd, h) -> do
200+
-- traceM cmd
192201
case decodeT cmd of
193202
Right cmd' -> do
194203
let message duration =

app/Command/QuickBuild.hs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
-----------------------------------------------------------------------------
2+
--
3+
-- Module : Main
4+
-- Description : The server accepting commands for psc-ide
5+
-- Copyright : Christoph Hegemann 2016
6+
-- License : MIT (http://opensource.org/licenses/MIT)
7+
--
8+
-- Maintainer : Christoph Hegemann <[email protected]>
9+
-- Stability : experimental
10+
--
11+
-- |
12+
-- The server accepting commands for psc-ide
13+
-----------------------------------------------------------------------------
14+
15+
{-# LANGUAGE PackageImports #-}
16+
{-# LANGUAGE TemplateHaskell #-}
17+
18+
module Command.QuickBuild (command) where
19+
20+
import Protolude
21+
22+
import Data.Aeson qualified as Aeson
23+
import Data.Set qualified as Set
24+
import Control.Concurrent.STM (newTVarIO)
25+
import "monad-logger" Control.Monad.Logger (MonadLogger, logDebug, logError, logInfo)
26+
import Data.IORef (newIORef)
27+
import Data.Text.IO qualified as T
28+
import Data.ByteString.Char8 qualified as BS8
29+
import Data.ByteString.Lazy.Char8 qualified as BSL8
30+
import GHC.IO.Exception (IOErrorType(..), IOException(..))
31+
import Language.PureScript.Ide (handleCommand)
32+
import Language.PureScript.Ide.Command (Command(..), commandName)
33+
import Language.PureScript.Ide.Util (decodeT, displayTimeSpec, encodeT, logPerf, runLogger)
34+
import Language.PureScript.Ide.Error (IdeError(..))
35+
import Language.PureScript.Ide.State (updateCacheTimestamp)
36+
import Language.PureScript.Ide.Types (Ide, IdeConfiguration(..), IdeEnvironment(..), IdeLogLevel(..), emptyIdeState)
37+
import Network.Socket qualified as Network
38+
import Options.Applicative qualified as Opts
39+
import SharedCLI qualified
40+
import System.Directory (doesDirectoryExist, getCurrentDirectory, setCurrentDirectory)
41+
import System.FilePath ((</>))
42+
import System.IO (BufferMode(..), hClose, hFlush, hSetBuffering, hSetEncoding, utf8)
43+
import System.IO.Error (isEOFError)
44+
import Database.SQLite.Simple qualified as SQLite
45+
import Language.PureScript.Options as PO
46+
47+
listenOnLocalhost :: Network.PortNumber -> IO Network.Socket
48+
listenOnLocalhost port = do
49+
let hints = Network.defaultHints
50+
{ Network.addrFamily = Network.AF_INET
51+
, Network.addrSocketType = Network.Stream
52+
}
53+
addr:_ <- Network.getAddrInfo (Just hints) (Just "127.0.0.1") (Just (show port))
54+
bracketOnError
55+
(Network.socket (Network.addrFamily addr) (Network.addrSocketType addr) (Network.addrProtocol addr))
56+
Network.close
57+
(\sock -> do
58+
Network.setSocketOption sock Network.ReuseAddr 1
59+
Network.bind sock (Network.addrAddress addr)
60+
Network.listen sock Network.maxListenQueue
61+
pure sock)
62+
63+
data ServerOptions = ServerOptions
64+
{ _serverDirectory :: Maybe FilePath
65+
, _serverGlobs :: [FilePath]
66+
, _serverGlobsFromFile :: Maybe FilePath
67+
, _serverGlobsExcluded :: [FilePath]
68+
, _serverOutputPath :: FilePath
69+
, _srcFile :: FilePath
70+
, _serverPort :: Network.PortNumber
71+
, _serverLoglevel :: IdeLogLevel
72+
-- TODO(Christoph) Deprecated
73+
, _serverEditorMode :: Bool
74+
, _serverPolling :: Bool
75+
, _serverNoWatch :: Bool
76+
77+
} deriving (Show)
78+
79+
data ClientOptions = ClientOptions
80+
{ clientPort :: Network.PortNumber
81+
}
82+
83+
command :: Opts.Parser (IO ())
84+
command = Opts.helper <*> subcommands where
85+
subcommands :: Opts.Parser (IO ())
86+
subcommands = (Opts.subparser . fold)
87+
[ Opts.command "server"
88+
(Opts.info (fmap server serverOptions <**> Opts.helper)
89+
(Opts.progDesc "Start a server process"))
90+
]
91+
92+
server :: ServerOptions -> IO ()
93+
server opts'@(ServerOptions dir globs globsFromFile globsExcluded outputPath srcFile port logLevel editorMode polling noWatch) = do
94+
when (logLevel == LogDebug || logLevel == LogAll)
95+
(putText "Parsed Options:" *> print opts')
96+
maybe (pure ()) setCurrentDirectory dir
97+
ideState <- newTVarIO emptyIdeState
98+
cwd <- getCurrentDirectory
99+
let fullOutputPath = cwd </> outputPath
100+
101+
102+
when noWatch
103+
(putText "The --no-watch flag is deprecated and ignored. purs ide no longer uses a file system watcher, instead it relies on its clients to notify it about updates and checks timestamps to invalidate itself")
104+
105+
unlessM (doesDirectoryExist fullOutputPath) $ do
106+
putText "Your output directory didn't exist. This usually means you didn't compile your project yet."
107+
putText "psc-ide needs you to compile your project (for example by running pulp build)"
108+
109+
let
110+
conf = IdeConfiguration
111+
{ confLogLevel = logLevel
112+
, confOutputPath = outputPath
113+
, sqliteFilePath = outputPath </> "cache.db"
114+
, confGlobs = globs
115+
, confGlobsFromFile = globsFromFile
116+
, confGlobsExclude = globsExcluded
117+
}
118+
ts <- newIORef Nothing
119+
let
120+
env = IdeEnvironment
121+
{ ideStateVar = ideState
122+
, ideConfiguration = conf
123+
, ideCacheDbTimestamp = ts
124+
, query = \q -> SQLite.withConnection (outputPath </> "cache.db")
125+
(\conn -> SQLite.query_ conn $ SQLite.Query q)
126+
}
127+
startServer srcFile env
128+
129+
serverOptions :: Opts.Parser ServerOptions
130+
serverOptions =
131+
ServerOptions
132+
<$> optional (Opts.strOption (Opts.long "directory" `mappend` Opts.short 'd'))
133+
<*> many SharedCLI.inputFile
134+
<*> SharedCLI.globInputFile
135+
<*> many SharedCLI.excludeFiles
136+
<*> Opts.strOption (Opts.long "output-directory" `mappend` Opts.value "output/")
137+
<*> Opts.strOption (Opts.long "file" `mappend` Opts.value "output/")
138+
<*> (fromIntegral <$>
139+
Opts.option Opts.auto (Opts.long "port" `mappend` Opts.short 'p' `mappend` Opts.value (4242 :: Integer)))
140+
<*> (parseLogLevel <$> Opts.strOption
141+
(Opts.long "log-level"
142+
`mappend` Opts.value ""
143+
`mappend` Opts.help "One of \"debug\", \"perf\", \"all\" or \"none\""))
144+
-- TODO(Christoph): Deprecated
145+
<*> Opts.switch (Opts.long "editor-mode")
146+
<*> Opts.switch (Opts.long "no-watch")
147+
<*> Opts.switch (Opts.long "polling")
148+
149+
parseLogLevel :: Text -> IdeLogLevel
150+
parseLogLevel s = case s of
151+
"debug" -> LogDebug
152+
"perf" -> LogPerf
153+
"all" -> LogAll
154+
"none" -> LogNone
155+
_ -> LogDefault
156+
157+
startServer :: FilePath -> IdeEnvironment -> IO ()
158+
startServer fp'' env = do
159+
-- BSL8.putStrLn $ Aeson.encode fp''
160+
runLogger (confLogLevel (ideConfiguration env)) (runReaderT (rebuildC fp'') env)
161+
-- runLogger (confLogLevel (ideConfiguration env)) (runReaderT (forever (loop sock)) env)
162+
where
163+
rebuildC :: (Ide m, MonadLogger m) => FilePath -> m ()
164+
rebuildC fp = do
165+
runExceptT $ do
166+
result <- handleCommand (RebuildSync fp Nothing (Set.fromList [PO.JS]))
167+
168+
-- liftIO $ BSL8.putStrLn $ Aeson.encode result
169+
170+
return ()
171+
172+
173+
return ()
174+
175+
loop :: (Ide m, MonadLogger m) => Network.Socket -> m ()
176+
loop sock = do
177+
accepted <- runExceptT (acceptCommand sock)
178+
case accepted of
179+
Left err -> $(logError) err
180+
Right (cmd, h) -> do
181+
case decodeT cmd of
182+
Right cmd' -> do
183+
let message duration =
184+
"Command "
185+
<> commandName cmd'
186+
<> " took "
187+
<> displayTimeSpec duration
188+
logPerf message $ do
189+
result <- runExceptT $ do
190+
updateCacheTimestamp >>= \case
191+
Nothing -> pure ()
192+
Just (before, after) -> do
193+
-- If the cache db file was changed outside of the IDE
194+
-- we trigger a reset before processing the command
195+
$(logInfo) ("cachedb was changed from: " <> show before <> ", to: " <> show after)
196+
unless (isLoadAll cmd') $
197+
void (handleCommand Reset *> handleCommand (LoadSync []))
198+
handleCommand cmd'
199+
liftIO $ catchGoneHandle $ BSL8.hPutStrLn h $ case result of
200+
Right r -> Aeson.encode r
201+
Left err -> Aeson.encode err
202+
liftIO (hFlush stdout)
203+
Left err -> do
204+
let errMsg = "Parsing the command failed with:\n" <> err <> "\nCommand: " <> cmd
205+
$(logError) errMsg
206+
liftIO $ do
207+
catchGoneHandle (T.hPutStrLn h (encodeT (GeneralError errMsg)))
208+
hFlush stdout
209+
liftIO $ catchGoneHandle (hClose h)
210+
211+
isLoadAll :: Command -> Bool
212+
isLoadAll = \case
213+
Load [] -> True
214+
_ -> False
215+
216+
catchGoneHandle :: IO () -> IO ()
217+
catchGoneHandle =
218+
handle (\e -> case e of
219+
IOError { ioe_type = ResourceVanished } ->
220+
putText "[Error] psc-ide-server tried to interact with the handle, but the connection was already gone."
221+
_ -> throwIO e)
222+
223+
acceptCommand
224+
:: (MonadIO m, MonadLogger m, MonadError Text m)
225+
=> Network.Socket
226+
-> m (Text, Handle)
227+
acceptCommand sock = do
228+
h <- acceptConnection
229+
$(logDebug) "Accepted a connection"
230+
cmd' <- liftIO (catchJust
231+
-- this means that the connection was
232+
-- terminated without receiving any input
233+
(\e -> if isEOFError e then Just () else Nothing)
234+
(Just <$> T.hGetLine h)
235+
(const (pure Nothing)))
236+
case cmd' of
237+
Nothing -> throwError "Connection was closed before any input arrived"
238+
Just cmd -> do
239+
$(logDebug) ("Received command: " <> cmd)
240+
pure (cmd, h)
241+
where
242+
acceptConnection = liftIO $ do
243+
-- Use low level accept to prevent accidental reverse name resolution
244+
(s,_) <- Network.accept sock
245+
h <- Network.socketToHandle s ReadWriteMode
246+
hSetEncoding h utf8
247+
hSetBuffering h LineBuffering
248+
pure h

app/Main.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Command.Docs qualified as Docs
88
import Command.Graph qualified as Graph
99
import Command.Hierarchy qualified as Hierarchy
1010
import Command.Ide qualified as Ide
11+
import Command.QuickBuild qualified as QB
1112
import Command.Publish qualified as Publish
1213
import Command.REPL qualified as REPL
1314
import Control.Monad (join)
@@ -61,6 +62,9 @@ main = do
6162
[ Opts.command "bundle"
6263
(Opts.info Bundle.command
6364
(Opts.progDesc "This command was removed in v0.15.0. Run this command for migration information."))
65+
, Opts.command "sqlite"
66+
(Opts.info Bundle.initSqlite
67+
(Opts.progDesc "Init sqlite"))
6468
, Opts.command "compile"
6569
(Opts.info Compile.command
6670
(Opts.progDesc "Compile PureScript source files"))
@@ -76,6 +80,9 @@ main = do
7680
, Opts.command "ide"
7781
(Opts.info Ide.command
7882
(Opts.progDesc "Start or query an IDE server process"))
83+
, Opts.command "qb"
84+
(Opts.info QB.command
85+
(Opts.progDesc "Quick build module"))
7986
, Opts.command "publish"
8087
(Opts.info Publish.command
8188
(Opts.progDesc "Generates documentation packages for upload to Pursuit"))

profile-admin.txt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
'purs' 'compile' '--source-globs-file' '.spago/sources.txt' +RTS '-N' '-sprofile.txt'
2-
1,881,065,351,416 bytes allocated in the heap
3-
573,448,796,456 bytes copied during GC
4-
6,854,605,176 bytes maximum residency (51 sample(s))
5-
96,912,208 bytes maximum slop
6-
19822 MiB total memory in use (0 MB lost due to fragmentation)
2+
765,357,373,648 bytes allocated in the heap
3+
360,583,388,216 bytes copied during GC
4+
6,131,704,224 bytes maximum residency (40 sample(s))
5+
59,567,712 bytes maximum slop
6+
17666 MiB total memory in use (0 MB lost due to fragmentation)
77

88
Tot time (elapsed) Avg pause Max pause
9-
Gen 0 112618 colls, 112618 par 556.187s 189.050s 0.0017s 0.0491s
10-
Gen 1 51 colls, 50 par 233.076s 122.893s 2.4097s 4.1721s
9+
Gen 0 63238 colls, 63238 par 214.826s 86.156s 0.0014s 0.0344s
10+
Gen 1 40 colls, 39 par 126.759s 24.613s 0.6153s 2.3480s
1111

12-
Parallel GC work balance: 72.00% (serial 0%, perfect 100%)
12+
Parallel GC work balance: 62.83% (serial 0%, perfect 100%)
1313

14-
TASKS: 45 (1 bound, 44 peak workers (44 total), using -N10)
14+
TASKS: 58 (1 bound, 57 peak workers (57 total), using -N10)
1515

1616
SPARKS: 7516 (7516 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
1717

18-
INIT time 0.000s ( 0.004s elapsed)
19-
MUT time 746.850s (124.852s elapsed)
20-
GC time 789.263s (311.943s elapsed)
21-
EXIT time 0.126s ( 0.007s elapsed)
22-
Total time 1536.239s (436.806s elapsed)
18+
INIT time 0.000s ( 0.003s elapsed)
19+
MUT time 307.420s ( 79.011s elapsed)
20+
GC time 341.586s (110.770s elapsed)
21+
EXIT time 0.110s ( 0.004s elapsed)
22+
Total time 649.117s (189.789s elapsed)
2323

24-
Alloc rate 2,518,666,039 bytes per MUT second
24+
Alloc rate 2,489,613,396 bytes per MUT second
2525

26-
Productivity 48.6% of total user, 28.6% of total elapsed
26+
Productivity 47.4% of total user, 41.6% of total elapsed
2727

0 commit comments

Comments
 (0)