В разрезе: новостной агрегатор на Android с бэкендом. Система сборки

    Вводная часть (со ссылками на все статьи)

    Лет 10-15 назад, когда программы состояли из исходников и небольшого количества двоичных файлов с работой по сборке итоговых программ отлично справлялись всевозможные «?make». Однако сейчас современные программы и подходы к разработке сильно изменились – это:
    множество различных файлов (не считаю исходников) – стили, шаблоны, ресурсы, конфигурации, скрипты, бинарные данные и.т.д;

    • предпроцессоры;
    • системы проверки стиля исходников или всего проекта (lint, checkstyle и т.д.);
    • методики разработки, основанные на тестах, с их запуском при сборке;
    • различного типа стенды;
    • системы развёртывания на базе облачных технологий и т.д. и.т.п.

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

    Мой путь в использовании систем сборки был непонятное_количество_*make -> ant -> maven -> gradle (то обстоятельство, что android Studio под капотом использует gradle меня сильно порадовало).

    Gradle меня привлёк:

    • своей простой моделью (из которой правда можно вырастить монстра, соизмеримого с самим создаваемым продуктом);
    • гибкостью (как в части настройки самих скриптов, так и организации их распределения в рамках больших проектов);
    • постоянным развитием (как и со всеми остальными вещами в разработке — тут надо постоянно изучать что-то новое);
    • лёгкостью адаптации (знание groovy и gradle DSL обязательно);
    • наличием системы plugin’ов, разрабатываемых сообществом — это и разные предпроцессоры, генераторы кода, системы доставки и публикации и прочее, прочее (см. login.gradle.org)

    Ознакомиться с возможностями Gradle можно на сайте разработчиков в разделе документации (можно узнать всё!). Для тех, кто хочет сравнить gradle и maven есть интересное видео от JUG.

    В моём случае скрипты для сборки выглядят таким образом:

    image
    ,
    где:

    • build_scripts/build-tasks.gradle — все задачи для сборки с указанием их зависимостей;
    • build_scripts/dependencies.gradle — описания зависимостей и способов публикации;
    • build.gradle — основной скрипт, определяющий зависимые модули, библиотеки и включающий другие скрипты сборки;
    • settings.gradle — перечень зависимых модулей и настройки самого скрипта (можно переопределить ч/з аргументы запуска gradle).

    В такой конфигурации все изменения скрипта сборки производятся в централизованных файлах (не даются на откуп каждому проекту) и могут/должны корректироваться централизованно лицом, ответственным за сборку проекта.

    Tips


    Из интересных вещей/советов, которыми я хотел бы поделиться, при настройке gradle в моём проекте есть следующие:

    • Вынос версий артефактов (шарится по блокам модулей было скучным занятием)


      Объявляем блок с зависимостями:
      // project dependencies
      ext {
      COMMONS_POOL_VER='2.4.2'
      DROPWIZARD_CORE_VER='1.1.0'
      DROPWIZARD_METRICS_VER='3.2.2'
      DROPWIZARD_METRICS_INFLUXDB_VER='0.9.3'
      JSOUP_VER='1.10.2'
      STORM_VER='1.0.3'
      ...
      GROOVY_VER='2.4.7'
      // test
      TEST_JUNIT_VER='4.12'
      TEST_MOCKITO_VER='2.7.9'
      TEST_ASSERTJ_VER='3.6.2'
      }
      

      И используем его в проекте:
      project(':crawler_scripts') {
      	javaProject(it)
      	javaLogLibrary(it)
      	javaTestLibrary(it)
      
      	dependencies {
      		testCompile "org.codehaus.groovy:groovy:${GROOVY_VER}"
      		testCompile "edu.uci.ics:crawler4j:${CRAWLER4J_VER}"
      		testCompile "org.jsoup:jsoup:${JSOUP_VER}"
      		testCompile "joda-time:joda-time:${JODATIME_VER}"
      		testCompile "org.apache.commons:commons-lang3:${COMMONS_LANG_VER}"
      		testCompile "commons-io:commons-io:${COMMONS_IO_VER}"
      	}
      }
      

    • Вынос настроек во внешний файл
      Создаём или уже имеем файл с конфигурацией:
      ---
      # presented - for test/development only - use artifact from ""/provision/artifacts" directory
      storyline_components:
        crawler_scripts:
          version: "0.5"
        crawler:
          version: "0.6"
        server_storm:
          version: "presented"
        server_web:
          version: "0.1"
      

      И используем его в проекте:
      import com.fasterxml.jackson.databind.ObjectMapper
      import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
      
      buildscript {
      	repositories {
      		jcenter()
      	}
      	dependencies {
      		// reading YAML
      		classpath "com.fasterxml.jackson.core:jackson-databind:2.8.6"
      		classpath "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.8.6"
      	}
      }
      ....
      def loadArtifactVersions(type) {
      	Map result = new HashMap()
      	def name = "${projectDir}/deployment/${type}/hieradata/version.yaml"
      	println "Reading artifact versions from ${name}"
      	if (new File(name).exists()) {
      		ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
      		result = mapper.readValue(new FileInputStream(name), HashMap.class);
      	}
      	return  result['storyline_components'];
      }
      

    • Использование шаблонов

      Это моя любимая часть — позволяет сформировать необходимые конфигурационные файлы из шаблонов.
      Формируем шаблоны:
      version: '2'
      services:
      ...
        server_storm:
          domainname: story-line.ru
          hostname: server_storm
          build: ./server_storm
          depends_on:
              - zookeeper
              - elasticsearch
              - mongodb
          links:
              - zookeeper
              - elasticsearch
              - mongodb
          ports:
           - "${server_storm_ui_host_port}:8082"
           - "${server_storm_logviewer_host_port}:8083"
           - "${server_storm_nimbus_host_port}:6627"
           - "${server_storm_monit_host_port}:3000"
           - "${server_storm_drpc_host_port}:3772"
          volumes:
           - ${logs_dir}:/data/logs
           - ${data_dir}:/data/db
      ....
      

      И используем его в проекте:
      // выполнить копирование скриптов для подготовки сервера
      task copyTemplates (type: Copy, dependsOn: ['createStandDir']){
      	description "выполнить копирование шаблонов"
      	from "${projectDir}/deployment/docker_templates"
      	into project.ext.stand.deploy_dir
      	expand(project.ext.stand)
      	filteringCharset = 'UTF-8'
      }

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

    • Использование списков и замыканий groovy для специфичной обработки конкретных модулей

      Это фактически обычное использование groovy в скрипте сборки, но мне это помогло решить пару нетривиальных задач.

      Объявляем или получаем переменные множественного значения:
      ext {
      	// образы, создаваемые docker'ом
      	docker_machines = ['elasticsearch', 'zookeeper', 'mongodb', 'crawler', 'server_storm', 'server_web']
      	// образы, создаваемые docker'ом для которых необходимо копировать артефакты
      	docker_machines_w_artifacts = ['crawler', 'server_storm', 'server_web']
      }
      

      И используем их в проекте:
      // выполнить копирование шаблонов для docker с подстановкой значений
      docker_machines.each { machine ->
      	task "copyProvisionScripts_${machine}" (type: Copy, dependsOn: ['createStandDir']){
      ...
      	}
      }
      

    • Интеграция с репозитариями maven — больно громоздкое описание для включения в статью (и сама работа достаточно бесполезная с учётом наличия примеров в документации)

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

      Объявляем блоки:
      def javaTestLibrary(project) {
          project.dependencies {
      		testCompile "org.apache.commons:commons-lang3:${COMMONS_LANG_VER}"
      		testCompile "commons-io:commons-io:${COMMONS_IO_VER}"
      		testCompile "junit:junit:${TEST_JUNIT_VER}"
      		testCompile "org.mockito:mockito-core:${TEST_MOCKITO_VER}"
      		testCompile  "org.assertj:assertj-core:${TEST_ASSERTJ_VER}"
          }
      }
      

      И используем их в проекте:
      project(':token') {
      	javaProject(it)
      	javaLogLibrary(it)
      	javaTestLibrary(it)
      }
      

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

    Спасибо за внимание!
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 2
    • 0
      Однако сейчас современные программы и подходы к разработке сильно изменились

      ИМХО конечно, но с точки зрения использования make ничего не поменялось.
      Все также нужно из множества файлов А получить множество файлов Б.


      Будь это получение xml отчета lint из множества .java и .xml. Или получение из множества файлов ресурсов с помощью aapt файла с .java кодом для доступа вида R.id.X,
      со всем этим gnu make отлично справиться.


      Другое дело, что язык для правил make слишком неудобный по сравнению например с groovy,
      да и встроенных правил (типа %.o : %.c) для сборки android проектов конечно нет.

      • 0
        Есть истинна в этих словах. Но сборка продукта системой сборки (а так же его тестирование и доставка) не всегда предполагают работу с только файлами — это могут быть и сетевые сервисы (FTP, HTTP), и сервисы ОС (docker например), и взаимодействие с облачными сервисами, а это становится для make не простой задачей (но не невыполнимой).

        Полагаю свой скрипт сборки с сохранением функционала я мог бы реализовать на ant, но зная про gradle — стоило бы?

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