Next: , Previous: , Up: Программный интерфейс   [Contents][Index]


8.7 Build Utilities

Как только вы начнете писать нетривиальные определения пакетов (see Описание пакетов) или другие действия сборки (see G-Expressions), вы, скорее всего, начнете искать помощников для действий, подобных оболочке—создание каталогов, рекурсивное копирование и удаление файлов, управление этапами сборки и т.д. Модуль (guix build utils) предоставляет такие служебные процедуры.

Большинство систем сборки загружают (guix build utils) (see Системы сборки). Таким образом, при написании настраиваемых фаз сборки для определений пакетов вы обычно можете предположить, что эти процедуры входят в область действия.

При написании G-выражений вы можете импортировать (guix build utils) на “стороне сборки”, используя with-import-modules, а затем поместить его в область видимости с помощью формы use-modules (see Using Guile Modules in GNU Guile Reference Manual):

(with-imported-modules '((guix build utils))  ;import it
  (computed-file "empty-tree"
                 #~(begin
                     ;; Put it in scope.
                     (use-modules (guix build utils))

                     ;; Happily use its 'mkdir-p' procedure.
                     (mkdir-p (string-append #$output "/a/b/c")))))

Оставшаяся часть этого раздела является справочником по большинству служебных процедур, предоставляемых (guix build utils).

8.7.1 Работа с именами файлов в store

В этом разделе описаны процедуры, относящиеся к именам файлов в store.

Procedure: %store-directory

Проверить целостность склада.

Procedure: store-file-name? file

Возвращает true, если объект obj — это пакет ранней версии.

Procedure: strip-store-file-name file

Удалиnm /gnu/store и хэш из file, имени файла в store. Результатом обычно является строка "package-version".

Procedure: package-name->name+version name

Учитывая name, имя пакета, такое как "foo-0.9.1b", возвращает два значения: "foo" и "0.9.1b". Если часть версии недоступна, возвращаются name и #f. Считается, что первый дефис, за которым следует цифра, обозначает часть версии.

8.7.2 Файловые системы

Процедуры, приведённые ниже, обеспечивают работу и управление ранними версиями пакетов.

Procedure: directory-exists? dir

Вернуть #t, если dir существует и является каталогом.

Procedure: executable-file? file

Вернуть #t, если file существует и исполняемый файл.

Вернуть #t, если file является символической ссылкой (также известной как “символическая ссылка”).

Procedure: elf-file? file
Procedure: ar-file? file
Procedure: gzip-file? file

Вернуть #t, если file является, соответственно, файлом ELF, архивом ar (например, статической библиотекой .a) или файлом gzip.

Procedure: reset-gzip-timestamp file [#:keep-mtime? #t]

Если file является файлом gzip, сбросить его timestamp (как в случае gzip --no-name) и вернуть истину. В противном случае вернуть #f. Когда keep-mtime? истинна, сохранить время модификации file.

8.7.3 Управление файлами

Следующие процедуры и макросы помогают создавать, изменять и удалять файлы. Они обеспечивают функциональность, сопоставимую с такими обычными утилитами оболочки, как mkdir -p, cp -r, rm -r и sed. Они дополняют обширный, но низкоуровневый интерфейс файловой системы Guile (see POSIX in GNU Guile Reference Manual).

Macro: with-directory-excursion directory body …

Запустить body с directory в качестве текущего каталога процесса.

По сути, этот макрос изменяет текущий каталог на directory перед вычислением body, используя chdir (see Processes in GNU Guile Reference Manual). Она возвращается в исходный каталог, когда остается динамический extent body, будь то через возврат нормальной процедуры или через нелокальный выход, такой как исключение.

Procedure: mkdir-p dir

Создать каталог dir и всех его предков.

Procedure: install-file file directory

Создать каталог, если он не существует, и скопировать туда file под тем же именем.

Procedure: make-file-writable file

Сделать file доступным для записи его владельцу.

Procedure: copy-recursively source destination [#:log (current-output-port)] [#:follow-symlinks? #f]  [#:copy-file

copy-file] [#:keep-mtime? #f] [#:keep-permissions? #t] [#:select? (const #t)] Copy source directory to destination. Follow symlinks if follow-symlinks? is true; otherwise, just preserve them. Call copy-file to copy regular files. Call select?, taking two arguments, file and stat, for each entry in source, where file is the entry’s absolute file name and stat is the result of lstat (or stat if follow-symlinks? is true); exclude entries for which select? does not return true. When keep-mtime? is true, keep the modification time of the files in source on those of destination. When keep-permissions? is true, preserve file permissions. Write verbose output to the log port.

Procedure: delete-file-recursively dir [#:follow-mounts? #f]

Delete dir recursively, like rm -rf, without following symlinks. Don’t follow mount points either, unless follow-mounts? is true. Report but ignore errors.

Macro: substitute* file ((regexp match-var…) body…) … Substitute regexp in

file by the string returned by body. body is evaluated with each match-var bound to the corresponding positional regexp sub-expression. For example:

(substitute* file
  (("hello")
   "good morning\n")
  (("foo([a-z]+)bar(.*)$" all letters end)
   (string-append "baz" letters end)))

Здесь, когда строка file содержит hello, она заменяется на good morning. Каждый раз, когда строка file соответствует второму регулярному выражению, all привязывается к полному совпадению, letters привязано к первому подвыражению, а end привязано к последнему.

Когда одно из match-var - _, никакая переменная не связана с соответствующей подстрокой соответствия.

В качестве альтернативы file может быть списком имен файлов, и в этом случае все они могут быть заменены.

Будьте осторожны с использованием $ для поиска конца строки; само по себе он не будет соответствовать завершению новой строки в строке. Например, для поиска целой строки, заканчивающейся обратной косой чертой, нужен регекс типа "(.*)\\\\\n$".

8.7.4 Файловые системы

В этом разделе описаны процедуры поиска и фильтрации файлов.

Procedure: file-name-predicate regexp

Вернуть предикат, который возвращает истину при передаче имени файла, базовое имя которого совпадает с regexp.

Procedure: find-files dir [pred] [#:stat lstat] [#:directories? #f] [#:fail-on-error? #f] Возвращает

лексикографически отсортированный список файлов в dir, для которых pred возвращает истину. pred передается два аргумента: абсолютное имя файла и его буфер статистики; предикат по умолчанию всегда возвращает истину. pred также может быть регулярным выражением, в этом случае оно эквивалентно (file-name-predicate pred). stat используется для получения информации о файле; использование lstat означает, что символические ссылки не соблюдаются. Если directories? истина, то каталоги также будут включены. Если fail-on-error? истина, генерировать исключение при ошибке.

Вот несколько примеров, в которых мы предполагаем, что текущий каталог является корнем дерева исходников Guix:

;; List all the regular files in the current directory.
(find-files ".")
 ("./.dir-locals.el" "./.gitignore" )

;; List all the .scm files under gnu/services.
(find-files "gnu/services" "\\.scm$")
 ("gnu/services/admin.scm" "gnu/services/audio.scm" )

;; List ar files in the current directory.
(find-files "." (lambda (file stat) (ar-file? file)))
 ("./libformat.a" "./libstore.a" )
Procedure: which program

Вернуть полное имя файла для program, как в $PATH, или #f, если program не найдена.

Procedure: search-input-file inputs name
Procedure: search-input-directory inputs name

Return the complete file name for name as found in inputs; search-input-file searches for a regular file and search-input-directory searches for a directory. If name could not be found, an exception is raised.

Here, inputs must be an association list like inputs and native-inputs as available to build phases (see Фазы сборки).

Here is a (simplified) example of how search-input-file is used in a build phase of the wireguard-tools package:

(add-after 'install 'wrap-wg-quick
  (lambda* (#:key inputs outputs #:allow-other-keys)
    (let ((coreutils (string-append (assoc-ref inputs "coreutils")
                                    "/bin")))
      (wrap-program (search-input-file outputs "bin/wg-quick")
        #:sh (search-input-file inputs "bin/bash")
        `("PATH" ":" prefix ,(list coreutils))))))

8.7.5 Program Invocation

You’ll find handy procedures to spawn processes in this module, essentially convenient wrappers around Guile’s system* (see system* in GNU Guile Reference Manual).

Procedure: invoke program args…

Invoke program with the given args. Raise an &invoke-error exception if the exit code is non-zero; otherwise return #t.

The advantage compared to system* is that you do not need to check the return value. This reduces boilerplate in shell-script-like snippets for instance in package build phases.

Procedure: invoke-error? c

Return true if c is an &invoke-error condition.

Procedure: invoke-error-program c
Procedure: invoke-error-arguments c
Procedure: invoke-error-exit-status c
Procedure: invoke-error-term-signal c
Procedure: invoke-error-stop-signal c

Access specific fields of c, an &invoke-error condition.

Procedure: report-invoke-error c [port]

Report to port (by default the current error port) about c, an &invoke-error condition, in a human-friendly way.

Typical usage would look like this:

(use-modules (srfi srfi-34) ;for 'guard'
             (guix build utils))

(guard (c ((invoke-error? c)
           (report-invoke-error c)))
  (invoke "date" "--imaginary-option"))

-| command "date" "--imaginary-option" failed with status 1
Procedure: invoke/quiet program args…

Invoke program with args and capture program’s standard output and standard error. If program succeeds, print nothing and return the unspecified value; otherwise, raise a &message error condition that includes the status code and the output of program.

Here’s an example:

(use-modules (srfi srfi-34) ;for 'guard'
             (srfi srfi-35) ;for 'message-condition?'
             (guix build utils))

(guard (c ((message-condition? c)
           (display (condition-message c))))
  (invoke/quiet "date")  ;all is fine
  (invoke/quiet "date" "--imaginary-option"))

-| 'date --imaginary-option' exited with status 1; output follows:

    date: unrecognized option '--imaginary-option'
    Try 'date --help' for more information.

8.7.6 Фазы сборки

(guix build utils) также содержит инструменты для управления фазами сборки, которые используются системами сборки (see Системы сборки). Фазы сборки представлены в виде ассоциативных списков или “alists” (see Association Lists in GNU Guile Reference Manual), где каждый ключ представляет собой символ, обозначающий фазу, а связанное значение представляет собой процедуру (see Фазы сборки).

Ядро Guile и модуль (srfi srfi-1) предоставляют инструменты для управления списками. Модуль (guix build utils) дополняет их инструментами, написанными с учетом фаз сборки.

Macro: modify-phases phases clause…

Изменить phases последовательно в соответствии с каждым clause, которое может иметь одну из следующих форм:

(delete old-phase-name)
(replace old-phase-name new-phase)
(add-before old-phase-name new-phase-name new-phase)
(add-after old-phase-name new-phase-name new-phase)

Где каждая phase-name выше - это выражение, преобразующееся в символ, а new-phase - выражение, преобразующееся в процедуру.

Пример ниже взят из определения пакета grep. Он добавляет фазу для запуска после фазы install, которая называется fix-egrep-and-fgrep. Эта фаза представляет собой процедуру (lambda* обозначает анонимную процедуру), которая принимает аргумент ключевого слова #:output и игнорирует дополнительные аргументы ключевого слова (see Optional Arguments in GNU Guile Reference Manual, for more on lambda* and optional and keyword arguments.) В фазе используется substitute* для изменения установленных сценариев egrep и fgrep, чтобы они ссылались на grep по абсолютному имени файла:

(modify-phases %standard-phases
  (add-after 'install 'fix-egrep-and-fgrep
    ;; Patch 'egrep' and 'fgrep' to execute 'grep' via its
    ;; absolute file name instead of searching for it in $PATH.
    (lambda* (#:key outputs #:allow-other-keys)
      (let* ((out (assoc-ref outputs "out"))
             (bin (string-append out "/bin")))
        (substitute* (list (string-append bin "/egrep")
                           (string-append bin "/fgrep"))
          (("^exec grep")
           (string-append "exec " bin "/grep")))))))

В приведенном ниже примере фазы изменяются двумя способами: стандартная фаза configure удаляется, предположительно потому, что в пакете нет сценария configure или чего-то подобного, и фаза install по умолчанию заменяется файлом, который вручную копирует устанавливаемые исполняемые файлы:

(modify-phases %standard-phases
  (delete 'configure)      ;no 'configure' script
  (replace 'install
    (lambda* (#:key outputs #:allow-other-keys)
      ;; The package's Makefile doesn't provide an "install"
      ;; rule so do it by ourselves.
      (let ((bin (string-append (assoc-ref outputs "out")
                                "/bin")))
        (install-file "footswitch" bin)
        (install-file "scythe" bin)))))

8.7.7 Wrappers

It is not unusual for a command to require certain environment variables to be set for proper functioning, typically search paths (see Search Paths). Failing to do that, the command might fail to find files or other commands it relies on, or it might pick the “wrong” ones—depending on the environment in which it runs. Examples include:

For a package writer, the goal is to make sure commands always work the same rather than depend on some external settings. One way to achieve that is to wrap commands in a thin script that sets those environment variables, thereby ensuring that those run-time dependencies are always found. The wrapper would be used to set PATH, GUILE_LOAD_PATH, or QT_PLUGIN_PATH in the examples above.

To ease that task, the (guix build utils) module provides a couple of helpers to wrap commands.

Procedure: wrap-program program [#:sh sh] [#:rest variables]

Make a wrapper for program. variables should look like this:

'(variable delimiter position list-of-directories)

where delimiter is optional. : will be used if delimiter is not given.

For example, this call:

(wrap-program "foo"
              '("PATH" ":" = ("/gnu/.../bar/bin"))
              '("CERT_PATH" suffix ("/gnu/.../baz/certs"
                                    "/qux/certs")))

will copy foo to .foo-real and create the file foo with the following contents:

#!location/of/bin/bash
export PATH="/gnu/.../bar/bin"
export CERT_PATH="$CERT_PATH${CERT_PATH:+:}/gnu/.../baz/certs:/qux/certs"
exec -a $0 location/of/.foo-real "$@"

If program has previously been wrapped by wrap-program, the wrapper is extended with definitions for variables. If it is not, sh will be used as the interpreter.

Procedure: wrap-script program [#:guile guile] [#:rest variables]

Wrap the script program such that variables are set first. The format of variables is the same as in the wrap-program procedure. This procedure differs from wrap-program in that it does not create a separate shell script. Instead, program is modified directly by prepending a Guile script, which is interpreted as a comment in the script’s language.

Special encoding comments as supported by Python are recreated on the second line.

Note that this procedure can only be used once per file as Guile scripts are not supported.


Next: Search Paths, Previous: Фазы сборки, Up: Программный интерфейс   [Contents][Index]