В разрезе: новостной агрегатор на 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)
      }
      

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

    Спасибо за внимание!
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 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 — стоило бы?

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