dburrows/ blog

archive

To reply to a blog post, email me at Daniel_Burrows@alumni.brown.edu. Please let me know whether I should post your reply on the blog page.

Package Management Sudoku 2

I got several emails in reply to my previous post on representing Sudoku problems as package dependencies. Several people asked about the algorithm that aptitude uses to solve these problems. A slightly out-of-date discussion package management and the aptitude algorithm can be found at http://people.debian.org/~dburrows/model.pdf. One question was interesting enough to deserve a follow-up blog post: Wolf T. referred to the page http://norvig.com/sudoku.html and asked how long it would take aptitude aptitude to solve the hard Sudoku problems on that page. He suspected that aptitude would do basic constraint propagation and then be forced to exhaustively search all the possible solutions to the problem.

In brief: the answer is that it doesn't work by default, but not for the reason he suggested, and you can make it work with a few command-line arguments. In order to try this out you'll need a new version of the Sudoku reducer that can parse the problems on that Web page: debsudoku2.py.

If I do a straight conversion of one of those problems and then run it through aptitude, it goes spinning off and never comes back. But aptitude is coded to avoid exhaustive searches; otherwise it couldn't solve dependency problems in reasonable amounts of time. In this particular situation, though, aptitude is clearly performing an exhaustive search; here's one series of status outputs:

open: 5124; closed: 4993; defer: 0; conflict: 374
Resolving dependencies...
open: 10090; closed: 9942; defer: 0; conflict: 398
Resolving dependencies...
open: 14726; closed: 14888; defer: 0; conflict: 440
Resolving dependencies...

Here open is the number of partial solutions that aptitude wants to examine, and closed is the number that it's already examined. aptitude isn't making progress here and it isn't finding solutions; in fact, it's creating, on average, about two new partial solutions for every one it examines.

Why is aptitude doing this? Its resolver isn't a brute-force algorithm; in fact, it looks remarkably like the Sudoku solver discussed at http://norvig.com/sudoku.html. On each step, aptitude takes a partial solution, picks one dependency, and resolves it by installing or removing a package. This is pretty much equivalent to filling in one not-filled-in cell; in fact, in the conflicts Sudoku reduction, it's exactly equivalent.

There is one difference, though, and it stems from a difference in the problem domains of Sudoku and package management:

Not all dependency resolutions are created equal.

In Sudoku, any solution to the puzzle will do. Not so in package management; solving a dependency problem by removing every package on the system will produce a formally correct answer and a very unhappy user. Even results that are not utterly disastrous could be annoying to the user, so the resolver needs to somehow aim for good answers.

The page on Sudoku solvers suggests sticking to whatever value you placed in a cell, unless that doesn't lead to any solutions at all. This is technically known as a depth-first search. The problem with taking this approach when resolving package dependencies is that an apparently safe choice can end up requiring undesirable actions later on (like removing large swathes of the system). For this reason, aptitude tries to strike a balance between sticking to its current line of reasoning, and abandoning it when it seems to be turning out badly (this makes it a form of best-first search).

On typical Debian archives, the package relationships are simple enough that it's OK if aptitude does this from time to time; it makes the search a little more thorough (thus costly), but the ability to back out of questionable branches is very valuable. But it looks like when aptitude is applied to Sudoku puzzles, it has trouble settling on a single series of choices. It keeps thinking that the solutions are getting worse, so it backs up and tries another branch, but they don't look any better over there, so it backs up and tries another branch, and so on.

The option Aptitude::ProblemResolver::StepScore sets how hard aptitude will try to stick to the same line of reasoning. Its default value is 70, which is reasonable for package archives, but turning it up to a large value such as 10000 will cause aptitude to use a straight depth-first search, just like the Sudoku page recommends:

$ aptitude -s -o 'aptitude::problemresolver::stepscore=10000' -o 'aptitude::auto-install=false' install puzzle

Setting auto-install to false is a workaround for another odd corner case/bug that happens to bite this Sudoku problem: for technical reasons, doing this lets aptitude know that it should avoid solutions that cancel the installation of puzzle. Otherwise you have to sit there telling it no for a bunch of solutions you aren't interested in. Future versions of aptitude will probably provide a way to explicitly give hints to the resolver.

With these options, aptitude was able to solve the hard Sudoku puzzles that I gave it in anywhere from fifteen seconds to a minute and 47 seconds. In contrast, the straight Sudoku solver does 30 problems in a second. Why is aptitude so slow? Based on skimming debug traces and looking at the run-time behavior, I conjecture that there are several reasons:

  1. The aptitude search algorithm is very heavyweight. In the case where it takes over a minute, it only actually examines about 5,000 possible solutions. I don't know exactly where all this time is going, but I do know that the resolver does a lot of work on each step, for instance to try and detect dead ends that it hasn't reached yet. This is the conflicts counter that appears while the resolver is running. This pays off tremendously in terms of allowing aptitude to skip huge amounts of work (it may be why aptitude can solve Sudoku puzzles at all!), but it means that the resolver takes a long time for each potential solution it looks at.

  2. The Sudoku page suggests taking the cell with the fewest number of possible entries as the choice. I distinctly remember writing code to do this in aptitude, but it looks like I threw it out at some point or never committed it. I don't see why it should be any worse than the current approach, though (which is to pick an arbitrary dependency); maybe it just got misplaced at some point. Implementing this behavior decreased the time required to solve one puzzle from a minute and 45 seconds to just under a minute; I've committed this change to the post-lenny branch.

  3. The Sudoku resolver has the advantage of being able to very quickly identify illegal moves. aptitude's resolver can learn this information, but it takes time and it can't apply Sudoku-specific optimizations. For instance, the Sudoku solver knows exactly which values can be placed in a given cell at any time. aptitude has to actually consider each of the cells that's available as a possibility and prove to itself that, e.g., 4 and 6 can't be written in the same cell at the same time. And although it can learn this information, it takes time to do so and it takes time to see whether any of the past conflicts that it remembers are present in a given solution.

As I noted above, I've already checked in one change to the resolver's behavior. It might also be worth seeing what happens with a massive increase in StepScore, so I've pegged it at 10000 on my computer to see whether the results I get are noticeably worse.

The third item above -- the fact that aptitude needs to do more work to understand which packages can't be installed -- is interesting. The Sudoku solver is very proactive about this: it keeps track at all times of which cell values are legal, and updates these sets every time it transitions. In contrast, aptitude constantly re-checks all the possible moves it could make to see which ones are currently legal.

I think that the resolver's speed could be boosted a little by doing the following:

  1. Cache the currently legal solutions of each dependency at the time it's inserted into the queue.

  2. When a new search node is generated by resolving a dependency, drop the formerly legal moves that are knocked out (for instance, by installing a different version of the same package).

  3. Come up with a quick way of re-testing new conflicts (the global list of dead-ends) so that search nodes that are already in the queue can be checked against the conflicts that were added since they were queued.

These changes won't make aptitude able to solve problems that it couldn't solve before, or at least not very many of them, but they should make the resolver faster on every problem it encounters. However, I don't think I want to actually implement them. It would be tricky to confirm that the resolver got faster on real dependency problems (since it's normally almost instantaneous), the changes would make the resolver code more complex and fragile (less readable, less maintainable), and there's a real chance of breaking something. Unless there are cases where the resolver is currently too slow on real package archives that don't involve an exponential blowup, I'm going to shelve the idea for now.

Posted Sat 23 Aug 2008 11:15:00 AM PDT Tags: apt cs package management sudoku
Package Management Sudoku

Update: There are more notes on how aptitude performs as a Sudoku solver here.

Russell Coker recently wrote a post entitled Ownership of the Local SE Linux Policy. This post has nothing to do with the substance of his post, which is a discussion of how distributions should configure SELinux by default. I know nothing about SELinux, but something that Russell said in passing caught my attention:

I am not aware of the Debian package dependencies (or those of any other distribution) being about to represent that the postfix package depends on selinux-policy-default-postfix if and only if the selinux-policy-default package is installed. Please note that I am not suggesting that we add support for such things, a package management system that can solve Sudoku based on package dependency rules is not something that I think would be useful or worth having.

(emphasis added)

As it happens, a little-known fact about the Debian packaging system is that you can, in fact, describe Sudoku puzzles in it!

Note for the impatient: If you want to skip all this talk and jump to the code, see debsudoku.py.

For those of you who, like me, are not Sudoku-heads, here's a quick summary of the rules (for the common case of a game with 3 blocks):

  1. The game is played by filling in a 9-by-9 grid with numbers between 1 and 9, inclusive. The board is divided into 9 3-by-3 blocks of grid cells in the obvious way.
  2. All the cells in a column must contain different numbers.
  3. All the cells in a row must contain different numbers.
  4. All the cells in a block must contain different numbers.

There are many ways that you could convert a Sudoku problem into a Debian package archive, but here's a particularly simple one that is close to my summary of the rules. Create 9 * 9 * 9 = 729 packages, named cellR.C-V where R stands for row, C for column, and V for value. Each of these packages represents writing the value V in the cell (R, C). To describe the relationships of each cell to the other cells in the problem, we can create several virtual packages:

Converting these requirements to package dependencies is quite straightforward. For the package cellR.C-V, in block row BR and block column BC, we add these lines to the package's definition:

Provides: cellR.C, blockBR.BC-V, rowR-V, colC-V
Conflicts: cellR.C, blockBR.BC-V, rowR-V, colC-V

For instance, the cell at (5, 8) will get these provides/conflicts for V=2:

Provides: cell5.8, block2.3-2, row5-2, col8-2
Conflicts: cell5.8, block2.3-2, row5-2, col8-2

This relies on the fact that self-conflicts are ignored (see Debian Policy section 7.4).

To actually describe the puzzle we want to solve, we need to add another package that requires two things:

  1. Every cell contains a value.
  2. The cells that the problem fixes have their fixed values.

For instance,

Package: puzzle
Depends: cell1.1, cell1.2, ..., cell5.9-3, cell6.1, ....

if we want to require that the cell at (5, 9) contains the value 3.

Proof-of-concept

As a proof-of-concept, I have written a hacky Python script, named debsudoku.py, that can convert ksudoku saved games into Packages files suitable for use with apt-get or aptitude. In fact, it has two modes: conflicts, which implements the conversion described above, and depends, which implements a different but logically equivalent conversion using only Depends instead of Conflicts. To use the script, save a ksudoku board without doing any work on it, then run

python debsudoku.py /path/to/the/ksudoku/board

I've only done minimal testing on this, so don't use it to fly airplanes (in fact, please don't use any Sudoku player to fly airplanes), but I did use it, in combination with aptitude, to solve a single ksudoku puzzle.

Real-world evaluation

Interestingly, in a test with an easy puzzle generated by ksudoku, aptitude was able to solve the puzzle in just a few seconds when it was expressed with the conflicts reduction, but failed to find a solution at all using depends (I stopped it after several minutes).

I think this is due to how the depends reduction is performed. Take rows: instead of generating conflicts between cells in a row, it requires that each row contain every number between 1 and 9. The problem with this is that aptitude's resolver functions poorly when there are non-obvious conflicts in the dependency structure. It will decide to try a combination of packages that can't be installed together, but it won't realize it. As a result of this, it ends up trying many possible combinations of packages. For instance, it might try to place the number 3 in both (2, 5) and (5, 5). There is no dependency outlawing this in the depends reduction, so aptitude will happily continue trying to resolve dependencies. Since it has used 3 twice in the fifth column, it will not be able to find any way to resolve all the dependencies that state that all the numbers between 1 and 9 appear in the 5th column, but it lacks the higher-level reasoning that would allow it to break out; it will end up trying many or most of the possible ways of filling cells in before it gives up on the offending re-use of 3.

Luckily, real-world package archives (what the aptitude resolver is designed to run on) don't usually look like this.

Posted Tue 19 Aug 2008 10:23:00 PM PDT Tags: apt cs package management sudoku
No hyphenation joy in GTK+.

I've thought on and off that it would be nice for the graphical version of aptitude to have decent hyphenation. Word-wrapping descriptions looks OK, but when the text area is thinner than the entire screen, it can look a little ragged on the right-hand side. For instance, take this rendering of the description of the toy program bb:

Unhyphenated text is ugly

Not too bad, but if I render it in LaTeX, here's how it comes out:

Hyphenated text is pretty

Of course, for very thin text columns (and the above is arguably pushing it), hyphenation and text justification aren't what you want. But it would be nice to be able to at least see what it looked like.

Figuring that GTK+ might have some support for this, I delved into Google. I found this:

Line breaking

Line breaking adds additional layers of complexity. For one, all potential line breaks must be identified, including discretionary hyphens for Latin scripts, and word boundaries in (???). Secondly, inserting hyphens may require reshaping. Third is the line breaking algorithm itself, which may be nontrivial.

-- Pango 0.1 proposal, Raph Levien, 1999

and this:

Future plans for Pango include support for high quality printing, better hyphenation support, full justification, and the ability to handle vertical text such as Chinese and Japanese.

-- Pango: Text handling for the World, LWN (Forrest Cook) 2001

and also this:

I've been working on code to do hyphenation, hopefully to add to Pango. My new code is faster than libhnj and groff and uses less memory.

-- Hyphenation Design (Was Re: Possible Pango 1.4 ideas), Damon Chaplin, post to org.gnome.gtk-i18n-list.

But no sign that any work has been done on hyphenation recently. :-(

The one intriguing thing I noticed, although it's not GTK+ at all, was the libhyphen0 library, which might be useful. Unfortunately, it's a bit more complicated than just flipping a switch in GTK+: the library is documentation-free, so to figure out how to use it I'll need to read through its source code and deduce the expected interface from that. Meh. Probably something to stick way at the bottom of the priority list.

Posted Mon 11 Aug 2008 09:16:58 PM PDT Tags: gtk hyphenation
aptitude 0.4.11.9 released

I've released version 0.4.11.9 of the aptitude package manager (release notes). This release incorporates a few trivial bug-fixes that I hoped to get out before the lenny freeze, but real life intervened. More importantly, it includes the fix for bug #483459, a non-fatal but highly annoying bug that affected Polish users. I hope the release team allows this one into lenny after it's been tested in unstable, but we'll see. I'll put in a formal request for an exemption once the new release has steeped a enough.

Posted Mon 11 Aug 2008 08:44:19 PM PDT Tags: aptitude release
CWidget tutoral 1: 'Hello, world!'

Late last year, I spent some time disentangling aptitude's internal UI library and packaging it as a separate library, which I named cwidget. This article is the first in what will hopefully become a series describing the basic concepts and APIs of cwidget. It applies to the versions of cwidget currently available in Debian sid and lenny, 0.5.11-1 and 0.5.12-1. Autogenerated documentation of cwidget can be found here (but beware that as of this writing it is somewhat incomplete).


In this tutorial, I will walk through a simple program (hello.cc) that initializes cwidget, displays a simple message box, and then terminates the program when the user confirms the message. I assume a basic familiarity with C++.

Screenshot of the 'Hello, World' program

The first thing to do is to include the portions of cwidget that our program will use. Most importantly, we need the top level cwidget routines. These are the global routines that initialize cwidget, shut it down, and control its main loop (among other things).

#include <cwidget/toplevel.h>

cwidget comes with a collection of stock dialog boxes for things like displaying messages to the user. To access the routines that build these dialogs, we write:

#include <cwidget/dialogs.h>

All the symbols provided by the cwidget library are in the cwidget namespace. Using the full library name means that there's a reasonable chance that its names will not conflict with the names provided by other libraries; however, it's a real pain to type cwidget over and over. Since this is the only namespaced library we are using in this program, it's handy to define cw as an alias for cwidget:

namespace cw = cwidget;

Now we're ready to write the main routine. The first interaction of client code with the cwidget library is to initialize it. This will initialize the ncurses library and put the terminal into a mode suitable for a full-screen curses program.

cw::toplevel::init();

Next, we create a dialog box that will be displayed to the user. There are two things to note about this code:

  1. cwidget is Unicode-aware and the ok() routine expects the message string to be a wide-character string. In this case, that means that we need to pass in a wide-character string (indicated by typing L in front of the string).

  2. The second argument tells cwidget what to do when the ok button is pressed. The expression

      sigc::ptr_fun(cw::toplevel::exitmain);
    

    creates a slot using libsigc++. Briefly, a slot is a reference to a function or to a method of a class instance. When invoked, this particular slot will call cwidget::toplevel::exitmain(), which causes the main cwidget loop to exit. The slot is wrapped in cwidget::util::arg, a utility function that handles passing slots as optional arguments.

cw::widgets::widget_ref dialog =
    cw::dialogs::ok(L"Hello, world!",
                    cw::util::arg(sigc::ptr_fun(cw::toplevel::exitmain)));

Now that we have a widget, the next step is to arrange for it to appear on the terminal. In cwidget, control of the terminal is assigned to a single top-level widget: whatever it displays is what gets displayed on the terminal, and all user input is passed directly to it. In a real program this widget will typically manage a collection of sub-widgets and assign each one a screen region, but for now let's just display the dialog box we created:

cw::toplevel::settoplevel(dialog);

With everything initialized, we're ready to start the main loop. mainloop() will keep the display up-to-date and dispatch incoming events (for instance, keystrokes and mouse presses) until exitmain() is invoked.

cw::toplevel::mainloop();

When the user presses Enter or clicks on the Ok button in the dialog, exitmain() will be invoked by the binding that we set up earlier. Once this happens, we need to shut down cwidget in order to restore the terminal to its original state. If you skip this step, the terminal will be garbled and will not work properly when your program exits.

cw::toplevel::shutdown();

And that's it! To compile the program, change to the directory containing hello.cc and run:

$ g++ -o hello hello.cc $(pkg-config --cflags --libs cwidget)

Of course, you'll need to have the g++ and libcwidget-dev packages installed for this to work!

Posted Fri 04 Jul 2008 01:03:00 PM PDT Tags: cwidget tutorial
Welcome to Obey Arthur Liu, GSOC coder

So I've been inexcusably tardy in announcing this, but Obey Arthur Liu is working on aptitude for Google's Summer of Code program. His goal is to write a third interface to the program, using GTK+ to produce the UI; as the abstract for his project states:

I will create a GTK+ GUI for Aptitude that will work alongside improved current ncurses and command-line interfaces. This will offer an alternative to Synaptic with an interface design geared toward usability and advanced functionality.

I'm looking forward to seeing what he comes up with! I think there are a lot of interesting possibilities in the design space of GUI package managers; hopefully he'll have time to explore at least a few of them. At the moment he's focusing on figuring out the care and feeding of the apt library backend and learning some of the quirks you have to deal with when writing a frontend against it.

Arthur's blog can be found at http://www.milliways.fr and on Planet Debian. The Mercurial repository for his work can currently be found at http://dev.graffit.net/aptitude/hg/.

Posted Thu 19 Jun 2008 07:22:00 AM PDT Tags: aptitude debian gsoc
aptitude 0.4.11.4 released

I've released version 0.4.11.4 of the aptitude package manager (release notes). The main motivation for this release is bug #483920, which would cause the resolver to choose solutions badly (e.g., it might prefer downgrading a bunch of packages to keeping them at their current versions). It also includes a simple fix for bug #136874, the fact that the output of aptitude search is badly suited for scripts (it still is, but now there's a command-line option that will make it better).

Posted Sat 07 Jun 2008 06:38:00 PM PDT Tags: aptitude release
Worst Debian day ever.

Regarding the OpenSSL debacle, Julien Blache writes:

Worst Debian day ever since the 2003 compromise. And that was a BAD one.

I disagree. This is far, far worse than the 2003 compromise. The compromise was scary, but the key updates for users were straightforward and as far as we know, user security was never actually compromised. In contrast, every single user who uses Debian or Ubuntu for anything serious is now (Update: and has been for two years) vulnerable to attacks on their supposedly secure cryptography [0] unless they perform a labor-intensive and error-prone series of steps to regenerate all their cryptographic keys -- not to mention finding a secure way to distribute their public keys to everyone who needs them!

[0]: Update: I should perhaps make it clear that I mean anyone who generated a key on such a system. But in a way this makes things worse: unless you're willing to regenerate every key in sight, you need to check each key manually, which means that there's a nontrivial chance that you'll overlook one and leave yourself vulnerable...

Posted Wed 14 May 2008 08:10:00 AM PDT Tags: debian
Breaking radio silence

We've spent the last week and a half moving out of the bizarre twilight zone known as Redmond into the city of Seattle proper. This cuts down on my commute tremendously (although poor Kate has to do the opposite of what I used to do each day), and it's great to be able to walk to the grocery store whenever I want. It may also mean that I'll cut back a bit on my Debian work in favor of actually having a life.

Half my stuff is still in boxes, so I'll be busy with this move for a while longer, but at least I have Internet and email access again now.

Posted Fri 09 May 2008 10:30:00 PM PDT
The trifecta of loathing

I talked to my mother last week about the upcoming Pennsylvania primary (scheduled for tomorrow, April 22nd). She lives in the semi-rural Pennsylvania town where I grew up, and so I asked her what her impression was of how the locals felt about the campaign. Well, she said, they think the war is a disaster and they don't want to vote for McCain because they think he'll continue it forever. And of course they don't want to vote for a black man. And they all hate Hillary and think she's evil. So they're having some trouble trying to decide who to vote for; they can't figure out which candidate they despise the least.

Posted Mon 21 Apr 2008 08:30:00 PM PDT Tags: politics
aptitude 0.4.11.2 released

I've released version 0.4.11.2 of the aptitude package manager (release notes). This is basically a bugfix release to clean out some problems that were introduced in the previous release (most notably a nasty problem in safe-upgrade).

Posted Sat 12 Apr 2008 08:07:00 PM PDT Tags: aptitude release
aptitude 0.4.11.1 released

Last weekend, I released version 0.4.11.1 of the aptitude package manager (release notes). This is a minor release focusing mostly on bug-fixes and on a few features that I've been meaning to add but that didn't make it into 0.4.11. I think the most interesting addition is --show-why, which is like --show-deps but traces back to the closest manually installed package.

In some wide-character locales, the fix for bug #472625 in this upload of aptitude triggers bug #473874 and bug #474065 in apt; the symptoms are an error message about line N too long (max 1024) on start-up, and messages saying that method http has died unexpectedly! when you try to update the package lists or install new packages. I've written patches to fix these bugs in apt, and once they're reviewed by the other apt maintainers they should be fixed in the next upload of apt (version 0.7.12). Until then, if you're bitten by this bug, you can work around it by diverting /usr/share/aptitude/aptitude-defaults.$LANG and replacing it with an empty file. For instance, for Russian (ru):

 # dpkg-divert --rename --local --divert /usr/share/aptitude/aptitude-defaults.ru.real --add /usr/share/aptitude/aptitude-defaults.ru
 # echo > /usr/share/aptitude/aptitude-defaults.ru

Once a fixed apt is uploaded, you can remove the diversion like this:

 # rm /usr/share/aptitude/aptitude-defaults.ru
 # dpkg-divert --rename --local --remove /usr/share/aptitude/aptitude-defaults.ru
Posted Sat 05 Apr 2008 08:00:00 AM PDT Tags: aptitude release
'Suck less' mugs on Cafepress

On the Seattle Underground tour, they tell you about Seattle spirit, which boils down to:

If you've got a bad idea, stick with it!

A while back, I posted about how I had created a T-shirt with a cute little graph showing the decrease in Suck over time. I have yet to sell a single one, but I've had a similar graphic pinned up in my cubicle at work for a while, and several people there have told me they think it should go on a coffee mug. So what the heck, I spent some time today resizing the picture, and now you can get it on a mug, large mug, or stein.

Posted Sun 16 Mar 2008 02:39:51 PM PDT
Apologies for spamming the planet...

I just happened to take a look at the output of a Web log analyzer and noticed a huge increase in traffic yesterday. I thought that was odd, since I hadn't written any new blog posts recently or published new stuff on my Web page. So I took a look at the logs themselves and noticed that a whole bunch of my recent hits listed their referer as planet.debian.net. This is where you start getting a sinking feeling...and yes, when I checked the Planet in a Web browser, sure enough, it looks like my whole blog got dumped out on the Planet. For some reason my own RSS reader didn't pick it up, so I wouldn't even have noticed if I hadn't been checking on my Web server stats.

I'm not sure why, but I bet it has to do with the fact that I upgraded ikiwiki yesterday. So if you've seen the new ikiwiki version that came out recently, take a little care when you update and make sure it doesn't impact your RSS feed.

Posted Sun 16 Mar 2008 12:08:00 PM PDT
aptitude 0.4.11 released

I've released version 0.4.11 of the aptitude package manager (release notes). Due to ongoing work by the installer team, I uploaded this release to experimental rather than unstable; once things are clear I will upload a build to unstable (probably promoting the experimental cwidget to unstable at the same time). The initial upload accidentally left out libept support, so I'm preparing a new upload as I write this.

The aptitude search language and its documentation have gotten particular attention in this release. The most visible change is the introduction of a new syntax for search expressions: in addition to the old style of ~i, you can now type ?installed. Not only is this more readable and more memorable, it means that I'll be much more able to expand the breadth of the query language: it was becoming rather difficult to select new single-character flags for search terms, so expanding the namespace is nice. On this note, a number of search terms have been added to the language; see the release notes or the aptitude user's manual for details.


PS: if you're wondering about the quote in the release notes: I just needed to vent a little about that incident in a way that I found amusing and harmless; I'm over it now, and hopefully this will be the last time I mention it in public.

Posted Sun 16 Mar 2008 11:54:00 AM PDT Tags: aptitude release