Compare commits
4 Commits
v0.1.15.2-
...
issue-179
| Author | SHA1 | Date | |
|---|---|---|---|
|
f04708e8ae
|
|||
|
80e1924e5f
|
|||
|
c7393bd7c5
|
|||
|
664713bb13
|
29
.github/workflows/shimgen.yaml
vendored
Normal file
29
.github/workflows/shimgen.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Shimgen CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build-shimgen:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: compile
|
||||
run: cl /O1 scoop-better-shimexe/shim.c
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: shim.exe
|
||||
path: shim.exe
|
||||
|
||||
@@ -105,6 +105,10 @@ variables:
|
||||
- golden
|
||||
when: on_failure
|
||||
|
||||
# .test_ghcup_scoop:
|
||||
# script:
|
||||
# - cl /O1 scoop-better-shimexe/shim.c
|
||||
|
||||
.test_ghcup_version:linux:
|
||||
extends:
|
||||
- .test_ghcup_version
|
||||
@@ -184,6 +188,12 @@ variables:
|
||||
- set CABAL_DIR="$CI_PROJECT_DIR/cabal"
|
||||
- bash ./.gitlab/before_script/windows/install_deps.sh
|
||||
|
||||
# .test_ghcup_scoop:windows:
|
||||
# extends:
|
||||
# - .windows
|
||||
# - .test_ghcup_scoop
|
||||
# - .root_cleanup
|
||||
|
||||
.release_ghcup:
|
||||
script:
|
||||
- bash ./.gitlab/script/ghcup_release.sh
|
||||
@@ -338,6 +348,11 @@ test:windows:recommended:
|
||||
CABAL_VERSION: "3.4.0.0"
|
||||
needs: []
|
||||
|
||||
# test:windows:scoop:
|
||||
# stage: test
|
||||
# extends: .test_ghcup_scoop:windows
|
||||
# needs: []
|
||||
|
||||
######## linux release ########
|
||||
|
||||
release:linux:64bit:
|
||||
@@ -420,13 +435,19 @@ release:darwin:aarch64:
|
||||
script: |
|
||||
set -Eeuo pipefail
|
||||
function runInNixShell() {
|
||||
time nix-shell .gitlab/shell.nix \
|
||||
time nix-shell $CI_PROJECT_DIR/.gitlab/shell.nix \
|
||||
-I nixpkgs=https://github.com/angerman/nixpkgs/archive/75f7281738b.tar.gz \
|
||||
--argstr system "aarch64-darwin" \
|
||||
--pure \
|
||||
--keep CI_PROJECT_DIR --keep MAKE_ARGS --keep HADRIAN_ARGS --keep CABAL_CACHE \
|
||||
--keep CI_PROJECT_DIR \
|
||||
--keep MACOSX_DEPLOYMENT_TARGET \
|
||||
--keep JSON_VERSION --keep ARTIFACT \
|
||||
--keep JSON_VERSION \
|
||||
--keep ARTIFACT \
|
||||
--keep OS \
|
||||
--keep ARCH \
|
||||
--keep CABAL_DIR \
|
||||
--keep GHC_VERSION \
|
||||
--keep CABAL_VERSION \
|
||||
--run "$1" 2>&1
|
||||
}
|
||||
runInNixShell ./.gitlab/before_script/darwin/install_deps.sh 2>&1
|
||||
|
||||
@@ -142,6 +142,11 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# check that lazy loading works for 'whereis'
|
||||
cp "$CI_PROJECT_DIR/ghcup-${JSON_VERSION}.yaml" "$CI_PROJECT_DIR/ghcup-${JSON_VERSION}.yaml.bak"
|
||||
echo '**' > "$CI_PROJECT_DIR/ghcup-${JSON_VERSION}.yaml"
|
||||
eghcup whereis ghc $(ghc --numeric-version)
|
||||
mv -f "$CI_PROJECT_DIR/ghcup-${JSON_VERSION}.yaml.bak" "$CI_PROJECT_DIR/ghcup-${JSON_VERSION}.yaml"
|
||||
|
||||
eghcup rm $(ghc --numeric-version)
|
||||
|
||||
@@ -153,6 +158,7 @@ if [ "${OS}" = "LINUX" ] ; then
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
eghcup upgrade
|
||||
eghcup upgrade -f
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ import GHCup.Version
|
||||
import Codec.Archive
|
||||
#endif
|
||||
import Control.Concurrent
|
||||
import Control.DeepSeq ( force )
|
||||
import Control.Exception ( evaluate )
|
||||
import Control.Exception.Safe
|
||||
#if !MIN_VERSION_base(4,13,0)
|
||||
import Control.Monad.Fail ( MonadFail )
|
||||
@@ -64,6 +66,7 @@ import System.Environment
|
||||
import System.Exit
|
||||
import System.FilePath
|
||||
import System.IO hiding ( appendFile )
|
||||
import System.IO.Unsafe ( unsafeInterleaveIO )
|
||||
import Text.Read hiding ( lift )
|
||||
import Text.PrettyPrint.HughesPJClass ( prettyShow )
|
||||
import URI.ByteString
|
||||
@@ -1237,7 +1240,12 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
|
||||
let runLogger = myLoggerT loggerConfig
|
||||
let siletRunLogger = myLoggerT loggerConfig { colorOutter = \_ -> pure () }
|
||||
|
||||
pfreq <- (
|
||||
----------------------------------------
|
||||
-- Getting download and platform info --
|
||||
----------------------------------------
|
||||
|
||||
|
||||
pfreq <- unsafeInterleaveIO $ (
|
||||
runLogger . runE @'[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound] . liftE $ platformRequest
|
||||
) >>= \case
|
||||
VRight r -> pure r
|
||||
@@ -1246,13 +1254,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
|
||||
($(logError) $ T.pack $ prettyShow e)
|
||||
exitWith (ExitFailure 2)
|
||||
|
||||
|
||||
|
||||
----------------------------------------
|
||||
-- Getting download and platform info --
|
||||
----------------------------------------
|
||||
|
||||
ghcupInfo <-
|
||||
ghcupInfo <- unsafeInterleaveIO $
|
||||
( runLogger
|
||||
. runE @'[JSONError , DownloadFailed, FileDoesNotExistError]
|
||||
$ liftE
|
||||
@@ -1265,12 +1267,25 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
|
||||
($(logError) $ T.pack $ prettyShow e)
|
||||
exitWith (ExitFailure 2)
|
||||
|
||||
|
||||
|
||||
-------------------------
|
||||
-- Setting up appstate --
|
||||
-------------------------
|
||||
|
||||
|
||||
let appstate@AppState{dirs = Dirs{..}
|
||||
, ghcupInfo = GHCupInfo { _ghcupDownloads = dls, .. }
|
||||
, ghcupInfo = ~GHCupInfo { _ghcupDownloads = dls, .. }
|
||||
} = AppState settings dirs keybindings ghcupInfo pfreq
|
||||
|
||||
|
||||
---------------------------
|
||||
-- Running startup tasks --
|
||||
---------------------------
|
||||
|
||||
case optCommand of
|
||||
Upgrade _ _ -> pure ()
|
||||
Whereis _ _ -> pure ()
|
||||
_ -> do
|
||||
lookupEnv "GHCUP_SKIP_UPDATE_CHECK" >>= \case
|
||||
Nothing -> runLogger $ flip runReaderT appstate $ checkForUpdates
|
||||
@@ -1278,12 +1293,15 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
|
||||
|
||||
|
||||
-- ensure global tools
|
||||
(siletRunLogger $ flip runReaderT appstate $ runE ensureGlobalTools) >>= \case
|
||||
VRight _ -> pure ()
|
||||
VLeft e -> do
|
||||
runLogger
|
||||
($(logError) $ T.pack $ prettyShow e)
|
||||
exitWith (ExitFailure 30)
|
||||
case optCommand of
|
||||
Whereis _ _ -> pure ()
|
||||
_ -> do
|
||||
(siletRunLogger $ flip runReaderT appstate $ runE ensureGlobalTools) >>= \case
|
||||
VRight _ -> pure ()
|
||||
VLeft e -> do
|
||||
runLogger
|
||||
($(logError) $ T.pack $ prettyShow e)
|
||||
exitWith (ExitFailure 30)
|
||||
|
||||
|
||||
-------------------------
|
||||
@@ -1771,13 +1789,13 @@ Make sure to clean up #{tmpdir} afterwards.|])
|
||||
runLogger $ $(logError) $ T.pack $ prettyShow e
|
||||
pure $ ExitFailure 30
|
||||
|
||||
Upgrade uOpts force -> do
|
||||
Upgrade uOpts force' -> do
|
||||
target <- case uOpts of
|
||||
UpgradeInplace -> Just <$> liftIO getExecutablePath
|
||||
(UpgradeAt p) -> pure $ Just p
|
||||
UpgradeGHCupDir -> pure (Just (binDir </> "ghcup" <> exeExt))
|
||||
|
||||
runUpgrade (liftE $ upgradeGHCup target force) >>= \case
|
||||
runUpgrade (liftE $ upgradeGHCup target force') >>= \case
|
||||
VRight v' -> do
|
||||
let pretty_v = prettyVer v'
|
||||
let vi = fromJust $ snd <$> getLatest dls GHCup
|
||||
@@ -1849,6 +1867,7 @@ Make sure to clean up #{tmpdir} afterwards.|])
|
||||
|
||||
Nuke ->
|
||||
runRm (do
|
||||
void $ liftIO $ evaluate $ force appstate
|
||||
lift $ $logWarn "WARNING: This will remove GHCup and all installed components from your system."
|
||||
lift $ $logWarn "Waiting 10 seconds before commencing, if you want to cancel it, now would be the time."
|
||||
liftIO $ threadDelay 10000000 -- wait 10s
|
||||
@@ -1899,7 +1918,7 @@ fromVersion' SetRecommended tool = do
|
||||
(\(x, y) -> (mkTVer x, Just y)) <$> getRecommended dls tool
|
||||
?? TagNotFound Recommended tool
|
||||
fromVersion' (SetToolVersion v) tool = do
|
||||
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
|
||||
~AppState { ghcupInfo = ~GHCupInfo { _ghcupDownloads = dls }} <- lift ask
|
||||
let vi = getVersionInfo (_tvVersion v) tool dls
|
||||
case pvp $ prettyVer (_tvVersion v) of
|
||||
Left _ -> pure (v, vi)
|
||||
|
||||
@@ -82,7 +82,6 @@ library
|
||||
QuasiQuotes
|
||||
RecordWildCards
|
||||
ScopedTypeVariables
|
||||
Strict
|
||||
StrictData
|
||||
TupleSections
|
||||
TypeApplications
|
||||
@@ -195,7 +194,6 @@ executable ghcup
|
||||
PackageImports
|
||||
RecordWildCards
|
||||
ScopedTypeVariables
|
||||
Strict
|
||||
StrictData
|
||||
TupleSections
|
||||
|
||||
@@ -207,6 +205,7 @@ executable ghcup
|
||||
, base >=4.13 && <5
|
||||
, bytestring ^>=0.10
|
||||
, containers ^>=0.6
|
||||
, deepseq ^>=1.4
|
||||
, filepath ^>=1.4.2.1
|
||||
, ghcup
|
||||
, haskus-utils-variant >=3.0 && <3.2
|
||||
@@ -261,7 +260,6 @@ executable ghcup-gen
|
||||
QuasiQuotes
|
||||
RecordWildCards
|
||||
ScopedTypeVariables
|
||||
Strict
|
||||
StrictData
|
||||
TupleSections
|
||||
TypeApplications
|
||||
@@ -305,6 +303,7 @@ executable ghcup-gen
|
||||
test-suite ghcup-test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Main.hs
|
||||
build-tool-depends: hspec-discover:hspec-discover -any
|
||||
hs-source-dirs: test
|
||||
other-modules:
|
||||
GHCup.ArbitraryTypes
|
||||
@@ -324,8 +323,6 @@ test-suite ghcup-test
|
||||
-Wall -fwarn-tabs -fwarn-incomplete-uni-patterns
|
||||
-fwarn-incomplete-record-updates
|
||||
|
||||
build-tool-depends: hspec-discover:hspec-discover
|
||||
|
||||
build-depends:
|
||||
, base >=4.13 && <5
|
||||
, bytestring ^>=0.10
|
||||
|
||||
27
lib/GHCup.hs
27
lib/GHCup.hs
@@ -1,4 +1,5 @@
|
||||
{-# LANGUAGE CPP #-}
|
||||
{-# LANGUAGE BangPatterns #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
@@ -42,6 +43,7 @@ import GHCup.Version
|
||||
import Codec.Archive ( ArchiveResult )
|
||||
#endif
|
||||
import Control.Applicative
|
||||
import Control.DeepSeq ( force )
|
||||
import Control.Exception ( evaluate )
|
||||
import Control.Exception.Safe
|
||||
import Control.Monad
|
||||
@@ -1393,20 +1395,22 @@ rmGhcupDirs = do
|
||||
rmEnvFile :: (MonadCatch m, MonadLogger m, MonadIO m) => FilePath -> m ()
|
||||
rmEnvFile enFilePath = do
|
||||
$logInfo "Removing Ghcup Environment File"
|
||||
hideError doesNotExistErrorType $ liftIO $ deleteFile enFilePath
|
||||
liftIO $ deleteFile enFilePath
|
||||
|
||||
rmConfFile :: (MonadCatch m, MonadLogger m, MonadIO m) => FilePath -> m ()
|
||||
rmConfFile confFilePath = do
|
||||
$logInfo "removing Ghcup Config File"
|
||||
hideError doesNotExistErrorType $ liftIO $ deleteFile confFilePath
|
||||
liftIO $ deleteFile confFilePath
|
||||
|
||||
rmDir :: (MonadLogger m, MonadIO m, MonadCatch m) => FilePath -> m ()
|
||||
rmDir dir = do
|
||||
$logInfo [i|removing #{dir}|]
|
||||
contents <- hideErrorDef [doesNotExistErrorType] []
|
||||
$ liftIO
|
||||
(getDirectoryContentsRecursive dir >>= evaluate)
|
||||
forM_ contents (liftIO . deleteFile . (dir </>))
|
||||
rmDir dir =
|
||||
-- 'getDirectoryContentsRecursive' is lazy IO. In case
|
||||
-- an error leaks through, we catch it here as well,
|
||||
-- althought 'deleteFile' should already handle it.
|
||||
hideErrorDef [doesNotExistErrorType] () $ do
|
||||
$logInfo [i|removing #{dir}|]
|
||||
contents <- liftIO $ getDirectoryContentsRecursive dir
|
||||
forM_ contents (liftIO . deleteFile . (dir </>))
|
||||
|
||||
rmBinDir :: (MonadCatch m, MonadIO m) => FilePath -> m ()
|
||||
rmBinDir binDir = do
|
||||
@@ -1421,7 +1425,9 @@ rmGhcupDirs = do
|
||||
|
||||
reportRemainingFiles :: MonadIO m => FilePath -> m [FilePath]
|
||||
reportRemainingFiles dir = do
|
||||
remainingFiles <- liftIO $ getDirectoryContentsRecursive dir
|
||||
-- force the files so the errors don't leak
|
||||
(force -> !remainingFiles) <- liftIO
|
||||
(getDirectoryContentsRecursive dir >>= evaluate)
|
||||
let normalizedFilePaths = fmap normalise remainingFiles
|
||||
let sortedByDepthRemainingFiles = sortBy (flip compareFn) normalizedFilePaths
|
||||
let remainingFilesAbsolute = fmap (dir </>) sortedByDepthRemainingFiles
|
||||
@@ -1448,7 +1454,8 @@ rmGhcupDirs = do
|
||||
|
||||
deleteFile :: FilePath -> IO ()
|
||||
deleteFile filepath = do
|
||||
hideError InappropriateType $ rmFile filepath
|
||||
hideError doesNotExistErrorType
|
||||
$ hideError InappropriateType $ rmFile filepath
|
||||
|
||||
removeDirIfEmptyOrIsSymlink :: (MonadCatch m, MonadIO m) => FilePath -> m ()
|
||||
removeDirIfEmptyOrIsSymlink filepath =
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
{-# LANGUAGE CPP #-}
|
||||
{-# LANGUAGE BangPatterns #-}
|
||||
{-# LANGUAGE DeriveGeneric #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
|
||||
{-|
|
||||
Module : GHCup.Types
|
||||
@@ -21,6 +23,7 @@ module GHCup.Types
|
||||
where
|
||||
|
||||
import Control.Applicative
|
||||
import Control.DeepSeq ( NFData, rnf )
|
||||
import Control.Monad.Logger
|
||||
import Data.Map.Strict ( Map )
|
||||
import Data.List.NonEmpty ( NonEmpty (..) )
|
||||
@@ -60,6 +63,8 @@ data GHCupInfo = GHCupInfo
|
||||
}
|
||||
deriving (Show, GHC.Generic)
|
||||
|
||||
instance NFData GHCupInfo
|
||||
|
||||
|
||||
|
||||
-------------------------
|
||||
@@ -79,6 +84,8 @@ data Requirements = Requirements
|
||||
}
|
||||
deriving (Show, GHC.Generic)
|
||||
|
||||
instance NFData Requirements
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -105,9 +112,13 @@ data Tool = GHC
|
||||
| Stack
|
||||
deriving (Eq, GHC.Generic, Ord, Show, Enum, Bounded)
|
||||
|
||||
instance NFData Tool
|
||||
|
||||
data GlobalTool = ShimGen
|
||||
deriving (Eq, GHC.Generic, Ord, Show, Enum, Bounded)
|
||||
|
||||
instance NFData GlobalTool
|
||||
|
||||
|
||||
-- | All necessary information of a tool version, including
|
||||
-- source download and per-architecture downloads.
|
||||
@@ -123,6 +134,8 @@ data VersionInfo = VersionInfo
|
||||
}
|
||||
deriving (Eq, GHC.Generic, Show)
|
||||
|
||||
instance NFData VersionInfo
|
||||
|
||||
|
||||
-- | A tag. These are currently attached to a version of a tool.
|
||||
data Tag = Latest
|
||||
@@ -133,6 +146,8 @@ data Tag = Latest
|
||||
| UnknownTag String -- ^ used for upwardscompat
|
||||
deriving (Ord, Eq, GHC.Generic, Show) -- FIXME: manual JSON instance
|
||||
|
||||
instance NFData Tag
|
||||
|
||||
tagToString :: Tag -> String
|
||||
tagToString Recommended = "recommended"
|
||||
tagToString Latest = "latest"
|
||||
@@ -159,6 +174,8 @@ data Architecture = A_64
|
||||
| A_ARM64
|
||||
deriving (Eq, GHC.Generic, Ord, Show)
|
||||
|
||||
instance NFData Architecture
|
||||
|
||||
archToString :: Architecture -> String
|
||||
archToString A_64 = "x86_64"
|
||||
archToString A_32 = "i386"
|
||||
@@ -181,6 +198,8 @@ data Platform = Linux LinuxDistro
|
||||
-- ^ must exit
|
||||
deriving (Eq, GHC.Generic, Ord, Show)
|
||||
|
||||
instance NFData Platform
|
||||
|
||||
platformToString :: Platform -> String
|
||||
platformToString (Linux distro) = "linux-" ++ distroToString distro
|
||||
platformToString Darwin = "darwin"
|
||||
@@ -206,6 +225,8 @@ data LinuxDistro = Debian
|
||||
-- ^ must exit
|
||||
deriving (Eq, GHC.Generic, Ord, Show)
|
||||
|
||||
instance NFData LinuxDistro
|
||||
|
||||
distroToString :: LinuxDistro -> String
|
||||
distroToString Debian = "debian"
|
||||
distroToString Ubuntu = "ubuntu"
|
||||
@@ -232,6 +253,7 @@ data DownloadInfo = DownloadInfo
|
||||
}
|
||||
deriving (Eq, Ord, GHC.Generic, Show)
|
||||
|
||||
instance NFData DownloadInfo
|
||||
|
||||
|
||||
|
||||
@@ -245,6 +267,8 @@ data TarDir = RealDir FilePath
|
||||
| RegexDir String -- ^ will be compiled to regex, the first match will "win"
|
||||
deriving (Eq, Ord, GHC.Generic, Show)
|
||||
|
||||
instance NFData TarDir
|
||||
|
||||
instance Pretty TarDir where
|
||||
pPrint (RealDir path) = text path
|
||||
pPrint (RegexDir regex) = text regex
|
||||
@@ -257,6 +281,10 @@ data URLSource = GHCupURL
|
||||
| AddSource (Either GHCupInfo URI) -- ^ merge with GHCupURL
|
||||
deriving (GHC.Generic, Show)
|
||||
|
||||
instance NFData URLSource
|
||||
instance NFData (URIRef Absolute) where
|
||||
rnf (URI !_ !_ !_ !_ !_) = ()
|
||||
|
||||
|
||||
data UserSettings = UserSettings
|
||||
{ uCache :: Maybe Bool
|
||||
@@ -298,6 +326,9 @@ data KeyBindings = KeyBindings
|
||||
}
|
||||
deriving (Show, GHC.Generic)
|
||||
|
||||
instance NFData KeyBindings
|
||||
instance NFData Key
|
||||
|
||||
defaultKeyBindings :: KeyBindings
|
||||
defaultKeyBindings = KeyBindings
|
||||
{ bUp = KUp
|
||||
@@ -315,9 +346,11 @@ data AppState = AppState
|
||||
{ settings :: Settings
|
||||
, dirs :: Dirs
|
||||
, keyBindings :: KeyBindings
|
||||
, ghcupInfo :: GHCupInfo
|
||||
, pfreq :: PlatformRequest
|
||||
} deriving (Show)
|
||||
, ghcupInfo :: ~GHCupInfo
|
||||
, pfreq :: ~PlatformRequest
|
||||
} deriving (Show, GHC.Generic)
|
||||
|
||||
instance NFData AppState
|
||||
|
||||
data Settings = Settings
|
||||
{ cache :: Bool
|
||||
@@ -329,6 +362,8 @@ data Settings = Settings
|
||||
}
|
||||
deriving (Show, GHC.Generic)
|
||||
|
||||
instance NFData Settings
|
||||
|
||||
data Dirs = Dirs
|
||||
{ baseDir :: FilePath
|
||||
, binDir :: FilePath
|
||||
@@ -336,19 +371,25 @@ data Dirs = Dirs
|
||||
, logsDir :: FilePath
|
||||
, confDir :: FilePath
|
||||
}
|
||||
deriving Show
|
||||
deriving (Show, GHC.Generic)
|
||||
|
||||
instance NFData Dirs
|
||||
|
||||
data KeepDirs = Always
|
||||
| Errors
|
||||
| Never
|
||||
deriving (Eq, Show, Ord)
|
||||
deriving (Eq, Show, Ord, GHC.Generic)
|
||||
|
||||
instance NFData KeepDirs
|
||||
|
||||
data Downloader = Curl
|
||||
| Wget
|
||||
#if defined(INTERNAL_DOWNLOADER)
|
||||
| Internal
|
||||
#endif
|
||||
deriving (Eq, Show, Ord)
|
||||
deriving (Eq, Show, Ord, GHC.Generic)
|
||||
|
||||
instance NFData Downloader
|
||||
|
||||
data DebugInfo = DebugInfo
|
||||
{ diBaseDir :: FilePath
|
||||
@@ -371,7 +412,9 @@ data PlatformResult = PlatformResult
|
||||
{ _platform :: Platform
|
||||
, _distroVersion :: Maybe Versioning
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
deriving (Eq, Show, GHC.Generic)
|
||||
|
||||
instance NFData PlatformResult
|
||||
|
||||
platResToString :: PlatformResult -> String
|
||||
platResToString PlatformResult { _platform = plat, _distroVersion = Just v' }
|
||||
@@ -387,7 +430,9 @@ data PlatformRequest = PlatformRequest
|
||||
, _rPlatform :: Platform
|
||||
, _rVersion :: Maybe Versioning
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
deriving (Eq, Show, GHC.Generic)
|
||||
|
||||
instance NFData PlatformRequest
|
||||
|
||||
pfReqToString :: PlatformRequest -> String
|
||||
pfReqToString (PlatformRequest arch plat ver) =
|
||||
@@ -434,6 +479,8 @@ data VersionCmp = VR_gt Versioning
|
||||
| VR_eq Versioning
|
||||
deriving (Eq, GHC.Generic, Ord, Show)
|
||||
|
||||
instance NFData VersionCmp
|
||||
|
||||
|
||||
-- | A version range. Supports && and ||, but not arbitrary
|
||||
-- combinations. This is a little simplified.
|
||||
@@ -441,6 +488,7 @@ data VersionRange = SimpleRange (NonEmpty VersionCmp) -- And
|
||||
| OrRange (NonEmpty VersionCmp) VersionRange
|
||||
deriving (Eq, GHC.Generic, Ord, Show)
|
||||
|
||||
instance NFData VersionRange
|
||||
|
||||
instance Pretty Versioning where
|
||||
pPrint = text . T.unpack . prettyV
|
||||
|
||||
9
scoop-better-shimexe/LICENSE-MIT
Normal file
9
scoop-better-shimexe/LICENSE-MIT
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Grégoire Geis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
10
scoop-better-shimexe/LICENSE-UNLICENSE
Normal file
10
scoop-better-shimexe/LICENSE-UNLICENSE
Normal file
@@ -0,0 +1,10 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
71
scoop-better-shimexe/README.md
Normal file
71
scoop-better-shimexe/README.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# `shim.c`
|
||||
|
||||
[`shim.c`](./shim.c) is a simple Windows program that, when started:
|
||||
1. Looks for a file with the exact same name as the running program, but with
|
||||
the extension `shim` (e.g. `C:\bin\foo.exe` will read the file `C:\bin\foo.shim`).
|
||||
2. Reads and [parses](#shim-format) the files into a
|
||||
[Scoop](https://github.com/lukesampson/scoop) shim format.
|
||||
3. Executes the target executable with the given arguments.
|
||||
|
||||
`shim.c` was originally made to replace [Scoop](https://github.com/lukesampson/scoop)'s
|
||||
[`shim.cs`](https://github.com/lukesampson/scoop/blob/96de9c14bb483f9278e4b0a9e22b1923ee752901/supporting/shimexe/shim.cs)
|
||||
since it had several important flaws:
|
||||
1. [It was made in C#](https://github.com/lukesampson/scoop/tree/96de9c14bb483f9278e4b0a9e22b1923ee752901/supporting/shimexe),
|
||||
and thus required an instantiation of a .NET command line app everytime it was started,
|
||||
which can make a command run much slower than if it had been ran directly;
|
||||
2. [It](https://github.com/lukesampson/scoop/issues/2339) [did](https://github.com/lukesampson/scoop/issues/1896)
|
||||
[not](https://github.com/felixse/FluentTerminal/issues/221) handle Ctrl+C and other
|
||||
signals correctly, which could be quite infuriating (and essentially killing REPLs and long-running apps).
|
||||
|
||||
[`shim.c`](./shim.c) is:
|
||||
- **Faster**, because it does not use the .NET Framework, and parses the `.shim` file in a simpler way.
|
||||
- **More efficient**, because by the time the target of the shim is started, all allocated memory will have been freed.
|
||||
- And more importantly, it **works better**:
|
||||
- Signals originating from pressing `Ctrl+C` are ignored, and therefore handled directly by the spawned child.
|
||||
Your processes and REPLs will no longer close when pressing `Ctrl+C`.
|
||||
- Children are automatically killed when the shim process is killed. No more orphaned processes and weird behaviors.
|
||||
|
||||
> **Note**: This project is not affiliated with [Scoop](https://github.com/lukesampson/scoop).
|
||||
|
||||
|
||||
## Installation for Scoop
|
||||
|
||||
- In a Visual Studio command prompt, run `cl /O1 shim.c`.
|
||||
- Replace any `.exe` in `scoop\shims` by `shim.exe`.
|
||||
|
||||
An additional script, `repshims.bat`, is provided. It will replace all `.exe`s in the user's Scoop directory
|
||||
by `shim.exe`.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
Given the following shim `gs.shim`:
|
||||
```
|
||||
path = C:\Program Files\Git\git.exe
|
||||
args = status -u
|
||||
```
|
||||
|
||||
In this directory, where `gs.exe` is the compiled `shim.c`:
|
||||
```
|
||||
C:\Bin\
|
||||
gs.exe
|
||||
gs.shim
|
||||
```
|
||||
|
||||
Then calling `gs -s` will run the program `C:\Program Files\Git\git.exe status -u -s`.
|
||||
|
||||
|
||||
## Shim format
|
||||
|
||||
Shims follow the same format as Scoop's shims: line-separated `key = value` pairs.
|
||||
```
|
||||
path = C:\Program Files\Git\git.exe
|
||||
args = status -uno
|
||||
```
|
||||
|
||||
`path` is a required value, but `args` can be omitted. Also, do note that lines **must** end with a line feed.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
`SPDX-License-Identifier: MIT OR Unlicense`
|
||||
15
scoop-better-shimexe/repshims.bat
Normal file
15
scoop-better-shimexe/repshims.bat
Normal file
@@ -0,0 +1,15 @@
|
||||
@echo off
|
||||
|
||||
if not defined SCOOP set SCOOP=%USERPROFILE%\scoop
|
||||
|
||||
for %%x in ("%SCOOP%\shims\*.exe") do (
|
||||
echo Replacing %%x by new shim.
|
||||
copy /B /Y shim.exe "%%~x" >NUL
|
||||
)
|
||||
|
||||
if not defined SCOOP_GLOBAL set SCOOP_GLOBAL=%ProgramData%\scoop
|
||||
|
||||
for %%x in ("%SCOOP_GLOBAL%\shims\*.exe") do (
|
||||
echo Replacing %%x by new shim.
|
||||
copy /B /Y shim.exe "%%~x" >NUL
|
||||
)
|
||||
256
scoop-better-shimexe/shim.c
Normal file
256
scoop-better-shimexe/shim.c
Normal file
@@ -0,0 +1,256 @@
|
||||
#pragma comment(lib, "SHELL32.LIB")
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <wchar.h>
|
||||
#include <Windows.h>
|
||||
|
||||
#ifndef ERROR_ELEVATION_REQUIRED
|
||||
# define ERROR_ELEVATION_REQUIRED 740
|
||||
#endif
|
||||
|
||||
#define MAX_FILENAME_SIZE 512
|
||||
|
||||
BOOL WINAPI ctrlhandler(DWORD fdwCtrlType)
|
||||
{
|
||||
switch (fdwCtrlType) {
|
||||
// Ignore all events, and let the child process
|
||||
// handle them.
|
||||
case CTRL_C_EVENT:
|
||||
case CTRL_CLOSE_EVENT:
|
||||
case CTRL_LOGOFF_EVENT:
|
||||
case CTRL_BREAK_EVENT:
|
||||
case CTRL_SHUTDOWN_EVENT:
|
||||
return TRUE;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int compute_program_length(const wchar_t* commandline)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (commandline[0] == L'"') {
|
||||
// Wait till end of string
|
||||
i++;
|
||||
|
||||
for (;;) {
|
||||
wchar_t c = commandline[i++];
|
||||
|
||||
if (c == 0)
|
||||
return i - 1;
|
||||
else if (c == L'\\')
|
||||
i++;
|
||||
else if (c == L'"')
|
||||
return i;
|
||||
}
|
||||
} else {
|
||||
for (;;) {
|
||||
wchar_t c = commandline[i++];
|
||||
|
||||
if (c == 0)
|
||||
return i - 1;
|
||||
else if (c == L'\\')
|
||||
i++;
|
||||
else if (c == L' ')
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
DWORD exit_code = 0;
|
||||
|
||||
wchar_t* path = NULL;
|
||||
wchar_t* args = NULL;
|
||||
wchar_t* cmd = NULL;
|
||||
|
||||
// Find filename of current executable.
|
||||
wchar_t filename[MAX_FILENAME_SIZE + 2];
|
||||
const unsigned int filename_size = GetModuleFileNameW(NULL, filename, MAX_FILENAME_SIZE);
|
||||
|
||||
if (filename_size >= MAX_FILENAME_SIZE) {
|
||||
fprintf(stderr, "The filename of the program is too long to handle.\n");
|
||||
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Use filename of current executable to find .shim
|
||||
filename[filename_size - 3] = L's';
|
||||
filename[filename_size - 2] = L'h';
|
||||
filename[filename_size - 1] = L'i';
|
||||
filename[filename_size - 0] = L'm';
|
||||
filename[filename_size + 1] = 0 ;
|
||||
|
||||
FILE* shim_file;
|
||||
|
||||
if ((shim_file = _wfsopen(filename, L"r,ccs=UTF-8", _SH_DENYNO)) == NULL) {
|
||||
fprintf(stderr, "Cannot open shim file for read.\n");
|
||||
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
size_t command_length = 256;
|
||||
size_t path_length;
|
||||
size_t args_length;
|
||||
|
||||
// Read shim
|
||||
wchar_t linebuf[8192];
|
||||
|
||||
for (;;) {
|
||||
const wchar_t* line = fgetws(linebuf, 8192, shim_file);
|
||||
|
||||
if (line == NULL)
|
||||
break;
|
||||
|
||||
if (line[4] != L' ' || line[5] != L'=' || line[6] != L' ')
|
||||
continue;
|
||||
|
||||
const int linelen = wcslen(line);
|
||||
const int len = linelen - 8 + (line[linelen - 1] != '\n');
|
||||
|
||||
if (line[0] == L'p' && line[1] == L'a' && line[2] == L't' && line[3] == L'h') {
|
||||
// Reading path
|
||||
path = calloc(len + 1, sizeof(wchar_t));
|
||||
wmemcpy(path, line + 7, len);
|
||||
|
||||
command_length += len;
|
||||
path_length = len;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == L'a' && line[1] == L'r' && line[2] == L'g' && line[3] == L's') {
|
||||
// Reading args
|
||||
args = calloc(len + 1, sizeof(wchar_t));
|
||||
wmemcpy(args, line + 7, len);
|
||||
|
||||
command_length += len + 1;
|
||||
args_length = len;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
fclose(shim_file);
|
||||
|
||||
if (path == NULL) {
|
||||
fprintf(stderr, "Could not read shim file.\n");
|
||||
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Find length of command to run
|
||||
wchar_t* given_cmd = GetCommandLineW();
|
||||
const int program_length = compute_program_length(given_cmd);
|
||||
|
||||
given_cmd += program_length;
|
||||
|
||||
const int given_length = wcslen(given_cmd);
|
||||
|
||||
command_length += given_length;
|
||||
|
||||
// Start building command to run, using '[path] [args]', as given by shim.
|
||||
cmd = calloc(command_length, sizeof(wchar_t));
|
||||
int cmd_i = 0;
|
||||
|
||||
wmemcpy(cmd, path, path_length);
|
||||
cmd[path_length] = ' ';
|
||||
cmd_i += path_length + 1;
|
||||
|
||||
if (args != NULL) {
|
||||
wmemcpy(cmd + path_length + 1, args, args_length);
|
||||
cmd[path_length + args_length + 1] = ' ';
|
||||
cmd_i += args_length + 1;
|
||||
}
|
||||
|
||||
// Copy all given arguments to command
|
||||
wmemcpy(cmd + cmd_i, given_cmd, given_length);
|
||||
|
||||
// Find out if the target program is a console app
|
||||
SHFILEINFOW sfi = {0};
|
||||
const BOOL is_windows_app = HIWORD(SHGetFileInfoW(path, -1, &sfi, sizeof(sfi), SHGFI_EXETYPE));
|
||||
|
||||
if (is_windows_app)
|
||||
// Unfortunately, this technique will still show a window for a fraction of time,
|
||||
// but there's just no workaround.
|
||||
FreeConsole();
|
||||
|
||||
// Create job object, which can be attached to child processes
|
||||
// to make sure they terminate when the parent terminates as well.
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
|
||||
HANDLE jobHandle = CreateJobObject(NULL, NULL);
|
||||
|
||||
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
|
||||
SetInformationJobObject(jobHandle, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
|
||||
|
||||
// Start subprocess
|
||||
STARTUPINFOW si = {0};
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
if (CreateProcessW(NULL, cmd, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
|
||||
AssignProcessToJobObject(jobHandle, pi.hProcess);
|
||||
ResumeThread(pi.hThread);
|
||||
} else {
|
||||
if (GetLastError() == ERROR_ELEVATION_REQUIRED) {
|
||||
// We must elevate the process, which is (basically) impossible with
|
||||
// CreateProcess, and therefore we fallback to ShellExecuteEx,
|
||||
// which CAN create elevated processes, at the cost of opening a new separate
|
||||
// window.
|
||||
// Theorically, this could be fixed (or rather, worked around) using pipes
|
||||
// and IPC, but... this is a question for another day.
|
||||
SHELLEXECUTEINFOW sei = {0};
|
||||
|
||||
sei.cbSize = sizeof(SHELLEXECUTEINFOW);
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
sei.lpFile = path;
|
||||
sei.lpParameters = cmd + path_length + 1;
|
||||
sei.nShow = SW_SHOW;
|
||||
|
||||
if (!ShellExecuteExW(&sei)) {
|
||||
fprintf(stderr, "Unable to create elevated process: error %li.", GetLastError());
|
||||
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pi.hProcess = sei.hProcess;
|
||||
} else {
|
||||
fprintf(stderr, "Could not create process with command '%ls'.\n", cmd);
|
||||
|
||||
exit_code = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore Ctrl-C and other signals
|
||||
if (!SetConsoleCtrlHandler(ctrlhandler, TRUE))
|
||||
fprintf(stderr, "Could not set control handler; Ctrl-C behavior may be invalid.\n");
|
||||
|
||||
// Wait till end of process
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
|
||||
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||
|
||||
// Dispose of everything
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(jobHandle);
|
||||
|
||||
cleanup:
|
||||
|
||||
// Free obsolete buffers
|
||||
free(path);
|
||||
free(args);
|
||||
free(cmd);
|
||||
|
||||
return (int)exit_code;
|
||||
}
|
||||
Reference in New Issue
Block a user