diff options
| -rw-r--r-- | doc/guix.texi | 60 | ||||
| -rw-r--r-- | guix/scripts/pack.scm | 104 | ||||
| -rw-r--r-- | tests/pack.scm | 40 |
3 files changed, 198 insertions, 6 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index 8297ad2ca6e..5953fcfa26a 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -6955,6 +6955,16 @@ directly be used as a file system container image with the | |||
| 6955 | environment}, using commands like @command{singularity shell} or | 6955 | environment}, using commands like @command{singularity shell} or |
| 6956 | @command{singularity exec}. | 6956 | @command{singularity exec}. |
| 6957 | 6957 | ||
| 6958 | @cindex AppImage, create an AppImage file with @command{guix pack} | ||
| 6959 | Another format internally based on SquashFS is | ||
| 6960 | @uref{https://appimage.org/, AppImage}. An AppImage file can be created | ||
| 6961 | and executed without any special privileges: | ||
| 6962 | |||
| 6963 | @example | ||
| 6964 | file=$(guix pack -f appimage --entry-point=bin/guile guile) | ||
| 6965 | $file --help | ||
| 6966 | @end example | ||
| 6967 | |||
| 6958 | Several command-line options allow you to customize your pack: | 6968 | Several command-line options allow you to customize your pack: |
| 6959 | 6969 | ||
| 6960 | @table @code | 6970 | @table @code |
| @@ -7071,6 +7081,48 @@ to install Guix-produced @samp{.rpm} packages on a system where | |||
| 7071 | installation or other, non-rpm packs. | 7081 | installation or other, non-rpm packs. |
| 7072 | @end quotation | 7082 | @end quotation |
| 7073 | 7083 | ||
| 7084 | @item appimage | ||
| 7085 | @cindex AppImage, create an AppImage file with @command{guix pack} | ||
| 7086 | This produces an @uref{https://appimage.org/, AppImage file} with the | ||
| 7087 | @samp{.AppImage} extension. AppImage is a SquashFS volume prefixed with | ||
| 7088 | a runtime that mounts the SquashFS file system and executes the binary | ||
| 7089 | provided with @option{--entry-point}. This results in a self-contained | ||
| 7090 | archive that bundles the software and all its requirements into a single | ||
| 7091 | file. When the file is made executable it runs the packaged software. | ||
| 7092 | |||
| 7093 | @example | ||
| 7094 | guix pack -f appimage --entry-point=bin/vlc vlc | ||
| 7095 | @end example | ||
| 7096 | |||
| 7097 | The runtime used by AppImages invokes the @command{fusermount3} command | ||
| 7098 | to mount the image quickly. If that command is unavailable, the | ||
| 7099 | AppImage fails to run, but it can still be started by passing the | ||
| 7100 | @option{--appimage-extract-and-run} flag. | ||
| 7101 | |||
| 7102 | @quotation Warning | ||
| 7103 | When building an AppImage, always @emph{pass} the @option{--relocatable} | ||
| 7104 | option (or @option{-R}, or @option{-RR}) to make sure the image can be | ||
| 7105 | used on systems where Guix is not installed. A warning is printed when | ||
| 7106 | this option is not used. | ||
| 7107 | @end quotation | ||
| 7108 | |||
| 7109 | @example | ||
| 7110 | guix pack -f appimage --entry-point=bin/hello --relocatable hello | ||
| 7111 | @end example | ||
| 7112 | |||
| 7113 | @quotation Note | ||
| 7114 | The resulting AppImage does not conform to the complete standard as it | ||
| 7115 | currently does not contain a @file{.DirIcon} file. This does not impact | ||
| 7116 | functionality of the AppImage itself, but possibly that of software used | ||
| 7117 | to manage AppImages. | ||
| 7118 | @end quotation | ||
| 7119 | |||
| 7120 | @quotation Note | ||
| 7121 | As the generated AppImage packages the complete dependency graph, it | ||
| 7122 | will be larger than comparable AppImage files found online, which depend | ||
| 7123 | on host system libraries. | ||
| 7124 | @end quotation | ||
| 7125 | |||
| 7074 | @end table | 7126 | @end table |
| 7075 | 7127 | ||
| 7076 | @cindex relocatable binaries | 7128 | @cindex relocatable binaries |
| @@ -7160,10 +7212,10 @@ execution engines listed above by setting the | |||
| 7160 | 7212 | ||
| 7161 | @cindex entry point, for Docker and Singularity images | 7213 | @cindex entry point, for Docker and Singularity images |
| 7162 | @item --entry-point=@var{command} | 7214 | @item --entry-point=@var{command} |
| 7163 | Use @var{command} as the @dfn{entry point} of the resulting pack, if the pack | 7215 | Use @var{command} as the @dfn{entry point} of the resulting pack, if the |
| 7164 | format supports it---currently @code{docker} and @code{squashfs} (Singularity) | 7216 | pack format supports it---currently @code{docker}, @code{appimage}, and |
| 7165 | support it. @var{command} must be relative to the profile contained in the | 7217 | @code{squashfs} (Singularity) support it. @var{command} must be |
| 7166 | pack. | 7218 | relative to the profile contained in the pack. |
| 7167 | 7219 | ||
| 7168 | The entry point specifies the command that tools like @code{docker run} or | 7220 | The entry point specifies the command that tools like @code{docker run} or |
| 7169 | @code{singularity run} automatically start by default. For example, you can | 7221 | @code{singularity run} automatically start by default. For example, you can |
diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm index 7c5fe76fe0b..26ba80b80d8 100644 --- a/guix/scripts/pack.scm +++ b/guix/scripts/pack.scm | |||
| @@ -10,6 +10,8 @@ | |||
| 10 | ;;; Copyright © 2022 Alex Griffin <a@ajgrf.com> | 10 | ;;; Copyright © 2022 Alex Griffin <a@ajgrf.com> |
| 11 | ;;; Copyright © 2023 Graham James Addis <graham@addis.org.uk> | 11 | ;;; Copyright © 2023 Graham James Addis <graham@addis.org.uk> |
| 12 | ;;; Copyright © 2023 Oleg Pykhalov <go.wigust@gmail.com> | 12 | ;;; Copyright © 2023 Oleg Pykhalov <go.wigust@gmail.com> |
| 13 | ;;; Copyright © 2024 Sebastian Dümcke <code@sam-d.com> | ||
| 14 | ;;; Copyright © 2024 Noé Lopez <noelopez@free.fr> | ||
| 13 | ;;; | 15 | ;;; |
| 14 | ;;; This file is part of GNU Guix. | 16 | ;;; This file is part of GNU Guix. |
| 15 | ;;; | 17 | ;;; |
| @@ -56,6 +58,7 @@ | |||
| 56 | #:use-module ((gnu packages compression) #:hide (zip)) | 58 | #:use-module ((gnu packages compression) #:hide (zip)) |
| 57 | #:use-module (gnu packages guile) | 59 | #:use-module (gnu packages guile) |
| 58 | #:use-module (gnu packages base) | 60 | #:use-module (gnu packages base) |
| 61 | #:autoload (gnu packages appimage) (appimage-type2-runtime) | ||
| 59 | #:autoload (gnu packages gnupg) (guile-gcrypt) | 62 | #:autoload (gnu packages gnupg) (guile-gcrypt) |
| 60 | #:autoload (gnu packages guile) (guile2.0-json guile-json) | 63 | #:autoload (gnu packages guile) (guile2.0-json guile-json) |
| 61 | #:use-module (srfi srfi-1) | 64 | #:use-module (srfi srfi-1) |
| @@ -64,6 +67,7 @@ | |||
| 64 | #:use-module (srfi srfi-35) | 67 | #:use-module (srfi srfi-35) |
| 65 | #:use-module (srfi srfi-37) | 68 | #:use-module (srfi srfi-37) |
| 66 | #:use-module (ice-9 match) | 69 | #:use-module (ice-9 match) |
| 70 | #:use-module (ice-9 optargs) | ||
| 67 | #:export (symlink-spec-option-parser | 71 | #:export (symlink-spec-option-parser |
| 68 | 72 | ||
| 69 | self-contained-tarball | 73 | self-contained-tarball |
| @@ -71,6 +75,7 @@ | |||
| 71 | rpm-archive | 75 | rpm-archive |
| 72 | docker-image | 76 | docker-image |
| 73 | squashfs-image | 77 | squashfs-image |
| 78 | self-contained-appimage | ||
| 74 | 79 | ||
| 75 | %formats | 80 | %formats |
| 76 | guix-pack)) | 81 | guix-pack)) |
| @@ -974,8 +979,100 @@ PREUN-FILE and POSTUN-FILE can be provided via EXTRA-OPTIONS." | |||
| 974 | (gexp->derivation (string-append name ".rpm") build | 979 | (gexp->derivation (string-append name ".rpm") build |
| 975 | #:target target | 980 | #:target target |
| 976 | #:references-graphs `(("profile" ,profile)))) | 981 | #:references-graphs `(("profile" ,profile)))) |
| 982 | |||
| 983 | ;;; | ||
| 984 | ;;; AppImage format | ||
| 985 | ;;; | ||
| 986 | (define* (self-contained-appimage name profile | ||
| 987 | #:key target | ||
| 988 | (profile-name "guix-profile") | ||
| 989 | entry-point | ||
| 990 | (compressor (lookup-compressor "zstd")) | ||
| 991 | localstatedir? | ||
| 992 | (symlinks '()) | ||
| 993 | (archiver tar) | ||
| 994 | (extra-options '())) | ||
| 995 | "Return a self-contained AppImage containing a store initialized with the | ||
| 996 | closure of PROFILE, a derivation. The AppImage contains /gnu/store unless | ||
| 997 | RELOCATABLE option is used; if LOCALSTATEDIR? is true, it also contains | ||
| 998 | /var/guix, including /var/guix/db with a properly initialized store database. | ||
| 999 | |||
| 1000 | SYMLINKS must be a list of (SOURCE -> TARGET) tuples denoting symlinks to be | ||
| 1001 | added to the pack." | ||
| 1002 | (unless entry-point | ||
| 1003 | (leave (G_ "entry-point must be provided in the '~a' format~%") | ||
| 1004 | 'appimage)) | ||
| 1005 | (let-keywords extra-options #f ((relocatable? #f)) | ||
| 1006 | (unless relocatable? | ||
| 1007 | (warning (G_ "AppImages should be built with the --relocatable flag~%")))) | ||
| 1008 | |||
| 1009 | (define runtime-package appimage-type2-runtime) | ||
| 1010 | (define runtime-path "bin/runtime-fuse3") | ||
| 1011 | (define %valid-compressors '("gzip" "zstd")) | ||
| 1012 | |||
| 1013 | (let ((compressor-name (compressor-name compressor))) | ||
| 1014 | (unless (member compressor-name %valid-compressors) | ||
| 1015 | (leave (G_ "~a is not a valid squashfs archive compressor used in | ||
| 1016 | generating the AppImage. Valid compressors are: ~a~%") | ||
| 1017 | compressor-name | ||
| 1018 | %valid-compressors))) | ||
| 977 | 1019 | ||
| 978 | 1020 | (define builder | |
| 1021 | (with-extensions (list guile-gcrypt) | ||
| 1022 | (with-imported-modules (source-module-closure | ||
| 1023 | '((guix build store-copy) | ||
| 1024 | (guix build utils)) | ||
| 1025 | #:select? not-config?) | ||
| 1026 | #~(begin | ||
| 1027 | (use-modules (guix build utils) | ||
| 1028 | (guix build store-copy) | ||
| 1029 | (rnrs io ports) | ||
| 1030 | (srfi srfi-1) | ||
| 1031 | (srfi srfi-26)) | ||
| 1032 | |||
| 1033 | (define (concatenate-files result file1 file2) | ||
| 1034 | "Creates a new file RESULT containing FILE1 followed by FILE2." | ||
| 1035 | (call-with-output-file result | ||
| 1036 | (lambda (output) | ||
| 1037 | (call-with-input-file file1 | ||
| 1038 | (lambda (input) | ||
| 1039 | (dump-port input output))) | ||
| 1040 | (call-with-input-file file2 | ||
| 1041 | (lambda (input) | ||
| 1042 | (dump-port input output)))))) | ||
| 1043 | |||
| 1044 | (let* ((appdir "AppDir") | ||
| 1045 | (squashfs "squashfs") | ||
| 1046 | (profile-items (map store-info-item | ||
| 1047 | (call-with-input-file "profile" read-reference-graph))) | ||
| 1048 | (profile (find (lambda (item) | ||
| 1049 | (string-suffix? "-profile" item)) | ||
| 1050 | profile-items))) | ||
| 1051 | (mkdir-p appdir) | ||
| 1052 | ;; Copy all store items from the profile to the AppDir. | ||
| 1053 | (populate-store '("profile") appdir) | ||
| 1054 | ;; Symlink the provided entry-point to AppDir/AppRun. | ||
| 1055 | (symlink (string-append "." profile "/" #$entry-point) | ||
| 1056 | (string-append appdir "/AppRun")) | ||
| 1057 | ;; Create .desktop file as required by the spec. | ||
| 1058 | (make-desktop-entry-file | ||
| 1059 | (string-append appdir "/" #$name ".desktop") | ||
| 1060 | #:name #$name | ||
| 1061 | #:exec #$entry-point) | ||
| 1062 | ;; Compress the AppDir. | ||
| 1063 | (invoke #+(file-append squashfs-tools "/bin/mksquashfs") appdir | ||
| 1064 | squashfs "-root-owned" "-noappend" | ||
| 1065 | "-comp" #+(compressor-name compressor)) | ||
| 1066 | ;; Append runtime and squashFS into file AppImage. | ||
| 1067 | (concatenate-files #$output | ||
| 1068 | #$(file-append runtime-package "/" runtime-path) | ||
| 1069 | squashfs) | ||
| 1070 | ;; Add execution permission. | ||
| 1071 | (chmod #$output #o555)))))) | ||
| 1072 | (gexp->derivation (string-append name ".AppImage") builder | ||
| 1073 | #:target target | ||
| 1074 | #:references-graphs `(("profile" ,profile)))) | ||
| 1075 | |||
| 979 | ;;; | 1076 | ;;; |
| 980 | ;;; Compiling C programs. | 1077 | ;;; Compiling C programs. |
| 981 | ;;; | 1078 | ;;; |
| @@ -1311,6 +1408,7 @@ libfakechroot.so and related ld.so machinery as a fallback." | |||
| 1311 | (squashfs . ,squashfs-image) | 1408 | (squashfs . ,squashfs-image) |
| 1312 | (docker . ,docker-image) | 1409 | (docker . ,docker-image) |
| 1313 | (deb . ,debian-archive) | 1410 | (deb . ,debian-archive) |
| 1411 | (appimage . ,self-contained-appimage) | ||
| 1314 | (rpm . ,rpm-archive))) | 1412 | (rpm . ,rpm-archive))) |
| 1315 | 1413 | ||
| 1316 | (define (show-formats) | 1414 | (define (show-formats) |
| @@ -1327,6 +1425,8 @@ libfakechroot.so and related ld.so machinery as a fallback." | |||
| 1327 | deb Debian archive installable via dpkg/apt")) | 1425 | deb Debian archive installable via dpkg/apt")) |
| 1328 | (display (G_ " | 1426 | (display (G_ " |
| 1329 | rpm RPM archive installable via rpm/yum")) | 1427 | rpm RPM archive installable via rpm/yum")) |
| 1428 | (display (G_ " | ||
| 1429 | appimage AppImage self-contained and executable format")) | ||
| 1330 | (newline)) | 1430 | (newline)) |
| 1331 | 1431 | ||
| 1332 | (define (required-option symbol) | 1432 | (define (required-option symbol) |
| @@ -1694,6 +1794,8 @@ Create a bundle of PACKAGE.\n")) | |||
| 1694 | (process-file-arg opts 'preun-file) | 1794 | (process-file-arg opts 'preun-file) |
| 1695 | #:postun-file | 1795 | #:postun-file |
| 1696 | (process-file-arg opts 'postun-file))) | 1796 | (process-file-arg opts 'postun-file))) |
| 1797 | ('appimage | ||
| 1798 | (list #:relocatable? relocatable?)) | ||
| 1697 | (_ '()))) | 1799 | (_ '()))) |
| 1698 | (target (assoc-ref opts 'target)) | 1800 | (target (assoc-ref opts 'target)) |
| 1699 | (bootstrap? (assoc-ref opts 'bootstrap?)) | 1801 | (bootstrap? (assoc-ref opts 'bootstrap?)) |
diff --git a/tests/pack.scm b/tests/pack.scm index f8a9e09c284..1c1e3125575 100644 --- a/tests/pack.scm +++ b/tests/pack.scm | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net> | 3 | ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net> |
| 4 | ;;; Copyright © 2021, 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com> | 4 | ;;; Copyright © 2021, 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com> |
| 5 | ;;; Copyright © 2023 Oleg Pykhalov <go.wigust@gmail.com> | 5 | ;;; Copyright © 2023 Oleg Pykhalov <go.wigust@gmail.com> |
| 6 | ;;; Copyright © 2024 Noé Lopez <noelopez@free.fr> | ||
| 6 | ;;; | 7 | ;;; |
| 7 | ;;; This file is part of GNU Guix. | 8 | ;;; This file is part of GNU Guix. |
| 8 | ;;; | 9 | ;;; |
| @@ -32,7 +33,8 @@ | |||
| 32 | #:use-module (guix utils) | 33 | #:use-module (guix utils) |
| 33 | #:use-module ((guix build utils) #:select (%store-directory)) | 34 | #:use-module ((guix build utils) #:select (%store-directory)) |
| 34 | #:use-module (gnu packages) | 35 | #:use-module (gnu packages) |
| 35 | #:use-module ((gnu packages base) #:select (libc-utf8-locales-for-target)) | 36 | #:use-module ((gnu packages base) #:select (libc-utf8-locales-for-target |
| 37 | hello)) | ||
| 36 | #:use-module (gnu packages bootstrap) | 38 | #:use-module (gnu packages bootstrap) |
| 37 | #:use-module ((gnu packages package-management) #:select (rpm)) | 39 | #:use-module ((gnu packages package-management) #:select (rpm)) |
| 38 | #:use-module ((gnu packages compression) #:select (squashfs-tools)) | 40 | #:use-module ((gnu packages compression) #:select (squashfs-tools)) |
| @@ -341,6 +343,42 @@ | |||
| 341 | (built-derivations (list check)))) | 343 | (built-derivations (list check)))) |
| 342 | 344 | ||
| 343 | (unless store (test-skip 1)) | 345 | (unless store (test-skip 1)) |
| 346 | (test-assertm "appimage" | ||
| 347 | (mlet* %store-monad | ||
| 348 | ((guile (set-guile-for-build (default-guile))) | ||
| 349 | (profile -> (profile | ||
| 350 | (content (packages->manifest (list %bootstrap-guile hello))) | ||
| 351 | (hooks '()) | ||
| 352 | (locales? #f))) | ||
| 353 | (image (self-contained-appimage "hello-appimage" profile | ||
| 354 | #:entry-point "bin/hello" | ||
| 355 | #:extra-options | ||
| 356 | (list #:relocatable? #t))) | ||
| 357 | (check (gexp->derivation | ||
| 358 | "check-appimage" | ||
| 359 | #~(invoke #$image)))) | ||
| 360 | (built-derivations (list check)))) | ||
| 361 | |||
| 362 | (unless store (test-skip 1)) | ||
| 363 | (test-assertm "appimage + localstatedir" | ||
| 364 | (mlet* %store-monad | ||
| 365 | ((guile (set-guile-for-build (default-guile))) | ||
| 366 | (profile -> (profile | ||
| 367 | (content (packages->manifest (list %bootstrap-guile hello))) | ||
| 368 | (hooks '()) | ||
| 369 | (locales? #f))) | ||
| 370 | (image (self-contained-appimage "hello-appimage" profile | ||
| 371 | #:entry-point "bin/hello" | ||
| 372 | #:localstatedir? #t | ||
| 373 | #:extra-options | ||
| 374 | (list #:relocatable? #t))) | ||
| 375 | (check (gexp->derivation | ||
| 376 | "check-appimage" | ||
| 377 | #~(begin | ||
| 378 | (invoke #$image))))) | ||
| 379 | (built-derivations (list check)))) | ||
| 380 | |||
| 381 | (unless store (test-skip 1)) | ||
| 344 | (test-assertm "deb archive with symlinks and control files" | 382 | (test-assertm "deb archive with symlinks and control files" |
| 345 | (mlet* %store-monad | 383 | (mlet* %store-monad |
| 346 | ((guile (set-guile-for-build (default-guile))) | 384 | ((guile (set-guile-for-build (default-guile))) |
