module Generate where
import qualified Package
import qualified Flatpak
import Retrieve (formatVersion, archGHC, ghcDirUrl, ghcArchive)
import Flatpak (ModuleItem(ModuleEmbed))
import qualified Cabal.Plan as Plan
import qualified Shell.Utility.Quote as Quote
import qualified Data.Map as Map
import qualified Data.Text as Text
import Data.Foldable (foldMap)
import Data.Map (Map)
import Data.Maybe.HT (toMaybe)
import Data.Bool.HT (if')
import Text.Printf (printf)
tinfoPath :: Flatpak.Arch -> String
tinfoPath arch =
case arch of
Flatpak.I386 -> "/usr/lib/i386-linux-gnu/libtinfo.so"
Flatpak.X86_64 -> "/usr/lib/x86_64-linux-gnu/libtinfo.so"
Flatpak.ARM -> "/lib/arm-linux-gnueabihf/libtinfo.so"
-- FixMe: What is the correct path?
Flatpak.AArch64 -> "/lib/arm???-linux-gnueabihf/libtinfo.so"
projectMain :: Flatpak.Cabal -> (String, [Flatpak.Source])
projectMain project =
(Flatpak.mainPackage project, Flatpak.mainSources project)
projectMainExe :: Flatpak.Cabal -> String
projectMainExe = Flatpak.command . Flatpak.base
matchName :: String -> Plan.Unit -> Bool
matchName expected unit =
case Plan.uPId unit of
Plan.PkgId (Plan.PkgName name) _version -> expected == Text.unpack name
type Options = ([String], [String])
projectOptions :: Flatpak.Cabal -> Options
projectOptions project =
(Flatpak.cabalConfigureOptions project, Flatpak.ghcOptions project)
ghcPkgCleanup :: Plan.PlanJson -> Flatpak.Arch -> [String]
ghcPkgCleanup plan arch =
let archLinux =
archGHC arch ++ "-linux-" ++
Text.unpack (Plan.dispPkgId (Plan.pjCompilerId plan))
in map (++ archLinux) $
"/lib/" :
"/share/doc/" :
[]
ghcCleanup :: Plan.PlanJson -> [String]
ghcCleanup plan =
let (Plan.PkgId _ ghcVer) = Plan.pjCompilerId plan
withVersion prg = prg ++ "-" ++ formatVersion ghcVer
in map ("/" ++) $
concat [
map ("bin/" ++) $
"ghc" : withVersion "ghc" :
"ghc-pkg" : withVersion "ghc-pkg" :
"ghci" : withVersion "ghci" :
"haddock" : withVersion "haddock-ghc" :
"runghc" : withVersion "runghc" :
"runhaskell" :
"hp2ps" :
"hpc" :
"hsc2hs" :
[],
map ("lib/" ++) $
"libtinfo.so.5" :
"debug" :
withVersion "ghc" :
[],
map ("share/" ++) $
"man" :
("doc/" ++ withVersion "ghc") :
[]
]
-- | Generate the module to download, compile and install GHC.
ghcModule :: Plan.PlanJson -> Flatpak.Arch -> Plan.Sha256 -> Flatpak.Module
ghcModule plan arch hash =
let compiler = Plan.pjCompilerId plan
in Flatpak.Module {
Flatpak.name =
Text.unpack (Plan.dispPkgId compiler) ++
"-" ++ Flatpak.archString arch,
Flatpak.onlyArches = [arch],
Flatpak.buildsystem = "simple",
Flatpak.builddir = False,
Flatpak.configOpts = [],
Flatpak.buildCommands =
"mkdir -p /app/lib" :
printf "ln -s %s /app/lib/libtinfo.so.5" (tinfoPath arch) :
"./configure --prefix=/app" :
"make install" :
[],
Flatpak.postInstall = [],
Flatpak.cleanupModule = ghcCleanup plan,
Flatpak.sources =
[Flatpak.Source {
Flatpak.typ = Flatpak.archive,
Flatpak.url =
Just $
ghcDirUrl compiler ++ ghcArchive compiler arch,
Flatpak.destFilename = Nothing,
Flatpak.dest = Nothing,
Flatpak.sha256 = Just hash
}]
}
type RevisedUnit = (Plan.Unit, Package.Revision)
packageURLs :: RevisedUnit -> (String, String, String, String)
packageURLs (pkg, revision) =
let pkgId@(Plan.PkgId (Plan.PkgName name) _version) = Plan.uPId pkg
pkgName = Text.unpack name
pkgNameVer = Text.unpack $ Plan.dispPkgId pkgId
in (printf "https://hackage.haskell.org/package/%s/%s.tar.gz"
pkgNameVer pkgNameVer,
printf "https://hackage.haskell.org/package/%s/revision/%d.cabal"
pkgNameVer revision,
pkgNameVer,
printf "%s.cabal" pkgName)
reviseToSubdir :: Flatpak.SourceType -> Bool
reviseToSubdir (Flatpak.Archive n) = n == 0
reviseToSubdir _ = False
packageSources ::
(String, [Flatpak.Source]) -> Flatpak.SourceType ->
RevisedUnit -> [Flatpak.Source]
packageSources (mainPkg, mainSrcs) typ pkgRev@(pkg,_) =
let (pkgUrl, cabalUrl, cabalDest, cabalPath) = packageURLs pkgRev
in if' (matchName mainPkg pkg && not (null mainSrcs)) mainSrcs $
Flatpak.Source {
Flatpak.typ = typ,
Flatpak.url = Just pkgUrl,
Flatpak.destFilename = Nothing,
Flatpak.dest = Nothing,
Flatpak.sha256 = Plan.uSha256 pkg
} :
Flatpak.Source {
Flatpak.typ = Flatpak.File,
Flatpak.url = Just cabalUrl,
Flatpak.destFilename = Just cabalPath,
Flatpak.dest = toMaybe (reviseToSubdir typ) cabalDest,
Flatpak.sha256 = Plan.uCabalSha256 pkg
} :
[]
ghcOption :: String -> String
ghcOption opt = "--ghc-option=" ++ Quote.minimal opt
{- |
Generate a module to download, build and install a package from Hackage.
Due to the Cabal package "revision" not being part of a package,
we have to download the package,
extract and then overwrite the .cabal file with the latest revision.
-}
modul ::
(String, [Flatpak.Source]) -> Options -> Map String Flatpak.PackageHook ->
RevisedUnit -> Flatpak.Module
modul main (cabalCfgOptions, ghcOptions) hookMap pkgRev@(pkg,_) =
let (Plan.PkgId (Plan.PkgName name) _version) = Plan.uPId pkg
nameStr = Text.unpack name
in Flatpak.Module {
Flatpak.name = nameStr,
Flatpak.onlyArches = [],
Flatpak.buildsystem = "simple",
Flatpak.builddir = False,
Flatpak.configOpts = [],
Flatpak.buildCommands =
"echo '#! /usr/bin/env runhaskell' >Setup.txt" :
"echo '> import Distribution.Simple' >>Setup.txt" :
"echo '> main = defaultMain' >>Setup.txt" :
"ln -s Setup.txt Setup.lhs || true" :
unwords ("runhaskell Setup configure --prefix=/app" :
cabalCfgOptions) :
unwords ("runhaskell Setup build" : map ghcOption ghcOptions) :
"runhaskell Setup install" :
[],
Flatpak.postInstall =
foldMap Flatpak.postInstallHook $ Map.lookup nameStr hookMap,
Flatpak.cleanupModule = [],
Flatpak.sources = packageSources main Flatpak.archive pkgRev
}
manifest ::
Plan.PlanJson ->
[(Flatpak.Arch, Plan.Sha256)] -> [RevisedUnit] -> Flatpak.Cabal -> Flatpak.T
manifest plan archs units project =
let base = Flatpak.base project
hookMap =
Map.fromList $ map (\hook -> (Flatpak.nameHook hook, hook)) $
Flatpak.packageHooks project
in
base {
Flatpak.cleanup =
concatMap (ghcPkgCleanup plan . fst) archs ++ Flatpak.cleanup base,
Flatpak.modules =
map (ModuleEmbed . uncurry (ghcModule plan)) archs ++
Flatpak.modules base ++
map
(ModuleEmbed .
modul (projectMain project) (projectOptions project) hookMap)
units
}
cabalHash :: Flatpak.Arch -> Maybe String
cabalHash arch =
case arch of
Flatpak.I386 -> Just $
"c03043d5c9dfed9a1a4c795e4260221986dcdde28a68b909fcf4ccc07d7dd7b5"
Flatpak.X86_64 -> Just $
"97e61bcd143f0b8a181ea2261a1de587b58fa9f84d123b9b9cec4463ae2f41c6"
_ -> Nothing
cabalInstallModule :: Flatpak.Arch -> Flatpak.Module
cabalInstallModule arch =
let cabalVerName = "cabal-install-3.10.3.0"
url =
printf
"https://downloads.haskell.org/cabal/%s/%s-%s-linux-deb9.tar.xz"
cabalVerName cabalVerName (archGHC arch)
in Flatpak.Module {
Flatpak.name = cabalVerName ++ "-" ++ Flatpak.archString arch,
Flatpak.onlyArches = [arch],
Flatpak.buildsystem = "simple",
Flatpak.builddir = False,
Flatpak.configOpts = [],
Flatpak.buildCommands = ["install cabal /app/bin"],
Flatpak.postInstall = [],
Flatpak.cleanupModule = ["/bin/cabal"],
Flatpak.sources =
[Flatpak.Source {
Flatpak.typ =
Flatpak.Archive {Flatpak.stripComponents = 0},
Flatpak.url = Just url,
Flatpak.destFilename = Nothing,
Flatpak.dest = Nothing,
Flatpak.sha256 = Plan.parseSha256 . Text.pack =<< cabalHash arch
}]
}
allInOneModule :: Flatpak.Cabal -> [RevisedUnit] -> Flatpak.Module
allInOneModule project pkgs =
let (cabalCfgOptions, ghcOptions) = projectOptions project in
Flatpak.Module {
Flatpak.name = "haskell-parts",
Flatpak.onlyArches = [],
Flatpak.buildsystem = "simple",
Flatpak.builddir = False,
Flatpak.configOpts = [],
Flatpak.buildCommands =
"mkdir .cabal" :
"touch .cabal/config" :
"echo 'packages: */' > cabal.project" :
unwords ("cabal --config-file=.cabal/config -j$FLATPAK_BUILDER_N_JOBS" :
"--offline --store-dir /app/cabal" :
"--installdir=/app/bin" :
("v2-install exe:" ++ projectMainExe project) :
cabalCfgOptions ++ map ghcOption ghcOptions) :
[],
Flatpak.postInstall =
concatMap Flatpak.postInstallHook $ Flatpak.packageHooks project,
Flatpak.cleanupModule = [],
Flatpak.sources =
concatMap (packageSources (projectMain project) $
Flatpak.Archive {Flatpak.stripComponents = 0}) pkgs
}
manifestCabalInstall ::
Plan.PlanJson ->
[(Flatpak.Arch, Plan.Sha256)] -> [RevisedUnit] -> Flatpak.Cabal -> Flatpak.T
manifestCabalInstall plan archs pkgs project =
let base = Flatpak.base project in
base {
Flatpak.cleanup =
concatMap (ghcPkgCleanup plan . fst) archs ++ Flatpak.cleanup base,
Flatpak.cleanupCommands =
( "find /app/cabal/ghc-* -mindepth 1 " ++
"\\( \\( -not -path '*/" ++
(fst $ projectMain project) ++
"*' -a -path '*/bin/*' \\) -o -path '*/lib/*' \\) -delete" ) :
Flatpak.cleanupCommands base,
Flatpak.modules =
(map ModuleEmbed $
concatMap
(\(arch,ghcHash) ->
[ghcModule plan arch ghcHash, cabalInstallModule arch])
archs) ++
Flatpak.modules base ++
(ModuleEmbed $ allInOneModule project pkgs) :
[]
}