linkfly (linkfly) wrote,
linkfly
linkfly

Categories:

Устройство ASDF. Подсистема определения путей. COMPUTE-OUTPUT-TRANSLATIONS.

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

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

    Ф-ия COMPUTE-OUTPUT-TRANSLATIONS вычисляет значения для инициализации глобальной переменной *OUTPUT-TRANSLATIONS*. Эта ф-ия во многом схожа с ф-ией COMPUTE-SOURCE-REGISTRY, но несколько проще. В ней тоже работает некий конвейр обрабатывающий переменную среды и конфигурационные файлы, если они есть. А используемые ф-ии имеют созвучные с используемыми в COMPUTE-SOURCE-REGISTRY имена.

Логика её функционирования следующая:

COMPUTE-OUTPUT-TRANSLATIONS
(defun* compute-output-translations (&optional parameter) ...)
1. Создаётся контекст для последующего вызова ф-ии INHERIT-OUTPUT-TRANSLATIONS. В этом контексте на самом "верху" находится ф-ия REMOVE-DUPLICATES - она из получившегося результата удалит повторяющиеся элементы. Кроме того, используется макрос WHILE-COLLECTING определяющий локальную ф-ию C для сбора промежуточных результатов:

   (remove-duplicates
     (while-collecting (c)
       (inherit-output-translations ...)
     :test 'equal :from-end t
)


    Список с данными для конвейера состоит из символов именующих ф-ии, которые метод PROCESS-OUTPUT-TRANSLATIONS будет вызывать для получения конфигурационных данных. Во главе списка идёт символ WRAPPING-OUTPUT-TRANSLATIONS. Её вызов приведёт к получению таких конфигурационных данных, которые обеспечат лисп-системам сохранение скомпилированных системных файлов рядом с их исходниками, например для Linux/SBCL результат будет следующим:

       (wrapping-output-translations)
       => (:OUTPUT-TRANSLATIONS
        ((#P"/media/COMMON_LISP/lisp-dev-tools/lisp/sbcl/sbcl-1.0.53/lib/sbcl/"
        #P"**/"
)

         NIL
)

        :INHERIT-CONFIGURATION :ENABLE-USER-CACHE
)


    Далее следует имя ф-ии заданное аргументом PARAMETER (или соответственно NIL если PARAMETER не задан). Все остальные символы именующие функции, берутся из глобальной переменной *DEFAULT-OUTPUT-TRANSLATIONS*, которая по умолчанию равна:

        '(ENVIRONMENT-OUTPUT-TRANSLATIONS
          USER-OUTPUT-TRANSLATIONS-PATHNAME
          USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME
          SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME
          SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME)

    Эти ф-ии имеют следующее предназначение:

    ENVIRONMENT-OUTPUT-TRANSLATIONS - возвращает данные, сохранённые в переменной среды "ASDF_OUTPUT_TRANSLATIONS".

    USER-OUTPUT-TRANSLATIONS-PATHNAME - возвращает путь к конфигурационому файлу пользователя если он реально существует. Имя файла определяется глобальной динамической переменной *OUTPUT-TRANSLATIONS-FILE*, которая по умолчанию равна #P"asdf-output-translations.conf". Если выполнение происходит на UNIX-like -системе (например на Linux), то поиск файла производится в следующих директориях:
            - в директории, начальный путь которой, задан через переменную среды "XDG_CONFIG_HOME" а окончание пути - "common-lisp/".
            - в одной из директорий начинающихся с директорий в переменной среды "XDG_CONFIG_DIRS" (директории разделяются символом ":") и заканчивающихся, опять же, на "common-lisp/".
        - в директории начинающейся с домашней директории пользователя и оканчивающейся на ./config/common-lisp/, например: /home/user/common-lisp/.config

    USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-PATHNAME, но ищет не конфигурационный файл, а директорию для конфигурационных файлов по тем же путям поиска, с именем *OUTPUT-TRANSLATIONS-DIRECTORY* (по-умолчанию #P"asdf-output-translations.conf.d/").

    SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-PATHNAME, но файл заданный *OUTPUT-TRANSLATIONS-FILE* ищет в директории #P"/etc/common-lisp/".

    SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME - то же самое что и USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME, но ищет директорию заданную *OUTPUT-TRANSLATIONS-DIRECTORY* в директории #P"/etc/common-lisp/".

    Итак, вызов COMPUTE-OUTPUT-TRANSLATIONS, допустим с аргументом PARAMETER = NIL, приведёт к вызову INHERIT-OUTPUT-TRANSLATIONS со следующими аргументами:

        (INHERIT-OUTPUT-TRANSLATIONS
            '(WRAPPING-OUTPUT-TRANSLATIONS
             NIL
             ENVIRONMENT-OUTPUT-TRANSLATIONS
             USER-OUTPUT-TRANSLATIONS-PATHNAME
             USER-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME
             SYSTEM-OUTPUT-TRANSLATIONS-PATHNAME
             SYSTEM-OUTPUT-TRANSLATIONS-DIRECTORY-PATHNAME)
     :COLLECT #<CLOSURE (FLET C) {1004A48AAB}>)

        ... на месте не печатаемого объекта "#<CLOSURE (FLET C) {1004A48AAB}>" разумеется будет что-то другое.


INHERIT-OUTPUT-TRANSLATIONS
(defun* inherit-output-translations (inherit &key collect) ...)
2. Конвейрная обработка организуется функцией INHERIT-OUTPUT-TRANSLATIONS и методами PROCESS-OUTPUT-TRANSLATIONS. Они рекурсивно вызывают друг друга (хотя в большинстве случаев PROCESS-OUTPUT-TRANSLATIONS обходится без "неявно-рекурсивного" вызова INHERIT-OUTPUT-TRANSLATIONS), по пути складывая в список промежуточные результаты с помощью локальной функции-коллектора C, определённой как сказано выше макросом WHILE-COLLECTING (в теле ф-ии INHERIT-OUTPUT-TRANSLATIONS она связывается с ключом COLLECT) . Когда аргумент INHERIT не равен NIL, что сигнализирует о необходимости запуска "конвейера", она разбивает его на голову и хвост и передаёт ф-ии PROCESS-OUTPUT-TRANSLATIONS:

    (when inherit
      (process-output-translations (first inherit) :collect collect :inherit (rest inherit)))

    ... хвост (rest inherit) передаётся для того, чтобы метод PROCESS-OUTPUT-TRANSLATIONS мог рекурсивно (через вызов INHERIT-OUTPUT-TRANSLATIONS) продолжить дальнейшую обработку, если в конфигурационном DSL был указан ключ :inherit-configuration.


PROCESS-OUTPUT-TRANSLATIONS
(defgeneric process-output-translations (spec &key inherit collect) ...)
3. Эта обобщёная ф-ия включает 5 методов специализирующихся на следующих типах аргумента SPEC: NULL, SYMBOL, STRING, PATHNAME и CONS. В конечном счёте всё сводится к вызову метода специализирующегося на типе CONS, так как аргумент в этом случае, будет содержать список, представляющий конфигурационные данные (ради получения и обработки которых, собственно всё и затевается).

    3.1 Сначала я рассмотрю ф-ии на которых базируются разные специализации PROCESS-OUTPUT-TRANSLATIONS, после понимания работы этих ф-ий - анализ взаимодействия методов станет тривиальным.

        VALIDATE-CONFIGURATION-FILE
        (defun* validate-configuration-file (file validator &key description) ...)
        3.1.1 Ф-ия считывает с указанного файла FILE список, проверяет что он в файле единственный и вызывает функцию-валидатор VALIDATOR для проверки корректности данных. Ключ DESCRIPTION ей необходим в случае неудачи, для формирования сообщения об ошибке.

        VALIDATE-CONFIGURATION-DIRECTORY
        (defun* validate-configuration-directory (directory tag validator &key invalid-form-reporter) ...)
        3.1.2 А эта ф-ия ищет в указанной директории DIRECTORY файлы с расширением .conf (пропуская скрытые файлы), считывает из них множество форм, представляющих директивы анализируемого DSL (Domain Specific Language - язык предметной области), проверяет каждую их них функией-валидором VALIDATOR и составляет из них правильный формат конфигурационных данных (который, соответственно, возвращается в качестве результата), используя при этом,  переданный ключевой символ TAG (он становится первым элементом результирующего списка).

        VALIDATE-OUTPUT-TRANSLATIONS-FILE
        (defun* validate-output-translations-file (file) ...)
        3.1.3 Ф-ия для считывания и проверки конфигурационных данных обращается за помощью к ф-ии VALIDATE-CONFIGURATION-FILE, которая в качестве валидатора использует функцию VALIDATE-OUTPUT-TRANSLATIONS-FORM. Если проверка корректности данных прошла успешно, то они возвращаются в качестве результата.

        VALIDATE-OUTPUT-TRANSLATIONS-DIRECTORY
        (defun* validate-output-translations-directory (directory) ...)
        3.1.4 Для поиска файлов с конфигурационными данными в директории DIRECTORY, их считывания и проверки вызывается функция VALIDATE-CONFIGURATION-DIRECTORY. В качестве функции-валидатора она использует VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE, проверяющей каждую прочитанную форму из конфигурационных файлов (директиву анализируемого DSL).

        PARSE-OUTPUT-TRANSLATIONS-STRING
        (defun* parse-output-translations-string (string &key location) ...)
        3.1.5 Эта ф-ия используется для преобразования данных их переданной строки в правильно сформированные конфигурационные данные. Ф-ия анализирует переданный аргумент и выбирает одно из следующих действий:
            - если STRING не является строкой, то ф-ия сигнализирует об ошибке.
            - если STRING равна пустой строке или NIL то вызвращается список '(:output-translations :inherit-configuration).
            - если STRING содержит вложенную строку (то есть, строку вида "\"...\""), то эта вложенная строка "вынимается" из STRING с помощью вызова (read-from-string string) и повторяется всё заново со вложенной строкой (т.е. ф-ия рекусивно вызывается с получившейся строкой).
            - если первый символ в STRING это "(", то значит в строке содержится список и тогда он читается из строки, с помощью того же вызова (read-from-string string) и также происходит рекурсивный вызов с получившимся результатом.
            - если же не сработал ни один из получившихся вариантов, тогда строка рассматривается как последовательное перечисление директорий, каждая их которых разделяется специфичным для системы симолом-разделителем директорий (для Linux это символ ":"). Причём кол-во директорий должно быть обязательно чётным, иначе сигнализируется ошибка. Отсутствие текста между двумя символами-разделителями (например: "/home/src:/some/compiled::") говорит об использовании ключа :INHERIT-CONFIGURATION (его допускается использовать один раз, при нарушении этого правило сигнализируется ошибка).

        VALIDATE-OUTPUT-TRANSLATIONS-FORM
        (defun* validate-output-translations-form (form &key location) ...)
        3.1.6 Ф-ия проверяет корректность конфигурационных данных с помощью ф-ии VALIDATE-CONFIGURATION-FORM. Для проверки каждой директивы она использует ф-ию VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE. Ключ LOCATION используется в случае возникновения ошибки и содержит небольшую часть сообщения.

        VALIDATE-CONFIGURATION-FORM
        (defun* validate-configuration-form (form tag directive-validator
                          &key location invalid-form-reporter) ...)
    3.1.7 Ф-ия проверяет корректность конфигурационных данных, представленных аргументом FORM. В случае если аргумент FORM не является списком или его первый элемент не эквивалентен аргументу TAG, то глобальная переменная *IGNORED-CONFIGURATION-FORM* получает значение T и сигнализируется ошибка. Основная часть ф-ии состоит из итерирования по хвосту списка FORM. Ф-ия проверяет каждую директиву переданым валидатором DIRECTIVE-VALIDATOR, при этом она проверяет что ни один из ключей :INHERIT-CONFIGURATION или :IGNORE-INHERITED-CONFIGURATION не встречается более одного раза. А также она обрабатывает семантику ключа :IGNORE-INVALID-ENTRIES позволяющего игнорировать неправильные директивы, не сигнализируя ошибки.

        VALIDATE-OUTPUT-TRANSLATIONS-DIRECTIVE
        (defun* validate-output-translations-directive (directive) ...)
        3.1.8 Ф-ия отвечает непосредственно за валидацию директивы (обрабатываемого DSL). Если аргумент DIRECTIVE атом, то он обязан иметь только одно из следующих значений: :ENABLE-USER-CACHE, :DISABLE-CACHE, NIL. Если же он является списком, тогда считается корректной одна из следующих ситуаций:

            3.1.8.1 Длина списка = 2 и при этом:
                - либо в качестве первого элемента используется ключ :INCLUDE, а в качестве второго - объект типа STRING, PATHNAME или NIL.
                - либо первый элемент удовлетворяет предикату LOCATION-DESIGNATOR-P, а второй - либо предикату LOCATION-DESIGNATOR-P, либо предикату LOCATION-FUNCTION-P.                    

            3.1.8.2 Длина списка = 1 и его единственный элемент удовлетворяет предикату LOCATION-DESIGNATOR-P.

            Используемые предикаты работают следующим образом:

            LOCATION-DESIGNATOR-P
            Предикат возвращает значение T, в случае если аргумент:
                - атом и имеет тип STRING, PATHNAME или одно из следующих значений:  T, NIL, :ROOT :HOME :HERE :USER-CACHE :SYSTEM-CACHE :DEFAULT-DIRECTORY.
                - список, и тогда в качестве первого элемента в списке должно быть то, что описано выше, а в качестве оставшихся должны быть элементы типа STRING, PATHNAME, или следующие значения: :DEFAULT-DIRECTORY, :*/, :**/, :*.*.*, :IMPLEMENTATION, :IMPLEMENTATION-TYPE.

            LOCATION-FUNCTION-P
            Предикат возвращает T, в случае если аргумент:
                - список из двух элементов, первый из которых это :function, а второй: либо символ, либо список, представляющий лямбда-выражение, принимающее на вход два параметра.
        
        PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE
        (defun* process-output-translations-directive (directive &key inherit collect) ...)
        3.1.9 Это самая важная ф-ия, она занимается непосредственно обработкой директивы из, предварительно проверенных на корректность, конфигурационных данных. Смысл её в том, чтобы разобрать (распарсить) переданную директиву и сформировать данные для последующего помещения в результирующий список с помощью ф-ии передаваемой ключом COLLECT. Результаты обработки в конце-концов попадут в глобальную переменную *OUTPUT-TRANSLATIONS*, а она в свою очередь, необходима для работы ф-ии APPLY-OUTPUT-TRANSLATIONS (это ф-ия, вычисляющая пути к файлам содержащим результаты применения операций к компонентам - обычно к *.fasl файлам). В процессе работы ф-ия может рекурсивно вызвать сама себя или ф-ию INHERIT-OUTPUT-TRANSLATIONS с параметром INHERIT, если была вызывана с аргументом DIRECTIVE равным ключу :INHERIT-CONFIGURATION. Подробный разбор логики работы этой ф-ии будет дан в конце статьи.

    3.2 Теперь вернёмся к обобщённой ф-ии PROCESS-OUTPUT-TRANSLATIONS, её специализации работают следующим образом:

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS NULL)
Осуществляется переход к обработке следующего элемента:

    (inherit-output-translations inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS SYMBOL)
Вызывается ф-ия, имя которой представлено символом, для получения конфигурационных данных для дальнейшей обработки:

    (process-output-translations (funcall x) :inherit inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS STRING)
Аргумент обрабатывается ф-ией PARSE-OUTPUT-TRANSLATIONS-STRING, возвращающей конфигурационные данные для дальнейшей обработки:

    (process-output-translations (parse-output-translations-string string)
                 :inherit inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS PATHNAME)
Если аргумент представляет собой файл, то для чтения конфигурационных данных и их валидации (и возврата в качестве результата) вызывается ф-ия VALIDATE-OUTPUT-TRANSLATIONS-FILE. Если аргумент содержит путь к директории, то файлы в ней рассматриваются как содержащие конфигурационные данные. За сбор этих данных и их валидацию отвечает ф-ия VALIDATE-OUTPUT-TRANSLATIONS-DIRECTORY. Если аргумент представляет собой не существующий файл, то происходит переход к дальнейшей обработки:

    (inherit-output-translations inherit :collect collect)

(DEFMETHOD PROCESS-OUTPUT-TRANSLATIONS CONS)
В этом случае аргумент представляет собой конфигурационные данные. Производится их валидация и каждый элемент этих данных обработывается ф-ией PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE:

    (dolist (directive (cdr (validate-output-translations-form form)))
      (process-output-translations-directive directive
                         :inherit inherit
                         :collect collect
)
)


--------------------------------------------------------------
        
PROCESS-OUTPUT-TRANSLATIONS-DIRECTIVE
(defun* process-output-translations-directive (directive &key inherit collect) ...)
Это главная ф-ия, используемая в процессе обработки конфигурационных данных, используемых при формирования путей результатов применения операций к компонентам. Эти конфигурационные данные следуют специальным соглашениям ASDF для формирования этих путей, представляющих собой некий мини-DSL.

Логика работы:
    
1. Если аргумент DIRECTIVE - атом, то в зависимости от его значения-ключа происходит следующее:
    - :ENABLE-USER-CACHE - рекурсивный вызов с DIRECTIVE = '(t :user-cache)
    - :DISABLE-CACHE - рекурсивный вызов с DIRECTIVE = '(t t)
    - :INHERIT-CONFIGURATION - вызов INHERIT-OUTPUT-TRANSLATIONS с аргументом INHERIT.
    - для аргументов :IGNORE-INHERITED-CONFIGURATION, :IGNORE-INVALID-ENTRIES, NIL - возвращает NIL.

2. Если же аргумент список определяется локальное окружение с переменными, содержащими элементы списка:

   (let ((src (first directive))
      (dst (second directive))
)

      ...
)


3. Далее, если SRC = :INCLUDE то путь в DST обрабатывается "неявно-рекурсивным" вызовом ф-ии PROCESS-OUTPUT-TRANSLATIONS:

    (if (eq src :include)
        (when dst
     
     (process-output-translations (pathname dst) :inherit nil :collect collect))
    ...
)


4. Иначе, если переменная SRC не равна ни :INCLUDE ни NIL, то происходит следующее:

    4.1 Создаётся локальное окружение с переменной TRUSRC. Она становится равной либо T (если SRC = T):

       (let ((trusrc (or (eql src t)
                           
  ...)))
        ...
)


        ... либо её значение вычисляется из локальной SRC, которая должна представлять собой или путь к файлу непосредственно, или список описывающий путь (оформленный с помощью специальных соглашений ASDF по формированию путей):

       (let ((loc (resolve-location src :directory t :wilden t)))
          (if (absolute-pathname-p loc) (truenamize loc) loc))

        ... как видно используется ф-ия RESOLVE-LOCATION которая воспринимает SRC как директорию и создаёт, так называемый "wildcard" путь (путь использующий групповые символы). Например, вот результат применения RESOLVE-LOCATION с путём "/usr/somedir":

        (resolve-location "/usr/somedir" :directory t :wilden t)
        => #P"/usr/somedir/**/*.*"

        4.2 В созданном локальном окружении, возможные варианты выполнения распадаются на три ветки:
            
            4.2.1 Если переменная DST удовлетворяет предикату LOCATION-FUNCTION-P (т.е это список имеющий первый элемент :FUNCTION, а второй - либо символ, либо список с лямбда-выражением), то в результирующий список (собираемый ф-ией переданной в ключе COLLECT) попадает список из TRUSRC и из функционального объекта представленного вторым элементом DST:

               (cond
                  ((location-function-p dst)
                   (funcall collect
                            (list trusrc
                                  (if (symbolp (second dst))
                                      (fdefinition (second dst))
                                      (eval (second dst))
)
)
)
)

                  ...
)


            4.2.2 Если DST = T, то возвратить список из TRUSRC и T:

               (cond
         
         ...
       
           ((eq dst t)
              
       (funcall collect (list trusrc t)))
        
          ...)

            4.2.3 В остальных случаях в результирующий список попадают 2 списка:
                - список из обработанной (с помощью ф-ии RESOLVE-LOCATION) переменной DST если она не была равна NIL (а если была, то в список попадает значение переменной TRUSRC) и константы T.
                - и список из TRUSRC и обработанной DST (в случае если значение DST было равно NIL, то в списке оказывается значение TRUSRC).

--------------------------------------------------------
Продолжение следует ...
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