packages feed

qtah-generator-0.1.1: src/Graphics/UI/Qtah/Generator/Flags.hs

-- This file is part of Qtah.
--
-- Copyright 2015-2016 Bryan Gardiner <bog@khumba.net>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

-- | Haskell definitions for preprocessor flags that Qt uses for conditional
-- compilation.
--
-- A list of flags enabled on your system can be obtained with:
--
-- > gcc -dM -E $(pkg-config --cflags QtCore) /usr/include/qt4/Qt/qconfig.h | grep '#define QT'
--
-- Using @qglobal.h@ and @#define Q@ provides additional defintions,
-- e.g. version and windowing system information.
module Graphics.UI.Qtah.Generator.Flags (
  Version,
  qtVersion,
  keypadNavigation,
  qdoc,
  qrealFloat,
  wsWince,
  ) where

import Control.Monad (unless)
import Data.Char (isDigit, isSpace)
import Data.List (isPrefixOf)
import Graphics.UI.Qtah.Generator.Common (fromMaybeM, splitOn)
import System.Directory (findExecutable)
import System.Environment (lookupEnv)
import System.Exit (ExitCode (ExitSuccess))
import System.IO.Unsafe (unsafePerformIO)
import System.Process (readProcessWithExitCode)

-- | A type synonym for Qt version specifications.  These are just lists of
-- integers, of length two.  Examples are @[4, 8]@ and @[5, 0]@ to denote
-- versions 4.8 and 5.0 respectively.  A third component may be used in the
-- future, if necessary.
type Version = [Int]

-- | This is initialized at program startup with the version of Qt that the
-- generator will work with.  This controls which functions and types are made
-- available in the API.
--
-- This is determined the following method:
--
-- * If @QTAH_QT=x.y@ is in the environment, then this value will be used.
--
-- * Otherwise, if @QTAH_QT=x@ is in the environment, then we query @qmake
-- -qt=$QTAH_QT -version@ for the version of Qt to use.
--
-- * Otherwise, we query @qmake -version@ for the version of Qt to use.  Setting
-- @QT_SELECT@ in the environment can select a major version of Qt to use.
--
-- For more information on @qmake -qt@ and @QT_SELECT@, see @man qtchooser@.
qtVersion :: Version
{-# NOINLINE qtVersion #-}
qtVersion = unsafePerformIO readQtVersion

keypadNavigation :: Bool
keypadNavigation = False

qdoc :: Bool
qdoc = False

-- | Whether Qt was configured with qreal=float instead of double.
qrealFloat :: Bool
{-# NOINLINE qrealFloat #-}
qrealFloat = unsafePerformIO $ readBool "QTAH_QREAL_FLOAT" False

wsWince :: Bool
wsWince = False

-- | Reads a Qt version from the environment variable @QTAH_QT@.  See
-- 'qtVersion'.
readQtVersion :: IO Version
readQtVersion = do
  maybeStr <- fmap (\x -> case x of
                      Just "" -> Nothing
                      _ -> x) $
              lookupEnv "QTAH_QT"
  case maybeStr of
    Just str -> do
      let strs = splitOn '.' str
      unless (length strs `elem` [1, 2] && all (\n -> not (null n) && all isDigit n) strs) $
        fail $ concat
        ["qtah-generator requires QTAH_QT=x or QTAH_QT=x.y, can't parse value ", show str, "."]
      let version = map (read :: String -> Int) strs
      case version of
        [_, _] -> return version
        [x] -> queryQmakeQtVersion $ Just x
        _ -> fail $ concat
             ["qtah-generator: Internal error, incorrect parsing of QTAH_QT value ", show str, "."]
    Nothing -> queryQmakeQtVersion Nothing

  where queryQmakeQtVersion :: Maybe Int -> IO Version
        queryQmakeQtVersion maybePreferredMajorVersion = do
          case maybePreferredMajorVersion of
            Nothing ->
              -- No major version preference, so take whatever Qt is available.
              queryQmakeQtVersion' []
            Just preferredMajorVersion -> do
              -- Even though we have a preferred major version, we don't want to
              -- run "qmake -qt=X -version" initially because we might be on a
              -- system (NixOS) where qtchooser isn't available and the only
              -- qmake available *is* the desired version.  Only pass "-qt=X" if
              -- we get the wrong default version.
              defaultVersion <- queryQmakeQtVersion' []
              case defaultVersion of
                (x:_) | x == preferredMajorVersion -> return defaultVersion
                _ -> queryQmakeQtVersion' ["-qt=" ++ show preferredMajorVersion]

        queryQmakeQtVersion' :: [String] -> IO Version
        queryQmakeQtVersion' extraArgs = do
          qmakePath <- findQMake
          let args = extraArgs ++ ["-version"]
          (exitCode, out, err) <- readProcessWithExitCode qmakePath args ""
          let qmakeDebugWords =
                ["  Ran ", show (qmakePath : args), ".\nStdout:\n", out, "\nStderr:\n", err]
          unless (exitCode == ExitSuccess) $
            fail $ concat $ "qtah-generator: qmake returned non-zero exit code." : qmakeDebugWords

          let versionLinePrefix = "Using Qt version "

              maybeVersionStrs = do
                versionLine <- expectSingle $
                               filter (versionLinePrefix `isPrefixOf`) $
                               lines out
                let str = takeWhile (not . isSpace) $ drop (length versionLinePrefix) versionLine
                    strs = take 2 $ splitOn '.' str
                if length strs == 2 && all (\n -> not (null n) && all isDigit n) strs
                  then Just strs
                  else Nothing

          case maybeVersionStrs of
            Just strs -> return $ map (read :: String -> Int) strs
            Nothing ->
              fail $ concat $
              "qtah-generator: Can't parse Qt version from qmake output." : qmakeDebugWords

        expectSingle :: [a] -> Maybe a
        expectSingle [x] = Just x
        expectSingle _ = Nothing

-- | Reads a boolean value from the program's environment.  If the variable is
-- set and non-empty, then if must be one of the strings @true@ or @false@.  An
-- empty or unset value is treated as the provided default value.
readBool :: String -> Bool -> IO Bool
readBool name defaultValue = do
  maybeStr <- lookupEnv name
  case maybeStr of
    Nothing -> return defaultValue
    Just str -> case str of
      "" -> return defaultValue
      "true" -> return True
      "false" -> return False
      s -> fail $ concat
           ["qtah-generator: Expected a boolean value for ", name,
            " (true/false).  Got ", show s, "."]

findQMake :: IO FilePath
findQMake = lookupEnv "QTAH_QMAKE" >>= fromMaybeM findQmake
  where findQmake =
          findExecutable "qmake" >>=
          fromMaybeM
          (fail $ "qtah-generator: Can't find qmake.  Please ensure qmake " ++
           "is installed and set QTAH_QMAKE to qmake's path.")