[project @ 2005-11-24 16:51:18 by simonmar]
[ghc-hetmet.git] / ghc / utils / ghc-pkg / Main.hs
index a1322db..2f8be45 100644 (file)
@@ -32,10 +32,12 @@ import Prelude
 import System.Console.GetOpt
 import Text.PrettyPrint
 import qualified Control.Exception as Exception
+import Data.Maybe
 #else
 import GetOpt
 import Pretty
 import qualified Exception
+import Maybe
 #endif
 
 import Data.Char       ( isSpace )
@@ -45,6 +47,11 @@ import System        ( getArgs, getProgName, getEnv,
                  exitWith, ExitCode(..)
                )
 import System.IO
+#if __GLASGOW_HASKELL__ >= 600
+import System.IO.Error (try)
+#else
+import System.IO (try)
+#endif
 import Data.List ( isPrefixOf, isSuffixOf, intersperse, groupBy, sortBy )
 
 #ifdef mingw32_HOST_OS
@@ -99,6 +106,7 @@ data Flag
   | FlagForce
   | FlagAutoGHCiLibs
   | FlagDefinedName String String
+  | FlagSimpleOutput
   deriving Eq
 
 flags :: [OptDescr Flag]
@@ -120,7 +128,9 @@ flags = [
   Option ['D'] ["define-name"] (ReqArg toDefined "NAME=VALUE")
        "define NAME as VALUE",
   Option ['V'] ["version"] (NoArg FlagVersion)
-       "output version information and exit"
+       "output version information and exit",
+  Option [] ["simple-output"] (NoArg FlagSimpleOutput)
+        "print output in easy-to-parse format when running command 'list'"
   ]
  where
   toDefined str = 
@@ -152,9 +162,13 @@ usageHeader prog = substProg prog $
   "  $p hide {pkg-id}\n" ++
   "    Hide the specified package.\n" ++
   "\n" ++
-  "  $p list\n" ++
-  "    List registered packages in the global database, and also the" ++
-  "    user database if --user is given.\n" ++
+  "  $p list [pkg]\n" ++
+  "    List registered packages in the global database, and also the\n" ++
+  "    user database if --user is given. If a package name is given\n" ++
+  "    all the registered versions will be listed in ascending order.\n" ++
+  "\n" ++
+  "  $p latest pkg\n" ++
+  "    Prints the highest registered version of a package.\n" ++
   "\n" ++
   "  $p describe {pkg-id}\n" ++
   "    Give the registered description for the specified package. The\n" ++
@@ -199,7 +213,13 @@ runit cli nonopts = do
        pkgid <- readGlobPkgId pkgid_str
        hidePackage pkgid cli
     ["list"] -> do
-       listPackages cli
+       listPackages cli Nothing
+    ["list", pkgid_str] -> do
+       pkgid <- readGlobPkgId pkgid_str
+       listPackages cli (Just pkgid)
+    ["latest", pkgid_str] -> do
+       pkgid <- readGlobPkgId pkgid_str
+       latestPackage cli pkgid
     ["describe", pkgid_str] -> do
        pkgid <- readGlobPkgId pkgid_str
        describePackage cli pkgid
@@ -279,36 +299,57 @@ getPkgDatabases modify flags = do
        user_conf = archdir `joinFileName` "package.conf"
   user_exists <- doesFileExist user_conf
 
-  let
-       -- The semantics here are slightly strange.  If we are
-       -- *modifying* the database, then the default is to modify
-       -- the global database by default, unless you say --user.
-       -- If we are not modifying (eg. list, describe etc.) then
-       -- the user database is included by default.
-       databases
-         | modify          = foldl addDB [global_conf] flags
-         | not user_exists = foldl addDB [global_conf] flags
-         | otherwise       = foldl addDB [user_conf,global_conf] flags
-
-       -- implement the following rules:
-       --      --user means overlap with the user database
-       --      --global means reset to just the global database
-       --      -f <file> means overlap with <file>
-       addDB dbs FlagUser
-          | user_conf `elem` dbs     = dbs
-          | modify || user_exists    = user_conf : dbs
-       addDB dbs FlagGlobal     = [global_conf]
-       addDB dbs (FlagConfig f) = f : dbs
-       addDB dbs _              = dbs
+  -- If the user database doesn't exist, and this command isn't a
+  -- "modify" command, then we won't attempt to create or use it.
+  let sys_databases
+       | modify || user_exists = [user_conf,global_conf]
+       | otherwise             = [global_conf]
+
+  e_pkg_path <- try (getEnv "GHC_PACKAGE_PATH")
+  let env_stack =
+       case e_pkg_path of
+               Left  _ -> sys_databases
+               Right path
+                 | last cs == ""  -> init cs ++ sys_databases
+                 | otherwise      -> cs
+                 where cs = parseSearchPath path
+
+       -- The "global" database is always the one at the bottom of the stack.
+       -- This is the database we modify by default.
+      virt_global_conf = last env_stack
+
+  -- -f flags on the command line add to the database stack, unless any
+  -- of them are present in the stack already.
+  let flag_stack = filter (`notElem` env_stack) 
+                       [ f | FlagConfig f <- reverse flags ] ++ env_stack
+
+  -- Now we have the full stack of databases.  Next, if the current
+  -- command is a "modify" type command, then we truncate the stack
+  -- so that the topmost element is the database being modified.
+  final_stack <-
+     if not modify 
+        then return flag_stack
+       else let
+               go (FlagUser : fs)     = modifying user_conf
+               go (FlagGlobal : fs)   = modifying virt_global_conf
+               go (FlagConfig f : fs) = modifying f
+               go (_ : fs)            = go fs
+               go []                  = modifying virt_global_conf
+               
+               modifying f 
+                 | f `elem` flag_stack = return (dropWhile (/= f) flag_stack)
+                 | otherwise           = die ("requesting modification of database:\n\t" ++ f ++ "\n\twhich is not in the database stack.")
+            in
+               go flags
 
   -- we create the user database iff (a) we're modifying, and (b) the
   -- user asked to use it by giving the --user flag.
-  when (not user_exists && user_conf `elem` databases) $ do
+  when (not user_exists && user_conf `elem` final_stack) $ do
        putStrLn ("Creating user package database in " ++ user_conf)
        createDirectoryIfMissing True archdir
        writeFile user_conf emptyPackageConfig
 
-  db_stack <- mapM readParseDatabase databases
+  db_stack <- mapM readParseDatabase final_stack
   return db_stack
 
 readParseDatabase :: PackageDBName -> IO (PackageDBName,PackageDB)
@@ -406,11 +447,18 @@ modifyPackage fn pkgid flags  = do
 -- -----------------------------------------------------------------------------
 -- Listing packages
 
-listPackages ::  [Flag] -> IO ()
-listPackages flags = do
+listPackages ::  [Flag] -> Maybe PackageIdentifier -> IO ()
+listPackages flags mPackageName = do
+  let simple_output = FlagSimpleOutput `elem` flags
   db_stack <- getPkgDatabases False flags
-  mapM_ show_pkgconf (reverse db_stack)
-  where show_pkgconf (db_name,pkg_confs) =
+  let db_stack_filtered -- if a package is given, filter out all other packages
+        | Just this <- mPackageName =
+            map (\(conf,pkgs) -> (conf, filter (this `matchesPkg`) pkgs)) 
+               db_stack
+        | otherwise = db_stack
+      show_func = if simple_output then show_easy else mapM_ show_regular
+  show_func (reverse db_stack_filtered)
+  where show_regular (db_name,pkg_confs) =
          hPutStrLn stdout (render $
                text (db_name ++ ":") $$ nest 4 packages
                )
@@ -419,6 +467,23 @@ listPackages flags = do
                   | exposed p = doc
                   | otherwise = parens doc
                   where doc = text (showPackageId (package p))
+        show_easy db_stack = do
+          let pkgs = map showPackageId $ sortBy compPkgIdVer $
+                          map package (concatMap snd db_stack)
+          when (null pkgs) $ die "no matches"
+          hPutStrLn stdout $ concat $ intersperse " " pkgs
+
+-- -----------------------------------------------------------------------------
+-- Prints the highest (hidden or exposed) version of a package
+
+latestPackage ::  [Flag] -> PackageIdentifier -> IO ()
+latestPackage flags pkgid = do
+  db_stack <- getPkgDatabases False flags
+  ps <- findPackages db_stack pkgid
+  show_pkg (sortBy compPkgIdVer (map package ps))
+  where
+    show_pkg [] = die "no matches"
+    show_pkg pids = hPutStrLn stdout (showPackageId (last pids))
 
 -- -----------------------------------------------------------------------------
 -- Describe
@@ -432,28 +497,23 @@ describePackage flags pkgid = do
 -- PackageId is can have globVersion for the version
 findPackages :: PackageDBStack -> PackageIdentifier -> IO [InstalledPackageInfo]
 findPackages db_stack pkgid
-  = case [ p | p <- all_pkgs, pkgid `matches` p ] of
+  = case [ p | p <- all_pkgs, pkgid `matchesPkg` p ] of
        []  -> die ("cannot find package " ++ showPackageId pkgid)
-       [p] -> return [p]
-        -- if the version is globVersion, then we are allowed to match
-        -- multiple packages.  So eg. "Cabal-*" matches all Cabal packages,
-        -- but "Cabal" matches just one Cabal package - if there are more,
-        -- you get an error.
-       ps | versionTags (pkgVersion pkgid) == versionTags globVersion
-          -> return ps
-          | otherwise
-          -> die ("package " ++ showPackageId pkgid ++ 
-                       " matches multiple packages: " ++ 
-                       concat (intersperse ", " (
-                                map (showPackageId.package) ps)))
+       ps -> return ps
   where
-       pid `matches` pkg
-         = (pkgName pid == pkgName p)
-           && (pkgVersion pid == pkgVersion p || not (realVersion pid))
-         where p = package pkg
-
        all_pkgs = concat (map snd db_stack)
 
+matches :: PackageIdentifier -> PackageIdentifier -> Bool
+pid `matches` pid'
+  = (pkgName pid == pkgName pid')
+    && (pkgVersion pid == pkgVersion pid' || not (realVersion pid))
+
+matchesPkg :: PackageIdentifier -> InstalledPackageInfo -> Bool
+pid `matchesPkg` pkg = pid `matches` package pkg
+
+compPkgIdVer :: PackageIdentifier -> PackageIdentifier -> Ordering
+compPkgIdVer p1 p2 = pkgVersion p1 `compare` pkgVersion p2
+
 -- -----------------------------------------------------------------------------
 -- Field
 
@@ -916,8 +976,8 @@ oldRunit clis = do
       defines = [ (nm,val) | OF_DefinedName nm val <- clis ]
 
   case [ c | c <- clis, isAction c ] of
-    [ OF_List ]      -> listPackages new_flags
-    [ OF_ListLocal ] -> listPackages new_flags
+    [ OF_List ]      -> listPackages new_flags Nothing
+    [ OF_ListLocal ] -> listPackages new_flags Nothing
     [ OF_Add upd ]   -> 
        registerPackage input_file defines new_flags auto_ghci_libs upd force
     [ OF_Remove pkgid_str ]  -> do
@@ -1067,3 +1127,34 @@ pathSeparator = '\\'
 #else
 pathSeparator = '/'
 #endif
+
+-- | The function splits the given string to substrings
+-- using the 'searchPathSeparator'.
+parseSearchPath :: String -> [FilePath]
+parseSearchPath path = split path
+  where
+    split :: String -> [String]
+    split s =
+      case rest' of
+        []     -> [chunk] 
+        _:rest -> chunk : split rest
+      where
+        chunk = 
+          case chunk' of
+#ifdef mingw32_HOST_OS
+            ('\"':xs@(_:_)) | last xs == '\"' -> init xs
+#endif
+            _                                 -> chunk'
+
+        (chunk', rest') = break (==searchPathSeparator) s
+
+-- | A platform-specific character used to separate search path strings in 
+-- environment variables. The separator is a colon (\":\") on Unix and Macintosh, 
+-- and a semicolon (\";\") on the Windows operating system.
+searchPathSeparator :: Char
+#if mingw32_HOST_OS || mingw32_TARGET_OS
+searchPathSeparator = ';'
+#else
+searchPathSeparator = ':'
+#endif
+