Extend ghc environment file features
authorDuncan Coutts <duncan@well-typed.com>
Tue, 5 Jan 2016 21:13:26 +0000 (22:13 +0100)
committerBen Gamari <ben@smart-cactus.org>
Tue, 5 Jan 2016 21:13:39 +0000 (22:13 +0100)
A set of changes to enable local ghc env files to be useful for tools
like cabal. Ultimately it will allow cabal to maintain a ghc env file so
that users can simple run ghc or ghci in a project directory and get the
expected environment of the project.

Change the name of .ghc.environment files to include the platform and
ghc version, e.g. .ghc.environment.x86_64-linux-7.6.3, since their
content is version specific. Strictly speaking this is not backwards
compatible, but we think this feature is not widely used yet.

"Look up" for a local env file, like the behaviour of git/darcs etc. So
you can be anywhere within a project and get the expected environment.

Don't look for local env files when -hide-all-packages is given.

Extend the syntax of env files to allow specifying package dbs too.

Test Plan:
Currently completely untested. Compiles, that is all.
Sorry, have to disappear for the hols.

Reviewers: hvr, ezyang, austin, bgamari

Reviewed By: ezyang, bgamari

Subscribers: thomie

Differential Revision: https://phabricator.haskell.org/D1668

GHC Trac Issues: #11268

compiler/main/DynFlags.hs
docs/users_guide/packages.rst

index 29c1423..d2b46c1 100644 (file)
@@ -987,7 +987,15 @@ opt_i dflags = sOpt_i (settings dflags)
 versionedAppDir :: DynFlags -> IO FilePath
 versionedAppDir dflags = do
   appdir <- getAppUserDataDirectory (programName dflags)
-  return $ appdir </> (TARGET_ARCH ++ '-':TARGET_OS ++ '-':projectVersion dflags)
+  return $ appdir </> versionedFilePath dflags
+
+-- | A filepath like @x86_64-linux-7.6.3@ with the platform string to use when
+-- constructing platform-version-dependent files that need to co-exist.
+--
+versionedFilePath :: DynFlags -> FilePath
+versionedFilePath dflags =     TARGET_ARCH
+                        ++ '-':TARGET_OS
+                        ++ '-':projectVersion dflags
   -- NB: This functionality is reimplemented in Cabal, so if you
   -- change it, be sure to update Cabal.
 
@@ -3835,46 +3843,58 @@ setUnitId p s =  s{ thisPackage = stringToUnitId p }
 -- | Find the package environment (if one exists)
 --
 -- We interpret the package environment as a set of package flags; to be
--- specific, if we find a package environment
+-- specific, if we find a package environment file like
 --
--- > id1
--- > id2
--- > ..
--- > idn
+-- > clear-package-db
+-- > global-package-db
+-- > package-db blah/package.conf.d
+-- > package-id id1
+-- > package-id id2
 --
 -- we interpret this as
 --
 -- > [ -hide-all-packages
+-- > , -clear-package-db
+-- > , -global-package-db
+-- > , -package-db blah/package.conf.d
 -- > , -package-id id1
 -- > , -package-id id2
--- > , ..
--- > , -package-id idn
 -- > ]
+--
+-- There's also an older syntax alias for package-id, which is just an
+-- unadorned package id
+--
+-- > id1
+-- > id2
+--
 interpretPackageEnv :: DynFlags -> IO DynFlags
 interpretPackageEnv dflags = do
     mPkgEnv <- runMaybeT $ msum $ [
                    getCmdLineArg >>= \env -> msum [
-                       loadEnvFile  env
-                     , loadEnvName  env
+                       probeEnvFile env
+                     , probeEnvName env
                      , cmdLineError env
                      ]
                  , getEnvVar >>= \env -> msum [
-                       loadEnvFile env
-                     , loadEnvName env
-                     , envError    env
+                       probeEnvFile env
+                     , probeEnvName env
+                     , envError     env
+                     ]
+                 , notIfHideAllPackages >> msum [
+                       findLocalEnvFile >>= probeEnvFile
+                     , probeEnvName defaultEnvName
                      ]
-                 , loadEnvFile localEnvFile
-                 , loadEnvName defaultEnvName
                  ]
     case mPkgEnv of
       Nothing ->
         -- No environment found. Leave DynFlags unchanged.
         return dflags
-      Just ids -> do
+      Just envfile -> do
+        content <- readFile envfile
         let setFlags :: DynP ()
             setFlags = do
               setGeneralFlag Opt_HideAllPackages
-              mapM_ exposePackageId (lines ids)
+              parseEnvFile envfile content
 
             (_, dflags') = runCmdLine (runEwM setFlags) dflags
 
@@ -3887,13 +3907,31 @@ interpretPackageEnv dflags = do
      appdir <- liftMaybeT $ versionedAppDir dflags
      return $ appdir </> "environments" </> name
 
-    loadEnvName :: String -> MaybeT IO String
-    loadEnvName name = loadEnvFile =<< namedEnvPath name
+    probeEnvName :: String -> MaybeT IO FilePath
+    probeEnvName name = probeEnvFile =<< namedEnvPath name
 
-    loadEnvFile :: String -> MaybeT IO String
-    loadEnvFile path = do
+    probeEnvFile :: FilePath -> MaybeT IO FilePath
+    probeEnvFile path = do
       guard =<< liftMaybeT (doesFileExist path)
-      liftMaybeT $ readFile path
+      return path
+
+    parseEnvFile :: FilePath -> String -> DynP ()
+    parseEnvFile envfile = mapM_ parseEntry . lines
+      where
+        parseEntry str = case words str of
+          ["package-db", db]    -> addPkgConfRef (PkgConfFile (envdir </> db))
+            -- relative package dbs are interpreted relative to the env file
+            where envdir = takeDirectory envfile
+          ["clear-package-db"]  -> clearPkgConf
+          ["global-package-db"] -> addPkgConfRef GlobalPkgConf
+          ["user-package-db"]   -> addPkgConfRef UserPkgConf
+          ["package-id", pkgid] -> exposePackageId pkgid
+          -- and the original syntax introduced in 7.10:
+          [pkgid]               -> exposePackageId pkgid
+          []                    -> return ()
+          _                     -> throwGhcException $ CmdLineError $
+                                        "Can't parse environment file entry: "
+                                     ++ envfile ++ ": " ++ str
 
     -- Various ways to define which environment to use
 
@@ -3908,11 +3946,34 @@ interpretPackageEnv dflags = do
         Left err  -> if isDoesNotExistError err then mzero
                                                 else liftMaybeT $ throwIO err
 
+    notIfHideAllPackages :: MaybeT IO ()
+    notIfHideAllPackages =
+      guard (not (gopt Opt_HideAllPackages dflags))
+
     defaultEnvName :: String
     defaultEnvName = "default"
 
-    localEnvFile :: FilePath
-    localEnvFile = "./.ghc.environment"
+    -- e.g. .ghc.environment.x86_64-linux-7.6.3
+    localEnvFileName :: FilePath
+    localEnvFileName = ".ghc.environment" <.> versionedFilePath dflags
+
+    -- Search for an env file, starting in the current dir and looking upwards.
+    -- Fail if we get to the users home dir or the filesystem root. That is,
+    -- we don't look for an env file in the user's home dir. The user-wide
+    -- env lives in ghc's versionedAppDir/environments/default
+    findLocalEnvFile :: MaybeT IO FilePath
+    findLocalEnvFile = do
+        curdir  <- liftMaybeT getCurrentDirectory
+        homedir <- liftMaybeT getHomeDirectory
+        let probe dir | isDrive dir || dir == homedir
+                      = mzero
+            probe dir = do
+              let file = dir </> localEnvFileName
+              exists <- liftMaybeT (doesFileExist file)
+              if exists
+                then return file
+                else probe (takeDirectory dir)
+        probe curdir
 
     -- Error reporting
 
index 1eb7c31..c2e2709 100644 (file)
@@ -1311,26 +1311,47 @@ Package environments
 .. index::
    single: package environments
 
-A *package environment* is a file that tells ``ghc`` precisely which
-packages should be visible. It contains package IDs, one per line:
+A *package environment file* is a file that tells ``ghc`` precisely which
+packages should be visible. It can be used to create environments for ``ghc``
+or ``ghci`` that are local to a shell session or to some file system location.
+They are intended to be managed by build/package tools, to enable ``ghc`` and
+``ghci`` to automatically use an environment created by the tool.
+
+The file contains package IDs and optionally package databases, one directive
+per line:
 
 ::
 
-    package_id_1
-    package_id_2
+    clear-package-db
+    global-package-db
+    user-package-db
+    package-db db.d/
+    package-id id_1
+    package-id id_2
     ...
-    package_id_n
+    package-id id_n
 
-If a package environment is found, it is equivalent to passing these
+If such a package environment is found, it is equivalent to passing these
 command line arguments to ``ghc``:
 
 ::
 
     -hide-all-packages
-    -package-id package_id_1
-    -package-id package_id_2
+    -clear-package-db
+    -global-package-db
+    -user-package-db
+    -package-db db.d/
+    -package-id id_1
+    -package-id id_2
     ...
-    -package-id package_id_n
+    -package-id id_n
+
+Note the implicit ``-hide-all-packages`` and the fact that it is
+``-package-id``, not ``-package``. This is because the environment specifies
+precisely which packages should be visible.
+
+Note that for the ``package-db`` directive, if a relative path is given it
+must be relative to the location of the package environment file.
 
 In order, ``ghc`` will look for the package environment in the following
 locations:
@@ -1346,7 +1367,11 @@ locations:
 -  File ``$HOME/.ghc/arch-os-version/environments/name`` if the
    environment variable ``GHC_ENVIRONMENT`` is set to ⟨name⟩.
 
--  File ``./.ghc.environment`` if it exists.
+Additionally, unless ``-hide-all-packages`` is specified ``ghc`` will also
+look for the package environment in the following locations:
+
+-  File ``.ghc.environment.arch-os-version`` if it exists in the current
+   directory or any parent directory (but not the user's home directory).
 
 -  File ``$HOME/.ghc/arch-os-version/environments/default`` if it
    exists.