-- 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" #-}
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
%************************************************************************
\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
; 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
; 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
; 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}
\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}
-- 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
subst a b ls = map (\ x -> if x == a then b else x) ls
#else
dosifyPaths xs = xs
+dosifyPath xs = xs
#endif
-----------------------------------------------------------------------------
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