Command-not-found support

We want to allow distributions to hook snapd into bash’s “command not found” support. This is the thing that, in regular Ubuntu, prints e.g.

$ httpd
No command 'httpd' found, did you mean:
 Command 'http' from package 'httpie' (universe)
 Command 'xttpd' from package 'xtide' (universe)
httpd: command not found
$

(in other distributions it can be interactive, taking you all the way through installing the package and trying the command again).

The effort has several parts. In no particular order:

  • make the data available from the store
  • on core (at least) pull the data from the store regularly, and have a minimal command_not_found_handle that serves data from there
  • look into distributing the above data via the debian mirror system, and have apt fetch it as additional metadata for the current command-not-found.
  • look into replacing the (currently too slow for core) implementation of command-not-found with the above one in general Ubuntu, consuming the same catalogs as currently.
  • support other distributions in adding support for the additional source of packages in command-not-found. SuSE and Fedora do their own thing (and Fedora currently uses packagekit which is single-source, but is moving to just using gnome-software as a backend? perhaps not as the startup time would be a killer). We’re hopeful (but perhaps delusional) that the above faster command-not-found would be good enough to be cross-distro, in the fullness of time.

shameless plug, I have a minimal command-not-found written in rust (to gloss over the language) over at https://github.com/sergiusens/command-not-found

Yes! I’m aware. Sadly rust is AFAIK almost impossible for distributions so I’ll probably be writing glob c.

I was told a while back by some people familiar with the archive that command-not-found in something like rust would be a nice improvement on what we have today, unless you want to SRU it into the past, then yeah, that might be a bit more complicated.

Given how gnome is moving to rust for most things, I expect most distros to start including it as well.

That said, engineering practices tell you to use the language most of the team i familiar with unless there is an obvious advantage to doing otherwise.

We discussed command-not-found integration with snaps a bit today and I would like to start to talk about the UI.

Exact matches today

Right now command-not-found has the following outputs:

For a single package that provides a binary:

The program '2vcard' is currently not installed. You can install it by typing:
sudo apt install 2vcard

If multiple packages provides a given binary:

The program 'vi' can be found in the following packages:
 * vim
 * vim-gtk3
 * vim-tiny
Try: sudo apt install <selected package>

Exact matches with snaps and debs

For classic we now need to “mix-in” results from both debs and snaps. The strawman below integrates the deb and snap results closely. Alternatively we could keep command-not-found as is and just call something like snap advice-command <command> after command-not-found did the apt suggestions. Suggestions welcome.

Here is a strawman with the various cases:

no deb and no snap

Nothing to output

single deb xor single snap

deb:

The program '2vcard' is currently not installed. You can install it by typing:
sudo apt install 2vcard

snap:

The program 'hello-snap-cmd' is currently not installed. You can install it by typing:
sudo snap install hello-snap

single deb and single snap

The program 'foo' is available as a snap package and a deb package:
 * snap 'foo-snap'
 * deb 'foo-deb'
Try: sudo snap install <selected snap> or sudo apt install <selected deb>

multiple debs and no snap (and vice versa)

The program 'foo' can be found in the following deb packages:
 * 'foo-deb'
 * 'bar-deb'
Try: sudo apt install <selected deb>

(and the same text if only snaps are found)

multiple debs and single snap (and vice versa)

The program 'foo' is available as in multiple snap packages and as a deb package:
 * snap 'foo-snap' 
 * snap 'bar-snap' in track 'unstable'

 * deb 'foo-deb' 
Try: sudo snap install <selected snap> or sudo apt install <selected deb>
  1. multiple debs and mulitiple snaps
The program 'foo' is available as in multiple snap packages and as a deb package:
 * snap 'foo-snap' 
 * snap 'bar-snap' in track 'unstable'
 * deb 'foo-deb' 
 * deb 'bar-deb'
Try: sudo snap install <selected snap> or sudo apt install <selected deb>

Fuzzy matches

For mistyped commands it also offers:

No command 'emacsx' found, did you mean:
 Command 'emacs' from package 'emacs24-nox' (universe)
 Command 'emacs' from package 'emacs25' (main)

Adding snaps to the above sounds easy, it could simply be:

No command 'emacsx' found, did you mean:
 Command 'emacs' from snap 'emacs'
 Command 'emacs' from snap 'emacs' in track '24'
 Command 'emacs' from package 'emacs24' (main)

Thanks for those details, Michael.

I wonder if we can make the text more even across all alternatives, and also in terms of the options contained inside them.

We seem to have two main modes of operation: either we have an exact match, or we don’t, so as a preliminary idea for illustration and exploration, imagine something along those lines:

Application 'foo' not found, but may be installed.
For 'deb-with-foo' version 1.2 (universe):
    sudo apt install deb-with-foo
For 'snap-with-foo' version 1.2.1 (stable) by 'mvo':
    sudo snap install snap-with-foo
For 'snap-with-foo' version 1.3.0.beta1 (beta) by 'mvo':
    sudo snap install --channel=beta snap-with-foo 

This would be the result if there are any exact matches at all (deb or snap).

Without any exact matches, we’d instead show nearby matches in a similar way:

Application 'fooz' is not known, but similar names exist.
For 'foo' inside 'deb-with-foo' version 1.2 (universe):
    sudo apt install deb-with-foo
For 'fao' inside 'snap-with-foo' version 1.2.1 (stable) by 'mvo':
    sudo snap install snap-with-foo
For 'fao' inside 'snap-with-foo' version 1.3.0.beta1 (beta) by 'mvo':
    sudo snap install --channel=beta snap-with-foo 

How does that look?

how would it work for something that was also available as a flatpak? Wouldn’t this approach get a little too long winded?

Today command-not-found displays a line per alternative. That’s just twice as many because we need to qualify the delta between the alternatives. The other approaches would be to not qualify the difference, which means giving options becomes strange (“Okay, but why?”), or to cramp in one very long line which will often wrap.

@niemeyer Your suggestion looks good, I like that its more uniform. Please note that command-not-found for debs does not have version information currently and adding that is some extra work (if we want to do it).

With this suggestion an implementation could look like this:

  1. command-not-found queries its internal DB
  2. command-not-found calls snap advice-command and captures output and exit-code where a zero exit-code means that there are snaps available
  3. command-not-found shows header “Application ‘foo’ is not installed, but can be installed.” if any of its internal or external helper has results.
  4. command-not-found shows the output of snap advice-command verbatim. I.e. we need to make sure that it is not looking out of place with the existing output.
  5. command-not-found shows the result from its internal DB query

With the above we only need minimal changes to command-not-found and we can evolve snap advice-command (or what name we pick) independently. The “cost” in c-n-f is one extra fork/exec which is not great but also not too bad.

On core systems we just install a command_not_found_handle in bash that calls snap advice-command directly.

Does that sound reasonable?

with the current effort going on around rewriting c-n-f to be better (faster), i.e. https://github.com/shawnl/command-not-found, I feel we could work with that to replace c-n-f with something cross distro and more packaging-agnostic without needing to fork out.

OTOH, if we can agree on a protocol for the forking helper that’d work very nicely (and c-n-f could be just coordinating helpers, with no internal db)

@mvo It’s okay to miss version for that one line for the time being. We can keep that as a goal for when we have the time to implement it.

Sounds reasonable. For the command name, maybe “advise-install” or similar?

Yeah, that’d be reasonable I guess. The issue right now is we don’t have a lot of time to discuss a global plan everybody would be happy with without risking missing the 18.04 window. We can rediscuss and change things later on, but need something working well very soon.

@chipaca Thanks for pointing me to this code, it looks quite interesting. I poked around a little bit and I think the helper approach would work for it as well. It is also interesting that it does not solve the “alternatives” problem AFAICT (which the ubuntu version of c-n-f does). It looks like this implementation is using the “Contents” file of the archive to find the binaries. This will find most but a small (but important) subset of binaries that use update-alternatives to register there /usr/bin/$foo is missing. These include things like vi, emacs, awk etc. I think that c-n-f can only be replaced once this issue is resolved (maybe it is already by the git repo, I have not looked deeply into it yet). But in any case, it looks like the amount of work we need to do in either is relatively small if we go with the helper approach.

yes, the alternatives is mentioned in the bug report but that bit is relatively trivial to add tbh; i think that using the wrong source is potentially a bigger blocker – but again not hard to fix. In any case I think the helper approach is sane at least for now.

On the other other hand, I think having the snap helper in C, possibly using the bisect code from this project (or very similar) would be good for us as well. I don’t see a good reason for making this be go tbh (and it might block adoption elsewhere). As snapd is going to be downloading the data and sorting it etc, the helper can be relatively trivial…

Thanks again for pointing me to the bug report, that was an interessting read.

I did not see anything in there how the update-alternatives mechanism for commands will be captured though. Did I miss that? As far as the Contents file is concerned /usr/bin/vi does not exist. However we could probably teach the C implementation to look at the old gdbm index files and/or define a declarative way for update-alternatives (but extra work :slight_smile:

I am not (too) opposed to doing the helper in C (except that its in C) - if we can use modern C and glib it is probably bearable.

at some point my mind mixed up alternatives with typos and ran with that, sorry

@chipaca No worries!

@Chipaca When you have a moment, would you mind to update this topic with the newly proposed snippets, etc?