diff options
63 files changed, 5979 insertions, 569 deletions
diff --git a/.gitignore b/.gitignore index dacd234c9b..a424a397c4 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,8 @@ GTAGS /nix-setuid-helper /nix/scripts/guix-authenticate /nix/scripts/offload +/emacs/Makefile.in +/emacs/Makefile +/emacs/guix-autoloads.el +/emacs/guix-helper.scm +/emacs/guix-init.el diff --git a/HACKING b/HACKING index 9e47b9703b..0231c7d84d 100644 --- a/HACKING +++ b/HACKING @@ -89,6 +89,13 @@ in the form of patches as produced by ‘git format-patch’ sent to guix-devel@gnu.org. Please write commit logs in the [[http://www.gnu.org/prep/standards/html_node/Change-Logs.html#Change-Logs][GNU ChangeLog format]]; you can check the commit history for examples. +Before submitting a patch that adds or modifies a package definition, please +run ‘guix lint PACKAGE’, where PACKAGE is the name of the new or modified +package, and fix any errors it reports. In addition, please make sure the +package builds on your platform, using ‘guix build’. You may also want to +check that dependent package (if applicable) are not affected by the change; +‘guix refresh --list-dependent PACKAGE’ will help you do that. + When posting a patch to the mailing list, use "[PATCH] ..." as a subject. You may use your email client or the ‘git send-mail’ command. diff --git a/Makefile.am b/Makefile.am index fff5958355..1f2c4db80d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -62,17 +62,14 @@ MODULES = \ guix/build/git.scm \ guix/build/gnu-build-system.scm \ guix/build/gnu-dist.scm \ - guix/build/linux-initrd.scm \ guix/build/perl-build-system.scm \ guix/build/python-build-system.scm \ + guix/build/store-copy.scm \ guix/build/utils.scm \ guix/build/union.scm \ guix/build/pull.scm \ guix/build/rpath.scm \ guix/build/svn.scm \ - guix/build/vm.scm \ - guix/build/install.scm \ - guix/build/activation.scm \ guix/build/syscalls.scm \ guix/build/emacs-utils.scm \ guix/packages.scm \ @@ -89,6 +86,7 @@ MODULES = \ guix/scripts/authenticate.scm \ guix/scripts/refresh.scm \ guix/scripts/system.scm \ + guix/scripts/lint.scm \ guix.scm \ $(GNU_SYSTEM_MODULES) @@ -159,7 +157,8 @@ SCM_TESTS = \ tests/nar.scm \ tests/union.scm \ tests/profiles.scm \ - tests/syscalls.scm + tests/syscalls.scm \ + tests/lint.scm SH_TESTS = \ tests/guix-build.sh \ @@ -279,6 +278,10 @@ AM_DISTCHECK_CONFIGURE_FLAGS = \ --with-nix-prefix="$(NIX_PREFIX)" \ --enable-daemon +dist_emacsui_DATA = emacs/guix-main.scm +nodist_emacsui_DATA = emacs/guix-helper.scm +include emacs.am + dist-hook: sync-descriptions gen-ChangeLog assert-no-store-file-names distcheck-hook: assert-binaries-available assert-final-inputs-self-contained diff --git a/README b/README index 9328a4f89e..7dd016ef80 100644 --- a/README +++ b/README @@ -52,19 +52,23 @@ To do so: - Install the dependencies (see 'Requirements' above) and build tools using Guix: - guix package --install={autoconf,automake,bzip2,gcc-toolchain,gettext,guile,libgcrypt,pkg-config,sqlite} + guix package --install autoconf automake bzip2 gcc-toolchain gettext \ + guile libgcrypt pkg-config sqlite - - set the environment variables that Guix recommends you to set during the + - Set the environment variables that Guix recommends you to set during the package installation process: ACLOCAL_PATH, CPATH, LIBRARY_PATH, PKG_CONFIG_PATH - - set the PATH environment variable to refer to the profile: + - Set the PATH environment variable to refer to the profile: PATH=$HOME/.guix-profile/bin:$PATH - - re-run the configure script passing it the option - `--with-libgcrypt-prefix=$HOME/.guix-profile/' + - Re-run the 'configure' script passing it the option + '--with-libgcrypt-prefix=$HOME/.guix-profile/', as well as + '--localstatedir=/somewhere', where '/somewhere' is the 'localstatedir' + value of the currently installed Guix (failing to do that would lead the + new Guix to consider the store to be empty!). - - run "make" and "make install" + - Run "make", "make check", and "make install". * How It Works diff --git a/THANKS b/THANKS index d15cafa987..36eb124da6 100644 --- a/THANKS +++ b/THANKS @@ -7,6 +7,7 @@ suggestions, bug reports, patches, internationalization, or general infrastructure help: Lluís Batlle i Rossell <viric@viric.name> + Federico Beffa <beffa@ieee.org> Marek Benc <merkur32@gmail.com> Carlos Carleos <carleos@uniovi.es> Felipe Castro <fefcas@gmail.com> diff --git a/config-daemon.ac b/config-daemon.ac index a13525a7f5..072fb84b46 100644 --- a/config-daemon.ac +++ b/config-daemon.ac @@ -78,10 +78,13 @@ if test "x$guix_build_daemon" = "xyes"; then dnl lutimes and lchown: used when canonicalizing store items. dnl posix_fallocate: used when extracting archives. dnl vfork: to speed up spawning of helper programs. + dnl `--> now disabled because of unpredictable behavior: + dnl see <http://lists.gnu.org/archive/html/guix-devel/2014-05/msg00036.html> + dnl and Nix commit f794465c (Nov. 2012). dnl sched_setaffinity: to improve RPC locality. dnl statvfs: to detect disk-full conditions. dnl strsignal: for error reporting. - AC_CHECK_FUNCS([lutimes lchown posix_fallocate vfork sched_setaffinity \ + AC_CHECK_FUNCS([lutimes lchown posix_fallocate sched_setaffinity \ statvfs nanosleep strsignal]) dnl Check whether the store optimiser can optimise symlinks. diff --git a/configure.ac b/configure.ac index 71b3ddacaa..698dd85784 100644 --- a/configure.ac +++ b/configure.ac @@ -174,4 +174,11 @@ AC_CONFIG_FILES([scripts/guix], [chmod +x scripts/guix]) AC_CONFIG_FILES([pre-inst-env], [chmod +x pre-inst-env]) AC_CONFIG_FILES([test-env], [chmod +x test-env]) +dnl Emacs interface. +AM_PATH_LISPDIR +emacsuidir="${guilemoduledir}/guix/emacs" +AC_SUBST([emacsuidir]) +AC_CONFIG_FILES([emacs/guix-init.el + emacs/guix-helper.scm]) + AC_OUTPUT diff --git a/doc/emacs.texi b/doc/emacs.texi new file mode 100644 index 0000000000..55feb1bf9f --- /dev/null +++ b/doc/emacs.texi @@ -0,0 +1,321 @@ +@node Emacs Interface +@section Emacs Interface + +@cindex emacs +GNU Guix comes with a visual user interface for GNU@tie{}Emacs, known +as ``guix.el''. It can be used for routine package management tasks, +pretty much like the @command{guix package} command (@pxref{Invoking +guix package}). Specifically, ``guix.el'' makes it easy to: + +@itemize +@item browse and display packages and generations; +@item search, install, upgrade and remove packages; +@item display packages from previous generations; +@item do some other useful things. +@end itemize + +@menu +* Initial Setup: emacs Initial Setup. Preparing @file{~/.emacs}. +* Usage: emacs Usage. Using the interface. +* Configuration: emacs Configuration. Configuring the interface. +@end menu + +@node emacs Initial Setup +@subsection Initial Setup + +To be able to use ``guix.el'', you need to install the following +packages: + +@itemize +@item +@uref{http://www.gnu.org/software/emacs/, GNU Emacs}, version 24.3 or +later; + +@item +@uref{http://nongnu.org/geiser/, Geiser}, version 0.3 or later: it is +used for interacting with the Guile process. + +@end itemize + +When it is done, add the following into your init file (@pxref{Init +File,,, emacs, The Emacs Editor}): + +@example +(require 'guix-init nil t) +@end example + +However there is a chance that @code{load-path} of your Emacs does not +contain a directory with ``guix.el'' (usually it is +@file{/usr/share/emacs/site-lisp/}). In that case you need to add it +before requiring (@pxref{Lisp Libraries,,, emacs, The Emacs Editor}): + +@example +(add-to-list 'load-path "/path/to/directory-with-guix.el") +(require 'guix-init) +@end example + +Do not worry about the efficiency of that @code{require} thing. It will +not load the whole ``guix.el'' package, it will just autoload the main +interactive commands (@pxref{Autoload,,, elisp, Emacs Lisp}). + + +@node emacs Usage +@subsection Usage + +Once ``guix.el'' has been successfully configured, you should be able to +use commands for displaying packages and generations. This information +can be displayed in a ``list'' or ``info'' buffer. + +@menu +* Commands: emacs Commands. @kbd{M-x guix-@dots{}} +* General information: emacs General info. Common for both interfaces. +* ``List'' buffer: emacs List buffer. List-like interface. +* ``Info'' buffer: emacs Info buffer. Help-like interface. +@end menu + +@node emacs Commands +@subsubsection Commands + +You may use the following commands to display packages and generations: + +@table @kbd +@item M-x guix-all-available-packages +@itemx M-x guix-newest-available-packages +Display all/newest available packages. + +@item M-x guix-installed-packages +Display all packages installed in the current profile. + +@item M-x guix-obsolete-packages +Display obsolete packages (the packages that are installed in the +current profile but cannot be found among available packages). + +@item M-x guix-search-by-name +Display package(s) with the specified name. + +@item M-x guix-search-by-regexp +Search for packages by a specified regexp. By default ``name'', +``synopsis'' and ``description'' of the packages will be searched. This +can be changed by modifying @code{guix-search-params} variable. + +@item M-x guix-generations +List generations for the current profile. With numeric prefix, show so +many last generations. + +@end table + +It is possible to change the currently used profile with +@kbd{M-x@tie{}guix-set-current-profile}. This has the same effect as +specifying @code{--profile} option for @command{guix package} +(@pxref{Invoking guix package}). + +@node emacs General info +@subsubsection General information + +The following keys are available for both ``list'' and ``info'' types of +buffers: + +@table @kbd +@item l +@itemx r +Go backward/forward by the history of the displayed results (this +history is similar to the history of the Emacs @code{help-mode} or +@code{Info-mode}). + +@item g +Revert current buffer: update information about the displayed +packages/generations and redisplay it. + +@item R +Redisplay current buffer (without updating information). + +@item C-c C-z +Go to the Guix REPL (@pxref{The REPL,,, geiser, Geiser User Manual}). + +@item h +@itemx ? +Describe current mode to see all available bindings. + +@end table + +@emph{Hint:} If you need several ``list'' or ``info'' buffers, you can +simlpy @kbd{M-x clone-buffer} them, and each buffer will have its own +history. + +@emph{Warning:} Name/version pairs cannot be used to identify packages +(because a name is not necessarily unique), so ``guix.el'' uses special +identifiers that live only during a guile session, so if the Guix REPL +was restarted, you may want to revert ``list'' buffer (by pressing +@kbd{g}). + +@node emacs List buffer +@subsubsection ``List'' buffer + +An interface of a ``list'' buffer is similar to the interface provided +by ``package.el'' (@pxref{Package Menu,,, emacs, The Emacs Editor}). + +Default key bindings available for both ``package-list'' and +``generation-list'' buffers: + +@table @kbd +@item m +Mark the current entry. +@item M +Mark all entries. +@item u +Unmark the current entry. +@item @key{DEL} +Unmark backward. +@item U +Unmark all entries. +@item S +Sort entries by a specified column. +@end table + +A ``package-list'' buffer additionally provides the following bindings: + +@table @kbd +@item @key{RET} +Describe marked packages (display available information in a +``package-info'' buffer). +@item i +Mark a package for installation (with prefix, prompt for output(s) to +install). +@item d +Mark a package for deletion. +@item ^ +Mark a package for upgrading. +@item x +Execute actions on marked packages. +@end table + +A ``generation-list'' buffer additionally provides the following +bindings: + +@table @kbd +@item @key{RET} +List packages installed in the current generation. +@item i +Describe marked generations (display available information in a +``generation-info'' buffer). +@end table + +@node emacs Info buffer +@subsubsection ``Info'' buffer + +The interface of an ``info'' buffer is similar to the interface of +@code{help-mode} (@pxref{Help Mode,,, emacs, The Emacs Editor}). + +``Info'' buffer contains some buttons (as usual you may use @key{TAB} / +@kbd{S-@key{TAB}} to move between buttons---@pxref{Mouse References,,, +emacs, The Emacs Editor}) which can be used to: + +@itemize @bullet +@item (in a ``package-info'' buffer) + +@itemize @minus +@item install/remove a package; +@item jump to a package location; +@item browse home page of a package; +@item describe packages from ``Inputs'' fields. +@end itemize + +@item (in a ``generation-info'' buffer) + +@itemize @minus +@item remove a generation; +@item list packages installed in a generation; +@item jump to a generation directory. +@end itemize + +@end itemize + + +@node emacs Configuration +@subsection Configuration + +There are many variables you can modify to change the appearance or +behavior of Emacs user interface. Some of these variables are described +in this section. Also you can use Custom Interface (@pxref{Easy +Customization,,, emacs, The Emacs Editor}) to explore/set variables (not +all) and faces. + +@menu +* Guile and Build Options: emacs Build Options. Specifying how packages are built. +* Keymaps: emacs Keymaps. Configuring key bindings. +* Appearance: emacs Appearance. Settings for visual appearance. +@end menu + +@node emacs Build Options +@subsubsection Guile and Build Options + +@table @code +@item guix-guile-program +If you have some special needs for starting a Guile process, you may set +this variable, for example: + +@example +(setq guix-guile-program '("/bin/guile" "--no-auto-compile")) +@end example + +@item guix-use-substitutes +Has the same meaning as @code{--no-substitutes} option (@pxref{Invoking +guix build}). + +@item guix-dry-run +Has the same meaning as @code{--dry-run} option (@pxref{Invoking guix +build}). + +@end table + +@node emacs Keymaps +@subsubsection Keymaps + +If you want to change default key bindings, use the following keymaps +(@pxref{Init Rebinding,,, emacs, The Emacs Editor}): + +@table @code +@item guix-list-mode-map +Parent keymap with general keys for ``list'' buffers. + +@item guix-package-list-mode-map +Keymap with specific keys for ``package-list'' buffers. + +@item guix-generation-list-mode-map +Keymap with specific keys for ``generation-list'' buffers. + +@item guix-info-mode-map +Parent keymap with general keys for ``info'' buffers. + +@item guix-package-info-mode-map +Keymap with specific keys for ``package-info'' buffers. + +@item guix-generation-info-mode-map +Keymap with specific keys for ``generation-info'' buffers. + +@end table + +@node emacs Appearance +@subsubsection Appearance + +You can change almost any aspect of ``list'' / ``info'' buffers using +the following variables: + +@table @code +@item guix-list-column-format +@itemx guix-list-column-titles +@itemx guix-list-column-value-methods +Specify the columns, their names, what and how is displayed in ``list'' +buffers. + +@item guix-info-displayed-params +@itemx guix-info-insert-methods +@itemx guix-info-ignore-empty-vals +@itemx guix-info-param-title-format +@itemx guix-info-multiline-prefix +@itemx guix-info-indent +@itemx guix-info-fill-column +@itemx guix-info-delimiter +Various settings for ``info'' buffers. + +@end table diff --git a/doc/guix.texi b/doc/guix.texi index b6cd57acc2..e0251f5ffd 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -506,14 +506,16 @@ Unless @code{--lose-logs} is used, all the build logs are kept in the @var{localstatedir}. To save space, the daemon automatically compresses them with bzip2 by default. This option disables that. -@item --disable-store-optimization +@item --disable-deduplication +@cindex deduplication Disable automatic file ``deduplication'' in the store. By default, files added to the store are automatically ``deduplicated'': -if a newly added file is identical as another one found in the store, -the daemon makes the new file a hard link to the other file. This -slightly increases the input/output load at the end of a build process. -This option disables this. +if a newly added file is identical to another one found in the store, +the daemon makes the new file a hard link to the other file. This can +noticeably reduce disk usage, at the expense of slightly increasde +input/output load at the end of a build process. This option disables +this optimization. @item --gc-keep-outputs[=yes|no] Tell whether the garbage collector (GC) must keep outputs of live @@ -579,6 +581,7 @@ management tools it provides. @menu * Features:: How Guix will make your life brighter. * Invoking guix package:: Package installation, removal, etc. +* Emacs Interface:: Package management from Emacs. * Substitutes:: Downloading pre-built binaries. * Packages with Multiple Outputs:: Single source package, multiple outputs. * Invoking guix gc:: Running the garbage collector. @@ -953,6 +956,8 @@ Finally, since @command{guix package} may actually start build processes, it supports all the common build options that @command{guix build} supports (@pxref{Invoking guix build, common build options}). +@include emacs.texi + @node Substitutes @section Substitutes @@ -1457,7 +1462,10 @@ definitions like the one above may be automatically converted from the Nixpkgs distribution using the @command{guix import} command.}, the package may actually be built using the @code{guix build} command-line tool (@pxref{Invoking guix build}). @xref{Packaging Guidelines}, for -more information on how to test package definitions. +more information on how to test package definitions, and +@ref{Invoking guix lint}, for information on how to check a definition +for style conformance. + Eventually, updating the package definition to a new upstream version can be partly automated by the @command{guix refresh} command @@ -2270,6 +2278,22 @@ search path to be copied in the store, compiled, and made available in the load path during the execution of @var{exp}---e.g., @code{((guix build utils) (guix build gnu-build-system))}. +When @var{references-graphs} is true, it must be a list of tuples of one of the +following forms: + +@example +(@var{file-name} @var{package}) +(@var{file-name} @var{package} @var{output}) +(@var{file-name} @var{derivation}) +(@var{file-name} @var{derivation} @var{output}) +(@var{file-name} @var{store-item}) +@end example + +The right-hand-side of each element of @var{references-graphs} is automatically made +an input of the build process of @var{exp}. In the build environment, each +@var{file-name} contains the reference graph of the corresponding item, in a simple +text format. + The other arguments are as for @code{derivation} (@pxref{Derivations}). @end deffn @@ -2326,6 +2350,7 @@ programming interface of Guix in a convenient way. * Invoking guix download:: Downloading a file and printing its hash. * Invoking guix hash:: Computing the cryptographic hash of a file. * Invoking guix refresh:: Updating package definitions. +* Invoking guix lint:: Finding errors in package definitions. @end menu @node Invoking guix build @@ -2703,6 +2728,29 @@ for in @code{$PATH}. @end table +@node Invoking guix lint +@section Invoking @command{guix lint} +The @command{guix lint} is meant to help package developers avoid common +errors and use a consistent style. It runs a few checks on a given set of +packages in order to find common mistakes in their definitions. + +The general syntax is: + +@example +guix lint @var{options} @var{package}@dots{} +@end example + +If no package is given on the command line, then all packages are checked. +The @var{options} may be zero or more of the following: + +@table @code + +@item --list-checkers +@itemx -l +List and describe all the available checkers that will be run on packages +and exit. + +@end table @c ********************************************************************* @node GNU Distribution @@ -2946,7 +2994,7 @@ more information, @pxref{Invoking guix system}. This command may trigger downloads or builds of missing packages, which can take some time. Once that command has completed---and hopefully succeeded!---you can -unmount @file{/mnt} and boot into the new system. Cross fingers, and +run @command{reboot} and boot into the new system. Cross fingers, and join us on @code{#guix} on the Freenode IRC network or on @file{guix-devel@@gnu.org} to share your experience---good or not so good. @@ -3533,6 +3581,10 @@ system declaration like this: #:extra-modules '("my.ko" "modules.ko"))) @end example +@noindent +Note that for the example above, the SRFI-26 module needs to be imported +(@pxref{SRFI-26,,, guile, GNU Guile Reference Manual}). + It also handles common use cases that involves using the system as a QEMU guest, or as a ``live'' system whose root file system is volatile. diff --git a/emacs.am b/emacs.am new file mode 100644 index 0000000000..86ae3a2c9f --- /dev/null +++ b/emacs.am @@ -0,0 +1,43 @@ +# GNU Guix --- Functional package management for GNU +# Copyright © 2014 Alex Kost <alezost@gmail.com> +# +# This file is part of GNU Guix. +# +# GNU Guix is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# GNU Guix is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +AUTOLOADS = emacs/guix-autoloads.el + +ELFILES = \ + emacs/guix-backend.el \ + emacs/guix-base.el \ + emacs/guix-history.el \ + emacs/guix-info.el \ + emacs/guix-list.el \ + emacs/guix-utils.el \ + emacs/guix.el + +dist_lisp_DATA = \ + $(ELFILES) \ + $(AUTOLOADS) + +nodist_lisp_DATA = \ + emacs/guix-init.el + +$(AUTOLOADS): $(ELFILES) + $(EMACS) --batch --eval \ + "(let ((backup-inhibited t) \ + (generated-autoload-file \ + (expand-file-name \"$(AUTOLOADS)\" \"$(srcdir)\"))) \ + (update-directory-autoloads \ + (expand-file-name \"emacs\" \"$(srcdir)\")))" diff --git a/emacs/guix-backend.el b/emacs/guix-backend.el new file mode 100644 index 0000000000..46d0f06778 --- /dev/null +++ b/emacs/guix-backend.el @@ -0,0 +1,301 @@ +;;; guix-backend.el --- Communication with Geiser + +;; Copyright © 2014 Alex Kost <alezost@gmail.com> + +;; This file is part of GNU Guix. + +;; GNU Guix is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Guix is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file provides the code for interacting with Guile using Geiser. + +;; By default (if `guix-use-guile-server' is non-nil) 2 Geiser REPLs are +;; started. The main one (with "guile --listen" process) is used for +;; "interacting" with a user - for showing a progress of +;; installing/deleting Guix packages. The second (internal) REPL is +;; used for synchronous evaluating, e.g. when information about +;; packages/generations should be received for a list/info buffer. +;; +;; This "2 REPLs concept" makes it possible to have a running process of +;; installing/deleting packages and to continue to search/list/get info +;; about other packages at the same time. If you prefer to use a single +;; Guix REPL, do not try to receive any information while there is a +;; running code in the REPL (see +;; <https://github.com/jaor/geiser/issues/28>). +;; +;; If you need to use "guix.el" in another Emacs (i.e. when there is +;; a runnig "guile --listen..." REPL somewhere), you can either change +;; `guix-default-port' in that Emacs instance or set +;; `guix-use-guile-server' to t. +;; +;; Guix REPLs (unlike the usual Geiser REPLs) are not added to +;; `geiser-repl--repls' variable, and thus cannot be used for evaluating +;; while editing scm-files. The only purpose of Guix REPLs is to be an +;; intermediate between "Guix/Guile level" and "Emacs interface level". +;; That being said you can still want to use a Guix REPL while hacking +;; auxiliary scheme-files for "guix.el". You can just use "M-x +;; connect-to-guile" (connect to "localhost" and `guix-default-port') to +;; have a usual Geiser REPL with all stuff defined by "guix.el" package. + +;;; Code: + +(require 'geiser-mode) + +(defvar guix-load-path + (file-name-directory (or load-file-name + (locate-library "guix"))) + "Directory with scheme files for \"guix.el\" package.") + +(defvar guix-helper-file + (expand-file-name "guix-helper.scm" guix-load-path) + "Auxiliary scheme file for loading.") + +(defvar guix-guile-program (or geiser-guile-binary "guile") + "Name of the guile executable used for Guix REPL. +May be either a string (the name of the executable) or a list of +strings of the form: + + (NAME . ARGS) + +Where ARGS is a list of arguments to the guile program.") + + +;;; REPL + +(defgroup guix-repl nil + "Settings for Guix REPLs." + :prefix "guix-repl-" + :group 'guix) + +(defcustom guix-repl-startup-time 30000 + "Time, in milliseconds, to wait for Guix REPL to startup. +Same as `geiser-repl-startup-time' but is used for Guix REPL. +If you have a slow system, try to increase this time." + :type 'integer + :group 'guix-repl) + +(defcustom guix-repl-buffer-name "*Guix REPL*" + "Default name of a Geiser REPL buffer used for Guix." + :type 'string + :group 'guix-repl) + +(defcustom guix-after-start-repl-hook () + "Hook called after Guix REPL is started." + :type 'hook + :group 'guix-repl) + +(defcustom guix-use-guile-server t + "If non-nil, start guile with '--listen' argument. +This allows to receive information about packages using an additional +REPL while some packages are being installed/removed in the main REPL." + :type 'boolean + :group 'guix-repl) + +(defcustom guix-default-port 37246 + "Default port used if `guix-use-guile-server' is non-nil." + :type 'integer + :group 'guix-repl) + +(defvar guix-repl-buffer nil + "Main Geiser REPL buffer used for communicating with Guix. +This REPL is used for processing package actions and for +receiving information if `guix-use-guile-server' is nil.") + +(defvar guix-internal-repl-buffer nil + "Additional Geiser REPL buffer used for communicating with Guix. +This REPL is used for receiving information only if +`guix-use-guile-server' is non-nil.") + +(defvar guix-internal-repl-buffer-name "*Guix Internal REPL*" + "Default name of an internal Guix REPL buffer.") + +(defun guix-get-guile-program (&optional internal) + "Return a value suitable for `geiser-guile-binary'." + (if (or internal + (not guix-use-guile-server)) + guix-guile-program + (append (if (listp guix-guile-program) + guix-guile-program + (list guix-guile-program)) + ;; Guile understands "--listen=..." but not "--listen ..." + (list (concat "--listen=" + (number-to-string guix-default-port)))))) + +(defun guix-start-process-maybe () + "Start Geiser REPL configured for Guix if needed." + (guix-start-repl-maybe) + (if guix-use-guile-server + (guix-start-repl-maybe 'internal) + (setq guix-internal-repl-buffer guix-repl-buffer))) + +(defun guix-start-repl-maybe (&optional internal) + "Start Guix REPL if needed. +If INTERNAL is non-nil, start an internal REPL." + (let* ((repl-var (guix-get-repl-buffer-variable internal)) + (repl (symbol-value repl-var))) + (unless (and (buffer-live-p repl) + (get-buffer-process repl)) + ;; Kill REPL buffer with a dead process + (and (buffer-live-p repl) (kill-buffer repl)) + (or internal + (message "Starting Geiser REPL for Guix ...")) + (let ((geiser-guile-binary (guix-get-guile-program internal)) + (geiser-guile-init-file (or internal guix-helper-file)) + (repl (get-buffer-create + (guix-get-repl-buffer-name internal)))) + (condition-case err + (guix-start-repl repl + (and internal + (geiser-repl--read-address + "localhost" guix-default-port))) + (text-read-only + (error (concat "Couldn't start Guix REPL. Perhaps the port %s is busy.\n" + "See buffer '%s' for details") + guix-default-port (buffer-name repl)))) + (set repl-var repl) + (unless internal + (message "Guix REPL has been started.") + (run-hooks 'guix-after-start-repl-hook)))))) + +(defun guix-start-repl (buffer &optional address) + "Start Guix REPL in BUFFER. +If ADDRESS is non-nil, connect to a remote guile process using +this address (it should be defined by +`geiser-repl--read-address')." + ;; A mix of the code from `geiser-repl--start-repl' and + ;; `geiser-repl--to-repl-buffer'. + (let ((impl 'guile) + (geiser-guile-load-path (list guix-load-path)) + (geiser-repl-startup-time guix-repl-startup-time)) + (with-current-buffer buffer + (geiser-repl-mode) + (geiser-impl--set-buffer-implementation impl) + (geiser-repl--autodoc-mode -1) + (goto-char (point-max)) + (let* ((prompt-re (geiser-repl--prompt-regexp impl)) + (deb-prompt-re (geiser-repl--debugger-prompt-regexp impl)) + (prompt (geiser-con--combined-prompt prompt-re deb-prompt-re))) + (or prompt-re + (error "Oh no! Guix REPL in the buffer '%s' has not been started" + (buffer-name buffer))) + (geiser-repl--save-remote-data address) + (geiser-repl--start-scheme impl address prompt) + (geiser-repl--quit-setup) + (geiser-repl--history-setup) + (setq-local geiser-repl--repls (list buffer)) + (geiser-repl--set-this-buffer-repl buffer) + (setq geiser-repl--connection + (geiser-con--make-connection + (get-buffer-process (current-buffer)) + prompt-re + deb-prompt-re)) + (geiser-repl--startup impl address) + (geiser-repl--autodoc-mode 1) + (geiser-company--setup geiser-repl-company-p) + (add-hook 'comint-output-filter-functions + 'geiser-repl--output-filter + nil t) + (set-process-query-on-exit-flag + (get-buffer-process (current-buffer)) + geiser-repl-query-on-kill-p))))) + +(defun guix-get-repl-buffer (&optional internal) + "Return Guix REPL buffer; start REPL if needed. +If INTERNAL is non-nil, return an additional internal REPL." + (guix-start-process-maybe) + (let ((repl (symbol-value (guix-get-repl-buffer-variable internal)))) + ;; If a new Geiser REPL is started, `geiser-repl--repl' variable may + ;; be set to the new value in a Guix REPL, so set it back to a + ;; proper value here. + (with-current-buffer repl + (geiser-repl--set-this-buffer-repl repl)) + repl)) + +(defun guix-get-repl-buffer-variable (&optional internal) + "Return the name of a variable with a REPL buffer." + (if internal + 'guix-internal-repl-buffer + 'guix-repl-buffer)) + +(defun guix-get-repl-buffer-name (&optional internal) + "Return the name of a REPL buffer." + (if internal + guix-internal-repl-buffer-name + guix-repl-buffer-name)) + +(defun guix-switch-to-repl (&optional internal) + "Switch to Guix REPL. +If INTERNAL is non-nil (interactively with prefix), switch to the +additional internal REPL if it exists." + (interactive "P") + (geiser-repl--switch-to-buffer (guix-get-repl-buffer internal))) + + +;;; Evaluating expressions + +(defun guix-make-guile-expression (fun &rest args) + "Return string containing a guile expression for calling FUN with ARGS." + (format "(%S %s)" fun + (mapconcat + (lambda (arg) + (cond + ((null arg) "'()") + ((or (eq arg t) + ;; An ugly hack to separate 'false' from nil + (equal arg 'f) + (keywordp arg)) + (concat "#" (prin1-to-string arg t))) + ((or (symbolp arg) (listp arg)) + (concat "'" (prin1-to-string arg))) + (t (prin1-to-string arg)))) + args + " "))) + +(defun guix-eval (str &optional wrap) + "Evaluate guile expression STR. +If WRAP is non-nil, wrap STR into (begin ...) form. +Return a list of strings with result values of evaluation." + (with-current-buffer (guix-get-repl-buffer 'internal) + (let* ((wrapped (if wrap (geiser-debug--wrap-region str) str)) + (code `(:eval (:scm ,wrapped))) + (ret (geiser-eval--send/wait code))) + (if (geiser-eval--retort-error ret) + (error "Error in evaluating guile expression: %s" + (geiser-eval--retort-output ret)) + (cdr (assq 'result ret)))))) + +(defun guix-eval-read (str &optional wrap) + "Evaluate guile expression STR. +For the meaning of WRAP, see `guix-eval'. +Return elisp expression of the first result value of evaluation." + ;; Parsing scheme code with elisp `read' is probably not the best idea. + (read (replace-regexp-in-string + "#f\\|#<unspecified>" "nil" + (replace-regexp-in-string + "#t" "t" (car (guix-eval str wrap)))))) + +(defun guix-eval-in-repl (str) + "Switch to Guix REPL and evaluate STR with guile expression there." + (let ((repl (guix-get-repl-buffer))) + (with-current-buffer repl + (delete-region (geiser-repl--last-prompt-end) (point-max)) + (goto-char (point-max)) + (insert str) + (geiser-repl--send-input)) + (geiser-repl--switch-to-buffer repl))) + +(provide 'guix-backend) + +;;; guix-backend.el ends here diff --git a/emacs/guix-base.el b/emacs/guix-base.el new file mode 100644 index 0000000000..563df496cd --- /dev/null +++ b/emacs/guix-base.el @@ -0,0 +1,607 @@ +;;; guix-base.el --- Common definitions + +;; Copyright © 2014 Alex Kost <alezost@gmail.com> + +;; This file is part of GNU Guix. + +;; GNU Guix is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Guix is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file provides some base and common definitions for guix.el +;; package. + +;; List and info buffers have many common patterns that are defined +;; using `guix-define-buffer-type' macro from this file. + +;;; Code: + +(require 'cl-lib) +(require 'guix-backend) +(require 'guix-utils) + + +;;; Profiles + +(defvar guix-user-profile + (expand-file-name "~/.guix-profile") + "User profile.") + +(defvar guix-default-profile + (concat (or (getenv "NIX_STATE_DIR") "/var/guix") + "/profiles/per-user/" + (getenv "USER") + "/guix-profile") + "Default Guix profile.") + +(defvar guix-current-profile guix-default-profile + "Current profile.") + +(defun guix-set-current-profile (path) + "Set `guix-current-profile' to PATH. +Interactively, prompt for PATH. With prefix, use +`guix-default-profile'." + (interactive + (list (if current-prefix-arg + guix-default-profile + (read-file-name "Set profile: " + (file-name-directory guix-current-profile))))) + (let ((path (directory-file-name (expand-file-name path)))) + (setq guix-current-profile + (if (string= path guix-user-profile) + guix-default-profile + path)) + (message "Current profile has been set to '%s'." + guix-current-profile))) + + +;;; Parameters of the entries + +(defvar guix-param-titles + '((package + (id . "ID") + (name . "Name") + (version . "Version") + (license . "License") + (synopsis . "Synopsis") + (description . "Description") + (home-url . "Home page") + (outputs . "Outputs") + (inputs . "Inputs") + (native-inputs . "Native inputs") + (propagated-inputs . "Propagated inputs") + (location . "Location") + (installed . "Installed")) + (installed + (path . "Installed path") + (dependencies . "Dependencies") + (output . "Output")) + (generation + (id . "ID") + (number . "Number") + (prev-number . "Previous number") + (path . "Path") + (time . "Time"))) + "List for defining titles of entry parameters. +Titles are used for displaying information about entries. +Each element of the list has a form: + + (ENTRY-TYPE . ((PARAM . TITLE) ...))") + +(defun guix-get-param-title (entry-type param) + "Return title of an ENTRY-TYPE entry parameter PARAM." + (or (guix-get-key-val guix-param-titles + entry-type param) + (prog1 (symbol-name param) + (message "Couldn't find title for '%S %S'." + entry-type param)))) + +(defun guix-get-name-spec (name version &optional output) + "Return Guix package specification by its NAME, VERSION and OUTPUT." + (concat name "-" version + (when output (concat ":" output)))) + +(defun guix-get-full-name (entry &optional output) + "Return name specification of the package ENTRY and OUTPUT." + (guix-get-name-spec (guix-get-key-val entry 'name) + (guix-get-key-val entry 'version) + output)) + +(defun guix-get-installed-outputs (entry) + "Return list of installed outputs for the package ENTRY." + (mapcar (lambda (installed-entry) + (guix-get-key-val installed-entry 'output)) + (guix-get-key-val entry 'installed))) + +(defun guix-get-entry-by-id (id entries) + "Return entry from ENTRIES by entry ID." + (cl-find-if (lambda (entry) + (equal id (guix-get-key-val entry 'id))) + entries)) + + +;;; Location of the packages + +(defvar guix-directory nil + "Default Guix directory. +If it is not set by a user, it is set after starting Guile REPL. +This directory is used to define location of the packages.") + +(defun guix-set-directory () + "Set `guix-directory' if needed." + (or guix-directory + (setq guix-directory + (guix-eval-read "%guix-dir")))) + +(add-hook 'guix-after-start-repl-hook 'guix-set-directory) + +(defun guix-find-location (location) + "Go to LOCATION of a package. +LOCATION is a string of the form: + + \"PATH:LINE:COLUMN\" + +If PATH is relative, it is considered to be relative to +`guix-directory'." + (cl-multiple-value-bind (path line col) + (split-string location ":") + (let ((file (expand-file-name path guix-directory)) + (line (string-to-number line)) + (col (string-to-number col))) + (find-file file) + (goto-char (point-min)) + (forward-line (- line 1)) + (move-to-column col) + (recenter 1)))) + + +;;; Common definitions for buffer types + +(defvar-local guix-entries nil + "List of the currently displayed entries. +Each element of the list is alist with entry info of the +following form: + + ((PARAM . VAL) ...) + +PARAM is a name of the entry parameter. +VAL is a value of this parameter.") +(put 'guix-entries 'permanent-local t) + +(defvar-local guix-search-type nil + "Type of the current search.") +(put 'guix-search-type 'permanent-local t) + +(defvar-local guix-search-vals nil + "Values of the current search.") +(put 'guix-search-vals 'permanent-local t) + +(defsubst guix-set-vars (entries search-type search-vals) + (setq guix-entries entries + guix-search-type search-type + guix-search-vals search-vals)) + +(defmacro guix-define-buffer-type (buf-type entry-type &rest args) + "Define common stuff for BUF-TYPE buffers for displaying entries. + +ENTRY-TYPE is a type of displayed entries (see +`guix-get-entries'). + +In the text below TYPE means ENTRY-TYPE-BUF-TYPE. + +This macro defines `guix-TYPE-mode', a custom group, several user +variables and the following functions: + + - `guix-TYPE-get-params-for-receiving' + - `guix-TYPE-revert' + - `guix-TYPE-redisplay' + - `guix-TYPE-make-history-item' + - `guix-TYPE-set' + - `guix-TYPE-show' + - `guix-TYPE-get-show' + +The following stuff should be defined outside this macro: + + - `guix-BUF-TYPE-mode' - parent mode for the defined mode. + + - `guix-BUF-TYPE-insert-entries' - function for inserting + entries in the current buffer; it is called with 2 arguments: + entries of the form of `guix-entries' and ENTRY-TYPE. + + - `guix-BUF-TYPE-get-displayed-params' - function returning a + list of parameters displayed in the current buffer; it is + called with ENTRY-TYPE as argument. + + - `guix-TYPE-mode-initialize' (optional) - function for + additional mode settings; it is called without arguments. + +Remaining argument (ARGS) should have a form [KEYWORD VALUE] ... The +following keywords are available: + + - `:required' - default value for the defined + `guix-TYPE-required-params' variable. + + - `:history-size' - default value for the defined + `guix-TYPE-history-size' variable. + + - `:revert' - default value for the defined + `guix-TYPE-revert-no-confirm' variable." + (let* ((entry-type-str (symbol-name entry-type)) + (buf-type-str (symbol-name buf-type)) + (Entry-type-str (capitalize entry-type-str)) + (Buf-type-str (capitalize buf-type-str)) + (entry-str (concat entry-type-str " entries")) + (buf-str (concat buf-type-str " buffer")) + (prefix (concat "guix-" entry-type-str "-" buf-type-str)) + (group (intern prefix)) + (mode-map-str (concat prefix "-mode-map")) + (mode-map (intern mode-map-str)) + (parent-mode (intern (concat "guix-" buf-type-str "-mode"))) + (mode (intern (concat prefix "-mode"))) + (mode-init-fun (intern (concat prefix "-mode-initialize"))) + (buf-name-var (intern (concat prefix "-buffer-name"))) + (revert-var (intern (concat prefix "-revert-no-confirm"))) + (revert-fun (intern (concat prefix "-revert"))) + (redisplay-fun (intern (concat prefix "-redisplay"))) + (history-var (intern (concat prefix "-history-size"))) + (history-fun (intern (concat prefix "-make-history-item"))) + (params-var (intern (concat prefix "-required-params"))) + (params-fun (intern (concat prefix "-get-params-for-receiving"))) + (set-fun (intern (concat prefix "-set"))) + (show-fun (intern (concat prefix "-show"))) + (get-show-fun (intern (concat prefix "-get-show"))) + (revert-val nil) + (history-val 20) + (params-val '(id))) + + ;; Process the keyword args. + (while (keywordp (car args)) + (pcase (pop args) + (`:required (setq params-val (pop args))) + (`:history-size (setq history-val (pop args))) + (`:revert (setq revert-val (pop args))) + (_ (pop args)))) + + `(progn + (defgroup ,group nil + ,(concat Buf-type-str " buffer with " entry-str ".") + :prefix ,(concat prefix "-") + :group ',(intern (concat "guix-" buf-type-str))) + + (defcustom ,buf-name-var ,(format "*Guix %s %s*" + Entry-type-str Buf-type-str) + ,(concat "Default name of the " buf-str " for displaying " entry-str ".") + :type 'string + :group ',group) + + (defcustom ,history-var ,history-val + ,(concat "Maximum number of items saved in the history of the " buf-str ".\n" + "If 0, the history is disabled.") + :type 'integer + :group ',group) + + (defcustom ,revert-var ,revert-val + ,(concat "If non-nil, do not ask to confirm for reverting the " buf-str ".") + :type 'boolean + :group ',group) + + (defvar ,params-var ',params-val + ,(concat "List of required " entry-type-str " parameters.\n\n" + "Displayed parameters and parameters from this list are received\n" + "for each " entry-type-str ".\n\n" + "May be a special value `all', in which case all supported\n" + "parameters are received (this may be very slow for a big number\n" + "of entries).\n\n" + "Do not remove `id' from this list as it is required for\n" + "identifying an entry.")) + + (define-derived-mode ,mode ,parent-mode ,(concat "Guix-" Buf-type-str) + ,(concat "Major mode for displaying information about " entry-str ".\n\n" + "\\{" mode-map-str "}") + (setq-local revert-buffer-function ',revert-fun) + (setq-local guix-history-size ,history-var) + (and (fboundp ',mode-init-fun) (,mode-init-fun))) + + (let ((map ,mode-map)) + (define-key map (kbd "l") 'guix-history-back) + (define-key map (kbd "r") 'guix-history-forward) + (define-key map (kbd "g") 'revert-buffer) + (define-key map (kbd "R") ',redisplay-fun) + (define-key map (kbd "C-c C-z") 'guix-switch-to-repl)) + + (defun ,params-fun () + ,(concat "Return " entry-type-str " parameters that should be received.") + (unless (equal ,params-var 'all) + (cl-union ,params-var + (,(intern (concat "guix-" buf-type-str "-get-displayed-params")) + ',entry-type)))) + + (defun ,revert-fun (_ignore-auto noconfirm) + "Update information in the current buffer. +The function is suitable for `revert-buffer-function'. +See `revert-buffer' for the meaning of NOCONFIRM." + (when (or ,revert-var + noconfirm + (y-or-n-p "Update current information? ")) + (let ((entries (guix-get-entries ',entry-type guix-search-type + guix-search-vals (,params-fun)))) + (,set-fun entries guix-search-type guix-search-vals t)))) + + (defun ,redisplay-fun () + "Redisplay current information. +This function will not update the information, use +\"\\[revert-buffer]\" if you want the full update." + (interactive) + (,show-fun guix-entries) + (guix-result-message guix-entries ',entry-type + guix-search-type guix-search-vals)) + + (defun ,history-fun () + "Make and return a history item for the current buffer." + (list (lambda (entries search-type search-vals) + (,show-fun entries) + (guix-set-vars entries search-type search-vals) + (guix-result-message entries ',entry-type + search-type search-vals)) + guix-entries guix-search-type guix-search-vals)) + + (defun ,set-fun (entries search-type search-vals &optional history-replace) + ,(concat "Set up the " buf-str " for displaying " entry-str ".\n\n" + "Display ENTRIES, set variables and make history item.\n\n" + "ENTRIES should have a form of `guix-entries'.\n\n" + "See `guix-get-entries' for the meaning of SEARCH-TYPE and\n" + "SEARCH-VALS.\n\n" + "If HISTORY-REPLACE is non-nil, replace current history item,\n" + "otherwise add the new one.") + (when entries + (let ((buf (if (eq major-mode ',mode) + (current-buffer) + (get-buffer-create ,buf-name-var)))) + (with-current-buffer buf + (,show-fun entries) + (guix-set-vars entries search-type search-vals) + (funcall (if history-replace + #'guix-history-replace + #'guix-history-add) + (,history-fun))) + (pop-to-buffer buf + '((display-buffer-reuse-window + display-buffer-same-window))))) + (guix-result-message entries ',entry-type + search-type search-vals)) + + (defun ,show-fun (entries) + ,(concat "Display " entry-type-str " ENTRIES in the current " buf-str ".") + (let ((inhibit-read-only t)) + (erase-buffer) + (,mode) + (,(intern (concat "guix-" buf-type-str "-insert-entries")) + entries ',entry-type) + (goto-char (point-min)))) + + (defun ,get-show-fun (search-type &rest search-vals) + ,(concat "Search for " entry-str " and show results in the " buf-str ".\n" + "See `guix-get-entries' for the meaning of SEARCH-TYPE and\n" + "SEARCH-VALS.") + (let ((entries (guix-get-entries ',entry-type search-type + search-vals (,params-fun)))) + (,set-fun entries search-type search-vals)))))) + +(put 'guix-define-buffer-type 'lisp-indent-function 'defun) + + +;;; Messages + +(defvar guix-messages + '((package + (id + (0 "Packages not found.") + (1 "") + (many "%d packages." count)) + (name + (0 "The package '%s' not found." val) + (1 "A single package with name '%s'." val) + (many "%d packages with '%s' name." count val)) + (regexp + (0 "No packages matching '%s'." val) + (1 "A single package matching '%s'." val) + (many "%d packages matching '%s'." count val)) + (all-available + (0 "No packages are available for some reason.") + (1 "A single available package (that's strange).") + (many "%d available packages." count)) + (newest-available + (0 "No packages are available for some reason.") + (1 "A single newest available package (that's strange).") + (many "%d newest available packages." count)) + (installed + (0 "No installed packages.") + (1 "A single installed package.") + (many "%d installed packages." count)) + (obsolete + (0 "No obsolete packages.") + (1 "A single obsolete package.") + (many "%d obsolete packages." count)) + (generation + (0 "No packages installed in generation %d." val) + (1 "A single package installed in generation %d." val) + (many "%d packages installed in generation %d." count val))) + (generation + (id + (0 "Generations not found.") + (1 "") + (many "%d generations." count)) + (last + (0 "No available generations.") + (1 "The last generation.") + (many "%d last generations." count)) + (all + (0 "No available generations.") + (1 "A single available generation.") + (many "%d available generations." count))))) + +(defun guix-result-message (entries entry-type search-type search-vals) + "Display an appropriate message after displaying ENTRIES." + (let* ((val (car search-vals)) + (count (length entries)) + (count-key (if (> count 1) 'many count)) + (msg-spec (guix-get-key-val guix-messages + entry-type search-type count-key)) + (format (car msg-spec)) + (args (cdr msg-spec))) + (mapc (lambda (subst) + (setq args (cl-substitute (car subst) (cdr subst) args))) + (list (cons count 'count) + (cons val 'val))) + (apply #'message format args))) + + +;;; Getting info about packages and generations + +(defun guix-get-entries (entry-type search-type search-vals &optional params) + "Search for entries of ENTRY-TYPE. + +Call an appropriate scheme function and return a list of the +form of `guix-entries'. + +ENTRY-TYPE should be one of the following symbols: `package' or +`generation'. + +SEARCH-TYPE may be one of the following symbols: + +- If ENTRY-TYPE is `package': `id', `name', `regexp', + `all-available', `newest-available', `installed', `obsolete', + `generation'. + +- If ENTRY-TYPE is `generation': `id', `last', `all'. + +PARAMS is a list of parameters for receiving. If nil, get +information with all available parameters." + (guix-eval-read (guix-make-guile-expression + 'get-entries + guix-current-profile params + entry-type search-type search-vals))) + + +;;; Actions on packages and generations + +(defcustom guix-operation-confirm t + "If nil, do not prompt to confirm an operation." + :type 'boolean + :group 'guix) + +(defcustom guix-use-substitutes t + "If non-nil, use substitutes for the Guix packages." + :type 'boolean + :group 'guix) + +(defvar guix-dry-run nil + "If non-nil, do not perform the real actions, just simulate.") + +(defvar guix-temp-buffer-name " *Guix temp*" + "Name of a buffer used for displaying info before executing operation.") + +(defun guix-process-package-actions (&rest actions) + "Process package ACTIONS. +Each action is a list of the form: + + (ACTION-TYPE PACKAGE-SPEC ...) + +ACTION-TYPE is one of the following symbols: `install', +`upgrade', `remove'/`delete'. +PACKAGE-SPEC should have the following form: (ID [OUTPUT] ...)." + (let (install upgrade remove) + (mapc (lambda (action) + (let ((action-type (car action)) + (specs (cdr action))) + (cl-case action-type + (install (setq install (append install specs))) + (upgrade (setq upgrade (append upgrade specs))) + ((remove delete) (setq remove (append remove specs)))))) + actions) + (when (guix-continue-package-operation-p + :install install :upgrade upgrade :remove remove) + (guix-eval-in-repl + (guix-make-guile-expression + 'process-package-actions guix-current-profile + :install install :upgrade upgrade :remove remove + :use-substitutes? (or guix-use-substitutes 'f) + :dry-run? (or guix-dry-run 'f)))))) + +(cl-defun guix-continue-package-operation-p (&key install upgrade remove) + "Return non-nil if a package operation should be continued. +Ask a user if needed (see `guix-operation-confirm'). +INSTALL, UPGRADE, REMOVE are 'package action specifications'. +See `guix-process-package-actions' for details." + (or (null guix-operation-confirm) + (let* ((entries (guix-get-entries + 'package 'id + (list (append (mapcar #'car install) + (mapcar #'car upgrade) + (mapcar #'car remove))) + '(id name version location))) + (install-strings (guix-get-package-strings install entries)) + (upgrade-strings (guix-get-package-strings upgrade entries)) + (remove-strings (guix-get-package-strings remove entries))) + (if (or install-strings upgrade-strings remove-strings) + (let ((buf (get-buffer-create guix-temp-buffer-name))) + (with-current-buffer buf + (setq-local cursor-type nil) + (setq buffer-read-only nil) + (erase-buffer) + (guix-insert-package-strings install-strings "install") + (guix-insert-package-strings upgrade-strings "upgrade") + (guix-insert-package-strings remove-strings "remove") + (let ((win (temp-buffer-window-show + buf + '((display-buffer-reuse-window + display-buffer-at-bottom) + (window-height . fit-window-to-buffer))))) + (prog1 (y-or-n-p "Continue operation? ") + (quit-window nil win))))) + (message "Nothing to be done. If the REPL was restarted, information is not up-to-date.") + nil)))) + +(defun guix-get-package-strings (specs entries) + "Return short package descriptions for performing package actions. +See `guix-process-package-actions' for the meaning of SPECS. +ENTRIES is a list of package entries to get info about packages." + (delq nil + (mapcar + (lambda (spec) + (let* ((id (car spec)) + (outputs (cdr spec)) + (entry (guix-get-entry-by-id id entries))) + (when entry + (let ((location (guix-get-key-val entry 'location))) + (concat (guix-get-full-name entry) + (when outputs + (concat ":" + (mapconcat #'identity outputs ","))) + (when location + (concat "\t(" location ")"))))))) + specs))) + +(defun guix-insert-package-strings (strings action) + "Insert information STRINGS at point for performing package ACTION." + (when strings + (insert "Package(s) to " (guix-get-string action 'bold) ":\n") + (mapc (lambda (str) + (insert " " str "\n")) + strings) + (insert "\n"))) + +(provide 'guix-base) + +;;; guix-base.el ends here diff --git a/emacs/guix-helper.scm.in b/emacs/guix-helper.scm.in new file mode 100644 index 0000000000..554d55119f --- /dev/null +++ b/emacs/guix-helper.scm.in @@ -0,0 +1,64 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 Alex Kost <alezost@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This is an auxiliary file for the Emacs UI. It is used to add Guix +;; directories to path variables and to load the main code. + +;;; Code: + +(use-modules (ice-9 regex) + (srfi srfi-26)) + +(define %guix-dir) + +;; The code is taken from ‘guix’ executable script +(define (set-paths!) + (define-syntax-rule (push! elt v) (set! v (cons elt v))) + + (define config-lookup + (let ((config '(("prefix" . "@prefix@") + ("guilemoduledir" . "@guilemoduledir@"))) + (var-ref-regexp (make-regexp "\\$\\{([a-z]+)\\}"))) + (define (expand-var-ref match) + (lookup (match:substring match 1))) + (define (expand str) + (regexp-substitute/global #f var-ref-regexp str + 'pre expand-var-ref 'post)) + (define (lookup name) + (expand (assoc-ref config name))) + lookup)) + + (let ((module-dir (config-lookup "guilemoduledir")) + (updates-dir (and=> (or (getenv "XDG_CONFIG_HOME") + (and=> (getenv "HOME") + (cut string-append <> "/.config"))) + (cut string-append <> "/guix/latest")))) + (push! module-dir %load-compiled-path) + (if (and updates-dir (file-exists? updates-dir)) + (begin + (set! %guix-dir updates-dir) + (push! updates-dir %load-path) + (push! updates-dir %load-compiled-path)) + (set! %guix-dir module-dir)))) + +(set-paths!) + +(load-from-path "guix-main") + diff --git a/emacs/guix-history.el b/emacs/guix-history.el new file mode 100644 index 0000000000..5d301a689e --- /dev/null +++ b/emacs/guix-history.el @@ -0,0 +1,92 @@ +;;; guix-history.el --- History of buffer information + +;; Copyright © 2014 Alex Kost <alezost@gmail.com> + +;; This file is part of GNU Guix. + +;; GNU Guix is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Guix is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file provides support for history of buffers similar to the +;; history of a `help-mode' buffer. + +;;; Code: + +(require 'cl-macs) + +(defvar-local guix-history-stack-item nil + "Current item of the history. +A list of the form (FUNCTION [ARGS ...]). +The item is used by calling (apply FUNCTION ARGS).") +(put 'guix-history-stack-item 'permanent-local t) + +(defvar-local guix-history-back-stack nil + "Stack (list) of visited items. +Each element of the list has a form of `guix-history-stack-item'.") +(put 'guix-history-back-stack 'permanent-local t) + +(defvar-local guix-history-forward-stack nil + "Stack (list) of items visited with `guix-history-back'. +Each element of the list has a form of `guix-history-stack-item'.") +(put 'guix-history-forward-stack 'permanent-local t) + +(defvar guix-history-size 0 + "Maximum number of items saved in history. +If 0, the history is disabled.") + +(defun guix-history-add (item) + "Add ITEM to history." + (and guix-history-stack-item + (push guix-history-stack-item guix-history-back-stack)) + (setq guix-history-forward-stack nil + guix-history-stack-item item) + (when (>= (length guix-history-back-stack) + guix-history-size) + (setq guix-history-back-stack + (cl-loop for elt in guix-history-back-stack + for i from 1 to guix-history-size + collect elt)))) + +(defun guix-history-replace (item) + "Replace current item in history with ITEM." + (setq guix-history-stack-item item)) + +(defun guix-history-goto (item) + "Go to the ITEM of history. +ITEM should have the form of `guix-history-stack-item'." + (or (listp item) + (error "Wrong value of history element")) + (setq guix-history-stack-item item) + (apply (car item) (cdr item))) + +(defun guix-history-back () + "Go back to the previous element of history in the current buffer." + (interactive) + (or guix-history-back-stack + (user-error "No previous element in history")) + (push guix-history-stack-item guix-history-forward-stack) + (guix-history-goto (pop guix-history-back-stack))) + +(defun guix-history-forward () + "Go forward to the next element of history in the current buffer." + (interactive) + (or guix-history-forward-stack + (user-error "No next element in history")) + (push guix-history-stack-item guix-history-back-stack) + (guix-history-goto (pop guix-history-forward-stack))) + +(provide 'guix-history) + +;;; guix-history.el ends here diff --git a/emacs/guix-info.el b/emacs/guix-info.el new file mode 100644 index 0000000000..687a15eefa --- /dev/null +++ b/emacs/guix-info.el @@ -0,0 +1,556 @@ +;;; guix-info.el --- Info buffers for displaying entries + +;; Copyright © 2014 Alex Kost <alezost@gmail.com> + +;; This file is part of GNU Guix. + +;; GNU Guix is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Guix is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file provides a help-like buffer for displaying information +;; about Guix packages and generations. + +;;; Code: + +(require 'guix-history) +(require 'guix-base) +(require 'guix-utils) + +(defgroup guix-info nil + "General settings for info buffers." + :prefix "guix-info-" + :group 'guix) + +(defface guix-info-param-title + '((t :inherit font-lock-type-face)) + "Face used for titles of parameters." + :group 'guix-info) + +(defface guix-info-file-path + '((t :inherit link)) + "Face used for file paths." + :group 'guix-info) + +(defface guix-info-url + '((t :inherit link)) + "Face used for URLs." + :group 'guix-info) + +(defface guix-info-time + '((t :inherit font-lock-constant-face)) + "Face used for timestamps." + :group 'guix-info) + +(defface guix-info-action-button + '((((type x w32 ns) (class color)) + :box (:line-width 2 :style released-button) + :background "lightgrey" :foreground "black") + (t :inherit button)) + "Face used for action buttons." + :group 'guix-info) + +(defface guix-info-action-button-mouse + '((((type x w32 ns) (class color)) + :box (:line-width 2 :style released-button) + :background "grey90" :foreground "black") + (t :inherit highlight)) + "Mouse face used for action buttons." + :group 'guix-info) + +(defcustom guix-info-ignore-empty-vals nil + "If non-nil, do not display parameters with nil values." + :type 'boolean + :group 'guix-info) + +(defvar guix-info-param-title-format "%-18s: " + "String used to format a title of a parameter. +It should be a '%s'-sequence. After inserting a title formatted +with this string, a value of the parameter is inserted. +This string is used by `guix-info-insert-title-default'.") + +(defvar guix-info-multiline-prefix (make-string 20 ?\s) + "String used to format multi-line parameter values. +If a value occupies more than one line, this string is inserted +in the beginning of each line after the first one. +This string is used by `guix-info-insert-val-default'.") + +(defvar guix-info-indent 2 + "Number of spaces used to indent various parts of inserted text.") + +(defvar guix-info-fill-column 60 + "Column used for filling (word wrapping) parameters with long lines. +If a value is not multi-line and it occupies more than this +number of characters, it will be split into several lines.") + +(defvar guix-info-delimiter "\n\f\n" + "String used to separate entries.") + +(defvar guix-info-insert-methods + '((package + (name guix-package-info-name) + (version guix-package-info-version) + (license guix-package-info-license) + (synopsis guix-package-info-synopsis) + (description guix-package-info-insert-description + guix-info-insert-title-simple) + (outputs guix-package-info-insert-outputs + guix-info-insert-title-simple) + (home-url guix-info-insert-url) + (inputs guix-package-info-insert-inputs) + (native-inputs guix-package-info-insert-native-inputs) + (propagated-inputs guix-package-info-insert-propagated-inputs) + (location guix-package-info-insert-location)) + (installed + (path guix-package-info-insert-output-path + guix-info-insert-title-simple) + (dependencies guix-package-info-insert-output-dependencies + guix-info-insert-title-simple)) + (generation + (number guix-generation-info-insert-number) + (path guix-info-insert-file-path) + (time guix-info-insert-time))) + "Methods for inserting parameter values. +Each element of the list should have a form: + + (ENTRY-TYPE . ((PARAM INSERT-VALUE [INSERT-TITLE]) ...)) + +INSERT-VALUE may be either nil, a face name or a function. If it +is nil or a face, `guix-info-insert-val-default' function is +called with parameter value and INSERT-VALUE as arguments. If it +is a function, this function is called with parameter value and +entry info (alist of parameters and their values) as arguments. + +INSERT-TITLE may be either nil, a face name or a function. If it +is nil or a face, `guix-info-insert-title-default' function is +called with parameter title and INSERT-TITLE as arguments. If it +is a function, this function is called with parameter title as +argument.") + +(defvar guix-info-displayed-params + '((package name version synopsis outputs location home-url + license inputs native-inputs propagated-inputs description) + (installed path dependencies) + (generation number prev-number time path)) + "List of displayed entry parameters. +Each element of the list should have a form: + + (ENTRY-TYPE . (PARAM ...)) + +The order of displayed parameters is the same as in this list.") + +(defun guix-info-get-insert-methods (entry-type param) + "Return list of insert methods for parameter PARAM of ENTRY-TYPE. +See `guix-info-insert-methods' for details." + (guix-get-key-val guix-info-insert-methods + entry-type param)) + +(defun guix-info-get-displayed-params (entry-type) + "Return parameters of ENTRY-TYPE that should be displayed." + (guix-get-key-val guix-info-displayed-params + entry-type)) + +(defun guix-info-get-indent (&optional level) + "Return `guix-info-indent' \"multiplied\" by LEVEL spaces. +LEVEL is 1 by default." + (make-string (* guix-info-indent (or level 1)) ?\s)) + +(defun guix-info-insert-indent (&optional level) + "Insert `guix-info-indent' spaces LEVEL times (1 by default)." + (insert (guix-info-get-indent level))) + +(defun guix-info-insert-entries (entries entry-type) + "Display ENTRIES of ENTRY-TYPE in the current info buffer. +ENTRIES should have a form of `guix-entries'." + (guix-mapinsert (lambda (entry) + (guix-info-insert-entry entry entry-type)) + entries + guix-info-delimiter)) + +(defun guix-info-insert-entry (entry entry-type &optional indent-level) + "Insert ENTRY of ENTRY-TYPE into the current info buffer. +If INDENT-LEVEL is non-nil, indent displayed information by this +number of `guix-info-indent' spaces." + (let ((region-beg (point))) + (mapc (lambda (param) + (guix-info-insert-param param entry entry-type)) + (guix-info-get-displayed-params entry-type)) + (when indent-level + (indent-rigidly region-beg (point) + (* indent-level guix-info-indent))))) + +(defun guix-info-insert-param (param entry entry-type) + "Insert title and value of a PARAM at point. +ENTRY is alist with parameters and their values. +ENTRY-TYPE is a type of ENTRY." + (let ((val (guix-get-key-val entry param))) + (unless (and guix-info-ignore-empty-vals (null val)) + (let* ((title (guix-get-param-title entry-type param)) + (insert-methods (guix-info-get-insert-methods entry-type param)) + (val-method (car insert-methods)) + (title-method (cadr insert-methods))) + (guix-info-method-funcall title title-method + #'guix-info-insert-title-default) + (guix-info-method-funcall val val-method + #'guix-info-insert-val-default + entry) + (insert "\n"))))) + +(defun guix-info-method-funcall (val method default-fun &rest args) + "Call METHOD or DEFAULT-FUN. + +If METHOD is a function and VAL is non-nil, call this +function by applying it to VAL and ARGS. + +If METHOD is a face, propertize inserted VAL with this face." + (cond ((or (null method) + (facep method)) + (funcall default-fun val method)) + ((functionp method) + (apply method val args)) + (t (error "Unknown method '%S'" method)))) + +(defun guix-info-insert-title-default (title &optional face format) + "Insert TITLE formatted with `guix-info-param-title-format' at point." + (guix-format-insert title + (or face 'guix-info-param-title) + (or format guix-info-param-title-format))) + +(defun guix-info-insert-title-simple (title &optional face) + "Insert TITLE at point." + (guix-info-insert-title-default title face "%s:")) + +(defun guix-info-insert-val-default (val &optional face) + "Format and insert parameter value VAL at point. + +This function is intended to be called after +`guix-info-insert-title-default'. + +If VAL is a one-line string longer than `guix-info-fill-column', +split it into several short lines. See also +`guix-info-multiline-prefix'. + +If FACE is non-nil, propertize inserted line(s) with this FACE." + (guix-split-insert val face + guix-info-fill-column + (concat "\n" guix-info-multiline-prefix))) + +(defun guix-info-insert-val-simple (val &optional face-or-fun) + "Format and insert parameter value VAL at point. + +This function is intended to be called after +`guix-info-insert-title-simple'. + +If VAL is a one-line string longer than `guix-info-fill-column', +split it into several short lines and indent each line with +`guix-info-indent' spaces. + +If FACE-OR-FUN is a face, propertize inserted line(s) with this FACE. + +If FACE-OR-FUN is a function, call it with VAL as argument. If +VAL is a list, call the function on each element of this list." + (if (null val) + (progn (guix-info-insert-indent) + (guix-format-insert nil)) + (let ((prefix (concat "\n" (guix-info-get-indent)))) + (insert prefix) + (if (functionp face-or-fun) + (guix-mapinsert face-or-fun + (if (listp val) val (list val)) + prefix) + (guix-split-insert val face-or-fun + guix-info-fill-column prefix))))) + +(defun guix-info-insert-action-button (label action &optional message + &rest properties) + "Make action button with LABEL and insert it at point. +For the meaning of ACTION, MESSAGE and PROPERTIES, see +`guix-insert-button'." + (apply #'guix-insert-button + label 'guix-info-action-button action message + 'mouse-face 'guix-info-action-button-mouse + properties)) + +(defun guix-info-insert-file-path (path &optional _) + "Make button from file PATH and insert it at point." + (guix-insert-button + path 'guix-info-file-path + (lambda (btn) (find-file (button-label btn))) + "Find file")) + +(defun guix-info-insert-url (url &optional _) + "Make button from URL and insert it at point." + (guix-insert-button + url 'guix-info-url + (lambda (btn) (browse-url (button-label btn))) + "Browse URL")) + +(defun guix-info-insert-time (seconds &optional _) + "Insert formatted time string using SECONDS at point." + (guix-info-insert-val-default (guix-get-time-string seconds) + 'guix-info-time)) + + +(defvar guix-info-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent + map (make-composed-keymap button-buffer-map + special-mode-map)) + map) + "Parent keymap for info buffers.") + +(define-derived-mode guix-info-mode special-mode "Guix-Info" + "Parent mode for displaying information in info buffers.") + + +;;; Displaying packages + +(guix-define-buffer-type info package + :required (id installed non-unique)) + +(defface guix-package-info-name + '((t :inherit font-lock-keyword-face)) + "Face used for a name of a package." + :group 'guix-package-info) + +(defface guix-package-info-version + '((t :inherit font-lock-builtin-face)) + "Face used for a version of a package." + :group 'guix-package-info) + +(defface guix-package-info-synopsis + '((t :inherit font-lock-doc-face)) + "Face used for a synopsis of a package." + :group 'guix-package-info) + +(defface guix-package-info-description + '((t)) + "Face used for a description of a package." + :group 'guix-package-info) + +(defface guix-package-info-license + '((t :inherit font-lock-string-face)) + "Face used for a license of a package." + :group 'guix-package-info) + +(defface guix-package-info-location + '((t :inherit link)) + "Face used for a location of a package." + :group 'guix-package-info) + +(defface guix-package-info-installed-outputs + '((default :weight bold) + (((class color) (min-colors 88) (background light)) + :foreground "ForestGreen") + (((class color) (min-colors 88) (background dark)) + :foreground "PaleGreen") + (((class color) (min-colors 8)) + :foreground "green") + (t :underline t)) + "Face used for installed outputs of a package." + :group 'guix-package-info) + +(defface guix-package-info-uninstalled-outputs + '((t :weight bold)) + "Face used for uninstalled outputs of a package." + :group 'guix-package-info) + +(defface guix-package-info-obsolete + '((t :inherit error)) + "Face used if a package is obsolete." + :group 'guix-package-info) + +(defun guix-package-info-insert-description (desc &optional _) + "Insert description DESC at point." + (guix-info-insert-val-simple desc 'guix-package-info-description)) + +(defun guix-package-info-insert-location (location &optional _) + "Make button from file LOCATION and insert it at point." + (guix-insert-button + location 'guix-package-info-location + (lambda (btn) (guix-find-location (button-label btn))) + "Find location of this package")) + +(defmacro guix-package-info-define-insert-inputs (&optional type) + "Define a face and a function for inserting package inputs. +TYPE is a type of inputs. +Function name is `guix-package-info-insert-TYPE-inputs'. +Face name is `guix-package-info-TYPE-inputs'." + (let* ((type-str (symbol-name type)) + (type-name (and type (concat type-str "-"))) + (type-desc (and type (concat type-str " "))) + (face (intern (concat "guix-package-info-" type-name "inputs"))) + (fun (intern (concat "guix-package-info-insert-" type-name "inputs")))) + `(progn + (defface ,face + '((t :inherit button)) + ,(concat "Face used for " type-desc "inputs of a package.") + :group 'guix-package-info) + + (defun ,fun (inputs &optional _) + ,(concat "Make buttons from " type-desc "INPUTS and insert them at point.") + (guix-package-info-insert-full-names inputs ',face))))) + +(guix-package-info-define-insert-inputs) +(guix-package-info-define-insert-inputs native) +(guix-package-info-define-insert-inputs propagated) + +(defun guix-package-info-insert-full-names (names face) + "Make buttons from package NAMES and insert them at point. +NAMES is a list of strings. +Propertize buttons with FACE." + (if names + (guix-info-insert-val-default + (with-temp-buffer + (guix-mapinsert (lambda (name) + (guix-package-info-insert-full-name + name face)) + names + guix-list-separator) + (buffer-substring (point-min) (point-max)))) + (guix-format-insert nil))) + +(defun guix-package-info-insert-full-name (name face) + "Make button and insert package NAME at point. +Propertize package button with FACE." + (guix-insert-button + name face + (lambda (btn) + (guix-package-info-get-show 'name (button-label btn))) + "Describe this package")) + + +;;; Inserting outputs and installed parameters + +(defvar guix-package-info-output-format "%-10s" + "String used to format output names of the packages. +It should be a '%s'-sequence. After inserting an output name +formatted with this string, an action button is inserted.") + +(defvar guix-package-info-obsolete-string "(This package is obsolete)" + "String used if a package is obsolete.") + +(defun guix-package-info-insert-outputs (outputs entry) + "Insert OUTPUTS from package ENTRY at point." + (and (guix-get-key-val entry 'obsolete) + (guix-package-info-insert-obsolete-text)) + (and (guix-get-key-val entry 'non-unique) + (guix-get-key-val entry 'installed) + (guix-package-info-insert-non-unique-text + (guix-get-full-name entry))) + (insert "\n") + (mapc (lambda (output) + (guix-package-info-insert-output output entry)) + outputs)) + +(defun guix-package-info-insert-obsolete-text () + "Insert a message about obsolete package at point." + (guix-info-insert-indent) + (guix-format-insert guix-package-info-obsolete-string + 'guix-package-info-obsolete)) + +(defun guix-package-info-insert-non-unique-text (full-name) + "Insert a message about non-unique package with FULL-NAME at point." + (insert "\n") + (guix-info-insert-indent) + (insert "Installed outputs are displayed for a non-unique ") + (guix-package-info-insert-full-name full-name + 'guix-package-info-inputs) + (insert " package.")) + +(defun guix-package-info-insert-output (output entry) + "Insert OUTPUT at point. +Make some fancy text with buttons and additional stuff if the +current OUTPUT is installed (if there is such output in +`installed' parameter of a package ENTRY)." + (let* ((installed (guix-get-key-val entry 'installed)) + (obsolete (guix-get-key-val entry 'obsolete)) + (installed-entry (cl-find-if + (lambda (entry) + (string= (guix-get-key-val entry 'output) + output)) + installed)) + (action-type (if installed-entry 'delete 'install))) + (guix-info-insert-indent) + (guix-format-insert output + (if installed-entry + 'guix-package-info-installed-outputs + 'guix-package-info-uninstalled-outputs) + guix-package-info-output-format) + (guix-package-info-insert-action-button action-type entry output) + (when obsolete + (guix-info-insert-indent) + (guix-package-info-insert-action-button 'upgrade entry output)) + (insert "\n") + (when installed-entry + (guix-info-insert-entry installed-entry 'installed 2)))) + +(defun guix-package-info-insert-action-button (type entry output) + "Insert button to process an action on a package OUTPUT at point. +TYPE is one of the following symbols: `install', `delete', `upgrade'. +ENTRY is an alist with package info." + (let ((type-str (capitalize (symbol-name type))) + (full-name (guix-get-full-name entry output))) + (guix-info-insert-action-button + type-str + (lambda (btn) + (guix-process-package-actions + (list (button-get btn 'action-type) + (list (button-get btn 'id) + (button-get btn 'output))))) + (concat type-str " '" full-name "'") + 'action-type type + 'id (guix-get-key-val entry 'id) + 'output output))) + +(defun guix-package-info-insert-output-path (path &optional _) + "Insert PATH of the installed output." + (guix-info-insert-val-simple path #'guix-info-insert-file-path)) + +(defun guix-package-info-insert-output-dependencies (deps &optional _) + "Insert dependencies DEPS of the installed output." + (guix-info-insert-val-simple deps #'guix-info-insert-file-path)) + + +;;; Displaying generations + +(guix-define-buffer-type info generation) + +(defface guix-generation-info-number + '((t :inherit font-lock-keyword-face)) + "Face used for a number of a generation." + :group 'guix-generation-info) + +(declare-function guix-package-list-get-show "guix-list" t t) + +(defun guix-generation-info-insert-number (number &optional _) + "Insert generation NUMBER and action buttons." + (guix-info-insert-val-default number 'guix-generation-info-number) + (guix-info-insert-indent) + (guix-info-insert-action-button + "Packages" + (lambda (btn) + (guix-package-list-get-show 'generation + (button-get btn 'number))) + "Show installed packages for this generation" + 'number number) + (guix-info-insert-indent) + (guix-info-insert-action-button + "Delete" + (lambda (btn) (error "Sorry, not implemented yet")) + "Delete this generation")) + +(provide 'guix-info) + +;;; guix-info.el ends here diff --git a/emacs/guix-init.el.in b/emacs/guix-init.el.in new file mode 100644 index 0000000000..4e62728187 --- /dev/null +++ b/emacs/guix-init.el.in @@ -0,0 +1,14 @@ +(require 'guix-autoloads) + +(defvar guix-load-path + (replace-regexp-in-string "${prefix}" "@prefix@" "@emacsuidir@") + "Directory with scheme files for \"guix.el\" package.") + +(defvar guix-default-profile + (concat (or (getenv "NIX_STATE_DIR") "@guix_localstatedir@/guix") + "/profiles/per-user/" + (getenv "USER") + "/guix-profile") + "Default Guix profile.") + +(provide 'guix-init) diff --git a/emacs/guix-list.el b/emacs/guix-list.el new file mode 100644 index 0000000000..08fb3cba5c --- /dev/null +++ b/emacs/guix-list.el @@ -0,0 +1,594 @@ +;;; guix-list.el --- List buffers for displaying entries -*- lexical-binding: t -*- + +;; Copyright © 2014 Alex Kost <alezost@gmail.com> + +;; This file is part of GNU Guix. + +;; GNU Guix is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Guix is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file provides a list-like buffer for displaying information +;; about Guix packages and generations. + +;;; Code: + +(require 'cl-lib) +(require 'tabulated-list) +(require 'guix-info) +(require 'guix-history) +(require 'guix-base) +(require 'guix-utils) + +(defgroup guix-list nil + "General settings for list buffers." + :prefix "guix-list-" + :group 'guix) + +(defface guix-list-file-path + '((t :inherit guix-info-file-path)) + "Face used for file paths." + :group 'guix-list) + +(defcustom guix-list-describe-warning-count 10 + "The maximum number of entries for describing without a warning. +If a user wants to describe more than this number of marked +entries, he will be prompted for confirmation." + :type 'integer + :group 'guix-list) + +(defvar guix-list-column-format + `((package + (name 20 t) + (version 10 nil) + (outputs 13 t) + (installed 13 t) + (synopsis 30 nil)) + (generation + (number 5 + ,(lambda (a b) (guix-list-sort-numerically 0 a b)) + :right-align t) + (time 20 t) + (path 30 t))) + "Columns displayed in list buffers. +Each element of the list has a form: + + (ENTRY-TYPE . ((PARAM WIDTH SORT . PROPS) ...)) + +PARAM is the name of an entry parameter of ENTRY-TYPE. For the +meaning of WIDTH, SORT and PROPS, see `tabulated-list-format'.") + +(defvar guix-list-column-titles + '((generation + (number . "N."))) + "Column titles for list buffers. +Has the same structure as `guix-param-titles', but titles from +this list have a priority.") + +(defvar guix-list-column-value-methods + '((package + (name . guix-package-list-get-name) + (synopsis . guix-list-get-one-line) + (description . guix-list-get-one-line) + (installed . guix-package-list-get-installed-outputs)) + (generation + (time . guix-list-get-time) + (path . guix-list-get-file-path))) + "Methods for inserting parameter values in columns. +Each element of the list has a form: + + (ENTRY-TYPE . ((PARAM . FUN) ...)) + +PARAM is the name of an entry parameter of ENTRY-TYPE. + +FUN is a function returning a value that will be inserted. The +function is called with 2 arguments: the first one is the value +of the parameter; the second argument is an entry info (alist of +parameters and their values).") + +(defun guix-list-get-param-title (entry-type param) + "Return title of an ENTRY-TYPE entry parameter PARAM." + (or (guix-get-key-val guix-list-column-titles + entry-type param) + (guix-get-param-title entry-type param))) + +(defun guix-list-get-column-format (entry-type) + "Return column format for ENTRY-TYPE." + (guix-get-key-val guix-list-column-format entry-type)) + +(defun guix-list-get-displayed-params (entry-type) + "Return list of parameters of ENTRY-TYPE that should be displayed." + (mapcar #'car + (guix-list-get-column-format entry-type))) + +(defun guix-list-get-sort-key (entry-type param &optional invert) + "Return suitable sort key for `tabulated-list-sort-key'. +Define column title by ENTRY-TYPE and PARAM. If INVERT is +non-nil, invert the sort." + (when (memq param (guix-list-get-displayed-params entry-type)) + (cons (guix-list-get-param-title entry-type param) invert))) + +(defun guix-list-sort-numerically (column a b) + "Compare COLUMN of tabulated entries A and B numerically. +It is a sort predicate for `tabulated-list-format'. +Return non-nil, if B is bigger than A." + (cl-flet ((num (entry) + (string-to-number (aref (cadr entry) column)))) + (> (num b) (num a)))) + +(defun guix-list-make-tabulated-vector (entry-type fun) + "Call FUN on each column specification for ENTRY-TYPE. + +FUN is called with 2 argument: parameter name and column +specification (see `guix-list-column-format'). + +Return a vector made of values of FUN calls." + (apply #'vector + (mapcar (lambda (col-spec) + (funcall fun (car col-spec) (cdr col-spec))) + (guix-list-get-column-format entry-type)))) + +(defun guix-list-get-list-format (entry-type) + "Return ENTRY-TYPE list specification for `tabulated-list-format'." + (guix-list-make-tabulated-vector + entry-type + (lambda (param spec) + (cons (guix-list-get-param-title entry-type param) + spec)))) + +(defun guix-list-insert-entries (entries entry-type) + "Display ENTRIES of ENTRY-TYPE in the current list buffer. +ENTRIES should have a form of `guix-entries'." + (setq tabulated-list-entries + (guix-list-get-tabulated-entries entries entry-type)) + (tabulated-list-print)) + +(defun guix-list-get-tabulated-entries (entries entry-type) + "Return list of values of ENTRY-TYPE for `tabulated-list-entries'. +Values are taken from ENTRIES which should have the form of +`guix-entries'." + (mapcar (lambda (entry) + (list (guix-get-key-val entry 'id) + (guix-list-get-tabulated-entry entry entry-type))) + entries)) + +(defun guix-list-get-tabulated-entry (entry entry-type) + "Return array of values for `tabulated-list-entries'. +Parameters are taken from ENTRY of ENTRY-TYPE." + (guix-list-make-tabulated-vector + entry-type + (lambda (param _) + (let ((val (guix-get-key-val entry param)) + (fun (guix-get-key-val guix-list-column-value-methods + entry-type param))) + (if (and val fun) + (funcall fun val entry) + (guix-get-string val)))))) + +(defun guix-list-get-one-line (str &optional _) + "Return one-line string from a multi-line STR." + (guix-get-one-line str)) + +(defun guix-list-get-time (seconds &optional _) + "Return formatted time string from SECONDS." + (guix-get-time-string seconds)) + +(defun guix-list-get-file-path (path &optional _) + "Return PATH button specification for `tabulated-list-entries'." + (list path + 'face 'guix-list-file-path + 'action (lambda (btn) (find-file (button-label btn))) + 'follow-link t + 'help-echo "Find file")) + +(defun guix-list-current-id () + "Return ID of the current entry." + (or (tabulated-list-get-id) + (user-error "No entry here"))) + +(defun guix-list-current-entry () + "Return alist of the current entry info." + (guix-get-entry-by-id (guix-list-current-id) guix-entries)) + +(defun guix-list-for-each-line (fun &rest args) + "Call FUN with ARGS for each entry line." + (or (derived-mode-p 'guix-list-mode) + (error "The current buffer is not in Guix List mode")) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (apply fun args) + (forward-line)))) + +(defun guix-list-fold-lines (fun init) + "Fold over entry lines in the current list buffer. +Call FUN with RESULT as argument for each line, using INIT as +the initial value of RESULT. Return the final result." + (let ((res init)) + (guix-list-for-each-line + (lambda () (setq res (funcall fun res)))) + res)) + + +;;; Marking and sorting + +(defvar-local guix-list-marked nil + "List of the marked entries. +Each element of the list has a form: + + (ID MARK-NAME . ARGS) + +ID is an entry ID. +MARK-NAME is a symbol from `guix-list-mark-alist'. +ARGS is a list of additional values.") + +(defvar guix-list-mark-alist + '((empty . ?\s) + (general . ?*)) + "Alist of available mark names and mark characters.") + +(defsubst guix-list-get-mark (name) + "Return mark character by its NAME." + (or (guix-get-key-val guix-list-mark-alist name) + (error "Mark '%S' not found" name))) + +(defsubst guix-list-get-mark-string (name) + "Return mark string by its NAME." + (string (guix-list-get-mark name))) + +(defun guix-list-current-mark () + "Return mark character of the current line." + (char-after (line-beginning-position))) + +(defun guix-list-get-marked (&rest mark-names) + "Return list of specs of entries marked with any mark from MARK-NAMES. +Entry specs are elements from `guix-list-marked' list. +If MARK-NAMES are not specified, use all marks from +`guix-list-mark-alist' except the `empty' one." + (or mark-names + (setq mark-names + (delq 'empty + (mapcar #'car guix-list-mark-alist)))) + (cl-remove-if-not (lambda (assoc) + (memq (cadr assoc) mark-names)) + guix-list-marked)) + +(defun guix-list-get-marked-args (mark-name) + "Return list of (ID . ARGS) elements from lines marked with MARK-NAME. +See `guix-list-marked' for the meaning of ARGS." + (mapcar (lambda (spec) + (let ((id (car spec)) + (args (cddr spec))) + (cons id args))) + (guix-list-get-marked mark-name))) + +(defun guix-list-get-marked-id-list (&rest mark-names) + "Return list of IDs of entries marked with any mark from MARK-NAMES. +See `guix-list-get-marked' for details." + (mapcar #'car (apply #'guix-list-get-marked mark-names))) + +(defun guix-list-mark (mark-name &optional advance &rest args) + "Put a mark on the current line. +Also add the current entry to `guix-list-marked' using its ID and ARGS. +MARK-NAME is a symbol from `guix-list-mark-alist'. +If ADVANCE is non-nil, move forward by one line after marking. +Interactively, put a general mark and move to the next line." + (interactive '(general t)) + (let ((id (guix-list-current-id))) + (if (eq mark-name 'empty) + (setq guix-list-marked (assq-delete-all id guix-list-marked)) + (let ((assoc (assq id guix-list-marked)) + (val (cons mark-name args))) + (if assoc + (setcdr assoc val) + (push (cons id val) guix-list-marked))))) + (tabulated-list-put-tag (guix-list-get-mark-string mark-name) + advance)) + +(defun guix-list-mark-all (mark-name) + "Mark all lines with MARK-NAME mark. +MARK-NAME is a symbol from `guix-list-mark-alist'. +Interactively, put a general mark on all lines." + (interactive '(general)) + (guix-list-for-each-line #'guix-list-mark mark-name)) + +(defun guix-list-unmark () + "Unmark the current line and move to the next line." + (interactive) + (guix-list-mark 'empty t)) + +(defun guix-list-unmark-backward () + "Move up one line and unmark it." + (interactive) + (forward-line -1) + (guix-list-mark 'empty)) + +(defun guix-list-unmark-all () + "Unmark all lines." + (interactive) + (guix-list-mark-all 'empty)) + +(defun guix-list-restore-marks () + "Put marks according to `guix-list-mark-alist'." + (guix-list-for-each-line + (lambda () + (let ((mark-name (car (guix-get-key-val guix-list-marked + (guix-list-current-id))))) + (tabulated-list-put-tag + (guix-list-get-mark-string (or mark-name 'empty))))))) + +(defun guix-list-sort (&optional n) + "Sort guix list entries by the column at point. +With a numeric prefix argument N, sort the Nth column. +Same as `tabulated-list-sort', but also restore marks after sorting." + (interactive "P") + (tabulated-list-sort n) + (guix-list-restore-marks)) + + +(defvar guix-list-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map tabulated-list-mode-map) + (define-key map (kbd "m") 'guix-list-mark) + (define-key map (kbd "*") 'guix-list-mark) + (define-key map (kbd "M") 'guix-list-mark-all) + (define-key map (kbd "u") 'guix-list-unmark) + (define-key map (kbd "U") 'guix-list-unmark-all) + (define-key map (kbd "DEL") 'guix-list-unmark-backward) + (define-key map [remap tabulated-list-sort] 'guix-list-sort) + map) + "Parent keymap for list buffers.") + +(define-derived-mode guix-list-mode tabulated-list-mode "Guix-List" + "Parent mode for displaying information in list buffers." + (setq tabulated-list-padding 2)) + +(defmacro guix-list-define-entry-type (entry-type &rest args) + "Define common stuff for displaying ENTRY-TYPE entries in list buffers. + +Remaining argument (ARGS) should have a form [KEYWORD VALUE] ... The +following keywords are available: + + - `:sort-key' - default sort key for the tabulated list buffer. + + - `:invert-sort' - if non-nil, invert initial sort. + + - `:marks' - default value for the defined + `guix-ENTRY-TYPE-mark-alist' variable. + +This macro defines the following functions: + + - `guix-ENTRY-TYPE-describe' - display marked entries in info buffer. + + - `guix-ENTRY-TYPE-mark-MARK-NAME' functions for each mark + specified in `:marks' argument." + (let* ((entry-type-str (symbol-name entry-type)) + (entry-str (concat entry-type-str " entries")) + (prefix (concat "guix-" entry-type-str "-list")) + (mode-str (concat prefix "-mode")) + (init-fun (intern (concat prefix "-mode-initialize"))) + (describe-fun (intern (concat prefix "-describe"))) + (marks-var (intern (concat prefix "-mark-alist"))) + (marks-val nil) + (sort-key nil) + (invert-sort nil)) + + ;; Process the keyword args. + (while (keywordp (car args)) + (pcase (pop args) + (`:sort-key (setq sort-key (pop args))) + (`:invert-sort (setq invert-sort (pop args))) + (`:marks (setq marks-val (pop args))) + (_ (pop args)))) + + `(progn + (defvar ,marks-var ',marks-val + ,(concat "Alist of additional marks for `" mode-str "'.\n" + "Marks from this list are added to `guix-list-mark-alist'.")) + + ,@(mapcar (lambda (mark-spec) + (let* ((mark-name (car mark-spec)) + (mark-name-str (symbol-name mark-name))) + `(defun ,(intern (concat prefix "-mark-" mark-name-str "-simple")) () + ,(concat "Put '" mark-name-str "' mark and move to the next line.\n" + "Also add the current entry to `guix-list-marked'.") + (interactive) + (guix-list-mark ',mark-name t)))) + marks-val) + + (defun ,describe-fun (&optional arg) + ,(concat "Describe " entry-str " marked with a general mark.\n" + "If no entry is marked, describe the current " entry-type-str ".\n" + "With prefix (if ARG is non-nil), describe the " entry-str "\n" + "marked with any mark.") + (interactive "P") + (let* ((ids (or (apply #'guix-list-get-marked-id-list + (unless arg '(general))) + (list (guix-list-current-id)))) + (count (length ids))) + (when (or (<= count guix-list-describe-warning-count) + (y-or-n-p (format "Do you really want to describe %d entries? " + count))) + (,(intern (concat "guix-" entry-type-str "-info-get-show")) + 'id ids)))) + + (defun ,init-fun () + ,(concat "Initial settings for `" mode-str "'.") + ,(when sort-key + `(setq tabulated-list-sort-key + (guix-list-get-sort-key + ',entry-type ',sort-key ,invert-sort))) + (setq tabulated-list-format + (guix-list-get-list-format ',entry-type)) + (setq-local guix-list-mark-alist + (append guix-list-mark-alist ,marks-var)) + (tabulated-list-init-header))))) + +(put 'guix-list-define-entry-type 'lisp-indent-function 'defun) + + +;;; Displaying packages + +(guix-define-buffer-type list package) + +(guix-list-define-entry-type package + :sort-key name + :marks ((install . ?I) + (upgrade . ?U) + (delete . ?D))) + +(defface guix-package-list-installed + '((t :inherit guix-package-info-installed-outputs)) + "Face used if there are installed outputs for the current package." + :group 'guix-package-list) + +(defface guix-package-list-obsolete + '((t :inherit guix-package-info-obsolete)) + "Face used if a package is obsolete." + :group 'guix-package-list) + +(defcustom guix-package-list-generation-marking-enabled nil + "If non-nil, allow putting marks in a list with 'generation packages'. + +By default this is disabled, because it may be confusing. For +example a package is installed in some generation, so a user can +mark it for deletion in the list of packages from this +generation, but the package may not be installed in the latest +generation, so actually it cannot be deleted. + +If you managed to understand the explanation above or if you +really know what you do or if you just don't care, you can set +this variable to t. It should not do much harm anyway (most +likely)." + :type 'boolean + :group 'guix-package-list) + +(let ((map guix-package-list-mode-map)) + (define-key map (kbd "RET") 'guix-package-list-describe) + (define-key map (kbd "x") 'guix-package-list-execute) + (define-key map (kbd "i") 'guix-package-list-mark-install) + (define-key map (kbd "^") 'guix-package-list-mark-upgrade) + (define-key map (kbd "d") 'guix-package-list-mark-delete)) + +(defun guix-package-list-get-name (name entry) + "Return NAME of the package ENTRY. +Colorize it with `guix-package-list-installed' or +`guix-package-list-obsolete' if needed." + (guix-get-string name + (cond ((guix-get-key-val entry 'obsolete) + 'guix-package-list-obsolete) + ((guix-get-key-val entry 'installed) + 'guix-package-list-installed)))) + +(defun guix-package-list-get-installed-outputs (installed &optional _) + "Return string with outputs from INSTALLED entries." + (guix-get-string + (mapcar (lambda (entry) + (guix-get-key-val entry 'output)) + installed))) + +(defun guix-package-list-marking-check () + "Signal an error if marking is disabled for the current buffer." + (when (and (not guix-package-list-generation-marking-enabled) + (derived-mode-p 'guix-package-list-mode) + (eq guix-search-type 'generation)) + (error "Action marks are disabled for lists of 'generation packages'"))) + +(defun guix-package-list-mark-install (&optional arg) + "Mark the current package for installation and move to the next line. +With ARG, prompt for the outputs to install (several outputs may +be separated with \",\")." + (interactive "P") + (guix-package-list-marking-check) + (let* ((entry (guix-list-current-entry)) + (available (guix-get-key-val entry 'outputs)) + (installed (guix-get-installed-outputs entry)) + (to-install (if arg + (guix-completing-read-multiple + "Output(s) to install: " available nil t) + '("out"))) + (to-install (cl-set-difference to-install installed + :test #'string=))) + (if to-install + (apply #'guix-list-mark 'install t to-install) + (user-error "This package is already installed")))) + +(defun guix-package-list-mark-delete (&optional arg) + "Mark the current package for deletion and move to the next line. +With ARG, prompt for the outputs to delete (several outputs may +be separated with \",\")." + (interactive "P") + (guix-package-list-marking-check) + (let* ((entry (guix-list-current-entry)) + (installed (guix-get-installed-outputs entry))) + (or installed + (user-error "This package is not installed")) + (let ((to-delete (when arg + (guix-completing-read-multiple + "Output(s) to delete: " installed nil t)))) + (if to-delete + (apply #'guix-list-mark 'delete t to-delete) + (guix-package-list-mark-delete-simple))))) + +(defun guix-package-list-mark-upgrade () + "Mark the current package for upgrading and move to the next line." + (interactive) + (guix-package-list-marking-check) + (let ((entry (guix-list-current-entry))) + (or (guix-get-installed-outputs entry) + (user-error "This package is not installed")) + (when (or (guix-get-key-val entry 'obsolete) + (y-or-n-p "This package is not obsolete. Try to upgrade it anyway? ")) + (guix-package-list-mark-upgrade-simple)))) + +(defun guix-package-list-execute () + "Perform actions on the marked packages." + (interactive) + (let ((actions (delq nil + (mapcar #'guix-package-list-make-action + '(install delete upgrade))))) + (if actions + (apply #'guix-process-package-actions actions) + (user-error "No operations specified")))) + +(defun guix-package-list-make-action (action-type) + "Return action specification for the packages marked with ACTION-TYPE. +Return nil, if there are no packages marked with ACTION-TYPE. +The specification is suitable for `guix-process-package-actions'." + (let ((specs (guix-list-get-marked-args action-type))) + (and specs (cons action-type specs)))) + + +;;; Displaying generations + +(guix-define-buffer-type list generation) + +(guix-list-define-entry-type generation + :sort-key number + :invert-sort t + :marks ((delete . ?D))) + +(let ((map guix-generation-list-mode-map)) + (define-key map (kbd "RET") 'guix-generation-list-show-packages) + (define-key map (kbd "i") 'guix-generation-list-describe) + (define-key map (kbd "d") 'guix-generation-list-mark-delete-simple)) + +(defun guix-generation-list-show-packages () + "List installed packages for the generation at point." + (interactive) + (guix-package-list-get-show 'generation (guix-list-current-id))) + +(provide 'guix-list) + +;;; guix-list.el ends here diff --git a/emacs/guix-main.scm b/emacs/guix-main.scm new file mode 100644 index 0000000000..1383d08830 --- /dev/null +++ b/emacs/guix-main.scm @@ -0,0 +1,603 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 Alex Kost <alezost@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Information about packages and generations is passed to the elisp +;; side in the form of alists of parameters (such as ‘name’ or +;; ‘version’) and their values. These alists are called "entries" in +;; this code. So to distinguish, just "package" in the name of a +;; function means a guile object ("package" record) while +;; "package entry" means alist of package parameters and values (see +;; ‘package-param-alist’). +;; +;; "Entry" is probably not the best name for such alists, because there +;; already exists "manifest-entry" which has nothing to do with the +;; "entry" described above. Do not be confused :) + +;; ‘get-entries’ function is the “entry point” for the elisp side to get +;; information about packages and generations. + +;; Since name/version pair is not necessarily unique, we use +;; `object-address' to identify a package (for ‘id’ parameter), if +;; possible. However for the obsolete packages (that can be found in +;; installed manifest but not in a package directory), ‘id’ parameter is +;; still "name-version" string. So ‘id’ package parameter in the code +;; below is either an object-address number or a full-name string. +;; +;; Important: as object addresses live only during guile session, elisp +;; part should take care about updating information after "Guix REPL" is +;; restarted (TODO!) +;; +;; ‘installed’ parameter of a package entry contains information about +;; installed outputs. It is a list of "installed entries" (see +;; ‘package-installed-param-alist’). + +;; To speed-up the process of getting information, the following +;; auxiliary variables are used: +;; +;; - `%packages' - VHash of "package address"/"package" pairs. +;; +;; - `%package-table' - Hash table of +;; "name+version key"/"list of packages" pairs. +;; +;; - `%current-manifest-entries-table' - Hash table of +;; "name+version key"/"list of manifest entries" pairs. This variable +;; is set by `set-current-manifest-maybe!' when it is needed. + +;;; Code: + +(use-modules + (ice-9 vlist) + (ice-9 match) + (srfi srfi-1) + (srfi srfi-11) + (srfi srfi-19) + (srfi srfi-26) + (guix) + (guix packages) + (guix profiles) + (guix licenses) + (guix utils) + (guix ui) + (guix scripts package) + (gnu packages)) + +(define-syntax-rule (first-or-false lst) + (and (not (null? lst)) + (first lst))) + +(define full-name->name+version package-name->name+version) +(define (name+version->full-name name version) + (string-append name "-" version)) + +(define* (make-package-specification name #:optional version output) + (let ((full-name (if version + (name+version->full-name name version) + name))) + (if output + (string-append full-name ":" output) + full-name))) + +(define name+version->key cons) +(define key->name+version car+cdr) + +(define %current-manifest #f) +(define %current-manifest-entries-table #f) + +(define %packages + (fold-packages (lambda (pkg res) + (vhash-consq (object-address pkg) pkg res)) + vlist-null)) + +(define %package-table + (let ((table (make-hash-table (vlist-length %packages)))) + (vlist-for-each + (lambda (elem) + (match elem + ((address . pkg) + (let* ((key (name+version->key (package-name pkg) + (package-version pkg))) + (ref (hash-ref table key))) + (hash-set! table key + (if ref (cons pkg ref) (list pkg))))))) + %packages) + table)) + +;; FIXME get rid of this function! +(define (set-current-manifest-maybe! profile) + (define (manifest-entries->hash-table entries) + (let ((entries-table (make-hash-table (length entries)))) + (for-each (lambda (entry) + (let* ((key (name+version->key + (manifest-entry-name entry) + (manifest-entry-version entry))) + (ref (hash-ref entries-table key))) + (hash-set! entries-table key + (if ref (cons entry ref) (list entry))))) + entries) + entries-table)) + + (when profile + (let ((manifest (profile-manifest profile))) + (unless (and (manifest? %current-manifest) + (equal? manifest %current-manifest)) + (set! %current-manifest manifest) + (set! %current-manifest-entries-table + (manifest-entries->hash-table + (manifest-entries manifest))))))) + +(define (manifest-entries-by-name+version name version) + (or (hash-ref %current-manifest-entries-table + (name+version->key name version)) + '())) + +(define (packages-by-name+version name version) + (or (hash-ref %package-table + (name+version->key name version)) + '())) + +(define (packages-by-full-name full-name) + (call-with-values + (lambda () (full-name->name+version full-name)) + packages-by-name+version)) + +(define (package-by-address address) + (and=> (vhash-assq address %packages) + cdr)) + +(define (packages-by-id id) + (if (integer? id) + (let ((pkg (package-by-address id))) + (if pkg (list pkg) '())) + (packages-by-full-name id))) + +(define (package-by-id id) + (first-or-false (packages-by-id id))) + +(define (newest-package-by-id id) + (and=> (id->name+version id) + (lambda (name) + (first-or-false (find-best-packages-by-name name #f))))) + +(define (id->name+version id) + (if (integer? id) + (and=> (package-by-address id) + (lambda (pkg) + (values (package-name pkg) + (package-version pkg)))) + (full-name->name+version id))) + +(define (fold-manifest-entries proc init) + "Fold over `%current-manifest-entries-table'. +Call (PROC NAME VERSION ENTRIES RESULT) for each element of the hash +table, using INIT as the initial value of RESULT." + (hash-fold (lambda (key entries res) + (let-values (((name version) (key->name+version key))) + (proc name version entries res))) + init + %current-manifest-entries-table)) + +(define (fold-object proc init obj) + (fold proc init + (if (list? obj) obj (list obj)))) + +(define* (object-transformer param-alist #:optional (params '())) + "Return function for transforming an object into alist of parameters/values. + +PARAM-ALIST is alist of available object parameters (symbols) and functions +returning values of these parameters. Each function is called with object as +a single argument. + +PARAMS is list of parameters from PARAM-ALIST that should be returned by a +resulting function. If PARAMS is not specified or is an empty list, use all +available parameters. + +Example: + + (let ((alist `((plus1 . ,1+) (minus1 . ,1-) (mul2 . ,(cut * 2 <>)))) + (number->alist (object-transformer alist '(plus1 mul2)))) + (number->alist 8)) + => + ((plus1 . 9) (mul2 . 16)) +" + (let ((alist (let ((use-all-params (null? params))) + (filter-map (match-lambda + ((param . fun) + (and (or use-all-params + (memq param params)) + (cons param fun))) + (_ #f)) + param-alist)))) + (lambda (object) + (map (match-lambda + ((param . fun) + (cons param (fun object)))) + alist)))) + +(define package-installed-param-alist + (list + (cons 'output manifest-entry-output) + (cons 'path manifest-entry-item) + (cons 'dependencies manifest-entry-dependencies))) + +(define manifest-entry->installed-entry + (object-transformer package-installed-param-alist)) + +(define (manifest-entries->installed-entries entries) + (map manifest-entry->installed-entry entries)) + +(define (installed-entries-by-name+version name version) + (manifest-entries->installed-entries + (manifest-entries-by-name+version name version))) + +(define (installed-entries-by-package package) + (installed-entries-by-name+version (package-name package) + (package-version package))) + +(define (package-inputs-names inputs) + "Return list of full names of the packages from package INPUTS." + (filter-map (match-lambda + ((_ (? package? package)) + (package-full-name package)) + (_ #f)) + inputs)) + +(define (package-license-names package) + "Return list of license names of the PACKAGE." + (fold-object (lambda (license res) + (if (license? license) + (cons (license-name license) res) + res)) + '() + (package-license package))) + +(define (package-unique? package) + "Return #t if PACKAGE is a single package with such name/version." + (null? (cdr (packages-by-name+version (package-name package) + (package-version package))))) + +(define package-param-alist + (list + (cons 'id object-address) + (cons 'name package-name) + (cons 'version package-version) + (cons 'license package-license-names) + (cons 'synopsis package-synopsis) + (cons 'description package-description) + (cons 'home-url package-home-page) + (cons 'outputs package-outputs) + (cons 'non-unique (negate package-unique?)) + (cons 'inputs (lambda (pkg) (package-inputs-names + (package-inputs pkg)))) + (cons 'native-inputs (lambda (pkg) (package-inputs-names + (package-native-inputs pkg)))) + (cons 'propagated-inputs (lambda (pkg) (package-inputs-names + (package-propagated-inputs pkg)))) + (cons 'location (lambda (pkg) (location->string + (package-location pkg)))) + (cons 'installed installed-entries-by-package))) + +(define (package-param package param) + "Return the value of a PACKAGE PARAM." + (define (accessor param) + (and=> (assq param package-param-alist) + cdr)) + (and=> (accessor param) + (cut <> package))) + +(define (matching-package-entries ->entry predicate) + "Return list of package entries for the matching packages. +PREDICATE is called on each package." + (fold-packages (lambda (pkg res) + (if (predicate pkg) + (cons (->entry pkg) res) + res)) + '())) + +(define (make-obsolete-package-entry name version entries) + "Return package entry for an obsolete package with NAME and VERSION. +ENTRIES is a list of manifest entries used to get installed info." + `((id . ,(name+version->full-name name version)) + (name . ,name) + (version . ,version) + (outputs . ,(map manifest-entry-output entries)) + (obsolete . #t) + (installed . ,(manifest-entries->installed-entries entries)))) + +(define (package-entries-by-name+version ->entry name version) + "Return list of package entries for packages with NAME and VERSION." + (let ((packages (packages-by-name+version name version))) + (if (null? packages) + (let ((entries (manifest-entries-by-name+version name version))) + (if (null? entries) + '() + (list (make-obsolete-package-entry name version entries)))) + (map ->entry packages)))) + +(define (package-entries-by-spec profile ->entry spec) + "Return list of package entries for packages with name specification SPEC." + (set-current-manifest-maybe! profile) + (let-values (((name version) + (full-name->name+version spec))) + (if version + (package-entries-by-name+version ->entry name version) + (matching-package-entries + ->entry + (lambda (pkg) (string=? name (package-name pkg))))))) + +(define (package-entries-by-regexp profile ->entry regexp match-params) + "Return list of package entries for packages matching REGEXP string. +MATCH-PARAMS is a list of parameters that REGEXP can match." + (define (package-match? package regexp) + (any (lambda (param) + (let ((val (package-param package param))) + (and (string? val) (regexp-exec regexp val)))) + match-params)) + + (set-current-manifest-maybe! profile) + (let ((re (make-regexp regexp regexp/icase))) + (matching-package-entries ->entry (cut package-match? <> re)))) + +(define (package-entries-by-ids profile ->entry ids) + "Return list of package entries for packages matching KEYS. +IDS may be an object-address, a full-name or a list of such elements." + (set-current-manifest-maybe! profile) + (fold-object + (lambda (id res) + (if (integer? id) + (let ((pkg (package-by-address id))) + (if pkg + (cons (->entry pkg) res) + res)) + (let ((entries (package-entries-by-spec #f ->entry id))) + (if (null? entries) + res + (append res entries))))) + '() + ids)) + +(define (newest-available-package-entries profile ->entry) + "Return list of package entries for the newest available packages." + (set-current-manifest-maybe! profile) + (vhash-fold (lambda (name elem res) + (match elem + ((version newest pkgs ...) + (cons (->entry newest) res)))) + '() + (find-newest-available-packages))) + +(define (all-available-package-entries profile ->entry) + "Return list of package entries for all available packages." + (set-current-manifest-maybe! profile) + (matching-package-entries ->entry (const #t))) + +(define (manifest-package-entries ->entry) + "Return list of package entries for the current manifest." + (fold-manifest-entries + (lambda (name version entries res) + ;; We don't care about duplicates for the list of + ;; installed packages, so just take any package (car) + ;; matching name+version + (cons (car (package-entries-by-name+version ->entry name version)) + res)) + '())) + +(define (installed-package-entries profile ->entry) + "Return list of package entries for all installed packages." + (set-current-manifest-maybe! profile) + (manifest-package-entries ->entry)) + +(define (generation-package-entries profile ->entry generation) + "Return list of package entries for packages from GENERATION." + (set-current-manifest-maybe! + (generation-file-name profile generation)) + (manifest-package-entries ->entry)) + +(define (obsolete-package-entries profile _) + "Return list of package entries for obsolete packages." + (set-current-manifest-maybe! profile) + (fold-manifest-entries + (lambda (name version entries res) + (let ((packages (packages-by-name+version name version))) + (if (null? packages) + (cons (make-obsolete-package-entry name version entries) res) + res))) + '())) + + +;;; Generation entries + +(define (profile-generations profile) + "Return list of generations for PROFILE." + (let ((generations (generation-numbers profile))) + (if (equal? generations '(0)) + '() + generations))) + +(define (generation-param-alist profile) + "Return alist of generation parameters and functions for PROFILE." + (list + (cons 'id identity) + (cons 'number identity) + (cons 'prev-number (cut previous-generation-number profile <>)) + (cons 'path (cut generation-file-name profile <>)) + (cons 'time (lambda (gen) + (time-second (generation-time profile gen)))))) + +(define (matching-generation-entries profile ->entry predicate) + "Return list of generation entries for the matching generations. +PREDICATE is called on each generation." + (filter-map (lambda (gen) + (and (predicate gen) (->entry gen))) + (profile-generations profile))) + +(define (last-generation-entries profile ->entry number) + "Return list of last NUMBER generation entries. +If NUMBER is 0 or less, return all generation entries." + (let ((generations (profile-generations profile)) + (number (if (<= number 0) +inf.0 number))) + (map ->entry + (if (> (length generations) number) + (list-head (reverse generations) number) + generations)))) + +(define (all-generation-entries profile ->entry) + "Return list of all generation entries." + (last-generation-entries profile ->entry +inf.0)) + +(define (generation-entries-by-ids profile ->entry ids) + "Return list of generation entries for generations matching IDS. +IDS is a list of generation numbers." + (matching-generation-entries profile ->entry (cut memq <> ids))) + + +;;; Getting package/generation entries + +(define %package-entries-functions + (alist->vhash + `((id . ,package-entries-by-ids) + (name . ,package-entries-by-spec) + (regexp . ,package-entries-by-regexp) + (all-available . ,all-available-package-entries) + (newest-available . ,newest-available-package-entries) + (installed . ,installed-package-entries) + (obsolete . ,obsolete-package-entries) + (generation . ,generation-package-entries)) + hashq)) + +(define %generation-entries-functions + (alist->vhash + `((id . ,generation-entries-by-ids) + (last . ,last-generation-entries) + (all . ,all-generation-entries)) + hashq)) + +(define (get-entries profile params entry-type search-type search-vals) + "Return list of entries. +ENTRY-TYPE and SEARCH-TYPE define a search function that should be +applied to PARAMS and VALS." + (let-values (((vhash ->entry) + (case entry-type + ((package) + (values %package-entries-functions + (object-transformer + package-param-alist params))) + ((generation) + (values %generation-entries-functions + (object-transformer + (generation-param-alist profile) params))) + (else (format (current-error-port) + "Wrong entry type '~a'" entry-type))))) + (match (vhash-assq search-type vhash) + ((key . fun) + (apply fun profile ->entry search-vals)) + (_ '())))) + + +;;; Actions + +(define* (package->manifest-entry* package #:optional output) + (and package + (begin + (check-package-freshness package) + (package->manifest-entry package output)))) + +(define* (make-install-manifest-entries id #:optional output) + (package->manifest-entry* (package-by-id id) output)) + +(define* (make-upgrade-manifest-entries id #:optional output) + (package->manifest-entry* (newest-package-by-id id) output)) + +(define* (make-manifest-pattern id #:optional output) + "Make manifest pattern from a package ID and OUTPUT." + (let-values (((name version) + (id->name+version id))) + (and name version + (manifest-pattern + (name name) + (version version) + (output output))))) + +(define (convert-action-pattern pattern proc) + "Convert action PATTERN into a list of objects returned by PROC. +PROC is called: (PROC ID) or (PROC ID OUTPUT)." + (match pattern + ((id . outputs) + (if (null? outputs) + (let ((obj (proc id))) + (if obj (list obj) '())) + (filter-map (cut proc id <>) + outputs))) + (_ '()))) + +(define (convert-action-patterns patterns proc) + (append-map (cut convert-action-pattern <> proc) + patterns)) + +(define* (process-package-actions + profile #:key (install '()) (upgrade '()) (remove '()) + (use-substitutes? #t) dry-run?) + "Perform package actions. + +INSTALL, UPGRADE, REMOVE are lists of 'package action patterns'. +Each pattern should have the following form: + + (ID . OUTPUTS) + +ID is an object address or a full-name of a package. +OUTPUTS is a list of package outputs (may be an empty list)." + (format #t "The process begins ...~%") + (let* ((install (append + (convert-action-patterns + install make-install-manifest-entries) + (convert-action-patterns + upgrade make-upgrade-manifest-entries))) + (remove (convert-action-patterns remove make-manifest-pattern)) + (transaction (manifest-transaction (install install) + (remove remove))) + (manifest (profile-manifest profile)) + (new-manifest (manifest-perform-transaction + manifest transaction))) + (unless (and (null? install) (null? remove)) + (let* ((store (open-connection)) + (derivation (run-with-store + store (profile-derivation new-manifest))) + (derivations (list derivation)) + (new-profile (derivation->output-path derivation))) + (set-build-options store + #:use-substitutes? use-substitutes?) + (manifest-show-transaction store manifest transaction + #:dry-run? dry-run?) + (show-what-to-build store derivations + #:use-substitutes? use-substitutes? + #:dry-run? dry-run?) + (unless dry-run? + (let ((name (generation-file-name + profile + (+ 1 (generation-number profile))))) + (and (build-derivations store derivations) + (let* ((entries (manifest-entries new-manifest)) + (count (length entries))) + (switch-symlinks name new-profile) + (switch-symlinks profile name) + (format #t (N_ "~a package in profile~%" + "~a packages in profile~%" + count) + count))))))))) + diff --git a/emacs/guix-utils.el b/emacs/guix-utils.el new file mode 100644 index 0000000000..c1fe1a3a38 --- /dev/null +++ b/emacs/guix-utils.el @@ -0,0 +1,160 @@ +;;; guix-utils.el --- General utility functions + +;; Copyright © 2014 Alex Kost <alezost@gmail.com> + +;; This file is part of GNU Guix. + +;; GNU Guix is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Guix is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file provides auxiliary general functions for guix.el package. + +;;; Code: + +;; (require 'cl-lib) + +(defvar guix-true-string "Yes") +(defvar guix-false-string "–") +(defvar guix-list-separator ", ") + +(defvar guix-time-format "%F %T" + "String used to format time values. +For possible formats, see `format-time-string'.") + +(defun guix-get-string (val &optional face) + "Convert VAL into a string and return it. + +VAL can be an expression of any type. +If VAL is t/nil, it is replaced with +`guix-true-string'/`guix-false-string'. +If VAL is list, its elements are concatenated using +`guix-list-separator'. + +If FACE is non-nil, propertize returned string with this FACE." + (let ((str (cond + ((stringp val) val) + ((null val) guix-false-string) + ((eq t val) guix-true-string) + ((numberp val) (number-to-string val)) + ((listp val) (mapconcat #'guix-get-string + val guix-list-separator)) + (t (prin1-to-string val))))) + (if (and val face) + (propertize str 'face face) + str))) + +(defun guix-get-time-string (seconds) + "Return formatted time string from SECONDS. +Use `guix-time-format'." + (format-time-string guix-time-format (seconds-to-time seconds))) + +(defun guix-get-one-line (str) + "Return one-line string from a multi-line STR." + (replace-regexp-in-string "\n" " " str)) + +(defun guix-format-insert (val &optional face format) + "Convert VAL into a string and insert it at point. +If FACE is non-nil, propertize VAL with FACE. +If FORMAT is non-nil, format VAL with FORMAT." + (let ((str (guix-get-string val face))) + (insert (if format + (format format str) + str)))) + +(defun guix-mapinsert (function sequence separator) + "Like `mapconcat' but for inserting text. +Apply FUNCTION to each element of SEQUENCE, and insert SEPARATOR +at point between each FUNCTION call." + (when sequence + (funcall function (car sequence)) + (mapc (lambda (obj) + (insert separator) + (funcall function obj)) + (cdr sequence)))) + +(defun guix-insert-button (label face action &optional message + &rest properties) + "Make button with LABEL and insert it at point. +Propertize button with FACE. +ACTION is a function called when the button is pressed. It +should accept button as the argument. +MESSAGE is a button message. +See `insert-text-button' for the meaning of PROPERTIES." + (if (null label) + (guix-format-insert nil) + (apply #'insert-text-button + label + 'face face + 'action action + 'follow-link t + 'help-echo message + properties))) + +(defun guix-split-insert (val &optional face col separator) + "Convert VAL into a string, split it and insert at point. + +If FACE is non-nil, propertize returned string with this FACE. + +If COL is non-nil and result string is a one-line string longer +than COL, split it into several short lines. + +Separate inserted lines with SEPARATOR." + (if (null val) + (guix-format-insert nil) + (let ((strings (guix-split-string (guix-get-string val) col))) + (guix-mapinsert (lambda (str) (guix-format-insert str face)) + strings + (or separator ""))))) + +(defun guix-split-string (str &optional col) + "Split string STR by lines and return list of result strings. +If COL is non-nil and STR is a one-line string longer than COL, +split it into several short lines." + (let ((strings (split-string str "\n *"))) + (if (and col + (null (cdr strings)) ; if not multi-line + (> (length str) col)) + (split-string (guix-get-filled-string str col) "\n") + strings))) + +(defun guix-get-filled-string (str col) + "Return string by filling STR to column COL." + (with-temp-buffer + (insert str) + (let ((fill-column col)) + (fill-region (point-min) (point-max))) + (buffer-string))) + +(defun guix-completing-read-multiple (prompt table &optional predicate + require-match initial-input + hist def inherit-input-method) + "Same as `completing-read-multiple' but remove duplicates in result." + (cl-remove-duplicates + (completing-read-multiple prompt table predicate + require-match initial-input + hist def inherit-input-method) + :test #'string=)) + +(defun guix-get-key-val (alist &rest keys) + "Return value from ALIST by KEYS. +ALIST is alist of alists of alists ... which can be consecutively +accessed with KEYS." + (let ((val alist)) + (dolist (key keys val) + (setq val (cdr (assq key val)))))) + +(provide 'guix-utils) + +;;; guix-utils.el ends here diff --git a/emacs/guix.el b/emacs/guix.el new file mode 100644 index 0000000000..7336f6732e --- /dev/null +++ b/emacs/guix.el @@ -0,0 +1,141 @@ +;;; guix.el --- Interface for GNU Guix package manager + +;; Copyright © 2014 Alex Kost <alezost@gmail.com> + +;; Package-Requires: ((geiser "0.3")) +;; Keywords: tools + +;; This file is part of GNU Guix. + +;; GNU Guix is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Guix is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This package provides an interface for searching, listing and getting +;; information about Guix packages and generations; and for +;; installing/upgrading/removing packages. + +;;; Code: + +(require 'guix-list) +(require 'guix-info) + +(defgroup guix nil + "Interface for Guix package manager." + :prefix "guix-" + :group 'external) + +(defcustom guix-list-single-package nil + "If non-nil, list a package even if it is the only matching result. +If nil, show a single package in the info buffer." + :type 'boolean + :group 'guix) + +(defcustom guix-show-generations-function 'guix-generation-list-get-show + "Default function used to display generations." + :type '(choice (function-item guix-generation-list-get-show) + (function-item guix-generation-info-get-show)) + :group 'guix) + +(defvar guix-search-params '(name synopsis description) + "Default list of package parameters for searching by regexp.") + +(defvar guix-search-history nil + "A history of minibuffer prompts.") + +(defun guix-get-show-packages (search-type &rest search-vals) + "Search for packages and show results. + +See `guix-get-entries' for the meaning of SEARCH-TYPE and +SEARCH-VALS. + +Results are displayed in the list buffer, unless a single package +is found and `guix-list-single-package' is nil." + (let* ((list-params (guix-package-list-get-params-for-receiving)) + (packages (guix-get-entries 'package search-type + search-vals list-params))) + (if (or guix-list-single-package + (cdr packages)) + (guix-package-list-set packages search-type search-vals) + (let ((info-params (guix-package-info-get-params-for-receiving))) + (unless (equal list-params info-params) + ;; If we don't have required info, we should receive it again + (setq packages (guix-get-entries 'package search-type + search-vals info-params)))) + (guix-package-info-set packages search-type search-vals)))) + +(defun guix-get-show-generations (search-type &rest search-vals) + "Search for generations and show results." + (apply guix-show-generations-function search-type search-vals)) + +;;;###autoload +(defun guix-search-by-name (name) + "Search for Guix packages by NAME. +NAME is a string with name specification. It may optionally contain +a version number. Examples: \"guile\", \"guile-2.0.11\"." + (interactive + (list (read-string "Package name: " nil 'guix-search-history))) + (guix-get-show-packages 'name name)) + +;;;###autoload +(defun guix-search-by-regexp (regexp &rest params) + "Search for Guix packages by REGEXP. +PARAMS are package parameters that should be searched. +If PARAMS are not specified, use `guix-search-params'." + (interactive + (list (read-string "Regexp: " nil 'guix-search-history))) + (or params (setq params guix-search-params)) + (guix-get-show-packages 'regexp regexp params)) + +;;;###autoload +(defun guix-installed-packages () + "Display information about installed Guix packages." + (interactive) + (guix-get-show-packages 'installed)) + +;;;###autoload +(defun guix-obsolete-packages () + "Display information about obsolete Guix packages." + (interactive) + (guix-get-show-packages 'obsolete)) + +;;;###autoload +(defun guix-all-available-packages () + "Display information about all available Guix packages." + (interactive) + (guix-get-show-packages 'all-available)) + +;;;###autoload +(defun guix-newest-available-packages () + "Display information about the newest available Guix packages." + (interactive) + (guix-get-show-packages 'newest-available)) + +;;;###autoload +(defun guix-generations (&optional number) + "Display information about last NUMBER generations. +If NUMBER is nil, display all generations. + +Generations can be displayed in a list or info buffers depending +on `guix-show-generations-function'. + +Interactively, NUMBER is defined by a numeric prefix." + (interactive "P") + (if (numberp number) + (guix-get-show-generations 'last number) + (guix-get-show-generations 'all))) + +(provide 'guix) + +;;; guix.el ends here diff --git a/gnu-system.am b/gnu-system.am index 6213763326..510a217b98 100644 --- a/gnu-system.am +++ b/gnu-system.am @@ -28,7 +28,7 @@ GNU_SYSTEM_MODULES = \ gnu/packages/acl.scm \ gnu/packages/admin.scm \ gnu/packages/algebra.scm \ - gnu/packages/aidc.scm \ + gnu/packages/aidc.scm \ gnu/packages/apl.scm \ gnu/packages/apr.scm \ gnu/packages/asciidoc.scm \ @@ -58,7 +58,7 @@ GNU_SYSTEM_MODULES = \ gnu/packages/compression.scm \ gnu/packages/complexity.scm \ gnu/packages/conkeror.scm \ - gnu/packages/cook.scm \ + gnu/packages/cook.scm \ gnu/packages/cpio.scm \ gnu/packages/cppi.scm \ gnu/packages/cross-base.scm \ @@ -151,7 +151,7 @@ GNU_SYSTEM_MODULES = \ gnu/packages/lightning.scm \ gnu/packages/links.scm \ gnu/packages/linux.scm \ - gnu/packages/lisp.scm \ + gnu/packages/lisp.scm \ gnu/packages/lout.scm \ gnu/packages/lsh.scm \ gnu/packages/lsof.scm \ @@ -180,6 +180,7 @@ GNU_SYSTEM_MODULES = \ gnu/packages/nettle.scm \ gnu/packages/node.scm \ gnu/packages/noweb.scm \ + gnu/packages/nvi.scm \ gnu/packages/ocaml.scm \ gnu/packages/ocrad.scm \ gnu/packages/onc-rpc.scm \ @@ -198,6 +199,7 @@ GNU_SYSTEM_MODULES = \ gnu/packages/pkg-config.scm \ gnu/packages/plotutils.scm \ gnu/packages/popt.scm \ + gnu/packages/postgresql.scm \ gnu/packages/pth.scm \ gnu/packages/pulseaudio.scm \ gnu/packages/pretty-print.scm \ @@ -275,7 +277,15 @@ GNU_SYSTEM_MODULES = \ gnu/system/linux.scm \ gnu/system/linux-initrd.scm \ gnu/system/shadow.scm \ - gnu/system/vm.scm + gnu/system/vm.scm \ + \ + gnu/build/activation.scm \ + gnu/build/file-systems.scm \ + gnu/build/install.scm \ + gnu/build/linux-boot.scm \ + gnu/build/linux-initrd.scm \ + gnu/build/vm.scm + patchdir = $(guilemoduledir)/gnu/packages/patches dist_patch_DATA = \ @@ -351,6 +361,7 @@ dist_patch_DATA = \ gnu/packages/patches/mit-krb5-init-fix.patch \ gnu/packages/patches/mpc123-initialize-ao.patch \ gnu/packages/patches/module-init-tools-moduledir.patch \ + gnu/packages/patches/nvi-assume-preserve-path.patch \ gnu/packages/patches/orpheus-cast-errors-and-includes.patch \ gnu/packages/patches/patchelf-page-size.patch \ gnu/packages/patches/patchutils-xfail-gendiff-tests.patch \ diff --git a/guix/build/activation.scm b/gnu/build/activation.scm index b04b017881..362669cbf9 100644 --- a/guix/build/activation.scm +++ b/gnu/build/activation.scm @@ -16,9 +16,9 @@ ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. -(define-module (guix build activation) +(define-module (gnu build activation) + #:use-module (gnu build linux-boot) #:use-module (guix build utils) - #:use-module (guix build linux-initrd) #:use-module (ice-9 ftw) #:use-module (ice-9 match) #:use-module (srfi srfi-1) diff --git a/gnu/build/file-systems.scm b/gnu/build/file-systems.scm new file mode 100644 index 0000000000..5c04771e19 --- /dev/null +++ b/gnu/build/file-systems.scm @@ -0,0 +1,299 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 Ludovic Courtès <ludo@gnu.org> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu build file-systems) + #:use-module (guix build utils) + #:use-module (rnrs io ports) + #:use-module (rnrs bytevectors) + #:use-module (ice-9 match) + #:use-module (ice-9 rdelim) + #:use-module (system foreign) + #:autoload (system repl repl) (start-repl) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:export (disk-partitions + partition-label-predicate + find-partition-by-label + canonicalize-device-spec + + MS_RDONLY + MS_NOSUID + MS_NODEV + MS_NOEXEC + MS_BIND + MS_MOVE + bind-mount + + mount-flags->bit-mask + check-file-system + mount-file-system)) + +;;; Commentary: +;;; +;;; This modules provides tools to deal with disk partitions, and to mount and +;;; check file systems. +;;; +;;; Code: + +;; Linux mount flags, from libc's <sys/mount.h>. +(define MS_RDONLY 1) +(define MS_NOSUID 2) +(define MS_NODEV 4) +(define MS_NOEXEC 8) +(define MS_BIND 4096) +(define MS_MOVE 8192) + +(define (bind-mount source target) + "Bind-mount SOURCE at TARGET." + (mount source target "" MS_BIND)) + +(define-syntax %ext2-endianness + ;; Endianness of ext2 file systems. + (identifier-syntax (endianness little))) + +;; Offset in bytes of interesting parts of an ext2 superblock. See +;; <http://www.nongnu.org/ext2-doc/ext2.html#DEF-SUPERBLOCK>. +;; TODO: Use "packed structs" from Guile-OpenGL or similar. +(define-syntax %ext2-sblock-magic (identifier-syntax 56)) +(define-syntax %ext2-sblock-creator-os (identifier-syntax 72)) +(define-syntax %ext2-sblock-uuid (identifier-syntax 104)) +(define-syntax %ext2-sblock-volume-name (identifier-syntax 120)) + +(define (read-ext2-superblock device) + "Return the raw contents of DEVICE's ext2 superblock as a bytevector, or #f +if DEVICE does not contain an ext2 file system." + (define %ext2-magic + ;; The magic bytes that identify an ext2 file system. + #xef53) + + (define superblock-size + ;; Size of the interesting part of an ext2 superblock. + 264) + + (define block + ;; The superblock contents. + (make-bytevector superblock-size)) + + (call-with-input-file device + (lambda (port) + (seek port 1024 SEEK_SET) + + ;; Note: work around <http://bugs.gnu.org/17466>. + (and (eqv? superblock-size (get-bytevector-n! port block 0 + superblock-size)) + (let ((magic (bytevector-u16-ref block %ext2-sblock-magic + %ext2-endianness))) + (and (= magic %ext2-magic) + block)))))) + +(define (ext2-superblock-uuid sblock) + "Return the UUID of ext2 superblock SBLOCK as a 16-byte bytevector." + (let ((uuid (make-bytevector 16))) + (bytevector-copy! sblock %ext2-sblock-uuid uuid 0 16) + uuid)) + +(define (ext2-superblock-volume-name sblock) + "Return the volume name of SBLOCK as a string of at most 16 characters, or +#f if SBLOCK has no volume name." + (let ((bv (make-bytevector 16))) + (bytevector-copy! sblock %ext2-sblock-volume-name bv 0 16) + + ;; This is a Latin-1, nul-terminated string. + (let ((bytes (take-while (negate zero?) (bytevector->u8-list bv)))) + (if (null? bytes) + #f + (list->string (map integer->char bytes)))))) + +(define (disk-partitions) + "Return the list of device names corresponding to valid disk partitions." + (define (partition? major minor) + (let ((marker (format #f "/sys/dev/block/~a:~a/partition" major minor))) + (catch 'system-error + (lambda () + (not (zero? (call-with-input-file marker read)))) + (lambda args + (if (= ENOENT (system-error-errno args)) + #f + (apply throw args)))))) + + (call-with-input-file "/proc/partitions" + (lambda (port) + ;; Skip the two header lines. + (read-line port) + (read-line port) + + ;; Read each subsequent line, and extract the last space-separated + ;; field. + (let loop ((parts '())) + (let ((line (read-line port))) + (if (eof-object? line) + (reverse parts) + (match (string-tokenize line) + (((= string->number major) (= string->number minor) + blocks name) + (if (partition? major minor) + (loop (cons name parts)) + (loop parts)))))))))) + +(define (partition-label-predicate label) + "Return a procedure that, when applied to a partition name such as \"sda1\", +return #t if that partition's volume name is LABEL." + (lambda (part) + (let* ((device (string-append "/dev/" part)) + (sblock (catch 'system-error + (lambda () + (read-ext2-superblock device)) + (lambda args + ;; When running on the hand-made /dev, + ;; 'disk-partitions' could return partitions for which + ;; we have no /dev node. Handle that gracefully. + (if (= ENOENT (system-error-errno args)) + (begin + (format (current-error-port) + "warning: device '~a' not found~%" + device) + #f) + (apply throw args)))))) + (and sblock + (let ((volume (ext2-superblock-volume-name sblock))) + (and volume + (string=? volume label))))))) + +(define (find-partition-by-label label) + "Return the first partition found whose volume name is LABEL, or #f if none +were found." + (and=> (find (partition-label-predicate label) + (disk-partitions)) + (cut string-append "/dev/" <>))) + +(define* (canonicalize-device-spec spec #:optional (title 'any)) + "Return the device name corresponding to SPEC. TITLE is a symbol, one of +the following: + + • 'device', in which case SPEC is known to designate a device node--e.g., + \"/dev/sda1\"; + • 'label', in which case SPEC is known to designate a partition label--e.g., + \"my-root-part\"; + • 'any', in which case SPEC can be anything. +" + (define max-trials + ;; Number of times we retry partition label resolution, 1 second per + ;; trial. Note: somebody reported a delay of 16 seconds (!) before their + ;; USB key would be detected by the kernel, so we must wait for at least + ;; this long. + 20) + + (define canonical-title + ;; The realm of canonicalization. + (if (eq? title 'any) + (if (string-prefix? "/" spec) + 'device + 'label) + title)) + + (case canonical-title + ((device) + ;; Nothing to do. + spec) + ((label) + ;; Resolve the label. + (let loop ((count 0)) + (let ((device (find-partition-by-label spec))) + (or device + ;; Some devices take a bit of time to appear, most notably USB + ;; storage devices. Thus, wait for the device to appear. + (if (> count max-trials) + (error "failed to resolve partition label" spec) + (begin + (format #t "waiting for partition '~a' to appear...~%" + spec) + (sleep 1) + (loop (+ 1 count)))))))) + ;; TODO: Add support for UUIDs. + (else + (error "unknown device title" title)))) + +(define (check-file-system device type) + "Run a file system check of TYPE on DEVICE." + (define fsck + (string-append "fsck." type)) + + (let ((status (system* fsck "-v" "-p" device))) + (match (status:exit-val status) + (0 + #t) + (1 + (format (current-error-port) "'~a' corrected errors on ~a; continuing~%" + fsck device)) + (2 + (format (current-error-port) "'~a' corrected errors on ~a; rebooting~%" + fsck device) + (sleep 3) + (reboot)) + (code + (format (current-error-port) "'~a' exited with code ~a on ~a; spawning REPL~%" + fsck code device) + (start-repl))))) + +(define (mount-flags->bit-mask flags) + "Return the number suitable for the 'flags' argument of 'mount' that +corresponds to the symbols listed in FLAGS." + (let loop ((flags flags)) + (match flags + (('read-only rest ...) + (logior MS_RDONLY (loop rest))) + (('bind-mount rest ...) + (logior MS_BIND (loop rest))) + (('no-suid rest ...) + (logior MS_NOSUID (loop rest))) + (('no-dev rest ...) + (logior MS_NODEV (loop rest))) + (('no-exec rest ...) + (logior MS_NOEXEC (loop rest))) + (() + 0)))) + +(define* (mount-file-system spec #:key (root "/root")) + "Mount the file system described by SPEC under ROOT. SPEC must have the +form: + + (DEVICE TITLE MOUNT-POINT TYPE (FLAGS ...) OPTIONS CHECK?) + +DEVICE, MOUNT-POINT, and TYPE must be strings; OPTIONS can be a string or #f; +FLAGS must be a list of symbols. CHECK? is a Boolean indicating whether to +run a file system check." + (match spec + ((source title mount-point type (flags ...) options check?) + (let ((source (canonicalize-device-spec source title)) + (mount-point (string-append root "/" mount-point))) + (when check? + (check-file-system source type)) + (mkdir-p mount-point) + (mount source mount-point type (mount-flags->bit-mask flags) + (if options + (string->pointer options) + %null-pointer)) + + ;; Update /etc/mtab. + (mkdir-p (string-append root "/etc")) + (let ((port (open-file (string-append root "/etc/mtab") "a"))) + (format port "~a ~a ~a ~a 0 0~%" + source mount-point type (or options "")) + (close-port port)))))) + +;;; file-systems.scm ends here diff --git a/guix/build/install.scm b/gnu/build/install.scm index 5bafe905de..e16896f8b8 100644 --- a/guix/build/install.scm +++ b/gnu/build/install.scm @@ -16,9 +16,8 @@ ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. -(define-module (guix build install) +(define-module (gnu build install) #:use-module (guix build utils) - #:use-module (guix build install) #:use-module (srfi srfi-26) #:use-module (ice-9 match) #:export (install-grub diff --git a/guix/build/linux-initrd.scm b/gnu/build/linux-boot.scm index d37da9a6ff..21ee58ad50 100644 --- a/guix/build/linux-initrd.scm +++ b/gnu/build/linux-boot.scm @@ -16,35 +16,24 @@ ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. -(define-module (guix build linux-initrd) +(define-module (gnu build linux-boot) #:use-module (rnrs io ports) - #:use-module (rnrs bytevectors) - #:use-module (system foreign) #:use-module (system repl error-handling) #:autoload (system repl repl) (start-repl) #:autoload (system base compile) (compile-file) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) #:use-module (ice-9 match) - #:use-module (ice-9 rdelim) #:use-module (ice-9 ftw) #:use-module (guix build utils) + #:use-module (gnu build file-systems) #:export (mount-essential-file-systems linux-command-line find-long-option make-essential-device-nodes configure-qemu-networking - disk-partitions - partition-label-predicate - find-partition-by-label - canonicalize-device-spec - - mount-flags->bit-mask - check-file-system - mount-file-system bind-mount - load-linux-module* device-number boot-system)) @@ -99,172 +88,6 @@ Return the value associated with OPTION, or #f on failure." (lambda (arg) (substring arg (+ 1 (string-index arg #\=))))))) -(define-syntax %ext2-endianness - ;; Endianness of ext2 file systems. - (identifier-syntax (endianness little))) - -;; Offset in bytes of interesting parts of an ext2 superblock. See -;; <http://www.nongnu.org/ext2-doc/ext2.html#DEF-SUPERBLOCK>. -;; TODO: Use "packed structs" from Guile-OpenGL or similar. -(define-syntax %ext2-sblock-magic (identifier-syntax 56)) -(define-syntax %ext2-sblock-creator-os (identifier-syntax 72)) -(define-syntax %ext2-sblock-uuid (identifier-syntax 104)) -(define-syntax %ext2-sblock-volume-name (identifier-syntax 120)) - -(define (read-ext2-superblock device) - "Return the raw contents of DEVICE's ext2 superblock as a bytevector, or #f -if DEVICE does not contain an ext2 file system." - (define %ext2-magic - ;; The magic bytes that identify an ext2 file system. - #xef53) - - (define superblock-size - ;; Size of the interesting part of an ext2 superblock. - 264) - - (define block - ;; The superblock contents. - (make-bytevector superblock-size)) - - (call-with-input-file device - (lambda (port) - (seek port 1024 SEEK_SET) - - ;; Note: work around <http://bugs.gnu.org/17466>. - (and (eqv? superblock-size (get-bytevector-n! port block 0 - superblock-size)) - (let ((magic (bytevector-u16-ref block %ext2-sblock-magic - %ext2-endianness))) - (and (= magic %ext2-magic) - block)))))) - -(define (ext2-superblock-uuid sblock) - "Return the UUID of ext2 superblock SBLOCK as a 16-byte bytevector." - (let ((uuid (make-bytevector 16))) - (bytevector-copy! sblock %ext2-sblock-uuid uuid 0 16) - uuid)) - -(define (ext2-superblock-volume-name sblock) - "Return the volume name of SBLOCK as a string of at most 16 characters, or -#f if SBLOCK has no volume name." - (let ((bv (make-bytevector 16))) - (bytevector-copy! sblock %ext2-sblock-volume-name bv 0 16) - - ;; This is a Latin-1, nul-terminated string. - (let ((bytes (take-while (negate zero?) (bytevector->u8-list bv)))) - (if (null? bytes) - #f - (list->string (map integer->char bytes)))))) - -(define (disk-partitions) - "Return the list of device names corresponding to valid disk partitions." - (define (partition? major minor) - (let ((marker (format #f "/sys/dev/block/~a:~a/partition" major minor))) - (catch 'system-error - (lambda () - (not (zero? (call-with-input-file marker read)))) - (lambda args - (if (= ENOENT (system-error-errno args)) - #f - (apply throw args)))))) - - (call-with-input-file "/proc/partitions" - (lambda (port) - ;; Skip the two header lines. - (read-line port) - (read-line port) - - ;; Read each subsequent line, and extract the last space-separated - ;; field. - (let loop ((parts '())) - (let ((line (read-line port))) - (if (eof-object? line) - (reverse parts) - (match (string-tokenize line) - (((= string->number major) (= string->number minor) - blocks name) - (if (partition? major minor) - (loop (cons name parts)) - (loop parts)))))))))) - -(define (partition-label-predicate label) - "Return a procedure that, when applied to a partition name such as \"sda1\", -return #t if that partition's volume name is LABEL." - (lambda (part) - (let* ((device (string-append "/dev/" part)) - (sblock (catch 'system-error - (lambda () - (read-ext2-superblock device)) - (lambda args - ;; When running on the hand-made /dev, - ;; 'disk-partitions' could return partitions for which - ;; we have no /dev node. Handle that gracefully. - (if (= ENOENT (system-error-errno args)) - (begin - (format (current-error-port) - "warning: device '~a' not found~%" - device) - #f) - (apply throw args)))))) - (and sblock - (let ((volume (ext2-superblock-volume-name sblock))) - (and volume - (string=? volume label))))))) - -(define (find-partition-by-label label) - "Return the first partition found whose volume name is LABEL, or #f if none -were found." - (and=> (find (partition-label-predicate label) - (disk-partitions)) - (cut string-append "/dev/" <>))) - -(define* (canonicalize-device-spec spec #:optional (title 'any)) - "Return the device name corresponding to SPEC. TITLE is a symbol, one of -the following: - - • 'device', in which case SPEC is known to designate a device node--e.g., - \"/dev/sda1\"; - • 'label', in which case SPEC is known to designate a partition label--e.g., - \"my-root-part\"; - • 'any', in which case SPEC can be anything. -" - (define max-trials - ;; Number of times we retry partition label resolution, 1 second per - ;; trial. Note: somebody reported a delay of 16 seconds (!) before their - ;; USB key would be detected by the kernel, so we must wait for at least - ;; this long. - 20) - - (define canonical-title - ;; The realm of canonicalization. - (if (eq? title 'any) - (if (string-prefix? "/" spec) - 'device - 'label) - title)) - - (case canonical-title - ((device) - ;; Nothing to do. - spec) - ((label) - ;; Resolve the label. - (let loop ((count 0)) - (let ((device (find-partition-by-label spec))) - (or device - ;; Some devices take a bit of time to appear, most notably USB - ;; storage devices. Thus, wait for the device to appear. - (if (> count max-trials) - (error "failed to resolve partition label" spec) - (begin - (format #t "waiting for partition '~a' to appear...~%" - spec) - (sleep 1) - (loop (+ 1 count)))))))) - ;; TODO: Add support for UUIDs. - (else - (error "unknown device title" title)))) - (define* (make-disk-device-nodes base major #:optional (minor 0)) "Make the block device nodes around BASE (something like \"/root/dev/sda\") with the given MAJOR number, starting with MINOR." @@ -395,18 +218,6 @@ networking values.) Return #t if INTERFACE is up, #f otherwise." (logand (network-interface-flags sock interface) IFF_UP))) -;; Linux mount flags, from libc's <sys/mount.h>. -(define MS_RDONLY 1) -(define MS_NOSUID 2) -(define MS_NODEV 4) -(define MS_NOEXEC 8) -(define MS_BIND 4096) -(define MS_MOVE 8192) - -(define (bind-mount source target) - "Bind-mount SOURCE at TARGET." - (mount source target "" MS_BIND)) - (define (load-linux-module* file) "Load Linux module from FILE, the name of a `.ko' file." (define (slurp module) @@ -479,74 +290,6 @@ UNIONFS." (copy-file "/proc/mounts" "/root/etc/mtab")) -(define (check-file-system device type) - "Run a file system check of TYPE on DEVICE." - (define fsck - (string-append "fsck." type)) - - (let ((status (system* fsck "-v" "-p" device))) - (match (status:exit-val status) - (0 - #t) - (1 - (format (current-error-port) "'~a' corrected errors on ~a; continuing~%" - fsck device)) - (2 - (format (current-error-port) "'~a' corrected errors on ~a; rebooting~%" - fsck device) - (sleep 3) - (reboot)) - (code - (format (current-error-port) "'~a' exited with code ~a on ~a; spawning REPL~%" - fsck code device) - (start-repl))))) - -(define (mount-flags->bit-mask flags) - "Return the number suitable for the 'flags' argument of 'mount' that -corresponds to the symbols listed in FLAGS." - (let loop ((flags flags)) - (match flags - (('read-only rest ...) - (logior MS_RDONLY (loop rest))) - (('bind-mount rest ...) - (logior MS_BIND (loop rest))) - (('no-suid rest ...) - (logior MS_NOSUID (loop rest))) - (('no-dev rest ...) - (logior MS_NODEV (loop rest))) - (('no-exec rest ...) - (logior MS_NOEXEC (loop rest))) - (() - 0)))) - -(define* (mount-file-system spec #:key (root "/root")) - "Mount the file system described by SPEC under ROOT. SPEC must have the -form: - - (DEVICE TITLE MOUNT-POINT TYPE (FLAGS ...) OPTIONS CHECK?) - -DEVICE, MOUNT-POINT, and TYPE must be strings; OPTIONS can be a string or #f; -FLAGS must be a list of symbols. CHECK? is a Boolean indicating whether to -run a file system check." - (match spec - ((source title mount-point type (flags ...) options check?) - (let ((source (canonicalize-device-spec source title)) - (mount-point (string-append root "/" mount-point))) - (when check? - (check-file-system source type)) - (mkdir-p mount-point) - (mount source mount-point type (mount-flags->bit-mask flags) - (if options - (string->pointer options) - %null-pointer)) - - ;; Update /etc/mtab. - (mkdir-p (string-append root "/etc")) - (let ((port (open-file (string-append root "/etc/mtab") "a"))) - (format port "~a ~a ~a ~a 0 0~%" - source mount-point type (or options "")) - (close-port port)))))) - (define (switch-root root) "Switch to ROOT as the root file system, in a way similar to what util-linux' switch_root(8) does." diff --git a/gnu/build/linux-initrd.scm b/gnu/build/linux-initrd.scm new file mode 100644 index 0000000000..bf60137e8f --- /dev/null +++ b/gnu/build/linux-initrd.scm @@ -0,0 +1,72 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2013, 2014 Ludovic Courtès <ludo@gnu.org> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu build linux-initrd) + #:use-module (ice-9 popen) + #:use-module (ice-9 ftw) + #:export (write-cpio-archive)) + +;;; Commentary: +;;; +;;; Tools to create Linux initial RAM disks ("initrds"). Initrds are +;;; essentially gzipped cpio archives, with a '/init' executable that the +;;; kernel runs at boot time. +;;; +;;; Code: + +(define* (write-cpio-archive output directory + #:key + (compress? #t) + (cpio "cpio") (gzip "gzip")) + "Write a cpio archive containing DIRECTORY to file OUTPUT, using CPIO. When +COMPRESS? is true, compress it using GZIP. On success, return OUTPUT." + + ;; Note: don't use '--no-absolute-filenames' since that strips leading + ;; slashes from symlink targets. + (let ((pipe (open-pipe* OPEN_WRITE cpio "-o" "-O" output + "-H" "newc" "--null"))) + (define (print0 file) + (format pipe "~a\0" file)) + + ;; Note: as per `ramfs-rootfs-initramfs.txt', always add directory entries + ;; before the files that are inside of it: "The Linux kernel cpio + ;; extractor won't create files in a directory that doesn't exist, so the + ;; directory entries must go before the files that go in those + ;; directories." + + ;; XXX: Use a deterministic order. + (file-system-fold (const #t) + (lambda (file stat result) ; leaf + (print0 file)) + (lambda (dir stat result) ; down + (unless (string=? dir directory) + (print0 dir))) + (const #f) ; up + (const #f) ; skip + (const #f) + #f + directory) + + (and (zero? (close-pipe pipe)) + (or (not compress?) + (and (zero? (system* gzip "--best" output)) + (rename-file (string-append output ".gz") + output)) + output)))) + +;;; linux-initrd.scm ends here diff --git a/guix/build/vm.scm b/gnu/build/vm.scm index 11e05f70be..27ccd047b7 100644 --- a/guix/build/vm.scm +++ b/gnu/build/vm.scm @@ -16,14 +16,13 @@ ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. -(define-module (guix build vm) +(define-module (gnu build vm) #:use-module (guix build utils) - #:use-module (guix build linux-initrd) - #:use-module (guix build install) + #:use-module (guix build store-copy) + #:use-module (gnu build linux-boot) + #:use-module (gnu build install) #:use-module (ice-9 match) #:use-module (ice-9 regex) - #:use-module (ice-9 rdelim) - #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) #:export (qemu-command load-in-linux-vm @@ -111,20 +110,6 @@ the #:references-graphs parameter of 'derivation'." (mkdir output) (copy-recursively "xchg" output)))) -(define (read-reference-graph port) - "Return a list of store paths from the reference graph at PORT. -The data at PORT is the format produced by #:references-graphs." - (let loop ((line (read-line port)) - (result '())) - (cond ((eof-object? line) - (delete-duplicates result)) - ((string-prefix? "/" line) - (loop (read-line port) - (cons line result))) - (else - (loop (read-line port) - result))))) - (define* (initialize-partition-table device partition-size #:key (label-type "msdos") @@ -140,26 +125,6 @@ success." (format #f "~aB" partition-size))) (error "failed to create partition table"))) -(define* (populate-store reference-graphs target) - "Populate the store under directory TARGET with the items specified in -REFERENCE-GRAPHS, a list of reference-graph files." - (define store - (string-append target (%store-directory))) - - (define (things-to-copy) - ;; Return the list of store files to copy to the image. - (define (graph-from-file file) - (call-with-input-file file read-reference-graph)) - - (delete-duplicates (append-map graph-from-file reference-graphs))) - - (mkdir-p store) - (chmod store #o1775) - (for-each (lambda (thing) - (copy-recursively thing - (string-append target thing))) - (things-to-copy))) - (define MS_BIND 4096) ; <sys/mounts.h> again! (define* (format-partition partition type diff --git a/gnu/packages.scm b/gnu/packages.scm index 14ad75561c..26d87c6b16 100644 --- a/gnu/packages.scm +++ b/gnu/packages.scm @@ -28,6 +28,7 @@ #:use-module (ice-9 vlist) #:use-module (ice-9 match) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) #:use-module (srfi srfi-39) #:export (search-patch @@ -45,7 +46,9 @@ package-transitive-dependents package-covering-dependents - check-package-freshness)) + check-package-freshness + + specification->package)) ;;; Commentary: ;;; @@ -326,3 +329,24 @@ but ~a is available upstream~%") (case key ((getaddrinfo-error ftp-error) #f) (else (apply throw key args)))))) + +(define (specification->package spec) + "Return a package matching SPEC. SPEC may be a package name, or a package +name followed by a hyphen and a version number. If the version number is not +present, return the preferred newest version." + (let-values (((name version) + (package-name->name+version spec))) + (match (find-best-packages-by-name name version) + ((p) ; one match + p) + ((p x ...) ; several matches + (warning (_ "ambiguous package specification `~a'~%") spec) + (warning (_ "choosing ~a from ~a~%") + (package-full-name p) + (location->string (package-location p))) + p) + (_ ; no matches + (if version + (leave (_ "~A: package not found for version ~a~%") + name version) + (leave (_ "~A: unknown package~%") name)))))) diff --git a/gnu/packages/avahi.scm b/gnu/packages/avahi.scm index d3a92844c2..3f2fdffc28 100644 --- a/gnu/packages/avahi.scm +++ b/gnu/packages/avahi.scm @@ -68,3 +68,25 @@ network. It is an implementation of the mDNS (for \"Multicast DNS\") and DNS-SD (for \"DNS-Based Service Discovery\") protocols.") (license lgpl2.1+))) + +(define-public nss-mdns + (package + (name "nss-mdns") + (version "0.10") + (source (origin + (method url-fetch) + (uri (string-append + "http://0pointer.de/lennart/projects/nss-mdns/nss-mdns-" + version ".tar.gz")) + (sha256 + (base32 + "0vgs6j0qsl0mwzh5a0m0bykr7x6bx79vnbyn0r3q289rghp3qs0y")))) + (build-system gnu-build-system) + (home-page "http://0pointer.de/lennart/projects/nss-mdns/") + (synopsis "The mDNS Name Service Switch (NSS) plug-in") + (description + "'nss-mdns' is a plug-in for the Name Service Switch (NSS) functionality +of the GNU C Library, providing host name resolution via Multicast DNS (mDNS). +It allows for name resolution by programs in the ad-hoc mDNS domain +'.local'.") + (license lgpl2.1+))) diff --git a/gnu/packages/commencement.scm b/gnu/packages/commencement.scm index a07ed85946..79c22a5974 100644 --- a/gnu/packages/commencement.scm +++ b/gnu/packages/commencement.scm @@ -744,6 +744,10 @@ COREUTILS-FINAL vs. COREUTILS, etc." (union-build (assoc-ref %outputs "debug") (list (assoc-ref %build-inputs "libc-debug")))))) + + (native-search-paths (package-native-search-paths gcc)) + (search-paths (package-search-paths gcc)) + (license (package-license gcc)) (synopsis "Complete GCC tool chain for C/C++ development") (description diff --git a/gnu/packages/datamash.scm b/gnu/packages/datamash.scm index 543cd038d3..ccf3729b0b 100644 --- a/gnu/packages/datamash.scm +++ b/gnu/packages/datamash.scm @@ -27,7 +27,7 @@ (define-public datamash (package (name "datamash") - (version "1.0.5") + (version "1.0.6") (source (origin (method url-fetch) @@ -35,7 +35,7 @@ version ".tar.gz")) (sha256 (base32 - "0f4rbbhl18fb851npza9cl8biynzr081f37ih2xsbvjlyrxhnz6b")))) + "0621a1xyf8gvcks0vb26g72dm4zh1s6y3a4cc5nnnl5m8mfc4m01")))) (native-inputs `(("which" ,which) ;for tests ("perl" ,perl))) ;for help2man @@ -45,4 +45,4 @@ (description "Perform basic numeric, textual and statistical operations on plain text files. Designed to work within standard pipelines without additional code.") - (license gpl3))) + (license gpl3+))) diff --git a/gnu/packages/gnutls.scm b/gnu/packages/gnutls.scm index 9b91a311a3..89fc86d6ad 100644 --- a/gnu/packages/gnutls.scm +++ b/gnu/packages/gnutls.scm @@ -36,7 +36,7 @@ (define-public libtasn1 (package (name "libtasn1") - (version "3.6") + (version "4.1") (source (origin (method url-fetch) @@ -44,7 +44,7 @@ version ".tar.gz")) (sha256 (base32 - "0c547qa1vfk1x2jzgjhf65izf4sfi86c6g46q7779g4aldk4gqqr")))) + "00gkyppzw6fqi5mnc3d8paf7bp6nfhi9213481awy07sviqnbvk0")))) (build-system gnu-build-system) (native-inputs `(("perl" ,perl) diff --git a/gnu/packages/linux.scm b/gnu/packages/linux.scm index 02a0ae36b5..442f80103a 100644 --- a/gnu/packages/linux.scm +++ b/gnu/packages/linux.scm @@ -189,7 +189,7 @@ for SYSTEM, or #f if there is no configuration for SYSTEM." #f))) (define-public linux-libre - (let* ((version "3.16.1") + (let* ((version "3.16.2") (build-phase '(lambda* (#:key system inputs #:allow-other-keys #:rest args) ;; Apply the neat patch. @@ -262,7 +262,7 @@ for SYSTEM, or #f if there is no configuration for SYSTEM." (uri (linux-libre-urls version)) (sha256 (base32 - "1x4y0017l4ndcab4smky2wx0n86r3wyks2r8yyp19ia9ccnl98mf")))) + "1p08cqy6427yi808fpbwbb4zbwhnkibj2i1wbrfa5rjhd4vnnffz")))) (build-system gnu-build-system) (native-inputs `(("perl" ,perl) ("bc" ,bc) @@ -1092,7 +1092,16 @@ UnionFS-FUSE additionally supports copy-on-write.") libs " dl)")))))) (arguments '(#:tests? #f - #:configure-flags '("-DCMAKE_EXE_LINKER_FLAGS=-static"))) + #:configure-flags '("-DCMAKE_EXE_LINKER_FLAGS=-static") + #:phases (alist-cons-after + 'install 'post-install + (lambda* (#:key outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out")) + (exe (string-append out "/bin/unionfs"))) + ;; By default, 'unionfs' keeps references to + ;; $glibc/share/locale and similar stuff. Remove them. + (remove-store-references exe))) + %standard-phases))) (inputs `(("fuse" ,fuse-static))))) (define-public sshfs-fuse diff --git a/gnu/packages/man.scm b/gnu/packages/man.scm index 2e319a897e..1dbc4c96d5 100644 --- a/gnu/packages/man.scm +++ b/gnu/packages/man.scm @@ -145,7 +145,7 @@ Linux kernel and C library interfaces employed by user-space programs.") (define-public help2man (package (name "help2man") - (version "1.45.1") + (version "1.46.1") (source (origin (method url-fetch) @@ -153,7 +153,7 @@ Linux kernel and C library interfaces employed by user-space programs.") version ".tar.xz")) (sha256 (base32 - "1hk7ciqinq7djdb7s94y3jxh06rp8i93bpjmg4r40cniws8wf3y7")))) + "0iqwb3qirl7rp1wwpbh01q89qxvi4h3bc73wi03av6hl4sh05z9x")))) (build-system gnu-build-system) (arguments `(;; There's no `check' target. #:tests? #f)) diff --git a/gnu/packages/nvi.scm b/gnu/packages/nvi.scm new file mode 100644 index 0000000000..3037e5bafc --- /dev/null +++ b/gnu/packages/nvi.scm @@ -0,0 +1,68 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 Marek Benc <merkur32@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu packages nvi) + #:use-module (gnu packages) + #:use-module (gnu packages bdb) + #:use-module (gnu packages ncurses) + #:use-module (guix packages) + #:use-module (guix download) + #:use-module (guix build-system gnu) + #:use-module (guix licenses)) + +(define-public nvi + (package + (name "nvi") + (version "1.81.6") + (source + (origin + (method url-fetch) + (uri ;; sites.google.coma/bostic.com/keithbostic/vi is stale. + (string-append "http://harrier.slackbuilds.org/misc/nvi-" version + ".tar.bz2")) + (sha256 + (base32 "0nbbs1inyrqds0ywn3ln5slv54v5zraq7lszkg8nsavv4kivhh9l")) + (patches (list (search-patch "nvi-assume-preserve-path.patch"))) + (snippet + ;; Create a wrapper for the configure script, make it executable. + '(let ((conf-wrap (open-output-file "configure"))) + (display "#!/bin/sh" conf-wrap) + (newline conf-wrap) + (display + "../nvi-1.81.6/dist/configure --srcdir=../nvi-1.81.6/dist $@" + conf-wrap) + (newline conf-wrap) + (close-output-port conf-wrap) + (chmod "configure" #o0755))))) + + (build-system gnu-build-system) + (arguments + `(#:out-of-source? #t)) + (inputs + `(("bdb" ,bdb) + ("ncurses" ,ncurses))) + (synopsis "The Berkeley Vi Editor") + (description + "Vi is the original screen based text editor for Unix systems. It is +considered the standard text editor, and is available on almost all Unix +systems. Nvi is intended as a \"bug-for-bug compatible\" clone of the original +BSD vi editor. As such, it doesn't have a lot of snazzy features as do some +of the other vi clones such as elvis and vim. However, if all you want is vi, +this is the one to get.") + (home-page "https://sites.google.com/a/bostic.com/keithbostic/vi") + (license bsd-3))) diff --git a/gnu/packages/parallel.scm b/gnu/packages/parallel.scm index a4755e043d..92f15adbc4 100644 --- a/gnu/packages/parallel.scm +++ b/gnu/packages/parallel.scm @@ -27,7 +27,7 @@ (define-public parallel (package (name "parallel") - (version "20140722") + (version "20140822") (source (origin (method url-fetch) @@ -35,7 +35,7 @@ version ".tar.bz2")) (sha256 (base32 - "165vf8hpl47z38aswsll1284l8xa9a8jwx3a3d2rzshm9yzbiq5n")))) + "1m3z77fnc2bfissq642p44xgw1jyp00wiwk1slhqj8bipicnl54a")))) (build-system gnu-build-system) (inputs `(("perl" ,perl))) (home-page "http://www.gnu.org/software/parallel/") diff --git a/gnu/packages/patches/nvi-assume-preserve-path.patch b/gnu/packages/patches/nvi-assume-preserve-path.patch new file mode 100644 index 0000000000..18a8f68cca --- /dev/null +++ b/gnu/packages/patches/nvi-assume-preserve-path.patch @@ -0,0 +1,30 @@ +Make configure assume nvi can store backups in /var/tmp. + +--- a/dist/configure 2014-09-01 14:46:01.075778095 +0200 ++++ b/dist/configure 2014-09-01 14:52:08.411790122 +0200 +@@ -21319,23 +21319,8 @@ + if test "${vi_cv_path_preserve+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 + else +- dirlist="/var/preserve /var/tmp /usr/tmp" +- vi_cv_path_preserve=no +- for i in $dirlist; do +- if test -d $i/vi.recover; then +- vi_cv_path_preserve=$i/vi.recover +- break; +- fi +- done +- if test "$vi_cv_path_preserve" = no; then +- for i in $dirlist; do +- if test -d $i -a -w $i; then +- vi_cv_path_preserve=$i/vi.recover +- break; +- fi +- done +- +- fi ++# Assume /var/tmp ++ vi_cv_path_preserve=/var/tmp/vi.recover + fi + + if test "$vi_cv_path_preserve" = no; then diff --git a/gnu/packages/postgresql.scm b/gnu/packages/postgresql.scm new file mode 100644 index 0000000000..d6b505f6a2 --- /dev/null +++ b/gnu/packages/postgresql.scm @@ -0,0 +1,52 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 David Thompson <davet@gnu.org> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (gnu packages postgresql) + #:use-module ((guix licenses) #:select (x11-style)) + #:use-module (guix packages) + #:use-module (guix download) + #:use-module (guix build-system gnu) + #:use-module (gnu packages) + #:use-module (gnu packages compression) + #:use-module (gnu packages readline)) + +(define-public postgresql + (package + (name "postgresql") + (version "9.3.5") + (source (origin + (method url-fetch) + (uri (string-append "http://ftp.postgresql.org/pub/source/v" + version "/postgresql-" version ".tar.gz")) + (sha256 + (base32 + "08kga00izykgvnx7hn995wc4zjqslspapaa8z63045p1ya14mr4g")))) + (build-system gnu-build-system) + (inputs + `(("readline" ,readline) + ("zlib" ,zlib))) + (home-page "http://www.postgresql.org/") + (synopsis "Powerful object-relational database system") + (description + "PostgreSQL is a powerful object-relational database system. It is fully +ACID compliant, has full support for foreign keys, joins, views, triggers, and +stored procedures (in multiple languages). It includes most SQL:2008 data +types, including INTEGER, NUMERIC, BOOLEAN, CHAR, VARCHAR, DATE, INTERVAL, and +TIMESTAMP. It also supports storage of binary large objects, including +pictures, sounds, or video.") + (license (x11-style "file://COPYRIGHT")))) diff --git a/gnu/packages/python.scm b/gnu/packages/python.scm index 641a5dfc67..81b6c956a9 100644 --- a/gnu/packages/python.scm +++ b/gnu/packages/python.scm @@ -22,8 +22,8 @@ (define-module (gnu packages python) #:use-module ((guix licenses) - #:select (asl2.0 bsd-3 bsd-style cc0 expat x11 x11-style - gpl2 gpl2+ lgpl2.1+ + #:select (asl2.0 bsd-3 bsd-2 bsd-style cc0 expat x11 x11-style + gpl2 gpl2+ gpl3+ lgpl2.0+ lgpl2.1+ psfl public-domain)) #:use-module ((guix licenses) #:select (zlib) #:renamer (symbol-prefix-proc 'license:)) @@ -939,6 +939,448 @@ In short, SCons is an easier, more reliable and faster way to build software.") (license x11))) +(define-public python-extras + (package + (name "python-extras") + (version "0.0.3") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/e/extras/extras-" + version ".tar.gz")) + (sha256 + (base32 + "1h7zx4dfyclalg0fqnfjijpn0f793a9mx8sy3b27gd31nr6dhq3s")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments + ;; error in setup.cfg: command 'test' has no such option 'buffer' + '(#:tests? #f)) + (home-page "https://github.com/testing-cabal/extras") + (synopsis "Useful extensions to the Python standard library") + (description + "Extras is a set of extensions to the Python standard library.") + (license expat))) + +(define-public python2-extras + (package-with-python2 python-extras)) + +(define-public python-mimeparse + (package + (name "python-mimeparse") + (version "0.1.4") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/p/python-mimeparse/python-mimeparse-" + version ".tar.gz")) + (sha256 + (base32 + "1hyxg09kaj02ri0rmwjqi86wk4nd1akvv7n0dx77azz76wga4s9w")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments + '(#:tests? #f)) ; no setup.py test command + (home-page + "https://github.com/dbtsai/python-mimeparse") + (synopsis "Python library for parsing MIME types.") + (description + "Mimeparse provides basic functions for parsing MIME type names and +matching them against a list of media-ranges.") + (license expat))) + +(define-public python2-mimeparse + (package-with-python2 python-mimeparse)) + +(define-public python-nose + (package + (name "python-nose") + (version "1.3.4") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/n/nose/nose-" + version ".tar.gz")) + (sha256 + (base32 + "00qymfgwg4iam4xi0w9bnv7lcb3fypq1hzfafzgs1rfmwaj67g3n")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments + '(#:tests? #f)) ; FIXME: test suite fails + (home-page "http://readthedocs.org/docs/nose/") + (synopsis "Python testing library") + (description + "Nose extends the unittest library to make testing easier.") + (license lgpl2.0+))) + +(define-public python2-nose + (package-with-python2 python-nose)) + +(define-public python-unittest2 + (package + (name "python-unittest2") + (version "0.5.1") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/u/unittest2py3k/unittest2py3k-" + version ".tar.gz")) + (sha256 + (base32 + "00yl6lskygcrddx5zspkhr0ibgvpknl4678kkm6s626539grq93q")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "http://pypi.python.org/pypi/unittest2") + (synopsis "Python unit testing library") + (description + "Unittest2 is a replacement for the unittest module in the Python +standard library.") + (license psfl))) + +(define-public python2-unittest2 + (package (inherit python-unittest2) + (name "python2-unittest2") + (version "0.5.1") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/u/unittest2/unittest2-" + version ".tar.gz")) + (sha256 + (base32 + "0wbs4i4x3x7klr3v35ss6p9mcqz883i1xgcpkhvl7n2lyv6yhpda")))) + (inputs + `(("python2-setuptools" ,python-setuptools))) + (arguments + `(#:python ,python-2 + #:tests? #f)))) ; no setup.py test command + +(define-public python-py + (package + (name "python-py") + (version "1.4.23") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/p/py/py-" + version ".tar.gz")) + (sha256 + (base32 + "1jkhffpai419v5rickm2vz86p9bkg3b3kcm2k4bi5wfajhw2m3xs")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "http://pylib.readthedocs.org/") + (synopsis "Python library for parsing, I/O, instrospection, and logging") + (description + "Py is a Python library for file name parsing, .ini file parsing, I/O, +code introspection, and logging.") + (license expat))) + +(define-public python2-py + (package-with-python2 python-py)) + +(define-public python-pytest + (package + (name "python-pytest") + (version "2.6.1") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/p/pytest/pytest-" + version ".tar.gz")) + (sha256 + (base32 + "0g2w4p0n42wvz8rq4k6gnzpkakgz3g8sfanxk8jrsra9675snkcr")) + (modules '((guix build utils))) + (snippet + ;; One of the tests involves the /usr directory, so it fails. + '(substitute* "testing/test_argcomplete.py" + (("def test_remove_dir_prefix\\(self\\):") + "@pytest.mark.xfail\n def test_remove_dir_prefix(self):"))))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-py" ,python-py) + ("python-nose" ,python-nose) + ("python-mock" ,python-mock))) + (home-page "http://pytest.org") + (synopsis "Python testing library") + (description + "Pytest is a testing tool that provides auto-discovery of test modules +and functions, detailed info on failing assert statements, modular fixtures, +and many external plugins.") + (license expat))) + +(define-public python2-pytest + (package-with-python2 python-pytest)) + +(define-public python-scripttest + (package + (name "python-scripttest") + (version "1.3") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/s/scripttest/scripttest-" + version ".tar.gz")) + (sha256 + (base32 + "0f4w84k8ck82syys7yg9maz93mqzc8p5ymis941x034v44jzq74m")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-pytest" ,python-pytest))) + (home-page "http://pythonpaste.org/scripttest/") + (synopsis "Python library to test command-line scripts") + (description "Scripttest is a Python helper library for testing +interactive command-line applications. With it you can run a script in a +subprocess and see the output as well as any file modifications.") + (license expat))) + +(define-public python2-scripttest + (package-with-python2 python-scripttest)) + +(define-public python-testtools + (package + (name "python-testtools") + (version "1.0.0") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/t/testtools/testtools-" + version ".tar.gz")) + (sha256 + (base32 + "1dyml28ykpl5jb9khdmcdvhy1cxqingys6qvj2k04fzlaj6z3bbx")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-mimeparse" ,python-mimeparse) + ("python-extras" ,python-extras))) + (home-page "https://github.com/testing-cabal/testtools") + (synopsis + "Extensions to the Python standard library unit testing framework") + (description + "Testtools extends the Python standard library unit testing framework to +provide matchers, more debugging information, and cross-Python +compatibility.") + (license psfl))) + +(define-public python2-testtools + (package-with-python2 python-testtools)) + +(define-public python-testscenarios + (package + (name "python-testscenarios") + (version "0.4") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/t/testscenarios/testscenarios-" + version ".tar.gz")) + (sha256 + (base32 + "1671jvrvqlmbnc42j7pc5y6vc37q44aiwrq0zic652pxyy2fxvjg")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-testtools" ,python-testtools) + ("python-mimeparse" ,python-mimeparse))) + (home-page "https://launchpad.net/testscenarios") + (synopsis "Pyunit extension for dependency injection") + (description + "Testscenarios provides clean dependency injection for Python unittest +style tests.") + (license (list bsd-3 asl2.0)))) ; at the user's option + +(define-public python2-testscenarios + (package-with-python2 python-testscenarios)) + +(define-public python-testresources + (package + (name "python-testresources") + (version "0.2.7") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/t/testresources/testresources-" + version ".tar.gz")) + (sha256 + (base32 + "0cbj3plbllyz42c4b5xxgwaa7mml54lakslrn4kkhinxhdri22md")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "https://launchpad.net/testresources") + (synopsis + "Pyunit extension for managing test resources") + (description + "Testresources is an extension to Python's unittest to allow declarative +use of resources by test cases.") + (license (list bsd-3 asl2.0)))) ; at the user's option + +(define-public python2-testresources + (package-with-python2 python-testresources)) + +(define-public python-subunit + (package + (name "python-subunit") + (version "0.0.21") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/p/python-subunit/python-subunit-" + version ".tar.gz")) + (sha256 + (base32 + "1nkw9wfbvizmpajbj3in8ns07g7lwkiv8hip14jjlwk3cacls6jv")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-testtools" ,python-testtools) + ("python-mimeparse" ,python-mimeparse) + ("python-testscenarios" ,python-testscenarios))) + (home-page "http://launchpad.net/subunit") + (synopsis "Python implementation of the subunit protocol") + (description + "Python-subunit is a Python implementation of the subunit test streaming +protocol.") + (license (list bsd-3 asl2.0)))) ; at the user's option + +(define-public python2-subunit + (package-with-python2 python-subunit)) + +(define-public python-fixtures + (package + (name "python-fixtures") + (version "0.3.16") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/f/fixtures/fixtures-" + version ".tar.gz")) + (sha256 + (base32 + "0x9r2gwilcig5g54k60bxzg96zabizq1855lrprlb4zckalp9asc")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments + '(#:tests? #f)) ; no setup.py test command + (home-page "https://launchpad.net/python-fixtures") + (synopsis "Python test fixture library") + (description + "Fixtures provides a way to create reusable state, useful when writing +Python tests.") + (license (list bsd-3 asl2.0)))) ; at user's option + +(define-public python2-fixtures + (package-with-python2 python-fixtures)) + +(define-public python-testrepository + (package + (name "python-testrepository") + (version "0.0.20") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/t/testrepository/testrepository-" + version ".tar.gz")) + (sha256 + (base32 + "1ssqb07c277010i6gzzkbdd46gd9mrj0bi0i8vn560n2k2y4j93m")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-testtools" ,python-testtools) + ("python-subunit" ,python-subunit) + ("python-fixtures" ,python-fixtures) + ("python-mimeparse" ,python-mimeparse))) + (home-page "https://launchpad.net/testrepository") + (synopsis "Database for Python test results") + (description "Testrepository provides a database of test results which can +be used as part of a developer's workflow to check things such as what tests +have failed since the last commit or what tests are currently failing.") + (license (list bsd-3 asl2.0)))) ; at user's option + +(define-public python2-testrepository + (package-with-python2 python-testrepository)) + +(define-public python-coverage + (package + (name "python-coverage") + (version "3.7.1") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/c/coverage/coverage-" + version ".tar.gz")) + (sha256 + (base32 + "0knlbq79g2ww6xzsyknj9rirrgrgc983dpa2d9nkdf31mb2a3bni")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "http://nedbatchelder.com/code/coverage") + (synopsis "Code coverage measurement for Python") + (description + "Coverage measures code coverage, typically during test execution. It +uses the code analysis tools and tracing hooks provided in the Python standard +library to determine which lines are executable, and which have been +executed.") + (license bsd-3))) + +(define-public python2-coverage + (package-with-python2 python-coverage)) + +(define-public python-discover + (package + (name "python-discover") + (version "0.4.0") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/d/discover/discover-" + version ".tar.gz")) + (sha256 + (base32 + "0y8d0zwiqar51kxj8lzmkvwc3b8kazb04gk5zcb4nzg5k68zmhq5")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "http://pypi.python.org/pypi/discover/") + (synopsis + "Python test discovery for unittest") + (description + "Discover provides test discovery for unittest, a feature that has been +backported from Python 2.7 for Python 2.4+") + (license bsd-3))) + +(define-public python2-discover + (package-with-python2 python-discover)) + (define-public behave (package (name "behave") @@ -968,3 +1410,427 @@ technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. Behave uses tests written in a natural language style, backed up by Python code.") (license x11))) + +(define-public python-exif-read + (package + (name "python-exif-read") + (version "1.4.2") + (source (origin + (method url-fetch) + (uri + (string-append + "https://pypi.python.org/packages/source/E/ExifRead/ExifRead-" + version ".tar.gz")) + (sha256 + (base32 + "17c627gcdmyc05hz4zk8qs4pjgw6rc68qzjzgz8gh1cmpsd7acf1")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments `(#:tests? #f)) ; no tests + (home-page "https://github.com/ianare/exif-py") + (synopsis "Python library to extract EXIF data from image files") + (description + "ExifRead is a Python library to extract EXIF data from tiff and jpeg +files.") + (license bsd-3))) + +(define-public python2-exif-read + (package-with-python2 python-exif-read)) + +(define-public python-pyld + (package + (name "python-pyld") + (version "0.6.0") + (source (origin + (method url-fetch) + (uri + (string-append + "https://pypi.python.org/packages/source/P/PyLD/PyLD-" + version ".tar.gz")) + (sha256 + (base32 + "1l9ymj85fsvayqplinzpk0kyiq6m74ps9xd3a9fhlxfn1rldf8x8")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments `(#:tests? #f)) ; no tests + (home-page "http://github.com/digitalbazaar/pyld") + (synopsis "Python implementation of the JSON-LD specification") + (description + "PyLD is an implementation of the JSON-LD specification.") + (license bsd-3))) + +(define-public python2-pyld + (package-with-python2 python-pyld)) + +(define-public python-certifi + (package + (name "python-certifi") + (version "14.05.14") + (source (origin + (method url-fetch) + (uri + (string-append + "https://pypi.python.org/packages/source/c/certifi/certifi-" + version ".tar.gz")) + (sha256 + (base32 + "0s8vxzfz6s4m6fvxc7z25k9j35w0rh6jkw3wwcd1az1mssncn6qy")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments `(#:tests? #f)) ; no tests + (home-page "http://python-requests.org/") + (synopsis "Python CA certificate bundle") + (description + "Certifi is a Python library that contains a CA certificate bundle, which +is used by the Requests library to verify HTTPS requests.") + (license asl2.0))) + +(define-public python2-certifi + (package-with-python2 python-certifi)) + +(define-public python2-requests + (package + (name "python2-requests") + (version "2.4.0") + (source (origin + (method url-fetch) + (uri + (string-append + "https://pypi.python.org/packages/source/r/requests/requests-" + version ".tar.gz")) + (sha256 + (base32 + "0gknlfx1wakrrm1zi8gi03x2lzj4dsns0vjw0nsmgqvkphyf01vh")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-certifi" ,python-certifi))) + (arguments `(#:tests? #f ; no tests + #:python ,python-2)) + (home-page "http://python-requests.org/") + (synopsis "Python HTTP library") + (description + "Requests is a Python HTTP client library. It aims to be easier to use +than Python’s urllib2 library.") + (license asl2.0))) + +(define-public python-jsonschema + (package + (name "python-jsonschema") + (version "2.4.0") + (source (origin + (method url-fetch) + (uri + (string-append + "https://pypi.python.org/packages/source/j/jsonschema/jsonschema-" + version ".tar.gz")) + (sha256 + (base32 + "1yik3031ziygvq66rj3mzfqdgxj29sg1bkfc46wsgi7lnbqs560j")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "http://github.com/Julian/jsonschema") + (synopsis "Implementation of JSON Schema for Python") + (description + "Jsonschema is an implementation of JSON Schema for Python.") + (license expat))) + +(define-public python2-jsonschema + (package-with-python2 python-jsonschema)) + +(define-public python-unidecode + (package + (name "python-unidecode") + (version "0.04.16") + (source (origin + (method url-fetch) + (uri + (string-append + "https://pypi.python.org/packages/source/U/Unidecode/Unidecode-" + version ".tar.gz")) + (sha256 + (base32 + "0yv56vc49rvippyxgxvcyz7jklc07ky38rcspax7p00sgmriiljc")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "https://pypi.python.org/pypi/Unidecode") + (synopsis "ASCII transliterations of Unicode text") + (description + "Unidecode provides ASCII transliterations of Unicode text. Unidecode is +useful when integrating with legacy code that doesn't support Unicode, or for +ease of entry of non-Roman names on a US keyboard, or when constructing ASCII +machine identifiers from human-readable Unicode strings that should still be +somewhat intelligeble.") + (license gpl2+))) + +(define-public python2-unidecode + (package-with-python2 python-unidecode)) + +(define-public python-pyjwt + (package + (name "python-pyjwt") + (version "0.2.1") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/P/PyJWT/PyJWT-" + version ".tar.gz")) + (sha256 + (base32 + "1ahqblfy2sj3xz34wsa48cn9rp0dspzq56p54i5znmws3b8gml6g")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments + '(#:tests? #f)) ; test suite doesn't work + (home-page "http://github.com/progrium/pyjwt") + (synopsis "JSON Web Token implementation in Python") + (description + "PyJWT is a JSON Web Token implementation written in Python.") + (license expat))) + +(define-public python2-pyjwt + (package-with-python2 python-pyjwt)) + +(define-public python-oauthlib + (package + (name "python-oauthlib") + (version "0.6.3") + (source (origin + (method url-fetch) + (uri + (string-append + "https://pypi.python.org/packages/source/o/oauthlib/oauthlib-" + version ".tar.gz")) + (sha256 + (base32 + "1yaj3j64la4arwsbhbfmpnickzh3jpg9nlpyg409v8pp24isn48a")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-pyjwt" ,python-pyjwt) + ("python-pycrypto" ,python-pycrypto) + ("python-nose" ,python-nose) + ("python-mock" ,python-mock))) + (home-page "https://github.com/idan/oauthlib") + (synopsis "OAuth implementation for Python") + (description + "Oauthlib is a generic, spec-compliant, thorough implementation of the +OAuth request-signing logic.") + (license bsd-3))) + +(define-public python2-oauthlib + (let ((base (package-with-python2 python-oauthlib))) + (package + (inherit base) + (name "python2-oauthlib") + (version "0.6.3") + (source (origin + (method url-fetch) + (uri + (string-append + "https://pypi.python.org/packages/source/o/oauthlib/oauthlib-" + version ".tar.gz")) + (sha256 + (base32 + "1yaj3j64la4arwsbhbfmpnickzh3jpg9nlpyg409v8pp24isn48a")))) + (inputs + (append (package-inputs base) + `(("python2-unittest2" ,python2-unittest2))))))) + +(define-public python-itsdangerous + (package + (name "python-itsdangerous") + (version "0.24") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/i/itsdangerous/itsdangerous-" + version ".tar.gz")) + (sha256 + (base32 + "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "http://github.com/mitsuhiko/itsdangerous") + (synopsis "Python library for passing data to/from untrusted environments") + (description + "Itsdangerous provides various helpers to pass trusted data to untrusted +environments and back.") + (license bsd-3))) + +(define-public python2-itsdangerous + (package-with-python2 python-itsdangerous)) + +(define-public python-virtualenv + (package + (name "python-virtualenv") + (version "1.11.6") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/v/virtualenv/virtualenv-" + version ".tar.gz")) + (sha256 + (base32 + "1xq4prmg25n9cz5zcvbqx68lmc3kl39by582vd8pzs9f3qalqyiy")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-mock" ,python-mock) + ("python-nose" ,python-nose))) + (home-page "https://virtualenv.pypa.io/") + (synopsis "Virtual Python environment builder") + (description + "Virtualenv is a tool to create isolated Python environments.") + (license expat))) + +(define-public python2-virtualenv + (package-with-python2 python-virtualenv)) + +(define-public python-markupsafe + (package + (name "python-markupsafe") + (version "0.23") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/M/MarkupSafe/MarkupSafe-" + version ".tar.gz")) + (sha256 + (base32 + "1hvip33wva3fnmvfp9x5klqri7hpl1hkgqmjbss18nmrb7zimv54")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "http://github.com/mitsuhiko/markupsafe") + (synopsis "XML/HTML/XHTML markup safe string implementation for Python") + (description + "Markupsafe provides an XML/HTML/XHTML markup safe string implementation +for Python.") + (license bsd-3))) + +(define-public python2-markupsafe + (package-with-python2 python-markupsafe)) + +(define-public python-jinja2 + (package + (name "python-jinja2") + (version "2.7.3") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/J/Jinja2/Jinja2-" + version ".tar.gz")) + (sha256 + (base32 + "1nwg9yfqgy421lncnm63k1zf9xkd1klc0jm0fr4p3dad01fsq91f")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-markupsafe" ,python-markupsafe))) + (home-page "http://jinja.pocoo.org/") + (synopsis "Python template engine") + (description + "Jinja2 is a small but fast and easy to use stand-alone template engine +written in pure Python.") + (license bsd-3))) + +(define-public python2-jinja2 + (package-with-python2 python-jinja2)) + +(define-public python-docutils + (package + (name "python-docutils") + (version "0.12") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/d/docutils/docutils-" + version ".tar.gz")) + (sha256 + (base32 + "1ylnjnw1x4b2y7blr6x35ncdzn69k253kw4cdkv6asdb21w73ny7")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (arguments + '(#:tests? #f)) ; no setup.py test command + (home-page "http://docutils.sourceforge.net/") + (synopsis "Python Documentation Utilities") + (description + "Docutils is a modular system for processing documentation into useful +formats, such as HTML, XML, and LaTeX. For input Docutils supports +reStructuredText.") + ;; Most of the source code is public domain, but some source files are + ;; licensed under the PFSL, BSD 2-clause, and GPLv3+ licenses. + (license (list public-domain psfl bsd-2 gpl3+)))) + +(define-public python2-docutils + (package-with-python2 python-docutils)) + +(define-public python-pygments + (package + (name "python-pygments") + (version "1.6") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/P/Pygments/Pygments-" + version ".tar.gz")) + (sha256 + (base32 + "1h11r6ss8waih51vcksfvzghfxiav2f8svc0812fa5kmyz5d97kr")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools))) + (home-page "http://pygments.org/") + (synopsis "Syntax highlighting") + (description + "Pygments is a syntax highlighting package written in Python.") + (license bsd-2))) + +(define-public python2-pygments + (package-with-python2 python-pygments)) + +(define-public python-sphinx + (package + (name "python-sphinx") + (version "1.2.3") + (source + (origin + (method url-fetch) + (uri (string-append + "https://pypi.python.org/packages/source/S/Sphinx/Sphinx-" + version ".tar.gz")) + (sha256 + (base32 + "011xizm3jnmf4cvs5i6kgf6c5nn046h79i8j0vd0f27yw9j3p4wl")))) + (build-system python-build-system) + (inputs + `(("python-setuptools" ,python-setuptools) + ("python-jinja2" ,python-jinja2) + ("python-docutils" ,python-docutils) + ("python-pygments" ,python-pygments))) + (home-page "http://sphinx-doc.org/") + (synopsis "Python documentation generator") + (description "Sphinx is a tool that makes it easy to create documentation +for Python projects or other documents consisting of multiple reStructuredText +sources.") + (license bsd-3))) + +(define-public python2-sphinx + (package-with-python2 python-sphinx)) diff --git a/gnu/packages/telephony.scm b/gnu/packages/telephony.scm index 363058dd54..b127d70976 100644 --- a/gnu/packages/telephony.scm +++ b/gnu/packages/telephony.scm @@ -56,13 +56,13 @@ reimplementation.") (define-public ucommon (package (name "ucommon") - (version "6.1.8") + (version "6.1.10") (source (origin (method url-fetch) (uri (string-append "mirror://gnu/commoncpp/" name "-" version ".tar.gz")) (sha256 (base32 - "1ibz5snb2gq0psw8z152qcbd1zaijpsksc3lrp6av60inyq3waws")))) + "14myyz4vdq1hzp39k835lk1c9pr4pkhzapsxv50qnh9w3v9mx8fp")))) (build-system gnu-build-system) (synopsis "(u)Common C++ framework for threaded applications") (description "GNU Common C++ is an portable, optimized class framework for diff --git a/gnu/services/base.scm b/gnu/services/base.scm index 578d4cba71..b5b49d1a01 100644 --- a/gnu/services/base.scm +++ b/gnu/services/base.scm @@ -29,7 +29,7 @@ #:use-module ((gnu packages base) #:select (canonical-package glibc)) #:use-module (gnu packages package-management) - #:use-module ((guix build linux-initrd) + #:use-module ((gnu build file-systems) #:select (mount-flags->bit-mask)) #:use-module (guix gexp) #:use-module (guix monads) diff --git a/gnu/services/dmd.scm b/gnu/services/dmd.scm index dfda2708f5..59c2373779 100644 --- a/gnu/services/dmd.scm +++ b/gnu/services/dmd.scm @@ -35,7 +35,7 @@ (define modules ;; Extra modules visible to dmd.conf. '((guix build syscalls) - (guix build linux-initrd) + (gnu build file-systems) (guix build utils))) (mlet %store-monad ((modules (imported-modules modules)) @@ -50,7 +50,7 @@ (use-modules (ice-9 ftw) (guix build syscalls) (guix build utils) - ((guix build linux-initrd) + ((gnu build file-systems) #:select (check-file-system canonicalize-device-spec))) (register-services diff --git a/gnu/system.scm b/gnu/system.scm index 2469ade1f3..ea7fdf1cb7 100644 --- a/gnu/system.scm +++ b/gnu/system.scm @@ -362,9 +362,10 @@ alias ll='ls -l' stateful part of OS, including user accounts and groups, special directories, etc." (define %modules - '((guix build activation) - (guix build utils) - (guix build linux-initrd))) + '((gnu build activation) + (gnu build linux-boot) + (gnu build file-systems) + (guix build utils))) (define (service-activations services) ;; Return the activation scripts for SERVICES. @@ -399,7 +400,7 @@ etc." (set! %load-compiled-path (cons #$compiled %load-compiled-path))) - (use-modules (guix build activation)) + (use-modules (gnu build activation)) ;; Populate /etc. (activate-etc #$etc) diff --git a/gnu/system/linux-initrd.scm b/gnu/system/linux-initrd.scm index e48b399a9d..627d17bac2 100644 --- a/gnu/system/linux-initrd.scm +++ b/gnu/system/linux-initrd.scm @@ -68,16 +68,22 @@ initrd." ;; General Linux overview in `Documentation/early-userspace/README' and ;; `Documentation/filesystems/ramfs-rootfs-initramfs.txt'. - (define (string->regexp str) - ;; Return a regexp that matches STR exactly. - (string-append "^" (regexp-quote str) "$")) - - (mlet* %store-monad ((source (imported-modules modules)) - (compiled (compiled-modules modules))) + (define graph-files + (unfold-right zero? + number->string + 1- + (length to-copy))) + + (mlet %store-monad ((source (imported-modules modules)) + (compiled (compiled-modules modules)) + (module-dir (flat-linux-module-directory linux + linux-modules))) (define builder - ;; TODO: Move most of this code to (guix build linux-initrd). + ;; TODO: Move most of this code to (gnu build linux-initrd). #~(begin - (use-modules (guix build utils) + (use-modules (gnu build linux-initrd) + (guix build utils) + (guix build store-copy) (ice-9 pretty-print) (ice-9 popen) (ice-9 match) @@ -87,9 +93,7 @@ initrd." (rnrs bytevectors) ((system foreign) #:select (sizeof))) - (let ((cpio (string-append #$cpio "/bin/cpio")) - (gzip (string-append #$gzip "/bin/gzip")) - (modules #$source) + (let ((modules #$source) (gos #$compiled) (scm-dir (string-append "share/guile/" (effective-version))) (go-dir (format #f ".cache/guile/ccache/~a-~a-~a-~a" @@ -101,6 +105,7 @@ initrd." (effective-version)))) (mkdir #$output) (mkdir "contents") + (with-directory-excursion "contents" (copy-recursively #$guile ".") (call-with-output-file "init" @@ -127,74 +132,58 @@ initrd." #:output-file (string-append go-dir "/init.go")) ;; Copy Linux modules. - (let* ((linux #$linux) - (module-dir (and linux - (string-append linux "/lib/modules")))) - (mkdir "modules") - #$@(map (lambda (module) - #~(match (find-files module-dir - #$(string->regexp module)) - ((file) - (format #t "copying '~a'...~%" file) - (copy-file file (string-append "modules/" - #$module))) - (() - (error "module not found" #$module module-dir)) - ((_ ...) - (error "several modules by that name" - #$module module-dir)))) - linux-modules)) - - (let ((store #$(string-append "." (%store-prefix))) - (to-copy '#$to-copy)) - (unless (null? to-copy) - (mkdir-p store)) - ;; XXX: Should we do export-references-graph? - (for-each (lambda (input) - (let ((target - (string-append store "/" - (basename input)))) - (copy-recursively input target))) - to-copy)) + (mkdir "modules") + (copy-recursively #$module-dir "modules") + + ;; Populate the initrd's store. + (with-directory-excursion ".." + (populate-store '#$graph-files "contents")) ;; Reset the timestamps of all the files that will make it in the ;; initrd. (for-each (cut utime <> 0 0 0 0) (find-files "." ".*")) - (system* cpio "--version") - (let ((pipe (open-pipe* OPEN_WRITE cpio "-o" - "-O" (string-append #$output "/initrd") - "-H" "newc" "--null"))) - (define print0 - (let ((len (string-length "./"))) - (lambda (file) - (format pipe "~a\0" (string-drop file len))))) - - ;; Note: as per `ramfs-rootfs-initramfs.txt', always add - ;; directory entries before the files that are inside of it: "The - ;; Linux kernel cpio extractor won't create files in a directory - ;; that doesn't exist, so the directory entries must go before - ;; the files that go in those directories." - (file-system-fold (const #t) - (lambda (file stat result) ; leaf - (print0 file)) - (lambda (dir stat result) ; down - (unless (string=? dir ".") - (print0 dir))) - (const #f) ; up - (const #f) ; skip - (const #f) - #f - ".") - - (and (zero? (close-pipe pipe)) - (with-directory-excursion #$output - (and (zero? (system* gzip "--best" "initrd")) - (rename-file "initrd.gz" "initrd"))))))))) + (write-cpio-archive (string-append #$output "/initrd") "." + #:cpio (string-append #$cpio "/bin/cpio") + #:gzip (string-append #$gzip "/bin/gzip")))))) (gexp->derivation name builder - #:modules '((guix build utils))))) + #:modules '((guix build utils) + (guix build store-copy) + (gnu build linux-initrd)) + #:references-graphs (zip graph-files to-copy)))) + +(define (flat-linux-module-directory linux modules) + "Return a flat directory containing the Linux kernel modules listed in +MODULES and taken from LINUX." + (define build-exp + #~(begin + (use-modules (ice-9 match) (ice-9 regex) + (guix build utils)) + + (define (string->regexp str) + ;; Return a regexp that matches STR exactly. + (string-append "^" (regexp-quote str) "$")) + + (define module-dir + (string-append #$linux "/lib/modules")) + + (mkdir #$output) + (for-each (lambda (module) + (match (find-files module-dir (string->regexp module)) + ((file) + (format #t "copying '~a'...~%" file) + (copy-file file (string-append #$output "/" module))) + (() + (error "module not found" module module-dir)) + ((_ ...) + (error "several modules by that name" + module module-dir)))) + '#$modules))) + + (gexp->derivation "linux-modules" build-exp + #:modules '((guix build utils)))) (define (file-system->spec fs) "Return a list corresponding to file-system FS that can be passed to the @@ -277,7 +266,7 @@ exception and backtrace!)." (expression->initrd #~(begin - (use-modules (guix build linux-initrd) + (use-modules (gnu build linux-boot) (guix build utils) (srfi srfi-26)) @@ -293,7 +282,8 @@ exception and backtrace!)." #:volatile-root? '#$volatile-root?)) #:name "base-initrd" #:modules '((guix build utils) - (guix build linux-initrd)) + (gnu build linux-boot) + (gnu build file-systems)) #:to-copy helper-packages #:linux linux-libre #:linux-modules linux-modules)) diff --git a/gnu/system/vm.scm b/gnu/system/vm.scm index 42fc23ee8f..205bf2cb19 100644 --- a/gnu/system/vm.scm +++ b/gnu/system/vm.scm @@ -23,7 +23,7 @@ #:use-module (guix derivations) #:use-module (guix packages) #:use-module (guix monads) - #:use-module ((guix build vm) + #:use-module ((gnu build vm) #:select (qemu-command)) #:use-module (gnu packages base) #:use-module (gnu packages guile) @@ -112,10 +112,12 @@ input tuple. The output file name is when building for SYSTEM." (qemu qemu-headless) (env-vars '()) (modules - '((guix build vm) - (guix build install) - (guix build linux-initrd) - (guix build utils))) + '((gnu build vm) + (gnu build install) + (gnu build linux-boot) + (gnu build file-systems) + (guix build utils) + (guix build store-copy))) (guile-for-build (%guile-for-build)) @@ -164,7 +166,7 @@ made available under the /xchg CIFS share." ;; Code that launches the VM that evaluates EXP. #~(begin (use-modules (guix build utils) - (guix build vm)) + (gnu build vm)) (let ((inputs '#$(list qemu coreutils)) (linux (string-append #$linux "/bzImage")) @@ -217,48 +219,46 @@ INPUTS is a list of inputs (as for packages). When COPY-INPUTS? is true, copy all of INPUTS into the image being built. When REGISTER-CLOSURES? is true, register INPUTS in the store database of the image so that Guix can be used in the image." - (mlet %store-monad - ((graph (sequence %store-monad (map input->name+output inputs)))) - (expression->derivation-in-linux-vm - name - #~(begin - (use-modules (guix build vm) - (guix build utils)) - - (let ((inputs - '#$(append (list qemu parted grub e2fsprogs util-linux) - (map canonical-package - (list sed grep coreutils findutils gawk)) - (if register-closures? (list guix) '()))) - - ;; This variable is unused but allows us to add INPUTS-TO-COPY - ;; as inputs. - (to-register - '#$(map (match-lambda - ((name thing) thing) - ((name thing output) `(,thing ,output))) - inputs))) - - (set-path-environment-variable "PATH" '("bin" "sbin") inputs) - - (let ((graphs '#$(match inputs - (((names . _) ...) - names)))) - (initialize-hard-disk "/dev/vda" - #:system-directory #$os-derivation - #:grub.cfg #$grub-configuration - #:closures graphs - #:copy-closures? #$copy-inputs? - #:register-closures? #$register-closures? - #:disk-image-size #$disk-image-size - #:file-system-type #$file-system-type - #:file-system-label #$file-system-label) - (reboot)))) - #:system system - #:make-disk-image? #t - #:disk-image-size disk-image-size - #:disk-image-format disk-image-format - #:references-graphs graph))) + (expression->derivation-in-linux-vm + name + #~(begin + (use-modules (gnu build vm) + (guix build utils)) + + (let ((inputs + '#$(append (list qemu parted grub e2fsprogs util-linux) + (map canonical-package + (list sed grep coreutils findutils gawk)) + (if register-closures? (list guix) '()))) + + ;; This variable is unused but allows us to add INPUTS-TO-COPY + ;; as inputs. + (to-register + '#$(map (match-lambda + ((name thing) thing) + ((name thing output) `(,thing ,output))) + inputs))) + + (set-path-environment-variable "PATH" '("bin" "sbin") inputs) + + (let ((graphs '#$(match inputs + (((names . _) ...) + names)))) + (initialize-hard-disk "/dev/vda" + #:system-directory #$os-derivation + #:grub.cfg #$grub-configuration + #:closures graphs + #:copy-closures? #$copy-inputs? + #:register-closures? #$register-closures? + #:disk-image-size #$disk-image-size + #:file-system-type #$file-system-type + #:file-system-label #$file-system-label) + (reboot)))) + #:system system + #:make-disk-image? #t + #:disk-image-size disk-image-size + #:disk-image-format disk-image-format + #:references-graphs inputs)) ;;; diff --git a/guix/build/pull.scm b/guix/build/pull.scm index e5b8797503..841787f0bb 100644 --- a/guix/build/pull.scm +++ b/guix/build/pull.scm @@ -21,6 +21,7 @@ #:use-module (system base compile) #:use-module (ice-9 ftw) #:use-module (ice-9 match) + #:use-module (ice-9 format) #:use-module (srfi srfi-1) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) @@ -47,43 +48,70 @@ normally, and 1 if an exception is raised." (pid #t))) +(define* (report-build-progress total completed cont + #:optional (log-port (current-error-port))) + "Report that COMPLETED out of TOTAL files have been completed, and call +CONT." + (display #\cr log-port) + (format log-port "compiling...\t~5,1f% of ~d files" ;FIXME: i18n + (* 100. (/ completed total)) total) + (force-output log-port) + (cont)) + (define* (p-for-each proc lst - #:optional (max-processes (current-processor-count))) + #:optional (max-processes (current-processor-count)) + #:key (progress report-build-progress)) "Invoke PROC for each element of LST in a separate process, using up to -MAX-PROCESSES processes in parallel. Raise an error if one of the processes -exit with non-zero." +MAX-PROCESSES processes in parallel. Call PROGRESS at each step, passing it +the continuation. Raise an error if one of the processes exit with non-zero." + (define total + (length lst)) + (define (wait-for-one-process) (match (waitpid WAIT_ANY) ((_ . status) (unless (zero? (status:exit-val status)) (error "process failed" proc status))))) - (let loop ((lst lst) - (running 0)) + (let loop ((lst lst) + (running 0) + (completed 0)) (match lst (() (or (zero? running) - (begin + (let ((running (- running 1)) + (completed (+ completed 1))) (wait-for-one-process) - (loop lst (- running 1))))) + (progress total completed + (lambda () + (loop lst running completed)))))) ((head . tail) (if (< running max-processes) - (begin + (let ((running (+ 1 running))) (call-with-process (cut proc head)) - (loop tail (+ running 1))) - (begin + (progress total completed + (lambda () + (loop tail running completed)))) + (let ((running (- running 1)) + (completed (+ completed 1))) (wait-for-one-process) - (loop lst (- running 1)))))))) + (progress total completed + (lambda () + (loop lst running completed))))))))) (define* (build-guix out tarball - #:key tar gzip gcrypt) - "Build and install Guix in directory OUT using source from TARBALL." + #:key tar gzip gcrypt + (debug-port (%make-void-port "w"))) + "Build and install Guix in directory OUT using source from TARBALL. Write +any debugging output to DEBUG-PORT." (setvbuf (current-output-port) _IOLBF) (setvbuf (current-error-port) _IOLBF) (setenv "PATH" (string-append tar "/bin:" gzip "/bin")) - (system* "tar" "xvf" tarball) + (format debug-port "extracting '~a'...~%" tarball) + (system* "tar" "xf" tarball) + (match (scandir "." (lambda (name) (and (not (member name '("." ".."))) (file-is-directory? name)))) @@ -92,11 +120,13 @@ exit with non-zero." (x (error "tarball did not produce a single source directory" x))) - (format #t "copying and compiling Guix to `~a'...~%" out) + (format #t "copying and compiling to '~a'...~%" out) ;; Copy everything under guix/ and gnu/ plus {guix,gnu}.scm. - (copy-recursively "guix" (string-append out "/guix")) - (copy-recursively "gnu" (string-append out "/gnu")) + (copy-recursively "guix" (string-append out "/guix") + #:log debug-port) + (copy-recursively "gnu" (string-append out "/gnu") + #:log debug-port) (copy-file "guix.scm" (string-append out "/guix.scm")) (copy-file "gnu.scm" (string-append out "/gnu.scm")) @@ -121,12 +151,12 @@ exit with non-zero." (p-for-each (lambda (file) (let ((go (string-append (string-drop-right file 4) ".go"))) - (format (current-error-port) - "compiling '~a'...~%" file) - (compile-file file - #:output-file go - #:opts - %auto-compilation-options))) + (format debug-port "~%compiling '~a'...~%" file) + (parameterize ((current-warning-port debug-port)) + (compile-file file + #:output-file go + #:opts + %auto-compilation-options)))) (filter (cut string-suffix? ".scm" <>) @@ -144,6 +174,7 @@ exit with non-zero." (delete-file (string-append out "/guix/config.scm")) (delete-file (string-append out "/guix/config.go")) + (newline) #t) ;;; pull.scm ends here diff --git a/guix/build/store-copy.scm b/guix/build/store-copy.scm new file mode 100644 index 0000000000..a296bdf78f --- /dev/null +++ b/guix/build/store-copy.scm @@ -0,0 +1,69 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2013, 2014 Ludovic Courtès <ludo@gnu.org> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (guix build store-copy) + #:use-module (guix build utils) + #:use-module (srfi srfi-1) + #:use-module (ice-9 rdelim) + #:export (read-reference-graph + populate-store)) + +;;; Commentary: +;;; +;;; This module provides the tools to copy store items and their dependencies +;;; to another store. It relies on the availability of "reference graph" +;;; files as produced by 'gexp->derivation' et al. with the +;;; #:references-graphs parameter. +;;; +;;; Code: + +(define (read-reference-graph port) + "Return a list of store paths from the reference graph at PORT. +The data at PORT is the format produced by #:references-graphs." + (let loop ((line (read-line port)) + (result '())) + (cond ((eof-object? line) + (delete-duplicates result)) + ((string-prefix? "/" line) + (loop (read-line port) + (cons line result))) + (else + (loop (read-line port) + result))))) + +(define* (populate-store reference-graphs target) + "Populate the store under directory TARGET with the items specified in +REFERENCE-GRAPHS, a list of reference-graph files." + (define store + (string-append target (%store-directory))) + + (define (things-to-copy) + ;; Return the list of store files to copy to the image. + (define (graph-from-file file) + (call-with-input-file file read-reference-graph)) + + (delete-duplicates (append-map graph-from-file reference-graphs))) + + (mkdir-p store) + (chmod store #o1775) + (for-each (lambda (thing) + (copy-recursively thing + (string-append target thing))) + (things-to-copy))) + +;;; store-copy.scm ends here diff --git a/guix/gexp.scm b/guix/gexp.scm index e31324e101..ff80e305db 100644 --- a/guix/gexp.scm +++ b/guix/gexp.scm @@ -109,6 +109,17 @@ the cross-compilation target triplet." (return input))) inputs)))) +(define* (lower-reference-graphs graphs #:key system target) + "Given GRAPHS, a list of (FILE-NAME INPUT ...) lists for use as a +#:reference-graphs argument, lower it such that each INPUT is replaced by the +corresponding derivation." + (match graphs + (((file-names . inputs) ...) + (mlet %store-monad ((inputs (lower-inputs inputs + #:system system + #:target target))) + (return (map cons file-names inputs)))))) + (define* (gexp->derivation name exp #:key system (target 'current) @@ -127,10 +138,38 @@ names of Guile modules from the current search path to be copied in the store, compiled, and made available in the load path during the execution of EXP---e.g., '((guix build utils) (guix build gnu-build-system)). +When REFERENCES-GRAPHS is true, it must be a list of tuples of one of the +following forms: + + (FILE-NAME PACKAGE) + (FILE-NAME PACKAGE OUTPUT) + (FILE-NAME DERIVATION) + (FILE-NAME DERIVATION OUTPUT) + (FILE-NAME STORE-ITEM) + +The right-hand-side of each element of REFERENCES-GRAPHS is automatically made +an input of the build process of EXP. In the build environment, each +FILE-NAME contains the reference graph of the corresponding item, in a simple +text format. + +In that case, the reference graph of each store path is exported in +the build environment in the corresponding file, in a simple text format. + The other arguments are as for 'derivation'." (define %modules modules) (define outputs (gexp-outputs exp)) + (define (graphs-file-names graphs) + ;; Return a list of (FILE-NAME . STORE-PATH) pairs made from GRAPHS. + (map (match-lambda + ((file-name (? derivation? drv)) + (cons file-name (derivation->output-path drv))) + ((file-name (? derivation? drv) sub-drv) + (cons file-name (derivation->output-path drv sub-drv))) + ((file-name thing) + (cons file-name thing))) + graphs)) + (mlet* %store-monad (;; The following binding is here to force ;; '%current-system' and '%current-target-system' to be ;; looked up at >>= time. @@ -162,6 +201,11 @@ The other arguments are as for 'derivation'." #:system system #:guile guile-for-build) (return #f))) + (graphs (if references-graphs + (lower-reference-graphs references-graphs + #:system system + #:target target) + (return #f))) (guile (if guile-for-build (return guile-for-build) (package->derivation (default-guile) @@ -182,9 +226,12 @@ The other arguments are as for 'derivation'." (,builder) ,@(if modules `((,modules) (,compiled) ,@inputs) - inputs)) + inputs) + ,@(match graphs + (((_ . inputs) ...) inputs) + (_ '()))) #:hash hash #:hash-algo hash-algo #:recursive? recursive? - #:references-graphs references-graphs + #:references-graphs (and=> graphs graphs-file-names) #:local-build? local-build?))) (define* (gexp-inputs exp #:optional (references gexp-references)) @@ -449,14 +496,16 @@ its search path." (format port "#!~a/bin/guile --no-auto-compile~%!#~%" (ungexp guile)) + + ;; Write the 'eval-when' form so that it can be + ;; compiled. (write - '(set! %load-path - (cons (ungexp modules) %load-path)) - port) - (write - '(set! %load-compiled-path - (cons (ungexp compiled) - %load-compiled-path)) + '(eval-when (expand load eval) + (set! %load-path + (cons (ungexp modules) %load-path)) + (set! %load-compiled-path + (cons (ungexp compiled) + %load-compiled-path))) port) (write '(ungexp exp) port) (chmod port #o555))))))) diff --git a/guix/profiles.scm b/guix/profiles.scm index 55c3b6e768..9dc9ab43b9 100644 --- a/guix/profiles.scm +++ b/guix/profiles.scm @@ -32,6 +32,7 @@ #:use-module (ice-9 format) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) + #:use-module (srfi srfi-11) #:use-module (srfi srfi-19) #:use-module (srfi srfi-26) #:export (manifest make-manifest @@ -52,6 +53,7 @@ manifest-remove manifest-add + manifest-lookup manifest-installed? manifest-matching-entries @@ -60,6 +62,7 @@ manifest-transaction-install manifest-transaction-remove manifest-perform-transaction + manifest-transaction-effects manifest-show-transaction profile-manifest @@ -235,11 +238,16 @@ Remove MANIFEST entries that have the same name and output as ENTRIES." (manifest-entries manifest) entries)))) +(define (manifest-lookup manifest pattern) + "Return the first item of MANIFEST that matches PATTERN, or #f if there is +no match.." + (find (entry-predicate pattern) + (manifest-entries manifest))) + (define (manifest-installed? manifest pattern) "Return #t if MANIFEST has an entry matching PATTERN (a manifest-pattern), #f otherwise." - (->bool (find (entry-predicate pattern) - (manifest-entries manifest)))) + (->bool (manifest-lookup manifest pattern))) (define (manifest-matching-entries manifest patterns) "Return all the entries of MANIFEST that match one of the PATTERNS." @@ -266,6 +274,39 @@ Remove MANIFEST entries that have the same name and output as ENTRIES." (remove manifest-transaction-remove ; list of <manifest-pattern> (default '()))) +(define (manifest-transaction-effects manifest transaction) + "Compute the effect of applying TRANSACTION to MANIFEST. Return 3 values: +the list of packages that would be removed, installed, or upgraded when +applying TRANSACTION to MANIFEST. Upgrades are represented as pairs where the +head is the entry being upgraded and the tail is the entry that will replace +it." + (define (manifest-entry->pattern entry) + (manifest-pattern + (name (manifest-entry-name entry)) + (output (manifest-entry-output entry)))) + + (let loop ((input (manifest-transaction-install transaction)) + (install '()) + (upgrade '())) + (match input + (() + (let ((remove (manifest-transaction-remove transaction))) + (values (manifest-matching-entries manifest remove) + (reverse install) (reverse upgrade)))) + ((entry rest ...) + ;; Check whether installing ENTRY corresponds to the installation of a + ;; new package or to an upgrade. + + ;; XXX: When the exact same output directory is installed, we're not + ;; really upgrading anything. Add a check for that case. + (let* ((pattern (manifest-entry->pattern entry)) + (previous (manifest-lookup manifest pattern))) + (loop rest + (if previous install (cons entry install)) + (if previous + (alist-cons previous entry upgrade) + upgrade))))))) + (define (manifest-perform-transaction manifest transaction) "Perform TRANSACTION on MANIFEST and return new manifest." (let ((install (manifest-transaction-install transaction)) @@ -273,35 +314,48 @@ Remove MANIFEST entries that have the same name and output as ENTRIES." (manifest-add (manifest-remove manifest remove) install))) +(define (right-arrow port) + "Return either a string containing the 'RIGHT ARROW' character, or an ASCII +replacement if PORT is not Unicode-capable." + (with-fluids ((%default-port-encoding (port-encoding port))) + (let ((arrow "→")) + (catch 'encoding-error + (lambda () + (with-fluids ((%default-port-conversion-strategy 'error)) + (with-output-to-string + (lambda () + (display arrow))))) + (lambda (key . args) + "->"))))) + (define* (manifest-show-transaction store manifest transaction #:key dry-run?) "Display what will/would be installed/removed from MANIFEST by TRANSACTION." (define (package-strings name version output item) (map (lambda (name version output item) - (format #f " ~a-~a\t~a\t~a" name version output + (format #f " ~a~:[:~a~;~*~]\t~a\t~a" + name + (equal? output "out") output version (if (package? item) (package-output store item output) item))) name version output item)) - (let* ((remove (manifest-matching-entries - manifest (manifest-transaction-remove transaction))) - (install/upgrade (manifest-transaction-install transaction)) - (install '()) - (upgrade (append-map - (lambda (entry) - (let ((matching - (manifest-matching-entries - manifest - (list (manifest-pattern - (name (manifest-entry-name entry)) - (output (manifest-entry-output entry))))))) - (when (null? matching) - (set! install (cons entry install))) - matching)) - install/upgrade))) + (define → ;an arrow that can be represented on stderr + (right-arrow (current-error-port))) + + (define (upgrade-string name old-version new-version output item) + (format #f " ~a~:[:~a~;~*~]\t~a ~a ~a\t~a" + name (equal? output "out") output + old-version → new-version + (if (package? item) + (package-output store item output) + item))) + + (let-values (((remove install upgrade) + (manifest-transaction-effects manifest transaction))) (match remove - ((($ <manifest-entry> name version output item _) ..1) + ((($ <manifest-entry> name version output item) ..1) (let ((len (length name)) (remove (package-strings name version output item))) (if dry-run? @@ -317,9 +371,11 @@ Remove MANIFEST entries that have the same name and output as ENTRIES." remove)))) (_ #f)) (match upgrade - ((($ <manifest-entry> name version output item _) ..1) + (((($ <manifest-entry> name old-version) + . ($ <manifest-entry> _ new-version output item)) ..1) (let ((len (length name)) - (upgrade (package-strings name version output item))) + (upgrade (map upgrade-string + name old-version new-version output item))) (if dry-run? (format (current-error-port) (N_ "The following package would be upgraded:~%~{~a~%~}~%" diff --git a/guix/scripts/build.scm b/guix/scripts/build.scm index 5e4647de79..09401e923c 100644 --- a/guix/scripts/build.scm +++ b/guix/scripts/build.scm @@ -33,7 +33,7 @@ #:use-module (srfi srfi-26) #:use-module (srfi srfi-34) #:use-module (srfi srfi-37) - #:autoload (gnu packages) (find-best-packages-by-name) + #:autoload (gnu packages) (specification->package) #:autoload (guix download) (download-to-store) #:export (%standard-build-options set-build-options-from-command-line @@ -41,27 +41,6 @@ guix-build)) -(define (specification->package spec) - "Return a package matching SPEC. SPEC may be a package name, or a package -name followed by a hyphen and a version number. If the version number is not -present, return the preferred newest version." - (let-values (((name version) - (package-name->name+version spec))) - (match (find-best-packages-by-name name version) - ((p) ; one match - p) - ((p x ...) ; several matches - (warning (_ "ambiguous package specification `~a'~%") spec) - (warning (_ "choosing ~a from ~a~%") - (package-full-name p) - (location->string (package-location p))) - p) - (_ ; no matches - (if version - (leave (_ "~A: package not found for version ~a~%") - name version) - (leave (_ "~A: unknown package~%") name)))))) - (define (register-root store paths root) "Register ROOT as an indirect GC root for all of PATHS." (let* ((root (string-append (canonicalize-path (dirname root)) diff --git a/guix/scripts/lint.scm b/guix/scripts/lint.scm new file mode 100644 index 0000000000..83dde9a1a1 --- /dev/null +++ b/guix/scripts/lint.scm @@ -0,0 +1,213 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 Cyril Roelandt <tipecaml@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + +(define-module (guix scripts lint) + #:use-module (guix base32) + #:use-module (guix packages) + #:use-module (guix records) + #:use-module (guix ui) + #:use-module (guix utils) + #:use-module (gnu packages) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-11) + #:use-module (srfi srfi-37) + #:export (guix-lint + check-inputs-should-be-native + check-patches + check-synopsis-style)) + + +;;; +;;; Command-line options. +;;; + +(define %default-options + ;; Alist of default option values. + '()) + +(define (show-help) + (display (_ "Usage: guix lint [OPTION]... [PACKAGE]... +Run a set of checkers on the specified package; if none is specified, run the checkers on all packages.\n")) + (display (_ " + -h, --help display this help and exit")) + (display (_ " + -l, --list-checkers display the list of available lint checkers")) + (display (_ " + -V, --version display version information and exit")) + (newline) + (show-bug-report-information)) + +(define %options + ;; Specification of the command-line options. + ;; TODO: add some options: + ;; * --checkers=checker1,checker2...: only run the specified checkers + ;; * --certainty=[low,medium,high]: only run checkers that have at least this + ;; 'certainty'. + (list (option '(#\h "help") #f #f + (lambda args + (show-help) + (exit 0))) + (option '(#\l "list-checkers") #f #f + (lambda args + (list-checkers-and-exit))) + (option '(#\V "version") #f #f + (lambda args + (show-version-and-exit "guix lint"))))) + + +;;; +;;; Helpers +;;; +(define* (emit-warning package message #:optional field) + ;; Emit a warning about PACKAGE, printing the location of FIELD if it is + ;; given, the location of PACKAGE otherwise, the full name of PACKAGE and the + ;; provided MESSAGE. + (let ((loc (or (package-field-location package field) + (package-location package)))) + (format (guix-warning-port) (_ "~a: ~a: ~a~%") + (location->string loc) + (package-full-name package) + message))) + + +;;; +;;; Checkers +;;; +(define-record-type* <lint-checker> + lint-checker make-lint-checker + lint-checker? + ;; TODO: add a 'certainty' field that shows how confident we are in the + ;; checker. Then allow users to only run checkers that have a certain + ;; 'certainty' level. + (name lint-checker-name) + (description lint-checker-description) + (check lint-checker-check)) + +(define (list-checkers-and-exit) + ;; Print information about all available checkers and exit. + (format #t (_ "Available checkers:~%")) + (for-each (lambda (checker) + (format #t "- ~a: ~a~%" + (lint-checker-name checker) + (lint-checker-description checker))) + %checkers) + (exit 0)) + +(define (check-inputs-should-be-native package) + ;; Emit a warning if some inputs of PACKAGE are likely to belong to its + ;; native inputs. + (let ((inputs (package-inputs package))) + (match inputs + (((labels packages . _) ...) + (when (member "pkg-config" + (map package-name (filter package? packages))) + (emit-warning package + "pkg-config should probably be a native input" + 'inputs)))))) + + +(define (check-synopsis-style package) + ;; Emit a warning if stylistic issues are found in the synopsis of PACKAGE. + (define (check-final-period synopsis) + ;; Synopsis should not end with a period, except for some special cases. + (if (and (string=? (string-take-right synopsis 1) ".") + (not (string=? (string-take-right synopsis 4) "etc."))) + (emit-warning package + "no period allowed at the end of the synopsis" + 'synopsis))) + + (define (check-start-article synopsis) + (if (or (string=? (string-take synopsis 2) "A ") + (string=? (string-take synopsis 3) "An ")) + (emit-warning package + "no article allowed at the beginning of the synopsis" + 'synopsis))) + + (let ((synopsis (package-synopsis package))) + (if (string? synopsis) + (begin + (check-final-period synopsis) + (check-start-article synopsis))))) + +(define (check-patches package) + ;; Emit a warning if the patches requires by PACKAGE are badly named. + (let ((patches (and=> (package-source package) origin-patches)) + (name (package-name package)) + (full-name (package-full-name package))) + (if (and patches + (any (lambda (patch) + (let ((filename (basename patch))) + (not (or (eq? (string-contains filename name) 0) + (eq? (string-contains filename full-name) 0))))) + patches)) + (emit-warning package + "file names of patches should start with the package name" + 'patches)))) + +(define %checkers + (list + (lint-checker + (name "inputs-should-be-native") + (description "Identify inputs that should be native inputs") + (check check-inputs-should-be-native)) + (lint-checker + (name "patch-filenames") + (description "Validate filenames of patches") + (check check-patches)) + (lint-checker + (name "synopsis") + (description "Validate package synopsis") + (check check-synopsis-style)))) + +(define (run-checkers package) + ;; Run all the checkers on PACKAGE. + (for-each (lambda (checker) + ((lint-checker-check checker) package)) + %checkers)) + + +;;; +;;; Entry Point +;;; + +(define (guix-lint . args) + (define (parse-options) + ;; Return the alist of option values. + (args-fold* args %options + (lambda (opt name arg result) + (leave (_ "~A: unrecognized option~%") name)) + (lambda (arg result) + (alist-cons 'argument arg result)) + %default-options)) + + (let* ((opts (parse-options)) + (args (filter-map (match-lambda + (('argument . value) + value) + (_ #f)) + (reverse opts)))) + + + (if (null? args) + (fold-packages (lambda (p r) (run-checkers p)) '()) + (for-each + (lambda (spec) + (run-checkers spec)) + (map specification->package args))))) diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index 18af511ed8..b3b502425c 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -181,7 +181,8 @@ determined." #:key (error-port (current-error-port)) (quote? #t)) "Run COMMAND (a string list) on MACHINE, assuming an lsh gateway has been set up. When QUOTE? is true, perform shell-quotation of all the elements of -COMMAND." +COMMAND. Return either a pipe opened with MODE, or #f if the lsh client could +not be started." (define (shell-quote str) ;; Sort-of shell-quote STR so it can be passed as an argument to the ;; shell. @@ -315,8 +316,17 @@ hook." (let ((root-directory (string-append %state-directory "/gcroots/tmp"))) (false-if-exception (mkdir root-directory)) - (symlink ,file - (string-append root-directory "/" ,%gc-root-file))))) + (catch 'system-error + (lambda () + (symlink ,file + (string-append root-directory "/" ,%gc-root-file))) + (lambda args + ;; If FILE already exists, we can assume that either it's a stale + ;; reference (which is fine), or another process is already + ;; building the derivation represented by FILE (which is fine + ;; too.) Thus, do nothing in that case. + (unless (= EEXIST (system-error-errno args)) + (apply throw args))))))) (let ((pipe (remote-pipe machine OPEN_READ `("guile" "-c" ,(object->string script))))) @@ -535,7 +545,7 @@ allowed on MACHINE." (line (read-line pipe))) (close-pipe pipe) (if (eof-object? line) - 1. + +inf.0 ;MACHINE does not respond, so assume it is infinitely loaded (match (string-tokenize line) ((one five fifteen . _) (let* ((raw (string->number five)) @@ -546,7 +556,7 @@ allowed on MACHINE." (build-machine-name machine) raw normalized) normalized)) (_ - 1.))))) + +inf.0))))) ;something's fishy about MACHINE, so avoid it (define (machine-less-loaded? m1 m2) "Return #t if the load on M1 is lower than that on M2." diff --git a/guix/scripts/package.scm b/guix/scripts/package.scm index 6ecf37e1a6..95c0130c95 100644 --- a/guix/scripts/package.scm +++ b/guix/scripts/package.scm @@ -305,10 +305,12 @@ current settings and report only settings not already effective." ;; Use 'find-best-packages-by-name' and not 'find-packages-by-name'; ;; the former traverses the module tree only once and then allows for ;; efficient access via a vhash. - (match (or (find-best-packages-by-name name version) - (find-best-packages-by-name name #f)) + (match (find-best-packages-by-name name version) ((p _ ...) p) - (_ #f))))) + (_ + (match (find-best-packages-by-name name #f) + ((p _ ...) p) + (_ #f))))))) (define search-path-definition (match-lambda diff --git a/guix/scripts/pull.scm b/guix/scripts/pull.scm index c2bf536e86..5dafb84f91 100644 --- a/guix/scripts/pull.scm +++ b/guix/scripts/pull.scm @@ -38,15 +38,21 @@ "http://git.savannah.gnu.org/cgit/guix.git/snapshot/guix-master.tar.gz" ) -(define (unpack store tarball) +(define* (unpack store tarball #:key verbose?) "Return a derivation that unpacks TARBALL into STORE and compiles Scheme files." (define builder - '(begin + `(begin (use-modules (guix build pull)) (build-guix (assoc-ref %outputs "out") (assoc-ref %build-inputs "tarball") + + ;; XXX: This is not perfect, enabling VERBOSE? means + ;; building a different derivation. + #:debug-port (if ',verbose? + (current-error-port) + (%make-void-port "w")) #:tar (assoc-ref %build-inputs "tar") #:gzip (assoc-ref %build-inputs "gzip") #:gcrypt (assoc-ref %build-inputs "gcrypt")))) @@ -129,13 +135,10 @@ Download and deploy the latest version of Guix.\n")) (package-derivation store (if (assoc-ref opts 'bootstrap?) %bootstrap-guile - (canonical-package guile-2.0)))) - (current-build-output-port - (if (assoc-ref opts 'verbose?) - (current-error-port) - (%make-void-port "w")))) + (canonical-package guile-2.0))))) (let* ((config-dir (config-directory)) - (source (unpack store tarball)) + (source (unpack store tarball + #:verbose? (assoc-ref opts 'verbose?))) (source-dir (derivation->output-path source))) (if (show-what-to-build store (list source)) (if (build-derivations store (list source)) diff --git a/guix/scripts/system.scm b/guix/scripts/system.scm index 4f1869af38..7c0dde9030 100644 --- a/guix/scripts/system.scm +++ b/guix/scripts/system.scm @@ -28,7 +28,7 @@ #:use-module (guix profiles) #:use-module (guix scripts build) #:use-module (guix build utils) - #:use-module (guix build install) + #:use-module (gnu build install) #:use-module (gnu system) #:use-module (gnu system vm) #:use-module (gnu system grub) diff --git a/nix/nix-daemon/guix-daemon.cc b/nix/nix-daemon/guix-daemon.cc index 0bb9f7559c..8c63bed0bf 100644 --- a/nix/nix-daemon/guix-daemon.cc +++ b/nix/nix-daemon/guix-daemon.cc @@ -62,7 +62,7 @@ builds derivations on behalf of its clients."; #define GUIX_OPT_CACHE_FAILURES 4 #define GUIX_OPT_LOSE_LOGS 5 #define GUIX_OPT_DISABLE_LOG_COMPRESSION 6 -#define GUIX_OPT_DISABLE_STORE_OPTIMIZATION 7 +#define GUIX_OPT_DISABLE_DEDUPLICATION 7 #define GUIX_OPT_IMPERSONATE_LINUX_26 8 #define GUIX_OPT_DEBUG 9 #define GUIX_OPT_CHROOT_DIR 10 @@ -106,8 +106,14 @@ static const struct argp_option options[] = "Do not keep build logs" }, { "disable-log-compression", GUIX_OPT_DISABLE_LOG_COMPRESSION, 0, 0, "Disable compression of the build logs" }, - { "disable-store-optimization", GUIX_OPT_DISABLE_STORE_OPTIMIZATION, 0, 0, + + /* '--disable-deduplication' was known as '--disable-store-optimization' + up to Guix 0.7 included, so keep the alias around. */ + { "disable-deduplication", GUIX_OPT_DISABLE_DEDUPLICATION, 0, 0, "Disable automatic file \"deduplication\" in the store" }, + { "disable-store-optimization", GUIX_OPT_DISABLE_DEDUPLICATION, 0, + OPTION_ALIAS | OPTION_HIDDEN, NULL }, + { "impersonate-linux-2.6", GUIX_OPT_IMPERSONATE_LINUX_26, 0, 0, "Impersonate Linux 2.6" #ifndef HAVE_SYS_PERSONALITY_H @@ -163,7 +169,7 @@ parse_opt (int key, char *arg, struct argp_state *state) case GUIX_OPT_BUILD_USERS_GROUP: settings.buildUsersGroup = arg; break; - case GUIX_OPT_DISABLE_STORE_OPTIMIZATION: + case GUIX_OPT_DISABLE_DEDUPLICATION: settings.autoOptimiseStore = false; break; case GUIX_OPT_CACHE_FAILURES: @@ -249,6 +255,9 @@ main (int argc, char *argv[]) settings.useChroot = false; #endif + /* Turn automatic deduplication on by default. */ + settings.autoOptimiseStore = true; + argvSaved = argv; try @@ -326,6 +335,10 @@ main (int argc, char *argv[]) #endif printMsg (lvlDebug, + format ("automatic deduplication set to %1%") + % settings.autoOptimiseStore); + + printMsg (lvlDebug, format ("listening on `%1%'") % settings.nixDaemonSocketFile); run (nothing); diff --git a/nix/sync-with-upstream b/nix/sync-with-upstream index 720fae132e..e9cb070dcc 100755 --- a/nix/sync-with-upstream +++ b/nix/sync-with-upstream @@ -1,6 +1,6 @@ #!/bin/sh # GNU Guix --- Functional package management for GNU -# Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org> +# Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org> # # This file is part of GNU Guix. # @@ -65,7 +65,16 @@ done rm -fv "$top_srcdir/nix/libstore/schema.sql.hh" cp -v "$top_srcdir/nix-upstream/COPYING" "$top_srcdir/nix" -cp -v "$top_srcdir/nix-upstream/AUTHORS" "$top_srcdir/nix" + +# Generate an 'AUTHORS' file since upstream Nix no longer has one. +cat > "$top_srcdir/nix/AUTHORS" <<EOF +Most of the code is this directory was written by the following people for +the Nix project (http://nixos.org/nix). Thank you! + +EOF +( cd "$top_srcdir/nix-upstream" ; git shortlog --summary ) \ + | sed -'es/^ *[0-9]\+\(.*\)/ \1/g' \ + >> "$top_srcdir/nix/AUTHORS" # Substitutions. sed -i "$top_srcdir/nix/libstore/gc.cc" \ diff --git a/po/guix/Makevars b/po/guix/Makevars index 87bb438418..f5b498caa9 100644 --- a/po/guix/Makevars +++ b/po/guix/Makevars @@ -10,7 +10,8 @@ top_builddir = ../.. XGETTEXT_OPTIONS = \ --language=Scheme --from-code=UTF-8 \ --keyword=_ --keyword=N_ \ - --keyword=message + --keyword=message \ + --keyword=description COPYRIGHT_HOLDER = Ludovic Courtès diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in index bf2d31306a..5cc68ff404 100644 --- a/po/guix/POTFILES.in +++ b/po/guix/POTFILES.in @@ -10,6 +10,7 @@ guix/scripts/pull.scm guix/scripts/substitute-binary.scm guix/scripts/authenticate.scm guix/scripts/system.scm +guix/scripts/lint.scm guix/gnu-maintenance.scm guix/ui.scm guix/http-client.scm diff --git a/tests/derivations.scm b/tests/derivations.scm index 19bcebcb21..855b059d16 100644 --- a/tests/derivations.scm +++ b/tests/derivations.scm @@ -151,6 +151,28 @@ ;; the contents. (valid-path? %store (derivation->output-path drv))))) +(test-assert "identical files are deduplicated" + (let* ((build1 (add-text-to-store %store "one.sh" + "echo hello, world > \"$out\"\n" + '())) + (build2 (add-text-to-store %store "two.sh" + "# Hey!\necho hello, world > \"$out\"\n" + '())) + (drv1 (derivation %store "foo" + %bash `(,build1) + #:inputs `((,%bash) (,build1)))) + (drv2 (derivation %store "bar" + %bash `(,build2) + #:inputs `((,%bash) (,build2))))) + (and (build-derivations %store (list drv1 drv2)) + (let ((file1 (derivation->output-path drv1)) + (file2 (derivation->output-path drv2))) + (and (valid-path? %store file1) (valid-path? %store file2) + (string=? (call-with-input-file file1 get-string-all) + "hello, world\n") + (= (stat:ino (lstat file1)) + (stat:ino (lstat file2)))))))) + (test-assert "fixed-output-derivation?" (let* ((builder (add-text-to-store %store "my-fixed-builder.sh" "echo -n hello > $out" '())) diff --git a/tests/gexp.scm b/tests/gexp.scm index bf52401c66..ea4df48403 100644 --- a/tests/gexp.scm +++ b/tests/gexp.scm @@ -324,6 +324,78 @@ (return (string=? (derivation-file-name drv) (derivation-file-name xdrv))))) +(test-assertm "gexp->derivation, store copy" + (let ((build-one #~(call-with-output-file #$output + (lambda (port) + (display "This is the one." port)))) + (build-two (lambda (one) + #~(begin + (mkdir #$output) + (symlink #$one (string-append #$output "/one")) + (call-with-output-file (string-append #$output "/two") + (lambda (port) + (display "This is the second one." port)))))) + (build-drv #~(begin + (use-modules (guix build store-copy)) + + (mkdir #$output) + (populate-store '("graph") #$output)))) + (mlet* %store-monad ((one (gexp->derivation "one" build-one)) + (two (gexp->derivation "two" (build-two one))) + (drv (gexp->derivation "store-copy" build-drv + #:references-graphs + `(("graph" ,two)) + #:modules + '((guix build store-copy) + (guix build utils)))) + (ok? (built-derivations (list drv))) + (out -> (derivation->output-path drv))) + (let ((one (derivation->output-path one)) + (two (derivation->output-path two))) + (return (and ok? + (file-exists? (string-append out "/" one)) + (file-exists? (string-append out "/" two)) + (file-exists? (string-append out "/" two "/two")) + (string=? (readlink (string-append out "/" two "/one")) + one))))))) + +(test-assertm "gexp->derivation #:references-graphs" + (mlet* %store-monad + ((one (text-file "one" "hello, world")) + (two (gexp->derivation "two" + #~(symlink #$one #$output:chbouib))) + (drv (gexp->derivation "ref-graphs" + #~(begin + (use-modules (guix build store-copy)) + (with-output-to-file #$output + (lambda () + (write (call-with-input-file "guile" + read-reference-graph)))) + (with-output-to-file #$output:one + (lambda () + (write (call-with-input-file "one" + read-reference-graph)))) + (with-output-to-file #$output:two + (lambda () + (write (call-with-input-file "two" + read-reference-graph))))) + #:references-graphs `(("one" ,one) + ("two" ,two "chbouib") + ("guile" ,%bootstrap-guile)) + #:modules '((guix build store-copy) + (guix build utils)))) + (ok? (built-derivations (list drv))) + (guile-drv (package->derivation %bootstrap-guile)) + (g-one -> (derivation->output-path drv "one")) + (g-two -> (derivation->output-path drv "two")) + (g-guile -> (derivation->output-path drv))) + (return (and ok? + (equal? (call-with-input-file g-one read) (list one)) + (equal? (call-with-input-file g-two read) + (list one (derivation->output-path two "chbouib"))) + (equal? (call-with-input-file g-guile read) + (list (derivation->output-path guile-drv))))))) + (define shebang (string-append "#!" (derivation->output-path (%guile-for-build)) "/bin/guile --no-auto-compile")) diff --git a/tests/lint.scm b/tests/lint.scm new file mode 100644 index 0000000000..56558c904f --- /dev/null +++ b/tests/lint.scm @@ -0,0 +1,110 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2012, 2013 Cyril Roelandt <tipecaml@gmail.com> +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. + + +(define-module (test-packages) + #:use-module (guix build download) + #:use-module (guix build-system gnu) + #:use-module (guix packages) + #:use-module (guix scripts lint) + #:use-module (guix ui) + #:use-module (gnu packages) + #:use-module (gnu packages pkg-config) + #:use-module (srfi srfi-64)) + +;; Test the linter. + + +(test-begin "lint") + +(define-syntax-rule (dummy-package name* extra-fields ...) + (package extra-fields ... (name name*) (version "0") (source #f) + (build-system gnu-build-system) + (synopsis #f) (description #f) + (home-page #f) (license #f) )) + +(define (call-with-warnings thunk) + (let ((port (open-output-string))) + (parameterize ((guix-warning-port port)) + (thunk)) + (get-output-string port))) + +(test-assert "synopsis: ends with a period" + (->bool + (string-contains (call-with-warnings + (lambda () + (let ((pkg (dummy-package "x" + (synopsis "Bad synopsis.")))) + (check-synopsis-style pkg)))) + "no period allowed at the end of the synopsis"))) + +(test-assert "synopsis: ends with 'etc.'" + (->bool + (string-null? (call-with-warnings + (lambda () + (let ((pkg (dummy-package "x" + (synopsis "Foo, bar, etc.")))) + (check-synopsis-style pkg))))))) + +(test-assert "synopsis: starts with 'A'" + (->bool + (string-contains (call-with-warnings + (lambda () + (let ((pkg (dummy-package "x" + (synopsis "A bad synopŝis")))) + (check-synopsis-style pkg)))) + "no article allowed at the beginning of the synopsis"))) + +(test-assert "synopsis: starts with 'An'" + (->bool + (string-contains (call-with-warnings + (lambda () + (let ((pkg (dummy-package "x" + (synopsis "An awful synopsis")))) + (check-synopsis-style pkg)))) + "no article allowed at the beginning of the synopsis"))) + +(test-assert "inputs: pkg-config is probably a native input" + (->bool + (string-contains + (call-with-warnings + (lambda () + (let ((pkg (dummy-package "x" + (inputs `(("pkg-config" ,pkg-config)))))) + (check-inputs-should-be-native pkg)))) + "pkg-config should probably be a native input"))) + +(test-assert "patches: file names" + (->bool + (string-contains + (call-with-warnings + (lambda () + (let ((pkg (dummy-package "x" + (source + (origin + (method url-fetch) + (uri "someurl") + (sha256 "somesha") + (patches (list "/path/to/y.patch"))))))) + (check-patches pkg)))) + "file names of patches should start with the package name"))) + +(test-end "lint") + + +(exit (= (test-runner-fail-count (test-runner-current)) 0)) diff --git a/tests/profiles.scm b/tests/profiles.scm index 047c5ba49b..99f1fd2763 100644 --- a/tests/profiles.scm +++ b/tests/profiles.scm @@ -26,6 +26,8 @@ #:use-module (guix derivations) #:use-module (gnu packages bootstrap) #:use-module (ice-9 match) + #:use-module (ice-9 regex) + #:use-module (srfi srfi-11) #:use-module (srfi srfi-64)) ;; Test the (guix profiles) module. @@ -53,6 +55,13 @@ (manifest-entry (inherit guile-2.0.9) (output "debug"))) +(define glibc + (manifest-entry + (name "glibc") + (version "2.19") + (item "/gnu/store/...") + (output "out"))) + (test-begin "profiles") @@ -136,6 +145,34 @@ (equal? m1 m2) (null? (manifest-entries m3))))) +(test-assert "manifest-transaction-effects" + (let* ((m0 (manifest (list guile-1.8.8))) + (t (manifest-transaction + (install (list guile-2.0.9 glibc)) + (remove (list (manifest-pattern (name "coreutils"))))))) + (let-values (((remove install upgrade) + (manifest-transaction-effects m0 t))) + (and (null? remove) + (equal? (list glibc) install) + (equal? (list (cons guile-1.8.8 guile-2.0.9)) upgrade))))) + +(test-assert "manifest-show-transaction" + (let* ((m (manifest (list guile-1.8.8))) + (t (manifest-transaction (install (list guile-2.0.9))))) + (let-values (((remove install upgrade) + (manifest-transaction-effects m t))) + (with-store store + (and (string-match "guile\t1.8.8 → 2.0.9" + (with-fluids ((%default-port-encoding "UTF-8")) + (with-error-to-string + (lambda () + (manifest-show-transaction store m t))))) + (string-match "guile\t1.8.8 -> 2.0.9" + (with-fluids ((%default-port-encoding "ISO-8859-1")) + (with-error-to-string + (lambda () + (manifest-show-transaction store m t)))))))))) + (test-assert "profile-derivation" (run-with-store %store (mlet* %store-monad |