1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
#!/usr/bin/env stack
{- stack script
--resolver lts-13.12
--ghc-options -Wall
--package bytestring,text,filepath,directory,optparse-applicative,typed-process
-}
{-# LANGUAGE CPP #-}
-- = About
--
-- Install multiple versions of haskell-code-indexer, each with the version of
-- GHC it was built with appended to the executable name.
--
-- = Original
--
-- Modified from the BSD3 licensed script at:
-- https://github.com/haskell/haskell-ide-engine/blob/ec5e34ca52d389b713df918f02ff63920aede4be/install.hs
--
-- Thanks haskell-ide-engine folks!
--
-- = Changes from the original
--
-- + Switched from Shake to IO script
-- + Added optparse-applicative
-- + Switched to Stack only (PRs welcome to support other tools)
module Main (main) where
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LBS
import Data.Char (isSpace)
import Data.Foldable
import Data.List (dropWhileEnd)
import qualified Data.Text as T
import Data.Text.Encoding
import System.FilePath ((<.>), (</>))
import Options.Applicative
import System.Directory (copyFile, removeFile)
import System.Process.Typed
-- | Keep this in sync with the stack.yamls at the top level of the project.
supportedGhcVersions :: [Version]
supportedGhcVersions =
map Version ["8.0.2", "8.2.2", "8.4.3", "8.4.4", "8.6.3", "8.6.4"]
newtype Version = Version { unVersion :: String } deriving Eq
-- * CLI args
data Args = Args
{ argBuildVersions :: [Version]
, argBuildServer :: Bool
}
cliArgs :: IO Args
cliArgs =
customExecParser (prefs showHelpOnError) argsParser
argsParser :: ParserInfo Args
argsParser =
fmap defaultToAll $
info (helper <*> parser) (fullDesc <> progDesc desc)
where
parser :: Parser Args
parser =
Args
<$> (some indexVersion <|> pure mempty)
<*> switch
( long "server"
<> help "Build haskell-code-server"
)
indexVersion :: Parser Version
indexVersion =
argument (eitherReader checkVersion)
( metavar "INDEX_VERSION"
<> help "haskell-code-indexer-X-Y-Z version to build"
)
checkVersion :: String -> Either String Version
checkVersion s =
case find ((==) (Version s)) supportedGhcVersions of
Nothing ->
Left . unwords $
"Not a supported GHC version. Currently supported versions are:"
: map unVersion supportedGhcVersions
Just v ->
Right v
defaultToAll :: Args -> Args
defaultToAll args =
if argBuildVersions args == mempty && argBuildServer args == False
then Args (reverse supportedGhcVersions) True -- reverse to build latest first
else args
desc :: String
desc =
"Install haskell-code-indexer executables with the GHC version they were"
<> " compiled with appended to their name. Builds everything if you don't"
<> " specify options. Note that if you already have an indexer executable"
<> " without the GHC version appended in your Stack's local bin"
<> " it will be deleted."
-- * Build
main :: IO ()
main =
run =<< cliArgs
run :: Args -> IO ()
run args = do
putStrLn (startupNotice args)
when (argBuildServer args) buildServer
for_ (argBuildVersions args) buildVersion
startupNotice :: Args -> String
startupNotice args =
unlines
$ "Building:"
: (if argBuildServer args
then [" + haskell-code-explorer"]
else mempty)
<> map versionEntry (argBuildVersions args)
where
versionEntry :: Version -> String
versionEntry v =
" + haskell-code-indexer-" <> unVersion v
buildServer :: IO ()
buildServer =
void $ execStack ["build", "--copy-bins", "haskell-code-explorer:haskell-code-server"]
buildVersion :: Version -> IO ()
buildVersion v = do
execStackWithVersion_ v ["build", "--copy-bins", "haskell-code-explorer:haskell-code-indexer"]
localBinDir <- getLocalBin
let
-- exe is "exe" on Windows and "" otherwise
fromFile = localBinDir </> "haskell-code-indexer" <.> exe
toFile = localBinDir </> "haskell-code-indexer-" ++ unVersion v <.> exe
copyFile fromFile toFile
removeFile fromFile
exe :: String
#if defined(mingw32_HOST_OS)
exe = "exe"
#else
exe = ""
#endif
-- | E.g. @"/home/user/bin"@.
getLocalBin :: IO FilePath
getLocalBin = do
stackLocalDir' <- decodeUtf8 <$> execStack ["path", "--stack-yaml=stack.yaml", "--local-bin"]
pure $ trimEnd (T.unpack stackLocalDir')
-- | Uses the stack.yaml for the given @Version@.
execStackWithVersion_ :: Version -> [String] -> IO ()
execStackWithVersion_ v args = do
let stackFile = "stack-" ++ unVersion v ++ ".yaml"
void $ execStack (("--stack-yaml=" ++ stackFile) : args)
execStack :: [String] -> IO ByteString
execStack =
fmap LBS.toStrict . readProcessStdout_ . proc "stack"
trimEnd :: String -> String
trimEnd = dropWhileEnd isSpace
|