Uninstalling Cabal packages

Edit (2015-08-10): the tools have been updated to fix a few bugs as well as incompatibilities with the latest GHC; they now also work on Cabal sandboxes.

Problems

A. There is no way to uninstall packages in Cabal

Currently, cabal-install doesn't know how to uninstall packages. The best you can do is to unregister the package with the GHC package-database manager, eliminating the package metadata from the database without removing the actual files.

There is a package called cabal-uninstall (and probably a few others) that remedies this to some extent, but it doesn't work if the package has already been unregistered.

Additionally, while ghc-pkg does warn you of dependency breakage, it doesn't provide an easy way to remove a package and all its dependents transitively.

B. Removing broken packages is a tedious chore

If for some reason your packages get broken, there's no easy way to clean up the mess.

On my system, this usually happens because Arch Linux upgrades some of the globally-installed packages, breaking swaths of user-installed packages in the process (or it could be due to a GHC upgrade).

To fix this you'd have to unregister all of the broken packages. And if you want to reclaim the space used by the dead packages, you'd have to dive into ~/.cabal and remove them manually.

Tools

To help fix the issues outlined above, here are some scripts to assist the process:

  • cabal-gc: a "garbage collector" that searches for any lingering packages whose files still remain but are no longer registered. (Note: it does not remove any executables in the bin directory.)

  • ghc-pkg-check: a thin wrapper around ghc-pkg check that suppresses the haddock warnings that no-one seems to care about.

  • ghc-pkg-unregister: a thin wrapper around ghc-pkg unregister that allows multiple packages to be unregistered in one call.

While it's possible to fully automate this process, it's best to do them one at a time to make sure nothing bad happens.

Tutorial

First off, if you want to manage packages in a sandbox, be sure to run this command prior to everything else below:

cabal [--sandbox-config-file=./cabal.sandbox.config] exec -- "$SHELL"

(The --sandbox-config-file=… flag is optional.)

Now,

  • if your goal is to uninstall a specific package as well as its dependents, then follow all the steps below;
  • if your goal is to only remove broken packages, then jump straight to step 2.

1. Remove the target packages

To remove packages, the first thing to do would be to run:

ghc-pkg-unregister my-package-0.5.0 some-package-1.0.0

If there are any dependent packages, you will receive an error telling you that those will be broken:

ghc-pkg: unregistering some-package-1.0.0 would break the following packages: other-package-2.0.0 another-package-3.0.0 (use --force to override)

If you're alright with this, add the --force flag to remove it for good. We'll eliminate the broken packages shortly.

ghc-pkg-unregister --force some-package-1.0.0

unregistering some-package-1.0.0 would break the following packages: other-package-2.0.0 another-package-3.0.0 (ignoring)

Despite the conditional mood in the message, the package will indeed be unregistered.

2. Remove the dependent packages

Now, we need to get rid of the broken packages. Let's see which packages are broken:

ghc-pkg-check

There are problems in package other-package-2.0.0: dependency "some-package-1.0.0-…" doesn't exist There are problems in package another-package-3.0.0: dependency "some-package-1.0.0-…" doesn't exist The following packages are broken, either because they have a problem listed above, or because they depend on a broken package. other-package-2.0.0 another-package-3.0.0

To remove all of these broken packages, run:

ghc-pkg-check --simple-output | xargs ghc-pkg-unregister --force

To make sure all of the broken packages are taken care of, run ghc-pkg-check again. If there are more broken packages, you'll need to keep repeating this step until all of them have been eliminated.

3. Delete the associated files

This step is optional, although it can be useful to reclaim some disk space.

Since we will be deleting files, it's best to perform a dry run first:

cabal-gc

can be removed: /home/user/.cabal/lib/x86_64-linux-ghc-7.10.1/anoth_d9K7jTHCBftCdWJFHesDel can be removed: /home/user/.cabal/lib/x86_64-linux-ghc-7.10.1/other_8j0AdSQVkKosS3ev8r3pBu can be removed: /home/user/.cabal/lib/x86_64-linux-ghc-7.10.1/mypac_XYf894wKraySpNs9RUwwMV can be removed: /home/user/.cabal/lib/x86_64-linux-ghc-7.10.1/somep_x3XTaLFeSmcMHNpv7Ng8xw can be removed: /home/user/.cabal/share/doc/x86_64-linux-ghc-7.10.1/another-package-3.0.0 can be removed: /home/user/.cabal/share/doc/x86_64-linux-ghc-7.10.1/other-package-2.0.0 can be removed: /home/user/.cabal/share/doc/x86_64-linux-ghc-7.10.1/my-package-0.5.0 can be removed: /home/user/.cabal/share/doc/x86_64-linux-ghc-7.10.1/some-package-1.0.0

If everything looks okay, go ahead and delete them for real:

cabal-gc -r

removed: /home/user/.cabal/lib/x86_64-linux-ghc-7.10.1/anoth_d9K7jTHCBftCdWJFHesDel removed: /home/user/.cabal/lib/x86_64-linux-ghc-7.10.1/other_8j0AdSQVkKosS3ev8r3pBu removed: /home/user/.cabal/lib/x86_64-linux-ghc-7.10.1/mypac_XYf894wKraySpNs9RUwwMV removed: /home/user/.cabal/lib/x86_64-linux-ghc-7.10.1/somep_x3XTaLFeSmcMHNpv7Ng8xw removed: /home/user/.cabal/share/doc/x86_64-linux-ghc-7.10.1/another-package-3.0.0 removed: /home/user/.cabal/share/doc/x86_64-linux-ghc-7.10.1/other-package-2.0.0 removed: /home/user/.cabal/share/doc/x86_64-linux-ghc-7.10.1/my-package-0.5.0 removed: /home/user/.cabal/share/doc/x86_64-linux-ghc-7.10.1/some-package-1.0.0

Comments

Bash on Windows is really slow

Bash is really slow on Windows. See for yourself:

# builtin command
time { :; }
# Linux:   0.000s
# Windows: 0.000s

# builtin command in a subshell
time { ( : ); }
# Linux:   0.001s
# Windows: 0.054s

# non-builtin command
time { date; }
# Linux:   0.002s
# Windows: 0.108s

Notice that running a non-builtin command can be two orders of magnitude more expensive on Windows than on Linux. Creating a subshell is also very slow. Running 10 commands could cost as much as a full second, if not more! This is why shell scripts such as configure scripts are incredibly slow on Windows.

The results here are from Cygwin, but those from Mingw are in the same ballpark. This is probably due to the fact that forking is inherently slow on Windows due to the lack of direct support from the OS, so there's little that can be done to avoid the performance penalty.

Moral of the story? If you are writing shell scripts for Windows, try to avoid making calls to non-builtin commands and avoid making subshells when possible. Unfortunately, there's not much you can do with only builtin commands. Furthermore, many operations, such as command substitution $(…) and pipelines … | … will implicitly spawn subshells. :(

On the bright side, printf is a builtin command in Bash, so at least you don't have to choose between printf and echo for performance reasons (the latter being a portability nightmare).

Comments

The usual arithmetic conversions

In C and C++, the usual arithmetic conversions are a set of implicit conversions performed to match the operand types in built-in binary operations such as addition, subtraction, less-than, etc (note that bit-shifts are not included). These conversions can occasionally result in subtle bugs, such as:

/* this'll never terminate :C */
int i;
unsigned n = 10;
for (i = 0; n - i >= 0; ++i) { /* ... */ }

A somewhat contrived example, but the point is that mixing signed and unsigned integers can often result in unexpected conversions. In the expression n - i of this example, because n is unsigned and has the same conversion rank as i, i is implicitly converted into unsigned. The result is therefore also unsigned, causing the loop condition to be trivially satisfied. Therefore, it's always a good idea to enable compiler warnings for sign-to-unsigned conversions (-Wsign-conversion in GCC and Clang).

The exact rules are rather long and arcane so I won't duplicate them here, but the key is recognize that there is actually a rationale behind it, which can be summarized as the following rules:

  • Rule A. The final conversion rank is always the larger of the two, no more, no less. The rank is related to the size of integer types, so you'll never get a type whose size is larger than expected.
  • Rule B. The conversion will never cause a signed integer overflow, so undefined behavior won't occur due to the conversion itself (it can still occur due to the operation, however).

Note: Although Rule A holds for the usual arithmetic conversions, integer promotion does violate Rule A. In addition, the rationale presented here is a "best guess" and doesn't necessarily correlate with the rationale of the standards committee.

The unfortunate corollary of the two rules is that frequently you will end up with signed-to-unsigned conversions because the compiler is actively avoiding a potential signed overflow while preserving Rule A. When can this happen? When both of the following hold:

  • Naturally, one operand needs to be unsigned and the other operand needs to be signed.
  • The type of the signed operand can't represent every possible value in the type of the unsigned operand. That is, when an overflow of the signed operand would otherwise occur.

In fact, this is really the only scenario that you have to worry about as far as the usual arithmetic conversions are concerned (for integers). In all other cases, the value is preserved by this conversion and hence you won't see any surprises.

Note: I haven't actually said anything about floating-point numbers. They are like a trump card: any time a floating-point number is present, the conversion will favor the most precise floating-point number. This is not quite as bad as the signed-to-unsigned conversions, but it can still be lossy if you have very large integers.

Official reference: N1570 Section 6.3.1.8.

Comments