Posts tagged ‘ghc’

Updating Haskell packages on Arch

A few days ago I noticed that there were a few Haskell packages on AUR that had received updates. This was the excuse I had been waiting for to address the second part of keeping my Haskell packages up-to-date (I’ve written about the first part before, dealing with an update to GHC).

It’s easy to find the packages with available updates:

% yaourt -Su --aur

Unfortunately it isn’t as easy as just updating the listed packages. Any package that depends on an updated package really should be re-compiled and re-installed to guarantee that the entire system behaves as expected after the upgrade. Of course pacman can handle it:

% pacman -Rcn <all pkgs with updates>

This will list all packages that will be removed. After removing them all, they can all be re-installed. That is of course not quite as nice as it could be, since they all then will be explicitly installed, it would be nicer to just re-install the “top-level packages”. This is one way to achieve this.

I did a bit of refactoring and put the Arch-related functions from the previous post in their own module, Arch. Then I added a function that takes a package and recursively inspects Required By until a “top-level package” (i.e. a package that doesn’t require any other package) is reached:

getTopRequiredBy pkg = let
        tops = do
            first <- getRequiredBy pkg
            if null first
                then return [pkg]
                else liftM concat $ mapM getTopRequiredBy first
    in liftM nub tops

After that it’s straight forward to write up a little tool which offers some advice on what to do:

main = do
    pkgsToUpgrade <- getArgs
    pkgsToReinstall <- liftM (nub . concat) $ mapM Arch.getTopRequiredBy pkgsToUpgrade
    putStrLn $ "To remove     : pacman -Rnc " ++ unwords pkgsToUpgrade
    putStrLn $ "To re-install : yaourt -S " ++ unwords pkgsToReinstall

Using it on the packages I wanted to upgrade gave the following output:

% runhaskell PkgUpgrade.hs haskell-{configfile,haxml,json,missingh,safe,testpack,time}
To remove     : pacman -Rnc haskell-configfile haskell-haxml haskell-json haskell-missingh haskell-safe haskell-testpack haskell-time
To re-install : yaourt -S haskell-configfile haskell-haxml haskell-json haskell-hsh haskell-safe

Following that advice seemed to work just like I intended.

Updating GHC on Arch

Arch is somewhat of a hybrid distribution in the sense that if you have any sort of ‘peculiar’ needs then you are likely to have to build packages from source. As expected developing in Haskell is a “peculiar need” :-)

After every upgrade of GHC I find myself in the situation where the system (pacman) and GHC have different views of what packages are available. What is needed then is somehow finding out the difference, and this is how I found that difference after the recent 6.10.3 -> 6.10.4 upgrade of GHC. Once I know what packages are missing from GHC’s view of the world I can use pacman to first remove and then yaourt to rebuild the packages.

First I noted that the old package.conf file wasn’t deleted during the upgrade, apparently pacman noted the changes that installing packages resulted in and saved the file as package.conf.pacsave. Finding the name of all the ‘missing’ packages was then as simple as loading both /usr/lib/ghc-6.10.3/package.conf.pacsave and /usr/lib/ghc-6.10.4/package.conf, filter out the package names and take the difference:

printMissingPackages = let
        pkgNameStr = PackageName . display . packageName
    in do
        oldPackConf <- readFile "/usr/lib/ghc-6.10.3/package.conf.pacsave"
        curPackConf <- readFile "/usr/lib/ghc-6.10.4/package.conf"
        let oldPacks = (read oldPackConf) :: [InstalledPackageInfo_ String]
        let curPacks = (read curPackConf) :: [InstalledPackageInfo_ String]
        let gonePacks = (map pkgNameStr oldPacks) \\ (map pkgNameStr curPacks)
        putStrLn "Missing packages:"
        mapM_ (putStrLn . display) gonePacks

That isn’t the most useful output however, so I decided to modify it to print out the name of the Arch package that needed re-compilation. The following functions generates the name of the .hi of the first module in the Haskell package, it then uses pacman to look up the owner of the file:

ghcPkg2ArchPkg pkg = let
        hsFileLoc = head $ libraryDirs pkg
        hsFile = map (\ c -> if c == '.' then '/' else c) $ head $ exposedModules pkg
        hsFullFile = hsFileLoc </> hsFile <.> "hi"
    in do
        exists <- doesDirectoryExist hsFullFile
        if exists
            then liftM Just $ archOwnerOfFile hsFullFile
            else return Nothing
 
archOwnerOfFile fn = let
        pkgFromPacmanOutput = head . tail . reverse . words
    in do
        res <- rawSystemStdout silent "/usr/bin/pacman" ["-Qo", fn]
        return $ pkgFromPacmanOutput res

Now I can find the list of Arch packages that aren’t known to the new version of GHC by mapping ghcPkg2ArchPkg over gonePkgs. In other words that is the list of packages that need to be removed, but that can be different from the list of packages that needs to be rebuilt with yaourt (basically I only want to tell it to build and install the ‘top-level’ packages, i.e. packages that aren’t dependencies of any other packages. It’s of course possible to build that second list from the first one, with the help of pacman.

archGetRequiredBy pkg = let
        extractPkgs pkgDesc = let
                deps = (drop 3 . words . head . filter (isPrefixOf "Required By") . lines) pkgDesc
            in
                if deps == ["None"]
                    then []
                    else deps
    in do
        res <- rawSystemStdout silent "/usr/bin/pacman" ["-Qi", pkg]
        return $ extractPkgs res

Now I can modify printMissingPackages to print some more useful information. This is the full function:

printMissingPackages = let
        pkgNameStr = PackageName . display . packageName
    in do
        oldPackConf <- readFile "/usr/lib/ghc-6.10.3/package.conf.pacsave"
        curPackConf <- readFile "/usr/lib/ghc-6.10.4/package.conf"
        let oldPacks = (read oldPackConf) :: [InstalledPackageInfo_ String]
        let curPacks = (read curPackConf) :: [InstalledPackageInfo_ String]
        let gonePacks = (map pkgNameStr oldPacks) \\ (map pkgNameStr curPacks)
        putStrLn "Missing packages:"
        mapM_ (putStrLn . display) gonePackprints
        let gonePkgs = filter (\ p -> pkgNameStr p `elem` gonePacks) oldPacks
        archPkgs <- liftM catMaybes $ mapM ghcPkg2ArchPkg gonePkgs
        putStrLn "Packages to remove:"
        mapM_ putStrLn archPkgs
        archTopPkgs <- filterM (liftM ([] ==) . archGetRequiredBy) archPkgs
        putStrLn "\nPackages to install:"
        mapM_ putStrLn archTopPkgs

On my system it produced the following output:

Missing packages:
terminfo
vty
wl-pprint

Packages to remove:
haskell-terminfo
haskell-vty
haskell-wl-pprint

Packages to install:
haskell-vty
haskell-wl-pprint

And after a quick pacman -Rn ... and a not so quick yaourt -S ... I reran it and the output was

Packages to remove:

Packages to install:

Exactly as expected.