help-guix
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Fast `guix environment --container' switch


From: Ludovic Courtès
Subject: Re: Fast `guix environment --container' switch
Date: Thu, 17 Dec 2020 22:52:51 +0100
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux)

Hi!

Pierre Neidhardt <mail@ambrevar.xyz> skribis:

> `guix environment --container ... -- my-foo-program` is great but a bit
> slow to start.
>
> Is there a way to speed this up?

The attached program (based on an experiment from 2018¹ with exciting
yet to date mythical prospects) picks a program from $PATH (typically
from your profile) and runs it in a container.  As in:

  guix run inkscape

It has less work to do compared to ‘guix environment’ so it is faster.

HTH!

Ludo’.

¹ https://lists.gnu.org/archive/html/help-guix/2018-01/msg00117.html

;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2018, 2019, 2020 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 scripts run)
  #:use-module (guix ui)
  #:use-module (guix utils)
  #:use-module (guix scripts)
  #:use-module (guix store)
  #:use-module (guix packages)
  #:use-module (guix derivations)
  #:use-module ((guix build utils) #:select (which mkdir-p))
  #:use-module (gnu build linux-container)
  #:use-module (gnu system file-systems)
  #:use-module (gnu packages)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-9 gnu)
  #:use-module (srfi srfi-11)
  #:use-module (srfi srfi-26)
  #:use-module (srfi srfi-37)
  #:use-module (ice-9 match)
  #:export (guix-run))

(define %options
  (list (option '(#\h "help") #f #f
                (lambda args
                  (show-help)
                  (exit 0)))
        (option '(#\V "version") #f #f
                (lambda args
                  (show-version-and-exit "guix run")))))

(define (show-help)
  (display (G_ "Usage: guix run PACKAGE COMMAND...
Run COMMAND from PACKAGE in a container.\n"))
  (newline)
  (display (G_ "
  -h, --help             display this help and exit"))
  (display (G_ "
  -V, --version          display version information and exit"))
  (newline)
  (show-bug-report-information))



(define (bind-mount-spec/ro item)
  (and (file-exists? item)
       (file-system
         (device item)
         (mount-point item)
         (type "none")
         (flags '(bind-mount read-only))
         (check? #f))))

(define (bind-mount-spec/rw item)
  (and (file-exists? item)
       (file-system
         (inherit (bind-mount-spec/ro item))
         (flags '(bind-mount)))))

;; Safe in which applications run.
(define-immutable-record-type <safe>
  (safe namespaces mappings)
  safe?
  (namespaces  safe-namespaces)
  (mappings    safe-mappings))

(define (application-safe items)
  "Return safe corresponding to the application whose dependencies are listed
in ITEMS."
  (define packages
    (map (compose (cut package-name->name+version <> #\-)
                  store-path-package-name)
         items))

  (define x11? (member "libx11" packages))
  (define dbus? (member "dbus" packages))
  (define alsa? (member "alsa-lib" packages))
  (define pulseaudio? (member "pulseaudio" packages))

  (define mappings
    (let-syntax ((if (syntax-rules ()
                       ((_ condition body)
                        (if condition
                            (or (and=> body list) '())
                            '()))))
                 (ro (identifier-syntax bind-mount-spec/ro))
                 (rw (identifier-syntax bind-mount-spec/rw)))
      `(,(rw "/var/run/nscd/socket")
        ,@(if x11? (rw (string-append (getenv "HOME") "/.Xauthority")))
        ,@(if x11? (rw "/tmp/.X11-unix"))
        ,@(if x11? (rw (string-append "/run/user/"
                                      (number->string (getuid)))))
        ,@(if dbus? (ro "/etc/machine-id"))
        ,@(if alsa? (rw "/dev/snd"))
        ,@(if pulseaudio? (rw (string-append (getenv "HOME") "/.pulse"))))))

  (define namespaces
    ;; X11 applications need to run in the same IPC namespace as
    ;; the server.
    (if x11?
        (fold delq %namespaces '(ipc net))
        %namespaces))

  (safe namespaces mappings))

(define %not-colon
  (char-set-complement (char-set #\:)))

(define (guix-run . args)
  (define (parse-options)
    ;; Return the alist of option values.  With this hack, the first
    ;; non-option argument is considered to be the beginning of the command.
    (let-values (((args command) (span (cut string-prefix? "-" <>) args)))
      (args-fold* args %options
                  (lambda (opt name arg result)
                    (leave (G_ "~A: unrecognized option~%") name))
                  (lambda (arg result)
                    (pk 'arg arg)
                    (alist-cons 'argument arg result))
                  '())
      command))

  (with-error-handling
    (match (parse-options)
      ((command args ...)
       (with-store store
         (let* ((full     (search-path (string-tokenize (getenv "PATH") 
%not-colon)
                                       command))
                (resolved (and=> full readlink*))
                (prefix   (and=> resolved (lambda (file)
                                            (and (store-path? file)
                                                 (direct-store-path file))))))
           (unless full
             (leave (G_ "command '~a' not found~%") command))
           (unless prefix
             (leave (G_ "command '~a' is not in '~a'~%")
                    command (%store-prefix)))

           (let* ((items (requisites store (list prefix)))
                  (safe  (application-safe items))
                  (env   (environ))
                  (cwd   (getcwd)))

             (call-with-container
                 (append (map bind-mount-spec/ro items)
                         (safe-mappings safe)
                         (list (bind-mount-spec/ro cwd))) ;XXX: avoid that?
               (lambda ()
                 (environ env)                    ;TODO: filter ENV
                 (mkdir-p (getenv "HOME"))
                 (chdir cwd)

                 (newline)
                 (catch #t
                   (lambda ()
                     (apply execl resolved command args))
                   (lambda (key . args)
                     (print-exception (current-error-port) #f key args)
                     (exit 1))))

               #:namespaces (safe-namespaces safe)))))))))

reply via email to

[Prev in Thread] Current Thread [Next in Thread]