diff options
| -rw-r--r-- | cabal-helper.cabal | 68 | ||||
| -rw-r--r-- | lib/Distribution/Helper.hs | 365 | ||||
| -rw-r--r-- | src/CabalHelper/Compiletime/Compile.hs | 316 | ||||
| -rw-r--r-- | src/CabalHelper/Compiletime/Log.hs | 45 | ||||
| -rw-r--r-- | src/CabalHelper/Compiletime/Types.hs | 56 | ||||
| -rw-r--r-- | src/CabalHelper/Compiletime/Wrapper.hs | 227 | ||||
| -rw-r--r-- | src/CabalHelper/Shared/Sandbox.hs | 13 | ||||
| -rw-r--r-- | tests/CompileTest.hs | 23 | 
8 files changed, 419 insertions, 694 deletions
| diff --git a/cabal-helper.cabal b/cabal-helper.cabal index 1d6bbf3..b7b5f73 100644 --- a/cabal-helper.cabal +++ b/cabal-helper.cabal @@ -88,60 +88,24 @@ library    hs-source-dirs:      lib, src    exposed-modules:     Distribution.Helper    other-modules: -                       CabalHelper.Shared.InterfaceTypes -                       CabalHelper.Shared.Sandbox -                       Paths_cabal_helper -  autogen-modules:     Paths_cabal_helper - -  ghc-options:         -Wall - -  -- well actually this is a "runtime"-tool-depends :) -  build-tool-depends:  cabal-helper:cabal-helper-wrapper - -  build-depends:       base             < 5    && >= 4.7 -  build-depends:       Cabal            < 2.5  && >= 2.0     || < 1.26 && >= 1.14 -                     , cabal-plan       < 0.5  && >= 0.3.0.0 -                     , containers       < 1    && >= 0.5.5.1 -                     , directory        < 1.4  && >= 1.2.1.0 -                     , filepath         < 1.5  && >= 1.3.0.0 -                     , transformers     < 0.6  && >= 0.3.0.0 -                     , mtl              < 2.3  && >= 2.0 -                     , process          < 1.7  && >= 1.1.0.1 -  if !os(windows) -    build-depends:     unix             < 2.8  && >= 2.5.1.1 -  build-depends:       unix-compat      < 0.6  && >= 0.4.3.1 -                     , semigroupoids    < 5.3  && >= 5.2 - - - -executable cabal-helper-wrapper -  main-is:             CabalHelper/Compiletime/Wrapper.hs -  if flag(dev) -    ghc-options:         -Wall -  scope:               private - -  -- Common c-h-wrapper-fields -- See [Note test dependencies] -  default-language:    Haskell2010 -  default-extensions:  NondecreasingIndentation -  other-extensions:    TemplateHaskell -  hs-source-dirs:      src -  other-modules:                         CabalHelper.Compiletime.Compat.Environment                         CabalHelper.Compiletime.Compat.ProgramDb                         CabalHelper.Compiletime.Compat.Version                         CabalHelper.Compiletime.Compile                         CabalHelper.Compiletime.Data -                       CabalHelper.Compiletime.Log                         CabalHelper.Compiletime.Types                         CabalHelper.Shared.Common                         CabalHelper.Shared.InterfaceTypes                         CabalHelper.Shared.Sandbox                         Paths_cabal_helper -  build-tool-depends:  cabal-install:cabal + +  autogen-modules: +                       Paths_cabal_helper + +  ghc-options:         -Wall +    build-depends:       base             < 5    && >= 4.7 -  if os(windows) -    build-depends:     base                       >= 4.7 -  build-depends:       Cabal            < 2.5  && >= 2.0 || < 1.26 && >= 1.14 +  build-depends:       Cabal            < 2.5  && >= 2.0     || < 1.26 && >= 1.14                       , cabal-plan       < 0.5  && >= 0.3.0.0                       , containers       < 1    && >= 0.5.5.1                       , bytestring       < 0.11 && >= 0.9.2.1 @@ -150,6 +114,7 @@ executable cabal-helper-wrapper                       , mtl              < 2.3  && >= 2.0                       , process          < 1.7  && >= 1.1.0.1                       , pretty-show      < 1.9  && >= 1.8.1 +                     , semigroupoids    < 5.3  && >= 5.2                       , text             < 1.3  && >= 1.0.0.0                       , template-haskell < 2.14 && >= 2.7.0.0                       , temporary        < 1.3  && >= 1.2.1 @@ -158,7 +123,9 @@ executable cabal-helper-wrapper      build-depends:     unix             < 2.8  && >= 2.5.1.1    build-depends:       unix-compat      < 0.6  && >= 0.4.3.1                       , utf8-string      < 1.1  && >= 1.0.1.1 +    build-tools:         cabal +  build-tool-depends:  cabal-install:cabal  test-suite compile-test @@ -166,9 +133,8 @@ test-suite compile-test    main-is:             CompileTest.hs    hs-source-dirs:      tests    ghc-options:         -Wall -  build-tools:         cabal -  -- Instantiate common c-h-wrapper-fields -- See [Note test dependencies] +  -- Common c-h-wrapper-fields -- See [Note test dependencies]    default-language:    Haskell2010    default-extensions:  NondecreasingIndentation    other-extensions:    TemplateHaskell @@ -179,13 +145,13 @@ test-suite compile-test                         CabalHelper.Compiletime.Compat.Version                         CabalHelper.Compiletime.Compile                         CabalHelper.Compiletime.Data -                       CabalHelper.Compiletime.Log                         CabalHelper.Compiletime.Types                         CabalHelper.Shared.Common                         CabalHelper.Shared.InterfaceTypes                         CabalHelper.Shared.Sandbox                         Paths_cabal_helper    build-tool-depends:  cabal-install:cabal +  build-tools:         cabal    build-depends:       base             < 5    && >= 4.7    if os(windows)      build-depends:     base                       >= 4.7 @@ -206,7 +172,6 @@ test-suite compile-test      build-depends:     unix             < 2.8  && >= 2.5.1.1    build-depends:       unix-compat      < 0.6  && >= 0.4.3.1                       , utf8-string      < 1.1  && >= 1.0.1.1 -  build-tools:         cabal  test-suite ghc-session @@ -230,13 +195,13 @@ test-suite ghc-session                         CabalHelper.Compiletime.Compat.Version                         CabalHelper.Compiletime.Compile                         CabalHelper.Compiletime.Data -                       CabalHelper.Compiletime.Log                         CabalHelper.Compiletime.Types                         CabalHelper.Shared.Common                         CabalHelper.Shared.InterfaceTypes                         CabalHelper.Shared.Sandbox                         Paths_cabal_helper    build-tool-depends:  cabal-install:cabal +  build-tools:         cabal    build-depends:       base             < 5    && >= 4.7    if os(windows)      build-depends:     base                       >= 4.7 @@ -257,7 +222,7 @@ test-suite ghc-session      build-depends:     unix             < 2.8  && >= 2.5.1.1    build-depends:       unix-compat      < 0.6  && >= 0.4.3.1                       , utf8-string      < 1.1  && >= 1.0.1.1 -  build-tools:         cabal +  executable cabal-helper-main @@ -269,9 +234,10 @@ executable cabal-helper-main                         CabalHelper.Shared.Common                         CabalHelper.Shared.InterfaceTypes                         CabalHelper.Shared.Sandbox +                       CabalHelper.Shared.Common -  -- This component is usually built at runtime by cabal-helper-wrapper but -  -- during development it's convinient to build it via cabal +  -- This component is usually built at runtime but during development it's +  -- convinient to build it via cabal    if flag(dev)      buildable:         True    else diff --git a/lib/Distribution/Helper.hs b/lib/Distribution/Helper.hs index 1d93b84..622972a 100644 --- a/lib/Distribution/Helper.hs +++ b/lib/Distribution/Helper.hs @@ -14,10 +14,9 @@  -- You should have received a copy of the GNU General Public License  -- along with this program.  If not, see <http://www.gnu.org/licenses/>. -{-# LANGUAGE CPP, RecordWildCards, FlexibleContexts, ConstraintKinds, +{-# LANGUAGE RecordWildCards, FlexibleContexts, ConstraintKinds,    GeneralizedNewtypeDeriving, DeriveDataTypeable, DeriveGeneric, DeriveFunctor, -  NamedFieldPuns, OverloadedStrings - #-} +  NamedFieldPuns, OverloadedStrings, ViewPatterns #-}  {-|  Module      : Distribution.Helper @@ -80,7 +79,7 @@ module Distribution.Helper (    , NeedsBuildOutput(..)    -- * General information -  , buildPlatform +  , Distribution.Helper.buildPlatform    -- * Stuff that cabal-install really should export    , Distribution.Helper.getSandboxPkgDb @@ -90,10 +89,6 @@ module Distribution.Helper (    , reconfigure    , writeAutogenFiles -  -- * $libexec related error handling -  , LibexecNotFoundError(..) -  , libexecNotFoundError -    -- * Reexports    , module Data.Functor.Apply    ) where @@ -105,46 +100,40 @@ import Control.Monad.IO.Class  import Control.Monad.State.Strict  import Control.Monad.Reader  import Control.Exception as E -import Data.Char  import Data.List  import Data.Maybe -import qualified Data.Map as Map +import qualified Data.Map.Strict as Map  import Data.Version -import Data.Typeable +import qualified Data.Text as Text  import Data.Function  import Data.Functor.Apply -import Distribution.System (buildOS, OS(Windows))  import System.Environment  import System.FilePath hiding ((<.>)) -import qualified System.FilePath as FP  import System.Directory  import System.Process -import System.IO.Unsafe  import Text.Printf -import GHC.Generics +import Text.Show.Pretty  import Prelude -import Paths_cabal_helper (getLibexecDir) + +import CabalHelper.Compiletime.Compile +import CabalHelper.Compiletime.Types  import CabalHelper.Shared.InterfaceTypes  import CabalHelper.Shared.Sandbox --- | Paths or names of various programs we need. -data Programs = Programs { -      -- | The path to the @cabal@ program. -      cabalProgram  :: FilePath, -      -- | The path to the @ghc@ program. -      ghcProgram    :: FilePath, +import Distribution.System (buildPlatform) +import Distribution.Text (display) +import Distribution.Verbosity (silent, deafening) +import Distribution.Package (packageName, packageVersion) +import Distribution.Simple.GHC as GHC (configure) + +import qualified CabalHelper.Compiletime.Compat.ProgramDb as ProgDb +    ( defaultProgramDb, programPath, lookupProgram, ghcProgram, ghcPkgProgram) +import CabalHelper.Compiletime.Compat.Version +import CabalHelper.Shared.Common -      -- | The path to the @ghc-pkg@ program. If -      -- not changed it will be derived from the path to 'ghcProgram'. -      ghcPkgProgram :: FilePath -    } deriving (Eq, Ord, Show, Read, Generic, Typeable) --- | Default all programs to their unqualified names, i.e. they will be searched --- for on @PATH@. -defaultPrograms :: Programs -defaultPrograms = Programs "cabal" "ghc" "ghc-pkg"  -- | Environment for running a 'Query'. The real constructor is not exposed,  -- the field accessors are however. See below. Use the 'mkQueryEnv' smart @@ -353,35 +342,41 @@ reconfigure readProc progs cabalOpts = do  readHelper :: (MonadIO m, MonadQuery m) => [String] -> m [Maybe ChResponse]  readHelper args = ask >>= \qe -> liftIO $ do -  out <- either error id <$> invokeHelper qe args +  out <- invokeHelper qe args    let res = read out    liftIO $ evaluate res `E.catch` \se@(SomeException _) -> do        md <- lookupEnv' "CABAL_HELPER_DEBUG"        let msg = "readHelper: exception: '" ++ show se ++ "'" -      error $ msg ++ case md of +      panicIO $ msg ++ case md of          Nothing -> ", for more information set the environment variable CABAL_HELPER_DEBUG"          Just _ -> ", output: '"++ out ++"'" -invokeHelper :: QueryEnv -> [String] -> IO (Either String String) -invokeHelper QueryEnv {..} args = do -  let progArgs = [ "--with-ghc="     ++ ghcProgram qePrograms -                 , "--with-ghc-pkg=" ++ ghcPkgProgram qePrograms -                 , "--with-cabal="   ++ cabalProgram qePrograms -                 ] -  exe  <- findLibexecExe -  let args' = progArgs ++ "v1-style":qeProjectDir:qeDistDir:args -  out <- qeReadProcess exe args' "" -  (Right <$> evaluate out) `E.catch` \(SomeException _) -> -      return $ Left $ concat -                 ["invokeHelper", ": ", exe, " " -                 , intercalate " " (map show args') -                 , " failed" -                 ] - -getPackageId :: MonadQuery m => m (String, Version) -getPackageId = ask >>= \QueryEnv {..} -> do -  [ Just (ChResponseVersion pkgName pkgVer) ] <- readHelper [ "package-id" ] -  return (pkgName, pkgVer) +invokeHelper :: QueryEnv -> [String] -> IO String +invokeHelper QueryEnv {..} args0 = do +  let opts = defaultCompileOptions +             { oPrograms = qePrograms +             , oCabalPkgDb = PackageDbDir <$> qeCabalPkgDb } + +  opts' <- overrideVerbosityEnvVar =<< guessProgramPaths opts + +  exe <- wrapperV1 opts' qeProjectDir qeDistDir + +  let args1 = qeProjectDir : qeDistDir : args0 + +  out <- qeReadProcess exe args1 "" +  evaluate out `E.catch` \(SomeException _) -> +      panicIO $ concat +        ["invokeHelper", ": ", exe, " " +        , intercalate " " (map show args1) +        , " failed!" +        ] + +getPackageId :: (MonadQuery m, MonadIO m) => m (String, Version) +getPackageId = ask >>= \QueryEnv {..} -> liftIO $ do +  let v = silent +  [cfile] <- filter isCabalFile <$> getDirectoryContents qeProjectDir +  gpd <- readPackageDescription v (qeProjectDir </> cfile) +  return $ (display (packageName gpd), toDataVersion (packageVersion gpd))  getSomeConfigState :: MonadQuery m => m SomeLocalBuildInfo  getSomeConfigState = ask >>= \QueryEnv {..} -> do @@ -439,151 +434,129 @@ writeAutogenFiles qe  =    liftIO $ void $ invokeHelper qe ["write-autogen-files"]  -- | Get the path to the sandbox package-db in a project -getSandboxPkgDb :: (FilePath -> [String] -> String -> IO String) -             -> String -             -- ^ Cabal build platform, i.e. @buildPlatform@ -             -> Version -             -- ^ GHC version (@cProjectVersion@ is your friend) -             -> IO (Maybe FilePath) -getSandboxPkgDb readProc = -    CabalHelper.Shared.Sandbox.getSandboxPkgDb $ unsafePerformIO $ buildPlatform readProc - -buildPlatform :: (FilePath -> [String] -> String -> IO String) -> IO String -buildPlatform readProc = do -  exe  <- findLibexecExe -  CabalHelper.Shared.Sandbox.dropWhileEnd isSpace <$> readProc exe ["print-build-platform"] "" - --- | This exception is thrown by all 'runQuery' functions if the internal --- wrapper executable cannot be found. You may catch this and present the user --- an appropriate error message however the default is to print --- 'libexecNotFoundError'. -data LibexecNotFoundError = LibexecNotFoundError String FilePath -                          deriving (Typeable) -instance Exception LibexecNotFoundError -instance Show LibexecNotFoundError where -  show (LibexecNotFoundError exe dir) = -    libexecNotFoundError exe dir "https://github.com/DanielG/cabal-helper/issues" - -findLibexecExe :: IO FilePath -findLibexecExe = do -    libexecdir <- getLibexecDir -    let exeName = "cabal-helper-wrapper" -        exe = libexecdir </> exeName FP.<.> exeExtension' - -    exists <- doesFileExist exe - -    if exists -       then return exe -       else do -         mdir <- tryFindCabalHelperTreeDistDir -         dir <- case mdir of -           Nothing -> -               throwIO $ LibexecNotFoundError exeName libexecdir -           Just dir -> -               return dir - -         return $ dir </> "build" </> exeName </> exeName - -findPlanJson :: FilePath -> IO (Maybe FilePath) -findPlanJson base = -    findFile (map (</> "cache") $ parents base) "plan.json" - -parents :: FilePath -> [FilePath] -parents path = takeWhile (not . (`elem` ["", "."]) . dropDrive) dirs -  where dirs = iterate takeDirectory path - -data DistDir = DistDir { ddType :: DistDirType, unDistDir :: FilePath } -  deriving (Eq, Ord, Read, Show) -data DistDirType = NewBuildDist | OldBuildDist -  deriving (Eq, Ord, Read, Show) - -tryFindCabalHelperTreeDistDir :: IO (Maybe FilePath) -tryFindCabalHelperTreeDistDir = do -  exe <- canonicalizePath =<< getExecutablePath' -  mplan <- findPlanJson exe -  let mdistdir = takeDirectory . takeDirectory <$> mplan -  cwd <- getCurrentDirectory - -  let candidates = sortBy (compare `on` ddType) $ concat -        [ maybeToList $ DistDir NewBuildDist <$> mdistdir -        , [ DistDir OldBuildDist $ (!!3) $ iterate takeDirectory exe ] -        , if takeFileName exe == "ghc" -- we're probably in ghci; try CWD -            then [ DistDir NewBuildDist $ cwd </> "dist-newstyle" -                 , DistDir NewBuildDist $ cwd </> "dist" -                 , DistDir OldBuildDist $ cwd </> "dist" -                 ] -            else [] -        ] - -  distdirs -      <-  filterM isDistDir candidates -      >>= mapM toOldBuildDistDir - -  return $ fmap unDistDir $ join $ listToMaybe $ distdirs - -isCabalHelperSourceDir :: FilePath -> IO Bool -isCabalHelperSourceDir dir = -    doesFileExist $ dir </> "cabal-helper.cabal" - -isDistDir :: DistDir -> IO Bool -isDistDir (DistDir NewBuildDist dir) = -    doesFileExist (dir </> "cache" </> "plan.json") -isDistDir (DistDir OldBuildDist dir) = -    doesFileExist (dir </> "setup-config") - -toOldBuildDistDir :: DistDir -> IO (Maybe DistDir) -toOldBuildDistDir (DistDir NewBuildDist dir) = do -    PlanJson {pjUnits} <- decodePlanJson $ dir </> "cache" </> "plan.json" -    let munit = find isCabalHelperUnit $ Map.elems pjUnits -    return $ DistDir OldBuildDist <$> join ((\Unit { uDistDir = mdistdir } -> mdistdir) <$> munit) -  where -    isCabalHelperUnit -      Unit { uPId = PkgId (PkgName n) _ -           , uType = UnitTypeLocal -           , uComps -           } | n == "cabal-helper" && -               Map.member (CompNameExe "cabal-helper-wrapper") uComps -             = True -    isCabalHelperUnit _ = False -toOldBuildDistDir x = return $ Just x - - - - -libexecNotFoundError :: String   -- ^ Name of the executable we were trying to -                                 -- find -                     -> FilePath -- ^ Path to @$libexecdir@ -                     -> String   -- ^ URL the user will be directed towards to -                                 -- report a bug. -                     -> String -libexecNotFoundError exe dir reportBug = printf - ( "Could not find $libexecdir/%s\n" - ++"\n" - ++"If you are a cabal-helper developer you can set the environment variable\n" - ++"`cabal_helper_libexecdir' to override $libexecdir[1]. The following will\n" - ++"work in the cabal-helper source tree:\n" - ++"\n" - ++"    $ export cabal_helper_libexecdir=$PWD/dist/build/%s\n" - ++"\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" - ++"\n" - ++"    %s") exe exe dir reportBug - -getExecutablePath' :: IO FilePath -getExecutablePath' = -#if MIN_VERSION_base(4,6,0) -    getExecutablePath -#else -    getProgName -#endif +getSandboxPkgDb +    :: String +    -- ^ Cabal build platform, i.e. @buildPlatform@ +    -> Version +    -- ^ GHC version (@cProjectVersion@ is your friend) +    -> FilePath +    -- ^ Path to the project directory, i.e. a directory containing a +    -- @cabal.sandbox.config@ file +    -> IO (Maybe FilePath) +getSandboxPkgDb buildPlat ghc_ver projdir = +    CabalHelper.Shared.Sandbox.getSandboxPkgDb buildPlat ghc_ver projdir + +buildPlatform :: String +buildPlatform = display Distribution.System.buildPlatform  lookupEnv' :: String -> IO (Maybe String)  lookupEnv' k = lookup k <$> getEnvironment -exeExtension' :: FilePath -exeExtension' -    | Windows <- buildOS = "exe" -    | otherwise = "" + +guessProgramPaths :: CompileOptions -> IO CompileOptions +guessProgramPaths opts = do +    let v | oVerbose opts = deafening +          | otherwise     = silent + +        mGhcPath0    | same ghcProgram progs dprogs = Nothing +                     | otherwise = Just $ ghcProgram progs +        mGhcPkgPath0 | same ghcPkgProgram progs dprogs = Nothing +                     | otherwise = Just $ ghcPkgProgram progs + +    (_compiler, _mplatform, progdb) +        <- GHC.configure +               v +               mGhcPath0 +               mGhcPkgPath0 +               ProgDb.defaultProgramDb +    let getProg p = ProgDb.programPath <$> ProgDb.lookupProgram p progdb +        mghcPath1    = getProg ProgDb.ghcProgram +        mghcPkgPath1 = getProg ProgDb.ghcPkgProgram + +        progs' = progs +          { ghcProgram    = fromMaybe (ghcProgram progs) mghcPath1 +          , ghcPkgProgram = fromMaybe (ghcProgram progs) mghcPkgPath1 +          } +    return opts { oPrograms = progs' } + where +   same f o o'  = f o == f o' +   progs = oPrograms opts +   dprogs = defaultPrograms + +overrideVerbosityEnvVar :: CompileOptions -> IO CompileOptions +overrideVerbosityEnvVar opts = do +  x <- lookup  "CABAL_HELPER_DEBUG" <$> getEnvironment +  return $ case x of +    Just _  -> opts { oVerbose = True } +    Nothing -> opts + +wrapperV1 +    :: CompileOptions +    -> FilePath +    -> FilePath +    -> IO FilePath +wrapperV1 opts projdir distdir = do +  cfgf <- canonicalizePath (distdir </> "setup-config") +  mhdr <- getCabalConfigHeader cfgf +  case (mhdr, oCabalVersion opts) of +    (Nothing, _) -> panicIO $ printf "\ +\Could not read Cabal's persistent setup configuration header\n\ +\- Check first line of: %s\n\ +\- Maybe try: $ cabal configure" cfgf +    (Just (hdrCabalVersion, _), Just ver) +      | hdrCabalVersion /= ver -> panicIO $ printf "\ +\Cabal version %s was requested but setup configuration was\n\ +\written by version %s" (showVersion ver) (showVersion hdrCabalVersion) +    (Just (hdrCabalVersion, _), _) -> do +      compileHelper' opts hdrCabalVersion projdir Nothing distdir + +wrapperV2 +    :: CompileOptions +    -> FilePath +    -> FilePath +    -> UnitId +    -> IO (FilePath, FilePath) +wrapperV2 opts projdir distdir unitid@(UnitId (Text.unpack -> unitid')) = do +  let plan_path = distdir </> "cache" </> "plan.json" +  plan@PlanJson {pjCabalLibVersion=Ver (makeDataVersion -> pjCabalLibVersion) } +      <- decodePlanJson plan_path +  case oCabalVersion opts of +    Just ver | pjCabalLibVersion /= ver -> let +        sver = showVersion ver +        spjVer = showVersion pjCabalLibVersion +      in panicIO $ printf "\ +\Cabal version %s was requested but plan.json was written by version %s" sver spjVer +    _ -> case Map.lookup unitid $ pjUnits plan of +      Just u@Unit {uType} | uType /= UnitTypeLocal -> do +        panicIO $ "\ +\UnitId '"++ unitid' ++"' points to non-local unit: " ++ ppShow u +      Just Unit {uDistDir=Nothing} -> panicIO $ printf "\ +\plan.json doesn't contain 'dist-dir' for UnitId '"++ unitid' ++"'" +      Just Unit {uType=UnitTypeLocal, uDistDir=Just distdirv1} -> do +        exe <- compileHelper' opts pjCabalLibVersion projdir (Just (plan, distdir)) distdirv1 +        return (exe, distdirv1) +      _ -> let +          units = map (\(UnitId u) -> Text.unpack u) +                $ Map.keys +                $ Map.filter ((==UnitTypeLocal) . uType) +                $ pjUnits plan +          units_list = unlines $ map ("  "++) units +        in +          panicIO $ "\ +\UnitId '"++ unitid' ++"' not found in plan.json, available local units:\n" ++ units_list + + +compileHelper' +    :: CompileOptions +    -> Version +    -> FilePath +    -> Maybe (PlanJson, FilePath) +    -> FilePath +    -> IO FilePath +compileHelper' opts pjCabalLibVersion projdir mnewstyle distdirv1 = do +  eexe <- compileHelper opts pjCabalLibVersion projdir mnewstyle distdirv1 +  case eexe of +    Left rv -> +        panicIO $ "compileHelper': compiling helper failed! (exit code "++ show rv +    Right exe -> +        return exe diff --git a/src/CabalHelper/Compiletime/Compile.hs b/src/CabalHelper/Compiletime/Compile.hs index 8da426f..2b80b2f 100644 --- a/src/CabalHelper/Compiletime/Compile.hs +++ b/src/CabalHelper/Compiletime/Compile.hs @@ -14,7 +14,7 @@  -- You should have received a copy of the GNU General Public License  -- along with this program.  If not, see <http://www.gnu.org/licenses/>.  {-# LANGUAGE RecordWildCards, FlexibleContexts, NamedFieldPuns, DeriveFunctor, -GADTs #-} +  GADTs, ImplicitParams, ConstraintKinds #-}  {-|  Module      : CabalHelper.Compiletime.Compile @@ -58,7 +58,6 @@ import Distribution.Text (display)  import Paths_cabal_helper (version)  import CabalHelper.Compiletime.Data -import CabalHelper.Compiletime.Log  import CabalHelper.Compiletime.Types  import CabalHelper.Shared.Common  import CabalHelper.Shared.Sandbox (getSandboxPkgDb) @@ -87,33 +86,41 @@ data CompPaths = CompPaths  -- executable.  data CompilationProductScope = CPSGlobal | CPSProject -compileHelper :: Options -> Version -> FilePath -> Maybe (PlanJson, FilePath) -> FilePath -> IO (Either ExitCode FilePath) +compileHelper +    :: CompileOptions +    -> Version +    -> FilePath +    -> Maybe (PlanJson, FilePath) +    -> FilePath +    -> IO (Either ExitCode FilePath)  compileHelper opts hdrCabalVersion projdir mnewstyle distdir = do -  ghcVer <- ghcVersion opts -  Just (prepare, comp) <- runMaybeT $ msum $ -    case oCabalPkgDb opts of -      Nothing -> -        [ compileCabalSource -        , compileNewBuild ghcVer -        , compileSandbox ghcVer -        , compileGlobal -        , MaybeT $ Just <$> compileWithCabalInPrivatePkgDb -        ] -      Just db -> -          [ return $ (return (), compileWithPkg (Just db) hdrCabalVersion CPSProject) -        ] - -  appdir <- appCacheDir - -  let cp@CompPaths {compExePath} = compPaths appdir distdir comp -  exists <- doesFileExist compExePath -  if exists -    then do -      vLog opts $ "helper already compiled, using exe: "++compExePath -      return (Right compExePath) -    else do -      vLog opts $ "helper exe does not exist, compiling "++compExePath -      prepare >> compile comp cp opts +    let ?opts = opts + +    ghcVer <- ghcVersion +    Just (prepare, comp) <- runMaybeT $ msum $ +      case oCabalPkgDb opts of +        Nothing -> +          [ compileCabalSource +          , compileNewBuild ghcVer +          , compileSandbox ghcVer +          , compileGlobal +          , MaybeT $ Just <$> compileWithCabalInPrivatePkgDb +          ] +        Just db -> +            [ return $ (return (), compileWithPkg (Just db) hdrCabalVersion CPSProject) +          ] + +    appdir <- appCacheDir + +    let cp@CompPaths {compExePath} = compPaths appdir distdir comp +    exists <- doesFileExist compExePath +    if exists +      then do +        vLog $ "helper already compiled, using exe: "++compExePath +        return (Right compExePath) +      else do +        vLog $ "helper exe does not exist, compiling "++compExePath +        prepare >> compile comp cp   where     logMsg = "using helper compiled with Cabal from " @@ -121,24 +128,24 @@ compileHelper opts hdrCabalVersion projdir mnewstyle distdir = do  -- for relaxed deps: find (sameMajorVersionAs hdrCabalVersion) . reverse . sort     -- | Check if this version is globally available -   compileGlobal :: MaybeT IO (IO (), Compile) +   compileGlobal :: Env => MaybeT IO (IO (), Compile)     compileGlobal = do -       cabal_versions <- listCabalVersions opts +       cabal_versions <- listCabalVersions         ver <- MaybeT $ return $ find (== hdrCabalVersion) cabal_versions -       vLog opts $ logMsg ++ "user/global package-db" +       vLog $ logMsg ++ "user/global package-db"         return $ (return (), compileWithPkg Nothing ver CPSGlobal)     -- | Check if this version is available in the project sandbox -   compileSandbox :: Version -> MaybeT IO (IO (), Compile) +   compileSandbox :: Env => Version -> MaybeT IO (IO (), Compile)     compileSandbox ghcVer = do -       let mdb_path = getSandboxPkgDb projdir (display buildPlatform) ghcVer +       let mdb_path = getSandboxPkgDb (display buildPlatform) ghcVer projdir         sandbox <- PackageDbDir <$> MaybeT mdb_path -       cabal_versions <- listCabalVersions' opts (Just sandbox) +       cabal_versions <- listCabalVersions' (Just sandbox)         ver <- MaybeT $ return $ find (== hdrCabalVersion) cabal_versions -       vLog opts $ logMsg ++ "sandbox package-db" +       vLog $ logMsg ++ "sandbox package-db"         return $ (return (), compileWithPkg (Just sandbox) ver CPSProject) -   compileNewBuild :: Version -> MaybeT IO (IO (), Compile) +   compileNewBuild :: Env => Version -> MaybeT IO (IO (), Compile)     compileNewBuild ghcVer = do         (PlanJson {pjUnits}, distdir_newstyle) <- maybe mzero pure mnewstyle         let cabal_pkgid = @@ -150,28 +157,28 @@ compileHelper opts hdrCabalVersion projdir mnewstyle distdir = do         let inplace_db_path = distdir_newstyle               </> "packagedb" </> ("ghc-" ++ showVersion ghcVer)             inplace_db = PackageDbDir inplace_db_path -       cabal_versions <- listCabalVersions' opts (Just inplace_db) +       cabal_versions <- listCabalVersions' (Just inplace_db)         ver <- MaybeT $ return $ find (== hdrCabalVersion) cabal_versions -       vLog opts $ logMsg ++ "v2-build package-db " ++ inplace_db_path +       vLog $ logMsg ++ "v2-build package-db " ++ inplace_db_path         return $ (return (), compileWithPkg (Just inplace_db) ver CPSProject)     -- | Compile the requested Cabal version into an isolated package-db if it's     -- not there already -   compileWithCabalInPrivatePkgDb :: IO (IO (), Compile) +   compileWithCabalInPrivatePkgDb :: Env => IO (IO (), Compile)     compileWithCabalInPrivatePkgDb = do         db@(PackageDbDir db_path) -           <- getPrivateCabalPkgDb opts (CabalVersion hdrCabalVersion) -       vLog opts $ logMsg ++ "private package-db in " ++ db_path +           <- getPrivateCabalPkgDb (CabalVersion hdrCabalVersion) +       vLog $ logMsg ++ "private package-db in " ++ db_path         return (prepare db, compileWithPkg (Just db) hdrCabalVersion CPSGlobal)       where         prepare db = do -         db_exists <- liftIO $ cabalVersionExistsInPkgDb opts hdrCabalVersion db +         db_exists <- liftIO $ cabalVersionExistsInPkgDb hdrCabalVersion db           when (not db_exists) $ -           void $ installCabal opts (Right hdrCabalVersion) `E.catch` +           void $ installCabal (Right hdrCabalVersion) `E.catch`               \(SomeException _) -> errorInstallCabal hdrCabalVersion distdir     -- | See if we're in a cabal source tree -   compileCabalSource :: MaybeT IO (IO (), Compile) +   compileCabalSource :: Env => MaybeT IO (IO (), Compile)     compileCabalSource = do         let cabalFile = projdir </> "Cabal.cabal"         cabalSrc <- liftIO $ doesFileExist cabalFile @@ -179,17 +186,17 @@ compileHelper opts hdrCabalVersion projdir mnewstyle distdir = do         case cabalSrc of           False -> mzero           True -> do -           vLog opts $ "projdir looks like Cabal source tree (Cabal.cabal exists)" +           vLog $ "projdir looks like Cabal source tree (Cabal.cabal exists)"             cf <- liftIO $ readFile cabalFile             let buildType = cabalFileBuildType cf                 ver       = cabalFileVersion cf             case buildType of               "simple" -> do -                 vLog opts $ "Cabal source tree is build-type:simple, moving on" +                 vLog $ "Cabal source tree is build-type:simple, moving on"                   mzero               "custom" -> do -                 vLog opts $ "compiling helper with local Cabal source tree" +                 vLog $ "compiling helper with local Cabal source tree"                   return $ (return (), compileWithCabalSource projdir' ver)               _ -> error $ "compileCabalSource: unknown build-type: '"++buildType++"'" @@ -209,16 +216,16 @@ compileHelper opts hdrCabalVersion projdir mnewstyle distdir = do     cabalPkgId v = "Cabal-" ++ showVersion v -compile :: Compile -> CompPaths -> Options -> IO (Either ExitCode FilePath) -compile comp paths@CompPaths {..} opts@Options {..} = do +compile :: Env => Compile -> CompPaths -> IO (Either ExitCode FilePath) +compile comp paths@CompPaths {..} = do      createDirectoryIfMissing True compOutDir      createHelperSources compSrcDir -    vLog opts $ "compSrcDir: " ++ compSrcDir -    vLog opts $ "compOutDir: " ++ compOutDir -    vLog opts $ "compExePath: " ++ compExePath +    vLog $ "compSrcDir: " ++ compSrcDir +    vLog $ "compOutDir: " ++ compOutDir +    vLog $ "compExePath: " ++ compExePath -    invokeGhc opts $ compGhcInvocation comp paths +    invokeGhc $ compGhcInvocation comp paths  compPaths :: FilePath -> FilePath -> Compile -> CompPaths  compPaths appdir distdir c = @@ -309,25 +316,27 @@ cabalMinVersionMacro (Version (mj1:mj2:mi:_) _) =  cabalMinVersionMacro _ =      error "cabalMinVersionMacro: Version must have at least 3 components" -invokeGhc :: Options -> GhcInvocation -> IO (Either ExitCode FilePath) -invokeGhc opts@Options {..} GhcInvocation {..} = do -  rv <- callProcessStderr' opts Nothing oGhcProgram $ concat -    [ [ "-outputdir", giOutDir -      , "-o", giOutput +invokeGhc :: Env => GhcInvocation -> IO (Either ExitCode FilePath) +invokeGhc GhcInvocation {..} = do +    rv <- callProcessStderr' Nothing oGhcProgram $ concat +      [ [ "-outputdir", giOutDir +        , "-o", giOutput +        ] +      , map ("-optP"++) giCPPOptions +      , map ("-package-conf="++) $ unPackageDbDir <$> giPackageDBs +      , map ("-i"++) $ nub $ "" : giIncludeDirs +      , if giHideAllPackages then ["-hide-all-packages"] else [] +      , concatMap (\p -> ["-package", p]) giPackages +      , giWarningFlags +      , ["--make"] +      , giInputs        ] -    , map ("-optP"++) giCPPOptions -    , map ("-package-conf="++) $ unPackageDbDir <$> giPackageDBs -    , map ("-i"++) $ nub $ "" : giIncludeDirs -    , if giHideAllPackages then ["-hide-all-packages"] else [] -    , concatMap (\p -> ["-package", p]) giPackages -    , giWarningFlags -    , ["--make"] -    , giInputs -    ] -  return $ -    case rv of -      ExitSuccess -> Right giOutput -      e@(ExitFailure _) -> Left e +    return $ +      case rv of +        ExitSuccess -> Right giOutput +        e@(ExitFailure _) -> Left e +  where +    CompileOptions {..} = ?opts  -- | Cabal library version we're compiling the helper exe against. @@ -347,26 +356,26 @@ exeName CabalVersion {cabalVersion} = intercalate "-"      , "Cabal" ++ showVersion cabalVersion      ] -readProcess' :: Options -> FilePath -> [String] -> String -> IO String -readProcess' opts@Options{..} exe args inp = do -  vLog opts $ intercalate " " $ map formatProcessArg (oGhcPkgProgram:args) +readProcess' :: Env => FilePath -> [String] -> String -> IO String +readProcess' exe args inp = do +  vLog $ intercalate " " $ map formatProcessArg (exe:args)    outp <- readProcess exe args inp -  vLog opts $ unlines $ map ("=> "++) $ lines outp +  vLog $ unlines $ map ("=> "++) $ lines outp    return outp  callProcessStderr' -    :: Options -> Maybe FilePath -> FilePath -> [String] -> IO ExitCode -callProcessStderr' opts mwd exe args = do +    :: Env => Maybe FilePath -> FilePath -> [String] -> IO ExitCode +callProcessStderr' mwd exe args = do    let cd = case mwd of               Nothing -> []; Just wd -> [ "cd", formatProcessArg wd++";" ] -  vLog opts $ intercalate " " $ cd ++ map formatProcessArg (exe:args) +  vLog $ intercalate " " $ cd ++ map formatProcessArg (exe:args)    (_, _, _, h) <- createProcess (proc exe args) { std_out = UseHandle stderr                                                  , cwd = mwd }    waitForProcess h -callProcessStderr :: Options -> Maybe FilePath -> FilePath -> [String] -> IO () -callProcessStderr opts mwd exe args = do -  rv <- callProcessStderr' opts mwd exe args +callProcessStderr :: Env => Maybe FilePath -> FilePath -> [String] -> IO () +callProcessStderr mwd exe args = do +  rv <- callProcessStderr' mwd exe args    case rv of      ExitSuccess -> return ()      ExitFailure v -> processFailedException "callProcessStderr" exe args v @@ -387,8 +396,8 @@ formatProcessArg xs  data HEAD = HEAD deriving (Eq, Show) -installCabal :: Options -> Either HEAD Version -> IO (PackageDbDir, CabalVersion) -installCabal opts ever = do +installCabal :: Env => Either HEAD Version -> IO (PackageDbDir, CabalVersion) +installCabal ever = do    appdir <- appCacheDir    let message ver = do        let sver = showVersion ver @@ -409,16 +418,16 @@ installCabal opts ever = do    withSystemTempDirectory "cabal-helper-Cabal-source" $ \tmpdir -> do      (srcdir, cabalVer) <- case ever of        Left HEAD -> do -        second CabalHEAD <$> unpackCabalHEAD opts tmpdir +        second CabalHEAD <$> unpackCabalHEAD tmpdir        Right ver -> do          message ver          let patch = fromMaybe nopCabalPatchDescription $                find ((ver`elem`) . cpdVersions) patchyCabalVersions -        (,) <$> unpackPatchedCabal opts ver tmpdir patch <*> pure (CabalVersion ver) +        (,) <$> unpackPatchedCabal ver tmpdir patch <*> pure (CabalVersion ver) -    db <- createPkgDb opts cabalVer +    db <- createPkgDb cabalVer -    runCabalInstall opts db srcdir ever +    runCabalInstall db srcdir ever      return (db, cabalVer) @@ -436,9 +445,9 @@ Otherwise we might be able to use the shipped Setup.hs  -}  runCabalInstall -    :: Options -> PackageDbDir -> CabalSourceDir -> Either HEAD Version-> IO () -runCabalInstall opts (PackageDbDir db) (CabalSourceDir srcdir) ever = do -  civ@CabalInstallVersion {..} <- cabalInstallVersion opts +    :: Env => PackageDbDir -> CabalSourceDir -> Either HEAD Version-> IO () +runCabalInstall (PackageDbDir db) (CabalSourceDir srcdir) ever = do +  civ@CabalInstallVersion {..} <- cabalInstallVersion    cabal_opts <- return $ concat        [          [ "--package-db=clear" @@ -446,45 +455,45 @@ runCabalInstall opts (PackageDbDir db) (CabalSourceDir srcdir) ever = do          , "--package-db=" ++ db          , "--prefix=" ++ db </> "prefix"          ] -        , withGHCProgramOptions opts +        , withGHCProgramOptions          , if cabalInstallVer >= Version [1,20,0,0] []               then ["--no-require-sandbox"]               else []          , [ "install", srcdir ] -        , if oVerbose opts +        , if oVerbose ?opts              then ["-v"]              else []          , [ "--only-dependencies" ]        ] -  callProcessStderr opts (Just "/") (oCabalProgram opts) cabal_opts +  callProcessStderr (Just "/") oCabalProgram cabal_opts -  runSetupHs opts db srcdir ever civ +  runSetupHs db srcdir ever civ    hPutStrLn stderr "done" -withGHCProgramOptions :: Options -> [String] -withGHCProgramOptions opts = -    concat [ [ "--with-ghc=" ++ oGhcProgram opts ] -           , if oGhcPkgProgram opts /= oGhcPkgProgram defaultOptions -               then [ "--with-ghc-pkg=" ++ oGhcPkgProgram opts ] +withGHCProgramOptions :: Env => [String] +withGHCProgramOptions = +    concat [ [ "--with-ghc=" ++ oGhcProgram ] +           , if oGhcProgram /= ghcPkgProgram defaultPrograms +               then [ "--with-ghc-pkg=" ++ oGhcPkgProgram ]                 else []             ]  runSetupHs -    :: Options -    -> FilePath +    :: Env +    => FilePath      -> FilePath      -> Either HEAD Version      -> CabalInstallVersion      -> IO () -runSetupHs opts@Options {..} db srcdir ever CabalInstallVersion {..} +runSetupHs db srcdir ever CabalInstallVersion {..}      | cabalInstallVer >= parseVer "1.24" = do -      go $ \args -> callProcessStderr opts (Just srcdir) oCabalProgram $ +      go $ \args -> callProcessStderr (Just srcdir) oCabalProgram $          [ "act-as-setup", "--" ] ++ args      | otherwise = do -      SetupProgram {..} <- compileSetupHs opts db srcdir -      go $ callProcessStderr opts (Just srcdir) setupProgram +      SetupProgram {..} <- compileSetupHs db srcdir +      go $ callProcessStderr (Just srcdir) setupProgram    where      parmake_opt :: Maybe Int -> [String]      parmake_opt nproc' @@ -497,7 +506,7 @@ runSetupHs opts@Options {..} db srcdir ever CabalInstallVersion {..}      go :: ([String] -> IO ()) -> IO ()      go run = do        run $ [ "configure", "--package-db", db, "--prefix", db </> "prefix" ] -              ++ withGHCProgramOptions opts +              ++ withGHCProgramOptions        mnproc <- join . fmap readMaybe <$> lookupEnv "NPROC"        run $ [ "build" ] ++ parmake_opt mnproc        run [ "copy" ] @@ -507,16 +516,16 @@ runSetupHs opts@Options {..} db srcdir ever CabalInstallVersion {..}  newtype SetupProgram = SetupProgram { setupProgram :: FilePath } -compileSetupHs :: Options -> FilePath -> FilePath -> IO SetupProgram -compileSetupHs opts db srcdir = do -  ver <- ghcVersion opts +compileSetupHs :: Env => FilePath -> FilePath -> IO SetupProgram +compileSetupHs db srcdir = do +  ver <- ghcVersion    let no_version_macros          | ver >= Version [8] [] = [ "-fno-version-macros" ]          | otherwise             = []        file = srcdir </> "Setup" -  callProcessStderr opts (Just srcdir) (oGhcProgram opts) $ concat +  callProcessStderr (Just srcdir) oGhcProgram $ concat      [ [ "--make"        , "-package-conf", db        ] @@ -588,35 +597,35 @@ patchyCabalVersions = [       renameFile versionFileTmp versionFile  unpackPatchedCabal -    :: Options -    -> Version +    :: Env +    => Version      -> FilePath      -> CabalPatchDescription      -> IO CabalSourceDir -unpackPatchedCabal opts cabalVer tmpdir (CabalPatchDescription _ variant patch) = do -  res@(CabalSourceDir dir) <- unpackCabal opts cabalVer tmpdir variant +unpackPatchedCabal cabalVer tmpdir (CabalPatchDescription _ variant patch) = do +  res@(CabalSourceDir dir) <- unpackCabal cabalVer tmpdir variant    patch dir    return res  data UnpackCabalVariant = Pristine | LatestRevision  newtype CabalSourceDir = CabalSourceDir { unCabalSourceDir :: FilePath }  unpackCabal -    :: Options -> Version -> FilePath -> UnpackCabalVariant -> IO CabalSourceDir -unpackCabal opts cabalVer tmpdir variant = do +    :: Env => Version -> FilePath -> UnpackCabalVariant -> IO CabalSourceDir +unpackCabal cabalVer tmpdir variant = do    let cabal = "Cabal-" ++ showVersion cabalVer        dir = tmpdir </> cabal        variant_opts = case variant of Pristine -> [ "--pristine" ]; _ -> []        args = [ "get", cabal ] ++ variant_opts -  callProcessStderr opts (Just tmpdir) (oCabalProgram opts) args +  callProcessStderr (Just tmpdir) oCabalProgram args    return $ CabalSourceDir dir -unpackCabalHEAD :: Options -> FilePath -> IO (CabalSourceDir, CommitId) -unpackCabalHEAD opts tmpdir = do +unpackCabalHEAD :: Env => FilePath -> IO (CabalSourceDir, CommitId) +unpackCabalHEAD tmpdir = do    let dir = tmpdir </> "cabal-head.git"        url = "https://github.com/haskell/cabal.git"    ExitSuccess <- rawSystem "git" [ "clone", "--depth=1", url, dir]    commit <- -      withDirectory_ dir $ trim <$> readProcess' opts "git" ["rev-parse", "HEAD"] "" +      withDirectory_ dir $ trim <$> readProcess' "git" ["rev-parse", "HEAD"] ""    return (CabalSourceDir $ dir </> "Cabal", CommitId commit)   where     withDirectory_ :: FilePath -> IO a -> IO a @@ -661,58 +670,60 @@ errorInstallCabal cabalVer _distdir = panicIO $ printf "\   where     sver = showVersion cabalVer -listCabalVersions :: Options -> MaybeT IO [Version] -listCabalVersions opts = listCabalVersions' opts Nothing +listCabalVersions :: Env => MaybeT IO [Version] +listCabalVersions = listCabalVersions' Nothing -listCabalVersions' :: Options -> Maybe PackageDbDir -> MaybeT IO [Version] -listCabalVersions' opts@Options {..} mdb = do +listCabalVersions' :: Env => Maybe PackageDbDir -> MaybeT IO [Version] +listCabalVersions' mdb = do    case mdb of      Nothing -> mzero      Just (PackageDbDir db_path) -> do        exists <- liftIO $ doesDirectoryExist db_path        case exists of          False -> mzero -        True  -> MaybeT $ logIOError opts "listCabalVersions'" $ Just <$> do +        True  -> MaybeT $ logIOError "listCabalVersions'" $ Just <$> do            let mdbopt = ("--package-conf="++) <$> unPackageDbDir <$> mdb                args = ["list", "--simple-output", "Cabal"] ++ maybeToList mdbopt            catMaybes . map (fmap snd . parsePkgId . fromString) . words -                   <$> readProcess' opts oGhcPkgProgram args "" +                   <$> readProcess' oGhcPkgProgram args "" -cabalVersionExistsInPkgDb :: Options -> Version -> PackageDbDir -> IO Bool -cabalVersionExistsInPkgDb opts cabalVer db@(PackageDbDir db_path) = do +cabalVersionExistsInPkgDb :: Env => Version -> PackageDbDir -> IO Bool +cabalVersionExistsInPkgDb cabalVer db@(PackageDbDir db_path) = do    exists <- doesDirectoryExist db_path    case exists of      False -> return False      True -> fromMaybe False <$> runMaybeT (do -      vers <- listCabalVersions' opts (Just db) +      vers <- listCabalVersions' (Just db)        return $ cabalVer `elem` vers) -ghcVersion :: Options -> IO Version -ghcVersion opts@Options {..} = do -    parseVer . trim <$> readProcess' opts oGhcProgram ["--numeric-version"] "" +ghcVersion :: Env => IO Version +ghcVersion = do +    parseVer . trim <$> readProcess' oGhcProgram ["--numeric-version"] "" -ghcPkgVersion :: Options -> IO Version -ghcPkgVersion opts@Options {..} = do -    parseVer . trim . dropWhile (not . isDigit) <$> readProcess' opts oGhcPkgProgram ["--version"] "" +ghcPkgVersion :: Env => IO Version +ghcPkgVersion = +  parseVer . trim . dropWhile (not . isDigit) +    <$> readProcess' oGhcPkgProgram ["--version"] ""  newtype CabalInstallVersion = CabalInstallVersion { cabalInstallVer :: Version } -cabalInstallVersion :: Options -> IO CabalInstallVersion -cabalInstallVersion opts@Options {..} = do -    CabalInstallVersion . parseVer . trim -      <$> readProcess' opts oCabalProgram ["--numeric-version"] "" - -createPkgDb :: Options -> CabalVersion -> IO PackageDbDir -createPkgDb opts@Options {..} cabalVer = do -  db@(PackageDbDir db_path) <- getPrivateCabalPkgDb opts cabalVer +cabalInstallVersion :: Env => IO CabalInstallVersion +cabalInstallVersion = do +  CabalInstallVersion . parseVer . trim +    <$> readProcess' oCabalProgram ["--numeric-version"] "" + +createPkgDb :: Env => CabalVersion -> IO PackageDbDir +createPkgDb cabalVer = do +  db@(PackageDbDir db_path) <- getPrivateCabalPkgDb cabalVer    exists <- doesDirectoryExist db_path -  when (not exists) $ callProcessStderr opts Nothing oGhcPkgProgram ["init", db_path] +  when (not exists) $ +       callProcessStderr Nothing oGhcPkgProgram ["init", db_path]    return db -getPrivateCabalPkgDb :: Options -> CabalVersion -> IO PackageDbDir -getPrivateCabalPkgDb opts cabalVer = do +getPrivateCabalPkgDb :: Env => CabalVersion -> IO PackageDbDir +getPrivateCabalPkgDb cabalVer = do    appdir <- appCacheDir -  ghcVer <- ghcVersion opts +  ghcVer <- ghcVersion    let db_path = appdir </> exeName cabalVer                  ++ "-ghc" ++ showVersion ghcVer                  ++ ".package-db" @@ -734,3 +745,14 @@ cabalFileTopField field cabalFile = value    Just value = extract <$> find ((field++":") `isPrefixOf`) ls    ls = map (map toLower) $ lines cabalFile    extract = dropWhile (/=':') >>> drop 1 >>> dropWhile isSpace >>> takeWhile (not . isSpace) + +vLog :: (Env, MonadIO m) => String -> m () +vLog msg | CompileOptions { oVerbose = True } <- ?opts = +    liftIO $ hPutStrLn stderr msg +vLog _ = return () + +logIOError :: Env => String -> IO (Maybe a) -> IO (Maybe a) +logIOError label a = do +  a `catchIOError` \ex -> do +      vLog $ label ++ ": " ++ show ex +      return Nothing diff --git a/src/CabalHelper/Compiletime/Log.hs b/src/CabalHelper/Compiletime/Log.hs deleted file mode 100644 index a329c54..0000000 --- a/src/CabalHelper/Compiletime/Log.hs +++ /dev/null @@ -1,45 +0,0 @@ --- cabal-helper: Simple interface to Cabal's configuration state --- Copyright (C) 2017-2018  Daniel Gröber <cabal-helper@dxld.at> --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU 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 General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program.  If not, see <http://www.gnu.org/licenses/>. - -{-# LANGUAGE ScopedTypeVariables #-} - -{-| -Module      : CabalHelper.Compiletime.Log -Description : Basic logging facilities -License     : GPL-3 --} - -module CabalHelper.Compiletime.Log where - -import Control.Monad -import Control.Monad.IO.Class -import Control.Exception as E -import Data.String -import System.IO -import Prelude - -import CabalHelper.Compiletime.Types - -vLog :: MonadIO m => Options -> String -> m () -vLog Options { oVerbose = True } msg = -    liftIO $ hPutStrLn stderr msg -vLog _ _ = return () - -logIOError :: Options -> String -> IO (Maybe a) -> IO (Maybe a) -logIOError opts label a = do -  a `E.catch` \(ex :: IOError) -> do -      vLog opts $ label ++ ": " ++ show ex -      return Nothing diff --git a/src/CabalHelper/Compiletime/Types.hs b/src/CabalHelper/Compiletime/Types.hs index 77c3255..10fe916 100644 --- a/src/CabalHelper/Compiletime/Types.hs +++ b/src/CabalHelper/Compiletime/Types.hs @@ -14,7 +14,8 @@  -- You should have received a copy of the GNU General Public License  -- along with this program.  If not, see <http://www.gnu.org/licenses/>. -{-# LANGUAGE DeriveGeneric, DeriveDataTypeable, DefaultSignatures #-} +{-# LANGUAGE DeriveGeneric, DeriveDataTypeable, DefaultSignatures, +  KindSignatures, ImplicitParams, ConstraintKinds #-}  {-|  Module      : CabalHelper.Compiletime.Types @@ -25,18 +26,47 @@ License     : GPL-3  module CabalHelper.Compiletime.Types where  import Data.Version +import Data.Typeable +import GHC.Generics -data Options = Options { -          oHelp          :: Bool -        , oVerbose       :: Bool -        , oGhcProgram    :: FilePath -        , oGhcPkgProgram :: FilePath -        , oCabalProgram  :: FilePath -        , oCabalVersion  :: Maybe Version -        , oCabalPkgDb    :: Maybe PackageDbDir -} +type Env = (?opts :: CompileOptions) -newtype PackageDbDir = PackageDbDir { unPackageDbDir :: FilePath } +-- | Paths or names of various programs we need. +data Programs = Programs { +      -- | The path to the @cabal@ program. +      cabalProgram  :: FilePath, + +      -- | The path to the @ghc@ program. +      ghcProgram    :: FilePath, + +      -- | The path to the @ghc-pkg@ program. If +      -- not changed it will be derived from the path to 'ghcProgram'. +      ghcPkgProgram :: FilePath +    } deriving (Eq, Ord, Show, Read, Generic, Typeable) + +-- | Default all programs to their unqualified names, i.e. they will be searched +-- for on @PATH@. +defaultPrograms :: Programs +defaultPrograms = Programs "cabal" "ghc" "ghc-pkg" + +data CompileOptions = CompileOptions +    { oVerbose       :: Bool +    , oCabalPkgDb    :: Maybe PackageDbDir +    , oCabalVersion  :: Maybe Version +    , oPrograms      :: Programs +    } -defaultOptions :: Options -defaultOptions = Options False False "ghc" "ghc-pkg" "cabal" Nothing Nothing +oCabalProgram :: Env => FilePath +oCabalProgram = cabalProgram $ oPrograms ?opts + +oGhcProgram :: Env => FilePath +oGhcProgram = ghcProgram $ oPrograms ?opts + +oGhcPkgProgram :: Env => FilePath +oGhcPkgProgram = ghcPkgProgram $ oPrograms ?opts + +defaultCompileOptions :: CompileOptions +defaultCompileOptions = +    CompileOptions False Nothing Nothing defaultPrograms + +newtype PackageDbDir = PackageDbDir { unPackageDbDir :: FilePath } diff --git a/src/CabalHelper/Compiletime/Wrapper.hs b/src/CabalHelper/Compiletime/Wrapper.hs deleted file mode 100644 index 461ef96..0000000 --- a/src/CabalHelper/Compiletime/Wrapper.hs +++ /dev/null @@ -1,227 +0,0 @@ --- cabal-helper: Simple interface to Cabal's configuration state --- Copyright (C) 2015-2018  Daniel Gröber <cabal-helper@dxld.at> --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU 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 General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program.  If not, see <http://www.gnu.org/licenses/>. -{-# LANGUAGE RecordWildCards, NamedFieldPuns, FlexibleContexts, ViewPatterns #-} -module Main where - -import Cabal.Plan -import Control.Applicative -import Control.Monad -import Data.Char -import Data.List -import Data.Maybe -import Data.String -import Text.Printf -import Text.Show.Pretty -import System.Console.GetOpt -import System.Environment -import System.Directory -import System.FilePath -import System.Process -import System.Exit -import System.IO -import Prelude - -import qualified Data.Text as Text -import qualified Data.Map.Strict as Map - -import Distribution.System (buildPlatform) -import Distribution.Text (display) -import Distribution.Verbosity (silent, deafening) -import Distribution.Package (packageName, packageVersion) -import Distribution.Simple.GHC as GHC (configure) - -import Paths_cabal_helper (version) -import CabalHelper.Compiletime.Compat.ProgramDb -    ( defaultProgramDb, programPath, lookupProgram, ghcProgram, ghcPkgProgram) -import CabalHelper.Compiletime.Compat.Version -import CabalHelper.Compiletime.Compile -import CabalHelper.Compiletime.Types -import CabalHelper.Shared.Common -import CabalHelper.Shared.InterfaceTypes - -usage :: IO () -usage = do -  prog <- getProgName -  hPutStr stderr $ "Usage: " ++ prog ++ " " ++ usageMsg - where -   usageMsg = "\ -\( print-appcachedir\n\ -\| print-build-platform\n\ -\| [--verbose]\n\ -\  [--with-ghc=GHC_PATH]\n\ -\  [--with-ghc-pkg=GHC_PKG_PATH]\n\ -\  [--with-cabal=CABAL_PATH]\n\ -\  [--with-cabal-version=VERSION]\n\ -\  [--with-cabal-pkg-db=PKG_DB]\n\ -\  v1-style PROJ_DIR DIST_DIR \n\ -\      ( print-exe | package-id | [CABAL_HELPER_ARGS...] )\n\ -\  v2-style PROJ_DIR DIST_NEWSTYLE_DIR DIST_DIR\n\ -\      ( print-exe | package-id | [CABAL_HELPER_ARGS...] )\n\ -\)\n" - -globalArgSpec :: [OptDescr (Options -> Options)] -globalArgSpec = -      [ option "h" ["help"] "Display help message" $ -              NoArg $ \o -> o { oHelp = True } -      , option "" ["verbose"] "Be more verbose" $ -              NoArg $ \o -> o { oVerbose = True } - -      , option "" ["with-ghc"] "GHC executable to use" $ -              reqArg "PROG" $ \p o -> o { oGhcProgram = p } - -      , option "" ["with-ghc-pkg"] "ghc-pkg executable to use (only needed when guessing from GHC path fails)" $ -              reqArg "PROG" $ \p o -> o { oGhcPkgProgram = p } - -      , option "" ["with-cabal"] "cabal-install executable to use" $ -               reqArg "PROG" $ \p o -> o { oCabalProgram = p } - -      , option "" ["with-cabal-version"] "Cabal library version to use" $ -               reqArg "VERSION" $ \p o -> o { oCabalVersion = Just $ parseVer p } - -      , option "" ["with-cabal-pkg-db"] "package database to look for Cabal library in" $ -               reqArg "PKG_DB" $ \p o -> o { oCabalPkgDb = Just (PackageDbDir p) } - -      ] - where -   option :: [Char] -> [String] -> String -> ArgDescr a -> OptDescr a -   option s l udsc dsc = Option s l dsc udsc - -   reqArg :: String -> (String -> a) -> ArgDescr a -   reqArg udsc dsc = ReqArg dsc udsc - -parseCommandArgs :: Options -> [String] -> (Options, [String]) -parseCommandArgs opts argv -    = case getOpt RequireOrder globalArgSpec argv of -        (o,r,[])   -> (foldr id opts o, r) -        (_,_,errs) -> -            panic $ "Parsing command options failed:\n" ++ concat errs - -guessProgramPaths :: Options -> IO Options -guessProgramPaths opts = do -    let v | oVerbose opts = deafening -          | otherwise     = silent - -        mGhcPath0    | same oGhcProgram opts dopts = Nothing -                     | otherwise = Just $ oGhcProgram opts -        mGhcPkgPath0 | same oGhcPkgProgram opts dopts = Nothing -                     | otherwise = Just $ oGhcPkgProgram opts - -    (_compiler, _mplatform, progdb) -        <- GHC.configure -               v -               mGhcPath0 -               mGhcPkgPath0 -               defaultProgramDb - -    let mghcPath1    = programPath <$> lookupProgram ghcProgram progdb -        mghcPkgPath1 = programPath <$> lookupProgram ghcPkgProgram progdb - -    return $ opts { oGhcProgram    = fromMaybe (oGhcProgram opts) mghcPath1 -                  , oGhcPkgProgram = fromMaybe (oGhcProgram opts) mghcPkgPath1 -                  } - where -   same f o o'  = f o == f o' -   dopts = defaultOptions - -overrideVerbosityEnvVar :: Options -> IO Options -overrideVerbosityEnvVar opts = do -  x <- lookup  "CABAL_HELPER_DEBUG" <$> getEnvironment -  return $ case x of -    Just _  -> opts { oVerbose = True } -    Nothing -> opts - -main :: IO () -main = handlePanic $ do -  (opts', args) <- parseCommandArgs defaultOptions <$> getArgs -  opts <- overrideVerbosityEnvVar =<< guessProgramPaths opts' -  case args of -    _ | oHelp opts -> usage -    [] -> usage -    "help":[] -> usage -    "version":[] -> putStrLn $ showVersion version -    "print-appdatadir":[] -> putStrLn =<< appCacheDir -    "print-appcachedir":[] -> putStrLn =<< appCacheDir -    "print-build-platform":[] -> putStrLn $ display buildPlatform - -    _:projdir:_distdir:"package-id":[] -> do -      let v | oVerbose opts = deafening -            | otherwise    = silent -      -- ghc-mod will catch multiple cabal files existing before we get here -      [cfile] <- filter isCabalFile <$> getDirectoryContents projdir -      gpd <- readPackageDescription v (projdir </> cfile) -      putStrLn $ show $ -        [Just $ ChResponseVersion (display (packageName gpd)) (toDataVersion $ packageVersion gpd)] - -    "v2-style":projdir:distdir_newstyle:unitid':args' -> do -      let unitid = UnitId $ Text.pack unitid' -      let plan_path = distdir_newstyle </> "cache" </> "plan.json" -      plan@PlanJson {pjCabalLibVersion=Ver (makeDataVersion -> pjCabalLibVersion) } -          <- decodePlanJson plan_path -      case oCabalVersion opts of -        Just ver | pjCabalLibVersion /= ver -> let -            sver = showVersion ver -            spjVer = showVersion pjCabalLibVersion -          in panic $ printf "\ -\Cabal version %s was requested but plan.json was written by version %s" sver spjVer -        _ -> case Map.lookup unitid $ pjUnits plan of -               Just u@Unit {uType} | uType /= UnitTypeLocal -> do -                 panic $ "\ -\UnitId '"++ unitid' ++"' points to non-local unit: " ++ ppShow u -               Just Unit {uDistDir=Nothing} -> panic $ printf "\ -\plan.json doesn't contain 'dist-dir' for UnitId '"++ unitid' ++"'" -               Just Unit {uType=UnitTypeLocal, uDistDir=Just distdir} -> -                 runHelper opts projdir (Just (plan, distdir_newstyle)) distdir pjCabalLibVersion args' -               _ -> let -                   units = map (\(UnitId u) -> Text.unpack u) -                         $ Map.keys -                         $ Map.filter ((==UnitTypeLocal) . uType) -                         $ pjUnits plan - -                   units_list = unlines $ map ("  "++) units -                 in -                   panic $ "\ -\UnitId '"++ unitid' ++"' not found in plan.json, available local units:\n" ++ units_list - -    "v1-style":projdir:distdir:args' -> do -      cfgf <- canonicalizePath (distdir </> "setup-config") -      mhdr <- getCabalConfigHeader cfgf -      case (mhdr, oCabalVersion opts) of -        (Nothing, _) -> panic $ printf "\ -\Could not read Cabal's persistent setup configuration header\n\ -\- Check first line of: %s\n\ -\- Maybe try: $ cabal configure" cfgf -        (Just (hdrCabalVersion, _), Just ver) -          | hdrCabalVersion /= ver -> panic $ printf "\ -\Cabal version %s was requested but setup configuration was\n\ -\written by version %s" (showVersion ver) (showVersion hdrCabalVersion) -        (Just (hdrCabalVersion, _), _) -> -          runHelper opts projdir Nothing distdir hdrCabalVersion args' -    _ -> do -      hPutStrLn stderr "Invalid command line!" -      usage -      exitWith $ ExitFailure 1 - -runHelper :: Options -> FilePath -> Maybe (PlanJson, FilePath) -> FilePath -> DataVersion -> [String] -> IO () -runHelper opts projdir mnewstyle distdir cabal_ver args' = do -  eexe <- compileHelper opts cabal_ver projdir mnewstyle distdir -  case eexe of -      Left e -> exitWith e -      Right exe -> do -        case args' of -          "print-exe":_ -> putStrLn exe -          _ -> do -            (_,_,_,h) <- createProcess $ proc exe $ projdir : distdir : args' -            exitWith =<< waitForProcess h diff --git a/src/CabalHelper/Shared/Sandbox.hs b/src/CabalHelper/Shared/Sandbox.hs index 2f3774f..f7b7470 100644 --- a/src/CabalHelper/Shared/Sandbox.hs +++ b/src/CabalHelper/Shared/Sandbox.hs @@ -34,16 +34,17 @@ import Prelude  import qualified Data.Traversable as T  -- | Get the path to the sandbox package-db in a project -getSandboxPkgDb :: FilePath -             -- ^ Path to the cabal package root directory (containing the -             -- @cabal.sandbox.config@ file) -             -> String +getSandboxPkgDb :: String               -- ^ Cabal build platform, i.e. @buildPlatform@               -> Version               -- ^ GHC version (@cProjectVersion@ is your friend) +             -> FilePath +             -- ^ Path to the cabal package root directory (containing the +             -- @cabal.sandbox.config@ file)               -> IO (Maybe FilePath) -getSandboxPkgDb d platform ghcVer = do -  mConf <- T.traverse readFile =<< mightExist (d </> "cabal.sandbox.config") +getSandboxPkgDb platform ghcVer projdir = do +  mConf <- +      T.traverse readFile =<< mightExist (projdir </> "cabal.sandbox.config")    return $ fixPkgDbVer <$> (extractSandboxDbDir =<< mConf)   where diff --git a/tests/CompileTest.hs b/tests/CompileTest.hs index 4c1f752..9eb6175 100644 --- a/tests/CompileTest.hs +++ b/tests/CompileTest.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE ScopedTypeVariables, GADTs #-} +{-# LANGUAGE ScopedTypeVariables, GADTs, ImplicitParams #-}  import System.Environment (getArgs)  import System.Directory @@ -44,15 +44,19 @@ setupHOME = do  main :: IO ()  main = do +  let ?progs = defaultPrograms +  let ?opts = defaultCompileOptions { oVerbose = True } +    args <- getArgs    case args of      "list-versions":[] -> do -        mapM_ print =<< (allCabalVersions <$> ghcVersion defaultOptions) +        mapM_ print =<< (allCabalVersions <$> ghcVersion)      "list-versions":ghc_ver_str:[] ->          mapM_ print $ allCabalVersions (parseVer ghc_ver_str)      _ ->          test args +test :: Env => [String] -> IO ()  test args = do    let action         | null args = testAllCabalVersions @@ -135,13 +139,13 @@ allCabalVersions ghc_ver = let      reverse $ filter (flip withinRange'CH constraint) cabal_versions -testAllCabalVersions :: IO () +testAllCabalVersions :: Env => IO ()  testAllCabalVersions = do -  ghc_ver <- ghcVersion defaultOptions +  ghc_ver <- ghcVersion    let relevant_cabal_versions = allCabalVersions ghc_ver    testCabalVersions $ map Right relevant_cabal_versions ++ [Left HEAD] -testCabalVersions :: [Either HEAD Version] -> IO () +testCabalVersions :: Env => [Either HEAD Version] -> IO ()  testCabalVersions versions = do    rvs <- forM versions $ \ver -> do             let sver = either show showVersion ver @@ -167,9 +171,10 @@ testCabalVersions versions = do     isLeft' (Left _) = True     isLeft' (Right _) = False -compilePrivatePkgDb :: Either HEAD Version -> IO (Either ExitCode FilePath) +compilePrivatePkgDb +    :: Env => Either HEAD Version -> IO (Either ExitCode FilePath)  compilePrivatePkgDb eCabalVer = do -    res <- E.try $ installCabal defaultOptions { oVerbose = True } eCabalVer +    res <- E.try $ installCabal eCabalVer      case res of        Right (db, cabalVer) ->            compileWithPkg db cabalVer @@ -177,7 +182,8 @@ compilePrivatePkgDb eCabalVer = do            print ioe            return $ Left (ExitFailure 1) -compileWithPkg :: PackageDbDir +compileWithPkg :: Env +               => PackageDbDir                 -> CabalVersion                 -> IO (Either ExitCode FilePath)  compileWithPkg db cabalVer = do @@ -187,7 +193,6 @@ compileWithPkg db cabalVer = do      compile        comp        (compPaths appdir (error "compile-test: distdir not available") comp) -      defaultOptions { oVerbose = True }  cabalPkgId :: CabalVersion -> String | 
