cabal-flatpak 0.1.2 → 0.1.3
raw patch · 5 files changed
+133/−67 lines, 5 filesdep ~aesondep ~http-client-tls
Dependency ranges changed: aeson, http-client-tls
Files
- cabal-flatpak.cabal +10/−8
- src/Flatpak.hs +3/−0
- src/Generate.hs +39/−43
- src/Main.hs +78/−14
- src/Option.hs +3/−2
cabal-flatpak.cabal view
@@ -1,6 +1,6 @@ Cabal-Version: 2.2 Name: cabal-flatpak-Version: 0.1.2+Version: 0.1.3 License: BSD-3-Clause License-File: LICENSE Author: Henning Thielemann <haskell@henning-thielemann.de>@@ -20,12 +20,14 @@ Build-Type: Simple Extra-Source-Files: Makefile+ flatpak.cabal.json++Extra-Doc-Files: ReadMe.md Changes.md- flatpak.cabal.json Source-Repository this- Tag: 0.1.2+ Tag: 0.1.3 Type: darcs Location: https://hub.darcs.net/thielema/cabal-flatpak/ @@ -38,20 +40,20 @@ cabal-plan >=0.5 && <0.8, cryptohash-sha256 >=0.11 && <0.12, zlib >=0.5.3 && <0.8,- tar >=0.5.0.3 && <0.7,- http-client-tls >=0.3.5.3 && <0.4,+ tar >=0.5.0.3 && <0.8,+ http-client-tls >=0.3.5.3 && <0.5, http-client >=0.5.12 && <0.8, http-types >=0.10 && <0.13, aeson-pretty >=0.8.7 && <0.9,- aeson >=2.0 && <2.3,+ aeson >=2.0 && <2.4, yaml >=0.11 && <0.12, shell-utility >=0.1 && <0.2,- optparse-applicative >=0.11 && <0.19,+ optparse-applicative >=0.11 && <0.20, pathtype >=0.8 && <0.9, directory >=1.2.3 && <1.4, text >=1.2 && <2.2, bytestring >=0.9 && <0.13,- containers >=0.5 && <0.8,+ containers >=0.5 && <0.9, utility-ht >=0.0.12 && <0.1, base >=4.5 && <5
src/Flatpak.hs view
@@ -118,6 +118,7 @@ typ :: SourceType, url :: Maybe String, destFilename :: Maybe String,+ dest :: Maybe String, sha256 :: Maybe Plan.Sha256 } deriving (Show) @@ -127,6 +128,7 @@ <*> (parseSourceType o =<< o .: "type") <*> o .:? ("url", Nothing) <*> o .:? ("dest-filename", Nothing)+ <*> o .:? ("dest", Nothing) <*> o .:? ("sha256", Nothing) instance ToJSON Source where@@ -134,6 +136,7 @@ (pairsFromSourceType . typ) <> ("url" .=? (url, Nothing)) <> ("dest-filename" .=? (destFilename, Nothing)) <>+ ("dest" .=? (dest, Nothing)) <> ("sha256" .=? (sha256, Nothing)) <> mempty
src/Generate.hs view
@@ -12,6 +12,7 @@ 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)@@ -31,6 +32,9 @@ 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@@ -109,13 +113,14 @@ 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)+packageURLs :: RevisedUnit -> (String, String, String, String) packageURLs (pkg, revision) = let pkgId@(Plan.PkgId (Plan.PkgName name) _version) = Plan.uPId pkg pkgName = Text.unpack name@@ -124,24 +129,31 @@ 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, cabalPath) = packageURLs pkgRev+ 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 } : []@@ -210,17 +222,17 @@ cabalHash arch = case arch of Flatpak.I386 -> Just $- "b2da736cc27609442b10f77fc1a687aba603a7a33045b722dbf1a0066fade198"+ "c03043d5c9dfed9a1a4c795e4260221986dcdde28a68b909fcf4ccc07d7dd7b5" Flatpak.X86_64 -> Just $- "6136c189ffccaa39916f9cb5788f757166444a2d0c473b987856a79ecbf0c714"+ "97e61bcd143f0b8a181ea2261a1de587b58fa9f84d123b9b9cec4463ae2f41c6" _ -> Nothing cabalInstallModule :: Flatpak.Arch -> Flatpak.Module cabalInstallModule arch =- let cabalVerName = "cabal-install-2.4.1.0"+ let cabalVerName = "cabal-install-3.10.3.0" url = printf- "https://downloads.haskell.org/cabal/%s/%s-%s-unknown-linux.tar.xz"+ "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,@@ -237,28 +249,14 @@ Flatpak.Archive {Flatpak.stripComponents = 0}, Flatpak.url = Just url, Flatpak.destFilename = Nothing,+ Flatpak.dest = Nothing, Flatpak.sha256 = Plan.parseSha256 . Text.pack =<< cabalHash arch }] } -reviseCabalScript :: [String]-reviseCabalScript =- "tarball=\"$1\"" :- "pkgid=\"$(basename $tarball .tar.gz)\"" :- "name=\"$(echo $pkgid | sed -r 's:^(.*)-(.*)$:\\1:')\"" :- "ver=\"$(echo $pkgid | sed -r 's:^(.*)-(.*)$:\\2:')\"" :- "echo Revising .cabal file for \"$pkgid\"" :- "gunzip \"$tarball\"" :- "mkdir \"$pkgid\"" :- "mv \"$name.cabal\" \"$pkgid\"" :- "tar rf \"$pkgid.tar\" \"$pkgid/$name.cabal\"" :- "gzip \"$pkgid.tar\"" :- []--allInOneModule ::- (String, [Flatpak.Source]) -> Options -> [String] ->- [RevisedUnit] -> Flatpak.Module-allInOneModule main (cabalCfgOptions, ghcOptions) postInstall pkgs =+allInOneModule :: Flatpak.Cabal -> [RevisedUnit] -> Flatpak.Module+allInOneModule project pkgs =+ let (cabalCfgOptions, ghcOptions) = projectOptions project in Flatpak.Module { Flatpak.name = "haskell-parts", Flatpak.onlyArches = [],@@ -266,26 +264,21 @@ Flatpak.builddir = False, Flatpak.configOpts = [], Flatpak.buildCommands =- "find . -name '*.tar.gz' | while read pkg; do bash revise.sh \"$pkg\"; done" : "mkdir .cabal" : "touch .cabal/config" :- unwords- ("cabal --config-file=.cabal/config install" :- "-j$FLATPAK_BUILDER_N_JOBS" :- "--offline --prefix=/app" :- cabalCfgOptions ++ map ghcOption ghcOptions ++- ["*.tar.gz"]) :+ "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 = postInstall,+ Flatpak.postInstall =+ concatMap Flatpak.postInstallHook $ Flatpak.packageHooks project, Flatpak.cleanupModule = [], Flatpak.sources =- Flatpak.Source {- Flatpak.typ = Flatpak.Script reviseCabalScript,- Flatpak.url = Nothing,- Flatpak.destFilename = Just "revise.sh",- Flatpak.sha256 = Nothing- } :- concatMap (packageSources main Flatpak.File) pkgs+ concatMap (packageSources (projectMain project) $+ Flatpak.Archive {Flatpak.stripComponents = 0}) pkgs } manifestCabalInstall ::@@ -296,6 +289,12 @@ 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@@ -303,9 +302,6 @@ [ghcModule plan arch ghcHash, cabalInstallModule arch]) archs) ++ Flatpak.modules base ++- (ModuleEmbed $- allInOneModule (projectMain project) (projectOptions project)- (concatMap Flatpak.postInstallHook $ Flatpak.packageHooks project)- pkgs) :+ (ModuleEmbed $ allInOneModule project pkgs) : [] }
src/Main.hs view
@@ -21,6 +21,7 @@ import Control.Monad (when) import Control.Applicative ((<$>))+import Control.Arrow ((&&&)) import qualified Data.Aeson as Json import qualified Data.Yaml as Yaml@@ -73,7 +74,70 @@ ++ printf "%s, %s" xdgDir (Path.toString appDir) +{- |+Cabal operates on units.+A package can have multiple units,+for example a library unit and multiple executable units.+Each of those units can have its own set of library and executable dependencies. +However, when downloading packages in Flatpak+we don't differentiate between the units+and treat all units belonging to the same package as one.++The below heuristic+merges all units belonging to the same package into one package+and topologically sorts the packages.++This heuristic can be completely skipped when using the --cabal-install option,+because in this mode all we need to do is to download all necessary packages+in any order and cabal will determine the correct order of the units by itself.++The heuristic goes as follows:++1. Build a map between the unit IDs and the package ID that contains it+2. Get the dependency graph using the cabal-plan API+3. Group all units belonging to the same package+ and merge their dependencies to obtain package dependencies+4. Topologically sort the packages+5. Take one representative unit for each package+ and use them in the flatpak.json generation process.+ This is safe to do as long as the rest of the generator+ uses information that is common to all units in the same package.+ Those are: uPId, uSha256, uCabalSha256 and uType.++In certain situations+this heuristic can still fail to produce a buildable sequence.+For example, we can have two packages as such:+Pkg1@(lib1, exe1) and Pkg2@(lib2)+where the unit lib2 depends on lib1 and exe1 depends on lib2.+Unit-wise, it is possible to sequence the builds of the units+that avoids cyclical dependencies.+But package-wise, it is not possible.+In such cases and in general+it is better to enable the --cabal-install option which avoids this problem.++It is also not possible to use the qualified name of the component+for the 'configure' stage+and build a package in multiple parts in multiple Flatpak modules.+This is because the Plan.uComps is not precise enough,+as the documentation states that in certain situations+that field may contain more than one value.+In that case it is impossible to determine the precise component name.+-}+getSortedUnits :: Plan.PlanJson -> [Plan.Unit]+getSortedUnits plan =+ let+ allUnits = Plan.pjUnits plan+ -- Build a mapping from unit ID to package ID+ unitIdToPkgId unitId = Plan.uPId $ allUnits Map.! unitId+ -- Here we take one representative unit for a given package.+ pkgIdToUnit = Map.fromList $ map (Plan.uPId &&& id) $ Map.elems allUnits+ in filter ((Plan.UnitTypeBuiltin /=) . Plan.uType) $+ map (pkgIdToUnit Map.!) $ topSort $+ -- Merge unit groups into packages, merging their dependencies.+ Map.mapKeysWith Set.union unitIdToPkgId $+ Map.map (Set.map unitIdToPkgId) $ Plan.planJsonIdGraph plan+ main :: IO () main = do opt <- OP.execParser Option.info@@ -105,17 +169,19 @@ let matchExe name comp = case comp of Plan.CompNameExe nameExe -> name == nameExe; _ -> False+ let matchExeUnit name unit =+ any (matchExe name) $ Map.keys $ Plan.uComps unit let mainExe = Text.pack $ Flatpak.command $ Flatpak.base projectJson let mainPackage = Flatpak.mainPackage projectJson- let units =- filter- (\unit ->- if Generate.matchName mainPackage unit- then any (matchExe mainExe) $ Map.keys $ Plan.uComps unit- else Map.member Plan.CompNameLib $ Plan.uComps unit) $- filter ((Plan.UnitTypeBuiltin /=) . Plan.uType) $- map (Plan.pjUnits plan Map.!) $ topSort $ Plan.planJsonIdGraph plan + mainUnit <-+ case filter (matchExeUnit mainExe) $ Map.elems $ Plan.pjUnits plan of+ [x] -> return x+ [] -> fail "main package not found in build plan"+ _ -> fail "main package found multiple times in build plan"++ let units = getSortedUnits plan+ archs <- case Option.archs opt of [] ->@@ -138,12 +204,10 @@ let hackageDir = cabalDir </> Path.dir "packages" </> Path.dir "hackage.haskell.org" - mainVersion <-- case filter (Generate.matchName mainPackage) units of- [Plan.Unit{Plan.uPId = Plan.PkgId _pkg ver}] ->- return $ Retrieve.formatVersion ver- [] -> fail "main package not found in build plan"- _ -> fail "main package found multiple times in build plan"+ let mainVersion = case mainUnit of+ Plan.Unit{Plan.uPId = Plan.PkgId _pkg ver} ->+ Retrieve.formatVersion ver+ (revisions, mainCabalHash) <- do tar <- Package.readTar $ hackageDir </> Path.file "01-index.tar.gz" either fail return $
src/Option.hs view
@@ -7,7 +7,8 @@ import qualified Shell.Utility.ParseArgument as ParseArg import qualified Options.Applicative as OP-import Options.Applicative (help, metavar, argument, option, long, value, flag)+import Options.Applicative+ (help, metavar, argument, option, long, value, switch) import Control.Applicative (pure, (<*>)) @@ -36,7 +37,7 @@ parseFlags :: OP.Parser T parseFlags = pure Cons- <*> flag False True+ <*> switch (long "cabal-install" <> help "Build with one cabal-install (default: build module-wise using Cabal)") <*> OP.many