From mboxrd@z Thu Jan 1 00:00:00 1970 From: Alex Kost Subject: Re: [PATCH 3/3] emacs: Add "Source" field to 'guix-info' buffers. Date: Tue, 11 Nov 2014 22:13:54 +0300 Message-ID: <877fz14kjx.fsf@gmail.com> References: <87egtcbwd0.fsf@gmail.com> <87lhnktgh7.fsf@gnu.org> <878ujkb475.fsf@gmail.com> <87d28wq9kj.fsf@gnu.org> <878ujj2nyo.fsf@gmail.com> <87y4riiqhf.fsf@gnu.org> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:35676) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XoGtG-0003mD-88 for guix-devel@gnu.org; Tue, 11 Nov 2014 14:14:19 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1XoGt6-00050K-C3 for guix-devel@gnu.org; Tue, 11 Nov 2014 14:14:10 -0500 In-Reply-To: <87y4riiqhf.fsf@gnu.org> ("Ludovic \=\?utf-8\?Q\?Court\=C3\=A8s\=22'\?\= \=\?utf-8\?Q\?s\?\= message of "Tue, 11 Nov 2014 00:29:48 +0100") List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org Sender: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org To: Ludovic =?utf-8?Q?Court=C3=A8s?= Cc: guix-devel@gnu.org --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Ludovic Court=C3=A8s (2014-11-11 02:29 +0300) wrote: > Alex Kost skribis: > >> Ludovic Court=C3=A8s (2014-11-10 01:43 +0300) wrote: [...] >>>>> With the interface you propose, things might be slightly confusing: >>>>> sometimes clicking on =E2=80=9CDownload=E2=80=9D will do nothing (bec= ause the source is >>>>> already there), sometime =E2=80=9CShow=E2=80=9D will work without =E2= =80=9CDownload=E2=80=9D, sometimes >>>>> not, etc. Also it takes up quite a bit of space. >>>> >>>> Sorry, I didn't get it. What space do you mean? >>> >>> I meant visual space in the user interface; visual clutter. >> >> Is that "too much information is bad"? Do you suggest to remove >> something? > > I think a single button is clearer than two buttons plus a label, but > that=E2=80=99s OK here since we can=E2=80=99t really do just one button. What can be done is: if a source is not in the store after pressing a =E2=80=9CShow=E2=80=9D button, a user may be asked whether he wants to down= load it or not. I think it should be less confusing (the modified patch is attached). WDYT? >>> But note that derivation outputs not obtained by a =E2=80=98build-deriv= ations=E2=80=99 >>> call with the current store connection may be garbage-collected anytime. >>> That makes it more difficult to reliably determine whether the >>> =E2=80=9CDownload=E2=80=9D button should be displayed; to be safe, it w= ould have to be >>> displayed by default. Then we=E2=80=99re very close to the current pat= ch, I >>> think, no? >> >> I just check whether a final source file exists in the store and that's >> all. I think it's reliable, isn't it? > > There=E2=80=99s still the possibility that (1) the source is there, so no > =E2=80=9CDownload=E2=80=9D button, (2) the source is GC=E2=80=99d, and (3= ) there=E2=80=99s still no > =E2=80=9CDownload=E2=80=9D button and trying to access the source fails g= racelessly. > > That=E2=80=99s probably not very common in practice, and easily fixed by = hitting > =E2=80=98g=E2=80=99, so maybe it=E2=80=99s not worth worrying. WDYT? Do you mean a user deleted the source (with "guix gc") when a =E2=80=9Cpack= age info=E2=80=9D buffer was displayed? Sure such cases are not (and I think c= annot be) handled. It's the same as if: 1) a user has a list of installed packages, 2) installs another package somewhere outside (e.g., in a shell with "guix package -i"), 3) and wonders why the list is not up-to-date. Of course it is not up-to-date! He needs to revert a buffer after that. [...] >> +(define (package-source-names package) >> + "Return a list of source names (URLs) of the PACKAGE." >> + (let ((source (package-source package))) >> + (and (origin? source) >> + (filter-map (lambda (uri) >> + (cond ((string? uri) >> + uri) >> + ((git-reference? uri) >> + (git-reference-url uri)) >> + (else #f))) >> + (list-maybe (origin-uri source)))))) > > The #f case above just leads to degraded display, not breakage, right? > (I=E2=80=99m asking because of the other things beyond string? and > git-reference?.) Yes, there _would_ be just "Source: =E2=80=93", but it will not happen beca= use there are no other things beyond a string URL and a git-reference URL. So the source field will be empty only for packages with a false source (like =E2=80=98mit-scheme=E2=80=99 or bootstrap packages). Sources for all= other packages will be displayed. You may check it by evaluating the following: (setcdr (assq 'output guix-list-column-format) '((name 20 t) (version 10 t) (output 9 t) (installed 12 t) (source 30 t))) then "M-x guix-all-available-packages" and sort by the "Source" column. --=-=-= Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename=0001-emacs-Add-Source-field-to-guix-info-buffers.patch Content-Transfer-Encoding: quoted-printable >From 733c5276bcb9ded008e9c0a4dbe2e5fb6561b5eb Mon Sep 17 00:00:00 2001 From: Alex Kost Date: Sun, 9 Nov 2014 11:03:39 +0300 Subject: [PATCH] emacs: Add "Source" field to 'guix-info' buffers. MIME-Version: 1.0 Content-Type: text/plain; charset=3DUTF-8 Content-Transfer-Encoding: 8bit Suggested by Ludovic Court=C3=A8s. * emacs/guix-info.el (guix-info-insert-methods, guix-info-displayed-params): Add 'source' parameter. (guix-package-info-source): New face. (guix-package-source): New button type. (guix-package-info-auto-find-source, guix-package-info-auto-download-sour= ce, guix-package-info-download-buffer): New variables. (guix-package-info-show-source, guix-package-info-insert-source-url, guix-package-info-insert-source, guix-package-info-download-source, guix-package-info-redisplay-after-download): New procedures. * emacs/guix-base.el (guix-param-titles): Add 'source' parameter. (guix-operation-prompt): Add 'prompt' argument. (guix-after-source-download-hook): New variable. (guix-package-source-path, guix-package-source-build-derivation): New procedures. * emacs/guix-main.scm (%package-param-alist): Add 'source' parameter. (package-source-names, package-source-derivation->store-path, package-source-path, package-source-build-derivation): New procedures. --- emacs/guix-base.el | 33 +++++++++++-- emacs/guix-info.el | 130 ++++++++++++++++++++++++++++++++++++++++++++++++= ++-- emacs/guix-main.scm | 49 ++++++++++++++++++++ 3 files changed, 204 insertions(+), 8 deletions(-) diff --git a/emacs/guix-base.el b/emacs/guix-base.el index a6e56dc..23575ac 100644 --- a/emacs/guix-base.el +++ b/emacs/guix-base.el @@ -82,6 +82,7 @@ Interactively, prompt for PATH. With prefix, use (id . "ID") (name . "Name") (version . "Version") + (source . "Source") (license . "License") (synopsis . "Synopsis") (description . "Description") @@ -100,6 +101,7 @@ Interactively, prompt for PATH. With prefix, use (id . "ID") (name . "Name") (version . "Version") + (source . "Source") (license . "License") (synopsis . "Synopsis") (description . "Description") @@ -954,13 +956,14 @@ ENTRIES is a list of package entries to get info abou= t packages." strings) (insert "\n"))) =20 -(defun guix-operation-prompt () +(defun guix-operation-prompt (&optional prompt) "Prompt a user for continuing the current operation. -Return non-nil, if the operation should be continued; nil otherwise." +Return non-nil, if the operation should be continued; nil otherwise. +Ask a user with PROMPT for continuing an operation." (let* ((option-keys (mapcar #'guix-operation-option-key guix-operation-options)) (keys (append '(?y ?n) option-keys)) - (prompt (concat (propertize "Continue operation?" + (prompt (concat (propertize (or prompt "Continue operation?") 'face 'minibuffer-prompt) " (" (mapconcat @@ -1035,6 +1038,30 @@ Each element from GENERATIONS is a generation number= ." 'switch-to-generation profile generation) operation-buffer))) =20 +(defun guix-package-source-path (package-id) + "Return a store file path to a source of a package PACKAGE-ID." + (message "Calculating the source derivation ...") + (guix-eval-read + (guix-make-guile-expression + 'package-source-path package-id))) + +(defvar guix-after-source-download-hook nil + "Hook run after successful performing a 'source-download' operation.") + +(defun guix-package-source-build-derivation (package-id &optional prompt) + "Build source derivation of a package PACKAGE-ID. +Ask a user with PROMPT for continuing an operation." + (when (or (not guix-operation-confirm) + (guix-operation-prompt (or prompt + "Build the source derivation?"))) + (guix-eval-in-repl + (guix-make-guile-expression + 'package-source-build-derivation + package-id + :use-substitutes? (or guix-use-substitutes 'f) + :dry-run? (or guix-dry-run 'f)) + nil 'source-download))) + ;;; Pull =20 diff --git a/emacs/guix-info.el b/emacs/guix-info.el index 70ae39c..cbf8f46 100644 --- a/emacs/guix-info.el +++ b/emacs/guix-info.el @@ -1,4 +1,4 @@ -;;; guix-info.el --- Info buffers for displaying entries +;;; guix-info.el --- Info buffers for displaying entries -*- lexical-bin= ding: t -*- =20 ;; Copyright =C2=A9 2014 Alex Kost =20 @@ -24,7 +24,6 @@ =20 ;;; Code: =20 -(require 'guix-history) (require 'guix-base) (require 'guix-utils) =20 @@ -107,6 +106,8 @@ number of characters, it will be split into several lin= es.") guix-info-insert-title-simple) (outputs guix-package-info-insert-outputs guix-info-insert-title-simple) + (source guix-package-info-insert-source + 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) @@ -121,6 +122,8 @@ number of characters, it will be split into several lin= es.") (name guix-package-info-name) (version guix-output-info-insert-version) (output guix-output-info-insert-output) + (source guix-package-info-insert-source + guix-info-insert-title-simple) (path guix-package-info-insert-output-path guix-info-insert-title-simple) (dependencies guix-package-info-insert-output-dependencies @@ -157,10 +160,11 @@ is a function, this function is called with parameter= title as argument.") =20 (defvar guix-info-displayed-params - '((package name version synopsis outputs location home-url + '((package name version synopsis outputs source location home-url license inputs native-inputs propagated-inputs description) - (output name version output synopsis path dependencies location home-u= rl - license inputs native-inputs propagated-inputs description) + (output name version output synopsis source path dependencies location + home-url license inputs native-inputs propagated-inputs + description) (installed path dependencies) (generation number prev-number current time path)) "List of displayed entry parameters. @@ -652,6 +656,122 @@ ENTRY is an alist with package info." 'guix-package-info-insert-output-path) =20 +;;; Inserting a source + +(defface guix-package-info-source + '((t :inherit link :underline nil)) + "Face used for a source URL of a package." + :group 'guix-package-info) + +(defcustom guix-package-info-auto-find-source nil + "If non-nil, find a source file after pressing a \"Show\" button. +If nil, just display the source file path without finding." + :type 'boolean + :group 'guix-package-info) + +(defcustom guix-package-info-auto-download-source t + "If nil, do not automatically download a source file if it doesn't exist. +After pressing a \"Show\" button, a derivation of the package +source is calculated and a store file path is displayed. If this +variable is non-nil and the source file does not exist in the +store, it will be automatically downloaded (with a possible +prompt depending on `guix-operation-confirm' variable)." + :type 'boolean + :group 'guix-package-info) + +(defvar guix-package-info-download-buffer nil + "Buffer from which a current download operation was performed.") + +(define-button-type 'guix-package-source + :supertype 'guix + 'face 'guix-package-info-source + 'help-echo "" + 'action (lambda (_) + ;; As a source may not be a real URL (e.g., "mirror://..."), + ;; no action is bound to a source button. + (message "Yes, this is the source URL. What did you expect?"))) + +(defun guix-package-info-insert-source-url (url &optional _) + "Make button from source URL and insert it at point." + (guix-insert-button url 'guix-package-source)) + +(defun guix-package-info-show-source (entry-id package-id) + "Show file name of a package source in the current info buffer. +Find the file if needed (see `guix-package-info-auto-find-source'). +ENTRY-ID is an ID of the current entry (package or output). +PACKAGE-ID is an ID of the package which source to show." + (let* ((entry (guix-get-entry-by-id entry-id guix-entries)) + (file (guix-package-source-path package-id))) + (or file + (error "Couldn't define file path of the package source")) + (let* ((new-entry (cons (cons 'source-file file) + entry)) + (entries (cl-substitute-if + new-entry + (lambda (entry) + (equal (guix-get-key-val entry 'id) + entry-id)) + guix-entries + :count 1))) + (guix-redisplay-buffer :entries entries) + (if (file-exists-p file) + (if guix-package-info-auto-find-source + (guix-find-file file) + (message "The source store path is displayed.")) + (if guix-package-info-auto-download-source + (guix-package-info-download-source package-id) + (message "The source does not exist in the store.")))))) + +(defun guix-package-info-download-source (package-id) + "Download a source of the package PACKAGE-ID." + (setq guix-package-info-download-buffer (current-buffer)) + (guix-package-source-build-derivation + package-id + "The source does not exist in the store. Download it?")) + +(defun guix-package-info-insert-source (source entry) + "Insert SOURCE from package ENTRY at point. +SOURCE is a list of URLs." + (guix-info-insert-indent) + (if (null source) + (guix-format-insert nil) + (let* ((source-file (guix-get-key-val entry 'source-file)) + (entry-id (guix-get-key-val entry 'id)) + (package-id (or (guix-get-key-val entry 'package-id) + entry-id))) + (if (null source-file) + (guix-info-insert-action-button + "Show" + (lambda (btn) + (guix-package-info-show-source (button-get btn 'entry-id) + (button-get btn 'package-id))) + "Show the source store path of the current package" + 'entry-id entry-id + 'package-id package-id) + (unless (file-exists-p source-file) + (guix-info-insert-action-button + "Download" + (lambda (btn) + (guix-package-info-download-source + (button-get btn 'package-id))) + "Download the source into the store" + 'package-id package-id)) + (guix-info-insert-val-simple source-file + #'guix-info-insert-file-path)) + (guix-info-insert-val-simple source + #'guix-package-info-insert-source-url))= )) + +(defun guix-package-info-redisplay-after-download () + "Redisplay an 'info' buffer after downloading the package source. +This function is used to hide a \"Download\" button if needed." + (when (buffer-live-p guix-package-info-download-buffer) + (guix-redisplay-buffer :buffer guix-package-info-download-buffer) + (setq guix-package-info-download-buffer nil))) + +(add-hook 'guix-after-source-download-hook + 'guix-package-info-redisplay-after-download) + + ;;; Displaying outputs =20 (guix-define-buffer-type info output diff --git a/emacs/guix-main.scm b/emacs/guix-main.scm index 62eeabb..e0bdccb 100644 --- a/emacs/guix-main.scm +++ b/emacs/guix-main.scm @@ -46,10 +46,12 @@ (ice-9 vlist) (ice-9 match) (srfi srfi-1) + (srfi srfi-2) (srfi srfi-11) (srfi srfi-19) (srfi srfi-26) (guix) + (guix git-download) (guix packages) (guix profiles) (guix licenses) @@ -252,6 +254,18 @@ Example: (license-name license))) (list-maybe (package-license package)))) =20 +(define (package-source-names package) + "Return a list of source names (URLs) of the PACKAGE." + (let ((source (package-source package))) + (and (origin? source) + (filter-map (lambda (uri) + (cond ((string? uri) + uri) + ((git-reference? uri) + (git-reference-url uri)) + (else #f))) + (list-maybe (origin-uri source)))))) + (define (package-unique? package) "Return #t if PACKAGE is a single package with such name/version." (null? (cdr (packages-by-name (package-name package) @@ -263,6 +277,7 @@ Example: (name . ,package-name) (version . ,package-version) (license . ,package-license-names) + (source . ,package-source-names) (synopsis . ,package-synopsis) (description . ,package-description) (home-url . ,package-home-page) @@ -867,3 +882,37 @@ OUTPUTS is a list of package outputs (may be an empty = list)." GENERATIONS is a list of generation numbers." (with-store store (delete-generations store profile generations))) + +(define (package-source-derivation->store-path derivation) + "Return a store path of the package source DERIVATION." + (match (derivation-outputs derivation) + ;; Source derivation is always (("out" . derivation)). + (((_ . output-drv)) + (derivation-output-path output-drv)) + (_ #f))) + +(define (package-source-path package-id) + "Return a store file path to a source of a package PACKAGE-ID." + (and-let* ((package (package-by-id package-id)) + (source (package-source package))) + (with-store store + (package-source-derivation->store-path + (package-source-derivation store source))))) + +(define* (package-source-build-derivation package-id #:key dry-run? + (use-substitutes? #t)) + "Build source derivation of a package PACKAGE-ID." + (and-let* ((package (package-by-id package-id)) + (source (package-source package))) + (with-store store + (let* ((derivation (package-source-derivation store source)) + (derivations (list derivation))) + (set-build-options store + #:use-substitutes? use-substitutes?) + (show-what-to-build store derivations + #:use-substitutes? use-substitutes? + #:dry-run? dry-run?) + (unless dry-run? + (build-derivations store derivations)) + (format #t "The source store path: ~a~%" + (package-source-derivation->store-path derivation)))))) --=20 2.1.2 --=-=-=--