Изменение способа установки Setup.hs

В пакете encoding используется HaXml в его построении script (в Setup.hs). Случается, что биты интерфейса изменяются между HaXml-1.19 и HaXml-1.22. Было бы неплохо, если бы пакет кодировки мог строить с любой версией. Я попытался использовать обычный трюк Cabal, а именно, что-то вроде

{-# LANGUAGE CPP #-}
#if MIN_VERSION_HaXml(1,22,0)
-- HaXml-1.22 code
#else
-- HaXml-1.19 code
#endif

... но магия не может существовать до того, как пакет настроен, и этот файл создается, чтобы сделать возможным шаг настройки. Какие у меня варианты? Есть ли способ изменить команду, которая вызывает вызовы для компиляции Setup.hs? Есть ли еще один механизм условного выбора кода, который обходит кабала?

Ответ 1

Интерфейс Data.Data способен (почти!) создавать и деконструировать значения типа, который может или не может существовать. К сожалению, HaXml не имеет экземпляров Data для своих типов, и вы не можете определить его, так как вы не можете ссылаться на тип, который может или не может существовать, поэтому нам нужно обратиться к Template Haskell:

Следующий модуль экспортирует qnameCompat:

{-# LANGUAGE TemplateHaskell #-}
module HaXmlCompat (qnameCompat) where

import Language.Haskell.TH

qnameCompat :: Q [Dec]
qnameCompat = do
  mi <- maybeReify "N"
  case mi of
    Nothing -> sequence [
      tySynD (mkName "QName") [] [t| String |],
      valD [p| toQName |] (normalB [| id |]) [],
      valD [p| fromQName |] (normalB [| Just |]) []]
    Just (DataConI n _ _ _) -> do
      s <- newName "s"
      sequence [
        valD [p| toQName |] (normalB (conE n)) [],
        funD (mkName "fromQName") [
          clause [conP n [varP s]] (normalB (appE [| Just |] (varE s))) [],
          clause [ [p| _ |] ] (normalB [| Nothing |]) []]]
    Just i -> fail $
      "N exists, but isn't the sort of thing I expected: " ++ show i

maybeReify :: String -> Q (Maybe Info)
maybeReify = recover (return Nothing) . fmap Just . reify . mkName

При сращивании на верхнем уровне с использованием шаблона Haskell qnameCompat проверяет наличие N. Если это так, он выдает следующий код:

toQName = N
fromQName (N s) = Just s
fromQName _ = Nothing

Если это не так, выдается следующее:

type QName = String
toQName = id
fromQName = Just

Теперь вы можете создавать и деконструировать Element s, например. с помощью расширения ViewPatterns:

myElt :: String -> Element i
myElt = Elem (toQName "elemName") [] []

eltName :: Element i -> String
eltName (Elem (fromQName -> Just n) _ _) = n

ViewPatterns удобен, но не является существенным, конечно: использование обычного сопоставления шаблонов по результату fromQName будет работать так же хорошо.

(Эти идеи привели меня к разработке пакета notcpp, который включает в себя maybeReify и некоторые другие полезные утилиты)

Ответ 2

В cabal-install/Distribution/Client/SetupWrapper.hs не так много кнопок управления, управляющих компиляцией Setup.hs, поэтому лучше всего создать файл заглушки Setup.hs, который выполнит проверку версии, а затем отпустит real Setup.hs после того, как он выяснил, что такое версия.

Другим трюком является создание библиотеки совместимости, которую использует ваша программа установки script, которая имеет соответствующие трюки версии.

Но, может быть, реальный вопрос: вот почему Setup.hs использует внешние библиотеки?