{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
import qualified Data.ByteString as BS
import Data.List (sort)
import Data.List.Extra (nubOn)
import Data.Map.Strict (lookup, toList)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Semigroup.Foldable (fold1)
import Data.Text (Text, concat, intercalate,
pack)
import Data.Text.Encoding (encodeUtf8)
import Distribution.Pretty (prettyShow)
import Distribution.Types.PackageId (PackageIdentifier (..))
import Distribution.Types.PackageName (unPackageName)
import OpenSSL (withOpenSSL)
import qualified Options.Applicative as Opts
import Prelude hiding (concat, head, lookup,
reverse)
import Stackage
import System.FilePath (takeDirectory, (</>))
{- MANUAL TEST:
LC_ALL=C cabal v2-run stackage-to-hackage -- tests/snapshot/stack.yaml
LC_ALL=C cabal v2-run stackage-to-hackage -- tests/stackage/stack.yaml
git diff tests
-}
main :: IO ()
main = withOpenSSL $ do
Options{input} <- Opts.execParser $
Opts.info (Opts.helper <*> optionsParser) Opts.fullDesc
text <- BS.readFile input
stack <- readStack text
let dir = (takeDirectory input)
resolvers <- unroll dir stack
let
resolver = fold1 resolvers
freeze = genFreeze resolver
project = genProject stack resolver
BS.writeFile (dir </> "cabal.project") (encodeUtf8 $ printProject project)
BS.writeFile (dir </> "cabal.project.freeze") (encodeUtf8 $ printFreeze freeze)
printProject :: Project -> Text
printProject (Project (Ghc ghc) pkgs srcs) =
concat [ "-- Generated by stackage-to-hackage from stack.yaml\n\n"
, "with-compiler: ", ghc, "\n\n"
, "packages:\n ", packages, "\n\n"
, sources, "\n"
, "allow-older: *\n"
, "allow-newer: *\n"
]
where
packages = intercalate "\n , " ((<> "/"). pack <$> pkgs)
sources = intercalate "\n" (source =<< srcs)
source Git{repo, commit, subdirs} =
let base = concat [ "source-repository-package\n "
, "type: git\n "
, "location: ", repo, "\n "
, "tag: ", commit, "\n"]
in if null subdirs
then [base]
else (\d -> concat [base, " subdir: ", d, "\n"]) <$> subdirs
data Project = Project Ghc [FilePath] [Git] deriving (Show)
genProject :: Stack -> Resolver -> Project
genProject Stack{packages} Resolver{compiler, deps} = Project
(fromMaybe (Ghc "ghc") compiler)
(mapMaybe pickLocal packages)
(nubOn repo $ mapMaybe pickGit deps)
where
pickLocal (Local p) = Just p
pickLocal (Location _) = Nothing
pickGit (Hackage _ ) = Nothing
pickGit (SourceDep g) = Just g
printFreeze :: Freeze -> Text
printFreeze (Freeze deps (Flags flags)) =
concat [ "constraints: \n ", constraints, "\n"]
where
constraints = intercalate "\n , " (constrait <$> sort deps)
constrait pkg =
let name = (pack . unPackageName . pkgName $ pkg)
ver = (pack . prettyShow . pkgVersion $ pkg)
base = concat [name, " ==", ver]
in case lookup name flags of
Nothing -> base
Just entries -> concat [ name, " ", (custom entries)
, "\n , ", base]
custom (toList -> lst) = intercalate " " $ (renderFlag <$> lst)
renderFlag (name, True) = "+" <> name
renderFlag (name, False) = "-" <> name
data Freeze = Freeze [PackageIdentifier] Flags deriving (Show)
genFreeze :: Resolver -> Freeze
genFreeze Resolver{deps, flags} =
let pkgs = mapMaybe pick deps
uniqpkgs = nubOn pkgName pkgs
in Freeze uniqpkgs flags
where pick (Hackage p) = Just p
pick (SourceDep _) = Nothing
data Options = Options
{ input :: FilePath
}
optionsParser :: Opts.Parser Options
optionsParser = Options
<$> file
where
file = Opts.strArgument
( Opts.metavar "FILENAME"
<> Opts.help "Input stack.yaml")