Перейти к содержанию

Некоторые сведения о Perl 5/CPAN

Материал из Викиучебника — открытых книг для открытого мира

POD

CPAN



Изначально Perl поставляется с небольшим набором модулей[1]. Хотя этого набора достаточно, чтобы написать любую программу, зачастую оказывается, что ваша задача уже когда-либо, кем-либо решалась. Быть может существующее решение помогло бы вам сэкономить кучу времени. Именно это является основной целью CPAN — помочь программистам найти модули и программы и установить их в целевой дистрибутив Perl.

Собственно CPAN (англ. Comprehensive Perl Archive NetworkВсеобъемлющая сеть архивов Perl) является децентрализованным хранилищем модулей Perl. Технически она представляет собой сеть из репозиториев, хранящих архивы, к которым клиенты подключаются либо по FTP, либо по HTTP протоколу. Собственно протокол не играет роли, но репозиторий должен быть организован особым образом. Это требуется для того, чтобы можно было наладить версионирование и индексацию модулей.

Для подключения к этой сети написано некоторое количество клиентских утилит по типу CPAN.pm, CPAN++ и cpanminus. Эти утилиты не просто скачивают и распаковывают архивы с модулями, но и делают некоторые проверки, в частности они могут построить дерево зависимостей и установить эти зависимости вместе с заказываемым модулем, другими словами, это полноценный менеджер пакетов.

CPAN возник на заре развития сети Интернет и до сих пор следует своей идеологии, которая выражается в использовании согласованных пространств имен, режима тестирования модулей и четко определенного стиля документации.

Структура CPAN

[править]

Файлы CPAN называются дистрибутивами. Один дистрибутив может состоять из одного или нескольких модулей, документации, либо исполняемых файлов сценариев, упакованных в общий формат архивации (обычно это GZIP). Каждый дистрибутив обычно содержит сценарий установки (Makefile.PL или Build.PL) и тестовые сценарии. Сценарий установки выясняет параметры системы, на основе которых формирует Makefile[2] утилиты make, по которой модуль может быть собран (если в нем есть задачи для компиляции), а затем собранные артефакты могут быть распространены по общеизвестным путям системы. Тестовые сценарии призваны понять, могут ли быть части модулей исполнены на целевой системе без ошибок по ABI.

Так как CPAN хранит тысячи дистрибутивов, разработчики стараются занимать пространства имен модулей Perl с естественной иерархией, которые подсказывают область их применения. Например, пространство Net начинает любой модуль, который как-то связан с сетью. Сравните:

  • Net::SSL — поддержка Secure Sockets Layer;
  • Net::DNS — набор модулей для работы со службой DNS;
  • Net::HTTP::Methods — подмодуль, который облегчает работу с методами протокола HTTP.

Название дистрибутива формируется из имени основного пространства имен, где символы :: заменяются на символ тире - с добавлением номера версии дистрибутива (например, CGI-Application-3.1), но на самом деле это только соглашение. Важно чтобы имя архива было уникальным. По этой причине новые имена дистрибутивов формируются благодаря номеру версии, который и создает уникальность.

Сеть зеркал CPAN

[править]

Как уже было сказано ранее, CPAN это сеть из сетевых репозиториев, каждый из которых зеркалирует некоторого «соседа». На полноценном зеркале хранится около 36 ГБ данных в виде упакованных дистрибутивов. Большинство зеркал нацелены на центральный репозиторий, сайт которого расположен по адресу www.cpan.org, однако, существуют крупные FTP-серверы, на которых размещаются программы, которых нет в центральном репозитории. Некоторые компании могут создавать закрытые репозитории. Для поиска модулей в центральном хранилище используется сайт metacpan.org.

Группа разработчиков-добровольцев тестирует дистрибутивы по мере загрузки их на CPAN, после чего составляются отчеты тестирования, которые обобщаются и используются на различных веб-сайтах.

Для загрузки дистрибутива на CPAN разработчики используют сервер с интересным названием PAUSE (англ. Perl Authors Upload Server) — pause.perl.org. Для загрузки модуля разработчик должен создать учетную запись на сервере и с этого момента за автором будет закрепляться имя модуля, если такого ранее не существовало на CPAN.

Способы установки дистрибутивов CPAN

[править]

Проверка существования модуля в системе

[править]

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

Если вы знаете пакет модуля, то самое простое с чего можно начать — попробовать написать однострочник

$ perl -e 'use <пакет-модуля>'

Если модуль установлен, то он успешно будет импортирован и программа завершится успешно, иначе будет выведена ошибка директивы use. Еще можно попытаться открыть документацию к модулю

$ perldoc <пакет-модуля>

Дистрибутив Perl для Windows (ActivePerl) имеет свой менеджер пакетов PPM, у которого есть команды для работы с модулями. Во всех остальных случаях можно воспользоваться утилитами pmtools, но, к сожалению, они не идут в стандартной поставке дистрибутива Perl и их требуется устанавливать отдельно (опять проблема курицы и яйца).

Следующие простые программы помогут без сторонних решений выведать некоторую информацию об установке Perl в вашей системе.

Если Perl установлен с документацией, то обычно в её составе всегда есть страница perlmodlib.pod, в которой перечислен список стандартных модулей дистрибутива. Вы можете её распарсить для вывода по крайней мере стандартных модулей

# list-standard-mods.pl
use Data::Dumper;

$Data::Dumper::Terse = 1;

my %modList;
sub listStandardModules {
  my($module) = @_;

  unless (keys %modList) {
    chomp(my $perlmodlib = `perldoc -l perlmodlib`);
    die "cannot locate perlmodlib\n" unless $perlmodlib;

    open my $fh, "<", $perlmodlib
      or die "$0: open $perlmodlib: $!\n";

    while (<$fh>) {
      next unless /^=head\d\s+Pragmatic\s+Modules/ ..
                  /^=head\d\s+CPAN/;

      if (/^=item\s+(\w+(::\w+)*)/) {
        ++$modList{ lc $1 };
      }
    }
  }
}

&listStandardModules;
print Dumper \%modList;
$ perl ./list-standard-mods.pl
{
  'json::pp' => 1,
  'net::servent' => 1,
  'extutils::parsexs' => 1,
  'extutils::constant::utils' => 1,
  'cpan::meta::history::meta_1_0' => 1,
  'test2::event' => 1,
  'math::trig' => 1,
  'extutils::embed' => 1,
  ..................
}

Ещё можно воспользоваться следующим «дедовским» сценарием[3], который позволяет распарсить всю POD-документацию и файлы *.pm из каталогов, записанных в @INC, либо переданных командной строкой:

#!/usr/bin/perl
# Файл: pmdesc.pl

use strict;
use warnings;
use File::Find;
use Getopt::Long;
use Carp;

our (
    $OPT_v,
    $OPT_w,
    $OPT_a,
    $OPT_s,
    $START_DIR,
    %FUTURE
);

GetOptions(
    "v|verbose" => \$OPT_v,
    "w|warnings" => \$OPT_w,
    "a|prints-relative-path" => \$OPT_a,
    "s|sort" => \$OPT_s,
) or die "Bad usage";

@ARGV = @INC unless @ARGV;
select((select(STDOUT),
   $| = 1,
)[0]);

if ($OPT_s) {
    if (open (ME, "-|")) {
        $/ = '';
        while (<ME>) {
            chomp;
            print join ("\n", sort split /\n/), "\n";
        }
        exit;
    }
}

MAIN: {
    my %visited;
    my ($dev, $ino);
    @FUTURE{@ARGV} = (1) x @ARGV;
    foreach $START_DIR (@ARGV) {
        delete $FUTURE{$START_DIR};
        print "\n<<Modules from $START_DIR>>\n\n" if $OPT_v;
        next unless ($dev, $ino) = stat ($START_DIR);
        next if $visited{"$dev,$ino"}++;
        next unless $OPT_a || $START_DIR =~ m!^/!;
        find (\&wanted, $START_DIR);
    }
    exit;
}

sub modname {
    local $_ = $File::Find::name;
    if (index($_, $START_DIR . "/") == 0) {
        substr($_, 0, 1 + length($START_DIR)) = '';
    }
    s { /              }{::}gx;
    s { \.p(m|od)$     }{}x;
    return $_;
}

sub wanted {
    if ($FUTURE{$File::Find::name}) {
        warn "\t(Skipping $File::Find::name, qui venit in futuro.)\n" if 1 and $OPT_v;
        $File::Find::prune = 1;
        return;
    }
    return unless /\.pm$/ && -f;
    my $Module = &modname;
    if ($Module =~ /^CPAN(\Z|::)/) {
        warn "$Module -- skipping because it misbehaves\n";
        return;
    }
    my $file = $_;
    unless (open (POD, "< $file")) {
        warn "\tcannot open $file: $!";
        return 0;
    }
    $: = " -:";
    local $/ = '';
    POD_READING : {
        local $_;
        while (<POD>) {
            if (/=head\d\s+NAME/) {
                chomp($_ = <POD>);
                s/^.*?-\s+//s;
                s/\n/ /g;
                if (defined (my $v = getversion($Module))) {
                    print "$Module ($v) ";
                } else {
                    print "$Module ";
                }
                print "- $_\n";
                return 1;
            }
        }
    }
    warn "\t(MISSING DESC FOR $File::Find::name)\n" if $OPT_w;
    return 0;
}

sub getversion {
    my $mod = shift;
    my $vers = `$^X -m$mod -e 'print \$${mod}::VERSION' 2>/dev/null`;
    $vers =~ s/^\s*(.*?)\s*$/$1/;
    return ($vers || undef);
}

Сценарий примечателен тем, что он использует только встроенные модули, поэтому без проблем будет запускаться в любых дистрибутивах Perl. Сценарий печатает имя пакета модуля, его версию и строчку раздела NAME из POD документации, если она есть.

# Пример вызова (опция -s сортирует модули по алфавиту):
$ ./pmdesc.pl -s
# Процедура может быть достаточно затяжной, но не подумайте - скрипт не завис.
CPAN -- skipping because it misbehaves
AnyDBM_File (1.01) - provide framework for multiple DBMs
App::Cpan - easily interact with CPAN from the command line
App::Prove - Implements the C<prove> command.
App::Prove::State - State storage for the C<prove> command.
App::Prove::State::Result (3.44) - Individual test suite results.
App::Prove::State::Result::Test (3.44) - Individual test results.
Archive::Tar (2.40) - module for manipulations of tar archives
Archive::Tar::File (2.40) - a subclass for in-memory extracted file from Archive::Tar
Attribute::Handlers (1.03) - Simpler definition of attribute handlers
Authen::SASL::Perl::ANONYMOUS (2.1700) - Anonymous Authentication class
Authen::SASL::Perl::CRAM_MD5 - CRAM MD5 Authentication class
Authen::SASL::Perl::DIGEST_MD5 - Digest MD5 Authentication class
Authen::SASL::Perl::EXTERNAL (2.1700) - External Authentication class
Authen::SASL::Perl::GSSAPI - GSSAPI (Kerberosv5) Authentication class
Authen::SASL::Perl::LOGIN (2.1700) - Login Authentication class
Authen::SASL::Perl::PLAIN (2.1700) - Plain Login Authentication class
AutoLoader (5.74) - load subroutines only on demand
AutoSplit (1.06) - split a package for autoloading

<---- вывод обрезан ---->

Если вы работаете в командной оболочке *nix системы, то можно узнать какие *.pm файлы существуют в общеизвестных путях с помощью команды find

$ find $(perl -e 'print "@INC"') -type f -name "*.pm" -print

Другие методы вращаются вокруг утилит работы с CPAN, так как они всегда делают проверки общеизвестных путей перед работой с модулями.

Устройство дистрибутива

[править]

Всё, что касается устройства дистрибутивов Perl, на момент 2025 года стабильно находится в том же состоянии, что и примерно 30 лет назад. В лучших традициях TMTOWTDI в Perl существует несколько разных систем сборки и ни одна из них не является рекомендуемой кем-либо. В общем случае следует исходить из популярности того или иного способа.

В этой книге мы будем опираться на документ perlnewmod, который описывает самые базовые принципы создания модулей и сборки дистрибутивов. Так как Perl в основном использовался Unix сообществом, используемые подходы также пользуются инструментами Unix. Для исполнения графа сборки используется классическая утилита make; для упаковки — tar; для распространения по сети используется простая сетевая передача, а для установки в систему — простая распаковка и копирование файлов. В системе, отличной от Unix, инструменты могут быть другими, но суть в шагах останется такой же.

Если идти по классическому пути, получается как то так.

  1. Сначала разработчик генерирует каркас дистрибутива (т.н. boilerplate):
    $ h2xs -AX --skip-exporter --use-new-tests -n Acme::HelloWorld
    #   A - не генерирует код автозагрузки;
    #   X - не генерирует XS-код;
    #   n - задает имя модуля;
    #   --skip-exporter - не генерирует код модуля Exporter;
    #   --use-new-tests - генерирует модульные тесты для фреймворка Test::More. 
    
    Defaulting to backwards compatibility with perl 5.40.0
    If you intend this module to be compatible with earlier perl versions, please
    specify a minimum perl version with the -b option.
    
    Writing Acme-HelloWorld/lib/Acme/HelloWorld.pm
    Writing Acme-HelloWorld/Makefile.PL
    Writing Acme-HelloWorld/README
    Writing Acme-HelloWorld/t/Acme-HelloWorld.t
    Writing Acme-HelloWorld/Changes
    Writing Acme-HelloWorld/MANIFEST
    
  2. Затем разработчик пишет код. Обратите внимание, что код вашего модуля должен быть, в данном примере, в пределах директории ./lib/Acme, которая задает пространство имен для модуля. Попутно следует покрывать код модульными тестами, которые находятся в директории ./t. Код модульных тестов может зависеть от используемого фреймворка (в данном случае используется Test::More).
  3. Когда код достигает той стадии, что его нужно передавать другой стороне (например, тестировщикам или заказчикам), обычно подготавливается документация, а также вносятся правки в файлы README, Changes и MANIFEST. Также корректируется файл генерации сборочного файла Makefile.PL (в данном случае используется ExtUtils::MakeMaker). Очень важно перед отправкой правильно выставить номер версии дистрибутива, так как он должен быть уникальным для каждого нового релиза. В основном в Perl используются две системы версионирования: более старая с десятичным числом (например, 5.040) и более новая, в которой используется буква v (например, v5.40.0). Чтобы подчеркнуть, что это альфа-сборка (используемая разработчиками) обычно приписывают через нижнее подчеркивание номер сборки (5.040_01 или v5.40.0_1). Учтите, что обычно генераторы сборочного сценария берут версию модуля из исходного файла, где номер версии может быть указан по-разному (в зависимости от минимальной поддерживаемой версии интерпретатора):
    # Начиная с версии Perl 5.10 каждый модуль автоматически включает объект версии.
    # Чтобы назначить модулю версию, необходимо определить глобальную переменную
    our $VERSION = '0.09';
    # или
    our $VERSION = 'v0.9.0';
    
    # Для старых модулей, чтобы обеспечить совместимость с версией 5.10, использовалась директива
    use version 0.77;
    # но сейчас это скорее всего не актуально, потому что кто будет поддерживать настолько старый
    # интерпретатор.
    
    # С версии 5.12 допустимо версию объявлять рядом с директивой пространства имен.
    package MyModule v0.9.0;
    
  4. Затем нужно упаковать код в дистрибутивный пакет. Обычно это делается такой командой:
    $ perl Makefile.PL && make test && make distcheck && make dist
    
  5. Затем полученный архив передается в некоторое сетевое хранилище, откуда его смогут запросить по сети, либо он передается по почте. Если получатель использует менеджер пакетов, то конкретный набор команд может зависеть от него. Если пакет устанавливается вручную, то получатель обычно распаковывает исходные коды и собирает модуль так, как это делал разработчик.

Хотя последовательность шагов кажется простой и понятной, сложности начинаются с момента, когда в вашем приложении появляются внешние зависимости. Здесь возникает вопрос, как сказать пользователю, какие модули он должен догрузить и установить, чтобы на его системе заработал ваш модуль.

В этот момент появляется множество подходов. Самое простое — это поставлять с дистрибутивом список внешних зависимостей. Это может быть список или структура. В некоторых менеджерах пакетов предусмотрен файл с таким списком, по которому они делает дозагрузку недостающих модулей.

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

Ручная установка CPAN дистрибутивов

[править]

Ручной установкой лучше пользоваться только в очень крайнем случае, например, если у вас нет прямого доступа к CPAN, так как это очень однообразный и сопряженный с трудностями процесс, особенно если у модуля очень много зависимостей. Мы рассмотрим классическую установку в *nix системе.

Для примера мы возьмем дистрибутив автора Ingy döt Net, который позволяет парсить YAML. На момент написания этой страницы дистрибутив имеет версию YAML-1.31. Этот дистрибутив был выбран только потому, что он не имеет зависимостей и написан только на Perl[4], что позволяет установить его в любой системе.

  1. Перейдите на страницу модуля в Metacpan — https://metacpan.org/dist/YAML/view/lib/YAML.pod.
  2. В левом меню вы сможете найти ссылку на его загрузку — https://cpan.metacpan.org/authors/id/I/IN/INGY/YAML-1.31.tar.gz.
  3. Распакуйте архив дистрибутива любым доступным вам способом. В этом примере мы воспользуемся утилитой tar:
    tar xzvf YAML-1.31.tar.gz && cd YAML-1.31/
  4. Если посмотреть файлы дистрибутива
    ls 
    Changes  CONTRIBUTING  lib  LICENSE  Makefile.PL  MANIFEST  META.json  META.yml  README  t  xt
    
    то среди них можно найти Makefile.PL, который хранит программу для стандартной утилиты ExtUtils::MakeMaker. С помощью этой утилиты можно сгенерировать сборочный сценарий утилиты make. Чтобы сгенерировать сборочный сценарий, нужно запустить Makefile.PL следующим образом
    perl Makefile.PL
    
  5. Если никаких серьезных проблем ExtUtils::MakeMaker не выявит, то появится новый файл Makefile. Одной из проблем, которая может возникать во время установки модулей, это отсутствие нужного фреймворка тестирования. В данном примере дистрибутив использует модуль Test::YAML, с помощью которого реализуются тесты всей спецификации YAML. В нашем случае это не критично, поэтому мы можем опустить тестирование. Для сборки модуля нужно вызвать утилиту make. При этом важно, чтобы рабочей директорией была директория с файлом Makefile: это позволит вам не передавать его утилите сборки аргументом.
    make
    
    Данный модуль не требует какой-либо серьезной сборки, но в общем случае на этом этапе могут вызываться сторонние компиляторы и препроцессоры.
  6. Еще раз отметим, что если бы модуль был чуть сложнее, следовало бы запустить тесты командой
    make test
    
    но, к сожалению, тесты скорее всего не запустятся, потому что требуется установка фреймворка, что является зависимостью тестирования.
  7. Для установки дистрибутива в систему, нужно вызвать цель install. Так как установка потребует копирования в некоторые системные директории, могут понадобится права пользователя root
    sudo make install
    
  8. Если вы введете
    perldoc YAML
    
    то увидите, что документация была интегрирована в дистрибутив Perl.

Давайте напишем простую программу с этим модулем. Для этого сгенерируем YAML структуру

$ cat > /tmp/file.yml <<EOF
---
person:
  name: Larry
  surname: Wall
EOF
#!/usr/bin/perl
# yaml-printer.pl
use YAML qw { LoadFile };
use Data::Dumper;

$Data::Dumper::Terse = 1;

my $file = $ARGV[0];
my $hashref = LoadFile($file) or die "$!";

print Dumper $hashref;
$ perl ./yaml-printer.pl /tmp/file.yml 
{
  'person' => {
                'surname' => 'Wall',
                'name' => 'Larry'
              }
}

У ручного метода есть ряд больших недостатков:

  • Если модуль имеет зависимости, то необходимо установить все зависимости похожим образом, а также нужно убедиться, что установлены зависимости зависимостей. При этом каждая зависимость должна корректно быть установлена в системе, иначе нормальная работа модуля может быть невозможной.
  • Удаление или обновление модулей вручную также могут быть сопряжено с трудностями.
  • Обновленные версии модуля могут потребовать и обновление зависимостей, а также зависимости зависимостей.

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

Установка дистрибутивов через менеджер пакетов

[править]

Модуль CPAN.pm представляет собой одновременно и библиотеку и клиент для работы со CPAN. Чтобы его не путать ни с чем другим, обычно к его имени приписывается расширение, когда имеют в виду именно модуль.

Этот модуль в большинстве ситуаций есть в дистрибутиве Perl[5]. Данный модуль может быть вызван либо через утилиту cpan, которая является его оболочкой, либо вы можете вызвать его такой командой:

$ perl -MCPAN -e shell
# либо
$ cpan

# Оба варианта без аргументов дадут одинаковый результат.

Если CPAN.pm был вызван без аргументов, то он запускается в интерактивном режиме. Чтобы узнать какие команды вам доступны в этом режиме, введите команду h.

cpan[1]> h

Display Information                                                  (ver 2.27)
 command  argument          description
 a,b,d,m  WORD or /REGEXP/  about authors, bundles, distributions, modules
 i        WORD or /REGEXP/  about any of the above
 ls       AUTHOR or GLOB    about files in the author's directory
    (with WORD being a module, bundle or author name or a distribution
    name of the form AUTHOR/DISTRIBUTION)

Download, Test, Make, Install...
 get      download                     clean    make clean
 make     make (implies get)           look     open subshell in dist directory
 test     make test (implies make)     readme   display these README files
 install  make install (implies test)  perldoc  display POD documentation

Upgrade installed modules
 r        WORDs or /REGEXP/ or NONE    report updates for some/matching/all
 upgrade  WORDs or /REGEXP/ or NONE    upgrade some/matching/all modules

Pragmas
 force  CMD    try hard to do command  fforce CMD    try harder
 notest CMD    skip testing

Other
 h,?           display this menu       ! perl-code   eval a perl command
 o conf [opt]  set and query options   q             quit the cpan shell
 reload cpan   load CPAN.pm again      reload index  load newer indices
 autobundle    Snapshot                recent        latest CPAN uploads

Вы можете видеть, что команды разделены на 4 категории плюс директивные опции:

  • Команды, которые выводят справочную информацию;
  • Устанавливающие команды;
  • Обновляющие команды;
  • Команды, которые управляют базой данных CPAN.pm.

Давайте попробуем поработать со CPAN.pm. Установим модуль Text::Glob, который позволяет разрешать Glob-символы, в том числе и в оболочке CPAN.pm:

cpan[2]> install Text::Glob

Reading '/home/john/.cpan/Metadata'
  Database was generated on Mon, 04 Aug 2025 08:17:01 GMT
Running install for module 'Text::Glob'
Fetching with LWP:
http://www.cpan.org/authors/id/R/RC/RCLAMP/Text-Glob-0.11.tar.gz
Fetching with LWP:
HASH(0x564e23814490)authors/id/R/RC/RCLAMP/CHECKSUMS
Fetching with LWP:
HASH(0x564e23814490)authors/id/R/RC/RCLAMP/CHECKSUMS.gz
Fetching with LWP:
http://www.cpan.org/authors/id/R/RC/RCLAMP/CHECKSUMS
Checksum for /home/john/.cpan/sources/authors/id/R/RC/RCLAMP/Text-Glob-0.11.tar.gz ok
Scanning cache /home/john/.cpan/build for sizes
............................................................................DONE
Configuring R/RC/RCLAMP/Text-Glob-0.11.tar.gz with Makefile.PL
Checking if your kit is complete...
Looks good
Generating a Unix-style Makefile
Writing Makefile for Text::Glob
Writing MYMETA.yml and MYMETA.json
  RCLAMP/Text-Glob-0.11.tar.gz
  /usr/bin/perl Makefile.PL INSTALLDIRS=site -- OK
Running make for R/RC/RCLAMP/Text-Glob-0.11.tar.gz
cp lib/Text/Glob.pm blib/lib/Text/Glob.pm
Manifying 1 pod document
  RCLAMP/Text-Glob-0.11.tar.gz
  /usr/bin/make -- OK
The current configuration of allow_installing_outdated_dists is 'ask/no', but for this option we would need 'CPAN::DistnameInfo' installed. Please install 'CPAN::DistnameInfo' as soon as possible. As long as we are not equipped with 'CPAN::DistnameInfo' this option does not take effect
Running make test for RCLAMP/Text-Glob-0.11.tar.gz
PERL_DL_NONLAZY=1 "/usr/bin/perl" "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/Text-Glob.t ...... ok
t/Text-Glob_Sep.t .. ok
All tests successful.
Files=2, Tests=74,  0 wallclock secs ( 0.01 usr  0.00 sys +  0.06 cusr  0.00 csys =  0.07 CPU)
Result: PASS
Lockfile removed.
  RCLAMP/Text-Glob-0.11.tar.gz
  /usr/bin/make test -- OK
Running make install for RCLAMP/Text-Glob-0.11.tar.gz
Manifying 1 pod document
Installing /usr/local/share/perl/5.32.1/Text/Glob.pm
Installing /usr/local/man/man3/Text::Glob.3pm
Appending installation info to /usr/local/lib/x86_64-linux-gnu/perl/5.32.1/perllocal.pod
  RCLAMP/Text-Glob-0.11.tar.gz
  sudo /usr/bin/make install  -- OK

Выше вы может видеть, что команда install выполняет все операции, которые мы ранее выполняли вручную, по порядку, а именно: скачивает дистрибутив из сети, распаковывает, собирает, тестирует и наконец устанавливает в систему. CPAN.pm позволяет вам выполнять не все этапы этого конвейера, а только некоторые из них, другими словами, команда в директивной форме задает цель. Вы могли бы, скажем, просто скачать дистрибутив(ы) (команда get) или только запустить тесты (команда test). Если во время команды вам не хватает каких-то пакетов, то CPAN.pm выгрузит их автоматически.

Модуль CPAN.pm кэширует результаты и сохраняет артефакты в каталоге пользователя. Например, выше это был каталог /home/john/.cpan. Не слишком увлекайтесь загрузкой модулей, так как вы можете быстро исчерпать дисковое пространство.

Теперь попробуем узнать модули, которые старее загруженных на CPAN. Для этого мы можем использовать команду r (от report). Эта команда покажет версии установленных модулей и тех, что хранится на CPAN:

cpan[3]> r

Package namespace         installed    latest  in CPAN file
Archive::Tar                   2.36      3.04  BINGOS/Archive-Tar-3.04.tar.gz
Authen::SASL                   2.16    2.1800  EHUELS/Authen-SASL-2.1800.tar.gz
CGI                            4.51      4.70  LEEJO/CGI-4.70.tar.gz
CGI::Fast                      2.15      2.17  LEEJO/CGI-Fast-2.17.tar.gz
CPAN                           2.27      2.38  ANDK/CPAN-2.38.tar.gz
CPAN::Meta::Requirements      2.140     2.143  RJBS/CPAN-Meta-Requirements-2.143.tar.gz
CPAN::Meta::YAML              0.018     0.020  ETHER/CPAN-Meta-YAML-0.020.tar.gz
CPAN::Mini                 1.111016  1.111017  RJBS/CPAN-Mini-1.111017.tar.gz
Clone                          0.45      0.47  ATOOMIC/Clone-0.47.tar.gz

...............................

LWP                            6.52      6.79  OALDERS/libwww-perl-6.79.tar.gz
List::Util                     1.55      1.70  PEVANS/Scalar-List-Utils-1.70.tar.gz
Net::Cmd                       3.11      3.15  SHAY/libnet-3.15.tar.gz
Pod::Man                       4.14    v6.0.2  RRA/podlators-v6.0.2.tar.gz
Text::Tabs                2013.0523  2024.001  ARISTOTLE/Text-Tabs+Wrap-2024.001.tar.gz
78 installed modules have no parsable version number
(use 'o conf show_unparsable_versions 1' to show them)

CPAN.pm пытается во многом помогать пользователю. Например, обратите внимание, что некоторые установленные модули имеют номер версии, который не может быть определен текущим методом (78 installed modules have no parsable version number). Такое может быть, когда модуль очень старый и не следует принятым соглашениям, либо модули были установлены в обход менеджера. Тем не менее, вы можете управлять работой модуля CPAN.pm через его многочисленные опции. Для получения и установки опции служит команда o. Попробуем установить опцию show_unparsable_versions, как на нам рекомендует модуль, чтобы вывести модули, чьи версии не парсятся:

cpan[4]> o conf show_unparsable_versions 1
    show_unparsable_versions [1]
Please use 'o conf commit' to make the config permanent!

Опция будет установлена только для текущего сеанса. Чтобы установить её перманентно, нужно использовать команду o conf commit. Теперь снова выведем версии установленных модулей

cpan[5]> r

.............................................................................

Pod::Man                       4.14    v6.0.2  RRA/podlators-v6.0.2.tar.gz
Text::Tabs                2013.0523  2024.001  ARISTOTLE/Text-Tabs+Wrap-2024.001.tar.gz
78 installed modules have no parsable version number
  they are
        Module  = CGI::HTML::Functions   (LEEJO/CGI-4.70.tar.gz)
        Module  = Data::Dump::FilterContext (GARU/Data-Dump-1.25.tar.gz)
        Module  = Data::Dump::Filtered   (GARU/Data-Dump-1.25.tar.gz)
        Module  = File::FcntlLock::Errors (JTT/File-FcntlLock-0.22.tar.gz)
        Module  = File::FcntlLock::XS    (JTT/File-FcntlLock-0.22.tar.gz)
        Module  = Font::Metrics::Courier (GAAS/Font-AFM-1.20.tar.gz)
        Module  = Font::Metrics::CourierBold (GAAS/Font-AFM-1.20.tar.gz)
        Module  = Font::Metrics::CourierBoldOblique (GAAS/Font-AFM-1.20.tar.gz)

...................................................

        Module  = XML::XPathEngine::Root (MIROD/XML-XPathEngine-0.14.tar.gz)
        Module  = XML::XPathEngine::Step (MIROD/XML-XPathEngine-0.14.tar.gz)
        Module  = XML::XPathEngine::Variable (MIROD/XML-XPathEngine-0.14.tar.gz)
        Module  = YAML::Dumper           (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Dumper::Base     (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Error            (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Loader           (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Loader::Base     (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Marshall         (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Mo               (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Node             (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Tag              (INGY/YAML-1.31.tar.gz)
        Module  = YAML::Types            (INGY/YAML-1.31.tar.gz)
        Module  = lib::core::only        (HAARG/local-lib-2.000029.tar.gz)
        Module  = overload::numbers      (SHAY/perl-5.40.3.tar.gz)

На распечатке вывода выше вы могли заметить, что на CPAN есть версии пакетов более новые, чем установлены в нашем дистрибутиве. Например, попробуем обновить Archive::Tar до версии 3.04:

cpan[6]> upgrade Archive::Tar

Package namespace         installed    latest  in CPAN file
Archive::Tar                   2.36      3.04  BINGOS/Archive-Tar-3.04.tar.gz
Running install for module 'Archive::Tar'
Fetching with LWP:
http://www.cpan.org/authors/id/B/BI/BINGOS/Archive-Tar-3.04.tar.gz
Fetching with LWP:
HASH(0x5587b37fb750)authors/id/B/BI/BINGOS/CHECKSUMS
Fetching with LWP:
HASH(0x5587b37fb750)authors/id/B/BI/BINGOS/CHECKSUMS.gz
Fetching with LWP:
http://www.cpan.org/authors/id/B/BI/BINGOS/CHECKSUMS
Checksum for /home/john/.cpan/sources/authors/id/B/BI/BINGOS/Archive-Tar-3.04.tar.gz ok
Scanning cache /home/john/.cpan/build for sizes
............................................................................DONE
Configuring B/BI/BINGOS/Archive-Tar-3.04.tar.gz with Makefile.PL

Archive::Tar comes with a utility called 'ptardiff' which lets you run diffs against tar archives.

However, this utility requires you to have Text::Diff installed.

To add Text::Diff as a prerequisite, please supply the '-d' option when invoking this Makefile.PL.

###############################################################
##
##    Hi! Your script and sitescript locations are different
##
##    As your perl is v5.12.0 or greater the script included
##    in this distribution will be installed into sitescript
##
##    You might want to check that the following location is
##    in your PATH environment variable:
##
##    '/usr/local/bin'
##
##    Many thanks.
##
###############################################################

Checking if your kit is complete...
Looks good
Generating a Unix-style Makefile
Writing Makefile for Archive::Tar
Writing MYMETA.yml and MYMETA.json
  BINGOS/Archive-Tar-3.04.tar.gz
  /usr/bin/perl Makefile.PL INSTALLDIRS=site -- OK
Running make for B/BI/BINGOS/Archive-Tar-3.04.tar.gz
---- Unsatisfied dependencies detected during ----
----      BINGOS/Archive-Tar-3.04.tar.gz      ----
    IO::Compress::Xz [requires,optional]
    IO::Uncompress::UnXz [requires,optional]
cp lib/Archive/Tar.pm blib/lib/Archive/Tar.pm
cp lib/Archive/Tar/File.pm blib/lib/Archive/Tar/File.pm
cp lib/Archive/Tar/Constant.pm blib/lib/Archive/Tar/Constant.pm
cp bin/ptar blib/script/ptar
"/usr/bin/perl" -MExtUtils::MY -e 'MY->fixin(shift)' -- blib/script/ptar
cp bin/ptardiff blib/script/ptardiff
"/usr/bin/perl" -MExtUtils::MY -e 'MY->fixin(shift)' -- blib/script/ptardiff
cp bin/ptargrep blib/script/ptargrep
"/usr/bin/perl" -MExtUtils::MY -e 'MY->fixin(shift)' -- blib/script/ptargrep
Manifying 3 pod documents
Manifying 2 pod documents
  BINGOS/Archive-Tar-3.04.tar.gz
  /usr/bin/make -- OK
The current configuration of allow_installing_outdated_dists is 'ask/no', but for this option we would need 'CPAN::DistnameInfo' installed. Please install 'CPAN::DistnameInfo' as soon as possible. As long as we are not equipped with 'CPAN::DistnameInfo' this option does not take effect
Running make test for BINGOS/Archive-Tar-3.04.tar.gz
PERL_DL_NONLAZY=1 "/usr/bin/perl" "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/01_use.t .............. ok
t/02_methods.t .......... ok
t/03_file.t ............. ok
t/04_resolved_issues.t .. ok
t/05_iter.t ............. ok
t/06_error.t ............ ok
t/07_ptardiff.t ......... skipped: Text::Diff required to test ptardiff
t/08_ptargrep.t ......... ok
t/09_roundtrip.t ........ ok
t/10_ptar.t ............. ok
t/90_symlink.t .......... ok
t/99_pod.t .............. skipped: Test::Pod v0.95 required for testing POD
All tests successful.
Files=12, Tests=1707,  1 wallclock secs ( 0.05 usr  0.03 sys +  1.07 cusr  0.16 csys =  1.31 CPU)
Result: PASS
Warning: Configuration not saved.
Lockfile removed.
  BINGOS/Archive-Tar-3.04.tar.gz
  /usr/bin/make test -- OK
Running make install for BINGOS/Archive-Tar-3.04.tar.gz

.................................

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

cpan[7]> m Archive::Tar
Module id = Archive::Tar
    CPAN_USERID  BINGOS (Chris Williams <chris@bingosnet.co.uk>)
    CPAN_VERSION 3.04
    CPAN_FILE    B/BI/BINGOS/Archive-Tar-3.04.tar.gz
    UPLOAD_DATE  2025-02-25
    MANPAGE      Archive::Tar - module for manipulations of tar archives
    INST_FILE    /usr/local/share/perl/5.32.1/Archive/Tar.pm
    INST_VERSION 3.04

Чтобы завершить интерактивный сеанс оболочки, просто введите команду exit.

cpanminus

[править]

Если CPAN.pm очень большой и сложный менеджер пакетов, то cpanminus — это небольшая консольная утилита, в которую встроен минимальный набор команд. Её удобно использовать в неинтерактивных сценариях, где участие пользователя минимально, либо в ситуациях, когда есть ограничения по ресурсам.

$ cpanm --help
Usage: cpanm [options] Module [...]

Опции:
  -v,--verbose              Включает подробный вывод сообщений
  -q,--quiet                Отключает большую часть выходных данных
  --interactive             Включает интерактивную настройку (требуется Task:: модулям)
  -f,--force                Принудительная установка
  -n,--notest               Не запускать модульные тесты
  --test-only               Запуск тестов без установки
  -S,--sudo                 Переключиться в sudo режим, когда модуль устанавливается в системные директории
  --installdeps             Устанавливает зависимости целевого дистрибутива, не собирая их
  --showdeps                Показать только прямые зависимости
  --reinstall               Переустановить дистрибутив даже в том случае, когда установлена его последняя версия
  --mirror                  Определяет базовый URL CPAN-сервера (например, http://cpan.cpantesters.org/)
  --mirror-only             Использовать только файл индекса CPAN-зеркала вместо мета-базы данных
  -M,--from                 Использовать только этот URL в качестве CPAN-зеркала
  --prompt                  Останавливать исполнение с вопросом, когда стадия configure/build/test проваливается
  -l,--local-lib            Указывает директорию, в которую устанавливаются дистрибутивы
  -L,--local-lib-contained  Как --local-lib вместе с --self-contained
  --self-contained          Устанавливает все неосновные модули, даже если они установлены
  --auto-cleanup            Время хранения файлов в рабочих директориях cpanm в днях. По умолчанию 7 дней

Команды:
  --self-upgrade            Обновить cpanm
  --info                    Вывести информацию о дистрибутиве на CPAN
  --look                    Скачивает и распаковывает дистрибутив и больше ничего не делает
  -U,--uninstall            Удаляет модуль
  -V,--version              Выводит версию cpanm

Примеры:

  cpanm Test::More                                          # Установить Test::More
  cpanm MIYAGAWA/Plack-0.99_05.tar.gz                       # Установить по полному пути
  cpanm http://example.org/LDS/CGI.pm-3.20.tar.gz           # Установить по URL
  cpanm ~/dists/MyCompany-Enterprise-1.00.tar.gz            # Установить из файла архива дистрибутива
  cpanm --interactive Task::Kensho                          # Интерактивное конфигурирование
  cpanm .                                                   # Установить все из локальной директории
  cpanm --installdeps .                                     # Установить все зависимости для текущей директории
  cpanm -L extlib Plack                                     # Установить Plack и все не core-зависимости для extlib
  cpanm --mirror http://cpan.cpantesters.org/ DBI           # Использовать зеркало с облегченной синхронизацией
  cpanm -M https://cpan.metacpan.org App::perlbrew          # Использовать зеркало с защищенным соединением

Некоторые опции по умолчанию могут быть установлены в переменной окружения PERL_CPANM_OPT:

  export PERL_CPANM_OPT="--prompt --reinstall -l ~/perl --mirror http://cpan.cpantesters.org"

Для более подробной информации используйте man cpanm или perldoc cpanm.

Создание локального сетевого репозитория

[править]

В этом разделе мы поставим себе задачу развернуть локальный сетевой репозиторий, из которого мы могли бы выгружать и устанавливать дистрибутивы стандартными менеджерами пакетов типа CPAN.pm. В коммерческой разработке к репозиторию обычно «прикручивают» какой-нибудь сайт для поиска дистрибутивов, но мы не будем рассматривать этого здесь, так как это лишь вопрос удобства. Все это мы будем делать в окружении *nix, однако, с определенными поправками на установку и конфигурирование, все сказанное справедливо и для Windows.

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

  1. Обеспечить транспорт для доставки дистрибутивов на целевую систему заказчика. Решения здесь могут быть самыми разными, но для примера мы обойдемся простым файловым сервером, работающим поверх HTTP.
  2. Организовать репозиторий в формате, понятном менеджеру пакетов.

Файловый HTTP сервер

[править]

Что касается серверного приложения, то существует огромное количество готовых решений. Для этой задачи автор выбрал сервер Apache 2. Мы опустим шаг установки серверного приложения и запуска его как программы-демона, а начнем сразу с конфигурирования.

Для примера, имя сервера будет book08. Для Apache мы сконфигурируем виртуальный сервер, для чего необходимо в директории /etc/apache2/sites-available создать файл конфигурации для нового виртуального сервера local-cpan.conf:

<VirtualHost *:80>
    ServerName book08
    
    ServerAdmin webmaster@localhost
    DocumentRoot /usr/src/cpan/sources

    Alias /CPAN /usr/src/cpan/sources

    <Directory /usr/src/cpan/sources>
        Options Indexes FollowSymLinks MultiViews
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/cpan_error.log
    CustomLog ${APACHE_LOG_DIR}/cpan_access.log combined
</VirtualHost>

В конфигурации мы объявили виртуальный сервер с именем book08 и указали для него корневую директорию /usr/src/cpan/sources. Разумеется эта директория должна существовать, а пользователь, от которого запускается серверное приложение, должен иметь к ней доступ. Здесь сервер привязывается на все доступные сетевые интерфейсы, а сам сервер работает на стандартном порту 80. Чтобы было понятно, для чего используется файловый сервер, мы навесили на корневую директорию псевдоним, таким образом, полный URL сервера будет http://book08:80/CPAN.

Далее нужно активизировать этот сервер и перезапустить демон apache2:

$ sudo a2ensite local-cpan.conf
...
$ sudo systemctl restart apache2

Убедимся, что сервер заработал, командой curl:

$ curl http://book08/CPAN
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://book08/CPAN/">here</a>.</p>
<hr>
<address>Apache/2.4.65 (Debian) Server at book08 Port 80</address>
</body></html>

Хорошим признаком является то, что сервер отвечает. В данном случае он ответил 301 кодом, но только потому, что мы не поставили еще один слеш в конце путевого имени, что и говорится в ответе. Мы можем считать первый шаг выполненным.

Построение репозитория

[править]

Репозиторий CPAN не имеет какого-то очень сложного устройства и полностью встраивается в файловую систему. Обычно репозиторий минимально состоит из двух директорий:

  • authors — собственно в ней хранятся дистрибутивы авторов.
    • id — индекс репозитория. Индекс репозитория состоит из разветвленной древовидной системы директорий. За каждым автором в репозитории закрепляется короткий идентификатор, записываемый в верхнем регистре, например, JDOE. Дистрибутивы определенного автора всегда находятся в каталоге, имя которого совпадает с идентификатором автора. Если вы знакомы с системой сборки Apache Maven, то это по смыслу очень напоминает элемент <group>, определяемый для зависимости. Собственно идентификатор призван создать пространство имен, закрепляемое за каждым уникальным автором, чтобы не было конфликта по именам в разных дистрибутивах. Другими словами, два автора могут иметь дистрибутив с одинаковым именем. Так как, в общем случае, авторов может быть очень много, они не лежат в индексе плоско, а разложены по каталогам по принципу картотеки. Например, если в индексе существует автор с идентификатором JDOE, то в индексе заранее будет подготовлена такая ветка
      authors
       |- id
           |-J
             |-JD
                |-JDOE
      Это позволяет реализовать эффективный поиск дистрибутивов через алгоритмы обхода деревьев, зная идентификатор автора.
      • дистрибутивы — файлы, которые в каталоге автора находятся в сжатом виде. Так как дистрибутивы никак не раскладываются, их имена должны быть уникальными. На практике обычно номер версии дает переменную часть файлу дистрибутива. Кроме файлов дистрибутивов, в этом каталоге находится файл CHECKSUMS, генерируемый модулем CPAN::Checksums, в котором хранятся рассчитанные для дистрибутивов контрольные суммы. Они обычно используются для проверки целостности архива при доставке по сети.
  • modules — вторая по важности директория в репозитории. В ней хранится мета-информация с описанием репозитория.
    • 02packages.details.txt.gz — сжатый файл с описанием индекса. Обычно этот файл первым делом выгружают менеджеры пакетов для поиска того или иного дистрибутива. Сам файл имеет примерно такое содержимое:
      File:         02packages.details.txt
      URL:          http://www.perl.com/CPAN/modules/02packages.details.txt
      Description:  Package names found in directory $CPAN/authors/id/
      Columns:      package name, version, path
      Intended-For: Automated fetch routines, namespace documentation.
      Written-By:   <программа-обходившая-индекс>
      Line-Count:   <количество-записей>
      Last-Updated: Sun, 30 Nov 2025 21:08:01 GMT
      
      <таблица-со-всеми-дистрибутивами-в-индексе>
      Содержимое файла представляет собой большую таблицу с описанием того, что хранится в репозитории. В поле Columns предоставлены заголовки таблицы, а в поле Line-Count — количество строк в ней.

Так как описание индекса является статической информацией, после добавления нового дистрибутива, его нужно корректировать каждый раз. По этой причине добавление нового дистрибутива в хранилище проходит два этапа:

  • Определение места в индексе, перенос дистрибутива в индекс и расчет контрольных сумм.
  • Корректировка описания индекса (англ. inject, встраивание).

Для создания индекса в репозитории на CPAN существуют специальные утилиты разной степени сложности. Для нашей небольшой задачи привлекательной является утилита CPAN::Mini::Inject, которая создает минимальный индекс.

У этой утилиты есть 4 команды:

  • --add — добавляет дистрибутив в пользовательский репозиторий. По сути это индекс без описания. Пользовательский репозиторий не следует путать со CPAN-репозиторием, но в какой-то мере они похожи.
  • --mirror — делает точное зеркало CPAN-репозитория по указанной внешней ссылке в локальном CPAN-репозитории. В этой задаче мы этим пользоваться не будем, но имейте в виду.
  • --update — похожа на --mirror, но дополнительно делает шаг --inject.
  • --inject — добавляет в CPAN-репозиторий дистрибутивы из пользовательского репозитория.

Итак, у нас есть все инструменты. Если у вас не установлен модуль CPAN::Mini::Inject сделайте это с помощью утилиты CPAN.pm или любого другого менеджера пакетов. Для примера мы будем использовать наш шуточный дистрибутив Acme::HelloWorld, который демонстрировался здесь.

  1. Сначала сконфигурируем CPAN::Mini::Inject. Создайте файл конфигурации mcpani и разместите его в домашней директории пользователя, который будет делать inject:
    $ mkdir $HOME/.mcpani
    $ vim $HOME/.mcpani/config
    #
    # Редактор ...
    #
    local: /usr/src/cpan/sources
    remote: http://cpan.metacpan.org/
    repository: /usr/src/repo
    passive: yes
    skip_cleanup: no
    skip_perl: yes
    trace: yes
    
    В этой конфигурации local — директория локального CPAN-репозитория для команды --inject; remote — ссылка на удаленный репозиторий для команд --mirror и --update; repository — директория пользовательского репозитория для команды --add.
  2. Создайте директорию локального репозитория, если её еще нет:
    $ mkdir -p /usr/src/repo
    
  3. Далее нужно подготовить архив дистрибутива. Предположим, что он уже упакован, тогда мы можем вызвать mcpani в режиме --add:
    $ mcpani --add --module Acme::HelloWorld --authorid JDOE --file ./Acme-HelloWorld-0.01.tar.gz
    
    Здесь --module — имя модуля, по которому он регистрируется в репозитории; --authorid — идентификатор регистрирующего пользователя; --file — файл дистрибутива. Вы также можете указать версию дистрибутива через опцию --modversion, если имя архива какое-то нестандартное, но в данном случае имя стандартное, и версия будет вычислена автоматически. По умолчанию mcpani возьмет все настройки из конфигурации пользователя, поэтому мы ничего больше не указываем.
  4. Предыдущим шагом мы добавили дистрибутив в репозиторий пользователя, но не в CPAN-репозиторий. Для этого мы должны сделать inject:
    $ mcpani --inject
    
    На практике дистрибутивы обычно вставляются в CPAN-репозиторий не по одному, а сразу кучей, так как каждое такое добавление требует перестраивать индекс.
  5. Для проверки того, что индекс был обновлен, мы снова можем спросить сервер:
    $ curl http://book08/CPAN/modules/02packages.details.txt.gz -o /tmp/index && zcat /tmp/index
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100   315  100   315    0     0   307k      0 --:--:-- --:--:-- --:--:--  307k
    File:         02packages.details.txt
    URL:          http://www.perl.com/CPAN/modules/02packages.details.txt
    Description:  Package names found in directory $CPAN/authors/id/
    Columns:      package name, version, path
    Intended-For: Automated fetch routines, namespace documentation.
    Written-By:   CPAN::Mini::Inject 1.012
    Line-Count:   1
    Last-Updated: Sun, 30 Nov 2025 21:08:01 GMT
    
    Acme::HelloWorld                   0.01  J/JD/JDOE/Acme-HelloWorld-0.01.tar.gz
    

Наш репозиторий готов делиться своим единственным дистрибутивом.

Установка пробного пакета из репозитория

[править]

Теперь попробуем установить наш пакет из CPAN-репозитория. Для этого запустим CPAN.pm в интерактивном режиме:

$ perl -MCPAN -e shell

Чтобы можно было пользоваться нашим репозиторием, его нужно добавить в список серверов:

cpan[1]> o conf urllist unshift http://book08/CPAN
Please use 'o conf commit' to make the config permanent!

cpan[2]> o conf urllist                                                                                                                                                                                                             
    urllist           
        0 [http://book08/CPAN]
        1 [http://www.cpan.org/]
Type 'o conf' to view all configuration items

Обратите внимание, что мы добавили ссылку только на момент сеанса, а не перманентно. Теперь нам нужно выгрузить индекс с сервера:

cpan[3]> reload index
Reading '/home/john/.cpan/Metadata'
  Database was generated on Mon, 01 Dec 2025 07:17:01 GMT
Fetching with LWP:
http://book08/CPAN/authors/01mailrc.txt.gz
Reading '/home/john/.cpan/sources/authors/01mailrc.txt.gz'
............................................................................DONE
Fetching with LWP:
http://book08/CPAN/modules/02packages.details.txt.gz
Reading '/home/john/.cpan/sources/modules/02packages.details.txt.gz'
  Database was generated on Sun, 30 Nov 2025 21:08:01 GMT
............................................................................DONE

......

По выводу мы можем судить, что индекс был выгружен успешно. Однако, наш репозиторий не совсем полноценный с точки зрения CPAN.pm, так как сам клиент может пытаться выгружать дополнительные технологические файлы, которых нет у нашего зеркала.

Наконец, мы можем установить наш модуль:

cpan[4]> i Acme::HelloWorld                                                                                                                                                                                                         
Module id = Acme::HelloWorld
    CPAN_USERID  JDOE (Custom Non-CPAN author <CENSORED>)
    CPAN_VERSION 0.01
    CPAN_FILE    J/JD/JDOE/Acme-HelloWorld-0.01.tar.gz
    INST_FILE    (not installed)


cpan[5]> install Acme::HelloWorld                                                                                                                                                                                                   
Running install for module 'Acme::HelloWorld'
Fetching with LWP:
HASH(0x5564e969abd0)authors/id/J/JD/JDOE/CHECKSUMS
Fetching with LWP:
HASH(0x5564e969abd0)authors/id/J/JD/JDOE/CHECKSUMS.gz
Fetching with LWP:
http://www.cpan.org/authors/id/J/JD/JDOE/CHECKSUMS
Fetching with LWP:
http://www.cpan.org/authors/id/J/JD/JDOE/CHECKSUMS.gz

..................................

Checksum for /home/john/.cpan/sources/authors/id/J/JD/JDOE/Acme-HelloWorld-0.01.tar.gz ok
Scanning cache /home/john/.cpan/build for sizes
............................................................................DONE
Configuring J/JD/JDOE/Acme-HelloWorld-0.01.tar.gz with Makefile.PL
Checking if your kit is complete...
Looks good
Generating a Unix-style Makefile
Writing Makefile for Acme::HelloWorld
Writing MYMETA.yml and MYMETA.json
  JDOE/Acme-HelloWorld-0.01.tar.gz
  /usr/bin/perl Makefile.PL INSTALLDIRS=site -- OK
Running make for J/JD/JDOE/Acme-HelloWorld-0.01.tar.gz
cp lib/Acme/HelloWorld.pm blib/lib/Acme/HelloWorld.pm
Manifying 1 pod document
  JDOE/Acme-HelloWorld-0.01.tar.gz
  /usr/bin/make -- OK
Running make test for JDOE/Acme-HelloWorld-0.01.tar.gz
PERL_DL_NONLAZY=1 "/usr/bin/perl" "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/Acme-HelloWorld.t .. ok   
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.00 usr  0.00 sys +  0.02 cusr  0.00 csys =  0.02 CPU)
Result: PASS
Warning: Configuration not saved.
Lockfile removed.
  JDOE/Acme-HelloWorld-0.01.tar.gz
  /usr/bin/make test -- OK
Running make install for JDOE/Acme-HelloWorld-0.01.tar.gz
[sudo] пароль для john: 
Manifying 1 pod document
Installing /usr/local/share/perl/5.32.1/Acme/HelloWorld.pm
Installing /usr/local/man/man3/Acme::HelloWorld.3pm
Appending installation info to /usr/local/lib/x86_64-linux-gnu/perl/5.32.1/perllocal.pod
  JDOE/Acme-HelloWorld-0.01.tar.gz
  sudo /usr/bin/make install  -- OK

Мы можем убедиться, что модуль был установлен в дистрибутив Perl вызовом команды perldoc:

$ perldoc Acme::HelloWorld

Acme::HelloWorld
    Acme::HelloWorld - Prints 'Hello World!' to anyone who asks for it.

SYNOPSIS
      use Acme::HelloWorld qw { say_hello };
      say_hello();

DESCRIPTION
    This incredibly complex module is a top-secret development by ACME. It
    prints "Hello World!" securely and without delay. Our engineers spent
    hours trying to figure out how to print this message.

    In fact, we're wondering why we haven't received a prize for this module
    yet.

    To start all the gears of our module, you only need to call the
    following procedure.

     sayhello();

    Our module has the following features:

    *Thread safety*
        Don't worry about deadlocks and such, because we're doing this in a
        single thread.

    *Speed*
        In theory, if you're not trying to print text on your toaster,
        everything should happen quickly.

    *And more ...*

        * We haven't decided what to write here yet.
            The text has a left indent of 12 characters.

AUTHOR
    John Doe, <jdoe@acme.com<gt>

COPYRIGHT AND LICENSE
    Copyright (C) 2025 by ACME

    This library is free software; you can redistribute it and/or modify it
    under the same terms as Perl itself, either Perl version 5.32.1 or, at
    your option, any later version of Perl 5 you may have available

Удаление дистрибутивов

[править]

Вы могли заметить, что в CPAN.pm нет команды для удаления дистрибутива: есть установка, обновление, но не удаление. Логика этого кроется в том, что скорее всего вы хотите устанавливать только то, что вам нужно, а система установки не позволит встроить дистрибутив в систему, если проваливаются тесты, другими словами, логика в том, что вы устанавливаете нужные вам и стабильные модули, которые вы можете в дальнейшем обновлять.

Кроме того, когда модуль разрабатывается или тестируется, он обычно никогда не устанавливается сразу в систему, а существует где-то отдельно от неё и встраивается в запуск через переменную окружения PERL5LIB (см. модуль local::lib).

Тем не менее, всегда возможны ошибки и некоторые модули могут оказаться нежелательными. Удалить их всегда можно вручную, но в этом вопросе следует быть внимательным, так как модуль может раскидать множество своих файлов по разным участкам системы.

Немного облегчить эту задачу поможет утилита pm-uninstall. Например, попробуем удалить наш шуточный модуль:

$ sudo pm-uninstall Acme::HelloWorld

[sudo] пароль для john: 
--> Working on Acme::HelloWorld
Acme::HelloWorld is included in the distribution Acme-HelloWorld and contains:

  /usr/local/man/man3/Acme::HelloWorld.3pm
  /usr/local/share/perl/5.32.1/Acme/HelloWorld.pm

Are you sure you want to uninstall Acme-HelloWorld? [y] y

Successfully uninstalled Acme::HelloWorld

You may want to rebuild man(1) entries. Try mandb -c if needed

Построение дерева зависимостей

[править]

Иногда на практике бывает полезно посмотреть, какие зависимости требует тот или иной исходный код, написанный на Perl. Это может быть нужно в таких ситуациях:

  • Для оптимизации. Возможно вам нужно уменьшить число зависимостей, если имеются облегченные или более быстрые альтернативы. Тогда вы можете узнать, что конкретно от чего зависит, чтобы узнать, в какую сторону оптимизировать.
  • Для сопровождения. Возможно конечному заказчику будет интересно знать, какие модули использует ваше приложение, например, чтобы подготавливать рабочие мощности.

Так как Perl динамический язык и может загружать модули «лениво» (в момент, когда они потребуются) и компилировать код на ходу, существует два сценария сканирования зависимостей:

  • Статическое сканирование. Это обход всех зависимых исходных файлов в поисках модулей или библиотек, которые всегда обрабатываются на этапе большой компиляции.
  • Динамическое сканирование. Это статическое сканирование с добавлением всех зависимостей, которые получаются от работы директив require, вызываемых при определенных условиях, и компиляции через eval.

Сначала мы рассмотрим статическое сканирование. На CPAN уже давно существует модуль Module::ScanDeps, который позволяет получить все статические зависимости. Этому модулю может быть передан исходный код, либо файл с исходным кодом, которые будут отправной точкой для сканирования зависимостей. Модуль позволяет сканировать зависимости рекурсивно (в глубину), либо можно получить только зависимости первого уровня.

Ниже приведен код приложения, реализующего статическое сканирование. Приложение может сканировать как в простом режиме, так и рекурсивном. Для работы приложения, ему необходимо передать один или несколько исходных файлов, для которых будет построено дерево зависимостей. Для визуализации используется утилита Graphviz, поэтому перед использованием вы должны установить её в системе и убедиться, что команду dot видит исполняющее окружение. Graphviz вызывается опосредованно через модуль GraphViz2, поэтому этот модуль также нужно доустановить из CPAN. Получаемый граф сохраняется в файле PNG.

# Файл: gen-graph.pl
use strict;
use warnings;
use Module::ScanDeps;
use GraphViz2;
use Getopt::Long;

my $recurseFlag;
my $prefix;
GetOptions(
	"recurse" => \$recurseFlag,
	"out" => \$prefix
) or die "Bad usage: unknown option.";

$prefix = $prefix || "./";

for my $filename (@ARGV) { 
	do { print "Cannot open: $filename \n"; next; } unless -e $filename;
	print "Scanning dependencies for '$filename' ...\n";
	my $deps = scan_deps(
		files   => [$filename],
		recurse => $recurseFlag,
	);
	my ($label) = $filename =~ m|([^/\\]+)$|;
	my $gv = GraphViz2->new(
		global => { directed => 1 },
		graph  => { rankdir => 'LR', label => "Dependency graph for $label" },
		node   => { shape => 'box', style => 'filled', fillcolor => 'lightblue' },
	);
	foreach my $key (keys %$deps) {
		my $module = $deps->{$key};
		my $name = $key;
		$gv->add_node(name => $name);
		if ($module->{used_by}) {
			foreach my $parent (@{$module->{used_by}}) {
				$gv->add_edge(from => $parent, to => $name);
			}
		}
	}
	my $out = $prefix . "${label}_deps.png";
	$gv->run(format => 'png', output_file => $out);
	print "File $out SUCCESSFULLY saved\n";
}

Пусть у нас есть файл с таким исходным кодом

# Файл: test.pl
use IO::Socket;

тогда для простого сканирования нужно вызвать утилиту так

$ perl gen-graph.pl ./test.pl
Scanning dependencies for './test.pl' ...
File ./test.pl_deps.png SUCCESSFULLY saved

а для рекурсивного сканирования нужно использовать опцию -r

$ perl gen-graph.pl -r ./test.pl
Scanning dependencies for './test.pl' ...
File ./test.pl_deps.png SUCCESSFULLY saved

Будьте осторожны с рекурсивным сканированием: при очень большом дереве утилита Graphviz может очень долго его визуализировать.

Динамическое сканирование осложняется тем, что для того чтобы узнать какие зависимости получаются по ходу исполнения, этот код нужно исполнить. По этой причине сканирование уже больше похоже на профилирование. Для динамического сканирования можно воспользоваться модулем Devel::GraphVizProf, тогда вызов с построением графа будет таким.

$ perl -d:GraphVizProf test.pl > graph.dot
$ dot -Tpng graph.dot > test.pl_deps.png

Другой подход основан на том, что в проверяемый код нужно добавить такой блок END:

use strict;
use warnings;
use GraphViz2;

# Основная программа

END {
    my $gv = GraphViz2->new(global => { directed => 1 });
    foreach my $module (sort keys %INC) {
        $module =~ s/\//::/g;
        $module =~ s/\.pm$//;
        $gv->add_node(name => $module);
    }
    $gv->run(format => 'png', output_file => 'dynamic_deps.png');
}

Примечания

[править]
  1. Список директив и стандартных модулей
  2. Эта техника напоминает, например, системы сборки GNU Autotools и CMake.
  3. Исходный код взят из книги Кристиансен Т., Торкингтон Н. Perl: библиотека программиста = Perl Cookbook / под ред. O'Reilly; пер. с англ. Е. Матвеев. — 2-е изд. — Шаблон:СПб.: Питер, 2000. — С. 445—448. — ISBN 5-8046-0094-X. Однако, приведенный здесь код немного осовременен, а также были выброшены неиспользуемые части.
  4. Такие модули называются чистыми. Очень часто авторы используют буквы PP (англ. Pure Perl), чтобы это подчеркнуть.
  5. Однако, он может отсутствовать в ужатых сборках.