aboutsummaryrefslogtreecommitdiff
path: root/install.hs
blob: 6db36fa8843f70ebc0ba227b21d6a189e86cd47d (plain) (blame)
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","8.6.5"]

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