diff --git a/nixos/modules/programs/command-not-found/command-not-found.nix b/nixos/modules/programs/command-not-found/command-not-found.nix index 656c255fcb1856..40ffa228f7c5d5 100644 --- a/nixos/modules/programs/command-not-found/command-not-found.nix +++ b/nixos/modules/programs/command-not-found/command-not-found.nix @@ -9,20 +9,8 @@ with lib; let cfg = config.programs.command-not-found; - commandNotFound = pkgs.substituteAll { - name = "command-not-found"; - dir = "bin"; - src = ./command-not-found.pl; - isExecutable = true; - inherit (pkgs) perl; - inherit (cfg) dbPath; - perlFlags = concatStrings (map (path: "-I ${path}/${pkgs.perl.libPrefix} ") - [ pkgs.perlPackages.DBI pkgs.perlPackages.DBDSQLite pkgs.perlPackages.StringShellQuote ]); - }; - -in - -{ + commandNotFound = pkgs.callPackage ./. { dbPath = cfg.dbPath; }; +in { options.programs.command-not-found = { enable = mkOption { diff --git a/nixos/modules/programs/command-not-found/command-not-found.pl b/nixos/modules/programs/command-not-found/command-not-found.pl deleted file mode 100644 index ab7aa204653cd7..00000000000000 --- a/nixos/modules/programs/command-not-found/command-not-found.pl +++ /dev/null @@ -1,51 +0,0 @@ -#! @perl@/bin/perl -w @perlFlags@ - -use strict; -use DBI; -use DBD::SQLite; -use String::ShellQuote; -use Config; - -my $program = $ARGV[0]; - -my $dbPath = "@dbPath@"; - -my $dbh = DBI->connect("dbi:SQLite:dbname=$dbPath", "", "") - or die "cannot open database `$dbPath'"; -$dbh->{RaiseError} = 0; -$dbh->{PrintError} = 0; - -my $system = $ENV{"NIX_SYSTEM"} // $Config{myarchname}; - -my $res = $dbh->selectall_arrayref( - "select package from Programs where system = ? and name = ?", - { Slice => {} }, $system, $program); - -if (!defined $res || scalar @$res == 0) { - print STDERR "$program: command not found\n"; -} elsif (scalar @$res == 1) { - my $package = @$res[0]->{package}; - if ($ENV{"NIX_AUTO_INSTALL"} // "") { - print STDERR <{package}\n" foreach @$res; -} - -exit 127; diff --git a/nixos/modules/programs/command-not-found/default.nix b/nixos/modules/programs/command-not-found/default.nix new file mode 100644 index 00000000000000..22ea4634eda7c0 --- /dev/null +++ b/nixos/modules/programs/command-not-found/default.nix @@ -0,0 +1,19 @@ +{ stdenv, sqlite, pkgconfig, + dbPath ? "/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite" +}: +# build with: nix-build -E '(import {}).callPackage ./. {}' +stdenv.mkDerivation { + name = "command-not-found"; + src = ./src; + buildInputs = [ sqlite ]; + nativeBuildInputs = [ pkgconfig ]; + installPhase = '' + mkdir -p $out/bin + $CXX -O2 -std=c++17 -Wall \ + -DDB_PATH=\"${dbPath}\" \ + -DNIX_SYSTEM=\"${stdenv.system}\" \ + $(pkg-config --cflags --libs sqlite3) \ + -o $out/bin/command-not-found \ + command-not-found.cpp + ''; +} diff --git a/nixos/modules/programs/command-not-found/src/command-not-found.cpp b/nixos/modules/programs/command-not-found/src/command-not-found.cpp new file mode 100644 index 00000000000000..763d6a64fcdb99 --- /dev/null +++ b/nixos/modules/programs/command-not-found/src/command-not-found.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include + +#include + +int queryPackages(std::string system, std::string program, + std::vector &packages) { + sqlite3 *raw_db = nullptr; + if (sqlite3_open(DB_PATH, &raw_db) != SQLITE_OK) { + fprintf(stderr, "cannot open database \"%s\": %s\n", DB_PATH, + sqlite3_errmsg(raw_db)); + }; + std::unique_ptr> db(raw_db, [](auto db) { sqlite3_close(db); }); + + const char *sql = + "select package from Programs where system = ? and name = ?;"; + + sqlite3_stmt *raw_stmt = nullptr; + if (sqlite3_prepare_v2(db.get(), sql, -1, &raw_stmt, NULL) != SQLITE_OK) { + fprintf(stderr, "Failed to prepare query: %s\n", sqlite3_errmsg(db.get())); + return 1; + } + std::unique_ptr> stmt(raw_stmt, [](auto stmt) { sqlite3_finalize(stmt); }); + + if (sqlite3_bind_text(stmt.get(), 1, system.c_str(), -1, 0) != SQLITE_OK) { + fprintf(stderr, "Failed to bind system parameter: %s\n", + sqlite3_errmsg(db.get())); + return 1; + }; + if (sqlite3_bind_text(stmt.get(), 2, program.c_str(), -1, 0) != SQLITE_OK) { + fprintf(stderr, "Failed to bind program parameter: %s\n", + sqlite3_errmsg(db.get())); + return 1; + }; + + while (sqlite3_step(stmt.get()) == SQLITE_ROW) { + packages.push_back((const char *)(sqlite3_column_text(stmt.get(), 0))); + } + + return 0; +} + +int main(int argc, char **argv) { + const char *system = getenv("NIX_SYSTEM"); + const char *program = argv[1]; + + if (!system) { + system = NIX_SYSTEM; + } + + if (argc < 2) { + fprintf(stderr, "USAGE: %s PROGRAM\n", argv[0]); + return 1; + } + + std::vector packages = {}; + if (queryPackages(system, program, packages) < 0) { + return 1; + }; + + if (packages.size()) { + auto advice = packages.size() > 1 ? + "It is provided by several packages. You can install it by typing on of the of following commands:\n" : + "You can install it by typing:"; + fprintf(stderr, + "The program '%s' is currently not installed. %s\n", + program, advice); + for (auto pkg : packages) { + fprintf(stderr, " nix-env -iA nixos.%s\n", pkg.c_str()); + } + } else { + fprintf(stderr, "%s: command not found\n", program); + } + + return 127; +}