module Generate where import qualified Flatpak 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.Bool.HT (if') import Text.Printf (printf) archGHC :: Flatpak.Arch -> String archGHC arch = case arch of Flatpak.I386 -> "i386" Flatpak.X86_64 -> "x86_64" Flatpak.ARM -> "armv7" Flatpak.AArch64 -> "aarch64" formatVersion :: Plan.Ver -> String formatVersion = Text.unpack . Plan.dispVer ghcDirUrl :: Plan.PkgId -> String ghcDirUrl (Plan.PkgId (Plan.PkgName compName) compVersion) = printf "https://downloads.haskell.org/%s/%s/" (Text.unpack compName) (formatVersion compVersion) {- | ghc-8.4.4-x86_64-deb9 is available but fails due to missing libnuma.so.1 -} ghcNameScheme :: Plan.Ver -> String ghcNameScheme ghcVer = if' (ghcVer >= Plan.Ver [8, 6,1]) "%s-%s-deb9-linux.tar.xz" $ if' (ghcVer >= Plan.Ver [7,10,3]) "%s-%s-deb8-linux.tar.xz" $ if' (ghcVer >= Plan.Ver [7, 8,1]) "%s-%s-unknown-linux-deb7.tar.xz" $ "%s-%s-unknown-linux.tar.bz2" ghcArchive :: Plan.PkgId -> Flatpak.Arch -> String ghcArchive compiler@(Plan.PkgId _ compVersion) arch = printf (ghcNameScheme compVersion) (Text.unpack $ Plan.dispPkgId compiler) (archGHC arch) 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) 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 = ghcDirUrl compiler ++ ghcArchive compiler arch, Flatpak.sha256 = Just hash }] } packageURLs :: Plan.Unit -> (String, String) packageURLs pkg = 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/%s.cabal" pkgNameVer pkgName) packageSources :: (String, [Flatpak.Source]) -> Flatpak.SourceType -> Plan.Unit -> [Flatpak.Source] packageSources (mainPkg, mainSrcs) typ pkg = let (pkgUrl, cabalUrl) = packageURLs pkg in if' (matchName mainPkg pkg) mainSrcs $ Flatpak.Source { Flatpak.typ = typ, Flatpak.url = pkgUrl, Flatpak.sha256 = Plan.uSha256 pkg } : Flatpak.Source { Flatpak.typ = Flatpak.File, Flatpak.url = cabalUrl, 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 -> Plan.Unit -> Flatpak.Module modul main (cabalCfgOptions, ghcOptions) hookMap 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 pkg } manifest :: Plan.PlanJson -> [(Flatpak.Arch, Plan.Sha256)] -> [Plan.Unit] -> 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 $ "b2da736cc27609442b10f77fc1a687aba603a7a33045b722dbf1a0066fade198" Flatpak.X86_64 -> Just $ "6136c189ffccaa39916f9cb5788f757166444a2d0c473b987856a79ecbf0c714" _ -> Nothing cabalInstallModule :: Flatpak.Arch -> Flatpak.Module cabalInstallModule arch = let cabalVerName = "cabal-install-2.4.1.0" url = printf "https://downloads.haskell.org/cabal/%s/%s-%s-unknown-linux.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 = url, Flatpak.sha256 = Plan.parseSha256 . Text.pack =<< cabalHash arch }] } allInOneModule :: (String, [Flatpak.Source]) -> Options -> [String] -> [Plan.Unit] -> Flatpak.Module allInOneModule main (cabalCfgOptions, ghcOptions) postInstall pkgs = let dispPkgId = Text.unpack . Plan.dispPkgId in Flatpak.Module { Flatpak.name = "haskell-parts", Flatpak.onlyArches = [], Flatpak.buildsystem = "simple", Flatpak.builddir = False, Flatpak.configOpts = [], Flatpak.buildCommands = (flip concatMap pkgs $ \pkg -> let pkgId@(Plan.PkgId (Plan.PkgName name) _version) = Plan.uPId pkg pkgName = Text.unpack name pkgVerName = dispPkgId pkgId in printf "gunzip %s.tar.gz" pkgVerName : printf "mkdir %s" pkgVerName : printf "mv %s.cabal %s/" pkgName pkgVerName : printf "tar rf %s.tar %s/%s.cabal" pkgVerName pkgVerName pkgName : printf "gzip %s.tar" pkgVerName : []) ++ "mkdir .cabal" : "touch .cabal/config" : unwords ("cabal --config-file=.cabal/config install" : "--offline --prefix=/app" : cabalCfgOptions ++ map ghcOption ghcOptions ++ map (printf "%s.tar.gz" . dispPkgId . Plan.uPId) pkgs) : [], Flatpak.postInstall = postInstall, Flatpak.cleanupModule = [], Flatpak.sources = concatMap (packageSources main Flatpak.File) pkgs } manifestCabalInstall :: Plan.PlanJson -> [(Flatpak.Arch, Plan.Sha256)] -> [Plan.Unit] -> 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.modules = (map ModuleEmbed $ concatMap (\(arch,ghcHash) -> [ghcModule plan arch ghcHash, cabalInstallModule arch]) archs) ++ Flatpak.modules base ++ (ModuleEmbed $ allInOneModule (projectMain project) (projectOptions project) (concatMap Flatpak.postInstallHook $ Flatpak.packageHooks project) pkgs) : [] }