You are viewing linkfly

Tired of ads? Upgrade to paid account and never see ads again!
linkfly's Journal
 
[Most Recent Entries] [Calendar View] [Friends]

Below are the 20 most recent journal entries recorded in linkfly's LiveJournal:

    [ << Previous 20 ]
    Sunday, October 7th, 2012
    10:19 pm
    Рейтинг библиотек в Quicklisp репозитории
      Вообще говоря в движении opensource есть свои плюсы и минусы. Плюсов разумеется гораздо больше! :) Но есть и некоторые минусы. Один из них, например, - качестве библиотек никто не гарантирует и надо тратить время на исследования того, годная ли библиотека вообще для использования. Как с этим бороться? Во-первых, конечно же, нужно советоваться с коллегами, если кто-то получил драгоценный опыт использования какой-либо системы, то неплохо бы его перенять. А во-вторых гарантию качества даёт кол-во библиотек использующих интересующую вас систему. Вот об этом и пойдёт речь в этой заметке.
      Итак, я рад представить сообществу ASDF-систему для оценки рейтингов open-source библиотек из репозитория Quicklisp. Рейтинг системы в данном контексте будет ничем иным, как кол-вом библиотек, которые её используют. Инструкцию по использованию читайте здесь: https://github.com/LinkFly/ql-libs-analizing/blob/master/README_ru.

    Сам репозиторий: https://github.com/LinkFly/ql-libs-analizing
    Проект в Redmine: http://linkfly.ru:8201/redmine/projects/ql-libs-analizing (временно изменено, см. ниже)

    Считаю, это крайне полезная система особенно для тех кто строит бизнес, с упором на использование opensource решений.
    Предложения по развитию и баг-репорты пишите в проект на github'е или в Redmine.

    P.S. Совершенно внезапно перестал работать 1gb.ru, вместе со своими DNS-серверами, поэтому ссылка на Redmine временно будет следующая: http://178.140.218.145:8201/redmine/projects/ql-libs-analizing
    Monday, September 17th, 2012
    6:54 pm
    Проект LISP-DEV-TOOLS. Сервис для работы с проектом в Redmine.
        И вот теперь, когда я вновь "свободный художник" займусь вероятно своими open-source проектами:)
    Что касается конкретно lisp-dev-tools: вообще говоря, цель достигнута - это удобный инструмент для автоматизации разворачивания инфраструктуры для разработки на Common Lisp, я собственно сам им и пользуюсь. Судите сами: есть (по каким-либо причинам) у вас чистая ОСь (ну прямо девственно-чистая), и вам нужно получить всё самое необходимое для разработки на CL, причём не просто так - а согласно современному состоянию развития open-source инструментария для работы с Common Lisp'ом. Но вы не хотите совершать "лишних телодвижений" (я вот например, их о-о-очень не люблю делать), чтобы получить всё необходимое и обязательно сразу. Если вы пользуетесь lisp-dev-tools, то (даже в каком-нибудь jail'e с кучей ограничений) вы делаете так:

    git clone https://github.com/LinkFly/lisp-dev-tools.git
    cd lisp-dev-tools
    ./provide-slime

    ... И получаете ВСЁ!
    Но при этом есть возможность тонко настроить версии инструментов. Например, мне в ближайшем будущем точно понадобится фича в emacs 24-ой версии, позволяющая установливать дополнительные пакеты для Emacs, устанавливаемые в духе пакетной системы дистрибутивов Linux'a и пакетной системы в Quicklisp. Я тупо наугад подобрал версию Emacs'a которая у меня успешно загрузилась/скомпилировалась и установилась внутрь lisp-dev-tools (в репозитории используемого мной дистрибутива 24-ой версии соотв. не оказалось). Номер версии я поменял в lisp-dev-tools/conf/tools.conf.

    Кроме того, я иногда заглядываю в исходники sbcl'a и в этом случае, мне недостаточно простой (и быстрой) установки бинарной сборки - в этом случае, я дополнительно делаю:

    ./rebuild-lisp.sh

    Собственно и всё. Параметры по умолчанию, безусловно можно настроить как душе угодно.

    Далее, время от времени, по некоторым причинам мне нужно использовать другую лисп-систему и/или другую версию используемой лисп-системы и всё решается мгновенно, например:

    ./change-lisp ecl
    ./change-version 12.7.1

    В любой момент, я временно могу запустить, например тот же SLIME с другой Лисп-системой:

    LISP=sbcl ./run-slime

    И так далее и тому подобное ...

    А теперь у проекта появился, скажем так: "свой дом" в виде Redmine'овском сервиса по управлению проектами:

    http://linkfly.ru:8201/redmine/projects/lisp-dev-tools

    Я уже вполне его успешно использую этот проект, но некоторые фичи я бы ещё добавил.  В общем это "без 5-ти минут" мощнейший инструмент корпоративного класса и если общественность обратит на него внимание, то в самое ближайшее время - реально довести проект до "мажорной версии" и рекомендовать к использованию в производственных условиях.

    Репозиторий на github.com: https://github.com/LinkFly/lisp-dev-tools
    Sunday, May 20th, 2012
    10:30 pm
    Проект LISP-DEV-TOOLS. Новая версия - 0.0.4.
        Новая версия проекта lisp-dev-tools примечательна доведением подсистемы тестирования до очень серьёзного уровня - теперь запустив автоматическое тестирование (выполнив ./tests/run-tests.sh) есть возможность получить однозначный и качественный ответ: готова ли система для использования всего предоставляемого функционала. Впрочем, можно теперь исключать не представляющие интереса тесты или запускать только конкретные тесты (см. описание параметров --exclude и --only в конце README). Кроме того, система после тестов возвращается в своё исходное состояние! Многочисленные сообщения о содержимом каталогов и их размере до и после запуска тестов - помогут проконтролировать корректность возвращения в состояние, которое было до запуска тестов. Таким образом, до использования каких-либо возможностей системы, можно предварительно протестировать качество предоставления этих возможностей.

        И ещё одно важное добавление: к списку официально поддерживаемых дистрибутивов, прибавился ещё один, весьма актуальный на сегодняшний день - Ubuntu Server 12.04/x86-64. Правда последний релиз лисп-системы CMUCL 20c, по неизвестным пока причинам отказывается собираться на этой системе. Тестирование всего остального - показывает положительный результат, тесты при этом запускаются таким образом: ./tests/run-tests.sh --exclude=CMUCL
     
    Новое в версии 0.0.4 (по сравнению с версией 0.0.3):
    -----------------------------------------------------
    1. Добавлена поддержка дистрибутива Ubuntu Server 12.04/x86-64. В нём сейчас всё работает кроме компиляции лисп-системы CMUCL.
    2. Добавлено тестирование команды ./rebuild-lisp для SBCL (по умолчанию эта система устанавливается из бинарников).
    3. Исправлена ошибка с тестом постройки и установки Emacs - если до тестов его не было, то тестируется команда ./remove-emacs, которая соответственно его удаляет.
    4. Добавлен ключ --exclude для команды, запускающей тесты ./run-tests.sh, позволяющий указать что необходимо исключить из тестов.
    5. Добавлен ключ --only для команды, запускающей тесты ./run-tests.sh. Если значение ключа (пример: --only="SBCL WGET") задано, то тесты, не включенные в это значение - запускаться не будут.
    6. Реализовано полное восстановление системы после запуска тестов.
    7. Добавлен вывод содержания директорий и их размеров (а также общего размера) перед началом тестов и после них (для того, чтобы в случае ошибки возврата системы в исходное состояние можно было найти источник несоответствия).

        ... а также:

    8. Добавлены в проект (зафиксированы с помощью файла .gitignore) некоторые пустые директории, которые всё равно создаются во время работы проекта (для определённости).
    9. Обновлен список задач и план проекта.
    10. Большой рефакторинг кода тестов.
    11. Некоторые изменения для будущей поддержки BusyBox.
    12. Исправление постройки XCL на Ubuntu разные версий.
    13. Обновление некоторых URL-ов для успешной загрузки поддерживающего софта.
    14. Исправлена команда постройки CCL.
    15. Добавлены зависимости в параметры CCL для успешной постройки и перестройки (building, rebuilding).
    16. Исправлены другие ошибки.
    ------------------------------------------------------------------------------
    Saturday, May 19th, 2012
    6:57 pm
    Проект LISP-DEV-TOOLS. Анонс следующей версии 0.0.4.
    Ближайшие дни будет оформлен следующий релиз проекта lisp-dev-tools.
    Основные новые возможности проекта в поддержки ещё одного дистрибутива и усовершенствованное автоматическое тестирование. Ещё один поддерживаемый дистрибутив: Ubuntu Server 12.04 (x86-64). В части тестов: добавляется тестирование команды ./rebuild-lisp (для SBCL) и два параметра --exclude и --only для исключения некоторых тестов и соответственно для запуска конкретных тестов. А также полное восстановления состояние (и соответствующая проверка) после выполнения тестов.
    Thursday, April 19th, 2012
    2:14 pm
    Проект LISP-DEV-TOOLS. Новая версия - 0.0.3.
        Можно смело утверждать что новая версия проекта lisp-dev-tools стала гораздо более зрелой чем предыдущая. Особенно хочется отметить появление двух серьёзных возможностей: это появление обобщённого интерфейса использования (параметров командной строки --common-load --common-eval и --common-quit) и появление автоматических тестов (запукаются скриптом tests/run-tests.sh). Если в дистрибутиве, не входящим в число поддерживаемых, все тесты успешно пройдены - проект вполне можно рекомендовать к использованию в этом дистрибутиве! Если же тесты провалились, но вам нужно использовать проект в конкретном дистрибутиве - просто опишите программно-аппаратную среду (процессор, память, архитектуру, версию ядра, название/тип/версию дистрибутива ...) и вышлите мне результаты тестов из папки tests/tests-results/

    Новое в версии 0.0.3 (по сравнению с версией 0.0.2):
    -----------------------------------------------------
    1. Добавлено автоматическое тестирование, запускается скриптом tests/run-tests.sh (рекомендуется запускать перед промышленной эксплуатацией).
    2. Сделан общий интерфейс запуска для всех (за исключением XCL и WCL - они не поддерживают параметры
       запуска) лисп-систем. Он представлен тремя ключами:
         --common-load <файл_с_лисп_кодом>
         --common-eval <заключенный_в_кавычки_лисп-код>
         --common-quit
    3. Осуществлено разделение на современные (modern), слишком сырые/молодые (young) лисп-системы и
        устаревшие (obsolete). Это отображено в параметрах запуска скриптов ./get-all-lisps и ./provide-all-lisps
    4. Налажена работа с символическими ссылками (для управления поддерживающис софтом) теперь изменение в поддерживающих инструментах не влиют на git.
    5. Удалены некоторые уже не нужные файлы.
    6. Обновлён и изменён TODO: скорректировано состояние выполненных и назначенных задач, а также изменён и скорректирован план развития проекта.
    7. Начаты работы по отладке работы проекта в дистрибутиве Arch Linux.
    8. Скорректированы некоторые сообщения системы.
    9. Удалён баг проявляющийся при постройке XCL на Ubuntu 11.04 x86_64.
    10. Исправлена загрузка JRE среды для системы ABCL.
    11. Устранен баг в загрузке архивов с лисп-системами.
    12. Изменена версия gawk - для устранение ошибки в обеспечении лисп-системы GCL.
    13. Сглаживание различий в выводе программы "file", сейчас в проверке на символическую ссылку с помощью "readlink"
    14. Устранены также другие ошибки.
    ------------------------------------------------------------------------------
    Tuesday, February 28th, 2012
    12:27 am
    Проект LISP-DEV-TOOLS. Новая версия - 0.0.2.
       С радостью сообщаю о выходе новой версии проекта lisp-dev-tools анонс которого был сделан в предыдущем посте. Эта версия хоть и имеет не слишком пафосный номер, тем не менее очень серьёзно повзрослела по сравнению с предыдущей версией и неуклонно движется к мажорному релизу проекта. Отловлены неожиданные сюрпризы (что особенно важно для новичков), в смысле исправлены баги и устранены найденные неоднозначности, возникающие при работе с системой. Особенно хочется отметить добавление целых трёх поддерживаемых дистрибутивов Linux и появление совершенно прозрачной (и корректно работающей) проброске параметров к запускаемой лисп-системе, с возможностью посмотреть сформированную командную строку без её выполнения. А также возможность её сохранить для анализа и/или использования в дальнейшем (можно её скопировать и когда необходимо вставить в шелл или скрипт и выполнить, получив аналогичный запуск системы). Ниже перечислены конкретные изменения.

    Новое в версии 0.0.2 (по сравнению с версией 0.0.1):
    1. Создан и "закреплён" список задач и примерный план добавления возможностей в следующие версии.
    2. Исправлена корректировка файла Makefile для постройки XCL.
    3. Добавлена поддержка 3-ёх дистрибутивов Linux:
               - Ubuntu Server 10.10/x86-64
               - Debian 6.0.4/x86-64
               - Gentoo (из livedvd-amd64-multilib-2012)
    4. Добавлена полноценная поддержка интерпретатора bash (ранее корректная обработка скриптов производилась только интерпретатором dash).
    5. Удалена загрузка и компиляция софта от которого зависит поддерживающий софт, если он уже в наличии.
    6. Скорректированы зависимости для постройки лисп-систем.
    7. Устранена ошибка появляющаяся при перекомпиляции SBCL через ./rebuild-lisp, появляющаяся из-за попыток скрипта сборки лисп-системы - прочитать .git директорию.
    8. Устранена ошибка поиска библиотеки libgmp.so.3 для постройки и запуска WCL.
    9. Добавлена возможность полностью корректного проброса параметров к текущей лисп-системе, указываемых при запуске ./run-lisp <множество параметров>.
    10. Добавлена специальная переменная GET_CMD_P, которая при установке в значение "yes" позволяет получить полностью корректную командную строку с которой запускается лисп-система (эту коммандную строку можно скопировать, вставить в шелл, выполнить и получить тот же результат который был бы если при выполнении "./run-lisp <какие-то параметры>", переменная GET_CMD_P была бы не равна "yes".
    11. К значение переменной XDG_CONFIG_DIRS при запуске лисп-системы (через ./run-lisp ...) добавляется полный путь вида "<директория_с_lisp-dev-tools>/conf".
    12. Появляющиеся сообщения системы стали более лаконичными и читабельными.
    13. Для успешного использования рантайм опций лисп-систем, опции загрузки Quicklisp перенесены в конец формируемой коммандной строки.
    14. Добавлена таблица в формате CSV - status.csv. В ней отображено текущее состояние поддержки lisp-dev-tools в различных дистрибутивах (имеется в виду стабильная и ожидаемая работа, без неожиданных сюрпризов).
    15. Множество баг-фиксов связанных с обеспечением (загрузкой, постройкой и инсталяцией в lisp-dev-tools, при необходимости) поддерживающего софта.
    16. Исправление множества багов, связанных с обеспечением и выполнением лисп-систем (в том числе, связанные с различием в дистрибутивах).

    Wednesday, February 22nd, 2012
    12:56 pm
    Проект LISP-DEV-TOOLS. Анонс следующей версии 0.0.2.
       Ближайшие дни оформлю следующий минорный релиз проекта lisp-dev-tools. Проект был представлен в посте Заповедник для Лисп'ов - проект LISP-DEV-TOOLS. Несмотря на весьма скромный номер версии проект серьёзно развился:
     - оттестирована работа на большем кол-ве дистрибутивов (добавились gentoo, fedora, debian 6.0.4)
     - заведена таблица в формате csv для учёта результатов тестирования работы проекта на разных дистрибутивах.
     - сделано и отлажено совершенно прозрачное "прокидывание" параметров указываемых при запуске скрипта run-lisp а также получение строки запуска текущей лисп-системы (которую можно скопировать, вставить в консоль и запустить, т.е. выводимая строка запуска в точности соотв. реально используемой строке запуска)
    - уточнение и расширение инструкции по использованию и добавление дополнительных примечаний (в README, README_ru)
    - повышена надёжность системы - выявлены и устранены неожиданные сюрпризы связанные с нюансами работы дистрибутивов.
    - устранено множество багов и неоднозначностей в работе системы.
    - сообщение системы стали более подробными и единообразными
    - оформлен список задач проекта (TODO).
    - и другое (подробнее - при выпуске новой версии).
    Monday, January 23rd, 2012
    5:09 pm
    Гипервизор на голом железе ESXi - следующее поколение виртуализации.
    Содержание:

    1. Введение.
    2. Некоторые возможности ESXi.
    3. Управление пользователями и полномочиями.
    4. Скриншоты.
    5. Заключение.
    6. Дополнение.
    7. Ссылки.

    ---------------------------------------

    1. Введение.

        Вначале пара слов о гипервизорах вообще и о понятии "на голом железе" в частности.

        Гипервизор это система управления виртуальными машинам. Как ни странно, встречаются до сих пор люди, незнакомые с понятием "виртуализация". Так что поясню, на всякий случай: виртуальная машина это модель реального компьютера с которой вы можете работать как с настоящим компьютером - ставить ОС, на неё программы и соотв. заходить на сайты, смотреть фильмы и даже играть в игры и прочее-прочее. Чувствую, обязательно следует упомянуть о снэпшотах, ибо был прецедент: в одной, казалось бы, серьёзной фирме, занимающейся разработкой банковского софта - использовалась система виртуализации VMware Workstation без задействования технологии снэпшотов вообще. А ведь это основное преимущество, которое предоставляют системы виртуализации! Итак, снэпшоты (snapshots) это сохранённые (причём идеально сохранённые) состояния виртуальной машины. Эти состояния, как не сложно догадаться, можно в любой момент восстановить. В VMware Workstation и тем более в ESXi можно создавать целые иерархии сохранённых состояний и возвращаться к ним в любой момент времени. Этакая машина времени - совершенно незаменимая вещь для разработчика и исследователя.

        Понятие "голое железо" (bare metal). В нашем контексте, это понятие означает что софт, о котором идёт речь, выполняется без какой-либо предустановленной операционной системы. Конкретно ESXi, представляет собой мини-операционную систему, "заточенную" для выполнение виртуальных машин и для управления ими. То есть это тонкий слой между реальными, физическими ресурсами и виртуальными машинами (и вообще виртуальными объектами, например виртуальными сетями). Зачем это нужно? Да так сразу и не расскажешь ... Ну в первую очередь, конечно же для получения преимуществ от упомянутых снэпшотов. Во вторых, для балансировки нагрузки. В третьих для упрощения администрирования ресурсов. В четвёртых для реализации более совершенной информационной безопасности. И это только первое что пришло ну ум. Итак, некоторые "фичи":
        - сохранение/восстановление состояний
        - балансировка нагрузки
        - упрощеное администрирование
        - безопасность
        
    2. Некоторые возможности ESXi.

        Вот этот текст, кстати сказать, я набираю в Emacs'e, запущенном на рабочей виртуалке (которая, разумеется, на сервере с ESXi). Главный профит на данный момент лично для меня: в любой момент, не делая никаких лишних телодвижений (вообще), я могу сорваться и поехать куда-либо по делам захватив с собой ноутбук (и Yota'у для интернета). И далее, смогу продолжить работу в любом месте, где есть покрытие связи, предоставляемой Yota'ой (а оно есть практически по всей Москве). Я даже виртуалку в Suspend-режим переводить не буду! Кроме того, мне ведь могут понадобиться в процессе и другие виртуальные машины. Например на каком-либо этапе тестирования разрабатываемого софта, необходимо проверить работу в реализуемой виртуальными машинами тестовое среде (одной из). И самое главное: мне ведь необходимо время от времени корректировать тестовые среды, т.е. создавать/изменять виртуальные машины и их состояния(snapshots). В простых случаях, конечно, можно обойтись и другими решениями вроде использования удалённых рабочих столов и обычных не "bare metal" (не на голом железе) системами виртуализации. Но в случае когда кол-во информационных объектов и ресурсов, с которыми ведётся работа, велико - требуется более мощное решение.

        Итак, некоторые полезные фичи:
    1) Пул ресурсов. Есть возможность создавать некие группы ресурсов (пулы ресурсов) и задавать параметры использования физических ресурсов сразу для всей группы. К тому же, эти группы можно вкладывать друг в друга и таким образом строить иерархию из групп и объектов виртуализации.
    2) Настройка ограничений. Тонко настраиваемые ограничения на использование физических ресурсов. Любопытно: настройка ресурсов вплоть до указания конкретной тактовой частоты для виртуальных процессоров.
    3) Резервирование ресурсов. Резервирование физических ресурсов для виртуальных машин и пулов ресурсов.
    4) Управление доступом. Имеется весьма гибкая подсистема управления пользователями, группами, ролями, привилегиями и разрешениями.

        
    3. Управление пользователями и полномочиями.

        Вот на управлении доступом остановимся по подробней. Эта подсистема, должен отметить, чрезвычайно гибкая и лаконичная. Вкратце, она состоит из следующих элементов:
            1) Пользователи. Могут входить в несколько групп.
        2) Группы. Содержат несколько пользователей.
        3) Роли. Содержат набор привилегий.
        4) Привилегии. Установленные привилегии, позволяют определённые действия в системе.
        5) Разрешения. Задаются для конкретного объекта (виртуальной машины или пула ресурсов) и состоят из пар пользователь_или_группа/роль.

        В общем схема такая:
            - создаём необходимых пользователей (вероятно, представляющих реальные личности, которых предполагается допустить к взаимодействию с ESXi), расфасовываем их по группам (например: admins, developers, vm-providers и т.д.).
            - безотносительно к пользователям и группам, создаём некоторое кол-во ролей - они в сущности представляют собой набор определённых привелегий (то есть набор допусков к определённым действиям в системе).
            - для конкретных элементов виртуализации, а именно для виртуальных машин и пулов ресурсов (содержащих виртуальные машины и другие пулы) назначаем разрешения. Под назначением разрешения здесь подразумевается добавление пользователей и групп а ассоциирование их (каждого пользователя и каждой группы) с определёнными ролями, причём каждый пользователь или группа может иметь только одну роль (считаю это разумный баланс между гибкостью и простотой использования).

    4. Скриншоты.

    Иерархия ресурсов и экран виртуальной машины:


    Редактирование разрешений:


    5. Заключение.


        Очевидно, что этой технологии самое место в бизнес-среде как либо связанной с IT (разумеется в случае, если требуется нечто большее чем заполнение данными электронной таблицы и печати документов в Word'e). Особенно это технология проявит себя в области разработки (и эксплуатации) промышленного software, так как позволяет эффективно управлять состояниями вычислительной среды, ресурсами и сократить разрыв между тестовой и продакшен (production) средами выполнения. Считаю, что овладение этой технологией и внедрение туда, где подобное пока ещё не используется - даст мощный технологический рывок вперёд.

    6. Дополнение.

        И напоследок напишу ещё пару слов о некоторых "чудесах", предоставляемых платными вариантами гипервизора:

        VMotion - эта технология позволяет осуществлять "горячую миграцию" (Live Migration) виртуальных машин с одного физического хоста на другой, то есть перемещение виртуальных машин без их выключения и потери сетевых соединений.
        
        DRS (Distributed Resource Scheduler) - эта технология в сущности развитие идеи горячей миграции: контролируя работу, так называемых, DRS-кластеров (это неявное объединение реальных аппаратных ресурсов - физические хосты можно объединять в эти самые DRS-кластеры), она осуществляет автоматическое перемещение виртуальных машин на менее загруженных физические хосты, соответственно без остановки и без потери сетевых соеденений.

        HA (High Availability) - технология высокой доступности, в случае выхода из строя физического хоста, обеспечивает автоматический запуск аналогичных виртуальных машин на другом хосте.

        FT (Fault Tolerance) - технология отказоустойчивости. Представляет собой дальнейшее развитие идеи "высокой доступности (High Availability): На физическом хосте отличного от того на котором выполняется виртуальная машина, эта технология поддерживает точную зеркальную копию этой виртульной машины. Всё что происходит в оригинале, выполняется и в копии. Как только, выйдет из строя оригинальная виртульная машина (например из-за поломки физического хоста) - зеркальная копия подхватит её работу, полностью сохранив контекст выполнения и сетевые соединения (вместе с их состояниями).

        VCB (VMware Consolidated Backup) - это набор инструментальных средств и интерфейсов, обеспечивающих резервное копирование.

        vShild Zones - технология управления сетевым траффиком, проходящим через виртуальные коммутаторы. Позволяет применять политики безопасности для групп виртуальных машин. При осуществлении горячей миграции (посредством технологии vMotion и соотв. использующей её DRS) виртульных машин между физическими хостами - эти политики безопасности сохраняются.

        vCenter Orchestrator - это инструментарий автоматизации рабочих процессов, связанных с управлением гипервизорами, виртуальными объектами и перечисленными технологиями (на его основе был создан отдельный продукт - VMware vCenter Lifecycle Manager.
     
    7. Ссылки.

    1. Зарегистрироваться и скачать ESXi можно здесь.
    2. Сайт корпорации разработчика: http://www.vmware.com/ru/
    3. VMware ESXi Server - основа для центра данных.
    4. vMind.ru - серверная виртуализация.
    5. vmgu.ru.
    Friday, December 30th, 2011
    1:25 am
    Устройство ASDF. Оглавление.

    1. Предисловие.
    2. Архитектура.
    3. Подсистема определения. DEFSYSTEM.
    4. Подсистема определения. PARSE-COMPONENT-FORM.
    5. Подсистема определения. UNION-OF-DEPENDENCIES.
    6. Подсистема загрузки. OPERATE.
    7. Подсистема загрузки. Подсистема планирования операций. TRAVERSE.
    8. Подсистема загрузки. Подсистема планирования операций. DO-TRAVERSE.
    9. Подсистема загрузки. Подсистема планирования операций. DO-DEP.
    10. Подсистема загрузки. MAKE-SUB-OPERATION.
    11. Подсистема загрузки. PERFORM-PLAN и PERFORM-WITH-RESTARTS
    12. Подсистема загрузки. Подсистема выполнения операций. PERFORM.
    13. Подсистема загрузки. FIND-COMPONENT.
    14. Подсистема поиска. FIND-SYSTEM.
    15. Подсистема поиска. COMPUTE-SOURCE-REGISTRY.
    16. Подсистема поиска. RESOLVE-LOCATION
    17. Подсистема определения путей. INPUT-FILES, OUTPUT-FILES и COMPONENT-PATHNAME.
    18. Подсистема определения путей. APPLY-OUTPUT-TRANSLATIONS.
    19. Подсистема определения путей. COMPUTE-OUTPUT-TRANSLATIONS.
    20. Эпилог.
    12:52 am
    Устройство ASDF. Эпилог.
    Это 20-ая статья цикла.
    Первая статья.
    Архитектура ASDF.
    Предыдущая статья.

        Вот и подошёл к концу этот, достаточно объёмный, цикл статей. Главное, что я хотел бы подчеркнуть - этот цикл представляет собой ТВОРЧЕСКИЙ ЭКСПЕРИМЕНТ! Причём эксперимент достаточно длительный и доведённый до конца. Один из смыслов этого эксперимента в том, чтобы разобравшись в устройстве фундаментальной для инфраструктуры языка COMMON LISP, библиотеке, с 10-летней (!) историей развития (не считая её предшественицы MK-DEFSYSTEM) - попытаться описать её строение и функционирование в максимально читабельном (и понятном не только законченым гикам) виде. Задача весьма, надо сказать, не простая. Решить её идеально, уверен, просто невозможно. У потенциальных читателей может быть совершенно разный уровень подготовки, а значит для одних подробное "разжёвывание" покажется скучным, а для других и такое изложение покажется слишком трудным для восприятия. В общем, пришлось проявить интуицию и выбрать некий баланс, ориентируясь на потенциального читателя. Этого читателя я представляю себе как либо очень не обычного индивида с совершенно гипертрофированной любознательностью и жгучим желанием знать как всё устроено изнутри, либо редкого высоко-квалифицированного специалиста хорошо знающего ASDF и по каким-либо причинам желающего узнать её внутреннее устройство, например для написания ASDF-расширений (хотя один тип читателя, не исключает другого, конечно же). Я надеюсь что наличие данного труда сильно помогло и ускорило изучение внутреннего устройства этой фундаментальной и чрезвычайно гибкой системы.

        Увлекательных исследований и интересных проектов!

    --------------------------------------
    Конец цикла.
    Tuesday, December 27th, 2011
    7:57 am
    Устройство 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).

    --------------------------------------------------------
    Продолжение следует ...
    Sunday, December 25th, 2011
    7:03 am
    Устройство ASDF. Подсистема определения путей. APPLY-OUTPUT-TRANSLATIONS.
    (сказаное ниже относится к версии 2.019.7)

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

    APPLY-OUTPUT-TRANSLATIONS
    (defun* apply-output-translations (path) ...)

       Ф-ия осуществляет преобразования путей на основе данных в глобальной переменной *OUTPUT-TRANSLATIONS*. Применяется для определения абсолютного пути к файлу, содержащему результат применения операции к компоненту (правда, заменой расширения на .fasl - она не занимается). Как правило это будет скомпилированный файл с расширением .fasl, получившийся после компиляции исходника с лисп-кодом.

    Примеры:

    (let ((*output-translations*
           '(((T #P"/home/linkfly/.cache/common-lisp/sbcl-1.0.53-linux-x64/**/*.*")))
    )
    )

      (apply-output-translations "/home/user/project/file.lisp")
    )

    => #P"/home/linkfly/.cache/common-lisp/sbcl-1.0.53-linux-x64/home/user/project/file.lisp"

    (let ((*output-translations*
                 '(((#P"/some/path/**/*.*" T)))
    )
    )

      (apply-output-translations "/home/user/project/file.lisp")
    )

    => #P"/home/user/project/file.lisp"

    (let ((*output-translations*
                 '(((#P"/some/path/**/*.*" T)
                    (#P"/media/COMMON_LISP/lisp-dev-tools/lisp/sbcl/sbcl-1.0.53/lib/sbcl/**/*.*"
                     #P"/tmp/lisp-fasls/sbcl/sbcl-1.0.53/lib/sbcl/**/*.*"
    )
    )
    )
    )
    )

      (apply-output-translations
       "/media/COMMON_LISP/lisp-dev-tools/lisp/sbcl/sbcl-1.0.53/lib/sbcl/sb-rt/rt.lisp"
    )
    )

    => #P"/tmp/lisp-fasls/sbcl/sbcl-1.0.53/lib/sbcl/sb-rt/rt.lisp"

    Логика работы в следующем:

    1. Осуществляется проверка: не является ли переданный аргумент PATH типом LOGICAL-PATHNAME и если это так, то PATH просто возвращается без какой-либо обработки.

    2. Если PATH имеет тип PATHNAME или STRING, то вызывается ф-ия ENSURE-OUTPUT-TRANSLATIONS для инициализации глобальной динамической переменной *OUTPUT-TRANSLATIONS* (если этого ещё не было сделано) и осуществляется обработка составляющих её элементов:

       ENSURE-OUTPUT-TRANSLATIONS
       (defun* ensure-output-translations () ...)
       2.1 Ф-ия проверяет была ли проинициализирована *OUTPUT-TRANSLATIONS* (содержит значение отличное от NIL). И если была, то возвращает её первый элемент с помощью ф-ии OUTPUT-TRANSLATIONS, а если нет, то производит инициализацию с помощью ф-и INITIALIZE-OUTPUT-TRANSLATIONS:

           (defun* ensure-output-translations ()
              (if (output-translations-initialized-p)
                  (output-translations)
                  (initialize-output-translations)
    )
    )


           INITIALIZE-OUTPUT-TRANSLATIONS
           (defun* initialize-output-translations
                (&optional (parameter *output-translations-parameter*)) ...
    )

           2.1.1 Предварительно ф-ия изменяет глобальную переменную *OUTPUT-TRANSLATIONS-PARAMETER* на значение переданного аргумента PARAMETER:

                 (setf *output-translations-parameter* parameter ...)

                 Вообще, аргумент PARAMETER используется для реализации двух возможностей:
                      - для включения поддержки старой системы ASDF-BINARY-LOCATIONS (которая предназначена в сущности для того же, для чего применяется и ASDF-OUTPUT-TRANSLATIONS - сортирует скомпилированные файлы в зависимости от пути к исходнику, а также типа и версии лисп-системы).
                      - для отключения сохранения скомпилированных файлов отдельно от исходников с помощью ф-ии DISABLE-OUTPUT-TRANSLATIONS (после её вызова скомпилированные файлы будут лежать рядом с исходниками).

                Затем INITIALIZE-OUTPUT-TRANSLATIONS выполняет главное своё предназначение - инициализацию *OUTPUT-TRANSLATIONS*:

               (setf ...
                      (output-translations) (compute-output-translations parameter)
    )


               ... как видно, осуществляется вызов ф-ии (setf output-translations), её определение декларировано следующим образом:

               (defun* (setf output-translations) (new-value) ...)

               ... в которой уже, переменной *OUTPUT-TRANSLATIONS* присваивается предварительно обработанный список NEW-VALUE, который был возвращён вызовом (compute-source-registry parameter). Этот список состоит из элементов вида (<T_или_PATHNAME> <T_или_PATHNAME>). Суть предварительной его обработки заключается в сортировке этого списка - все элементы-подсписки с первым элементом T выталкиваются в конец списка, а в начало попадают те элементы, в которых кол-во директорий в пути (которое представляется первым элементом) наибольшее:

               (stable-sort (copy-list new-value) #'>
                          
          :key #'(lambda (x)
                                
                (etypecase (car x)
                                   
                 ((eql t) -1)
                                   
                 (pathname
                                      
                   (let ((directory (pathname-directory (car x))))
                                        
                    (if (listp directory) (length directory) 0))))))

                Логика работы ф-ии COMPUTE-SOURCE-REGISTRY тема для отдельной статьи.

       2.2 Преобразование пути, заданного аргументом PATH ф-ия осуществляет руководствуясь парами (<T_или_PATHNAME> <T_или_PATHNAME>), содержащимся в *OUTPUT-TRANSLATIONS*. Для этого организуется цикл, в котором локальная P получает обработанное ф-ией TRUENAMIZE значение. Эта ф-ия, кроме сглаживания некоторых межсистемные различий в обработке путей, занимается "абсолютизацией" пути, используя для этого опциональную переменную DEFAULTS, равную по умолчанию глобальной переменной *DEFAULT-PATHNAME-DEFAULTS*. На каждом шаге итерации берётся упомянутая пара и разбирается на элементы:

           (loop :with p = (truenamize path)
               :for (source destination) :in (car *output-translations*)
               ...
    )


           2.2.1 На каждой итерации локальная переменная ROOT будет получать значение #P"/" если SOURCE либо равна T, либо равна НЕ-абсолютному пути, либо и то и другое:

               (loop
                   ...
                   :for root = (when (or (eq source t)
                                       
             (and (pathnamep source)
                                           
                (not (absolute-pathname-p source))))
                           
                (pathname-root p))
                   ...
    )


               ... иначе, как видно, ROOT получит значение NIL.

           2.2.2 Локальная переменная ABSOLUTE-SOURCE будет получать следующие значение в зависимости от следующих условий:

               - если SOURCE = T, тогда значение #P"/**/*.*"
               - если ROOT была определена, то значение SOURCE "абсолютизируется" (для этого добавляется "/" в начало пути).
               - иначе SOURCE это абсолютный путь и он никак не обрабатывается.

               (loop
                   ...
                   :for absolute-source = (cond
                                         
                 ((eq source t) (wilden root))
                                          
                (root (merge-pathnames* source root))
                                         
                 (t source))                     
                   ...
    )


               ... таким образом ABSOLUTE-SOURCE получает в любом случае абсолютный путь, а если SOURCE = T, то он будет не только абсолютным, но ещё и "wildcard" путём: #P"/**/*.*".

           2.2.3 Если SOURCE = T или путь соответствует шаблону ABSOLUTE-SOURCE, то правило для пути PATH считается найденым и вызывается ф-ия TRANSLATE-PATHNAME* для преобразования пути, а иначе P возвращается как есть:

               (loop
                   ...
                   :when (or (eq source t) (pathname-match-p p absolute-source))
                   :return (translate-pathname* p absolute-source destination root source)
                   :finally (return p)
    )


               TRANSLATE-PATHNAME*
               (defun* translate-pathname* (path absolute-source destination &optional root source)
               Ф-ия оборачивает стандартную ф-ию TRANSLATE-PATHNAME некоторым дополнительным функционалом. Сама стандартная ф-ия получает на вход путь, который надо преобразовать, "wildcard" путь для отделения от пути необходимого окончания и "wildcard" путь, который задаст начало результирующего пути, например:

                    (translate-pathname "/home/user/file.lisp" "/home/**/*.*" "/elsepath/**/*.*")
                    => #P"/elsepath/user/file.lisp"            

                Дополнительный функционал, который несёт в себе TRANSLATE-PATHNAME*, состоит в:

                    - вызове DESTINATION, если это ф-ия (с аргументами PATH и ABSOLUTE-SOURCE)
                    - возвращении пути PATH как есть, если DESTINATION = T
                    - и преобразовании пути с помощью TRANSLATE-PATHNAME (для подстановки пути DESTINATION в начало PATH) - в остальных случаях.

                Описанное выше, осуществляется кодом:

                    (cond
                      ((functionp destination)
                       (funcall destination path absolute-source)
    )

                      ((eq destination t)
                       path
    )

                      ...
                      (t
                       (translate-pathname path absolute-source destination)
    )
    )


                    ... пропущенные варианты не принципиальны и представляют собой превращение пути DESTINATION в абсолютный, если он относительный и сглаживание каких-то тонких нюансов в обработке путей.

    -------------------------------------------
    Продолжение следует ...
    Friday, December 23rd, 2011
    11:30 pm
    Устройство ASDF. Подсистема поиска. RESOLVE-LOCATION
    (сказаное ниже относится к версии 2.019.7)

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

     
       Ф-ия resolve-location необходима для того, чтобы получить нормальный для данной системы путь. Причём учитывая некоторые специальные соглашения ASDF для предопределённых директорий и для использования групповых символов (вроде "*.*" или "**"). Ф-ия занимается обработкой этих соглашений и "разруливает" многочисленные различия в обработке путей на разных платформах/лисп-системах. Вот так выглядит её определение:

       (defun* resolve-location (x &key directory wilden)
          ...)

        В теле RESOLVE-LOCATION используются две функции RESOLVE-ABSOLUTE-LOCATION-COMPONENT и RESOLVE-RELATIVE-LOCATION-COMPONENT (чуть ниже будет представлена схема зависимостей ф-ий друг от друга). Причём RESOLVE-LOCATION может быть вызвана рекурсивно, но не явно (в теле определения ф-ии нет рекурсивных вызовов), а опосредовано - RESOLVE-ABSOLUTE-LOCATION-COMPONENT в определённых ситуациях вызывает RESOLVE-LOCATION.

        RESOLVE-ABSOLUTE-LOCATION-COMPONENT использует:
    1) саму себя (вызывается рекурсивно).
    2) RESOLVE-RELATIVE-LOCATION-COMPONENT.
    3) RESOLVE-LOCATION (т.е. может делать опосредованные рекурсивные вызовы).
    4) динамические переменные *HERE-DIRECTORY*, *USER-CACHE*, *SYSTEM-CACHE*.
    5) общие ф-ии (назовём их так): MERGE-PATHNAMES*, ENSURE-DIRECTORY-PATHNAME, DEFAULT-DIRECTORY, USER-HOMEDIR, ABSOLUTE-PATHNAME-P.
    6) ф-ию WILDEN.
       
        RESOLVE-RELATIVE-LOCATION-COMPONENT использует:

    1) саму себя (вызывается рекурсивно).
    2) дин. переменные: *WILD-DIRECTORY*, *WILD-INFERIORS* *WILD-FILE*.
    3) общие ф-ии: RELATIVIZE-PATHNAME-DIRECTORY, MERGE-PATHNAMES*, ENSURE-DIRECTORY-PATHNAME, ABSOLUTE-PATHNAME-P.
    4) ф-ии для получения текущей реализации: IMPLEMENTATION-IDENTIFIER, IMPLEMENTATION-TYPE.
    5) ф-ию WILDEN.


    На "общих ф-иях" не будем останавливаться, их названия говорят сами за себя. Ф-ии для получения текущей
    реализации, в случае SBCL/Linux работают так:

    (implementation-identifier) => "sbcl-1.0.48-linux-x86"
    (implementation-type) => :SBCL

    Ф-ия WILDEN добавляет путь из "wildcard" символов в конец пути, это говорит о том что это: "любой файл или директория имеющие началом указанную директорию", обычно это путь #P"**/*.*".

    Примеры:

    (resolve-location :home)
    = > #P"/home/someuser/"

    (resolve-location '(:root :implementation "main-source"))
    => #P"sbcl-1.0.48-linux-x86/main-source"

    (resolve-location '(:home (:implementation-type "my-source") "main-source"))
    => #P"/home/lispuser/sbcl/my-source/main-source"

                        Логика работы RESOLVE-LOCATION.

    1. Ф-ия передаёт управление (вызывает) ф-ию RESOLVE-ABSOLUTE-LOCATION-COMPONENT если X - атом.

       (if (atom x)
          (resolve-absolute-location-component x :directory directory :wilden wilden)

    2. Если X не атом, а список, то организуется итерация по списку, при этом:

        2.1 Определяется сразу базовый абсолютный путь PATH (который дальше будет аргументом SUPER в вызовах RESOLVE-RELATIVE-LOCATION-COMPONENT). Для его определения берётся первый элемент из списка X:

       (loop :with path = (resolve-absolute-location-component
                                    (car x) :directory (and (or directory (cdr x)) t)
                                    :wilden (and wilden (null (cdr x))))
           ...)

        ... как видно ключ :DIRECTORY для RESOLVE-ABSOLUTE-LOCATION-COMPONENT равен T если либо задан соотв. ключ в вызове RESOLVE-LOCATION либо в списке X больше одного аргумента, а :WILDEN если задан соотв. ключ и в списке X нет больше аргументов.

        2.2 Собственно, декларирование прохода по списку X, начиная с его хвоста. Локальная переменная COMPONENT будет получать текущий элемент, а переменная MOREP будет нам говорить о том, остались ли в списке ещё элементы:

       (loop
           ...
           :for (component . morep) :on (cdr x)
           ...)

        2.3 Определяется локальная переменная DIR - она будет вычисляться на каждой итерации и будет равна T, если значение ключа DIRECTORY равно T либо если в списке ещё есть элементы (MOREP не равна NIL):

       (loop
           ...
           :for dir = (and (or morep directory) t)
           ...)
       
        2.4 Определяется локальная переменная WILD - она также вычисляется на каждой итерации и равна истине, только в случае если значение WILDEN = T и в списке нет больше элементов (то есть это последняя итерация):

       (loop
           ...
           :for wild = (and wilden (not morep))
           ...)

        2.5 В главном операторе итерации вычисляется новый PATH с помощью старого PATH и вычисленных локальных переменных. Старый PATH становится началом для вычисляемого пути (значением аргумента SUPER для RESOLVE-RELATIVE-LOCATION-COMPONENT):

       (loop
           ...
           :do (setf path (resolve-relative-location-component
                   path component :directory dir :wilden wild))
           ...)

        2.6 После прохода по списку возвращается переменная PATH:

             (loop ... :finally (return path))


                    RESOLVE-ABSOLUTE-LOCATION-COMPONENT.
                    (defun* resolve-absolute-location-component (x &key directory wilden) ...)

        Ф-ия занимается тем, что формирует путь используя строки, имена путей (pathnames), предопределённые ключи :root :home :here :user-cache :system-cache :default-directory , а также списки. Если передаётся список, то для его головы происходит рекурсивный вызов и результат становится началом вычисляемого пути, а его окончанием - результат предварительной обработки хвоста ф-ей RESOLVE-RELATIVE-LOCATION-COMPONENT.

    Примеры:

    (resolve-absolute-location-component :root)
    => #P""

    (resolve-absolute-location-component "/my/path")
    => #P"/my/path"

    (resolve-absolute-location-component '(:home ("my" "path")))
    => #P"/home/lispuser/my/path"

                    Логика работы RESOLVE-ABSOLUTE-LOCATION-COMPONENT.
    1. Создаёт локальный контекст и устанавливает значение для лексической переменной R в соответствии с типом аргумента X:

       (let* ((r (etypecase x ...))
           ...)
          ...)

        LET -> ETYPECASE
        1.1 Если X путь (pathname) то она просто возвращается. Если строка, то  используется либо ENSURE-DIRECTORY-PATHNAME (которая выбрасывает ошибку в случае если данный pathspec не получается рассматривать как путь к директории) либо PARSE-NAMESTRING (что по крайней мере для SBCL чревато проблемами в обработке путей и лучше для этой системы было бы использовать SB-EXT:PARSE-NATIVE-NAMESTRING) в зависимости от переданного аргумента DIRECTORY:

         (etypecase x
                (pathname x)
                (string (if directory (ensure-directory-pathname x) (parse-namestring x)))
            ...)

        LET -> ETYPECASE
        1.2 Если X список (cons), то осуществляется выход из ф-ии с некоторым значением:
       
         (etypecase x
            ...
            (cons
                 (return-from resolve-absolute-location-component (if ...) ...))
             ...)
            
             ... которое формируется следующим образом:

                LET -> ETYPECASE -> RETURN-FROM
                1.2.1 Если список состоит из одного элемента, то ф-ия просто рекурсивно вызывается для этого элемента:

                   (if (null (cdr x))
                      (resolve-absolute-location-component   
                         (car x) :directory directory :wilden wilden)
                  ...)

                LET -> ETYPECASE -> RETURN-FROM
                1.2.2 Иначе вычисляется путь, в котором началом станет результат вызова RESOLVE-ABSOLUTE-LOCATION-COMPONENT с первым элементом списка:

                    (resolve-absolute-location-component (car x) :directory t :wilden nil)
                                 
                 ... а его окончанием, результат вызова RESOLVE-RELATIVE-LOCATION-COMPONENT с хвостом списка:

                    (resolve-relative-location-component (cdr x) :directory directory :wilden wilden)   
                                                             
            LET -> ETYPECASE
            1.3 Также может быть задан один из предопределённых ключей:

                :root :home :here :user-cache :system-cache :default-directory
           
    :root - просто некоторый относительный путь, возможно (если :wilden = t) с "wildcard" символами.

    :home - путь становится равным домашней директории текущего пользователя (на Linux это #P"/home/someuser/").

    :here - если была задана некоторая текущая директория для процесса обработки, через динамическую переменную *HERE-DIRECTORY*, то вызывается resolve-location для этой директории иначе она вызывается с ключом :default-directory :

       (resolve-location (or *here-directory*
                  ;; give semantics in the case of use interactively
                 :default-directory)
                  :directory t :wilden nil)

       ... динамическая переменная *HERE-DIRECTORY* обычно задаётся при обработке файла конфигурации имя которого в *SOURCE-REGISTRY-FILE* (по умолчанию "source-registry.conf") и будет равна директории в которой этот файл находится, либо при обработке директории, имя которой в *SOURCE-REGISTRY-DIRECTORY* (по умолчанию "source-registry.conf.d/"), с конфигурационными файлами и будет равна ей же.

    :user-cache - здесь вызывается RESOLVE-LOCATION со значением *USER-CACHE*, которая по умолчанию равна:

        '(:HOME ".cache" "common-lisp" :IMPLEMENTATION)

        ... результат вызова на Linux будет таким:

        (resolve-location *user-cache* :directory t :wilden nil)

        => #P"/home/someuser/.cache/common-lisp/sbcl-1.0.48-linux-x86/"

    :system-cache - при использовании этого ключа сигнализируется ошибка с сообщением о том, что этот ключ устарел.

    :default-directory - вычисляется директория из *DEFAULT-PATHNAME-DEFAULTS*.

        LET
        2. Далее в локальном контексте определяется переменная S. Её значение вычисляется с использованием определённой ранее R. Если был задан ключ :WILDEN и тип X не является типом PATHNAME то к значению R прибавляется путь #P"**/*.*" (для обозначения любого файла и директории с началом пути равным R), иначе просто берётся значение R:

       (let (...
               (s (if (and wilden (not (pathnamep x)))
                      (wilden r)
                      r)))
         ...)

        LET
        3. Проверяется что переменная S обозначает абсолютный путь, если это не так выбрасывается ошибка. Иначе S возвращается из ф-ии RESOLVE-ABSOLUTE-LOCATION-COMPONENT.


            RESOLVE-RELATIVE-LOCATION-COMPONENT.
            (defun* resolve-relative-location-component (super x &key directory wilden) ...)
           
        Зачем нужна RESOLVE-RELATIVE-LOCATION-COMPONENT ? Она необходима для обработки ещё нескольких соглашений ASDF по формированию пути. Они нужны для повышения гибкости в задании пути, с помощью так называемых "wildcard" символов (символов для обозначения группы файлов/директорий, например: "*"), предопределённых имён директорий и единообразного использования ASDF на разных платформах. В соглашении применяются следующие ключи:
       
              :default-directory :*/ :**/ :*.*.* :implementation :implementation-type :uid (только для unix-систем)
              
         Работа этой ф-ии напоминает работу ф-ии resolve-absolute-location-component.

    Примеры:

    (resolve-relative-location-component "/home/user/" :**/)
    => #P"/home/user/**/"

    (resolve-relative-location-component nil :**/)
    => #P"**/"

    (resolve-relative-location-component "/tmp/" '(:implementation :uid :**/))
    => #P"/tmp/sbcl-1.0.48-linux-x86/1000/**/"

                    Логика работы RESOLVE-RELATIVE-LOCATION-COMPONENT.

    1. Формируется локальный контекст в котором определяется переменная R, её значение вычисляется в зависимости от типа переданной переменной X:

    (let* ((r (etypecase x   
                  ...
    )
    )
              ...)
        ...)

        LET -> ETYPECASE
        1.1 Если X имеет тип PATHNAME или STRING то переменная R приравнивается к X:

       (etypecase x
          (pathname x)
          (string x)
          ...)

        LET -> ETYPECASE
        1.2 Если X список (cons), то осуществляется выход из ф-ии с некоторым значением:

       (etypecase x
          ...
          (cons
           (return-from resolve-relative-location-component ...))
          ...)
                   
        ... которое вычисляется следующим образом:
               
            LET -> ETYPECASE -> RETURN-FROM   
            1.2.1 Если список из одного элемента ты происходит рекурсивный вызов с этим аргументом:

                   (if (null (cdr x))
                        (resolve-relative-location-component
                               super (car x) :directory directory :wilden wilden)
                        ...)

            LET -> ETYPECASE -> RETURN-FROM
            1.2.2 Если же нет, то всё происходит почти также как и в RESOLVE-ABSOLUTE-LOCATION-COMPONENT:

                LET -> ETYPECASE -> RETURN-FROM -> IF
                1.2.2.1 Формируется локальный контекст с переменной CAR, в котором происходит рекурсивный вызов:
                         
                       (let* ((car (resolve-relative-location-component
                                                      super (car x) :directory t :wilden nil
    )
    )
                               ...)
                          ...)

            LET -> ETYPECASE -> RETURN-FROM -> IF
            1.2.2.2 Дальше значение CAR используется для задания начала вычисляемого пути, а его окончание формируется с помощью рекурсивного вызова RESOLVE-RELATIVE-LOCATION-COMPONENT:

                    (merge-pathnames*
                         (resolve-relative-location-component
                      
            (cdr x) :directory directory :wilden wilden)
                         car
    )


        LET -> ETYPECASE
        1.3 Если же X является одним из следующих ключей:

        :default-directory :*/ :**/ :*.*.* :implementation :implementation-type :uid (только для unix-систем)        

        ... то возвратить некоторое отображенное на этот ключ значение. Ниже представлены типичные значения на которые отображаются ключи для конфигурации SBCL/Linux:

    :default-directory = <значение *default-pathname-defaults* превращенное в относительный путь>
    :*/ = *wild-directory* = #P"*/"
    :**/ = *wild-directory* = #P"**/"
    :*.*.* = *wild-file* = #P"*.*"
    :implementation = (implementation-identifier) = "sbcl-1.0.48-linux-x86"
    :implementation-type = (string-downcase (implementation-type)) = "sbcl"
    :uid = (princ-to-string (get-uid)) = "1000"

    2. Проверка на относительность пути - выбрасывается ошибка в случае, если R представляет собой абсолютный путь:

       (when (absolute-pathname-p r)
          (error (compatfmt "~@<pathname ~S is not relative~@:>") x)
    )


    3. Пост-обработка окончательного результата. Если ключ WILDEN был задан и значение X не имеет тип PATHNAME , то значит надо добавить в конец пути относительный "wildcard" путь:

          (if (or (pathnamep x) (not wilden)) r (wilden r)))
                     
        ... например: (wilden "/home/someuser/") => #P"/home/someuser/**/*.*" - и это будет означать: "любые файлы и директории начинающиеся с /home/someuser/". Иначе R возвращается "как есть".

    -------------------------------------------------------------
    Продолжение следует ...
    10:02 pm
    Устройство ASDF. Подсистема определения путей. INPUT-FILES, OUTPUT-FILES и COMPONENT-PATHNAME.
    (сказаное ниже относится к версии 2.019.7)

    Это 17-ая статья цикла.
    Первая статья.
    Архитектура ASDF.
    Предыдущая статья.
     
    INPUT-FILES
    Ф-ия используется для получения абсолютного пути к файлу, представляющему компонент. Этим файлом будет в большинстве случаев либо файл *.lisp с лисп-исходником, либо файл *.fasl с результатом компиляции исходника. Для комбинаций аргументов (COMPILE-OP STATIC-FILE) и (OPERATION MODULE) метод ничего не делает. При вызове с аргументами (OPERATION COMPONENT) происходит следующее:

    1. В локальной PARENT сохраняется предок компонента.
    2. В локальной SELF-DEPS, сохранются пары всех операций, в которых вторым значением является сам этот компонент.
    3. Если нет пар (нет операций с этим компонентом) в SELF-DEPS, просто возвращается абсолютный путь к компоненту.
    4. Если SELF-DEPS не NIL, создаётся список из результатов применения анонимной ф-ии к каждой паре в SELF-DEPS. Эта ф-ия разбивает пару на операцию и имя компонента, а затем вычисляет путь к файлу, который связан как-либо с результатом применения операции. В случае с операцией COMPILE-OP этим результатом будет абсолютный путь к скомпилированному файлу (обычно *.fasl). Отвечает за вычисление пути к выходному файлу ф-ия OUTPUT-FILES:

      (output-files (make-instance op)
              (find-component parent name)
    )


              ... ф-ия FIND-COMPONENT относится к подсистеме загрузки, поэтому я здесь её рассматривать не буду. Смысл её тривиален - возвратить компонент с именем NAME найденный в PARENT.

    OUTPUT-FILES
    Эта ф-ия возвращает абсолютный путь к результату компиляции исходника (обычно это файл с расширением .fasl) Это обобщённая ф-ия включающая 5 методов, 3 из которых:

      (defmethod output-files load-source-op component)
      (defmethod output-files operation component)
      (defmethod output-files compile-op static-file)

       ... ничего не делают, а просто возвращают NIL.

    Другой метод является декоратором "обёртывающим" все последующие методы. А последний специализируется на (COMPILE-OP CL-SOURCE-FILE) аргументах:

    1 Сначала выполняется декорирующий метод со стандартным комбинатором :AROUND :

       (defmethod output-files :around (operation component) ...)

       Смысл его в том, чтобы список файлов, возвращенных следующим методом (по умолчанию, если не было определено дополнительных методов, это будет список из одного файла) обработать ф-ией APPLY-OUTPUT-TRANSLATIONS:

       (multiple-value-bind (files ...) (call-next-method)
           ...
           (mapcar #'apply-output-translations files)
    )


       Ф-ия APPLY-OUTPUT-TRANSLATIONS, конструирует абсолютные пути файлов ориентируясь на глобальную переменную *OUTPUT-TRANSLATIONS*. Эта переменная содержит wildcard пути (шаблоны путей, например: #P"/home/someuser/.cache/common-lisp/sbcl-1.0.48-linux-x86/**/*.*"). Она трансформирует путь компонента, таким образом, чтобы заменить начало пути по которому содержится компонент на специальный путь, формируемый на основе имени пользователя, версии лисп-системы и возможно других правил, которые достаточно гибко настраиваются. Логика работы этой ф-ии и принцип настройки wildcard путей во многом схожи с логикой работы поиска *.asd файлов и настройкой путей для их поиска.

    2. И самый главный метод позволяющий получить путь для скомпилированного файла:

       (defmethod output-files ((operation compile-op) (c cl-source-file)) ...)

       Метод работает достаточно просто: вызывает (component-pathname с) для получения абсолютного пути компонента, превращает его в путь к лисп-файлу (добавляя расширение *.lisp) и выясняет с помощью стандартной ф-ии COMPILE-FILE-PATHNAME путь до скомпилированного файла:

       (let ((p (lispize-pathname (component-pathname c))))
          -broken-fasl-loader (list (compile-file-pathname p))
          +broken-fasl-loader (list p)
    )


        ... как мы видим, учитывается некая ситуация, в которой загрузчик fasl-файлов может не работать.

    COMPONENT-PATHNAME
    Ф-ия возвращает значение слота ABSOLUTE-PATHNAME если он установлен. Иначе вычисляется абсолютный путь, установливается вычисленное значение в этот слот и возвращается. Принцип вычисления в том, что путь формируется из имён предков компонента и/или путей, сохраненных в слоте relative-pathname каких-либо предков этого компонента. Начало же абсолютного пути берётся из самого верхнего в иерархии компонента, который имеет тип SYSTEM и содержит абсолютный путь, вычисленный ещё при выполнении формы (defsystem ...).

    Подробная логина работы состоит в следующем:

    1. Проверка: связан ли слот компонента ABSOLUTE-PATHNAME, если да, то возвращается его значение:

         (if (slot-boundp component 'absolute-pathname)
              (slot-value component 'absolute-pathname)
              ...
    )


    2. Если нет, то вычисляется абсолютный путь из относительного пути компонента и абсолютного пути его предка:

       (merge-pathnames*
         (component-relative-pathname component)
         (pathname-directory-pathname (component-parent-pathname component))
    )


       COMPONENT-RELATIVE-PATHNAME
       (defmethod component-relative-pathname ((component component)) ...)
       2.1 Относительный путь конструируется с помощью ф-ии COERCE-PATHNAME. Она вызывается с именем компонента (или его специфическим, относительным путём заданным опцией :path в описании компонента), ключом :type, ассоциированном со значением, возвращаемым ф-ией SOURCE-FILE-TYPE (для компонента класса CL-SOURCE-FILE это будет конечно же "lisp") и относительным путём его предка:

           (coerce-pathname
             (or (slot-value component 'relative-pathname)
                 (component-name component)
    )

             :type (source-file-type component (component-system component))
             :defaults (component-parent-pathname component)
    )


       COMPONENT-PARENT-PATHNAME
       2.2 Ф-ия для вычислеиня пути обращается к ф-ии COMPONENT-PATHNAME. Таким образом производятся неявные рекурсивные вызовы, вплоть до получения пути из самого "верхнего" объекта в иерархии компонентов, которым должен быть объект класса/подкласса SYSTEM.
        
    3. Если путь вычислен и он не абсолютный, то сигнализируется ошибка с сообщением "Invalid relative pathname ...".

    4. В слот ABSOLUTE-PATHNAME компонта устанавливается вычисленный абсолютный путь и он же возвращается в качестве результата ф-ии.

    --------------------------------------
    Продолжение следует ...
    Wednesday, December 21st, 2011
    3:14 pm
    Устройство 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 если это директория).

    -----------------------------------------
    Продолжение следует ...
    Tuesday, December 20th, 2011
    7:21 pm
    Устройство ASDF. Подсистема поиска. FIND-SYSTEM.
    (сказаное ниже относится к версии 2.019.2)

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

        Обобщённая ф-ия FIND-SYSTEM используется для поиска в оперативной памяти и на диске системы с именем NAME. Она возвращает объект, представляющий систему (экземпляр класса или подкласса SYSTEM). Сначала производится поиск в оперативной памяти уже определённой системы, конкретно в хэш-таблице связанной с символом *DEFINED-SYSTEMS*. Далее следует поиск файла с определением системы (файла с расширением *.asd). В случае, если система отсутствует в оперативной памяти или файл с её определением был обновлён - она загружается с помощью ф-ии LOAD-SYSDEF. Главный метод обобщённой ф-ии специализируется на объекте типа STRING, метод без специализации (или можно сказать специализирующийся на типе T) приводит имя параметра к унифицированной форме (к типу STRING) и передаёт управление соответствующему методу:

       (defmethod find-system (name &optional (error-p t))
          (find-system (coerce-name name) error-p)
    )


        А метод специализирующийся на типе NULL просто сигнализирует об ошибке с сообщением: "NIL is not a valid system name".

        Рассмотрим по пунктам процесс поиска/загрузки главного метода ф-ии FIND-SYSTEM:

       (defmethod find-system ((name string) &optional (error-p t)) ...)

    1. Тело функции заворачивается в макрос WITH-SYSTEM-DEFINITIONS. Смысл этого макроса в том, чтобы гарантировать наличие динамической переменной *SYSTEMS-BEING-DEFINED*, с которой связывается хэш-таблица, в которую попадают системы загружаемые в процессе поиска.

    2. Организуется бесконечный цикл, на каждой итерации которого устанавливается рестарт REINITIALIZE-SOURCE-REGISTRY-AND-RETRY, при выборе которого переинициализируется динамическая переменная *SOURCE-REGISTRY*, посредством вызова ф-ии INITIALIZE-SOURCE-REGISTRY. Таким образом пользователю позволяется исправить ошибку (например поместить систему, что не была найдена в пути поиска систем) и продолжить поиск (или продолжить выполнение ф-ии, которая инициирует поиск системы, например ф-ию LOAD-SYSTEM).

    3. С помощью MULTIPLE-VALUE-BIND определется локальный контекст для переменных FOUND-SYSTEM, PATHNAME, PREFIOUS-TIME:

       (multiple-value-bind (foundp found-system pathname previous previous-time)
            (locate-system name)
          (declare (ignore foundp))
          ...
    )


        Как видно, для их связывания используется множественное значение возвращаемое ф-ей LOCATE-SYSTEM.

        LOCATE-SYSTEM
        3.1 Ф-ия LOCATE-SYSTEM ищет систему в оперативной памяти, при необходимости загружая определение системы с диска. Логика работы в следующем:

            3.1.1 Сначала производится поиск ф-ией SYSTEM-REGISTERED-P, которая просматривает хэш-таблицу *DEFINED-SYSTEMS*. Если система была найдена, то локальная переменная IN-MEMORY получает пару из временной отметки и объекта системы:

            (let (...
                    (in-memory (system-registered-p name))
             ...
    )


            3.1.2 Локальная переменная PREVIOUS получает найденную систему из поля cdr пары IN-MEMORY.

            3.1.3 Локальная PREVIOUS-TIME получает временную отметку, которую берёт поле car пары IN-MEMORY.

            3.1.4 Затем производится поиск объекта системы или пути к файлу с её определением (файлу *.asd) с помощью ф-ии SEARCH-FOR-SYSTEM-DEFINITION. Эта ф-ия обращается сначала к ф-ии FIND-SYSTEM-IF-BEING-DEFINED, которая просматривает упоминавшийся выше хэш *SYSTEMS-BEING-DEFINED* (тот самый, гарантию существования которого обеспечивает макрос WITH-SYSTEM-DEFINITIONS). Если система не была найдена, то производится поиск файла с её определением, сначала в *central-registry* c помощью ф-ии SYSDEF-CENTRAL-REGISTRY-SEARCH, а затем в *SOURCE-REGISTRY* с помощью ф-ии SYSDEF-SOURCE-REGISTRY-SEARCH. Примечательный момент: если и на этот раз не удалось найти систему, то с помощью вызова ф-ии SYSDEF-FIND-ASDF происходит проверка, не саму ли ASDF-систему пытаются найти и если это так - то создаётся и возвращается объект системы с именем "asdf". Найденный путь или система связывается с локальной FOUND. Если была найдена система, а не путь - то результат также связывается с локальной FOUND-SYSTEM.
                          
            3.1.5 С локальной PATHNAME связывается путь к файлу с определением системы. Если система уже загружена в оперативную память, то путь к файлу с определением вычисляется с помощью ф-ии SYSTEM-SOURCE-FILE.

            3.1.6 Локальная FOUNDP связывается со значением T если что-либо было найдено (система или путь к её определению).

            3.1.7 Если путь PATHNAME был вычислен, не является абсолютным и представляет собой символическую ссылку, то он вычисляется в реальный путь, который становится новым значением pathname. Если система была найдена (путь брался из объекта системы), то слот объекта системы SOURCE-FILE получает новый реальный путь к файлу с её определением.

            3.1.8 Если система была найдена в оперативной памяти и её путь не равен, найденному в путях поиска, файлу с определением системы, то найденный путь становится новым путём к файлу определения системы и локальная previous-time приравнивается к NIL.

            3.1.9 Формируется результат:

                (values foundp found-system pathname previous previous-time)

        Итак, с помощью LOCATE-SYSTEM и MULTIPLE-VALUE-BIND было создано локальное окружение в котором определены переменные FOUND-SYSTEM, PATHNAME, PREVIOUS, PREVIOUS-TIME.

        3.2 Если системы не было найдено в оперативной памяти и поиск с помощью SEARCH-FOR-SYSTEM-DEFINITION выдал объект системы (ф-ия FIND-SYSTEM-IF-BEING-DEFINED нашла систему в *SYSTEMS-BEING-DEFINED*, то есть, среди определяемых в данных момент систем), то производится регистрация системы с помощью ф-и REGISTER-SYSTEM. Ф-ия не делает ничего волшебного, а всего лишь проверяет что имя системы является строкой и гарантирует наличие пары (временная_отметка . объект_системы) в хэше *DEFINED-SYSTEMS*.

        3.3 Проверка на актуальность файла с определением системы. Если файл *.asd был изменён, то инициируется перезагрузка системы - вызывается ф-ия LOAD-SYSDEF. Она загружает файл с определением как обычный лисп-файл, только в, специально созданный для этого, временный пакет (который, после загрузки системы удаляет).

        3.4 По завершении, ф-ия FIND-SYSTEM актуализирует (если нужно) временную метку системы и возвращает, свою главную цель - объект системы.
    ----------------------------------------------------------------------------------------------------------------

         Ф-ии поиска по хэш-таблицам *CENTRAL-REGISTRY* и *SOURCE-REGISTRY*

    SYSDEF-CENTRAL-REGISTRY-SEARCH.
    (defun* sysdef-central-registry-search (system) ...)

    1. Создаётся локальное окружение с переменной name, содержащей имя системы (в унифицированном с помощью COERCE-NAME виде), переменной TO-REMOVE, которая будет накапливать элементы для удаления из *CENTRAL-REGISTRY* и переменной TO-REPLACE, которая будет накапливать пары вида (текущий-эл. новый-эл.), для последующей перезаписи элементов в *CENTRAL-REGISTRY*.

    2. Организуется цикл внутри формы UNWIND-PROTECT, которая обеспечивает обязательное выполнение "очищающих" (cleanup) форм, независимо от успешности выполнения итераций цикла. В cleanup части UNWIND-PROTECT две формы: 1-ая удаляет те элементы из *CENTRAL-REGISTRY*, которые присутствуют в локальной TO-REMOVE, 2-ая заменяет элементы в *CENTRAL-REGISTRY*, руководствуясь парами из локальной TO-REPLACE. Собственно цикл организуются по списку в *CENTRAL-REGISTRY*.

    3. На каждой итерации цикла происходит обработка элемента из *CENTRAL-REGISTRY*. Предполагается что этот элемент является типом STRING или типом PATHNAME, а также может быть символьным выражением (sexpr), вычисляющимся (с помощью ф-ии EVAL) в элемент типа STRING или PATHNAME.

        3.1 Если подготовленный элемент не равен NIL, то производится проверка на то, что он является директорией.

            3.1.1 Если это так, формируется предполагаемый путь к файлу *.asd из имени системы и этой директории. Сформированный путь проверяется на валидность и в случае успеха, происходит выход из ф-ии SYSDEF-CENTRAL-REGISTRY-SEARCH, а сформированный путь возвращается в качестве результата.

            3.1.2 Если это не так, то формируется сообщение об ошибке и вызывается ф-ия ERROR с этим сообщением в контексте установленных рестартов REMOVE-ENTRY-FROM-REGISTRY и COERCE-ENTRY-TO-DIRECTORY:

                - REMOVE-ENTRY-FROM-REGISTRY - если пользователь выберет этот рестарт, то элемент будет занесёт в локальную переменную TO-REMOVE для последующего удаления из *CENTRAL-REGISTRY*.

                - COERCE-ENTRY-TO-DIRECTORY - если будет выбран этот рестарт то будет произведена попытка преобразования элемента в правильный путь с помощью ф-ии ENSURE-DIRECTORY-PATHNAME и пара из старого и нового пути будет добавлена в локальную TO-REPLACE для последующей перезаписи в cleanup форме UNWIND-PROTECT.

    SYSDEF-SOURCE-REGISTRY-SEARCH.
    (defun* sysdef-source-registry-search (system) ...)

        Ф-ия сначала обеспечивает инициализацию *SOURCE-REGISTRY* (с помощью ф-ии ENSURE-SOURCE-REGISTRY) хэш-таблицей с ключами из имён систем и значениями из путей к файлам с определениями этих систем. Затем из хэш-таблицы *SOURCE-REGISTRY* извлекается путь к лисп-системе стандартным, для получения значения по ключу из хэш-таблицы, способом. В качестве ключа используется имя системы:

        (values (gethash (coerce-name system) *source-registry*))

        Логика работы ф-ии ENSURE-SOURCE-REGISTRY состоит в следующем:

    1. Если значение в *SOURCE-REGISTRY* не является хэш-таблицей то для её инициализации вызывается ф-ия INITIALIZE-SOURCE-REGISTRY.

    2. Эта ф-ия инициализирует *SOURCE-REGISTRY* пустой хэш-таблицей и вызывает ф-ию COMPUTE-SOURCE-REGISTRY для её заполнения. Логика работы этой ф-ии - тема для отдельной статьи.

    -------------------------------------------
    Продолжение следует ...
    Sunday, December 18th, 2011
    11:43 pm
    Устройство ASDF. Подсистема загрузки. FIND-COMPONENT.
    (сказаное ниже относится к версии 2.019.2)

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

        Обобщённый метод FIND-COMPONENT весьма функционален и имеет не простую логику работы. Его объявление выглядит так:  

    (defgeneric* find-component (base path)
      (:documentation "Finds the component with PATH starting from BASE module;
              if BASE is nil, then the component is assumed to be a system."
    )
    )


    В общих чертах логика работы FIND-COMPONENT следующая:
     - если и BASE и PATH равны NIL, тогда результат NIL.
     - если BASE или PATH равны NIL (но не одновременно), то результатом будет то, что не равно NIL.
     - если мы передали MODULE и STRING, значит мы заполняем слот COMPONENTS-BY-NAME (переданного модуля) хэш-таблицей (в которой ключами будут имена а значениями объекты класса COMPONENT. Объекты берутся из слота COMPONENTS.
     - если в качестве BASE передали строку, тогда мы ищем систему по этой строке и если нашли, то ищем компонент уже в ней. Если передали в BASE символ, то делаем тоже самое, предварительно превратив символ в строку.

    Дальнейшие варианты комбинаций параметров приводят в итоге к уже обозначенным вызовам:

     - если параметры не равны NIL и второй параметр - символ, то превратить его в строку и повторить поиск.
     - если параметр BASE представляет собой список, то вызвать поиск с первым элементом этого списка в качестве параметра BASE, а в качестве параметра PATH передать текущий список PATH, но с добавленным в голову хвостом списка BASE.
     - и наконец, самое сложное: если PATH (ну собственно на то он и PATH) представляет собой список то вызвать поиск, предварительно поискав элемент в котором будет вестись поиск, то есть предварительно вычислив значение первого аргумента. А вычисляется он с помощью того же поиска, в таком вот вызове:

        (find-component module (car name))

        ... чтобы не создавать прецендента для путаницы я взял код как есть, name в данном контексте это и есть PATH

        И далее, найдя по первому элементу PATH, компонент  в текущем модуле, сокращаем путь поиска и идем дальше но уже в найденном компоненте. Да в общем-то ничего сложного, вот таблица с помощью которой можно себе представить механизм передачи управления:

    (module cons)           -> (t null)          ;; (find-component (find-component module
                                                              ;;                                                       (car name))
                                      -> (t cons)        ;;                            (cdr name))

    (component null)       -> component

    (component symbol) -> (component string) ;; (find-component component
                                                                         ;;                           (coerce-name name))

    (cons t)                      -> (null cons)   ;; (find-component (car base)
                                      -> (t cons)        ;;                           (cons (cdr base) path))

    (null null)                   -> nil

    (null t)                       -> (t null)          ;; (find-component path nil)

    (symbol t)                 -> (string t)

    (string t)                   -> (system t)    ;; system = (find-system base nil)

    (module string)        -> component  ;; (unless (slot-boundp module 'components-by-name)
                                                            ;;       (compute-module-components-by-name module))
                                                            ;; (values
                                                            ;;    (gethash name
                                                            ;;                 (module-components-by-name module)))

    Лирическое отступление.
        Сразу приходит мысль: а не стоило ли как-то инкапсулировать логику поиска от применения обобщённых ф-ий (generic functions)? Не знаю, возможно. Но вот завернуть эти все дела в макрос разбирающий простенький DSL или какое-нибудь расширение принципа работы case или написать макрос обёртку над применениями typecase'a, в общем это наверное можно "отрефакторить". Должен заметить, что я ни в коей мере не хочу сказать, что код плохой и его надо срочно менять. А делаю всего лишь осторожное предположение, о том что (возможно!) есть необходимость небольшого рефакторинга. Тем не менее, решение рабочее а проект прямо так скажем - "фундаментальный" в мире Лиспа. А в настоящем не "игрушечном" проекте всегда есть место для рефакторинга.

    --------------------------------------------
    Продолжение следует ...
    Saturday, December 17th, 2011
    5:34 pm
    Устройство ASDF. Подсистема загрузки. Подсистема выполнения операций. PERFORM.
    (сказаное ниже относится к версии 2.019.2)

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

    (defgeneric perform (operation component))

        Это обобшённая ф-ия имеющая множество методов. Некоторые из этих методов не делают ровным счётом ничего, это методы специализирующиеся на:

    (test-op component)
    (load-source-op static-file)
    (compile-op static-file)
    (load-op static-file)
    (operation module)

    Метод специализирующийся на (OPERATION SOURCE-FILE), если до него дошло дело - выбрасывает ошибку.

    Также имеются два декоратора:

        1) (defmethod perform :before compile-op source-file)
        
            - создаёт директории для того, чтобы можно было без проблем сохранить скомпилированный файл по нужному пути.

        2) (defmethod perform :after operation component)

            - для любой операции и любого компонента (соответственно после успешного применения операции к компоненту) записывает в хэш-таблицу (в слоте OPERATION-TIMES) компонента запись, с ключом из типа операции и значением равным текущему времени. Запись осуществляется с помощью ф-ии MARK-OPERATION-DONE, которая обеспечивает корректную работу с NFS в некоторых ситуациях.

    Остались не рассмотренными: метод для загрузки исходника, метод для загрузки скомпилированного исходника, и метод для компиляции исходника:

    1.(defmethod perform ((o load-source-op) (c cl-source-file))

        Метод предназначен для загрузки исходников без предварительной компиляции.

        1.1 Получить путь к исходнику:

            (component-pathname c)

            Ф-ия возвращает значение слота ABSOLUTE-PATHNAME если он установлен. Иначе вычисляет абсолютный путь, установливает вычисленное значение в этот слот и возвращает его. Принцип вычисления в том, что путь формируется из имён предков компонента и/или путей, сохраненных в слоте relative-pathname каких-либо предков этого компонента. Начало же абсолютного пути берётся из самого верхнего в иерархии компонента, который имеет тип SYSTEM и содержит абсолютный путь, вычисленный ещё при выполнении формы (defsystem ...).

        1.2 Загружается исходник, с помощью обычной ф-ии LOAD.
        1.3 В списке св-в компонента (содержащемся в слоте PROPERTIES), установливается значение св-ва 'last-loaded-as-source в текущую отметку времени, если загрузка была успешной:

           (setf (component-property c 'last-loaded-as-source)
              (and (call-with-around-compile-hook c (lambda () (load source)))
                   (get-universal-time)
    )
    )


            ... CALL-WITH-AROUND-COMPILE-HOOK делает тоже самое (см. ниже) что и в методе (perform compile-op cl-source-file): ищет в компоненте и в его предках слот AROUND-COMPILE и если он есть и не равен NIL (тогда он должен быть ф-ией, строкой или списком, представляющим лямбда выражение), то вызывает ф-ию содержащуюся в нём. Или если в нём содержится строка - читает её с помощью READ-FROM-STRING и выполняет прочитанное с помощью EVAL. А если содержится список с лямбда выражением, то просто используется EVAL. Полученной ф-ии передаётся (lambda () (load source)). Предполагается, что эта лямбда будет вызвана в конце выполнения ф-ии. Если слота нет или он содержит NIL, то просто вызывается эта лямбда.

    2. (defmethod perform ((o load-op) (c cl-source-file))
        Метод предназначен для загрузки скомпилированных файлов. Загружает все файлы, возвращенные методом INPUT-FILES:

           (map () #'load (input-files o c))
       
           Ф-ия INPUT-FILES используется для получения абсолютного пути к файлу, представляющему компонент. Этим файлом будет в большинстве случаев либо файл *.lisp с лисп-исходником, либо файл *.fasl с результатом компиляции исходника
      
    3. И наконец, самый сложный и важный метод:

       (defmethod perform ((operation compile-op) (c cl-source-file)) ...)

        3.1 Создаётся локальный контекст.
        3.2 Лексическая переменная SOURCE-FILE связывается с абсолютным путём к компоненту, в данном случае к исходному лисп-файлу.
        3.3 Лексическая переменная OUTPUT-FILE связывается с путём, по которому будет сохранён скомпилированный файл.
        3.4 Динамическая переменная *COMPILE-FILE-WARNINGS-BEHAVIOUR* связывается со значением слота ON-WARNINGS объекта OPERATION (по умолчанию = глобальной *COMPILE-FILE-WARNINGS-BEHAVIOUR* = :WARN).
        3.5 Динамическая переменная *COMPILE-FILE-FAILURE-BEHAVIOUR* связывается со значением слота ON-FAILURE объекта OPERATION (по умолчанию = глобальной *COMPILE-FILE-FAILURE-BEHAVIOUR* = :ERROR).
        3.6 Компилируется файл с помощью ф-ии, символ которой сохранён в переменной *COMPILE-OP-COMPILE-FILE-FUNCTION* (по умолчанию COMPILE-FILE*). При этом в компилирующую ф-ию передаётся список параметров компиляции из слота FLAGS объекта OPERATION. А это значит, что с каждым конкретным файлом можно связать индивидуальные параметры компиляции.
        3.7 Компиляция происходит внутри формы:

           (multiple-value-bind (output warnings-p failure-p) ...)

            ... которая, как мы видим, определяет локальные переменные для связывание значений возвращенных компилирующей ф-ией. Собствено компиляция происходит не сразу, до стандартной ф-ии COMPILE-FILE дело доходит в два этапа:

            3.7.1 Вызывается ф-ия-декоратор CALL-WITH-AROUND-COMPILE-HOOK. Её предназначение в том, чтобы найти в компоненте или в его предках ф-ию (хук), список с лямбда-выражением или строку, сохранённую в слоте AROUND-COMPILE (ф-ия, список или строка сохраняется указанием значения ключа :AROUND-COMPILE в опциях компонента, в DEFSYSTEM). Если "хук" представлен строкой, то читается её содержимое с помощью ф-ии READ-FROM-STRING и прочитанное выполняется с помощью EVAL. Предполагается, что результат выполнения в этом случае будет лямбда-выражением или символом, представляющим имя ф-ии. Если же "хук" предствален списком с лямбда-выражением, то для получения ф-ии также используется EVAL Далее, вызывается получившееся лямда-выражение или функция, а в качестве аргумента её передаётся ф-ия компилирующая файл. Предполагается, что если это ф-ия есть, она должна сделать что-то нужное пользователю и вызвать переданную ф-ию:

               (call-with-around-compile-hook c (lambda () ...))

               (defun ensure-function (fun &key (package :asdf))
                 (etypecase fun
                   ((or symbol function) fun)
                   (cons (eval `(function ,fun)))
                   (string (eval `(function ,(with-standard-io-syntax
                                              (let ((*package* (find-package package)))
                                                (read-from-string fun))))))))

               (defmethod call-with-around-compile-hook ((c component) thunk)
                  (let ((hook (around-compile-hook c)))
                    (if hook
                      (funcall (ensure-function hook) thunk)
                      (funcall thunk)
    )
    )
    )


                ... ф-ия AROUND-COMPILE-HOOK ищет в компоненте, а затем в его предках, слот AROUND-COMPILE и как только находит, возвращает его значение.

            3.7.2 В ф-ии передаваемой CALL-WITH-AROUND-COMPILE-HOOK вызывается ф-ия сохранённая в *COMPILE-OP-COMPILE-FILE-FUNCTION*. Если разработчики не передумают, это будет ф-ия COMPILE-FILE*. По интерфейсу она совместима со стандартной COMPILE-FILE, но выполняет ещё кое-какую полезную работу. В конце статьи будет её подробное описание.

        3.8 Выбросить ошибку если отсутствует результат компиляции (OUTPUT = NIL).
        3.9 Если произошла ошибка, то либо игнорировать, либо выбросить ошибку или предупреждение (в зависимости от того, что установлено в слоте ON-FAILURE компонента:

           (case (operation-on-failure operation)
              (:warn (warn ...))
              (:error (error 'compile-failed ...))
              (:ignore nil)
    )


        3.10 Если было предупреждение при компиляции, то также: игнорировать или выбросить ошибку или предупреждение в зависимости от того, что было установлено в слоте ON-WARNINGS компонента.

    ------------------------------------
    COMPILE-FILE*
    (defun* compile-file* (input-file &rest keys &key output-file &allow-other-keys)

    1. Получить точный путь для скомпилированного файла с помощью ф-ии COMPILE-FILE-PATHNAME* (которая получает, также дополнительные параметры компиляции.

        COMPILE-FILE-PATHNAME*
        (defun* compile-file-pathname* (input-file &rest keys &key output-file &allow-other-keys)

        1.1 Если путь OUTPUT-FILE абсолютный, то выполнить некоторые странные действие, необходимые видимо для того, чтобы сгладить какие-то различия между платформами.
        1.2. Если же путь OUTPUT-FILE относительный, то игнорировать его и превратить путь INPUT-FILE в абсолютный для скомпилированного файла. Далее, получить результат, применив к полученному пути ф-ию APPLY-OUTPUT-TRANSLATIONS (о которой было сказано выше).

    2. Локальную TMP-FILE связать с путём для временного файла расположенного в той же директории.
    3. И определить локальную STATUS = :ERROR
    4. Скомпилировать файл стандартной ф-ией COMPILE-FILE, в качестве выходного файла взять файл TMP-FILE и результаты компиляции связать с локальными переменными output-truename WARNINGS-P, FAILURE-P:

       (multiple-value-bind (output-truename warnings-p failure-p)
            (apply 'compile-file input-file :output-file tmp-file keys)
          ...
    )


    5. В зависимости от результата компиляции установить локальную переменную STATUS в соответствующее значение:

       (cond
          (failure-p
           (setf status *compile-file-failure-behaviour*)
    )

          (warnings-p
           (setf status *compile-file-warnings-behaviour*)
    )

          (t
           (setf status :success)
    )
    )


        ... используемые динамические переменные *COMPILE-FILE-FAILURE-BEHAVIOUR* и *COMPILE-FILE-WARNINGS-BEHAVIOUR*, принадлежат динамическому контексту, который был установлен в методе (PERFORM COMPILE-OP CL-SOURCE-OP).

    6. В зависимости от значения переменной STATUS осуществить следующие действия:

        6.1 Если status равна :SUCCESS :WARN или :IGNORE то:

            6.1.1 Удалить старый файл OUTPUT-FILE.
            6.1.2 Если компиляция была успешной (output-truename содержит путь к скомпилированному файлу), то переименовать скомпилированный файл и обновить OUTPUT-TRUENAME:

               (when output-truename
                 (rename-file output-truename output-file)
                 (setf output-truename output-file)
    )


        6.2 Если произошла ошибка (STATUS = :ERROR), то удалить неправильно скомпилированный файл и присвоить OUTPUT-TRUENAME значение NIL
        6.3 Вернуть из ф-ии 3 значения, также как это делает стандартная ф-ия COMPILE-FILE:

           (values output-truename warnings-p failure-p)

    ------------------------------------
    Продолжение следует ...








    Friday, December 16th, 2011
    12:34 am
    Устройство ASDF. Подсистема загрузки. PERFORM-PLAN и PERFORM-WITH-RESTARTS
    (сказаное ниже относится к версии 2.019.2)

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

    PERFORM-PLAN.
    (defmethod perform-plan ((steps list) &key) ...)

        Ф-ия занимается тем, что выполняет план составленный ф-ией TRAVERSE (кроме прочего, вызывает ф-ию PERFORM-WITH-RESTARTS для повторения неудавшихся операций). Сама ф-ия PERFORM-PLAN стремится сделать следующее:

    1. Защитить глобальные системные переменные *PACKAGE* и *READTABLE* от изменения:

       (let* ((*package* *package*)
                (*readtable* *readtable*)
                 ...
    )

          ...
    )


        ... "защита" *READTABLE* здесь делается на тот случай, если в *.asd файлах определяющих загружаемые системы (т.е. систему, процесс загрузки которой начался и зависимых от неё систем) используются пользовательские "макросы-чтения".

    2. Обернуть циклический перебор списка STEPS формой (with-compilation-unit () ...). Смысл этого в том, чтобы отложить предупреждения об использовании не определённых ф-ий до конца этой формы. Например:

           (progn (fmakunbound 'f1)
               (defun f2 () (1+ (f1 4)))
               (defun f1 (x) x)
    )

    ;
    ; caught STYLE-WARNING:
    ;   undefined function: F1
    ;
    ; compilation unit finished
    ;   Undefined function:
    ;     F1
    ;   caught 1 STYLE-WARNING condition
    STYLE-WARNING: redefining ASDF::F2 in DEFUN

        Как мы видим, выводится предупреждение об использовании не определённой ф-ии. Но мы то точно знаем что никакой проблемы нет, так как она определяется чуть ниже. WITH-COMPILATION-UNIT позволяет подавить не нужные, в данном контексте предупреждения:

           (with-compilation-unit ()
              (progn (fmakunbound 'f1)
                (defun f2 () (1+ (f1 4)))
                (defun f1 (x) x)
    )
    )


    STYLE-WARNING: redefining ASDF::F2 in DEFUN
    F1

    3. Обеспечить итерацию по списку STEPS (возвращённому ф-ией TRAVERSE и переданную вызывающим методом OPERATE) и определить локальный контекст, в котором переменная OP будет получать операцию, которую нужно произвести с компонентом, ну и собственно COMPONENT:

        (loop :for (op . component) :in steps :do ...)

    4. На каждой итерации выполнять указанную операцию с компонентом, устанавливая рестарты для повторной загрузки или перекомпиляции компонента:

        (perform-with-restarts op component)

    PERFORM-WITH-RESTARTS

        Эта обобщённая ф-ия (generic function) была введена для инкапсулирования установки и использования рестартов (в частности для повторения неудавшейся операции) , включает в себя три метода, один из которых метод-декоратор.

    1. Метод-декоратор. Выполнение любой операции с любым компонентом начинается с декорирующего метода (метода, определённого со стандартным комбинатором :AROUND):

       (defmethod perform-with-restarts :around (operation component)

        Циклически вызывает следующий метод (если идёт стандартная загрузка системы, то этим методом будет метод специализирующийся на операции LOAD-OP), устанавливая при этом рестарты RETRY и ACCEPT для повторения операции с компонентом и для отметки произошедшей операции как успешной. Такая отметка ставится с помощью метода:

            (mark-operation-done operation component)

        Этот метод обновляет/добавляет запись в хэш-таблицу, связанную со слотом OPERATION-TIMES компонента. Ключом этой записи будет класс операции, а значением максимальное из:
        - текущего времени.
        - времени создания исходного файла (если файл зависит сам от себя, тогда речь будет идти о скомпилированном файле):

       (defmethod mark-operation-done ((operation operation) (c component))
          (setf (gethash (type-of operation) (component-operation-times c))
            (reduce #'max
                (cons (get-universal-time)
                  (mapcar #'safe-file-write-date (input-files operation c))
    )
    )
    )
    )


        Странно не правда ли? Какой смысл выбирать максимальное значение из текущего времени и чего-то ещё, ведь время полученное в настоящий момент должно быть априори больше? Дело в том, что это необходимо для корректной работы с NFS протоколом 3-ей версии на Linux тогда, когда используется возможность "weak cache consistency" - ленивая целостность кэша. При таком раскладе, в некоторых ситуациях NFS может записать временную метку создания файла "с опережением". Из-за этого и начинается бардак. Интересную историю о поиске бага, связанного с этим неприятным нюансом, вы можете прочитать в увлекательной статье: http://boinkor.net/archives/2011/10/a-weird-problem-with-asdf-on-nfs.html

        По окончании работы метода, ф-ией perform-plan, возможно, будет инициирована обработка следующей пары (<операция> <компонент>).

    2. Метод передающий управление методу perform:

       (defmethod perform-with-restarts (operation component)
          (perform operation component)
    )


    3. Метод устанавливающий дополнительный рестарт TRY-RECOMPILING, который предлагается на выбор пользователю в случае неудачной загрузки компонента:

       (defmethod perform-with-restarts ((o load-op) (c cl-source-file)) ...)

        Циклически происходит следующее: устанавливается упомянутый выше рестарт и вызывается менее специализированный метод для выполнения операции с компонентом:

       (loop
           (restart-case
              (return (call-next-method))
            (try-recompiling () ...)
    )
    )


        Если произошла ошибка загрузки и пользователь выбрал рестарт TRY-RECOMPILING, вызывается метод PERFORM но с другой операцией:

        (perform (make-sub-operation c o c 'compile-op) c)

        ... MAKE-SUB-OPERATION создаёт объект операции класса COMPILE-OP со слотом PARENT, равным O.
    --------------------------------------
    Продолжение следует ...
    Thursday, December 15th, 2011
    4:10 am
    Устройство ASDF. Подсистема загрузки. MAKE-SUB-OPERATION.

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

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

    (defun* make-sub-operation (c o dep-c dep-o) ...)
       
        Ф-ия необходима для создания иерархии из объектов подкласса OPERATION. Иерархия строится через использование слота PARENT. Цель её построения в том, чтобы через последовательное чтение слотов PARENT добираться до самой верхней (корневой) операции и отслеживать, с помощью неё, анализ каких операций с компонентами выполняется, а с какими уже выполнен (для этого используются хэш-таблицы в слотах VISITING-NODES и VISITED-NODES).

        Логика работы:

    Определяется локальная переменная ARGS содержащая копию списка аргументов, с которыми создавался объект операции и локальная переменная FORCE-P получающая значение ключа :FORCE:

       (let* ((args (copy-list (operation-original-initargs o)))
           (force-p (getf args :force))
    )

          ...
    )


        Далее, одна из трёх ситуаций:

        1. Если компоненты различны и не имеют предков, то в новом списке аргументов сбрасывается в NIL значение ключа :FORCE :

                   (when (eql force-p t)
                      (setf (getf args :force) nil)
    )


            ... и создаётся объект операции DEP-O с предком O:

                   (apply 'make-instance dep-o
                      :parent o
                      :original-initargs args args
    )


        2. Если класс операции "o" подкласс DEP-O, то он просто возвращается.
        3. Иначе просто создаётся объект операции DEP-O с предком "o", как в пункте 1.

    ------------------------------------------------------
    Продолжение следует ...
[ << Previous 20 ]
About LiveJournal.com