From a9c93bdd8b027d6de09a3eada7721e7fd2d3e050 Mon Sep 17 00:00:00 2001
From: "Edward Z. Yang" <ezyang@cs.stanford.edu>
Date: Mon, 26 Oct 2015 20:48:36 +0100
Subject: [PATCH] Implement MIN_VERSION and VERSION macros natively in GHC.

Test Plan: validate

Reviewers: austin, thomie, bgamari

Reviewed By: thomie

Subscribers: thomie

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

GHC Trac Issues: #10970
---
 compiler/main/DriverPipeline.hs       | 45 +++++++++++++++++++++++++++
 compiler/main/Packages.hs             | 11 ++++++-
 docs/users_guide/phases.rst           | 16 ++++++++++
 testsuite/.gitignore                  |  3 +-
 testsuite/tests/driver/T10970.hs      |  6 ++++
 testsuite/tests/driver/T10970.stdout  |  2 ++
 testsuite/tests/driver/T10970a.hs     |  6 ++++
 testsuite/tests/driver/T10970a.stderr |  6 ++++
 testsuite/tests/driver/all.T          |  3 ++
 9 files changed, 96 insertions(+), 2 deletions(-)
 create mode 100644 testsuite/tests/driver/T10970.hs
 create mode 100644 testsuite/tests/driver/T10970.stdout
 create mode 100644 testsuite/tests/driver/T10970a.hs
 create mode 100644 testsuite/tests/driver/T10970a.stderr

diff --git a/compiler/main/DriverPipeline.hs b/compiler/main/DriverPipeline.hs
index 373afba3faa7..697353e5c3c1 100644
--- a/compiler/main/DriverPipeline.hs
+++ b/compiler/main/DriverPipeline.hs
@@ -74,6 +74,7 @@ import Data.List        ( isSuffixOf )
 import Data.Maybe
 import Data.Char
 import Data.Time
+import Data.Version
 
 -- ---------------------------------------------------------------------------
 -- Pre-process
@@ -2049,6 +2050,20 @@ doCpp dflags raw input_fn output_fn = do
           , "-include", ghcVersionH
           ]
 
+    -- MIN_VERSION macros
+    let uids = explicitPackages (pkgState dflags)
+        pkgs = catMaybes (map (lookupPackage dflags) uids)
+    mb_macro_include <-
+        -- Only generate if we have (1) we have set -hide-all-packages
+        -- (so we don't generate a HUGE macro file of things we don't
+        -- care about but are exposed) and (2) we actually have packages
+        -- to write macros for!
+        if gopt Opt_HideAllPackages dflags && not (null pkgs)
+            then do macro_stub <- newTempName dflags "h"
+                    writeFile macro_stub (generatePackageVersionMacros pkgs)
+                    return [SysTools.FileOption "-include" macro_stub]
+            else return []
+
     cpp_prog       (   map SysTools.Option verbFlags
                     ++ map SysTools.Option include_paths
                     ++ map SysTools.Option hsSourceCppOpts
@@ -2058,6 +2073,7 @@ doCpp dflags raw input_fn output_fn = do
                     ++ map SysTools.Option hscpp_opts
                     ++ map SysTools.Option sse_defs
                     ++ map SysTools.Option avx_defs
+                    ++ mb_macro_include
         -- Set the language mode to assembler-with-cpp when preprocessing. This
         -- alleviates some of the C99 macro rules relating to whitespace and the hash
         -- operator, which we tend to abuse. Clang in particular is not very happy
@@ -2087,6 +2103,35 @@ getBackendDefs dflags | hscTarget dflags == HscLlvm = do
 getBackendDefs _ =
     return []
 
+-- ---------------------------------------------------------------------------
+-- Macros (cribbed from Cabal)
+
+generatePackageVersionMacros :: [PackageConfig] -> String
+generatePackageVersionMacros pkgs = concat
+  [ "/* package " ++ sourcePackageIdString pkg ++ " */\n"
+  ++ generateMacros "" pkgname version
+  | pkg <- pkgs
+  , let version = packageVersion pkg
+        pkgname = map fixchar (packageNameString pkg)
+  ]
+
+fixchar :: Char -> Char
+fixchar '-' = '_'
+fixchar c   = c
+
+generateMacros :: String -> String -> Version -> String
+generateMacros prefix name version =
+  concat
+  ["#define ", prefix, "VERSION_",name," ",show (showVersion version),"\n"
+  ,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"
+  ,"  (major1) <  ",major1," || \\\n"
+  ,"  (major1) == ",major1," && (major2) <  ",major2," || \\\n"
+  ,"  (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"
+  ,"\n\n"
+  ]
+  where
+    (major1:major2:minor:_) = map show (versionBranch version ++ repeat 0)
+
 -- ---------------------------------------------------------------------------
 -- join object files into a single relocatable object file, using ld -r
 
diff --git a/compiler/main/Packages.hs b/compiler/main/Packages.hs
index 0e32947b31e7..9f60c1cc28be 100644
--- a/compiler/main/Packages.hs
+++ b/compiler/main/Packages.hs
@@ -7,7 +7,7 @@ module Packages (
         module PackageConfig,
 
         -- * Reading the package config, and processing cmdline args
-        PackageState(preloadPackages),
+        PackageState(preloadPackages, explicitPackages),
         emptyPackageState,
         initPackages,
         readPackageConfigs,
@@ -245,6 +245,10 @@ data PackageState = PackageState {
   -- is always mentioned before the packages it depends on.
   preloadPackages      :: [UnitId],
 
+  -- | Packages which we explicitly depend on (from a command line flag).
+  -- We'll use this to generate version macros.
+  explicitPackages      :: [UnitId],
+
   -- | This is a full map from 'ModuleName' to all modules which may possibly
   -- be providing it.  These providers may be hidden (but we'll still want
   -- to report them in error messages), or it may be an ambiguous import.
@@ -255,6 +259,7 @@ emptyPackageState :: PackageState
 emptyPackageState = PackageState {
     pkgIdMap = emptyUFM,
     preloadPackages = [],
+    explicitPackages = [],
     moduleToPkgConfAll = Map.empty
     }
 
@@ -961,6 +966,10 @@ mkPackageState dflags0 pkgs0 preload0 = do
 
   let pstate = PackageState{
     preloadPackages     = dep_preload,
+    explicitPackages    = foldUFM (\pkg xs ->
+                            if elemUFM (packageConfigId pkg) vis_map
+                                then packageConfigId pkg : xs
+                                else xs) [] pkg_db,
     pkgIdMap            = pkg_db,
     moduleToPkgConfAll  = mkModuleToPkgConfAll dflags pkg_db vis_map
     }
diff --git a/docs/users_guide/phases.rst b/docs/users_guide/phases.rst
index e7a46950b555..8945e3bbe707 100644
--- a/docs/users_guide/phases.rst
+++ b/docs/users_guide/phases.rst
@@ -340,6 +340,22 @@ defined by your local GHC installation, the following trick is useful:
     architecture, where⟨arch⟩ is the name of the current architecture
     (eg. ``i386``, ``x86_64``, ``powerpc``, ``sparc``, etc.).
 
+``VERSION_pkgname``
+    This macro is available starting GHC 8.0.  It is defined for every
+    exposed package, but only if the ``-hide-all-packages`` flag
+    is set.  This macro expands to a string recording the
+    version of ``pkgname`` that is exposed for module import.
+    It is identical in behavior to the ``VERSION_pkgname`` macros
+    that Cabal defines.
+
+``MIN_VERSION_pkgname(x,y,z)``
+    This macro is available starting GHC 8.0.  It is defined for every
+    exposed package, but only if the ``-hide-all-packages`` flag
+    is set. This macro is provided for convenience to write CPP
+    conditionals testing if a package version is ``x.y.z`` or
+    less.  It is identical in behavior to the ``MIN_VERSION_pkgname``
+    macros that Cabal defines.
+
 .. _cpp-string-gaps:
 
 CPP and string gaps
diff --git a/testsuite/.gitignore b/testsuite/.gitignore
index a2b645429b5c..d9f7dccabd91 100644
--- a/testsuite/.gitignore
+++ b/testsuite/.gitignore
@@ -532,6 +532,7 @@ mk/ghcconfig*_bin_ghc*.exe.mk
 /tests/driver/Hello062a.hs
 /tests/driver/Hello062b.hs
 /tests/driver/Hello062c.hs
+/tests/driver/T10970
 /tests/driver/T1959/E.hs
 /tests/driver/T1959/prog
 /tests/driver/T3007/A/Setup
@@ -631,8 +632,8 @@ mk/ghcconfig*_bin_ghc*.exe.mk
 /tests/driver/recomp013/C.hs
 /tests/driver/recomp014/A.hs
 /tests/driver/recomp014/A1.hs
-/tests/driver/recomp014/B.hsig
 /tests/driver/recomp014/B.hs-boot
+/tests/driver/recomp014/B.hsig
 /tests/driver/recomp014/C.hs
 /tests/driver/recomp014/recomp014
 /tests/driver/retc001/B.hs
diff --git a/testsuite/tests/driver/T10970.hs b/testsuite/tests/driver/T10970.hs
new file mode 100644
index 000000000000..9de4f80650f4
--- /dev/null
+++ b/testsuite/tests/driver/T10970.hs
@@ -0,0 +1,6 @@
+{-# LANGUAGE CPP #-}
+main = do
+    putStrLn VERSION_containers
+#if MIN_VERSION_base(3,0,0)
+    putStrLn "OK"
+#endif
diff --git a/testsuite/tests/driver/T10970.stdout b/testsuite/tests/driver/T10970.stdout
new file mode 100644
index 000000000000..2a2835658b19
--- /dev/null
+++ b/testsuite/tests/driver/T10970.stdout
@@ -0,0 +1,2 @@
+0.5.6.2
+OK
diff --git a/testsuite/tests/driver/T10970a.hs b/testsuite/tests/driver/T10970a.hs
new file mode 100644
index 000000000000..9de4f80650f4
--- /dev/null
+++ b/testsuite/tests/driver/T10970a.hs
@@ -0,0 +1,6 @@
+{-# LANGUAGE CPP #-}
+main = do
+    putStrLn VERSION_containers
+#if MIN_VERSION_base(3,0,0)
+    putStrLn "OK"
+#endif
diff --git a/testsuite/tests/driver/T10970a.stderr b/testsuite/tests/driver/T10970a.stderr
new file mode 100644
index 000000000000..74c68211685e
--- /dev/null
+++ b/testsuite/tests/driver/T10970a.stderr
@@ -0,0 +1,6 @@
+
+T10970a.hs:4:0: error:
+     error: missing binary operator before token "("
+     #if MIN_VERSION_base(3,0,0)
+     ^
+`gcc' failed in phase `C pre-processor'. (Exit code: 1)
diff --git a/testsuite/tests/driver/all.T b/testsuite/tests/driver/all.T
index 08fdc1f6f6c4..f946055c21a3 100644
--- a/testsuite/tests/driver/all.T
+++ b/testsuite/tests/driver/all.T
@@ -456,3 +456,6 @@ test('T365',
 
 test('T9360a', normal, run_command, ['{compiler} --interactive -e ""'])
 test('T9360b', normal, run_command, ['{compiler} -e "" --interactive'])
+
+test('T10970', normal, compile_and_run, ['-hide-all-packages -package base -package containers'])
+test('T10970a', normal, compile_fail, [''])
-- 
GitLab