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.
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:
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.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.
The Sudoku resolver has the advantage of being able to very quickly identify illegal
moves
. aptitude's resolver canlearn
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 actuallyconsider
each of the cells that's available as a possibility and prove to itself that, e.g.,4
and6
can't be written in the same cell at the same time. And although it canlearn
this information, it takes time to do so and it takes time to see whether any of the past conflicts that itremembers
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:
Cache the currently legal solutions of each dependency at the time it's inserted into the queue.
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).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 PDTUpdate: 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
):
- 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.
- All the cells in a column must contain different numbers.
- All the cells in a row must contain different numbers.
- 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:
The virtual package
cellR.Crepresents the statementThe cell (
It will be used to ensure that only one value is placed in any particular cell, and to ensure that all the cells in the puzzle are filled in with something.R,C) has been filled in with some value.The virtual package
blockR.C-Vrepresents the statementA cell in the block (
The blockR,C) has been filled in with the valueV.coordinates
are the block-row and block-column containing the cell; for instance, the cell at (5, 8) is in block (2, 3). This package will be used to ensure that only one cell in a given block contains the same value.The virtual package
rowR-Vrepresents the statementA cell in the row
It will be used to ensure that no two cells in any given row have the same value.Rhas been filled in with the valueV.The virtual package
colC-Vrepresents the statementA cell in the column
It will be used to ensure that no two cells in any given column have the same value.Chas been filled in with the valueV.
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:
- Every cell contains a value.
- 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 PDTI'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:

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

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.
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 PDTLate 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++.

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:
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
in front of the string).LThe second argument tells cwidget what to do when the
ok
button is pressed. The expressionsigc::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 incwidget::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 PDTSo 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 PDTI'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).
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 PDTWe'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 PDTI 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.
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).
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
on start-up, and messages saying that line N too long
(max 1024)
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 method http
has died unexpectedly!/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
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 PDTI 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 PDTI'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
, you can now type ~i
.
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.?installed
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