[project @ 2001-06-15 15:20:20 by simonpj]
[ghc-hetmet.git] / ghc / compiler / main / SysTools.lhs
index 4e8c0bb..271d947 100644 (file)
@@ -33,33 +33,41 @@ module SysTools (
 
        -- System interface
        getProcessID,           -- IO Int
-       system,                 -- String -> IO Int     -- System.system
+       system,                 -- String -> IO Int
 
        -- Misc
        showGhcUsage,           -- IO ()        Shows usage message and exits
        getSysMan,              -- IO String    Parallel system only
+       dosifyPath,             -- String -> String
 
        runSomething    -- ToDo: make private
  ) where
 
 import DriverUtil
 import Config
-import Outputable      ( panic )
+import Outputable
 import Panic           ( progName, GhcException(..) )
 import Util            ( global )
 import CmdLineOpts     ( dynFlag, verbosity )
 
-import List            ( intersperse )
-import Exception       ( throwDyn, catchAllIO )
+import List            ( intersperse, isPrefixOf )
+import Exception       ( throw, throwDyn, catchAllIO )
 import IO              ( hPutStr, hPutChar, hPutStrLn, hFlush, stderr )
+import IO              ( openFile, IOMode(..), hClose )        -- For temp "system"
 import Directory       ( doesFileExist, removeFile )
 import IOExts          ( IORef, readIORef, writeIORef )
 import Monad           ( when, unless )
 import qualified System
 import System          ( ExitCode(..) )
-import qualified Posix
 
 #include "../includes/config.h"
+
+#if !defined(mingw32_TARGET_OS)
+import qualified Posix
+#else
+import Addr              ( nullAddr )
+#endif
+
 #include "HsVersions.h"
 
 {-# DEPRECATED runSomething "runSomething should be private to SysTools" #-}
@@ -121,7 +129,6 @@ GLOBAL_VAR(v_Pgm_a,         error "pgm_a",   String)        -- as
 GLOBAL_VAR(v_Pgm_l,    error "pgm_l",   String)        -- ld
 GLOBAL_VAR(v_Pgm_MkDLL, error "pgm_dll", String)       -- mkdll
 
-GLOBAL_VAR(v_Pgm_PERL, error "pgm_PERL", String)       -- perl
 GLOBAL_VAR(v_Pgm_T,    error "pgm_T",    String)       -- touch
 GLOBAL_VAR(v_Pgm_CP,   error "pgm_CP",          String)        -- cp
 
@@ -140,42 +147,53 @@ GLOBAL_VAR(v_Pgm_sysman, error "pgm_sysman", String)      -- system manager
 %************************************************************************
 
 \begin{code}
-initSysTools :: String -- TopDir
-                       --      for "installed" this is the root of GHC's support files
-                       --      for "in-place" it is the root of the build tree
-
-            -> IO ()   -- Set all the mutable variables above, holding 
-                       --      (a) the system programs
-                       --      (b) the package-config file
-                       --      (c) the GHC usage message
-
-initSysTools top_dir
-  = do  { let installed   pgm = top_dir `slash` "extra-bin" `slash` pgm
-             inplace dir pgm = top_dir `slash` dir         `slash` pgm
-
-             installed_pkgconfig = installed "package.conf"
-             inplace_pkgconfig   = inplace cGHC_DRIVER_DIR "package.conf.inplace"
-
-       -- Discover whether we're running in a build tree or in an installation,
-       -- by looking for the package configuration file.
-       ; am_installed <- doesFileExist installed_pkgconfig
-
-       -- Check that the in-place package config exists if 
-       -- the installed one does not (we need at least one!)
-       ; if am_installed then return () else
-         do config_exists <- doesFileExist inplace_pkgconfig
-            if config_exists then return () else
-               throwDyn (InstallationError 
-                            ("Can't find package.conf in " ++ 
-                             inplace_pkgconfig))
-
-       ; let pkgconfig_path | am_installed = installed_pkgconfig
-                            | otherwise    = inplace_pkgconfig
-                                       
-       -- The GHC usage help message is found similarly to the package configuration
-       ; let ghc_usage_msg_path | am_installed = installed "ghc-usage.txt"
-                                | otherwise    = inplace cGHC_DRIVER_DIR "ghc-usage.txt"
-
+initSysTools :: [String]       -- Command-line arguments starting "-B"
+
+            -> IO String       -- Set all the mutable variables above, holding 
+                               --      (a) the system programs
+                               --      (b) the package-config file
+                               --      (c) the GHC usage message
+                               -- Return TopDir
+
+
+initSysTools minusB_args
+  = do  { (am_installed, top_dir) <- getTopDir minusB_args
+               -- top_dir
+               --      for "installed" this is the root of GHC's support files
+               --      for "in-place" it is the root of the build tree
+
+       ; let installed_bin pgm   =  top_dir `slash` "bin" `slash` pgm
+             installed     file  =  top_dir `slash` file
+             inplace dir   pgm   =  top_dir `slash` dosifyPath dir `slash` pgm
+
+       ; let pkgconfig_path
+               | am_installed = installed "package.conf"
+               | otherwise    = inplace cGHC_DRIVER_DIR "package.conf.inplace"
+
+             ghc_usage_msg_path
+               | am_installed = installed "ghc-usage.txt"
+               | otherwise    = inplace cGHC_DRIVER_DIR "ghc-usage.txt"
+
+               -- For all systems, unlit, split, mangle are GHC utilities
+               -- architecture-specific stuff is done when building Config.hs
+             unlit_path
+               | am_installed = installed_bin cGHC_UNLIT
+               | otherwise    = inplace cGHC_UNLIT_DIR cGHC_UNLIT
+
+               -- split and mangle are Perl scripts
+             split_script
+               | am_installed = installed_bin cGHC_SPLIT
+               | otherwise    = inplace cGHC_SPLIT_DIR cGHC_SPLIT
+
+             mangle_script
+               | am_installed = installed_bin cGHC_MANGLER
+               | otherwise    = inplace cGHC_MANGLER_DIR cGHC_MANGLER
+
+       -- Check that the package config exists
+       ; config_exists <- doesFileExist pkgconfig_path
+       ; when (not config_exists) $
+            throwDyn (InstallationError 
+                        ("Can't find package.conf in " ++ pkgconfig_path))
 
 #if defined(mingw32_TARGET_OS)
        --              WINDOWS-SPECIFIC STUFF
@@ -194,6 +212,11 @@ initSysTools top_dir
        ; let touch_path  | am_installed = installed cGHC_TOUCHY
                          | otherwise    = inplace cGHC_TOUCHY_DIR cGHC_TOUCHY
 
+       -- On Win32 we don't want to rely on #!/bin/perl, so we prepend 
+       -- a call to Perl to get the invocation of split and mangle
+       ; let split_path  = perl_path ++ " " ++ split_script
+             mangle_path = perl_path ++ " " ++ mangle_script
+
        ; let mkdll_path = cMKDLL
 #else
        --              UNIX-SPECIFIC STUFF
@@ -203,25 +226,16 @@ initSysTools top_dir
        ; let   cpp_path   = cRAWCPP
                gcc_path   = cGCC
                touch_path = cGHC_TOUCHY
-               perl_path  = cGHC_PERL
                mkdll_path = panic "Cant build DLLs on a non-Win32 system"
-#endif
-
-       -- For all systems, unlit, split, mangle are GHC utilities
-       -- architecture-specific stuff is done when building Config.hs
-       --
-       -- However split and mangle are Perl scripts, and on Win32 at least
-       -- we don't want to rely on #!/bin/perl, so we prepend a call to Perl
-       ; let unlit_path  | am_installed = installed cGHC_UNLIT
-                         | otherwise    = inplace cGHC_UNLIT_DIR cGHC_UNLIT
 
-             split_script  | am_installed = installed cGHC_SPLIT
-                           | otherwise    = inplace cGHC_SPLIT_DIR cGHC_SPLIT
-             mangle_script | am_installed = installed cGHC_MANGLER
-                           | otherwise    = inplace cGHC_MANGLER_DIR cGHC_MANGLER
+       -- On Unix, scripts are invoked using the '#!' method.  Binary
+       -- installations of GHC on Unix place the correct line on the front
+       -- of the script at installation time, so we don't want to wire-in
+       -- our knowledge of $(PERL) on the host system here.
+       ; let split_path  = split_script
+             mangle_path = mangle_script
 
-             split_path  = perl_path ++ " " ++ split_script
-             mangle_path = perl_path ++ " " ++ mangle_script
+#endif
 
        -- For all systems, copy and remove are provided by the host 
        -- system; architecture-specific stuff is done when building Config.hs
@@ -250,8 +264,8 @@ initSysTools top_dir
        ; writeIORef v_Pgm_MkDLL           mkdll_path
        ; writeIORef v_Pgm_T               touch_path
        ; writeIORef v_Pgm_CP              cp_path
-       ; writeIORef v_Pgm_PERL            perl_path
 
+       ; return top_dir
        }
 \end{code}
 
@@ -274,6 +288,66 @@ setPgm pgm    = unknownFlagErr ("-pgm" ++ pgm)
 \end{code}
 
 
+\begin{code}
+-- Find TopDir
+--     for "installed" this is the root of GHC's support files
+--     for "in-place" it is the root of the build tree
+--
+-- Plan of action:
+-- 1. Set proto_top_dir
+--     a) look for (the last) -B flag, and use it
+--     b) if there are no -B flags, get the directory 
+--        where GHC is running
+--
+-- 2. If package.conf exists in proto_top_dir, we are running
+--     installed; and TopDir = proto_top_dir
+--
+-- 3. Otherwise we are running in-place, so
+--     proto_top_dir will be /...stuff.../ghc/compiler
+--     Set TopDir to /...stuff..., which is the root of the build tree
+--
+-- This is very gruesome indeed
+
+getTopDir :: [String]
+         -> IO (Bool,          -- True <=> am installed, False <=> in-place
+                String)        -- TopDir
+
+getTopDir minusbs
+  = do { proto_top_dir <- get_proto
+
+       -- Discover whether we're running in a build tree or in an installation,
+       -- by looking for the package configuration file.
+       ; am_installed <- doesFileExist (proto_top_dir `slash` "package.conf")
+
+       ; if am_installed then
+           return (True, proto_top_dir)
+        else
+           return (False, remove_suffix proto_top_dir)
+       }
+  where
+    get_proto | not (null minusbs) 
+             = return (dosifyPath (drop 2 (last minusbs)))
+             | otherwise          
+             = do { maybe_exec_dir <- getExecDir -- Get directory of executable
+                  ; case maybe_exec_dir of       -- (only works on Windows)
+                       Nothing  -> throwDyn (InstallationError 
+                                               "missing -B<dir> option")
+                       Just dir -> return dir
+                  }
+
+    remove_suffix dir  -- "/...stuff.../ghc/compiler" --> "/...stuff..."
+       = ASSERT2( not (null p1) && 
+                  not (null p2) && 
+                  dosifyPath dir == dosifyPath (top_dir ++ "/ghc/compiler"),
+                  text dir )
+         top_dir
+       where
+        p1      = dropWhile (not . isSlash) (reverse dir)
+        p2      = dropWhile (not . isSlash) (tail p1)  -- head is '/'
+        top_dir = reverse (tail p2)                    -- head is '/'
+\end{code}
+
+
 %************************************************************************
 %*                                                                     *
 \subsection{Running an external program}
@@ -479,14 +553,14 @@ traceCmd phase_name cmd_line action
 -- Convert filepath into MSDOS form.
 
 dosifyPaths :: [String] -> [String]
+dosifyPath  :: String -> String
 -- dosifyPath does two things
 -- a) change '/' to '\'
 -- b) remove initial '/cygdrive/'
 
-#if defined(mingw32_TARGET_OS) && defined(MINIMAL_UNIX_DEPS)
+#if defined(mingw32_TARGET_OS)
 dosifyPaths xs = map dosifyPath xs
 
-dosifyPath  :: String -> String
 dosifyPath stuff
   = subst '/' '\\' real_stuff
  where
@@ -501,6 +575,7 @@ dosifyPath stuff
   subst a b ls = map (\ x -> if x == a then b else x) ls
 #else
 dosifyPaths xs = xs
+dosifyPath  xs = xs
 #endif
 
 -----------------------------------------------------------------------------
@@ -511,54 +586,83 @@ dosifyPaths xs = xs
 slash           :: String -> String -> String
 absPath, relPath :: [String] -> String
 
-slash s1 s2 = s1 ++ ('/' : s2)
-
+isSlash '/'   = True
+isSlash '\\'  = True
+isSlash other = False
 
 relPath [] = ""
 relPath xs = foldr1 slash xs
 
 absPath xs = "" `slash` relPath xs
 
+#if defined(mingw32_TARGET_OS)
+slash s1 s2 = s1 ++ ('\\' : s2)
+#else
+slash s1 s2 = s1 ++ ('/' : s2)
+#endif
+
 -----------------------------------------------------------------------------
--- Convert filepath into MSDOS form.
--- 
 -- Define      myGetProcessId :: IO Int
+--             getExecDir     :: IO (Maybe String)
 
 #ifdef mingw32_TARGET_OS
-foreign import "_getpid" getProcessID :: IO Int 
+foreign import "_getpid" getProcessID :: IO Int -- relies on Int == Int32 on Windows
+
+getExecDir :: IO (Maybe String)
+getExecDir = return Nothing
+{-
+foreign import stdcall "GetCurrentDirectoryA" getCurrentDirectory :: Int32 -> CString -> IO Int32
+getExecDir = do len <- getCurrentDirectory 0 nullAddr
+               buf <- mallocArray (fromIntegral len)
+               ret <- getCurrentDirectory len buf
+               if ret == 0 then return Nothing
+                           else do s <- peekCString buf
+                                   destructArray (fromIntegral len) buf
+                                   return (Just s)
+-}
 #else
 getProcessID :: IO Int
 getProcessID = Posix.getProcessID
+getExecDir :: IO (Maybe String) = do return Nothing
 #endif
 \end{code}
 
-
 %************************************************************************
 %*                                                                     *
 \subsection{System}
 %*                                                                     *
 %************************************************************************
 
--- This procedure executes system calls.  In pre-GHC-5.00 and earlier, 
--- the System.system implementation didn't work, so this acts as a fix-up
--- by passing the command line to 'sh'.
+In GHC prior to 5.01 (or so), on Windows, the implementation
+of "system" in the library System.system does not work for very 
+long command lines.  But GHC may need to make a system call with
+a very long command line, notably when it links itself during
+bootstrapping.  
+
+Solution: when compiling SysTools for Windows, using GHC prior
+to 5.01, write the command to a file and use "sh" (not cmd.exe)
+to execute it.  Such GHCs require "sh" on the path, but once
+bootstrapped this problem goes away.
+
+ToDo: remove when compiling with GHC < 5 is not relevant any more
+
 \begin{code}
-system :: String -> IO ExitCode
 system cmd
- = do
-#if !defined(mingw32_TARGET_OS)
-    -- in the case where we do want to use an MSDOS command shell, we assume
-    -- that files and paths have been converted to a form that's
-    -- understandable to the command we're invoking.
-   System.system cmd
-#else
-   tmp <- newTempName "sh"
-   h   <- openFile tmp WriteMode
-   hPutStrLn h cmd
-   hClose h
-   exit_code <- system ("sh - " ++ tmp) `catchAllIO` 
-                      (\exn -> removeFile tmp >> ioError exn)
-   removeFile tmp
-   return exit_code
+
+#if !defined(mingw32_TARGET_OS) || __GLASGOW_HASKELL__ > 501
+    -- The usual case
+ = System.system cmd
+
+#else  -- The Hackoid case
+ = do pid     <- getProcessID
+      tmp_dir <- readIORef v_TmpDir
+      let tmp = tmp_dir++"/sh"++show pid
+      h <- openFile tmp WriteMode
+      hPutStrLn h cmd
+      hClose h
+      exit_code <- System.system ("sh - " ++ tmp) `catchAllIO` 
+                                (\exn -> removeFile tmp >> throw exn)
+      removeFile tmp
+      return exit_code
 #endif
-\end{code}
+\end{code}
\ No newline at end of file