?

Log in

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