Создание сборки Drupal 7 (дистрибутивы, установочные профили)

Создание дистрибутивов в Drupal 7.

26.02.2014
17 комментариев
15 мин.

После того как я анонсировал сборку интернет-магазина, самое время рассказать как же делаются данные сборки.

Какие нужны инструменты

Для создания сборки нужен опыт написания модулей, к сожалению, моя статья не научит писать модули, а научит как знания из написания модулей применять в создании сборок. Впрочем, это не звонок: “Все, пипец, закрываю”, ибо есть начальный уровень который дастся очень легко.

Проблема в том, что тут нет никаких стандартов (по сборкам), кроме стандартов кодирования. Каждый делает как умеет и как может, ибо почти весь функционал используемый в модулях доступен в момент установки друпала. Т.е. всё Drupal API доступно с первого пункта, так как ядро полностью бутстрапается.

Гайдов очень мало, они не однозначные. Официальный покрывает лишь очеь поверхностные знания. По сути объясняется что где пишется и какие особенности. Например, вместо t() надо использовать st().

Если вы разработчик, то наверняка слышали о Lullabot, и скорее всего вы наткнетесь на их гайд по созданию сборок если начнете гуглить. Ни в коем случае не соблазняйтесь. Там расписано очень просто, сделать также, реально просто, но это не правильно и это близко не сборка. Суть в том, что там делается бекап базы уже рабочего сайта, а в инсталяционном профиле этот файл просто импортится в базу. В общем вариант очень неудачный, комментарии к тому гайду говорят об этом то же что и я. Хотя, честно, мне это по началу показалось очень простым и легким решением сделать сборку. Есть сборки на drupal.org которые реально его используют, но это все также не правильно.

Для создания сборок может сильно помочь модуль Features. Предварительно запаковав свои особенности в эти самые фичи, мы с легкостью можем развернуть нужный нам функционал, просто включив сгенерированный модуль. Например, запаковать тип содержимого, вьюхи и прочее. Реально удобно и просто, но не обольщайтесь что типа: “Да ща я тут весь сайт в фичи запакую и в путь”. В момент создания инсталяционного профиля и фич для него, вы узнаете о модуле Features намного глубже, и поймете, что он переносит далеко не все. В моей сборке, например, помимо фич, еще порядка 200-300 строк кода настроек поверх того что сделали фичи.

Level 1: Подготовка и базовый профиль

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

Далее нам необходимо создать 3 файла:

  1. my_first_distro.info
  2. my_first_distro.profile
  3. my_first_distro.install

Все модули, темы и необходимые для дистрибутива файлы помещаются в эту же папку. При этом, желательно сохранять структуру. Например так:

  • profiles - my_first_distro - my_first_distro.info
  • my_first_distro.profile
  • my_first_distro.install
  • modules - views
  • themes - bartik
  • libraries - fancyBox
  • ...
  • translation - ru.po

my\_first\_distro.info

В данном файле задается базовая информация о нашем будущем дистрибутиве. Название, версия ядра и какие модули необходимы для его успешной установки.

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

    ; Название нашего дистрибутива. Будет отображено в момент выбора дистрибутива.
    name = "My first distribution"
    ; Описание нашего дистрибутива. Будет показано под названием.
    description = "Устанавливает мой первый дистрибутив"
    ; Версия ядра Drupal.
    core = "7.x"
    ; Эта строка не обязательна, но если вы её напишите со значением 1, то сборка
    ; будет "эксклюзивной". То есть момент выбора профиля для установки будет
    ; пропущен и начнется принудительная установка данного профиля.
    ;exclusive = "1"
    
    ; Далее, в качестве зависимостей указываем необходимые модули. Во-первых, это те
    ; модули которые будут включены автоматически в момент установки. Во-вторых это
    ; список проверки. Если хотябы один из этого списка модулей не найдется, то
    ; установка перейдет на страницу с ошибкой и ожиданием её исрпавления.
    ; Для примера, установим парочку базовых модулей. По такому же принципу вы
    ; дописываете свои модули, views или сгенерированные Features. Зависимости вкл.
    ; автоматически, т.е. их указывать не нужно, но желательно.
    dependencies[] = block
    dependencies[] = color
    dependencies[] = comment
    dependencies[] = contextual
    dependencies[] = field
    dependencies[] = field_sql_storage
    dependencies[] = field_ui
    dependencies[] = file
    dependencies[] = filter
    dependencies[] = image
    dependencies[] = list
    dependencies[] = locale
    dependencies[] = menu
    dependencies[] = node
    dependencies[] = number
    dependencies[] = options
    dependencies[] = path
    dependencies[] = rdf
    dependencies[] = search
    dependencies[] = system
    dependencies[] = taxonomy
    dependencies[] = text
    dependencies[] = toolbar
    dependencies[] = update
    dependencies[] = user

my\_first\_distro.profile

Данный файл отвечает за какие-либо нужные нам настройки установочного профиля. Особо требований к нему никаких нету, как и к файлу .install. В обоих будут срабатывать функции. Но проанализировав сборки, каждый делает как ему удобнее. Я пошел по пути пары сборок и храню в данном файле только пару хуков системных форм (которые отображаются во время установки) которые меняют или устанавливают значения по-умолчанию для форм установки.

    <?php 
    /**
     * Используем hook_form_FORM_ID_alter().
     *
     * Альтерим форму настройки сайта. Это та, где вводится E-Mail сайта, его
     * название, страна, часовой пояс и регистрация юзера #1.
     */
    function system_form_install_configure_form_alter(&$form, $form_state) {
      // Мы заполняет поле названия сайта по умолчанию названием нашего дистра.
      $form['site_information']['site_name']['#default_value'] = 'My first distro site';
    }
    
    /**
     * Используем hook_form_alter().
     *
     * Альтерим форму выбора дистрибутива. Так как мы не указали что он
     * эксклюзивен, то у пользователя будет выбор, а мы лишь сделаем чтобы наш
     * дистрибутив был выбран по-умолчанию (чтобы стояла галочка).
     */
    function system_form_install_select_profile_form_alter(&$form, $form_state) {
      foreach ($form['profile'] as $key =?> $element) {
        // Указывается машинное имя сборки.
        $form['profile'][$key]['#value'] = 'my_first_distro';
      }
    }

my\_first\_distro.install

Инсталяционный файл, самый важный. В нем настраивается установка дистрибутива и все сопутствующие операции.

    <?php /**
     * Используем hook_install().
     *
     * Процесс установки нашего профиля. Так как этот хук обязателен, то
     * мы можем вызывать хуки других профилей, например standrat_install(). Он
     * установит стандартный профиль с его настройками а затем мы его уже можем
     * подкорректировать. Но это для сведения.
     * (!) данный процесс выполняется после установки всех модулей, что дает нам
     * возможность использовать их функционал и API. Пример с Features будет ниже.
     */
    function my_first_distro_install() {
        // Тут мы пишем все что нужно выполнить в момент установки.
        // Для примера давайте сделаем чтобы тема админки и сайта была Garland.
        // Так как данные значения хранятся в таблице variables, мы с легкостью
        // можем использовать функции variable_set, variable_get.
        // Устанавливаем основную тему для сайта Garland.
        variable_set('theme_default', 'garland');
        // Устанавливаем тему для админки - Garland.
        variable_set('admin_theme', 'garland');
        // Делаем чтобы тема админки использовалась при редактировании контента.
        variable_set('node_admin_theme', '1');
        
        // Теперь можно добавить парочку блоков в нужные нам регионы.
        // Обратите внимание что указывается тема. Если вы выбрали для админки и
        // сайта разные темы, то для каждой придется задавать блоки самостоятельно.
        $blocks = array(
        // Настраиваем блок с контентом.
        array(
          'module' =?> 'system',
          'delta' => 'main',
          'theme' => 'garland',
          'status' => 1,
          'weight' => 0,
          'region' => 'content',
          'pages' => '',
          'cache' => -1,
        ),
        // Настраиваем блок с авторизацией.
        array(
          'module' => 'user',
          'delta' => 'login',
          'theme' => 'garland',
          'status' => 1,
          'weight' => 0,
          'region' => 'sidebar_first',
          'pages' => '',
          'cache' => -1,
        ),
      );
      // Делаем запрос на удаления из таблицы block информации о блоках system и
      // user. Обратите внимание, что выше мы задали лишь массив, а сейчас удаляем
      // дефолтные значения.
      db_delete('block')->condition('module', 'system')->execute();
      db_delete('block')->condition('module', 'user')->execute();
      // А вот теперь добавляем в базу инфу о наших блоках.
      $query = db_insert('block')->fields(array('module', 'delta', 'theme', 'status', 'weight', 'region', 'pages', 'cache'));
      foreach ($blocks as $block) {
        $query->values($block);
      }
      $query->execute();
     
      // Также давайте создадим роль для пользователей "Администратор".
      $admin_role = new stdClass();
      $admin_role->name = 'administrator';
      $admin_role->weight = 10;
      user_role_save($admin_role);
      user_role_grant_permissions($admin_role->rid, array_keys(module_invoke_all('permission')));
     
      // Ну и укажем друпалу что роль у администратора такая-вот ;)
      variable_set('user_admin_role', $admin_role->rid);
     
      // Для полного набора, пользователю #1 (главном админу) также дадим эту роль.
      db_insert('users_roles')
        ->fields(array('uid' => 1, 'rid' => $admin_role->rid))
        ->execute();
        
      // Если вы также хотите использовать фичи для своего дистра, вам необходимо
      // также восстановить их до дефолтного состояния, что позволит избежать
      // ряда проблем. Лишним уж точно не будет.
      // Так как фич в примере не используется, этот код будет закомментирован как
      // пример, ибо он будет востребован.
      // Указываем список наших фич.
      /*$features = array(
        'feature_1',
        'feature_2',
      );*/
      // И при помощи Features API откатываем их в нужное состояние.
      /*features_revert($features);*/
     
      // В данном хуке выполняется львиная доля настройки дистра, можете глянуть
      // что я делаю в своем, тут уже у каждого своё продолжение.
    }

Level 2: Более расширенная настройка дистрибутива

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

Код который ниже, он вставляется после hook_install() который написан выше. Чтобы не растягивать я решил просто писать новое а тот вы и так видите ;)

hook_install()
    /**
     * Используем hook_install_tasks().
     *
     * В данном хуке мы регистрируем все свои задачи для выполнения.
     */
    function my_first_distro_install_tasks(&$install_state) {
      // Есть три типа задач: normal, batch и form.
      // Я расскожу про последние два, так как первый это тот же самый Form,
      // только не подразумевает submit колбэка для обработки данных.
     
      // Начнем с формы. Делается все просто, достаточно указать отображаемое
      // название задачи (отображается слева в моменту становки, где галочки)
      // отображать его в этом списке или нет, тип, и run - это условие выполнения.
      // По умолчанию и во всех случаях идет INSTALL_TASK_RUN_IF_NOT_COMPLETED.
      // Это значит что задача запустится после дефолтных. Там может быть условие
      // которое может регулировать этот параметр, он упомянится в батче.
      // Название таска - это в то же время и название вызываемой функции.
      // Т.е. когда друапл решит выполнить эту задачу, он автоматом вызовет
      // my_first_distro_form, но если вас такое не устраивает, вы можете передать
      // название вызываем функцией доп. параметрой function.
      $tasks['my_first_distro_form'] = array(
        'display_name' => 'Нужен ли вам батч',
        'display' => TRUE,
        'type' => 'form',
        'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED,
      );
    
      // Для простоты восприятия кода, начиная с этого комментария, рекомендуется
      // читать функцию my_first_distro_form, а потом уже вернуться сюда. Я это
      // помечу.
      // @see 147 строку. function my_first_distro_form()
     
      // Батч задается также как и форма, лишь убирается отображение (хотя вы
      // можете оставить его), и указывается соответствующий тип.
      // Но для начала мы получим значение переменной.
      $is_batch_needed = variable_get('my_first_distro_install_want_batch', FALSE);
      // Мы присваиваем значение в переменную, которая по умолчанию получит FALSE
      // если не указано TRUE. А будет TRUE, когда пользователь поставит галочку
      // в нашей форме, что ему надо выполнить батч операцию. Все просто!
      $tasks['my_first_distro_batch'] = array(
        'display' => FALSE,
        'type' => 'batch',
        // Если TRUE и стандартная установка пройдена, то запустит данная операция
        // Если FALSE, то выполнится пропуск данной операции.
        'run' => $is_batch_needed ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
      );
      // А теперь время вернуться к функции батч операци.
      // @see 189 строку function my_first_distro_batch()
     
      return $tasks;
    }
    
    /**
     * Наша форма, вызываемя на 115 строке.
     */
    function my_first_distro_form() {
      // Давайте просто выведем сообщение и чекбокс.
      $form['information'] = array(
        '#weight' => 0,
        '#markup' => '<p>Привет! Это моя первая форма и сообщение в ней. Если вы это видите, значит всё идет по плану.</p>',
      );
     
      // А теперь добавим чекбокс, который будет влиять на выполнение батч операции
      $form['want_batch'] = array(
        '#type' => 'checkbox',
        '#title' => 'Я хочу чтобы запустилась batch операция',
        '#weight' => 1,
      );
     
      // Так как это форма, нам необходима кнопка подтверждения с функцией, которая
      // обработает нашу форму.
      $form['submit'] = array(
        '#type' => 'submit',
        '#value' => 'Поехали!',
        '#weight' => 15,
        '#submit' => array('my_first_distro_form_submit'),
      );
     
      return $form;
    }
    
    /**
     * Обработчик формы my_first_distro_form.
     */
    function my_first_distro_form_submit($form, $form_state) {
      // Получаем значение нашего чекбокса. Если он установлен, мы задаем параметр
      // который позволит там запустить батч операцию. Таким способом передаются
      // значения в момент установки.
      if ($form_state['values']['want_batch'] == 1) {
        // Дадим ему более говорящие название. Чтобы потом не запутаться самим же.
        variable_set('my_first_distro_install_want_batch', TRUE);
        // Теперь вернитесь выше к переменной: $is_batch_needed
      }
    }
    
    /**
     * Функция вызываемая батч задачей.
     *
     * Данная функция должна возвращать готовый параметр для функции batch_set().
     * https://api.drupal.org/api/drupal/includes!form.inc/function/batch_set/7
     */
    function my_first_distro_batch() {
      // Тут мы задаем согласно batch_set() список функций для исполнения в batch.
      $batch = array(
        // Заголовок батч операции.
        'title' => 'Ура! Батч запустился и выполняется',
        // Список операций. Тут передается название вызываемой функции для операции
        // а в массиве можно передать данные. Если же их не нужно, то передается
        // пустой массив, иначе батч сломается.
        // После чего нам необходимо объявить данные функции.
        'operations' => array(
          array('my_first_distro_batch_operation_1', array()),
          array('my_first_distro_batch_operation_2', array($msg = 'Hello')),
        ),
      );
    
      return $batch;
    }
    
    /**
     * Первая функция для batch операций.
     */
    function my_first_distro_batch_operation_1() {
      drupal_set_message('Первая операция была вызвана успешно', 'status');
    }
    
    /**
     * Вторая функция для batch операций.
     *
     * В неё мы уже передавали значение.
     */
    function my_first_distro_batch_operation_2($msg = 'Bye') {
      drupal_set_message($msg, 'status');
    }

Level 3: Grand finale

В итоге мы написали простенький дистрибутив, который устанавливает базовый набор модулей, делает новую роль, присваивает её админу, расставляет блоки и меняет тему на Garland. А также сделали форму, в которой если пользователь поставит галочку, то выполнится две батч операции, которые выводят в конце установки два сообщения.

Дерзайте, создание сборок реально интересное занятие. Эту сборку-пример я прикреплю к материалу.

И напоследок скриншоты того что вышло.

Новый дистрибутив.
Собственная настройка.
Процесс установки.
Завершение установки.
Установленный сайт.
Прикрепленные файлы
My first distro — my_first_distro.zip, 5.94 КБ
Drupal
Drupal 7
Drupal profile

Комментарии

SAM   ср, 26/02/2014 - 12:39

Огромное спасибо за манул. Всё ясно и понятно.

Вопрос: Допустим через батч я подключу установку фич, с соответствующими шалочками для каждой фичи. Как мне удалить те фичи, которые не были установлены?
 

Niklan   ср, 26/02/2014 - 12:43

Тоже думаю над чисткой за собой. Тут явно только физическое удаление файлов. Т.е. надо создать массив со всеми фичами, затем кормим в module_exists() а если модуль выключен то удаляем файл через PHP функцию unlink, или Drupal (что куда правильнее но не уверен что она для этого) file_delete()

SAM   ср, 26/02/2014 - 13:41

Хорошо бы было, если прсото закинул фичи, к примеру в какую-нибудь папку. Инсталлер сам подхватил фичи и спросил, какие ставить, затем не нужные удалил) Как идея?

Как кстати при установки модуля выдать настройки модулям? Как посмотреть переменные?
 

Niklan   ср, 26/02/2014 - 13:53

Ну в моей сборке так и пашет, только не удаляет не используемые. Хотя там сейчас используется 100% фич и только 1 модуль можно подчистить - CKEditor, если не нужен WISYWYG редактор. А все остальные задействованы так или иначе а выбор лишь позволяет активировать их сабмодули.

По поповду переменных модуля. Многие из них хранят свои значения в таблице variable. Соответственно туда записать (variable_set('name',value)) и взять оттуда (variable_get('name',default)) данные очень просто. Что касатеся более навороченных модулей, они имеют свои таблицы, тут на помощь приходит db_insert или db_update для задания своих значений и данных. Можете глянуть в .install файл моего дистра, там таких операций завалом.

Так что тут придется капаться в базе, разбираться что и где храниться, иногда придется залазить в исходные коды модулей и смотреть как они сохраняют свои значения. Например я так и не смог перенести настройки CKEditor, они просто очень сурово завязаны на сохранении формы. Даже если скопировать настройки и сного туда же их импортнуть, CKEditor уже слетает, ибо не из формы. Вот в таких случаях приходится еще и разгребать модуль. Но мне пока не до него, у него там даже API нету для создания профилей, зато для получения есть.
 

kalabro   пт, 28/02/2014 - 09:59

Статья про сборки без единого упоминания Drush Make. Кажется, что-то здесь не так.

Niklan   пт, 28/02/2014 - 12:37

Херня drush make, его руками проще контролировать. Если хотите, я расскажу про drupal-org.make файл отдельной статьей. Он к сборкам имеет самое последнее отношение, это уже на этапе подготовки дистрибутива для выкладывания на drupal.org, а ведь не каждый, и я бы даже сказал, что маленькая часть будет туда выкладывать. Так что это из разряда "Подготовка дистрибутива для drupal.org" нежели его создание.

Niklan   сб, 01/03/2014 - 12:21

Прощайте. Если вы думаете что drush make сгенерит вам что-либо полезное... пусть генерит

Я даже уверен на 99.999% что drush make не сгенерит валидный .make файл. Что это за файл и зачем он используется я уже сказал. К сборкам он имеет последнее отношение и никак не влияет на них.

Alex Malkov   сб, 01/03/2014 - 14:57

Я бы не был столь категоричным по поводу Lullabot. Сборки прежде всего отличаются между собой предназначением. Т.е. мне, как пользователю с уровнем site-builder интереснее было бы увидеть рабочий сайт с каким-либо маломальским контентом.

В вашем случае мне придется, вероятнее всего, поймать пару нотисов и варнингов при разворачивании дистрибутива. И еще не факт, что мне удасться разобраться, что к чему. И даже если мне посчастливится все установить, то потом придется так или иначе настраивать CKEditor и т.п. модули. И если я правильно понял, то и структуры сайта я не увижу, пока не начну его заполнять контентом, что очень не наглядно.

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

Ну и к тому-же есть интересный вывод друпалеров со стажем http://www.drupal.ru/node/96324, что в общем-то намекает на практику Lullabot.

Так или иначе - спасибо, что поделились опытом. Есть интересные вещи.

p.s. В качестве пожелания - будьте добрее и уважительнее к вашим комментаторам. Печально видеть ответы автора в стиле д.ру.

Niklan   сб, 01/03/2014 - 15:12

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

И именно поэтому drush make тут пролетает. Статья именно про создание дистрибутива по типу официальной, только на русском и более подробно. И даже там drush make появляется в самом конце статьи, лишь в упоминании для тех, кто собрался заливать дистрибутив на drupal.org.

P.s. А я и не злой к комментаторам ;) Просто вот такой вот я, привык выражаться резко, но я за этим не несу никакой злобы или обиды, просто вот так вот, стараюсь конечно загубить в себе это качество, но увы, так быстро от такой заразы не избавиться. Если кого задевают мои высказывания, то я извиняюсь, конечно же, но в таком случае будет самым логичным просто закрыть мой блог и игнорировать мои сообщения, зачем себя травмировать моим бредом ;)

Андрей   вт, 20/05/2014 - 15:37

Здравствуйте. Спасибо за Ваш труд. С удовольствием читаю все статьи.

Можно ли создавать представления в профиле? Чтобы они создавались в процессе установки сайта. Без использования фичи или своего модуля.

Александр   пт, 26/12/2014 - 21:17

Доброго времени.
Подскажите что лучше использовать.
Стоит задача так же как и в вашем примере с выбором настроек установки, модулями, правами но
Но нужны еще и тестовые данные на некоторые роли и примеры заполнения некоторых типов материала.
С другой стороны можно написать хелпер на доске в админке, но хотелось бы для наглядности упростить работу пользователю. как быть в таком случае?
Наткнулся на модуль APPS. И в нем как будто бы есть возможность подгружать данные, но пока еще читаю-разбираюсь как оно работает

Александр   чт, 13/08/2015 - 12:27

Скажите, а есть способ, как после установки Друпала с вашим профилем вернутся на профиль standard?

Спасибо

Niklan   чт, 13/08/2015 - 12:30

Нет, такого способа нет ни на одной сборке. Только чистая установка.

Йог'Сарон   чт, 28/04/2016 - 05:46

Я что-то совсем не понимаю. Куда надо получившуюся папку деть, чтобы запустить потом установку как на скринах?