Skip to content

Commit 0c78854

Browse files
committed
Brute-force copy of install.hs from hie
Renamed the string stuff to be "haskell-ide-" instead of "hie-", nothing else changed. Initial hand tests show it working for stack installs. Closes haskell#14
1 parent 6ca2100 commit 0c78854

14 files changed

+750
-0
lines changed

install.hs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env stack
2+
{- stack
3+
runghc
4+
--stack-yaml=install/shake.yaml
5+
--package hie-install
6+
-}
7+
{- cabal:
8+
build-depends:
9+
base
10+
, hie-install
11+
-}
12+
-- call as:
13+
-- * `cabal v2-run install.hs --project-file install/shake.project <target>`
14+
-- * `stack install.hs <target>`
15+
16+
-- TODO: set `shake.project` in cabal-config above, when supported
17+
-- (see https://github.com/haskell/cabal/issues/6353)
18+
19+
import HieInstall (defaultMain)
20+
21+
main = defaultMain

install/Setup.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain

install/cabal.project

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages:
2+
./

install/hie-install.cabal

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: hie-install
2+
version: 0.8.0.0
3+
synopsis: Install the haskell-ide-engine
4+
license: BSD3
5+
author: Many, TBD when we release
6+
maintainer: [email protected]
7+
copyright: 2019
8+
build-type: Simple
9+
cabal-version: >=2.0
10+
11+
library
12+
hs-source-dirs: src
13+
exposed-modules: HieInstall
14+
other-modules: BuildSystem
15+
, Stack
16+
, Version
17+
, Cabal
18+
, Print
19+
, Env
20+
, Help
21+
build-depends: base >= 4.9 && < 5
22+
, shake >= 0.16.4 && < 0.19
23+
, directory
24+
, filepath
25+
, extra
26+
, text
27+
default-extensions: LambdaCase
28+
, TupleSections
29+
, RecordWildCards
30+
default-language: Haskell2010
31+
32+
if flag(run-from-stack)
33+
cpp-options: -DRUN_FROM_STACK
34+
else
35+
build-depends: cabal-install-parsers
36+
37+
flag run-from-stack
38+
description: Inform the application that it is run from stack
39+
default: False
40+
manual: True

install/shake.project

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages:
2+
install/

install/shake.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Used to provide a different environment for the shake build script
2+
resolver: lts-14.11 # GHC 8.6.5
3+
packages:
4+
- .
5+
6+
nix:
7+
packages: [ zlib ]
8+
9+
flags:
10+
hie-install:
11+
run-from-stack: true

install/src/BuildSystem.hs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{-# LANGUAGE CPP #-}
2+
3+
module BuildSystem where
4+
5+
buildSystem :: String
6+
buildSystem =
7+
#if RUN_FROM_STACK
8+
"stack"
9+
#else
10+
"cabal"
11+
#endif
12+
13+
isRunFromStack :: Bool
14+
isRunFromStack = buildSystem == "stack"
15+
16+
isRunFromCabal :: Bool
17+
isRunFromCabal = buildSystem == "cabal"

install/src/Cabal.hs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
{-# LANGUAGE CPP #-}
2+
3+
module Cabal where
4+
5+
import Development.Shake
6+
import Development.Shake.Command
7+
import Development.Shake.FilePath
8+
import Control.Monad
9+
import Data.Maybe ( isNothing
10+
, isJust
11+
)
12+
import Control.Monad.Extra ( whenMaybe )
13+
import System.Directory ( findExecutable
14+
, copyFile
15+
)
16+
17+
import Version
18+
import Print
19+
import Env
20+
import Data.Functor.Identity
21+
#if RUN_FROM_STACK
22+
import Control.Exception ( throwIO )
23+
#else
24+
import Cabal.Config
25+
#endif
26+
27+
getInstallDir :: IO FilePath
28+
#if RUN_FROM_STACK
29+
-- we should never hit this codepath
30+
getInstallDir = throwIO $ userError "Stack and cabal should never be mixed"
31+
#else
32+
getInstallDir = runIdentity . cfgInstallDir <$> readConfig
33+
#endif
34+
35+
execCabal :: CmdResult r => [String] -> Action r
36+
execCabal = command [] "cabal"
37+
38+
execCabal_ :: [String] -> Action ()
39+
execCabal_ = command [] "cabal"
40+
41+
cabalBuildData :: Action ()
42+
cabalBuildData = do
43+
execCabal_ ["v2-build", "hoogle"]
44+
execCabal_ ["v2-exec", "hoogle", "generate"]
45+
46+
getGhcPathOfOrThrowError :: VersionNumber -> Action GhcPath
47+
getGhcPathOfOrThrowError versionNumber =
48+
getGhcPathOf versionNumber >>= \case
49+
Nothing -> do
50+
printInStars $ ghcVersionNotFoundFailMsg versionNumber
51+
error (ghcVersionNotFoundFailMsg versionNumber)
52+
Just p -> return p
53+
54+
cabalInstallHie :: VersionNumber -> Action ()
55+
cabalInstallHie versionNumber = do
56+
localBin <- liftIO $ getInstallDir
57+
cabalVersion <- getCabalVersion
58+
ghcPath <- getGhcPathOfOrThrowError versionNumber
59+
60+
let isCabal3 = checkVersion [3,0,0,0] cabalVersion
61+
installDirOpt | isCabal3 = "--installdir"
62+
| otherwise = "--symlink-bindir"
63+
installMethod | isWindowsSystem && isCabal3 = ["--install-method=copy"]
64+
| otherwise = []
65+
execCabal_ $
66+
[ "v2-install"
67+
, "-w", ghcPath
68+
, "--write-ghc-environment-files=never"
69+
, installDirOpt, localBin
70+
, "--max-backjumps=5000"
71+
, "exe:haskell-ide"
72+
, "--overwrite-policy=always"
73+
]
74+
++ installMethod
75+
76+
let minorVerExe = "haskell-ide-" ++ versionNumber <.> exe
77+
majorVerExe = "haskell-ide-" ++ dropExtension versionNumber <.> exe
78+
79+
liftIO $ do
80+
copyFile (localBin </> "haskell-ide" <.> exe) (localBin </> minorVerExe)
81+
copyFile (localBin </> "haskell-ide" <.> exe) (localBin </> majorVerExe)
82+
83+
printLine $ "Copied executables "
84+
++ ("haskell-ide-wrapper" <.> exe) ++ ", "
85+
++ ("haskell-ide" <.> exe) ++ ", "
86+
++ majorVerExe ++ " and "
87+
++ minorVerExe
88+
++ " to " ++ localBin
89+
90+
checkCabal_ :: Action ()
91+
checkCabal_ = checkCabal >> return ()
92+
93+
-- | check `cabal` has the required version
94+
checkCabal :: Action String
95+
checkCabal = do
96+
cabalVersion <- getCabalVersion
97+
unless (checkVersion requiredCabalVersion cabalVersion) $ do
98+
printInStars $ cabalInstallIsOldFailMsg cabalVersion
99+
error $ cabalInstallIsOldFailMsg cabalVersion
100+
return cabalVersion
101+
102+
getCabalVersion :: Action String
103+
getCabalVersion = trimmedStdout <$> execCabal ["--numeric-version"]
104+
105+
-- | Error message when the `cabal` binary is an older version
106+
cabalInstallIsOldFailMsg :: String -> String
107+
cabalInstallIsOldFailMsg cabalVersion =
108+
"The `cabal` executable found in $PATH is outdated.\n"
109+
++ "found version is `"
110+
++ cabalVersion
111+
++ "`.\n"
112+
++ "required version is `"
113+
++ versionToString requiredCabalVersion
114+
++ "`."
115+
116+
117+
requiredCabalVersion :: RequiredVersion
118+
requiredCabalVersion | isWindowsSystem = requiredCabalVersionForWindows
119+
| otherwise = [2, 4, 1, 0]
120+
121+
requiredCabalVersionForWindows :: RequiredVersion
122+
requiredCabalVersionForWindows = [3, 0, 0, 0]

install/src/Env.hs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
module Env where
2+
3+
import Development.Shake
4+
import Development.Shake.Command
5+
import Control.Monad.IO.Class
6+
import Control.Monad
7+
import Development.Shake.FilePath
8+
import System.Info ( os
9+
, arch
10+
)
11+
import Data.Maybe ( isJust
12+
, isNothing
13+
, mapMaybe
14+
)
15+
import System.Directory ( findExecutable
16+
, findExecutables
17+
, listDirectory
18+
)
19+
import Data.Function ( (&)
20+
, on
21+
)
22+
import Data.List ( sort
23+
, sortBy
24+
, isInfixOf
25+
, nubBy
26+
)
27+
import Data.Ord ( comparing )
28+
import Control.Monad.Extra ( mapMaybeM )
29+
30+
import qualified Data.Text as T
31+
32+
import Version
33+
import Print
34+
35+
36+
type GhcPath = String
37+
38+
existsExecutable :: MonadIO m => String -> m Bool
39+
existsExecutable executable = liftIO $ isJust <$> findExecutable executable
40+
41+
42+
-- | Check if the current system is windows
43+
isWindowsSystem :: Bool
44+
isWindowsSystem = os `elem` ["mingw32", "win32"]
45+
46+
findInstalledGhcs :: IO [(VersionNumber, GhcPath)]
47+
findInstalledGhcs = do
48+
hieVersions <- getHieVersions :: IO [VersionNumber]
49+
knownGhcs <- mapMaybeM
50+
(\version -> getGhcPathOf version >>= \case
51+
Nothing -> return Nothing
52+
Just p -> return $ Just (version, p)
53+
)
54+
(reverse hieVersions)
55+
-- filter out not supported ghc versions
56+
availableGhcs <- filter ((`elem` hieVersions) . fst) <$> getGhcPaths
57+
return
58+
-- sort by version to make it coherent with getHieVersions
59+
$ sortBy (comparing fst)
60+
-- nub by version. knownGhcs takes precedence.
61+
$ nubBy ((==) `on` fst)
62+
-- filter out stack provided GHCs (assuming that stack programs path is the default one in linux)
63+
$ filter (not . isInfixOf ".stack" . snd) (knownGhcs ++ availableGhcs)
64+
65+
-- | Get the path to a GHC that has the version specified by `VersionNumber`
66+
-- If no such GHC can be found, Nothing is returned.
67+
-- First, it is checked whether there is a GHC with the name `ghc-$VersionNumber`.
68+
-- If this yields no result, it is checked, whether the numeric-version of the `ghc`
69+
-- command fits to the desired version.
70+
getGhcPathOf :: MonadIO m => VersionNumber -> m (Maybe GhcPath)
71+
getGhcPathOf ghcVersion =
72+
liftIO $ findExecutable ("ghc-" ++ ghcVersion <.> exe) >>= \case
73+
Nothing -> lookup ghcVersion <$> getGhcPaths
74+
path -> return path
75+
76+
-- | Get a list of GHCs that are available in $PATH
77+
getGhcPaths :: MonadIO m => m [(VersionNumber, GhcPath)]
78+
getGhcPaths = liftIO $ do
79+
paths <- findExecutables "ghc"
80+
forM paths $ \path -> do
81+
Stdout version <- cmd path ["--numeric-version"]
82+
return (trim version, path)
83+
84+
-- | No suitable ghc version has been found. Show a message.
85+
ghcVersionNotFoundFailMsg :: VersionNumber -> String
86+
ghcVersionNotFoundFailMsg versionNumber =
87+
"No GHC with version "
88+
<> versionNumber
89+
<> " has been found.\n"
90+
<> "Either install a fitting GHC, use the stack targets or modify the PATH variable accordingly."
91+
92+
93+
-- | Defines all different hie versions that are buildable.
94+
--
95+
-- The current directory is scanned for `stack-*.yaml` files.
96+
getHieVersions :: MonadIO m => m [VersionNumber]
97+
getHieVersions = do
98+
let stackYamlPrefix = T.pack "stack-"
99+
let stackYamlSuffix = T.pack ".yaml"
100+
files <- liftIO $ listDirectory "."
101+
let hieVersions =
102+
files
103+
& map T.pack
104+
& mapMaybe
105+
(T.stripPrefix stackYamlPrefix >=> T.stripSuffix stackYamlSuffix)
106+
& map T.unpack
107+
-- the following line excludes `8.6.3`, `8.8.1` and `8.8.2` on windows systems
108+
& filter (\p -> not (isWindowsSystem && p `elem` ["8.6.3", "8.8.1", "8.8.2"]))
109+
& sort
110+
return hieVersions
111+
112+
113+
-- | Most recent version of hie.
114+
-- Shown in the more concise help message.
115+
mostRecentHieVersion :: MonadIO m => m VersionNumber
116+
mostRecentHieVersion = last <$> getHieVersions

0 commit comments

Comments
 (0)