;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2013 Andreas Enge ;;; Copyright © 2014, 2015, 2017, 2020, 2021 Ludovic Courtès ;;; Copyright © 2015 Mark H Weaver ;;; Copyright © 2016, 2021 Efraim Flashner ;;; Copyright © 2017 Thomas Danckaert ;;; Copyright © 2017, 2019, 2020 Pierre Langlois ;;; Copyright © 2018–2021 Tobias Geerinckx-Rice ;;; Copyright © 2019 Ricardo Wurmus ;;; Copyright © 2020 Michael Rohleder ;;; Copyright © 2021 Simon Streit ;;; Copyright © 2021 Guillaume Le Vaillant ;;; Copyright © 2022 John Kehayias ;;; Copyright © 2022 Joeke de Graaf ;;; Copyright © 2023 Zheng Junjie <873216071@qq.com> ;;; ;;; This file is part of GNU Guix. ;;; ;;; GNU Guix is free software; you can redistribute it and/or modify it ;;; under the
aboutsummaryrefslogtreecommitdiff
blob: 3babf5d544501e47d982d9b1dc19ea4abca8d733 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2012, 2015, 2016, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
;;; Copyright © 2021 Brendan Tildesley <mail@brendan.scot>
;;;
;;; 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 build-utils)
  #:use-module (guix tests)
  #:use-module (guix build utils)
  #:use-module ((guix utils)
                #:select (%current-system call-with-temporary-directory))
  #:use-module (gnu packages)
  #:use-module (gnu packages bootstrap)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-35)
  #:use-module (srfi srfi-64)
  #:use-module (rnrs io ports)
  #:use-module (ice-9 popen))


(test-begin "build-utils")

(test-equal "alist-cons-before"
  '((a . 1) (x . 42) (b . 2) (c . 3))
  (alist-cons-before 'b 'x 42 '((a . 1) (b . 2) (c . 3))))

(test-assert "alist-cons-before, reference not found"
  (not (false-if-exception
        (alist-cons-before 'z 'x 42 '((a . 1) (b . 2) (c . 3))))))

(test-equal "alist-cons-after"
  '((a . 1) (b . 2) (x . 42) (c . 3))
  (alist-cons-after 'b 'x 42 '((a . 1) (b . 2) (c . 3))))

(test-assert "alist-cons-after, reference not found"
  (not (false-if-exception
        (alist-cons-after 'z 'x 42 '((a . 1) (b . 2) (c . 3))))))

(test-equal "alist-replace"
  '((a . 1) (b . 77) (c . 3))
  (alist-replace 'b 77 '((a . 1) (b . 2) (c . 3))))

(test-assert "alist-replace, key not found"
  (not (false-if-exception
        (alist-replace 'z 77 '((a . 1) (b . 2) (c . 3))))))

(test-equal "fold-port-matches"
  (make-list 3 "Guix")
  (call-with-input-string "Guix is cool, Guix rocks, and it uses Guile, Guix!"
    (lambda (port)
      (fold-port-matches cons '() "Guix" port))))

(test-equal "fold-port-matches, trickier"
  (reverse '("Guix" "guix" "Guix" "guiX" "Guix"))
  (call-with-input-string "Guix, guix, GuiGuixguiX, Guix"
    (lambda (port)
      (fold-port-matches cons '()
                         (list (char-set #\G #\g)
                               (char-set #\u)
                               (char-set #\i)
                               (char-set #\x #\X))
                         port))))

(test-equal "fold-port-matches, with unmatched chars"
  '("Guix" #\, #\space
    "guix" #\, #\space
    #\G #\u #\i "Guix" "guiX" #\, #\space
    "Guix")
  (call-with-input-string "Guix, guix, GuiGuixguiX, Guix"
    (lambda (port)
      (reverse
       (fold-port-matches cons '()
                          (list (char-set #\G #\g)
                                (char-set #\u)
                                (char-set #\i)
                                (char-set #\x #\X))
                          port
                          cons)))))

(test-equal "wrap-program, one input, multiple calls"
  "hello world\n"
  (call-with-temporary-directory
   (lambda (directory)
     (let ((bash (search-bootstrap-binary "bash" (%current-system)))
           (foo  (string-append directory "/foo")))

       (call-with-output-file foo
         (lambda (p)
           (format p
                   "#!~a~%echo \"${GUIX_FOO} ${GUIX_BAR}\"~%"
                   bash)))
       (chmod foo #o777)

       ;; wrap-program uses `which' to find bash for the wrapper shebang, but
       ;; it can't know about the bootstrap bash in the store, since it's not
       ;; named "bash".  Help it out a bit by providing a symlink it this
       ;; package's output.
       (with-environment-variable "PATH" (dirname bash)
         (wrap-program foo `("GUIX_FOO" prefix ("hello")))
         (wrap-program foo `("GUIX_BAR" prefix ("world")))

         ;; The bootstrap Bash is linked against an old libc and would abort
         ;; with an assertion failure when trying to load incompatible locale
         ;; data.
         (unsetenv "LOCPATH")

         (let* ((pipe (open-input-pipe foo))
                (str  (get-string-all pipe)))
           (with-directory-excursion directory
             (for-each delete-file '("foo" ".foo-real")))
           (and (zero? (close-pipe pipe))
                str)))))))

(test-assert "invoke/quiet, success"
  (begin
    (invoke/quiet "true")
    #t))

(test-assert "invoke/quiet, failure"
  (guard (c ((message-condition? c)
             (string-contains (condition-message c) "This is an error.")))
    (invoke/quiet "sh" "-c" "echo This is an error. ; false")
    #f))

(test-assert "invoke/quiet, failure, message on stderr"
  (guard (c ((message-condition? c)
             (string-contains (condition-message c)
                              "This is another error.")))
    (invoke/quiet "sh" "-c" "echo This is another error. >&2 ; false")
    #f))

(let ((script-contents "\
#!/anything/cabbage-bash-1.2.3/bin/sh

echo hello world"))

  (test-equal "wrap-script, simple case"
    (string-append
     (format #f "\
#!~a --no-auto-compile
#!#; Guix wrapper
#\\-~s
#\\-~s
"
             (which "guile")
             '(begin (let ((current (getenv "GUIX_FOO")))
                       (setenv "GUIX_FOO"
                               (if current
                                   (string-append "/some/path:/some/other/path"
                                                  ":" current)
                                   "/some/path:/some/other/path"))))
             '(let ((cl (command-line)))
                (apply execl "/anything/cabbage-bash-1.2.3/bin/sh"
                       (car cl) (append (quote ()) cl))))
     script-contents)
    (call-with-temporary-directory
     (lambda (directory)
       (let ((script-file-name (string-append directory "/foo")))
         (call-with-output-file script-file-name
           (lambda (port)
             (display script-contents port)))
         (chmod script-file-name #o777)
         (wrap-script script-file-name
                      `("GUIX_FOO" prefix ("/some/path"
                                           "/some/other/path")))
         (let ((str (call-with-input-file script-file-name get-string-all)))
           (with-directory-excursion directory
             (delete-file "foo"))
           str))))))

(let ((script-contents "\
#!/anything/cabbage-bash-1.2.3/bin/python3 -and -args
# vim:fileencoding=utf-8
print('hello world')"))

  (test-equal "wrap-script, with encoding declaration"
    (string-append
     (format #f "\
#!MYGUILE --no-auto-compile
#!#; # vim:fileencoding=utf-8
#\\-~s
#\\-~s
"
             '(begin (let ((current (getenv "GUIX_FOO")))
                       (setenv "GUIX_FOO"
                               (if current
                                   (string-append "/some/path:/some/other/path"
                                                  ":" current)
                                   "/some/path:/some/other/path"))))
             `(let ((cl (command-line)))
                (apply execl "/anything/cabbage-bash-1.2.3/bin/python3"
                       (car cl)
                       (append '("-and" "-args") cl))))
     script-contents)
    (call-with-temporary-directory
     (lambda (directory)
       (let ((script-file-name (string-append directory "/foo")))
         (call-with-output-file script-file-name
           (lambda (port)
             (format port script-contents)))
         (chmod script-file-name #o777)

         (wrap-script script-file-name
                      #:guile "MYGUILE"
                      `("GUIX_FOO" prefix ("/some/path"
                                           "/some/other/path")))
         (let ((str (call-with-input-file script-file-name get-string-all)))
           (with-directory-excursion directory
             (delete-file "foo"))
           str))))))

(test-assert "wrap-script, raises condition"
  (call-with-temporary-directory
   (lambda (directory)
     (let ((script-file-name (string-append directory "/foo")))
       (call-with-output-file script-file-name
         (lambda (port)
           (format port "This is not a script")))
       (chmod script-file-name #o777)
       (guard (c ((wrap-error? c) #t))
         (wrap-script script-file-name
                      #:guile "MYGUILE"
                      `("GUIX_FOO" prefix ("/some/path"
                                           "/some/other/path")))
         #f)))))

(define (arg-test bash-args)
  (call-with-temporary-directory
   (lambda (directory)
     (let ((script-file-name (string-append directory "/bash-test.sh")))
       (call-with-output-file script-file-name
         (lambda (port)
           (display (string-append "\
#!" (which "bash") bash-args "
echo \"$#$0$*${A}\"")
                    port)))

       (display "Unwrapped script contents:\n")
       (call-with-input-file script-file-name
         (lambda (port) (display (get-string-all port))))
       (newline) (newline)
       (chmod script-file-name #o777)
       (setenv "A" "A")
       (let* ((run-script (lambda _
                            (open-pipe*
                             OPEN_READ
                             script-file-name "1" "2" "3 3" "4")))
              (pipe (run-script))
              (unwrapped-output (get-string-all pipe)))
         (close-pipe pipe)

         (wrap-script script-file-name `("A" = ("A\nA")))

         (display "Wrapped script contents:\n")
         (call-with-input-file script-file-name
           (lambda (port) (display (get-string-all port))))
         (newline) (newline)

         (let* ((pipe (run-script))
                (wrapped-output (get-string-all pipe)))
           (close-pipe pipe)
           (display "./bash-test.sh 1 2 3\\ 3 4 # Output:\n")
           (display unwrapped-output) (newline)
           (display "./bash-test.sh 1 2 3\\ 3 4 # Output (wrapped):\n")
           (display wrapped-output) (newline)
           (string=? (string-append unwrapped-output "A\n")
                     wrapped-output)))))))

(test-assert "wrap-script, argument handling"
  (arg-test ""))

(test-assert "wrap-script, argument handling, bash --norc"
  (arg-test " --norc"))

(test-equal "substitute*, text contains a NUL byte, UTF-8"
  "c\0d"
  (with-fluids ((%default-port-encoding "UTF-8")
                (%default-port-conversion-strategy 'error))
    ;; The GNU libc is locale sensitive.  Depending on the value of LANG, the
    ;; test could fail with "string contains #\\nul character: ~S" or "cannot
    ;; convert wide string to output locale".
    (setlocale LC_ALL "en_US.UTF-8")
    (call-with-temporary-output-file
     (lambda (file port)
       (format port "a\0b")
       (flush-output-port port)

       (substitute* file
         (("a") "c")
         (("b") "d"))

       (with-input-from-file file
         (lambda _
           (get-string-all (current-input-port))))))))

(test-equal "search-input-file: exception if not found"
  `((path)
    (file . "does-not-exist"))
  (guard (e ((search-error? e)
             `((path . ,(search-error-path e))
               (file . ,(search-error-file e)))))
    (search-input-file '() "does-not-exist")))

(test-equal "search-input-file: can find if existent"
  (which "guile")
  (search-input-file
    `(("guile/bin" . ,(dirname (which "guile"))))
    "guile"))

(test-equal "search-input-file: can search in multiple directories"
  (which "guile")
  (call-with-temporary-directory
    (lambda (directory)
      (search-input-file
        `(("irrelevant" . ,directory)
          ("guile/bin" . ,(dirname (which "guile"))))
        "guile"))))

(test-end)
(method url-fetch) (uri (string-append "https://github.com/acoustid/chromaprint/releases/download/v" version "/chromaprint-" version ".tar.gz")) (sha256 (base32 "072y6c7ijkm6r674f6z089rbdazrmxzpdcsm6y6vf64b7gxdiam1")))) (build-system cmake-build-system) (arguments `(#:tests? #f ; tests require googletest *sources* ;;#:configure-flags '("-DBUILD_TESTS=ON") ; for building the tests #:configure-flags '("-DBUILD_TOOLS=ON") ; for fpcalc #:test-target "check")) (inputs ;; requires one of FFmpeg (prefered), FFTW3 or vDSP ;; use the same ffmpeg version as for acoustid-fingerprinter (list ffmpeg-4 boost)) (home-page "https://acoustid.org/chromaprint") (synopsis "Audio fingerprinting library") (description "Chromaprint is a library for calculating audio fingerprints which are used by the Acoustid service. Its main purpose is to provide an accurate identifier for record tracks.") (license license:lgpl2.1+))) (define-public python-audioread (package (name "python-audioread") (version "2.1.9") (source (origin (method url-fetch) (uri (pypi-uri "audioread" version)) (sha256 (base32 "129hab8x9sb3plff2bkq4xnzc3i8k9rgcm1a36l813kc0m10wj53")))) (build-system python-build-system) (arguments `(#:tests? #f)) ; there is no "audiofile" fixture (native-inputs (list python-pytest python-pytest-runner)) (home-page "https://github.com/sampsyo/audioread") (synopsis "Decode audio files using whichever backend is available") (description "This package provides a Python library for audo decoding. It uses whatever audio backend is available, such as GStreamer, Core Audio, MAD, FFmpeg, etc.") (license license:expat))) (define-public python-pyacoustid (package (name "python-pyacoustid") (version "1.2.2") (source (origin (method url-fetch) (uri (pypi-uri "pyacoustid" version)) (sha256 (base32 "0ha15m41r8ckmanc4k9nrlb9hprvhdjxndzw40a1yj3z1b1xjyf2")))) (build-system python-build-system) (arguments `(#:phases (modify-phases %standard-phases (add-after 'unpack 'chromaprint-path (lambda* (#:key inputs #:allow-other-keys) (substitute* "chromaprint.py" (("libchromaprint.so.1") (string-append (assoc-ref inputs "chromaprint") "/lib/libchromaprint.so.1"))) (substitute* "acoustid.py" (("'fpcalc'") (string-append "'" (assoc-ref inputs "chromaprint") "/bin/fpcalc'"))) #t))))) (inputs (list chromaprint)) (propagated-inputs (list python-audioread python-requests)) (home-page "https://github.com/beetbox/pyacoustid") (synopsis "Bindings for Chromaprint acoustic fingerprinting") (description "This package provides bindings for the Chromaprint acoustic fingerprinting library and the Acoustid API.") (license license:expat))) (define-public python-pytaglib (package (name "python-pytaglib") (version "1.4.6") (source (origin (method url-fetch) (uri (pypi-uri "pytaglib" version)) (sha256 (base32 "0li970qslfymz4vk1wrij2nfqw3l15cpc3fjjci48mpvg17gbnhn")) ;; Delete file generated by Cython. (modules '((guix build utils))) (snippet '(begin (delete-file "src/taglib.cpp"))))) (build-system python-build-system) (arguments '(#:phases (modify-phases %standard-phases ;; Ensure that the Cython file is regenerated. (add-after 'unpack 'setup-environment (lambda _ (setenv "PYTAGLIB_CYTHONIZE" "1")))))) (native-inputs (list python-cython python-pytest)) (inputs (list taglib)) (home-page "https://github.com/supermihi/pytaglib") (synopsis "Python bindings for taglib") (description "This package is a Python audio tagging library. It is cross-platform, works with all Python versions, and is very simple to use yet fully featured.") (license license:gpl3))) (define-public wavbreaker (package (name "wavbreaker") (version "0.15") (source (origin (method git-fetch) (uri (git-reference (url "https://github.com/thp/wavbreaker/") (commit version))) (file-name (git-file-name name version)) (sha256 (base32 "16h0sfcb8av6a368giizzwv9m0lq5c3bnf4b9vyyh9nkbbsc7c3j")))) (build-system meson-build-system) (arguments '(#:modules ((guix build utils) (guix build meson-build-system)) #:phases (modify-phases %standard-phases (add-after 'install 'wrap-program ;; This wrapping is necessary to make wavbreaker find things it ;; needs in pure environments. (lambda* (#:key inputs outputs #:allow-other-keys) (let ((out (assoc-ref outputs "out")) (adwaita-icons (assoc-ref inputs "adwaita-icon-theme")) (hicolor-icons (assoc-ref inputs "hicolor-icon-theme")) (shared-mime (assoc-ref inputs "shared-mime-info"))) (wrap-program (string-append out "/bin/wavbreaker") ;; Needed in order for wavbreakere to find the icons it needs `("XDG_DATA_DIRS" ":" prefix ,(map (lambda (package) (string-append package "/share")) `(,out ;for wavbreaker's icon ,adwaita-icons ,hicolor-icons ,shared-mime))) ;; This is necessary to load some pixbufs like Adwaita's ;; check-symbolic.svg and wavbreaker's own logo in the ;; 'about' section. `("GDK_PIXBUF_MODULE_FILE" = (,(getenv "GDK_PIXBUF_MODULE_FILE"))) ;; Needed for GTK's file chooser to not crash. `("GSETTINGS_SCHEMA_DIR" = (,(string-append (assoc-ref inputs "gtk+") "/share/glib-2.0/schemas")))))))))) (native-inputs (list pkg-config cmake)) (inputs (list glib gtk+ ao bash-minimal adwaita-icon-theme shared-mime-info hicolor-icon-theme gsettings-desktop-schemas)) (home-page "https://wavbreaker.sourceforge.io/") (synopsis "WAV and MP3 file splitter with a GUI") (description "Wavbreaker is a WAV and MP3 file splitter. It can be used to break up a WAV or MP3 audio file into multiple WAV files. Wavbreaker contains a helpful waveform display of the audio file being edited, to help the user in splitting the file at the right point. Wavbreaker also supports splitting MP3 files without re-encoding them, to preserve their original audio quality.") (license license:gpl2+)))