Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move parts of packcheck into haskell #78

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist-newstyle/
2 changes: 2 additions & 0 deletions .packcheck.ignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.packcheck.ignore
.gitignore
packcheck-x86_64-linux
117 changes: 117 additions & 0 deletions app/Packcheck.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -Wno-orphans #-}

--------------------------------------------------------------------------------
-- Imports
--------------------------------------------------------------------------------

import qualified Packcheck.Hlint as Hlint
import qualified Packcheck.Ghcup as Ghcup

import System.Console.CmdArgs

--------------------------------------------------------------------------------
-- Main
--------------------------------------------------------------------------------

data Cli
= Hlint
{ hlint_version :: String
, install_path :: String
, url_prefix :: String
, hlint_options :: String
, hlint_targets :: String -- Space seperated hlint targets
}
| Ghcup
{ url_prefix :: String
, ghcup_version :: String
, install_path :: FilePath
, tool_name :: String
, tool_install_options :: String
, tool_version :: String
}
deriving (Show, Data, Typeable)

ghcupMode :: Cli
ghcupMode =
Ghcup
{ url_prefix =
def &= help "ghcup url prefix to install from."
&= typ "URL"
&= groupname "Compile-time"
, ghcup_version =
def &= help "ghcup version to install."
&= typ "STRING"
&= groupname "Compile-time"
, install_path =
def &= help "path to install/lookup ghcup at."
&= typFile
&= groupname "Compile-time"
, tool_name =
def &= help "tool to install via ghcup."
&= typ "STRING"
&= groupname "Run-time"
, tool_install_options =
def &= help "hlint_options for the tool installation via ghcup."
&= typ "STRING"
&= groupname "Run-time"
, tool_version =
def &= help "tool version to install via ghcup."
&= typ "STRING"
&= groupname "Run-time"
} &= help "run ghcup"

hlintMode :: Cli
hlintMode =
Hlint
{ hlint_options =
def &= help "hlint_options passed to hlint."
&= typ "STRING"
&= groupname "Run-time"
, hlint_version =
def &= typ "STRING"
&= help "hlint version."
&= groupname "Compile-time"
, install_path =
def &= typDir
&= help "path to install/lookup hlint at."
&= groupname "Compile-time"
, url_prefix =
def &= typ "URL"
&= help "url to download hlint from."
&= groupname "Compile-time"
, hlint_targets =
def &= typ "STRING"
&= help "hlint hlint_targets."
&= groupname "Run-time"
} &= help "run hlint"

cliModes :: Cli
cliModes =
modes [hlintMode, ghcupMode]
&= program "packcheck"

main :: IO ()
main = do
opts <- cmdArgs cliModes
case opts of
Hlint {..} ->
Hlint.runHlint
$ Hlint.HlintConfig
{ Hlint.env_HLINT_OPTIONS = hlint_options
, Hlint.env_HLINT_VERSION = hlint_version
, Hlint.env_HLINT_PATH = install_path
, Hlint.env_HLINT_URL_PREFIX = url_prefix
, Hlint.env_HLINT_TARGETS = hlint_targets
}
Ghcup {..} ->
Ghcup.runGhcupWith
(Ghcup.GhcupConfig
{ Ghcup.env_GHCUP_URL_PREFIX = url_prefix
, Ghcup.env_GHCUP_VERSION = ghcup_version
, Ghcup.env_GHCUP_PATH = install_path
})
tool_name
tool_install_options
tool_version
162 changes: 162 additions & 0 deletions app/Packcheck/Common.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
module Packcheck.Common where

--------------------------------------------------------------------------------
-- Imports
--------------------------------------------------------------------------------

import Control.Concurrent (threadDelay)
import Control.Exception (Exception(..), SomeException)
import Control.Monad.Catch (catch)
import System.FilePath (takeFileName)
import Streamly.System.Process (ProcessFailure)
import Streamly.Unicode.String (str)

import qualified Streamly.Internal.System.Command as Cmd
import qualified System.IO.Temp as Temp

--------------------------------------------------------------------------------
-- Os specific constants
--------------------------------------------------------------------------------

data SysOs = Darwin | Linux | Windows
data SysArch = X86_64 | Arm_64

archiveExt, binExt :: String
sysOs :: SysOs
sysArch :: SysArch
#if defined(mingw32_HOST_OS)
archiveExt = ".zip"
sysOs = Windows
binExt = ".exe"
#elif defined(darwin_HOST_OS)
archiveExt = ".tar.gz"
sysOs = Darwin
binExt = ""
#elif defined(linux_HOST_OS)
archiveExt = ".tar.gz"
sysOs = Linux
binExt = ""
#endif
#if defined(x86_64_HOST_ARCH)
sysArch = X86_64
#elif defined(arm_HOST_ARCH)
sysArch = Arm_64
#endif

--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------

oneLine :: String -> String
oneLine = unwords . filter (not . null) . words

oneLineUnspaced :: String -> String
oneLineUnspaced = concat . filter (not . null) . words

echo :: String -> IO ()
echo = putStrLn

die :: String -> a
die = error

step :: String -> IO ()
step title = do
echo ""
echo "--------------------------------------------------"
echo title
echo "--------------------------------------------------"

verbose :: (String -> IO a) -> String -> IO a
verbose runner cmd = do
putStrLn $ ">> " ++ cmd
runner cmd

sh :: String -> IO ()
sh cmd = do
catch
(verbose Cmd.toStdout cmd)
(\(e :: ProcessFailure) ->
let eStr = displayException e
in die [str|Command [#{cmd}] failed with exception #{eStr}.|])

tenPow6 :: Int
tenPow6 = 1000000

retry :: IO a -> IO a
retry action =
catch1 action (catch1 (withDelay 2 action) (withDelay 10 action))

where

catch1 act next = catch act (\(_ :: SomeException) -> next)

withDelay nSec act = do
threadDelay (nSec * tenPow6)
act

-------------------------------------------------------------------------------
-- Install a generic tool
--------------------------------------------------------------------------------

data DownloadTool =
DownloadTool
{ dtDownloadUrl :: String
, dtInstallDir :: FilePath
, dtExeName :: String
, dtExePathInArchive :: FilePath
}

downloadToolSimple :: DownloadTool -> IO ()
downloadToolSimple DownloadTool{..} = do
step [str|Downloading #{dtDownloadUrl}|]
Temp.withSystemTempDirectory "temp" $ \tmpDir -> do
let downloadTo = [str|#{tmpDir}/#{dtExePathInArchive}#{binExt}|]
let curlCmd =
oneLine
[str|curl
--fail --progress-bar --location
-o#{downloadTo} #{dtDownloadUrl}|]
retry (sh curlCmd)
sh [str|mkdir -p #{dtInstallDir}|]
sh [str|mv #{tmpDir}/#{dtExePathInArchive} #{dtInstallDir}/#{dtExeName}|]
sh [str|chmod +x #{dtInstallDir}/#{dtExeName}|]

downloadTool :: DownloadTool -> IO ()
downloadTool DownloadTool{..} = do
step [str|Downloading #{dtDownloadUrl}|]
Temp.withSystemTempDirectory "temp" $ \tmpDir -> do
let archiveName = [str|tempArchive#{archiveExt}|]
downloadTo = [str|#{tmpDir}/#{archiveName}|]
exeNameInArchive = takeFileName dtExePathInArchive
let curlCmd =
oneLine
[str|curl
--fail --progress-bar --location
-o#{downloadTo} #{dtDownloadUrl}|]
#if defined(mingw32_HOST_OS)
let untarCmd =
oneLine
[str|7z x
-y #{downloadTo}
-o#{tmpDir}
#{dtExePathInArchive} > /dev/null|]
#elif defined(darwin_HOST_OS)
let untarCmd =
oneLine
[str|tar
-xzvf #{downloadTo}
-C#{tmpDir}
--include '*/#{exeNameInArchive}'|]
#elif defined(linux_HOST_OS)
let untarCmd =
oneLine
[str|tar
-xzvf #{tmpDir}/#{archiveName}
-C#{tmpDir}
--wildcards '*/#{exeNameInArchive}'|]
#endif
retry (sh curlCmd)
sh [str|mkdir -p #{dtInstallDir}|]
sh untarCmd
sh [str|mv #{tmpDir}/#{dtExePathInArchive} #{dtInstallDir}/#{dtExeName}|]
sh [str|chmod +x #{dtInstallDir}/#{dtExeName}|]
74 changes: 74 additions & 0 deletions app/Packcheck/Ghcup.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module Packcheck.Ghcup where

--------------------------------------------------------------------------------
-- Imports
--------------------------------------------------------------------------------

import System.FilePath (takeDirectory, takeFileName)
import Streamly.Unicode.String (str)

import qualified Streamly.Coreutils.FileTest as CU
import qualified Streamly.Coreutils.Which as CU

import Packcheck.Common

--------------------------------------------------------------------------------
-- Installation
--------------------------------------------------------------------------------

data GhcupConfig =
GhcupConfig
{ env_GHCUP_URL_PREFIX :: String
, env_GHCUP_VERSION :: String
, env_GHCUP_PATH :: String
}
deriving (Show)

downloadGhcup :: GhcupConfig -> IO ()
downloadGhcup GhcupConfig{..} =
downloadToolSimple
$ DownloadTool
{ dtDownloadUrl =
oneLineUnspaced
[str|#{env_GHCUP_URL_PREFIX}
/#{env_GHCUP_VERSION}
/#{arch}-#{os}-#{toolExe}-#{env_GHCUP_VERSION}|]
, dtExePathInArchive = toolExe
, dtInstallDir = takeDirectory env_GHCUP_PATH
, dtExeName = toolExe
}

where
toolExe = takeFileName env_GHCUP_PATH
arch =
case sysArch of
X86_64 -> "x86_64"
Arm_64 -> "aarch64"
os =
case sysOs of
Linux -> "linux"
Darwin -> "apple-darwin"
Windows -> die "ghcup does not support windows"

ensureGhcup :: GhcupConfig -> IO ()
ensureGhcup conf@(GhcupConfig{..}) = do
mGhcupPath <- CU.which "ghcup"
case mGhcupPath of
Just ghcupPath ->
echo [str|Using ghcup in PATH at #{ghcupPath}|]
Nothing -> do
ghcupPathExists <- CU.test env_GHCUP_PATH CU.isExisting
if ghcupPathExists
then die [str|#{env_GHCUP_PATH} already exists, not overwriting.|]
else do
echo "ghcup does not exist. Downloading ghcup."
downloadGhcup conf

runGhcupWith :: GhcupConfig -> String -> String -> String -> IO ()
runGhcupWith conf toolName toolInstallOptions toolVersion = do
ensureGhcup conf
sh [str|ghcup install #{toolName} #{toolInstallOptions} #{toolVersion}|]

runGhcup :: GhcupConfig -> String -> String -> IO ()
runGhcup conf toolName toolVersion =
runGhcupWith conf toolName "" toolVersion
Loading
Loading