linkfly (linkfly) wrote,
linkfly
linkfly

Устройство ASDF. Подсистема поиска. COMPUTE-SOURCE-REGISTRY.

(сказаное ниже относится к версии 2.019.5)

Это 15-ая статья цикла.
Первая статья.
Архитектура ASDF.
Предыдущая статья.

Определение:

    (defun* compute-source-registry (&optional parameter) ...)

    Необязательный аргумент PARAMETER если задан, должен быть символом, именующим ф-ию, которая при вызове без аргументов должна возвратить элемент данных для обработки ф-ией PROCESS-SOURCE-REGISTRY, которая занимается тем, что составляет список путей по которым следует искать *.asd файлы с определениями систем.

    Сначала я дам разного рода справочную информацию, которую можно бегло просмотреть и пользоваться ею при разборе логики работы ф-ии COMPUTE-SOURCE-REGISTRY (и вызываемых её, других ф-ий). Затем я приведу структурную схему зависимостей основных используемых элементов, это должно упростить разбор, принципа их взаимодействия.

В процессе своей работы ф-ия COMPUTE-SOURCE-REGISTRY прямо или опосредованно пользуется следующими сущностями (чуть позже будет дано их описание):

Макрос и переменная среды: WHILE-COLLECTION (в вызываемой ф-ии FLATTEN-SOURCE-REGISTRY), CL_SOURCE-REGISTRY (через ф-ию ENVIRONMENT-SOURCE-REGISTRY)

Динамические переменные:
*wild-asd*
*default-source-registry-exclusions*
*source-registry-exclusions*
*default-pathname-defaults*
*default-source-registries*
*ignored-configuration-form*
*inter-directory-separator*
*here-directory*
*source-registry-file*
*source-registry-directory*

Ф-ии используемые в определении:
flatten-source-registry
register-asd-directory

Методы обработки:
(process-source-registry cons ...)
(process-source-registry null ...)
(process-source-registry string ...)
(process-source-registry pathname ...)
(process-source-registry symbol ...)

Другие ф-ии используемые опосредовано:
inherit-source-registry
ф-ия wrapping-source-registry, ф-ия в parameter, ф-ии из *default-source-registries*
validate-source-registry-form
process-source-registry-directive
resolve-location
parse-source-registry-string
validate-source-registry-directory
validate-source-registry-file
collect-sub*directories-with-asd
collect-sub*directories
directory-has-asd-files-p

                Описание используемых сущностей (позже будет представлена схема взаимозависимостей):

Макрос и переменная среды:
1) WHILE-COLLECTING - макрос для сбора значений, создаёт локальную ф-ию которая аккумулирует значение своего аргумента.
2) "CL_SOURCE_REGISTRY" - переменная среды. Может содержать элемент данных для обработки ф-ией PROCESS-SOURCE-REGISTRY. Её значение, возвращается вызовом (ENVIRONMENT-SOURCE-REGISTRY).

Динамические переменные.
1) *wild-asd* - хранит "wildcard" путь для поиска файлов определения ASDF-систем, по умолчанию = #P"*.asd"
2) *default-source-registry-exclusions* - содержит имена директорий, которые надо исключать из рекурсивного поиска.
3) *source-registry-exclusions* - для внутреннего использования, она также содержит директории для исключения (переменная замыкает значение *DEFAULT-SOURCE-REGISTRY-EXCLUSIONS для того чтобы ф-ии были от неё не зависимы).
4) *default-pathname-defaults* - создаётся динамический контекст в котором значением этой переменной становится результат сглаживания некоторых шероховатостей top-level значения этой системной переменной:

    (truenamize (pathname-directory-pathname *default-pathname-defaults*))

5) *default-source-registries* - содержит символы, представляющие разные ф-ии, которые при вызове их без параметров возвращают некоторый элемент данных из которых ф-ия PROCESS-SOURCE-REGISTRY может получить в итоге конфигурационные данные для обработки. По умолчанию это:

    (ENVIRONMENT-SOURCE-REGISTRY
     USER-SOURCE-REGISTRY
     USER-SOURCE-REGISTRY-DIRECTORY
     SYSTEM-SOURCE-REGISTRY
     SYSTEM-SOURCE-REGISTRY-DIRECTORY
     DEFAULT-SOURCE-REGISTRY)

6) *ignored-configuration-form* - глобальная переменная свидетельствующая о некорректности конфигурационных данных.
7) *inter-directory-separator* - разделитель директорий по умолчанию (для ОС GNU/Linux) = #\: .
8) *here-directory* - устанавливает директорию для обработки ключа :here .
9) *source-registry-file* - имя файла в котором содержаться конфигурационные данные.
10) *source-registry-directory* - имя директории в которой содержаться файлы с конфигурационными данными.

Ф-ии используемые в определении:
1) flatten-source-registry - используется для получения списка из конфигурационных данных, состоящих из подсписков вида:
        (<директория> :recurse <t_или_nil> :exclude <список_имён_исключаемых_директорий>)

2) register-asd-directory - используется для аккумулирования директорий содержащих .asd файлы, с помощью списков, которые были получены вызовом FLATTEN-SOURCE-REGISTRY.

Методы обработки
PROCESS-SOURCE-REGISTRY - главная обобщённая ф-ия обработки конфигурационных данных (используемая неявно), содержит следующие специализации:
                   - (process-source-registry cons ...)
                   - (process-source-registry null ...)
                   - (process-source-registry string ...)
                   - (process-source-registry pathname ...)
                   - (process-source-registry symbol ...)

Другие ф-ии используемые неявно:
1) inherit-source-registry - организует конвейр обработки данных, вызывая PROCESS-SOURCE-REGISTRY с первым элементом переданного списка и его хвостом.
2) ф-ия wrapping-source-registry, ф-ия символ которой передан аргументом parameter, другие ф-ии из списка *DEFAULT-SOURCE-REGISTRIES* - это всё ф-ии для получения данных, обрабатываемых далее PROCESS-SOURCE-REGISTRY.
3) validate-source-registry-form - проверка на правильность переданных конфигурационных данных.
4) process-source-registry-directive - обработка опций (ключей) конфигурационных данных.
5) resolve-location - ф-ия обрабатывает строки, имена путей, а также ключи и списки соответствующие соглашениям ASDF по формированию имён путей.
6) parse-source-registry-string - подготавливает данные для обработки PROCESS-SOURCE-REGISTRY.
7) validate-source-registry-directory, validate-source-registry-file - проверка и возврат конфигурационных данных в файле или файлах заданной директории.
8) collect-sub*directories-with-asd - вызывает collect-sub*directories передавая ей директорию, предикат для проверки стоит ли аккумулировать директорию, предикат управляющий рекурсивным спуском по директориям и функцию-коллектор.
9) collect-sub*directories - рекурсивно проходит по директориям а аккумулирует их в соотв. с переданными предикатами и ф-ией коллектором.
10) directory-has-asd-files-p - проверяет, содержит ли директория .asd файлы.



                Логика работы COMPUTE-SOURCE-REGISTRY.

1. Вначале организуется итерация по некоторому списку, возвращённому ф-ией flatten-source-registry:

   (dolist (entry (flatten-source-registry parameter)) ... )

    FLATTEN-SOURCE-REGISTRY
    1.1 Основной функционал "обёртывается" вызовам REMOVE-DUPLICATES, удаляющим из результата повторяющиеся элементы, и макросом WHILE-COLLECTING, вводящим локальную ф-ию для сбора значений в результирующий список:

        (remove-duplicates
 
          (while-collecting (collect)
              ...))

    1.2 В динамической области, в которой системная переменная *DEFAULT-PATHNAME-DEFAULTS* защищается от изменений (и получает новое значение из обработанного старого) - вызывается ф-ия INHERIT-SOURCE-REGISTRY с параметрами:

       '(wrapping-source-registry
          nil
          environment-source-registry
          user-source-registry
          user-source-registry-directory
          system-source-registry
          system-source-registry-directory
          default-source-registry
)

       :register #'(lambda (directory &key recurse exclude)
              (collect (list directory :recurse recurse :exclude exclude))
)


    ... после WRAPPING-SOURCE-REGISTRY идёт значение аргумента PARAMETER, если COMPUTE-SOURCE-REGISTRY вызывалась из формы (initialize-source-registry) то этим значением, будет значение  динамической переменной *SOURCE-REGISTRY-PARAMETER* (которая вначале равна NIL). Ф-ия задаваемая в :register использует локальную ф-ию заданную макросом WHILE-COLLECTING для сбора значений.

        FLATTEN-SOURCE-REGISTRY -> INHERIT-SOURCE-REGISTRY
        1.2.1 Первый символ WRAPPING-SOURCE-REGISTRY необходим для получения конфигурации для поиска системных
библиотек, вызов:

            (wrapping-source-registry)
       
            ... возвращает:

            (:SOURCE-REGISTRY (:TREE #P"/usr/local/lib/sbcl/") :INHERIT-CONFIGURATION)

         Таким образом, в начале своей работы INHERIT-SOURCE-REGISTRY вызывает следующую форму:

            (process-source-registry '(:SOURCE-REGISTRY (:TREE #P"/usr/local/lib/sbcl/") :INHERIT-CONFIGURATION)
                 :register #'(lambda (directory &key recurse exclude)
                           (collect (list directory :recurse recurse :exclude exclude)))
                 
                 :inherit '(nil
                        environment-source-registry
                        user-source-registry
                        user-source-registry-directory
                        system-source-registry
                        system-source-registry-directory
                        default-source-registry))

    Чтобы отлаживать эту форму достаточно её обернуть в вызов:
    
    (while-collecting (collect) ...)

            (DEFMETHOD PROCESS-SOURCE-REGISTRY (FORM CONS) &KEY INHERIT REGISTER)
           1.2.1.1 Создаётся динамический контекст в котором переменная *SOURCE-REGISTRY-EXCLUSIONS* получит имена директорий, которые следует исключить из поиска (из переменной *DEFAULT-SOURCE-REGISTRY-EXCLUSIONS*):

                 (let ((*source-registry-exclusions* *default-source-registry-exclusions*)) ...)

            1.2.1.2 Передаваемый в первом аргументе список проверяется на корректность с помощью вызова:

                 (validate-source-registry-form '(:SOURCE-REGISTRY (:TREE #P"/usr/local/lib/sbcl/") :INHERIT-CONFIGURATION))

                ... проверка надо сказать довольно лютая, но самое брутальное впереди :)

            1.2.1.3 Далее идёт итерация по хвосту проверенного списка (то есть без ключа :source-registry), на каждом
шаге которой вызывается ф-ия PROCESS-SOURCE-REGISTRY-DIRECTIVE, в рассматриваемом случае будут последовательно вызваны формы:

                   (process-source-registry-directive '(:TREE #P"/usr/local/lib/sbcl/")
                                                                         :inherit inherit
                                                                         :register register)

                   (process-source-registry-directive :INHERIT-CONFIGURATION
                                                                         :inherit inherit
                                                                         :register register)

            PROCESS-SOURCE-REGISTRY -> PROCESS-SOURCE-REGISTRY-DIRECTIVE
            (defun* process-source-registry-directive (directive &key inherit register) ...)

            1.2.1.3.1 Создаётся локальный контекст, в котором переменная kw получает ключ, а REST получает всё что идёт после этого ключа в списке, если directive была задано списком, иначе REST = NIL.

            (destructuring-bind (kw &rest rest)
                  (if (consp directive) directive (list directive))
              ...)

            1.2.1.3.2 Дальше происходит ветвление в зависимости от KW (то есть от типа директивы DIRECTIVE), разберём эти директивы начиная с тех, которые представляются списком произвольной длины:

                (:exclude &rest dirs) - указывает какие имена директорий в дальнейшем надо игнорировать, устанавливая переменную *SOURCE-REGISTRY-EXCLUSIONS*.
                (:also-exclude &rest dirs) - добавляет директории для исключения из поиска.

Следующие директивы представляются одним ключом:
                :default-registry - инициирует вызов INHERIT-SOURCE-REGISTRY, ключ :REGISTER будет тем же, а вот в качестве параметра задаст '(default-source-registry) что приведёт снова к вызову PROCESS-SOURCE-REGISTRY с множеством всяких дефолтных директив возвращаемых ф-ией DEFAULT-SOURCE-REGISTRY.
                :inherit-configuration - это значит, мы рекурсивно будем вызывать PROCESS-SOURCE-REGISTRY с результатом вызовов ф-ий связанных с символами в списке INHERIT.
                :ignore-inherited-configuration - ничего не делать, возвратить NIL.

А эти директивы представляются списком из двух элементов:

                (:include pathname) - просто начинает обработку содержимого указанного кофигурационного файла. Далее будет вызван метод (process-source-registry pathname pathname ...), в котором устанавливается динамический контекст, с переменной *HERE-DIRECTORY*, которая устанавливается в директорию если в PATHNAME директория, и в директорию в которой содержится файл, если PATHNAME представляет собой файл.
                (:directory pathname) - регистрирует указанную директорию с помощью ф-ии REGISTER переданной ещё в теле ф-ии FLATTEN-SOURCE-REGISTRY вызову формы с INHERIT-SOURCE-REGISTRY.
                (:tree pathname) - тоже регистрирует директорию, но устанавливает ключи :RECURSE и :EXCLUDE для последующей обработки в COMPUTE-SOURCE-REGISTRY.                

Чтобы лучше понять работу двух описаных выше ф-ий вы можете включить для них трассировку и вызвать PROCESS-SOURCE-REGISTRY, как описано выше предварительно обернув в форму WHILE-COLLECTING:

    (trace process-source-registry)
    (trace process-source-registry-directive)
    (while-collecting (collect) (process-source-registry '(:SOURCE-REGISTRY ...) ...))

REGISTER-ASD-DIRECTORY.
(defun* register-asd-directory (directory &key recurse exclude collect) ...)
2. Аккумулирование директорий с .asd файлами учитавая ключи :RECURSE и :EXCLUDE .

    2.1 Если :RECURSE = NIL, то зарегистрировать все *.asd файлы в указанной директории с помощью ф-ии COLLECT-ASDS-IN-DIRECTORY (которой, кстати, передаётся параметр, представляющий функцию-коллектор - COLLECT).
    2.2 Иначе вызвать функцию аккумулирующую директории содержащие .asd файлы - COLLECT-SUB*DIRECTORIES-ASD-FILES.

    REGISTER-ASD-DIRECTORY -> COLLECT-SUB*DIRECTORIES-ASD-FILES
    (defun* collect-sub*directories-asd-files (directory &key (exclude *default-source-registry-exclusions*) collect) ...)
    2.3 Ф-ия COLLECT-SUB*DIRECTORIES-WITH-ASD в свою очередь, вызывает более общую ф-ию COLLECT-SUB*DIRECTORIES передавая ей директорию, предикат возвращающий T, предикат, проверяющий что директория не входит в список исключений, а также ф-ию-коллектор, регистрирующую все *.asd файлы находящиеся в директории:

        (collect-sub*directories
           directory
           (constantly t)
 
         #'(lambda (x) (not (member (car (last (pathname-directory x))) exclude :test #'equal)))
  
        #'(lambda (dir) (collect-asds-in-directory dir collect)))

        REGISTER-ASD-DIRECTORY -> COLLECT-SUB*DIRECTORIES-ASD-FILES -> COLLECT-SUB*DIRECTORIES 
        (defun* collect-sub*directories (directory collectp recursep collector) ...)
        2.3.1 С указанной в качестве аргумента директорией вызывается предикат (проверяющий наличие .asd файлов в директории) и в случае успеха эта директория обрабатывается переданной ф-ией collector (которая, как видно по тексту выше, регистрирует находящиеся в этой директории *.asd файлы):

             (when (funcall collectp directory)
     
           (funcall collector directory))

        2.3.2 Итерация по суб-директориям. Ф-ия SUBDIRECTORIES возвращает список суб-директорий (внутри себя сглаживая различия между множеством лисп-систем (включая, между прочим, лисп-машину "genera" - видимо они где-то используются):

             (dolist (subdir (subdirectories directory)) ...)

        2.3.3 Вызывается предикат, проверяющий нужен ли следующий рекурсивный вызов (проверяющий, не попадает ли директория в список исключений) и в случае успеха происходит рекурсивный вызов, уже для одной из суб-директорий:

           (when (funcall recursep subdir)
      
        (collect-sub*directories subdir collectp recursep collector))

                        Ф-ии для получения конфигурационных данных.

(environment-source-registry) - эта ф-ия отлична от всех, она просто читает переменную среды: (getenv "CL_SOURCE_REGISTRY").

    Нижеследующие функции без параметров нужны для поиска и возврата полного пути к конфигурационному файлу или директории:

(user-source-registry)
(system-source-registry)
(user-source-registry-directory)
(system-source-registry-directory)

    Отличаются они друг от друга следующим:
 - начинающиеся с префикса user- за начало абсолютного пути берут пользовательскую директорию. И например в Linux к ней добавляется суфикс регламентированный стандартом XDG .config/common-lisp/ начало пути получается таким: #P"/home/$USER/.config/common-lisp/".
 - начинающиеся с префикса system- получают в качестве начала пути системную директорию, для Linux будет: #P"/etc/common-lisp/".
 - ф-ии с добавленным окончанием "directory", добавляют к пути значение переменной *SOURCE-REGISTRY-DIRECTORY* (по умолчанию она равна #P"source-registry.conf.d/").
 - ф-ии без окончания directory добавляют к пути значение переменной *source-registry-file* (по умолчанию #P"source-registry.conf")

Результаты для Linux, при условии что вычисляемые пути реально существуют, будут скорее всего подобны следующим:

(user-source-registry) => #P"/home/someuser/.config/common-lisp/source-registry.conf"
(system-source-registry) => #P"/etc/common-lisp/source-registry.conf"
(user-source-registry-directory) => #P"/home/someuser/.config/common-lisp/source-registry.conf.d/"
(system-source-registry-directory) => #P"/etc/common-lisp/source-registry.conf.d/"

                Другие методы обобщённой ф-ии process-source-registry.

    (DEFMETHOD PROCESS-SOURCE-REGISTRY (X NULL) &KEY INHERIT REGISTER)
Неявная рекурсия - передаёт управление INHERIT-SOURCE-REGISTRY с параметром INHERIT.

    (DEFMETHOD PROCESS-SOURCE-REGISTRY (X SYMBOL) &KEY INHERIT REGISTER)
Вызывает ф-ию представленную символом, для получения данных и обрабатывает их рекурсивным вызовом.

    (DEFMETHOD PROCESS-SOURCE-REGISTRY (STRING STRING) &KEY INHERIT REGISTER)
Анализирует строку для получения данных (для обработки рекурсивным вызовом) с помощью ф-ии PARSE-SOURCE-REGISTRY-STRING. Эта ф-ия пытается прочитать форму, представляющую собой конфигурационные данные с помощью read-from-string, если не получилось составляет из них эти самые данные, трактуя строку как список путей разделённых *inter-directory-separator* (по умолчанию #\:). Если путь заканчивается на "//" он трактуется как: "любой путь начинающийся с данного". Затем выполняет рекурсивный вызов для обработки проанализированных данных.

    (DEFMETHOD PROCESS-SOURCE-REGISTRY (PATHNAME PATHNAME) &KEY INHERIT REGISTER)
Анализует, является ли PATHNAME путём к файлу или к директории и вызавает либо VALIDATE-SOURCE-REGISTRY-FILE, для чтения и проверки конфигурационных данных из этого файла, либо VALIDATE-SOURCE-REGISTRY-DIRECTORY для чтения конфигурационных файлов в этой директории и аккумулирование их в один список, представляющий конфигурационные данные. Затем выполняет рекурсивный вызов PROCESS-SOURCE-REGISTRY для их обработки. Здесь же устанавливается динамический контекст, в котором переменная *HERE-DIRECTORY* представляет собой директорию в которой содержится файл pathname (или сам pathname если это директория).

-----------------------------------------
Продолжение следует ...
Tags: asdf, lisp, programming, лисп, программирование
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments