packages feed

dynamic-cabal 0.1 → 0.2

raw patch · 6 files changed

+197/−46 lines, 6 filesdep ~directorysetup-changed

Dependency ranges changed: directory

Files

README.md view
@@ -3,7 +3,7 @@  [![Build Status](https://secure.travis-ci.org/bennofs/dynamic-cabal.png?branch=master)](http://travis-ci.org/bennofs/dynamic-cabal) -If you've ever used Cabal together with the GHC-API, you know the problem. Because GHC depends on a version of Cabal, which is often outdated, there is no way to parse the setup-config file generated by newer cabal versions. This library attemps to solve the problem by dynamically generting code that performs the action you want, and then compiling and loading that with GHC. With this method, you don't need to depend on Cabal at compile time and so you can use any version of Cabal.+If you've ever used Cabal together with the GHC-API, you know the problem. Because GHC depends on a version of Cabal, which is often outdated, there is no way to parse the setup-config file generated by newer cabal versions. This library attemps to solve the problem by dynamically generating code that performs the action you want, and then compiling and loading that with GHC. With this method, you don't need to depend on Cabal at compile time and so you can use any version of Cabal.  ## Usage 
Setup.hs view
@@ -47,7 +47,7 @@       modifyIORef depsVar $ appendDL . singletonDL $ depsEntry (buildInfo exe) exelbi suitelbi     deps <- fmap ($ []) $ readIORef depsVar -    rewriteFile (map fixchar $ dir </> "Build_" ++ testName suite ++ ".hs") $ unlines +    rewriteFile (dir </> (map fixchar $ "Build_" ++ testName suite ++ ".hs")) $ unlines        [ "module Build_" ++ map fixchar (testName suite) ++ " where"       , "getDistDir :: FilePath"       , "getDistDir = " ++ show distDir
dynamic-cabal.cabal view
@@ -1,5 +1,5 @@ name:          dynamic-cabal-version:       0.1+version:       0.2 license:       BSD3 category:      Distribution  cabal-version: >= 1.10@@ -13,7 +13,6 @@ synopsis:      dynamic-cabal description:   dynamic-cabal build-type:    Custom- extra-source-files:   .ghci   .gitignore@@ -57,6 +56,8 @@     , tasty-hunit     , HUnit     , tasty-th+    , containers+    , directory   ghc-options: -Wall   default-language: Haskell2010 
src/Distribution/Client/Dynamic/PackageDescription.hs view
@@ -2,9 +2,10 @@ -- to extract all targets along with their dependencies. module Distribution.Client.Dynamic.PackageDescription   ( Target(..)-  , TargetName(..)+  , TargetInfo(..)   , PackageDescription()   , targets+  , targetName, isLibrary, isExecutable, isTest, isBench   ) where  import Control.Applicative@@ -23,15 +24,21 @@ data CompilerFlavor data Extension data Dependency+data ModuleName instance Eq CompilerFlavor where _ == _ = undefined --- | The name of a target. Libraries don't have a name, they are always named after the package.-data TargetName = Library | Executable String | TestSuite String | BenchSuite String deriving (Show, Eq, Read, Ord)+-- | The specific information on a target, depending on the target type.+-- Libraries don't have a name, they are always named after the package, but other types do+data TargetInfo = Library [String] -- ^ contains the names of exposed modules+  | Executable String FilePath -- ^ contains the name of the executable and the path to the Main module+  | TestSuite String (Maybe FilePath) -- ^ contains the name of the test suite and the path to the Main module, for stdio tests+  | BenchSuite String  -- ^ contains the name of the benchmark+  deriving (Show, Eq, Read, Ord) --- | A target is a single Library, an Excutable, a TestSuite or a Benchmark.+-- | A target is a single Library, an Executable, a TestSuite or a Benchmark. data Target = Target-  { -- | The name of the target-    name         :: TargetName+  { -- | The specific info of the target+    info         :: TargetInfo      -- | All dependencies of the target, with their versions. If the version is not resolved yet, it'll be Nothing.      -- That only happens when the target is not enabled, though.@@ -46,6 +53,9 @@     -- | Additional options to pass to GHC when compiling source files.   , ghcOptions   :: [String] +    -- | Additional options to pass to CPP preprocessor when compiling source files.+  , cppOptions   :: [String]+     -- | The extensions to enable/disable. The elements are like GHC's -X flags, a disabled extension      -- is represented as the extension name prefixed by 'No'.     -- Example value: extensions = ["ScopedTypeVariables", "NoMultiParamTypeClasses"]@@ -54,10 +64,48 @@     -- | The 'buildable' field in the package description.   , buildable    :: Bool +    -- | other modules included in the target+  , otherModules :: [String]+     -- | Whether this target was enabled or not. This only matters for Benchmarks or Tests, Executables and Libraries are always enabled.   , enabled      :: Bool+  +   } deriving (Show, Eq, Read) ++-- | return the target name, or the empty string for the library target+targetName :: Target -> String+targetName t = case info t of+  (Library _)      -> ""+  (Executable n _) -> n+  (TestSuite  n _) -> n+  (BenchSuite n)   -> n++-- | is the target the library?+isLibrary :: Target -> Bool+isLibrary t = case info t of+  (Library _) -> True+  _           -> False++-- | is the target an executable?+isExecutable :: Target -> Bool+isExecutable t = case info t of+  (Executable _ _) -> True+  _                -> False++-- | is the target a test suite?+isTest :: Target -> Bool+isTest t = case info t of+  (TestSuite _ _) -> True+  _               -> False++-- | is the target a benchmark?+isBench :: Target -> Bool+isBench t = case info t of+  (BenchSuite _) -> True+  _              -> False+ buildable' :: Selector BuildInfo Bool buildable' = selector $ const $ useValue "Distribution.PackageDescription" $ Ident "buildable" @@ -71,16 +119,17 @@ -- | Get the names of the extensions to enable/disable for all source files in the package. If an extension should -- be disabled, it's name is prefixed by 'No'. This corresponds to the names of -X flags to pass to GHC. extensions' :: Selector BuildInfo [String]-extensions' = selector $ const $ expr $ \bi -> applyE2 map' display' $ append' <>$ applyE defaultExtensions' bi <>$ applyE oldExtensions' bi+extensions' = selector $ const $ expr $ \bi -> applyE2 map' display' $ applyE concat' $ expr $ map (<>$ bi) [defaultExtensions', oldExtensions', otherExtensions']   where display' :: ExpG (Extension -> String)         display' = useValue "Distribution.Text" $ Ident "display"         -        defaultExtensions', oldExtensions' :: ExpG (BuildInfo -> [Extension])+        defaultExtensions', oldExtensions',otherExtensions' :: ExpG (BuildInfo -> [Extension])         defaultExtensions' = useValue "Distribution.PackageDescription" $ Ident "defaultExtensions"         oldExtensions' = useValue "Distribution.PackageDescription" $ Ident "oldExtensions"+        otherExtensions' = useValue "Distribution.PackageDescription" $ Ident "otherExtensions"  -- | Get the options to pass to GHC for a given BuildInfo.-ghcOptions' :: Selector BuildInfo [FilePath]+ghcOptions' :: Selector BuildInfo [String] ghcOptions' = selector $ const $ concat' <>. applyE map' snd' <>. applyE filter' (applyE equal' ghc <>. fst') <>. options'   where options' :: ExpG (BuildInfo -> [(CompilerFlavor, [String])])         options' = useValue "Distribution.PackageDescription" $ Ident "options"@@ -88,6 +137,21 @@         ghc :: ExpG CompilerFlavor         ghc = useValue "Distribution.Compiler" $ Ident "GHC" +-- | Get the options to pass to GHC for a given BuildInfo.+cppOptions' :: Selector BuildInfo [String]+cppOptions' = selector $ const options'+  where options' :: ExpG (BuildInfo -> [String])+        options' = useValue "Distribution.PackageDescription" $ Ident "cppOptions"++-- | Get the non exposed modules.+otherModules' :: Selector BuildInfo [String]+otherModules' = selector $ const $ applyE map' display' <>. mods'+  where display' :: ExpG (Distribution.Client.Dynamic.PackageDescription.ModuleName -> String)+        display' = useValue "Distribution.Text" $ Ident "display"+        mods' :: ExpG (BuildInfo -> [Distribution.Client.Dynamic.PackageDescription.ModuleName])+        mods' = useValue "Distribution.PackageDescription" $ Ident "otherModules"++ -- | Get the dependencies of the target and the version of the dependency if possible. If the dependencies version -- is not a specific version (this only happens when the target is not enabled), return Nothing. dependencies' ::  Selector BuildInfo [(String, Maybe Version)]@@ -109,38 +173,55 @@         targetBuildDepends' = useValue "Distribution.PackageDescription" $ Ident "targetBuildDepends"  -- | Construct a 'Target' from a buildInfo, a targetName and a Bool that is True if the target is enabled, false otherwise.-buildInfoTarget :: Query BuildInfo (TargetName -> Bool -> Target)-buildInfoTarget = (\d src inc opts exts ba n -> Target n d src inc opts exts ba)+buildInfoTarget :: Query BuildInfo (TargetInfo -> Bool -> Target)+buildInfoTarget = (\d src inc opts copts exts ba oths n-> Target n d src inc opts copts exts ba oths)                  <$> query dependencies'                   <*> query hsSourceDirs'                   <*> query includeDirs'                   <*> query ghcOptions'+                 <*> query cppOptions'                  <*> query extensions'                  <*> query buildable'+                 <*> query otherModules' --- | Get the buildInfo of the library in the package. If there is no library in the package, +-- | Get the buildInfo of the library in the package, and its exposed modules. If there is no library in the package,  -- return the empty list.-library' :: ExpG (PackageDescription -> [BuildInfo])+library' :: ExpG (PackageDescription -> [([String],BuildInfo)]) library' = applyE2 maybe' (returnE $ List []) serialize' <>. useValue "Distribution.PackageDescription" (Ident "library")-  where serialize' = expr $ \lib -> expr [applyE buildInfo' lib]+  where serialize' = expr $ \lib -> applyE2 cons (tuple2 <>$ applyE modNames' lib <>$ applyE buildInfo' lib) (returnE $ List [])+        modNames'=applyE map' display' <>. mods'+        display' = useValue "Distribution.Text" $ Ident "display"+        mods'   = useValue "Distribution.PackageDescription" $ Ident "exposedModules"         buildInfo' = useValue "Distribution.PackageDescription" $ Ident "libBuildInfo" --- | Get the buildInfo and the name of each executable in the package.-executables' :: ExpG (PackageDescription -> [(String, BuildInfo)])+-- | Get the buildInfo, the name and Main module path of each executable in the package.+executables' :: ExpG (PackageDescription -> [((String,FilePath), BuildInfo)]) executables'= applyE map' serialize' <>. useValue "Distribution.PackageDescription" (Ident "executables")-  where serialize' = expr $ \exe -> tuple2 <>$ applyE exeName' exe <>$ applyE buildInfo' exe+  where serialize' = expr $ \exe -> tuple2 <>$ applyE exeInfo exe <>$ applyE buildInfo' exe+        exeInfo= expr $ \exe -> tuple2 <>$ applyE exeName' exe <>$ applyE modulePath' exe          exeName'   = useValue "Distribution.PackageDescription" $ Ident "exeName"+        modulePath'= useValue "Distribution.PackageDescription" $ Ident "modulePath"         buildInfo' = useValue "Distribution.PackageDescription" $ Ident "buildInfo" --- | Get the name, whether the target is enabled or not and the buildInfo of each testSuite in the package.-tests' :: ExpG (PackageDescription -> [((String, Bool), BuildInfo)])++-- | Get the name, whether the target is enabled or not, possibly the main module path and the buildInfo of each testSuite in the package.+tests' :: ExpG (PackageDescription -> [((String, Bool,Maybe FilePath), BuildInfo)]) tests' = applyE map' serialize' <>. useValue "Distribution.PackageDescription" (Ident "testSuites")   where serialize' = expr $ \test -> tuple2 -                                    <>$ applyE2 tuple2 (testName' <>$ test) (testEnabled' <>$ test) +                                    <>$ applyE3 tuple3 (testName' <>$ test) (testEnabled' <>$ test) (testPath <>. testInterface' <>$ test)                                      <>$ applyE buildInfo' test         testName'   = useValue "Distribution.PackageDescription" $ Ident "testName"         testEnabled' = useValue "Distribution.PackageDescription" $ Ident "testEnabled"         buildInfo' = useValue "Distribution.PackageDescription" $ Ident "testBuildInfo"+        testInterface' = useValue "Distribution.PackageDescription" $ Ident "testInterface"+        testPath=expr $ \ti->do+                v10  <- useCon "Distribution.PackageDescription" $ Ident "TestSuiteExeV10"+                versionVar  <- newName "version"+                fpVar  <- newName "filepath"                +                caseE ti +                        [(PApp v10 [PVar versionVar,PVar fpVar],applyE just' (useVar fpVar))+                        ,(PWildCard,nothing')+                        ]   -- | Get the name, whether it's enabled or not and the buildInfo of each benchmark in the package. benchmarks' :: ExpG (PackageDescription -> [((String, Bool), BuildInfo)])@@ -155,32 +236,32 @@ -- | Get the name of all targets and whether they are enabled (second field True) or not.  -- The resulting list is in the same order and has the same length as the list returned -- by buildInfos.-targetInfos :: Query PackageDescription [(TargetName, Bool)]-targetInfos = build <$> query hasLib <*> query exeNames <*> query testInfo <*> query benchInfo-  where hasLib :: Selector PackageDescription Bool-        hasLib = selector $ const $ not' <>. null' <>. library'+targetInfos :: Query PackageDescription [(TargetInfo, Bool)]+targetInfos = build <$> query libMods <*> query exeNames <*> query testInfo <*> query benchInfo+  where libMods :: Selector PackageDescription [[String]]+        libMods =  selector $ const $ applyE map' fst' <>. library' -        exeNames :: Selector PackageDescription [String]+        exeNames :: Selector PackageDescription [(String,FilePath)]         exeNames = selector $ const $ applyE map' fst' <>. executables' -        testInfo :: Selector PackageDescription [(String, Bool)]+        testInfo :: Selector PackageDescription [(String, Bool,Maybe FilePath)]         testInfo = selector $ const $ applyE map' fst' <>. tests'          benchInfo :: Selector PackageDescription [(String, Bool)]         benchInfo = selector $ const $ applyE map' fst' <>. benchmarks'          build lib exe test bench = concat-          [ [ (Library      , True) | lib           ]-          , [ (Executable x , True) | x <- exe       ]-          , [ (TestSuite  x , e)    | (x,e) <- test  ]-          , [ (BenchSuite x , e)    | (x,e) <- bench ]+          [ [ (Library    x   , True) | x        <- lib   ]+          , [ (Executable x mp, True) | (x,mp)   <- exe   ]+          , [ (TestSuite  x mp, e   ) | (x,e,mp) <- test  ]+          , [ (BenchSuite x   , e   ) | (x,e)    <- bench ]           ]  -- | Get the BuildInfo of all targets, even for disable or not buildable targets. buildInfos :: Selector PackageDescription [BuildInfo] buildInfos = selector $ const $ expr $ \bi -> applyE concat' $ expr $ map (<>$ bi) [libraryBI, exesBI, testsBI, benchsBI]   where libraryBI :: ExpG (PackageDescription -> [BuildInfo])-        libraryBI = library'+        libraryBI = applyE map' snd' <>. library'                  exesBI    :: ExpG (PackageDescription -> [BuildInfo])         exesBI    = applyE map' snd' <>. executables'
src/Distribution/Client/Dynamic/Query.hs view
@@ -22,6 +22,8 @@   , fmapQ   , on   , runQuery+  , runRawQuery+  , getCabalVersion   ) where  import           Control.Applicative@@ -140,9 +142,9 @@   setCurrentDirectory pwd   res <$ removeDirectoryRecursive tmp -generateSource :: Selector LocalBuildInfo o -> String -> FilePath -> Version -> IO ()+generateSource :: Selector LocalBuildInfo o -> String -> FilePath -> Version -> IO String generateSource (Selector s) modName setupConfig version = -  writeFile (modName <.> "hs") $ flip generateModule modName $ do+  return $ flip generateModule modName $ do     getLBI <- addDecl (Ident "getLBI") $                     applyE fmap' (read' <>. unlines' <>. applyE drop' 1 <>. lines' :: ExpG (String -> LocalBuildInfo))                 <>$ applyE readFile' (expr setupConfig)@@ -153,10 +155,24 @@ runQuery :: Query LocalBuildInfo a -> FilePath -> IO a runQuery (Query s post) setupConfig = do   setupConfig' <- canonicalizePath setupConfig+  version <- getCabalVersion setupConfig'+  src<-  generateSource s "DynamicCabalQuery" setupConfig' version+  runRawQuery' src setupConfig post+  +-- | Run a raw query, getting the full source from the first parameter+-- the module must be DynamicCabalQuery and it must have a result declaration+runRawQuery :: Typeable a => String -> FilePath -> IO a+runRawQuery s setupConfig = runRawQuery' s setupConfig id+  +-- | Run a raw query, getting the full source from the first parameter.+-- The module must be DynamicCabalQuery and it must have a result declaration.+-- The third argument is a function to apply to the result of running the query.+runRawQuery' :: Typeable i => String -> FilePath -> (i -> a) -> IO a+runRawQuery' s setupConfig post = do+  setupConfig' <- canonicalizePath setupConfig   withTempWorkingDir $ do     version <- getCabalVersion setupConfig'-    generateSource s "DynamicCabalQuery" setupConfig' version-+    writeFile "DynamicCabalQuery.hs" s     GHC.runGhc (Just GHC.Paths.libdir) $ do       dflags <- GHC.getSessionDynFlags       void $ GHC.setSessionDynFlags $ dflags@@ -172,3 +188,4 @@         void $ GHC.load GHC.LoadAllTargets         GHC.setContext [GHC.IIDecl $ GHC.simpleImportDecl $ GHC.mkModuleName "DynamicCabalQuery"]         GHC.dynCompileExpr "result" >>= maybe (fail "dynamic-cabal: runQuery: Result expression has wrong type") (MonadUtils.liftIO . fmap post) . fromDynamic+        
tests/Main.hs view
@@ -1,14 +1,20 @@-{-# LANGUAGE TemplateHaskell #-}-import Data.List-import Distribution.Client.Dynamic-import Test.HUnit-import Test.Tasty.HUnit-import Test.Tasty.TH+{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-}+import           Data.List+import qualified Data.Map as DM+import           Data.Version+import           Distribution.Client.Dynamic+import           System.Directory+import           Test.HUnit+import           Test.Tasty.HUnit+import           Test.Tasty.TH  case_targets :: Assertion case_targets = do   tgs <- runQuery (on localPkgDesc targets) "dist/setup-config"-  assertEqual "target names" (sort [Library, TestSuite "dynamic-cabal-tests", TestSuite "doctests"]) (sort $ map name tgs) +  assertEqual "target names" (sort [Library ["Distribution.Client.Dynamic"+      ,"Distribution.Client.Dynamic.Query"+      ,"Distribution.Client.Dynamic.LocalBuildInfo"+      ,"Distribution.Client.Dynamic.PackageDescription"], TestSuite "dynamic-cabal-tests" (Just "Main.hs"), TestSuite "doctests" (Just "doctests.hs")]) (sort $ map info tgs)    assertEqual "source directories" (sort $ map sourceDirs tgs) $ sort $ map return ["src", "tests", "tests"]   assertBool "ghc options" $ all (elem "-Wall" . ghcOptions)  tgs   assertBool "no extensions" $ all (null . extensions) tgs@@ -18,6 +24,52 @@ case_packageDBs = do   dbs <- runQuery packageDBs "dist/setup-config"   length dbs `seq` return ()++-- | test running raw query+case_raw :: Assertion+case_raw = do+  s<-src+  m::DM.Map String [String]<-runRawQuery s "dist/setup-config"+  assertEqual "map size" 3 (DM.size m)+  -- print m++-- | the raw query source code+src :: IO String+src=do+  setupConfig' <- canonicalizePath "dist/setup-config"+  cv<-getCabalVersion setupConfig'+  let optStr=if cv>=Version [1,15,0] []+               then "       let opts=renderGhcOptions ((fst $ head $ readP_to_S  parseVersion  \"7.6.3\") :: Version) $ componentGhcOptions V.silent lbi b clbi \"dist/build\""+               else "       let opts=ghcOptions lbi b clbi \"dist/build\""+  return $ unlines [+    "module DynamicCabalQuery where"+    ,"import Distribution.PackageDescription"+    ,"import Distribution.Simple.LocalBuildInfo"+    ,"import Data.IORef"+    ,"import qualified Data.Map as DM"+    ,"import qualified Distribution.Verbosity as V"+    ,"import Data.Version (parseVersion)"+    ,"import Text.ParserCombinators.ReadP(readP_to_S)"+    ,if cv>=Version [1,15,0] [] then "import Distribution.Simple.Program.GHC" else ""+    ,"import Distribution.Simple.GHC"+    ,"import Distribution.Version"+    ,"import Control.Monad"+    ,""+    ,"result :: IO (DM.Map String [String])"+    ,"result=do"+    ,"lbi<-liftM (read . Prelude.unlines . drop 1 . lines) $ Prelude.readFile \""++setupConfig' ++"\""+    ,"let pkg=localPkgDescr lbi"+    ,"r<-newIORef DM.empty"+    ,"withComponentsLBI pkg lbi (\\c clbi->do"+    ,"       let b=foldComponent libBuildInfo buildInfo testBuildInfo benchmarkBuildInfo c"+    ,optStr +    ,"       let n=foldComponent (const \"\") exeName testName benchmarkName c"+    ,"       modifyIORef r (DM.insert n opts)"+    ,"       return ()"+    ,"       )"+    ,"readIORef r" ]++  main :: IO () main = $(defaultMainGenerator)