From d406a16ac22e6ad02da0d2c75212614eda09d2cb Mon Sep 17 00:00:00 2001
From: John Ericson <John.Ericson@Obsidian.Systems>
Date: Fri, 31 May 2019 19:33:33 -0400
Subject: [PATCH] ghc-pkg needs settings file to un-hardcode target platform

This matches GHC itself getting the target platform from there.
---
 compiler/hieFile/HieBin.hs            |   3 +-
 compiler/main/SysTools.hs             |  56 +++++---------
 compiler/main/SysTools/BaseDir.hs     |  16 ----
 compiler/utils/Util.hs                |  22 ------
 libraries/ghc-boot/GHC/BaseDir.hs     |  18 ++++-
 libraries/ghc-boot/GHC/Settings.hs    | 104 ++++++++++++++++++++++++++
 libraries/ghc-boot/ghc-boot.cabal.in  |   2 +
 testsuite/tests/driver/T3007/Makefile |   6 +-
 utils/ghc-pkg/Main.hs                 |  34 ++++++++-
 utils/ghc-pkg/ghc.mk                  |   5 +-
 10 files changed, 182 insertions(+), 84 deletions(-)
 create mode 100644 libraries/ghc-boot/GHC/Settings.hs

diff --git a/compiler/hieFile/HieBin.hs b/compiler/hieFile/HieBin.hs
index 6c72dca03483..61e3d01d0ea8 100644
--- a/compiler/hieFile/HieBin.hs
+++ b/compiler/hieFile/HieBin.hs
@@ -4,6 +4,8 @@ Binary serialization for .hie files.
 {-# LANGUAGE ScopedTypeVariables #-}
 module HieBin ( readHieFile, readHieFileWithVersion, HieHeader, writeHieFile, HieName(..), toHieName, HieFileResult(..), hieMagic) where
 
+import GHC.Settings               ( maybeRead )
+
 import Config                     ( cProjectVersion )
 import GhcPrelude
 import Binary
@@ -17,7 +19,6 @@ import Outputable
 import PrelInfo
 import SrcLoc
 import UniqSupply                 ( takeUniqFromSupply )
-import Util                       ( maybeRead )
 import Unique
 import UniqFM
 
diff --git a/compiler/main/SysTools.hs b/compiler/main/SysTools.hs
index 5ab880513295..518d9fdb2f4f 100644
--- a/compiler/main/SysTools.hs
+++ b/compiler/main/SysTools.hs
@@ -40,17 +40,19 @@ module SysTools (
 
 import GhcPrelude
 
+import GHC.Settings
+
 import Module
 import Packages
 import Config
 import Outputable
 import ErrUtils
 import GHC.Platform
-import Util
 import DynFlags
 import Fingerprint
 import ToolSettings
 
+import qualified Data.Map as Map
 import System.FilePath
 import System.IO
 import System.Directory
@@ -151,41 +153,29 @@ initSysTools top_dir
 
        settingsStr <- readFile settingsFile
        platformConstantsStr <- readFile platformConstantsFile
-       mySettings <- case maybeReadFuzzy settingsStr of
+       settingsList <- case maybeReadFuzzy settingsStr of
                      Just s ->
                          return s
                      Nothing ->
                          pgmError ("Can't parse " ++ show settingsFile)
+       let mySettings = Map.fromList settingsList
        platformConstants <- case maybeReadFuzzy platformConstantsStr of
                             Just s ->
                                 return s
                             Nothing ->
                                 pgmError ("Can't parse " ++
                                           show platformConstantsFile)
-       let getSetting key = case lookup key mySettings of
-                            Just xs -> return $ expandTopDir top_dir xs
-                            Nothing -> pgmError ("No entry for " ++ show key ++ " in " ++ show settingsFile)
+       -- See Note [Settings file] for a little more about this file. We're
+       -- just partially applying those functions and throwing 'Left's; they're
+       -- written in a very portable style to keep ghc-boot light.
+       let getSetting key = either pgmError pure $
+             getFilePathSetting0 top_dir settingsFile mySettings key
+           getToolSetting :: String -> IO String
            getToolSetting key = expandToolDir mtool_dir <$> getSetting key
-           getBooleanSetting key = case lookup key mySettings of
-                                   Just "YES" -> return True
-                                   Just "NO" -> return False
-                                   Just xs -> pgmError ("Bad value for " ++ show key ++ ": " ++ show xs)
-                                   Nothing -> pgmError ("No entry for " ++ show key ++ " in " ++ show settingsFile)
-           readSetting key = case lookup key mySettings of
-                             Just xs ->
-                                 case maybeRead xs of
-                                 Just v -> return v
-                                 Nothing -> pgmError ("Failed to read " ++ show key ++ " value " ++ show xs)
-                             Nothing -> pgmError ("No entry for " ++ show key ++ " in " ++ show settingsFile)
-       crossCompiling <- getBooleanSetting "cross compiling"
+           getBooleanSetting :: String -> IO Bool
+           getBooleanSetting key = either pgmError pure $
+             getBooleanSetting0 settingsFile mySettings key
        targetPlatformString <- getSetting "target platform string"
-       targetArch <- readSetting "target arch"
-       targetOS <- readSetting "target os"
-       targetWordSize <- readSetting "target word size"
-       targetUnregisterised <- getBooleanSetting "Unregisterised"
-       targetHasGnuNonexecStack <- readSetting "target has GNU nonexec stack"
-       targetHasIdentDirective <- readSetting "target has .ident directive"
-       targetHasSubsectionsViaSymbols <- readSetting "target has subsections via symbols"
        tablesNextToCode <- getBooleanSetting "Tables next to code"
        myExtraGccViaCFlags <- getSetting "GCC extra via C opts"
        -- On Windows, mingw is distributed with GHC,
@@ -200,7 +190,10 @@ initSysTools top_dir
        gccSupportsNoPie <- getBooleanSetting "C compiler supports -no-pie"
        cpp_prog <- getToolSetting "Haskell CPP command"
        cpp_args_str <- getSetting "Haskell CPP flags"
-       let unreg_cc_args = if targetUnregisterised
+
+       platform <- either pgmError pure $ getTargetPlatform settingsFile mySettings
+
+       let unreg_cc_args = if platformUnregisterised platform
                            then ["-DNO_REGS", "-DUSE_MINIINTERPRETER"]
                            else []
            cpp_args = map Option (words cpp_args_str)
@@ -250,17 +243,6 @@ initSysTools top_dir
 
        let iserv_prog = libexec "ghc-iserv"
 
-       let platform = Platform {
-                          platformArch = targetArch,
-                          platformOS   = targetOS,
-                          platformWordSize = targetWordSize,
-                          platformUnregisterised = targetUnregisterised,
-                          platformHasGnuNonexecStack = targetHasGnuNonexecStack,
-                          platformHasIdentDirective = targetHasIdentDirective,
-                          platformHasSubsectionsViaSymbols = targetHasSubsectionsViaSymbols,
-                          platformIsCrossCompiling = crossCompiling
-                      }
-
        integerLibrary <- getSetting "integer library"
        integerLibraryType <- case integerLibrary of
          "integer-gmp" -> pure IntegerGMP
@@ -358,7 +340,7 @@ initSysTools top_dir
 
          , sPlatformConstants = platformConstants
 
-         , sRawSettings    = mySettings
+         , sRawSettings    = settingsList
          }
 
 
diff --git a/compiler/main/SysTools/BaseDir.hs b/compiler/main/SysTools/BaseDir.hs
index 1293d1898a2a..f67d2def6d7d 100644
--- a/compiler/main/SysTools/BaseDir.hs
+++ b/compiler/main/SysTools/BaseDir.hs
@@ -27,7 +27,6 @@ import Panic
 
 import System.Environment (lookupEnv)
 import System.FilePath
-import Data.List
 
 -- Windows
 #if defined(mingw32_HOST_OS)
@@ -76,10 +75,6 @@ $topdir/../../{mingw, perl}/.
 
 -}
 
--- | Expand occurrences of the @$topdir@ interpolation in a string.
-expandTopDir :: FilePath -> String -> String
-expandTopDir = expandPathVar "topdir"
-
 -- | Expand occurrences of the @$tooldir@ interpolation in a string
 -- on Windows, leave the string untouched otherwise.
 expandToolDir :: Maybe FilePath -> String -> String
@@ -90,17 +85,6 @@ expandToolDir Nothing         _ = panic "Could not determine $tooldir"
 expandToolDir _ s = s
 #endif
 
--- | @expandPathVar var value str@
---
---   replaces occurences of variable @$var@ with @value@ in str.
-expandPathVar :: String -> FilePath -> String -> String
-expandPathVar var value str
-  | Just str' <- stripPrefix ('$':var) str
-  , null str' || isPathSeparator (head str')
-  = value ++ expandPathVar var value str'
-expandPathVar var value (x:xs) = x : expandPathVar var value xs
-expandPathVar _ _ [] = []
-
 -- | Returns a Unix-format path pointing to TopDir.
 findTopDir :: Maybe String -- Maybe TopDir path (without the '-B' prefix).
            -> IO String    -- TopDir (in Unix format '/' separated)
diff --git a/compiler/utils/Util.hs b/compiler/utils/Util.hs
index 5770f2ffdcc2..aa4afa54513f 100644
--- a/compiler/utils/Util.hs
+++ b/compiler/utils/Util.hs
@@ -92,9 +92,6 @@ module Util (
         readRational,
         readHexRational,
 
-        -- * read helpers
-        maybeRead, maybeReadFuzzy,
-
         -- * IO-ish utilities
         doesDirNameExist,
         getModificationUTCTime,
@@ -1254,25 +1251,6 @@ readHexRational__ ('0' : x : rest)
 
 readHexRational__ _ = Nothing
 
-
-
-
------------------------------------------------------------------------------
--- read helpers
-
-maybeRead :: Read a => String -> Maybe a
-maybeRead str = case reads str of
-                [(x, "")] -> Just x
-                _         -> Nothing
-
-maybeReadFuzzy :: Read a => String -> Maybe a
-maybeReadFuzzy str = case reads str of
-                     [(x, s)]
-                      | all isSpace s ->
-                         Just x
-                     _ ->
-                         Nothing
-
 -----------------------------------------------------------------------------
 -- Verify that the 'dirname' portion of a FilePath exists.
 --
diff --git a/libraries/ghc-boot/GHC/BaseDir.hs b/libraries/ghc-boot/GHC/BaseDir.hs
index cc8335514489..196ab2eb7205 100644
--- a/libraries/ghc-boot/GHC/BaseDir.hs
+++ b/libraries/ghc-boot/GHC/BaseDir.hs
@@ -14,8 +14,9 @@
 -- and so needs the top dir location to do that too.
 module GHC.BaseDir where
 
-import Prelude -- See note [Why do we import Prelude here?]
+import Prelude -- See Note [Why do we import Prelude here?]
 
+import Data.List
 import System.FilePath
 
 -- Windows
@@ -26,6 +27,21 @@ import System.Environment (getExecutablePath)
 import System.Environment (getExecutablePath)
 #endif
 
+-- | Expand occurrences of the @$topdir@ interpolation in a string.
+expandTopDir :: FilePath -> String -> String
+expandTopDir = expandPathVar "topdir"
+
+-- | @expandPathVar var value str@
+--
+--   replaces occurences of variable @$var@ with @value@ in str.
+expandPathVar :: String -> FilePath -> String -> String
+expandPathVar var value str
+  | Just str' <- stripPrefix ('$':var) str
+  , null str' || isPathSeparator (head str')
+  = value ++ expandPathVar var value str'
+expandPathVar var value (x:xs) = x : expandPathVar var value xs
+expandPathVar _ _ [] = []
+
 -- | Calculate the location of the base dir
 getBaseDir :: IO (Maybe String)
 #if defined(mingw32_HOST_OS)
diff --git a/libraries/ghc-boot/GHC/Settings.hs b/libraries/ghc-boot/GHC/Settings.hs
new file mode 100644
index 000000000000..fc9f95a58647
--- /dev/null
+++ b/libraries/ghc-boot/GHC/Settings.hs
@@ -0,0 +1,104 @@
+-- Note [Settings file]
+-- ~~~~~~~~~~~~~~~~~~~~
+--
+-- GHC has a file, `${top_dir}/settings`, which is the main source of run-time
+-- configuration. ghc-pkg needs just a little bit of it: the target platform CPU
+-- arch and OS. It uses that to figure out what subdirectory of `~/.ghc` is
+-- associated with the current version/target.
+--
+-- This module has just enough code to read key value pairs from the settings
+-- file, and read the target platform from those pairs.
+--
+-- The  "0" suffix is because the caller will partially apply it, and that will
+-- in turn be used a few more times.
+module GHC.Settings where
+
+import Prelude -- See Note [Why do we import Prelude here?]
+
+import GHC.BaseDir
+import GHC.Platform
+
+import Data.Char (isSpace)
+import Data.Map (Map)
+import qualified Data.Map as Map
+
+-----------------------------------------------------------------------------
+-- parts of settings file
+
+getTargetPlatform
+  :: FilePath -> RawSettings -> Either String Platform
+getTargetPlatform settingsFile mySettings = do
+  let
+    getBooleanSetting = getBooleanSetting0 settingsFile mySettings
+    readSetting :: (Show a, Read a) => String -> Either String a
+    readSetting = readSetting0 settingsFile mySettings
+
+  targetArch <- readSetting "target arch"
+  targetOS <- readSetting "target os"
+  targetWordSize <- readSetting "target word size"
+  targetUnregisterised <- getBooleanSetting "Unregisterised"
+  targetHasGnuNonexecStack <- readSetting "target has GNU nonexec stack"
+  targetHasIdentDirective <- readSetting "target has .ident directive"
+  targetHasSubsectionsViaSymbols <- readSetting "target has subsections via symbols"
+  crossCompiling <- getBooleanSetting "cross compiling"
+
+  pure $ Platform
+    { platformArch = targetArch
+    , platformOS   = targetOS
+    , platformWordSize = targetWordSize
+    , platformUnregisterised = targetUnregisterised
+    , platformHasGnuNonexecStack = targetHasGnuNonexecStack
+    , platformHasIdentDirective = targetHasIdentDirective
+    , platformHasSubsectionsViaSymbols = targetHasSubsectionsViaSymbols
+    , platformIsCrossCompiling = crossCompiling
+    }
+
+-----------------------------------------------------------------------------
+-- settings file helpers
+
+type RawSettings = Map String String
+
+-- | See Note [Settings file] for "0" suffix
+getSetting0
+  :: FilePath -> RawSettings -> String -> Either String String
+getSetting0 settingsFile mySettings key = case Map.lookup key mySettings of
+  Just xs -> Right xs
+  Nothing -> Left $ "No entry for " ++ show key ++ " in " ++ show settingsFile
+
+-- | See Note [Settings file] for "0" suffix
+getFilePathSetting0
+  :: FilePath -> FilePath -> RawSettings -> String -> Either String String
+getFilePathSetting0 top_dir settingsFile mySettings key =
+  expandTopDir top_dir <$> getSetting0 settingsFile mySettings key
+
+-- | See Note [Settings file] for "0" suffix
+getBooleanSetting0
+  :: FilePath -> RawSettings -> String -> Either String Bool
+getBooleanSetting0 settingsFile mySettings key = do
+  rawValue <- getSetting0 settingsFile mySettings key
+  case rawValue of
+    "YES" -> Right True
+    "NO" -> Right False
+    xs -> Left $ "Bad value for " ++ show key ++ ": " ++ show xs
+
+-- | See Note [Settings file] for "0" suffix
+readSetting0
+  :: (Show a, Read a) => FilePath -> RawSettings -> String -> Either String a
+readSetting0 settingsFile mySettings key = case Map.lookup key mySettings of
+  Just xs -> case maybeRead xs of
+    Just v -> Right v
+    Nothing -> Left $ "Failed to read " ++ show key ++ " value " ++ show xs
+  Nothing -> Left $ "No entry for " ++ show key ++ " in " ++ show settingsFile
+
+-----------------------------------------------------------------------------
+-- read helpers
+
+maybeRead :: Read a => String -> Maybe a
+maybeRead str = case reads str of
+  [(x, "")] -> Just x
+  _ -> Nothing
+
+maybeReadFuzzy :: Read a => String -> Maybe a
+maybeReadFuzzy str = case reads str of
+  [(x, s)] | all isSpace s -> Just x
+  _ -> Nothing
diff --git a/libraries/ghc-boot/ghc-boot.cabal.in b/libraries/ghc-boot/ghc-boot.cabal.in
index 15721b1489a9..650f7518dc42 100644
--- a/libraries/ghc-boot/ghc-boot.cabal.in
+++ b/libraries/ghc-boot/ghc-boot.cabal.in
@@ -44,10 +44,12 @@ Library
             GHC.ForeignSrcLang
             GHC.HandleEncoding
             GHC.Platform
+            GHC.Settings
 
     build-depends: base       >= 4.7 && < 4.14,
                    binary     == 0.8.*,
                    bytestring == 0.10.*,
+                   containers >= 0.5 && < 0.7,
                    directory  >= 1.2 && < 1.4,
                    filepath   >= 1.3 && < 1.5,
                    ghc-boot-th == @ProjectVersionMunged@
diff --git a/testsuite/tests/driver/T3007/Makefile b/testsuite/tests/driver/T3007/Makefile
index 52b3331af1eb..e946350d45cd 100644
--- a/testsuite/tests/driver/T3007/Makefile
+++ b/testsuite/tests/driver/T3007/Makefile
@@ -8,14 +8,16 @@ clean:
 	rm -rf A/dist B/dist
 	rm -rf package.conf
 
+# --no-user-package-db to avoid warning about missing settings file
+
 T3007:
 	$(MAKE) -s --no-print-directory clean
 	'$(GHC_PKG)' init package.conf
 	cd A && '$(TEST_HC)' $(TEST_HC_OPTS) -v0 --make Setup
-	cd A && ./Setup configure -v0 --with-compiler='$(TEST_HC)' --ghc-pkg-option=--global-package-db=../package.conf --ghc-option=-package-db../package.conf
+	cd A && ./Setup configure -v0 --with-compiler='$(TEST_HC)' --ghc-pkg-option=--global-package-db=../package.conf --ghc-pkg-option=--no-user-package-db --ghc-option=-package-db../package.conf
 	cd A && ./Setup build -v0
 	cd A && ./Setup register --inplace -v0
 	cd B && '$(TEST_HC)' $(TEST_HC_OPTS) -v0 --make Setup
-	cd B && ./Setup configure -v0 --with-compiler='$(TEST_HC)' --ghc-pkg-option=--global-package-db=../package.conf --ghc-option=-package-db../package.conf
+	cd B && ./Setup configure -v0 --with-compiler='$(TEST_HC)' --ghc-pkg-option=--global-package-db=../package.conf --ghc-pkg-option=--no-user-package-db --ghc-option=-package-db../package.conf
 	cd B && ./Setup build -v0
 
diff --git a/utils/ghc-pkg/Main.hs b/utils/ghc-pkg/Main.hs
index 02ac7d22d90c..0e28ce93533a 100644
--- a/utils/ghc-pkg/Main.hs
+++ b/utils/ghc-pkg/Main.hs
@@ -17,6 +17,9 @@
 #endif
 #endif
 
+-- Fine if this comes from make/Hadrian or the pre-built base.
+#include <ghcplatform.h>
+
 -----------------------------------------------------------------------------
 --
 -- (c) The University of Glasgow 2004-2009.
@@ -27,11 +30,16 @@
 
 module Main (main) where
 
-import Version ( version, targetOS, targetARCH )
+import Version ( version )
 import qualified GHC.PackageDb as GhcPkg
 import GHC.PackageDb (BinaryStringRep(..))
 import GHC.HandleEncoding
 import GHC.BaseDir (getBaseDir)
+import GHC.Settings (getTargetPlatform, maybeReadFuzzy)
+import GHC.Platform
+  ( platformArch, platformOS
+  , stringEncodeArch, stringEncodeOS
+  )
 import qualified Distribution.Simple.PackageIndex as PackageIndex
 import qualified Data.Graph as Graph
 import qualified Distribution.ModuleName as ModuleName
@@ -592,14 +600,14 @@ getPkgDatabases :: Verbosity
                           -- commands that just read the DB, such as 'list'.
 
 getPkgDatabases verbosity mode use_user use_cache expand_vars my_flags = do
-  -- first we determine the location of the global package config.  On Windows,
+  -- Second we determine the location of the global package config.  On Windows,
   -- this is found relative to the ghc-pkg.exe binary, whereas on Unix the
   -- location is passed to the binary using the --global-package-db flag by the
   -- wrapper script.
   let err_msg = "missing --global-package-db option, location of global package database unknown\n"
   global_conf <-
      case [ f | FlagGlobalConfig f <- my_flags ] of
-        -- See note [Base Dir] for more information on the base dir / top dir.
+        -- See Note [Base Dir] for more information on the base dir / top dir.
         [] -> do mb_dir <- getBaseDir
                  case mb_dir of
                    Nothing  -> die err_msg
@@ -628,7 +636,25 @@ getPkgDatabases verbosity mode use_user use_cache expand_vars my_flags = do
       [] -> case e_appdir of
         Left _    -> return Nothing
         Right appdir -> do
-          let subdir = targetARCH ++ '-':targetOS ++ '-':Version.version
+          -- See Note [Settings File] about this file, and why we need GHC to share it with us.
+          let settingsFile = top_dir </> "settings"
+          exists_settings_file <- doesFileExist settingsFile
+          (arch, os) <- case exists_settings_file of
+            False -> do
+              warn $ "WARNING: settings file doesn't exist " ++ show settingsFile
+              warn "cannot know target platform so guessing target == host (native compiler)."
+              pure (HOST_ARCH, HOST_OS)
+            True -> do
+              settingsStr <- readFile settingsFile
+              mySettings <- case maybeReadFuzzy settingsStr of
+                Just s -> pure $ Map.fromList s
+                -- It's excusable to not have a settings file (for now at
+                -- least) but completely inexcusable to have a malformed one.
+                Nothing -> die $ "Can't parse settings file " ++ show settingsFile
+              case getTargetPlatform settingsFile mySettings of
+                Right platform -> pure (stringEncodeArch $ platformArch platform, stringEncodeOS $ platformOS platform)
+                Left e -> die e
+          let subdir = arch ++ '-':os ++ '-':Version.version
               dir = appdir </> subdir
           r <- lookForPackageDBIn dir
           case r of
diff --git a/utils/ghc-pkg/ghc.mk b/utils/ghc-pkg/ghc.mk
index a063e0b78737..37ce0a7c5bb5 100644
--- a/utils/ghc-pkg/ghc.mk
+++ b/utils/ghc-pkg/ghc.mk
@@ -69,7 +69,10 @@ endif
 
 $(eval $(call build-prog,utils/ghc-pkg,dist,0))
 
-$(ghc-pkg_INPLACE) : | $(INPLACE_PACKAGE_CONF)/.
+# ghc-pkg uses `settings` to figure out the target platform to figure out a
+# subdirectory for the user pkg db. So make sure `settings` exists (alterative
+# is to specify global package db only.
+$(ghc-pkg_INPLACE) : | $(INPLACE_PACKAGE_CONF)/. $(INPLACE_LIB)/settings
 
 utils/ghc-pkg/dist/package-data.mk: \
     utils/ghc-pkg/dist/build/Version.hs
-- 
GitLab