├── .github └── workflows │ └── release.yml ├── .gitignore ├── ChangeLog.md ├── LICENSE ├── README.md ├── Setup.hs ├── app ├── haskup-main.hs ├── haskwrap.hs └── installer.hs ├── package.yaml ├── src └── Haskup.hs ├── stack.yaml └── stack.yaml.lock /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - '**' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | sanity: 14 | name: Linux/Mac 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: true 18 | matrix: 19 | include: 20 | - os: ubuntu-latest 21 | - os: macos-latest 22 | steps: 23 | - name: Clone project 24 | uses: actions/checkout@v2 25 | - name: Cache dependencies 26 | uses: actions/cache@v1 27 | with: 28 | path: ~/.stack 29 | key: ${{ runner.os }}-${{ hashFiles('stack.yaml') }} 30 | - shell: bash 31 | name: Build and run tests 32 | run: | 33 | set -ex 34 | stack upgrade 35 | stack --version 36 | stack test --no-terminal --copy-bins --local-bin-path bin 37 | - name: Upload bindist 38 | uses: actions/upload-artifact@v2 39 | with: 40 | name: ${{ runner.os }} 41 | path: bin/* 42 | integration-tests: 43 | name: Windows 44 | runs-on: windows-latest 45 | env: 46 | STACK_ROOT: "c:\\sr" 47 | steps: 48 | - name: Clone project 49 | uses: actions/checkout@v2 50 | - name: Cache dependencies 51 | uses: actions/cache@v1 52 | with: 53 | path: "c:\\sr" 54 | key: ${{ runner.os }}-${{ hashFiles('stack.yaml') }} 55 | - name: Install NSIS 56 | run: choco install nsis-unicode -y 57 | - shell: bash 58 | name: Build and run tests 59 | run: | 60 | set -ex 61 | stack upgrade 62 | stack --version 63 | stack test --no-terminal --copy-bins --local-bin-path bin 64 | - name: Generate installer 65 | shell: cmd 66 | run: | 67 | bin\installer-generator.exe 68 | "c:\Program Files (x86)\NSIS\Unicode\makensis.exe" -V3 bin\haskup-installer.nsi 69 | - name: Upload bindist 70 | uses: actions/upload-artifact@v2 71 | with: 72 | name: ${{ runner.os }} 73 | path: bin/* 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | *.cabal 4 | bin/ 5 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog for haskwrap 2 | 3 | ## Unreleased changes 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Author name here (c) 2021 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Author name here nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # haskup 2 | 3 | *Cross platform Haskell toolchain installer* 4 | 5 | [![Release](https://github.com/snoyberg/haskup/actions/workflows/release.yml/badge.svg)](https://github.com/snoyberg/haskup/actions/workflows/release.yml) 6 | 7 | This package provides tooling that leverages the GHC toolchain installer logic present in Stack. Its primary purposes currently is to provide a Windows installer which provides 3 executables: 8 | 9 | * `stack.exe` 10 | * `cabal.exe` 11 | * `haskup.exe`, which can install and run various GHC version 12 | 13 | In the future, the goal is to have `haskup.exe` itself install and manage various Stack and Cabal versions. 14 | 15 | ## Sample usage 16 | 17 | ``` 18 | > haskup --ghc 8.8.3 ghci 19 | > haskup runghc Main.hs 20 | > haskup --ghc 8.6.5 exec cmd 21 | ``` 22 | 23 | Commands will automatically install GHC and `msys2` if necessary, and then run the command in question with a modified `PATH` that includes these directories. 24 | 25 | ## haskwrap 26 | 27 | This package provides a work in progress executable called `haskwrap`. This is intended for use cases where you don't want to call `haskup` explicitly. Instead, if you install `haskwrap` to a location like `/usr/local/bin/ghc`, running that executable will automatically invoke the real `ghc` behind the scenes. 28 | 29 | Again, this is a work in progress, and is not included in the Windows installer. 30 | 31 | This tool intentionally does minimalistic command line parsing, to allow passthrough of arguments as easily as possible to underlying tools. Restrictions: 32 | 33 | * This tool ignores all GHC RTS options (i.e., it sets `-rtsopts=ignoreAll`) 34 | * The only location where `--ghc` is accepted is as the very first argument to the command 35 | * The `exec` command is only recognized immediately following `--ghc VERSION` or, if that is absent, as the first argument 36 | 37 | ## Default GHC version 38 | 39 | If you leave off the `--ghc VERSION`, currently the executable will default to using GHC 8.10.4. This is _not good behavior_. Instead, there should be a config file for the default GHC version, potentially with environment variable and local filepath overrides, and some intelligent way of setting that default value on first execution. Contributions welcome to make that happen! 40 | 41 | ## Maintainers welcome! 42 | 43 | I (Michael Snoyman) wrote the first version of this to get it off the ground. I welcome other contributors and maintainers on board. If you're interested, please reach out to me on just about any channel, including the Haskell Foundation Slack or this issue tracker. 44 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /app/haskup-main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | import RIO 5 | import Haskup 6 | import Options.Applicative.Simple 7 | import qualified Paths_haskup 8 | import Control.Monad.Trans.Except (ExceptT) 9 | import Control.Monad.Trans.Writer (Writer) 10 | 11 | data Global = Global 12 | { ghcVersion :: !(Maybe String) 13 | } 14 | 15 | data Command 16 | = CommandInstall 17 | | CommandRun !RunOpts 18 | 19 | data RunOpts = RunOpts 20 | { roCommand :: !String 21 | , roArgs :: ![String] 22 | } 23 | 24 | globalParser :: Parser Global 25 | globalParser = Global 26 | <$> optional (strOption (long "ghc" <> metavar "GHC-VERSION")) 27 | 28 | commandParser :: ExceptT Command (Writer (Mod CommandFields Command)) () 29 | commandParser = do 30 | addCommand "install" "Install a Haskell toolchain" id $ pure CommandInstall 31 | addCommand "exec" "Execute an arbitrary command" CommandRun $ RunOpts 32 | <$> strArgument (metavar "COMMAND") 33 | <*> args 34 | addHelper "ghc" "Run the ghc executable" 35 | addHelper "ghci" "Run the ghci executable" 36 | addHelper "runghc" "Run the runghc executable" 37 | addHelper "cabal" "Run the cabal executable" 38 | addHelper "stack" "Run the stack executable" 39 | where 40 | addHelper cmd desc = addCommand cmd desc CommandRun $ RunOpts cmd 41 | <$> args 42 | args = many (strArgument (metavar "ARGUMENT")) 43 | 44 | main :: IO () 45 | main = runHaskup $ do 46 | (global, cmd) <- liftIO $ simpleOptions 47 | $(simpleVersion Paths_haskup.version) 48 | "haskup Haskell toolchain installer" 49 | "Installs, manages, and runs your Haskell toolchains" 50 | globalParser 51 | commandParser 52 | ghcVer <- maybe defaultGhcVer parseVersionThrowing $ ghcVersion global 53 | withToolchain (toolchainGhc ghcVer) $ 54 | case cmd of 55 | CommandInstall -> logInfo "Installation complete!" 56 | CommandRun ro -> execHaskupExe (roCommand ro) (roArgs ro) >>= either throwIO pure 57 | -------------------------------------------------------------------------------- /app/haskwrap.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE NoImplicitPrelude #-} 3 | import RIO 4 | import RIO.Process (exec) 5 | import RIO.FilePath (stripExtension) 6 | import System.Environment (getArgs, getProgName, getExecutablePath) 7 | import Haskup 8 | 9 | main :: IO () 10 | main = runHaskup $ do 11 | args0 <- liftIO getArgs 12 | (args1, ghcVer) <- 13 | case args0 of 14 | "--ghc":args1 -> 15 | case args1 of 16 | [] -> error "--ghc must be followed by a GHC version number" 17 | verS:args -> do 18 | ghcVer <- parseVersionThrowing verS 19 | pure (args, ghcVer) 20 | _ -> do 21 | ghcVer <- defaultGhcVer 22 | pure (args0, ghcVer) 23 | (exeName, args) <- 24 | case args1 of 25 | "exec":args2 -> 26 | case args2 of 27 | [] -> error "exec must be followed by a command" 28 | exeName:args -> pure (exeName, args) 29 | _ -> do 30 | exeName <- liftIO getProgName 31 | pure (exeName, args1) 32 | logInfo $ "exeName is " <> fromString exeName 33 | withToolchain (toolchainGhc ghcVer) $ do 34 | eexe <- findHaskupExe $ fromMaybe exeName $ stripExtension ".exe" exeName 35 | exe <- either throwIO pure eexe 36 | self <- liftIO getExecutablePath 37 | when (exe == self) $ error "haskwrap is calling itself, exiting" 38 | exec exe args 39 | -------------------------------------------------------------------------------- /app/installer.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NoImplicitPrelude #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | 6 | import RIO 7 | import Haskup (runHaskup) 8 | import Development.NSIS 9 | import Development.NSIS.Plugins.EnvVarUpdate 10 | import Network.HTTP.Download (verifiedDownload, mkDownloadRequest, setLengthCheck, CheckHexDigest (CheckHexDigestString), HashCheck (HashCheck), setHashChecks) 11 | import Path (Path, Abs, Rel, File, Dir, mkRelFile, mkRelDir, ()) 12 | import Path.IO (makeAbsolute) 13 | import qualified Crypto.Hash.Algorithms 14 | 15 | -- Note that it is *required* to use a NSIS compiler that supports long strings, 16 | -- to avoid corrupting the user's $PATH. 17 | 18 | executables :: [FilePath] 19 | executables = 20 | [ "haskup.exe" 21 | , "cabal.exe" 22 | , "stack.exe" 23 | ] 24 | 25 | data Download = Download 26 | { url :: !String 27 | , size :: !Int 28 | , sha256 :: !String 29 | , filename :: !(Path Rel File) 30 | } 31 | 32 | downloads :: [Download] 33 | downloads = 34 | [ Download 35 | { url = "https://github.com/commercialhaskell/stack/releases/download/v2.7.1/stack-2.7.1-windows-x86_64-bin.exe" 36 | , size = 59980288 37 | , sha256 = "1a15494065de2f72fe1e35d208f2c3c874801ef92df3c8804e910e1de0a93f05" 38 | , filename = $(mkRelFile "stack.exe") 39 | } 40 | , Download 41 | { url = "https://s3.amazonaws.com/download.fpcomplete.com/cabal/cabal-install-3.4.0.0-x86_64-windows.exe" 42 | , size = 87932677 43 | , sha256 = "5effd1993aff5e5825bd408a1ec1aa382ffd59b511bc1680a0527f3dbcc173e7" 44 | , filename = $(mkRelFile "cabal.exe") 45 | } 46 | ] 47 | 48 | main :: IO () 49 | main = runHaskup $ do 50 | destDir :: Path Abs Dir <- makeAbsolute ($(mkRelDir "bin") :: Path Rel Dir) 51 | for_ downloads $ \download -> do 52 | let dreq = setHashChecks [HashCheck Crypto.Hash.Algorithms.SHA256 (CheckHexDigestString (sha256 download))] 53 | $ setLengthCheck (Just (size download)) 54 | $ mkDownloadRequest 55 | $ fromString 56 | $ url download 57 | let dest = destDir (filename download) 58 | void $ verifiedDownload dreq dest (\_ -> pure ()) 59 | writeFileUtf8 "bin/haskup-installer.nsi" $ fromString $ nsis $ do 60 | _ <- constantStr "Name" "haskup" 61 | 62 | name "$Name" 63 | outFile "haskup-installer.exe" 64 | installDir "$APPDATA/local/bin" 65 | installDirRegKey HKCU "Software/$Name" "Install_Dir" 66 | requestExecutionLevel User 67 | 68 | page Directory 69 | page Components 70 | page InstFiles 71 | 72 | unpage Components 73 | unpage InstFiles 74 | 75 | void $ section "Install haskup executables" [Required] $ do 76 | setOutPath "$INSTDIR" 77 | for_ executables $ \exe -> 78 | file [OName $ fromString exe] $ fromString exe 79 | 80 | -- Write the installation path into the registry 81 | writeRegStr HKCU "SOFTWARE/$Name" "Install_Dir" "$INSTDIR" 82 | 83 | -- Write the uninstall keys for Windows 84 | writeRegStr HKCU "Software/Microsoft/Windows/CurrentVersion/Uninstall/$Name" "DisplayName" "$Name" 85 | writeRegStr HKCU "Software/Microsoft/Windows/CurrentVersion/Uninstall/$Name" "UninstallString" "\"$INSTDIR/uninstall-haskup.exe\"" 86 | writeRegDWORD HKCU "Software/Microsoft/Windows/CurrentVersion/Uninstall/$Name" "NoModify" 1 87 | writeRegDWORD HKCU "Software/Microsoft/Windows/CurrentVersion/Uninstall/$Name" "NoRepair" 1 88 | writeUninstaller "uninstall-haskup.exe" 89 | 90 | void $ section "Add to user %PATH%" 91 | [ Description "Add installation directory to user %PATH% to allow running haskup in the console." 92 | ] $ do 93 | setEnvVarPrepend HKCU "PATH" "$INSTDIR" 94 | 95 | -- Uninstallation sections. (Any section prepended with "un." is an 96 | -- uninstallation option.) 97 | section "un.haskup" [] $ do 98 | deleteRegKey HKCU "Software/Microsoft/Windows/CurrentVersion/Uninstall/$Name" 99 | deleteRegKey HKCU "Software/$Name" 100 | 101 | for_ executables $ \exe -> 102 | delete [] $ fromString $ "$INSTDIR/" ++ exe 103 | delete [] "$INSTDIR/uninstall-haskup.exe" 104 | rmdir [] "$INSTDIR" -- will not remove if not empty 105 | 106 | {- These changes will break Stack installs on uninstall, so leaving them out 107 | 108 | -- The description text is not actually added to the uninstaller as of 109 | -- nsis-0.3 110 | section "un.Install location on %PATH%" 111 | [ Description "Remove $INSTDIR from the user %PATH%. There may be other programs installed in that location." 112 | ] $ do 113 | setEnvVarRemove HKCU "PATH" "$INSTDIR" 114 | 115 | section "un.Compilers installed by stack" 116 | [ Unselected 117 | , Description "Remove %LOCALAPPDATA%/Programs/stack, which contains compilers that have been installed by Stack." 118 | ] $ do 119 | rmdir [Recursive] "$LOCALAPPDATA/Programs/stack" 120 | 121 | section "un.stack snapshots and configuration" 122 | [ Unselected 123 | , Description "Remove %APPDATA%/stack, which contains the user-defined global stack.yaml and the snapshot/compilation cache." 124 | ] $ do 125 | rmdir [Recursive] "$APPDATA/stack" 126 | 127 | -} 128 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: haskup 2 | version: 0.1.0.0 3 | github: "snoyberg/haskwrap" 4 | license: BSD3 5 | author: "Michael Snoyman" 6 | maintainer: "michael@snoyman.com" 7 | copyright: "2021 Michael Snoyman" 8 | 9 | extra-source-files: 10 | - README.md 11 | - ChangeLog.md 12 | 13 | synopsis: Wrapper executables that install a Haskell toolchain 14 | category: Development 15 | description: Please see the README on GitHub at 16 | 17 | dependencies: 18 | - base >= 4.14 && < 5 19 | - rio 20 | 21 | library: 22 | source-dirs: src 23 | dependencies: 24 | - stack >= 2.7 && < 2.8 25 | - rio-prettyprint 26 | - pantry 27 | 28 | executables: 29 | haskup: 30 | main: haskup-main.hs 31 | source-dirs: app 32 | ghc-options: 33 | - -threaded 34 | - -rtsopts 35 | dependencies: 36 | - optparse-simple 37 | - haskup 38 | - transformers 39 | 40 | haskwrap: 41 | main: haskwrap.hs 42 | source-dirs: app 43 | ghc-options: 44 | - -threaded 45 | - -with-rtsopts=-N 46 | - -rtsopts=ignoreAll 47 | dependencies: 48 | - haskup 49 | 50 | installer-generator: 51 | main: installer.hs 52 | source-dirs: app 53 | ghc-options: 54 | - -threaded 55 | - -rtsopts 56 | dependencies: 57 | - nsis 58 | - http-download 59 | - path 60 | - path-io 61 | - haskup 62 | - cryptonite 63 | -------------------------------------------------------------------------------- /src/Haskup.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE NoImplicitPrelude #-} 3 | module Haskup 4 | ( -- * Haskup monad 5 | Haskup 6 | , runHaskup 7 | , binDirsL 8 | -- * Install toolchain 9 | , Toolchain 10 | , toolchainGhc 11 | , withToolchain 12 | -- * Use toolchain 13 | , findHaskupExe 14 | , execHaskupExe 15 | -- * Configuration 16 | , defaultGhcVer 17 | -- * Helper reexports 18 | , Version 19 | , parseVersionThrowing 20 | -- * Error handling 21 | , HaskupException (..) 22 | ) where 23 | 24 | import RIO 25 | import RIO.Process (exec, augmentPathMap, withModifyEnvVars, HasProcessContext (..)) 26 | import RIO.PrettyPrint (HasTerm (..), HasStylesUpdate (..)) 27 | import RIO.Directory (findExecutable, findExecutablesInDirectories, canonicalizePath) 28 | import RIO.FilePath (isAbsolute) 29 | import Stack.Setup 30 | import Stack.Runners 31 | import Stack.Types.Config 32 | import Stack.Types.Version (Version, VersionCheck (..)) 33 | import Stack.Options.GlobalParser 34 | import Stack.Prelude (WantedCompiler (..), parseVersionThrowing, toFilePath) 35 | import System.Terminal (hIsTerminalDeviceOrMinTTY) 36 | import Pantry (HasPantryConfig (..)) 37 | 38 | -- | Environment for this library, should be initialized with 'runHaskup'. 39 | -- 40 | -- @since 0.1.0.0 41 | data Haskup = Haskup 42 | { _bc :: !BuildConfig 43 | , _haskupBinDirs :: ![FilePath] 44 | } 45 | 46 | instance HasLogFunc Haskup where 47 | logFuncL = buildConfigL.logFuncL 48 | instance HasProcessContext Haskup where 49 | processContextL = buildConfigL.processContextL 50 | instance HasRunner Haskup where 51 | runnerL = buildConfigL.runnerL 52 | instance HasPlatform Haskup where 53 | instance HasGHCVariant Haskup where 54 | instance HasTerm Haskup where 55 | useColorL = buildConfigL.useColorL 56 | termWidthL = buildConfigL.termWidthL 57 | instance HasPantryConfig Haskup where 58 | pantryConfigL = buildConfigL.pantryConfigL 59 | instance HasStylesUpdate Haskup where 60 | stylesUpdateL = buildConfigL.stylesUpdateL 61 | instance HasConfig Haskup where 62 | configL = buildConfigL.configL 63 | instance HasBuildConfig Haskup where 64 | buildConfigL = lens _bc (\x y -> x { _bc = y }) 65 | 66 | -- | Lens for all additional directories added to the @PATH@. 67 | -- 68 | -- This can be useful for ensuring you're looking for newly provided executables. 69 | -- 70 | -- @since 0.1.0.0 71 | binDirsL :: Lens' Haskup [FilePath] 72 | binDirsL = lens _haskupBinDirs (\x y -> x { _haskupBinDirs = y }) 73 | 74 | getDefaultTerminal :: IO Bool 75 | getDefaultTerminal = hIsTerminalDeviceOrMinTTY stdout 76 | 77 | -- | Initialize the @Haskup@ environment with default settings. 78 | -- 79 | -- Note that this does not provide any toolchains! You'll need to use 'withToolchain' to make that happen. 80 | -- 81 | -- @since 0.1.0.0 82 | runHaskup :: MonadIO m => RIO Haskup a -> m a 83 | runHaskup inner = liftIO $ do 84 | defaultTerminal <- getDefaultTerminal 85 | gopts <- globalOptsFromMonoid defaultTerminal mempty 86 | withRunnerGlobal gopts $ withConfig NoReexec $ withBuildConfig $ do 87 | bc <- ask 88 | let haskup = Haskup 89 | { _bc = bc 90 | , _haskupBinDirs = [] 91 | } 92 | runRIO haskup inner 93 | 94 | -- | Run the given subcommand with the specified toolchain available. 95 | -- 96 | -- Said another way: "install GHC and then run this action." 97 | -- 98 | -- @since 0.1.0.0 99 | withToolchain :: Toolchain -> RIO Haskup a -> RIO Haskup a 100 | withToolchain (ToolchainGhc ghcVer) inner = do 101 | let sopts = SetupOpts 102 | { soptsInstallIfMissing = True 103 | , soptsUseSystem = False 104 | , soptsWantedCompiler = WCGhc ghcVer 105 | , soptsCompilerCheck = MatchExact 106 | , soptsStackYaml = Nothing 107 | , soptsForceReinstall = False 108 | , soptsSanityCheck = False 109 | , soptsSkipGhcCheck = False 110 | , soptsSkipMsys = False 111 | , soptsResolveMissingGHC = Nothing 112 | , soptsGHCBindistURL = Nothing 113 | } 114 | (_, extraDirs) <- ensureCompilerAndMsys sopts 115 | let binDirs = map toFilePath $ edBins extraDirs 116 | local (over binDirsL (binDirs ++)) $ 117 | withModifyEnvVars (either impureThrow id . augmentPathMap binDirs) inner 118 | 119 | -- | Specifies which toolchain to install. 120 | -- 121 | -- Constructors are not exposed to make it possible to extend in a backwards compatible way in the future. 122 | -- 123 | -- See smart constructors like 'toolchainGhc'. 124 | -- 125 | -- @since 0.1.0.0 126 | data Toolchain = ToolchainGhc !Version 127 | 128 | -- | A toolchain providing the given GHC version. 129 | -- 130 | -- @since 0.1.0.0 131 | toolchainGhc :: Version -> Toolchain 132 | toolchainGhc = ToolchainGhc 133 | 134 | -- | Find the given executable as provided by haskup, if possible. 135 | -- 136 | -- This function will prefer executables present in the 'binDirsL'. 137 | -- 138 | -- On Windows, ensure that you do not pass in @.exe@ or other file extensions. 139 | -- 140 | -- @since 0.1.0.0 141 | findHaskupExe :: String -> RIO Haskup (Either HaskupException FilePath) 142 | findHaskupExe name 143 | | isAbsolute name = (Right <$> canonicalizePath name) `catchIO` \e -> pure $ Left $ AbsolutePathDoesNotExist name e 144 | | otherwise = do 145 | extraBin <- view binDirsL 146 | exes <- findExecutablesInDirectories extraBin name 147 | case exes of 148 | -- Generating a runtime exception here, since canonicalize is 149 | -- expected to succeed in this case (we already found an executable) 150 | exe:_ -> Right <$> canonicalizePath exe 151 | [] -> do 152 | mexe <- findExecutable name 153 | case mexe of 154 | Nothing -> pure $ Left $ ExeNotFound name 155 | Just exe -> Right <$> canonicalizePath exe 156 | 157 | -- | Exception for things that can go wrong in this library. 158 | -- 159 | -- Note that there is /no guarantee/ that other exceptions will not occur! 160 | -- 161 | -- @since 0.1.0.0 162 | data HaskupException 163 | = ExeNotFound !String 164 | | AbsolutePathDoesNotExist !FilePath !IOException 165 | deriving (Show, Typeable) 166 | instance Exception HaskupException 167 | 168 | -- | Attempt to execute the given executable with the provided arguments. 169 | -- 170 | -- Leverages 'findHaskupExe' and 'exec'. 171 | -- 172 | -- @since 0.1.0.0 173 | execHaskupExe :: String -> [String] -> RIO Haskup (Either HaskupException void) 174 | execHaskupExe name args = findHaskupExe name >>= traverse (\exe -> exec exe args) 175 | 176 | -- | Determine the default GHC version to be used for haskup when no override is present. 177 | -- 178 | -- @since 0.1.0.0 179 | defaultGhcVer :: RIO Haskup Version 180 | defaultGhcVer = do 181 | logWarn "This is really dumb, just defaulting to GHC 8.10.4 for now" 182 | parseVersionThrowing "8.10.4" 183 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-17.10 2 | 3 | extra-deps: 4 | - stack-2.7.1@rev:0 5 | -------------------------------------------------------------------------------- /stack.yaml.lock: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by Stack. 2 | # You should not edit this file by hand. 3 | # For more information, please see the documentation at: 4 | # https://docs.haskellstack.org/en/stable/lock_files 5 | 6 | packages: 7 | - completed: 8 | hackage: stack-2.7.1@sha256:fb87420a4d864d318bf4071d27fb75445ab16068dc3d3c267e931c9f4fcd34d1,20909 9 | pantry-tree: 10 | size: 11498 11 | sha256: 31e5b35d1d5ac557d876336745b2550bae8954fe8cb8f746a45f7accdf0ab88b 12 | original: 13 | hackage: stack-2.7.1@rev:0 14 | snapshots: 15 | - completed: 16 | size: 567241 17 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/10.yaml 18 | sha256: 321b3b9f0c7f76994b39e0dabafdc76478274b4ff74cc5e43d410897a335ad3b 19 | original: lts-17.10 20 | --------------------------------------------------------------------------------