Introducing cabal-helper-wrapper

The idea here is to build cabal-helper with whatever version of Cabal
the user happens to be using (which we find by looking at
dist/setup-config) at runtime.

This way we can support literally any version of Cabal as long as the
actual cabal-helper still compiles.

I tried to only use interfaces in Cabal that have been there since at
least 1.16 so I'm hoping this shouldn't break too much.
This commit is contained in:
Daniel Gröber 2015-03-01 04:51:22 +01:00
parent 48563a435e
commit 52e3233f44
5 changed files with 656 additions and 35 deletions

41
cabal-helper/Common.hs Normal file
View File

@ -0,0 +1,41 @@
-- ghc-mod: Making Haskell development *more* fun
-- Copyright (C) 2015 Daniel Gröber <dxld ÄT darkboxed DOT org>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
module Common where
import Control.Applicative
import Data.List
import Data.Maybe
import System.Environment
import System.IO
errMsg :: String -> IO ()
errMsg str = do
prog <- getProgName
hPutStrLn stderr $ prog ++ ": " ++ str
align :: String -> String -> String -> String
align n an str = let
h:rest = lines str
[hm] = match n h
rest' = [ move (hm - rm) r | r <- rest, rm <- match an r]
in
unlines (h:rest')
where
match p str' = maybeToList $
fst <$> find ((p `isPrefixOf`) . snd) ([0..] `zip` tails str')
move i str' | i > 0 = replicate i ' ' ++ str'
move i str' = drop i str'

345
cabal-helper/Main.hs Normal file
View File

@ -0,0 +1,345 @@
-- ghc-mod: Making Haskell development *more* fun
-- Copyright (C) 2015 Daniel Gröber <dxld ÄT darkboxed DOT org>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
{-# LANGUAGE CPP, BangPatterns, RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-deprecations #-}
import Distribution.Simple.Utils (cabalVersion)
import Distribution.Simple.Configure
import Distribution.Package (PackageIdentifier, InstalledPackageId, PackageId)
import Distribution.PackageDescription (PackageDescription,
FlagAssignment,
Executable(..),
Library(..),
TestSuite(..),
Benchmark(..),
BuildInfo(..),
TestSuiteInterface(..),
BenchmarkInterface(..),
withLib)
import Distribution.PackageDescription.Parse (readPackageDescription)
import Distribution.PackageDescription.Configuration (flattenPackageDescription)
import Distribution.Simple.Configure (getPersistBuildConfig)
import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..),
Component(..),
ComponentName(..),
ComponentLocalBuildInfo(..),
componentBuildInfo,
externalPackageDeps,
withComponentsLBI,
inplacePackageId)
import Distribution.Simple.GHC (componentGhcOptions)
import Distribution.Simple.Program.GHC (GhcOptions(..), renderGhcOptions)
import Distribution.Simple.Setup (ConfigFlags(..),Flag(..))
import Distribution.Simple.Build (initialBuildSteps)
import Distribution.Simple.BuildPaths (autogenModuleName, cppHeaderName, exeExtension)
import Distribution.Simple.Compiler (PackageDB(..))
import Distribution.ModuleName (components)
import qualified Distribution.ModuleName as C (ModuleName)
import Distribution.Text (display)
import Distribution.Verbosity (Verbosity, silent, deafening)
import Control.Applicative ((<$>))
import Control.Monad
import Control.Exception (catch, PatternMatchFail(..))
import Data.List
import Data.Maybe
import Data.Monoid
import Data.IORef
import System.Environment
import System.Directory
import System.FilePath
import System.Exit
import System.IO
import System.IO.Unsafe (unsafeInterleaveIO, unsafePerformIO)
import Text.Printf
import Common
--- \ / These types MUST be in sync with the copies in lib:ghc-mod
data GmComponentName = GmSetupHsName
| GmLibName
| GmExeName String
| GmTestName String
| GmBenchName String
deriving (Eq, Ord, Read, Show)
data GmCabalHelperResponse
= GmCabalHelperStrings [(GmComponentName, [String])]
| GmCabalHelperEntrypoints [(GmComponentName, Either FilePath [ModuleName])]
| GmCabalHelperLbi String
deriving (Read, Show)
--- ^ These types MUST be in sync with the copies in ../Types.hs
-- MUST be compatible to the one in GHC
newtype ModuleName = ModuleName String
deriving (Eq, Ord, Read, Show)
usage = do
prog <- getProgName
hPutStr stderr $ align "(" "|" ("Usage: " ++ prog ++ " " ++ usageMsg)
where
usageMsg = ""
++"DIST_DIR ( version\n"
++" | print-lbi\n"
++" | write-autogen-files\n"
++" | ghc-options [--with-inplace]\n"
++" | ghc-src-options [--with-inplace]\n"
++" | ghc-pkg-options [--with-inplace]\n"
++" | entrypoints\n"
++" | source-dirs\n"
++" ) ...\n"
commands :: [String]
commands = [ "print-bli"
, "write-autogen-files"
, "component-from-file"
, "ghc-options"
, "ghc-src-options"
, "ghc-pkg-options"
, "entrypoints"
, "source-dirs"]
main :: IO ()
main = do
args <- getArgs
distdir:args' <- case args of
[] -> usage >> exitFailure
_ -> return args
ddexists <- doesDirectoryExist distdir
when (not ddexists) $ do
errMsg $ "distdir '"++distdir++"' does not exist"
exitFailure
v <- maybe silent (const deafening) <$> lookupEnv "GHC_MOD_DEBUG"
lbi <- unsafeInterleaveIO $ getPersistBuildConfig distdir
let pd = localPkgDescr lbi
let
-- a =<< b $$ c == (a =<< b) $$ c
-- a <$$> b $$ c == a <$$> (b $$ c)
infixr 2 $$
($$) = ($)
infixr 1 <$$>
(<$$>) = (<$>)
collectCmdOptions :: [String] -> [[String]]
collectCmdOptions =
reverse . map reverse . foldl f [] . dropWhile isOpt
where
isOpt = ("--" `isPrefixOf`)
f [] x = [[x]]
f (a:as) x
| isOpt x = (x:a):as
| otherwise = [x]:(a:as)
let cmds = collectCmdOptions args'
if any (["version"] `isPrefixOf`) cmds
then do
putStrLn $
printf "using version %s of the Cabal library" (display cabalVersion)
exitSuccess
else return ()
print =<< flip mapM cmds $$ \cmd -> do
case cmd of
"write-autogen-files":[] -> do
let pd = localPkgDescr lbi
-- calls writeAutogenFiles
initialBuildSteps distdir pd lbi v
return Nothing
"ghc-options":flags ->
Just . GmCabalHelperStrings <$$> componentsMap lbi v distdir $$
\c clbi bi -> let
outdir = componentOutDir lbi c
(clbi', adopts) = case flags of
["--with-inplace"] -> (clbi, mempty)
[] -> removeInplaceDeps pd clbi
opts = componentGhcOptions v lbi bi clbi' outdir
in
renderGhcOptions (compiler lbi) $ opts `mappend` adopts
"ghc-src-options":flags ->
Just . GmCabalHelperStrings <$$> componentsMap lbi v distdir $$
\c clbi bi -> let
outdir = componentOutDir lbi c
(clbi', adopts) = case flags of
["--with-inplace"] -> (clbi, mempty)
[] -> removeInplaceDeps pd clbi
opts = componentGhcOptions v lbi bi clbi' outdir
comp = compiler lbi
opts' = mempty {
-- Not really needed but "unexpected package db stack: []"
ghcOptPackageDBs = [GlobalPackageDB],
ghcOptCppOptions = ghcOptCppOptions opts,
ghcOptCppIncludePath = ghcOptCppIncludePath opts,
ghcOptCppIncludes = ghcOptCppIncludes opts,
ghcOptFfiIncludes = ghcOptFfiIncludes opts,
ghcOptSourcePathClear = ghcOptSourcePathClear opts,
ghcOptSourcePath = ghcOptSourcePath opts
}
in
renderGhcOptions comp $ opts `mappend` adopts
"ghc-pkg-options":flags ->
Just . GmCabalHelperStrings <$$> componentsMap lbi v distdir $$
\c clbi bi -> let
comp = compiler lbi
outdir = componentOutDir lbi c
(clbi', adopts) = case flags of
["--with-inplace"] -> (clbi, mempty)
[] -> removeInplaceDeps pd clbi
opts = componentGhcOptions v lbi bi clbi' outdir
opts' = mempty {
ghcOptPackageDBs = ghcOptPackageDBs opts,
ghcOptPackages = ghcOptPackages opts,
ghcOptHideAllPackages = ghcOptHideAllPackages opts
}
in
renderGhcOptions (compiler lbi) $ opts' `mappend` adopts
"entrypoints":[] -> do
eps <- componentsMap lbi v distdir $ \c clbi bi ->
componentEntrypoints c
-- MUST append Setup component at the end otherwise CabalHelper gets
-- confused
let eps' = eps ++ [(GmSetupHsName, Right [ModuleName "Setup"])]
return $ Just $ GmCabalHelperEntrypoints eps'
"source-dirs":[] ->
Just . GmCabalHelperStrings <$$> componentsMap lbi v distdir $$
\c clbi bi -> hsSourceDirs bi
"print-lbi":[] ->
return $ Just $ GmCabalHelperLbi $ show lbi
cmd:_ | not (cmd `elem` commands) ->
errMsg ("Unknown command: " ++ cmd) >> usage >> exitFailure
_ ->
errMsg "Invalid usage!" >> usage >> exitFailure
getLibrary :: PackageDescription -> Library
getLibrary pd = unsafePerformIO $ do
lr <- newIORef (error "libraryMap: empty IORef")
withLib pd (writeIORef lr)
readIORef lr
componentsMap :: LocalBuildInfo
-> Verbosity
-> FilePath
-> ( Component
-> ComponentLocalBuildInfo
-> BuildInfo
-> a)
-> IO [(GmComponentName, a)]
componentsMap lbi v distdir f = do
let pd = localPkgDescr lbi
lr <- newIORef []
withComponentsLBI pd lbi $ \c clbi -> do
let bi = componentBuildInfo c
name = componentNameFromComponent c
l' <- readIORef lr
writeIORef lr $ (componentNameToGm name, f c clbi bi):l'
reverse <$> readIORef lr
componentNameToGm CLibName = GmLibName
componentNameToGm (CExeName n) = GmExeName n
componentNameToGm (CTestName n) = GmTestName n
componentNameToGm (CBenchName n) = GmBenchName n
componentNameFromComponent (CLib Library {}) = CLibName
componentNameFromComponent (CExe Executable {..}) = CExeName exeName
componentNameFromComponent (CTest TestSuite {..}) = CTestName testName
componentNameFromComponent (CBench Benchmark {..}) = CBenchName benchmarkName
componentOutDir lbi (CLib Library {..})= buildDir lbi
componentOutDir lbi (CExe Executable {..})= exeOutDir lbi exeName
componentOutDir lbi (CTest TestSuite { testInterface = TestSuiteExeV10 _ _, ..}) =
exeOutDir lbi testName
componentOutDir lbi (CTest TestSuite { testInterface = TestSuiteLibV09 _ _, ..}) =
exeOutDir lbi (testName ++ "Stub")
componentOutDir lbi (CBench Benchmark { benchmarkInterface = BenchmarkExeV10 _ _, ..})=
exeOutDir lbi benchmarkName
gmModuleName :: C.ModuleName -> ModuleName
gmModuleName = ModuleName . intercalate "." . components
componentEntrypoints :: Component -> Either FilePath [ModuleName]
componentEntrypoints (CLib Library {..})
= Right $ map gmModuleName exposedModules
componentEntrypoints (CExe Executable {..})
= Left modulePath
componentEntrypoints (CTest TestSuite { testInterface = TestSuiteExeV10 _ fp })
= Left fp
componentEntrypoints (CTest TestSuite { testInterface = TestSuiteLibV09 _ mn })
= Right [gmModuleName mn]
componentEntrypoints (CTest TestSuite {})
= Right []
componentEntrypoints (CBench Benchmark { benchmarkInterface = BenchmarkExeV10 _ fp})
= Left fp
componentEntrypoints (CBench Benchmark {})
= Left []
exeOutDir :: LocalBuildInfo -> String -> FilePath
exeOutDir lbi exeName =
----- Copied from Distribution/Simple/GHC.hs:buildOrReplExe
-- exeNameReal, the name that GHC really uses (with .exe on Windows)
let exeNameReal = exeName <.>
(if takeExtension exeName /= ('.':exeExtension)
then exeExtension
else "")
targetDir = (buildDir lbi) </> exeName
in targetDir
removeInplaceDeps :: PackageDescription
-> ComponentLocalBuildInfo
-> (ComponentLocalBuildInfo, GhcOptions)
removeInplaceDeps pd clbi = let
(ideps, deps) = partition isInplaceDep (componentPackageDeps clbi)
hasIdeps = not $ null ideps
clbi' = clbi { componentPackageDeps = deps }
lib = getLibrary pd
src_dirs = hsSourceDirs (libBuildInfo lib)
adopts = mempty {
ghcOptSourcePath =
#if CABAL_MAJOR == 1 && CABAL_MINOR >= 22
toNubListR src_dirs
#else
src_dirs
#endif
}
in (clbi', if hasIdeps then adopts else mempty)
where
isInplaceDep :: (InstalledPackageId, PackageId) -> Bool
isInplaceDep (ipid, pid) = inplacePackageId pid == ipid

258
cabal-helper/Wrapper.hs Normal file
View File

@ -0,0 +1,258 @@
-- ghc-mod: Making Haskell development *more* fun
-- Copyright (C) 2015 Daniel Gröber <dxld ÄT darkboxed DOT org>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
{-# LANGUAGE TemplateHaskell, OverloadedStrings, RecordWildCards #-}
module Main where
import Control.Applicative
import Control.Arrow
import Control.Monad
import Control.Monad.Trans.Maybe
import Data.Char
import Data.List
import Data.Maybe
import Data.String
import Data.Version
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Text.Printf
import Text.ParserCombinators.ReadP
import System.Environment
import System.Directory
import System.FilePath
import System.Process
import System.Exit
import System.IO
import Distribution.System (buildPlatform)
import Distribution.Text (display)
import NotCPP.Declarations
import Paths_ghc_mod
import Common
import Utils
ifD [d| getExecutablePath = getProgName |]
usage :: IO ()
usage = do
prog <- getProgName
hPutStr stderr $ align "(" "|" ("Usage: " ++ prog ++ " " ++ usageMsg)
where
usageMsg = "\
\( print-appdatadir\n\
\| print-build-platform\n\
\| DIST_DIR [CABAL_HELPER_ARGS...]\n\
\)\n"
main :: IO ()
main = do
args <- getArgs
case args of
"print-appdatadir":[] -> putStrLn =<< appDataDir
"print-build-platform":[] -> putStrLn $ display buildPlatform
distdir:_ -> do
cfgf <- canonicalizePath (distdir </> "setup-config")
mhdr <- (parseHeader =<<) . listToMaybe . BS8.lines <$> BS.readFile cfgf
case mhdr of
Nothing -> error $ printf "\
\Could not read Cabal's persistent setup configuration header\n\
\- Check first line of: %s\n\
\- Maybe try: $ cabal configure" cfgf
Just Header {..} -> do
eexe <- compileHelper hdrCabalVersion
case eexe of
Left e -> exitWith e
Right exe -> do
(_,_,_,h) <- createProcess $ proc exe args
exitWith =<< waitForProcess h
_ -> usage
appDataDir :: IO FilePath
appDataDir = (</> "cabal-helper") <$> getAppUserDataDirectory "ghc-mod"
tryFindSrcDirInGhcModTree :: IO (Maybe FilePath)
tryFindSrcDirInGhcModTree = do
dir <- (!!4) . iterate takeDirectory <$> getExecutablePath
exists <- doesFileExist $ dir </> "ghc-mod.cabal"
src_exists <- doesFileExist $ dir </> "cabal-helper/Main.hs"
if exists && src_exists
then return $ Just (dir </> "cabal-helper")
else return Nothing
tryFindRealSrcDir :: IO (Maybe FilePath)
tryFindRealSrcDir = do
datadir <- getDataDir
exists <- doesFileExist $ datadir </> "cabal-helper/Main.hs"
return $ if exists
then Just $ datadir </> "cabal-helper"
else Nothing
findCabalHelperSourceDir :: IO FilePath
findCabalHelperSourceDir = do
msrcdir <- runMaybeT $ MaybeT tryFindSrcDirInGhcModTree
<|> MaybeT tryFindRealSrcDir
case msrcdir of
Nothing -> getDataDir >>= errorNoMain
Just datadir -> return datadir
compileHelper :: Version -> IO (Either ExitCode FilePath)
compileHelper cabalVer = do
chdir <- findCabalHelperSourceDir
mver <- find (sameMajorVersion cabalVer) <$> listCabalVersions
couldBeSrcDir <- takeDirectory <$> getDataDir
case mver of
Nothing -> do
let cabalFile = couldBeSrcDir </> "Cabal.cabal"
cabal <- doesFileExist cabalFile
if cabal
then do
ver <- cabalFileVersion <$> readFile cabalFile
compile $ Compile chdir (Just couldBeSrcDir) ver []
else errorNoCabal cabalVer
Just ver ->
compile $ Compile chdir Nothing ver [cabalPkgId ver]
where
cabalPkgId v = "Cabal-" ++ showVersion v
errorNoCabal :: Version -> a
errorNoCabal cabalVer = error $ printf "\
\No appropriate Cabal package found, wanted version %s.\n\
\- Check output of: $ ghc-pkg list Cabal\n\
\- Maybe try: $ cabal install Cabal --constraint 'Cabal == %s.*'" sver mjver
where
sver = showVersion cabalVer
mjver = showVersion $ majorVer cabalVer
errorNoMain :: FilePath -> a
errorNoMain datadir = error $ printf "\
\Could not find $datadir/cabal-helper/Main.hs!\n\
\\n\
\If you are a developer you can use the environment variable `ghc_mod_datadir'\n\
\to override $datadir[1], `$ export ghc_mod_datadir=$PWD' will work in the\n\
\ghc-mod tree.\n\
\[1]: %s\n\
\\n\
\If you don't know what I'm talking about something went wrong with your\n\
\installation. Please report this problem here:\n\
\ https://github.com/kazu-yamamoto/ghc-mod/issues" datadir
data Compile = Compile {
cabalHelperSourceDir :: FilePath,
cabalSourceDir :: Maybe FilePath,
cabalVersion :: Version,
packageDeps :: [String]
}
compile :: Compile -> IO (Either ExitCode FilePath)
compile Compile {..} = do
outdir <- appDataDir
createDirectoryIfMissing True outdir
let exe = outdir </> "cabal-helper-" ++ showVersion (majorVer cabalVersion)
recompile <-
case cabalSourceDir of
Nothing -> do
tsrcs <- timeHsFiles cabalHelperSourceDir
texe <- timeMaybe exe
return $ any ((texe <) . Just) tsrcs
Just _ -> return True -- let ghc do the difficult recomp checking
let Version (mj:mi:_) _ = cabalVersion
let ghc_opts =
concat [
[ "-outputdir", outdir
, "-o", exe
, "-optP-DCABAL_MAJOR=" ++ show mj
, "-optP-DCABAL_MINOR=" ++ show mi
],
map ("-i"++) $ cabalHelperSourceDir:maybeToList cabalSourceDir,
concatMap (\p -> ["-package", p]) packageDeps,
[ "--make", cabalHelperSourceDir </> "Main.hs" ]
]
if recompile
then do
(_, _, _, h) <- createProcess
(proc "ghc" ghc_opts) { std_out = UseHandle stderr }
rv <- waitForProcess h
return $ case rv of
ExitSuccess -> Right exe
e@(ExitFailure _) -> Left e
else return $ Right exe
timeHsFiles :: FilePath -> IO [TimedFile]
timeHsFiles dir = do
fs <- map (dir</>) <$> getDirectoryContents dir
mapM timeFile =<< filterM isHsFile fs
where
isHsFile f = do
exists <- doesFileExist f
return $ exists && ".hs" `isSuffixOf` f
-- TODO: Include sandbox? Probably only relevant for build-type:custom projects.
listCabalVersions :: IO [Version]
listCabalVersions = do
catMaybes . map (fmap snd . parsePkgId . fromString) . words
<$> readProcess "ghc-pkg" ["list", "--simple-output", "Cabal"] ""
data Header = Header { hdrCabalVersion :: Version
, hdrCompilerVersion :: Version
}
-- | Find @version: XXX@ delcaration in a cabal file
cabalFileVersion :: String -> Version
cabalFileVersion cabalFile = do
fromJust $ parseVer . extract <$> find ("version" `isPrefixOf`) ls
where
ls = map (map toLower) $ lines cabalFile
extract = dropWhile (/=':') >>> dropWhile isSpace >>> takeWhile (not . isSpace)
parseHeader :: ByteString -> Maybe Header
parseHeader header = case BS8.words header of
["Saved", "package", "config", "for", _pkgId ,
"written", "by", cabalId,
"using", compId]
-> liftM2 Header (ver cabalId) (ver compId)
_ -> error "parsing setup-config header failed"
where
ver i = snd <$> parsePkgId i
parsePkgId :: ByteString -> Maybe (ByteString, Version)
parsePkgId bs =
case BS8.split '-' bs of
[pkg, vers] -> Just (pkg, parseVer $ BS8.unpack vers)
_ -> Nothing
parseVer :: String -> Version
parseVer vers = runReadP parseVersion vers
majorVer :: Version -> Version
majorVer (Version b _) = Version (take 2 b) []
sameMajorVersion :: Version -> Version -> Bool
sameMajorVersion a b = majorVer a == majorVer b
runReadP :: ReadP t -> String -> t
runReadP p i = let (a,""):[] = filter ((=="") . snd) $ readP_to_S p i in a

View File

@ -186,20 +186,25 @@ Executable ghc-modi
, ghc
, ghc-mod
Executable cabal-helper
Executable cabal-helper-wrapper
Default-Language: Haskell2010
Main-Is: GHCModCabal.hs
Other-Extensions: TemplateHaskell
Main-Is: Wrapper.hs
Other-Modules: Paths_ghc_mod
GHC-Options: -Wall
HS-Source-Dirs: src
HS-Source-Dirs: cabal-helper, .
X-Install-Target: $libexecdir
Build-Depends: base >= 4.0 && < 5
, bytestring
, binary
, containers
, Cabal >= 1.16
, directory
if flag(cabal-122)
Build-Depends: Cabal >= 1.22
else
Buildable: False
, filepath
, process
, transformers
, template-haskell
, time
Test-Suite doctest
Type: exitcode-stdio-1.0

View File

@ -1,28 +0,0 @@
{-# LANGUAGE BangPatterns #-}
module Main where
import Control.Applicative
import Distribution.Simple.Utils (cabalVersion)
import Distribution.Simple.Configure
import Distribution.Text ( display )
import System.Environment
import System.Directory
main :: IO ()
main = do
args <- getArgs
case args of
"version":[] -> do
putStrLn $ "using version " ++ display cabalVersion ++ " of the Cabal library"
"print-setup-config":args' -> do
mfile <- findFile ["dist"] "setup-config"
let file = case mfile of
Just f -> f
Nothing -> let !(f:[]) = args' in f
putStrLn =<< show <$> getConfigStateFile file
cmd:_ -> error $ "Unknown command: " ++ cmd
[] -> error "No command given"