New patch for Ticket #199 - support for a world file

Peter Robinson thaldyron at gmail.com
Tue Sep 29 16:27:34 EDT 2009


[Updated patch for world-file support
Peter Robinson <thaldyron at gmail.com>**20090929200551
 Ignore-this: 2362afdac5b775f3bc018883da9afd53
   This is a new patch for Ticket #199; it adds the "--one-shot" option.
   A world file entry contains the package-name, package-version, and
   user flags (if any).
   For example, the file entry generated by
   # cabal install stm-io-hooks --flags="-debug"
   looks like this:
   # stm-io-hooks -any --flags="-debug"
   To rebuild/upgrade the packages in world (e.g. when updating the compiler)
   use
   # cabal install world
   Installing package 'foo' without adding it to the world file:
   # cabal install foo --one-shot

] {
hunk ./Distribution/Client/Config.hs 161
+  worldFile  <- defaultWorldFile
hunk ./Distribution/Client/Config.hs 170
+    },
+    savedGlobalFlags = mempty {
+      globalWorldFile    = toFlag worldFile
hunk ./Distribution/Client/Config.hs 186
+  worldFile  <- defaultWorldFile
hunk ./Distribution/Client/Config.hs 190
-      globalRemoteRepos  = [defaultRemoteRepo]
+      globalRemoteRepos  = [defaultRemoteRepo],
+      globalWorldFile    = toFlag worldFile
hunk ./Distribution/Client/Config.hs 217
+-- | Default position of the world file
+defaultWorldFile :: IO FilePath
+defaultWorldFile = do
+  dir <- defaultCabalDir
+  return $ dir </> "world"
+
hunk ./Distribution/Client/Setup.hs 90
-    globalLocalRepos     :: [FilePath]
+    globalLocalRepos     :: [FilePath],
+    globalWorldFile      :: Flag FilePath
hunk ./Distribution/Client/Setup.hs 101
-    globalLocalRepos     = mempty
+    globalLocalRepos     = mempty,
+    globalWorldFile      = mempty
hunk ./Distribution/Client/Setup.hs 152
+
+      ,option [] ["world-file"]
+         "The location of the world file"
+         globalWorldFile (\v flags -> flags { globalWorldFile = v })
+         (reqArgFlag "FILE")
hunk ./Distribution/Client/Setup.hs 167
-    globalLocalRepos     = mempty
+    globalLocalRepos     = mempty,
+    globalWorldFile      = mempty
hunk ./Distribution/Client/Setup.hs 176
-    globalLocalRepos     = combine globalLocalRepos
+    globalLocalRepos     = combine globalLocalRepos,
+    globalWorldFile      = combine globalWorldFile
hunk ./Distribution/Client/Setup.hs 468
-    installSymlinkBinDir:: Flag FilePath
+    installSymlinkBinDir:: Flag FilePath,
+    installOneShot      :: Flag Bool
hunk ./Distribution/Client/Setup.hs 483
-    installSymlinkBinDir= mempty
+    installSymlinkBinDir= mempty,
+    installOneShot      = Flag False
hunk ./Distribution/Client/Setup.hs 571
+      , option [] ["one-shot"]
+          "Do not record the packages in the world file."
+          installOneShot (\v flags -> flags { installOneShot = v })
+          trueArg
hunk ./Distribution/Client/Setup.hs 595
-    installSymlinkBinDir= mempty
+    installSymlinkBinDir= mempty,
+    installOneShot      = mempty
hunk ./Distribution/Client/Setup.hs 608
-    installSymlinkBinDir= combine installSymlinkBinDir
+    installSymlinkBinDir= combine installSymlinkBinDir,
+    installOneShot      = combine installOneShot
hunk ./Distribution/Client/Types.hs 19
-         ( GenericPackageDescription, FlagAssignment )
+         ( GenericPackageDescription, FlagAssignment, FlagName(FlagName) )
hunk ./Distribution/Client/Types.hs 24
+import Distribution.Text
+          ( Text(disp,parse) )
+import qualified Distribution.Compat.ReadP as Parse
+import qualified Text.PrettyPrint as Disp
+         $
hunk ./Distribution/Client/Types.hs 30
+import Data.Char as Char
hunk ./Distribution/Client/Types.hs 117
-  deriving (Show)
+  deriving (Show,Eq)
+
+
+instance Text UnresolvedDependency where
+  disp udep = disp (dependency udep) Disp.<+> dispFlags (depFlags udep)
+    where $
+      dispFlags [] = Disp.empty
+      dispFlags fs = Disp.text "--flags=" $
+                     Disp.<> $
+                     (Disp.doubleQuotes $ flagAssToDoc fs)
+      flagAssToDoc = foldr (\(FlagName fname,val) flagAssDoc -> $
+                             (if not val then Disp.char '-' $
+                                         else Disp.empty)
+                             Disp.<> Disp.text fname $
+                             Disp.<+> flagAssDoc)
+                           Disp.empty $
+  parse = do
+    dep <- parse $
+    Parse.skipSpaces
+    flagAss <- Parse.option [] parseFlagAssignment
+    return $ UnresolvedDependency dep flagAss $
+    where
+      parseFlagAssignment :: Parse.ReadP r FlagAssignment
+      parseFlagAssignment = do $
+        Parse.string "--flags"
+        Parse.skipSpaces
+        Parse.char '='
+        Parse.skipSpaces
+        inDoubleQuotes $ Parse.many1 flag
+        where
+          inDoubleQuotes :: Parse.ReadP r a -> Parse.ReadP r a
+          inDoubleQuotes = Parse.between (Parse.char '"') (Parse.char '"') $
+
+          flag = do
+            Parse.skipSpaces
+            val <- negative Parse.+++ positive
+            name <- ident
+            Parse.skipSpaces
+            return (FlagName name,val)
+          negative = do
+            Parse.char '-'
+            return False
+          positive = return True
+
+          ident :: Parse.ReadP r String
+          ident = do $
+            -- First character must be a letter/digit to avoid flags
+            -- like "+-debug":
+            c  <- Parse.satisfy Char.isAlphaNum
+            cs <- Parse.munch (\ch -> Char.isAlphaNum ch || ch == '_' $
+                                                         || ch == '-')
+            return (c:cs)
+        $
+
addfile ./Distribution/Client/World.hs
hunk ./Distribution/Client/World.hs 1
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Distribution.Client.World
+-- Copyright   :  (c) Peter Robinson 2009
+-- License     :  BSD-like
+--
+-- Maintainer  :  thaldyron at gmail.com
+-- Stability   :  provisional
+-- Portability :  portable
+--
+-- Interface to the world-file that contains a list of explicitly $
+-- requested packages. Meant to be imported qualified.
+--
+-- A world file entry stores the package-name, package-version, and $
+-- user flags.
+-- For example, the entry generated by $
+-- # cabal install stm-io-hooks --flags="-debug" $
+-- looks like this:
+-- # stm-io-hooks -any --flags="-debug"
+-- To rebuild/upgrade the packages in world (e.g. when updating the compiler) $
+-- use
+-- # cabal install world
+-- $
+-----------------------------------------------------------------------------
+module Distribution.Client.World( insert, $
+                                  delete,
+                                  getContents,
+                                  worldPkg,
+                                )
+where
+import Distribution.Client.Utils( writeFileAtomic ) $
+import Distribution.Client.Types $
+    ( UnresolvedDependency(dependency) )
+import Distribution.Package $
+    ( PackageName(..), Dependency( Dependency ) )
+import Distribution.Version( anyVersion )
+import Distribution.Text( display, simpleParse )
+import Distribution.Verbosity ( Verbosity )
+import Distribution.Simple.Utils ( die, notice, chattyTry )
+import Data.List( unionBy, deleteFirstsBy, nubBy, all )
+import Data.Maybe( isJust, fromJust )
+import Control.Monad( unless )
+import System.IO.Error( isDoesNotExistError, )
+import qualified Data.ByteString.Lazy.Char8 as B
+import Prelude hiding ( getContents )
+
+
+-- | Adds packages to the world file; creates the file if it doesn't $
+-- exist yet. Flag assignments for a package are updated if already $
+-- present. IO errors are non-fatal.
+insert :: Verbosity -> Bool -> FilePath -> [UnresolvedDependency] -> IO ()
+insert = modifyWorld $ unionBy equalUDep
+
+-- | Removes packages from the world file. $
+-- Note: Currently unused as there is no mechanism in Cabal (yet) to $
+-- handle uninstalls. IO errors are non-fatal.
+delete :: Verbosity -> Bool -> FilePath -> [UnresolvedDependency] -> IO ()
+delete = modifyWorld $ flip (deleteFirstsBy equalUDep)
+
+-- | UnresolvedDependency values are considered equal if their dependency
+-- is equal, i.e., we don't care about differing flags.
+equalUDep :: UnresolvedDependency -> UnresolvedDependency -> Bool
+equalUDep u1 u2 = dependency u1 == dependency u2
+
+-- | Modifies the world file by applying an update-function ('unionBy'
+-- for 'insert', 'deleteFirstsBy' for 'delete') to the given list of $
+-- packages. IO errors are considered non-fatal.
+modifyWorld :: ([UnresolvedDependency] -> [UnresolvedDependency] $
+                -> [UnresolvedDependency]) $
+                        -- ^ Function that defines how $
+                        -- the list of user packages are merged with
+                        -- existing world packages.  $
+            -> Verbosity $
+            -> Bool                   -- ^ Dry-run?
+            -> FilePath               -- ^ Location of the world file
+            -> [UnresolvedDependency] -- ^ list of user supplied packages
+            -> IO ()      $
+modifyWorld _ _         _      _     []   = return ()
+modifyWorld f verbosity dryRun world pkgs = $
+  chattyTry "Error while updating world-file. " $ do
+    pkgsOldWorld <- getContents world
+    -- Filter out packages that are not in the world file:
+    let pkgsNewWorld = nubBy equalUDep $ f pkgs pkgsOldWorld
+    -- 'Dependency' is not an Ord instance, so we need to check for $
+    -- equivalence the awkward way:
+    if not (all (`elem` pkgsOldWorld) pkgsNewWorld &&
+            all (`elem` pkgsNewWorld) pkgsOldWorld) $
+      then $
+        unless dryRun $ do
+          writeFileAtomic world $ B.unlines $
+              [B.pack (display pkg) | pkg <- pkgsNewWorld]
+          notice verbosity "Updating world-file..."            $
+      else
+        notice verbosity "World-file already up to date."
+
+
+-- | Returns the content of the world file as a list
+getContents :: FilePath -> IO [UnresolvedDependency]
+getContents world = do
+  content <- safelyReadFile world
+  let result = map simpleParse (lines . B.unpack $ content)
+  if all isJust result
+    then return $ map fromJust result
+    else die "Could not parse world file."
+  where   $
+  safelyReadFile :: FilePath -> IO B.ByteString
+  safelyReadFile file = B.readFile file `catch` handler $
+    where
+      handler e | isDoesNotExistError e = return B.empty
+                | otherwise             = ioError e  $
+
+
+-- | A dummy package that represents the world file. $
+worldPkg :: Dependency
+worldPkg = Dependency (PackageName "world") anyVersion
+
+
hunk ./Main.hs 58
+import qualified Distribution.Client.World as World
hunk ./Main.hs 203
+      pkgsFlagAss    = configConfigurationsFlags configFlags'
+      pkgsNoWorld    = filter (/=World.worldPkg) pkgs $
+      -- User-specified packages except 'world':
+      uDepsNoWorld   = [ UnresolvedDependency pkg pkgsFlagAss
+                     | pkg <- pkgsNoWorld ]
+      worldFile      = fromFlag $ globalWorldFile globalFlags'
+      dryRun         = fromFlagOrDefault False (installDryRun installFlags')
+      oneShot        = fromFlagOrDefault False (installOneShot installFlags')
+
+  -- Read packages from the world file if requested:
+  uDepsFromWorld <- if pkgsNoWorld /= pkgs && not oneShot
+    then do $
+      unless (null pkgsFlagAss) $ $
+        die "Package world does not take any flags."
+      World.getContents worldFile
+    else return []
hunk ./Main.hs 223
-          [ UnresolvedDependency pkg (configConfigurationsFlags configFlags')
-          | pkg <- pkgs ]
+          (uDepsFromWorld ++ uDepsNoWorld)
+  unless oneShot $ World.insert verbosity dryRun worldFile uDepsNoWorld $
hunk ./cabal-install.cabal 73
+        Distribution.Client.World
}



More information about the cabal-devel mailing list