graph: Use 'derivation-input-derivation'.
[jackhill/guix/guix.git] / guix / scripts / size.scm
index 8f0cb7d..f549ce0 100644 (file)
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2015, 2016 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -21,7 +21,7 @@
   #:use-module (guix scripts)
   #:use-module (guix store)
   #:use-module (guix monads)
-  #:use-module (guix utils)
+  #:use-module (guix combinators)
   #:use-module (guix grafts)
   #:use-module (guix packages)
   #:use-module (guix derivations)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-34)
   #:use-module (srfi srfi-37)
   #:use-module (ice-9 match)
   #:use-module (ice-9 format)
+  #:use-module (ice-9 vlist)
   #:export (profile?
             profile-file
             profile-self-size
 (define substitutable-path-info*
   (store-lift substitutable-path-info))
 
-(define (query-path-info* item)
-  "Monadic version of 'query-path-info' that returns #f when ITEM is not in
-the store."
-  (lambda (store)
-    (guard (c ((nix-protocol-error? c)
-               ;; ITEM is not in the store; return #f.
-               (values #f store)))
-      (values (query-path-info store item) store))))
-
 (define (file-size item)
   "Return the size in bytes of ITEM, resorting to information from substitutes
 if ITEM is not in the store."
@@ -73,33 +66,45 @@ if ITEM is not in the store."
              ;; The nar size is an approximation, but a good one.
              (return (substitutable-nar-size info)))
             (()
-             (leave (_ "no available substitute information for '~a'~%")
+             (leave (G_ "no available substitute information for '~a'~%")
                     item)))))))
 
-(define* (display-profile profile #:optional (port (current-output-port)))
-  "Display PROFILE, a list of PROFILE objects, to PORT."
+(define profile-closure<?
+  (match-lambda*
+    ((($ <profile> name1 self1 total1)
+      ($ <profile> name2 self2 total2))
+     (< total1 total2))))
+
+(define profile-self<?
+  (match-lambda*
+    ((($ <profile> name1 self1 total1)
+      ($ <profile> name2 self2 total2))
+     (< self1 self2))))
+
+(define* (display-profile profile #:optional (port (current-output-port))
+                          #:key (profile<? profile-closure<?))
+  "Display PROFILE, a list of PROFILE objects, to PORT.  Sort entries
+according to PROFILE<?."
   (define MiB (expt 2 20))
 
   (format port "~64a ~8a ~a\n"
-          (_ "store item") (_ "total") (_ "self"))
+          (G_ "store item") (G_ "total") (G_ "self"))
   (let ((whole (reduce + 0 (map profile-self-size profile))))
     (for-each (match-lambda
                 (($ <profile> name self total)
                  (format port "~64a  ~6,1f  ~6,1f ~5,1f%\n"
                          name (/ total MiB) (/ self MiB)
                          (* 100. (/ self whole 1.)))))
-              (sort profile
-                    (match-lambda*
-                      ((($ <profile> _ _ total1) ($ <profile> _ _ total2))
-                       (> total1 total2)))))))
+              (sort profile (negate profile<?)))
+    (format port (G_ "total: ~,1f MiB~%") (/ whole MiB 1.))))
 
 (define display-profile*
   (lift display-profile %store-monad))
 
-(define (substitutable-requisites store item)
-  "Return the list of requisites of ITEM based on information available in
+(define (substitutable-requisites store items)
+  "Return the list of requisites of ITEMS based on information available in
 substitutes."
-  (let loop ((items  (list item))
+  (let loop ((items  items)
              (result '()))
     (match items
       (()
@@ -113,34 +118,45 @@ substitutes."
                (append (append-map substitutable-references info)
                        result)))))))
 
-(define (requisites* item)
+(define (requisites* items)
   "Return as a monadic value the requisites of ITEMS, based either on the
 information available in the local store or using information about
 substitutes."
   (lambda (store)
-    (guard (c ((nix-protocol-error? c)
-               (values (substitutable-requisites store item)
-                       store)))
-      (values (requisites store item) store))))
+    (let-values (((local missing)
+                  (partition (cut valid-path? store <>) items)))
+      (values (delete-duplicates
+               (append (requisites store local)
+                       (substitutable-requisites store missing)))
+              store))))
 
-(define (store-profile item)
+(define (store-profile items)
   "Return as a monadic value a list of <profile> objects representing the
-profile of ITEM and its requisites."
-  (mlet* %store-monad ((refs  (>>= (requisites* item)
+profile of ITEMS and their requisites."
+  (mlet* %store-monad ((refs  (>>= (requisites* items)
                                    (lambda (refs)
                                      (return (delete-duplicates
-                                              (cons item refs))))))
+                                              (append items refs))))))
                        (sizes (mapm %store-monad
                                     (lambda (item)
                                       (>>= (file-size item)
                                            (lambda (size)
                                              (return (cons item size)))))
                                     refs)))
+    (define size-table
+      (fold (lambda (pair result)
+              (match pair
+                ((item . size)
+                 (vhash-cons item size result))))
+            vlist-null sizes))
+
     (define (dependency-size item)
-      (mlet %store-monad ((deps (requisites* item)))
+      (mlet %store-monad ((deps (requisites* (list item))))
         (foldm %store-monad
                (lambda (item total)
-                 (return (+ (assoc-ref sizes item) total)))
+                 (return (+ (match (vhash-assoc item size-table)
+                              ((_ . size) size))
+                            total)))
                0
                (delete-duplicates (cons item deps)))))
 
@@ -196,13 +212,14 @@ the name of a PNG file."
            0
            (sort profiles
                  (match-lambda*
-                   ((($ <profile> _ _ total1) ($ <profile> _ _ total2))
+                   ((($ <profile> name1 self1 total1)
+                     ($ <profile> name2 self2 total2))
                     (> total1 total2))))))
 
   ;; TRANSLATORS: This is the title of a graph, meaning that the graph
   ;; represents a profile of the store (the "store" being the place where
   ;; packages are stored.)
-  (make-page-map (_ "store profile") data
+  (make-page-map (G_ "store profile") data
                  #:write-to-png file))
 
 \f
@@ -211,19 +228,22 @@ the name of a PNG file."
 ;;;
 
 (define (show-help)
-  (display (_ "Usage: guix size [OPTION]... PACKAGE
+  (display (G_ "Usage: guix size [OPTION]... PACKAGE
 Report the size of PACKAGE and its dependencies.\n"))
-  (display (_ "
+  (display (G_ "
       --substitute-urls=URLS
                          fetch substitute from URLS if they are authorized"))
-  (display (_ "
+  (display (G_ "
   -s, --system=SYSTEM    consider packages for SYSTEM--e.g., \"i686-linux\""))
-  (display (_ "
+  ;; TRANSLATORS: "closure" and "self" must not be translated.
+  (display (G_ "
+      --sort=KEY         sort according to KEY--\"closure\" or \"self\""))
+  (display (G_ "
   -m, --map-file=FILE    write to FILE a graphical map of disk usage"))
   (newline)
-  (display (_ "
+  (display (G_ "
   -h, --help             display this help and exit"))
-  (display (_ "
+  (display (G_ "
   -V, --version          display version information and exit"))
   (newline)
   (show-bug-report-information))
@@ -241,6 +261,15 @@ Report the size of PACKAGE and its dependencies.\n"))
                                      (string-tokenize arg)
                                      (alist-delete 'substitute-urls result))
                          rest)))
+        (option '("sort") #t #f
+                (lambda (opt name arg result . rest)
+                  (match arg
+                    ("closure"
+                     (alist-cons 'profile<? profile-closure<? result))
+                    ("self"
+                     (alist-cons 'profile<? profile-self<? result))
+                    (_
+                     (leave (G_ "~a: invalid sorting key~%") arg)))))
         (option '(#\m "map-file") #t #f
                 (lambda (opt name arg result)
                   (alist-cons 'map-file arg result)))
@@ -253,7 +282,8 @@ Report the size of PACKAGE and its dependencies.\n"))
                   (show-version-and-exit "guix size")))))
 
 (define %default-options
-  `((system . ,(%current-system))))
+  `((system . ,(%current-system))
+    (profile<? . ,profile-self<?)))
 
 \f
 ;;;
@@ -262,20 +292,22 @@ Report the size of PACKAGE and its dependencies.\n"))
 
 (define (guix-size . args)
   (with-error-handling
-    (let* ((opts     (parse-command-line args %options (list %default-options)))
+    (let* ((opts     (parse-command-line args %options (list %default-options)
+                                         #:build-options? #f))
            (files    (filter-map (match-lambda
                                    (('argument . file) file)
                                    (_ #f))
                                  opts))
+           (profile<? (assoc-ref opts 'profile<?))
            (map-file (assoc-ref opts 'map-file))
            (system   (assoc-ref opts 'system))
            (urls     (assoc-ref opts 'substitute-urls)))
       (match files
         (()
-         (leave (_ "missing store item argument\n")))
-        ((file)
+         (leave (G_ "missing store item argument\n")))
+        ((files ..1)
          (leave-on-EPIPE
-          ;; Turn off grafts because (1) hydra.gnu.org does not serve grafted
+          ;; Turn off grafts because (1) substitute servers do not serve grafted
           ;; packages, and (2) they do not make any difference on the
           ;; resulting size.
           (parameterize ((%graft? #f))
@@ -285,13 +317,13 @@ Report the size of PACKAGE and its dependencies.\n"))
                                  #:substitute-urls urls)
 
               (run-with-store store
-                (mlet* %store-monad ((item    (ensure-store-item file))
-                                     (profile (store-profile item)))
+                (mlet* %store-monad ((items   (mapm %store-monad
+                                                    ensure-store-item files))
+                                     (profile (store-profile items)))
                   (if map-file
                       (begin
                         (profile->page-map profile map-file)
                         (return #t))
-                      (display-profile* profile)))
-                #:system system)))))
-        ((files ...)
-         (leave (_ "too many arguments\n")))))))
+                      (display-profile* profile (current-output-port)
+                                        #:profile<? profile<?)))
+                #:system system)))))))))