Проект Lazybones — «Лентяй», который работает за вас

  • Tutorial
Я не люблю Maven.
О моей пассионарной ненависти к этой штуке можно написать не одну статью, но сегодня я хочу поговорить об одной очень хорошей фиче Мавена — об архетипах. Что это такое можно прочитать в официальной документации, в каждом из туторилов по Мавену на Хабре(1, 2, 3), да и вообще, вы наверняка знаете и сами.

Так вот, архетипы — это круто, и было бы здорово, если бы 1) во многих проектах со стандартной структурой они были. 2) можно было бы их прикрутить к тем, у которых их нет.

Примерно так думал Питер Ледбрук, когда смотрел на полное отсутствие архетапов в Ratpack. Тогда и родился проект Lazybones — инструмент генерации проектов.

image

В этой статье я расскажу вам как 1) Пользоваться Lazybones для генерации проектов, для которых уже созданы шаблоны. 2) Создавать новые шаблоны для любых проектов.

Использование существующих шаблонов Lazybones

Tут все будет предельно коротко:
  1. Устанавливаем Lazybones с помощью GVM или скачиваем дистрибутив с Bintray
  2. Смотрим какие шаблоны существуют с помощью команды lazybones list (или изучаем репозиторий)
  3. Изучаем информацию о выбраном шаблоне с помощью команды lazybones info <имя шаблона> (или читаем readme в packag-e шаблона на Бинтрее)
  4. Создаем проект командой lazybones create <имя шаблона> <версия шаблона> <имя директории в которой создавать>

Всё, спасибо за внимание, все свободны. Хотя нет, сейчас будем делать как раз интересное.

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

Поскольку вы все, скорее всего, знакомы с мавеновским архетипом #361 (maven-archetype-quickstart), мы сделаем что-то похожее (воссоздав все фичи, мы опустим некоторые повторы).
Мало того, что вы сможете сравнить количество скаченного интернета для создания обоих проектов, вы еще и сможете сравнить сложность создания самого шаблона, ибо процесс создания архетайпа Мавена прекрасно описан вот тут.

Итак, поехали.

Чего мы хотим добиться:
  • Создать базовый pom.xml с выбраными через интерактивную командную строку groupId, artifactId и версией
  • Создать директории src/main/java, src/main/resources (тоже самое для тестов, но мы не будем, для простоты примера)
  • Создать класс для примера, прописать в нем выбранный через интерактивную командную строку package и положить его в походящую под package директорию (тоже самое для теста, но мы не будем, для простоты примера)
  • В классе сгенерить main, который будет при запуске фазы теста печатать сообщение, выбранное через интерактивную командную строку
  • Собрать шаблон
  • Создать проект по нашему шаблону
  • Запустить mvn test
  • Профит


  1. Для сборки шаблона нам понадобится скрипт Gradle и директория с шаблонами. Поскольку нам лень, мы запустим lazybones:
    >lazybones create lazybones-project lzb-templates
    В результате у нас есть следующее:

    │   build.gradle //скрипт сборки шалбонов
    │   gradlew //файл запуска скрипта для никсов
    │   gradlew.bat //файл запуска скрипта для винды
    │   README.md //файл описывающий этот проект
    │
    ├───gradle //вспомогательная директория для скрипта
    │
    └───templates //пустая директория для наших шаблонов
    

  2. Заходим в директорию templates, создаем в ней под-директорию нашего шаблона, и начинаем ваять. Создаем файл версии. Он называется VERSION и содержит только версию, например 0.1

    >mkdir maven-simple
    >cd maven simple
    >echo 0.1 > VERSION
    

  3. Кроме того, нужно создать readme.md, который будет показан после создания проекта.
  4. Создаем директории src/main/java, src/main/resources. В java и resources из них кладем по пустому файлу .retain

    ├───maven-simple
    │   │   README.md
    │   │   VERSION
    │   │
    │   └───src
    │       └───main
    │           ├───java
    │           │       .retain
    │           │
    │           └───resources
    │                   .retain
    

  5. Теперь займемся шаблонами. Начнем с pom.xml:
    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>${groupId}</groupId>
        <artifactId>${artifactId}</artifactId>
        <version>${version}</version>
        <build>
         <plugins>
          <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>exec-maven-plugin</artifactId>
           <version>1.2.1</version>
           <executions>
            <execution>
             <phase>test</phase>
             <goals>
              <goal>java</goal>
             </goals>
             <configuration>
              <mainClass>${pkg}.App</mainClass>
              <arguments>
               <argument>${message}</argument>
              </arguments>
             </configuration>
            </execution>
           </executions>
          </plugin>
         </plugins>
        </build>
    </project>
    

    ААААА!!!!!!!
    Так, взяли себя в руки, смотрим. Обратите внимание на всякие ${...}. Это то, что мы будем менять на значения, которые нам задаст пользователь во время запуска create. По сути, это просто маркеры Groovy Templates. Если вы знакомы с Velocity, Freemarker или любым другим обработчиком шаблонов, вам всё будет знакомо. Но об этом позже.
    Адовый ад в — это всего лишь запуск main-а класса App. Обратите внимание, что нам пока нам неизвестны package этого класса и параметр, который мы передаем в main.
  6. Теперь смотрим на файл App.java:
    package ${pkg};
    
    public class App {
        public static void main(String[] args) {
            System.out.println(args[0]);
        }
    }
    

    Тут у нас всего одна переменная — опять-же, package. Заодно мы видим, что main печатает аргумент. Значит, что во время запуска мавена, в фазе теста, мы ожидаем увидеть сообщение, которое пользователь выберет, опять-же, во время create.
    Итак, теперь мы имеем все директории и шаблоны:
    │   App.java
    │   lazybones.groovy
    │   pom.xml
    │   README.md
    │   VERSION
    │
    └───src
        └───main
            ├───java
            │       .retain
            │
            └───resources
                    .retain
    

  7. А вот теперь начинается самое интересное. Нам осталось написать пост-процессор, который будет бежать после распаковывания директорий. Задачи: 1) Узнать всё, что нужно у пользователя, 2) перенести java файл в директорию, соответствующую package, 3) обработать шаблоны.
    Поможет нам в этом, конечно, элегантный Груви скрипт:
    import static org.apache.commons.io.FileUtils.moveFileToDirectory
    
    Map<String,String> props = [:]
    
    //метод ask принимает 2 параметра - сообщение, и значение по умолчанию.
    //он показывает сообщение и ждет ввода. Ввод (или значение по умолчанию, если ввод пустой) возвращается.
    props.groupId = ask('Выберите groupId [org.example]: ', 'org.example')
    props.artifactId = ask('Выберите artifactId [maven-simple]: ', 'maven-simple')
    props.version = ask('Выберите версию [1.0-SNAPSHOT]: ', '1.0-SNAPSHOT')
    props.pkg = ask("Выберите package для класса [$props.groupId]:", props.groupId)
    props.message = ask('Чего печатать в тесте? ', 'Привет, лентяй!')
    
    //метод processTemplates обрабатывает шаблоны, заменяя меаркеры значениями из мапы.
    processTemplates 'pom.xml', props
    
    //заменяем точки на слэши
    String packageDir = props.pkg.replaceAll(/\./, '/')
    //переносим исходник в нужную директорию
    moveFileToDirectory(new File(targetDir, 'App.java'), new File(targetDir, "src/main/java/$packageDir"), true)
    //обрабатываем шаблон
    processTemplates 'src/main/java/**/App.java', props
    

    Надеюсь, комментарии достаточно понятно объясняют, что происходит. Единственное, наверное, что нужно добавить, это то, что методы ask() и processTemplates() и поле targetDir попадают в скрипт из класса uk.co.cacoethes.lazybones.LazybonesScript, который является кастомным супер-классом этого скрипта.
  8. Пора собирать. У Lazybones есть свой плагин для Грейдла, который уже сконфигурирован в скрипте сборки, который мы сгенерировали в пункте 1. Этот плагин определяет task rules для сборки, установки в локальном кэше и деплоймента шаблонов на Бинтрей. Поскольку шаблон у нас не серъезный, на Бинтрей мы его класть не будем, а вот установить в кэш, чтобы попробовать запустить — обязательно. Запускаем сборку:
    >gradlew installTemplateMavenSimple
    :packageTemplateMavenSimple
    :installTemplateMavenSimple

    BUILD SUCCESSFUL
  9. Тестируем! Создаем новую директорию и в ней создаем проект из шаблона (как мы уже видели):
    >lazybones create maven-simple 0.1 maven-simple

    Creating project from template maven-simple 0.1 in 'maven-simple'
    Выберите groupId [org.example]: com.demo
    Выберите artifactId [maven-simple]:
    Выберите версию [1.0-SNAPSHOT]: 0.1
    Выберите package для класса [org.example]:org.simple
    Чего печатать в тесте? Привет, Хабр!

    Шаблон а-ля архетайп
    ---------------------------------
    Ты создал Мавеновский проект. Может хватит? Грейлд ждет тебя.

    Project created in maven-simple!

    Сообщение в конце, приходит, конечно из readme.md. Обратите внимание, я не указал artifactId, ожидаю maven-simple по умолчанию.
    Заходим в директорию maven-simple, и любуемся:
    │   pom.xml
    │   README.md
    │
    └───src
        └───main
            ├───java
            │   └───org
            │       └───simple
            │               App.java
            │
            └───resources
    

    Открываем pom.xml:
    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.demo</groupId>
        <artifactId>maven-simple</artifactId>
        <version>0.1</version>
        <build>
         <plugins>
          <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>exec-maven-plugin</artifactId>
           <version>1.2.1</version>
           <executions>
            <execution>
             <phase>test</phase>
             <goals>
              <goal>java</goal>
             </goals>
             <configuration>
              <mainClass>org.simple.App</mainClass>
              <arguments>
               <argument>Привет, Хабр!</argument>
              </arguments>
             </configuration>
            </execution>
           </executions>
          </plugin>
         </plugins>
        </build>
    </project>
    

    Всё как надо. Открываем App.java:
    package org.simple;
    
    public class App {
        public static void main(String[] args) {
            System.out.println(args[0]);
        }
    }
    

    Тоже порядок. Запускаем Мавен:
    >mvn test
    [INFO] Scanning for projects...
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] Building maven-simple 0.1
    [INFO] ------------------------------------------------------------------------
    [INFO]
    [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-simple ---
    [INFO] Copying 0 resource
    [INFO]
    [INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ maven-simple ---
    [INFO] Nothing to compile - all classes are up to date
    [INFO]
    [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-simple ---
    [INFO]
    [INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ maven-simple ---
    [INFO] No sources to compile
    [INFO]
    [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-simple ---
    [INFO] No tests to run.
    [INFO]
    [INFO] >>> exec-maven-plugin:1.2.1:java (default) @ maven-simple >>>
    [INFO]
    [INFO] <<< exec-maven-plugin:1.2.1:java (default) @ maven-simple <<<
    [INFO]
    [INFO] --- exec-maven-plugin:1.2.1:java (default) @ maven-simple ---
    Привет, Хабр!
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 0.768s
    [INFO] Finished at: Fri Apr 04 02:54:57 IDT 2014
    [INFO] Final Memory: 7M/304M
    [INFO] ------------------------------------------------------------------------
    


Вот и всё. Я надеюсь, вы прониклись простотой и изяществом как создания проектов из шаблонов так и созданием самих шаблонов в Lazybones. Мне кажется, за эти простоту и изящество во многом надо благодaрить Груви.

Бесстыдный пиар, можно не читать
Если вам понравилось и вы со мной согласны на счет изящества и простоты, и если вы хотите и сами подучить немножко Груви, то приходите ко мне на тренинг 15-го апреля в Казани, или 17-го апреля в Москве. Обещаю научить Грувийным плюшкам.
Поделиться публикацией
Комментарии 28
    0
    Т.е. с помощью грейдла мы создаем архетип для мавен? Элегантно! Похоже на какое то извращение.
    А для грейдла архетипы еще не сделали?
      0
      Я использую этот пример, потому что все знают что такое Мавеновские архетипы и удобно сравнивать.
      В Грейдле архетипы есть, называется Build Init Plugin.
        0
        У него 4 вида проектов или я что то неправильно прочел? А как создавать новые?
          0
          Все правильно, четыре. Pull request в сорцы Гредла? :)
            0
            Не солидно как то… А если я свой архетайп нужен, личный? Ну ты понял, короче…
              +1
              Ну, а статью я для чего писал?
                –1
                Чтобы заманить всех на свои семинары в Казане? =)
                  +1
                  Юморист?
                    0
                    Зато у вас с чувством юмора всё в порядке. Спасибо хоть не послали.
                      0
                      Да боже упаси. За что? :)
                    +2
                    а почему бы и нет? Вы имеете что-то против?
        +1
        хм… а archetype:create-from-project чем уныл?

        Вообще при всей прикольности мавена (дада, про closest-dependency я в курсе), архетипы не самый удобный с моей точки зрения инструмент. Во-первых, их надо помнить («мавеновский архетип #361» — это ок). Так из консольки-то не подберешь под себя. А во-вторых их надо реально поддерживать, обновлять, деплоить. Я вот свой архетип уже утомился апдейтить, поэтому тупо копирую пустой проект вместо создания из архетипа.
          +1
          Он не уныл, он просто только Мавен. А Lazybones для чего угодно. Я просто привел пример того, что всем знакомо.

          Как человеку опытному, как тебе Lazybones по сравнению в архетипами?
            0
            Вообще, я не люблю писать код :) Наверное, для грэдловских проектов он удобен, но, если говорить о мавене, то я не вижу очевидных преимуществ.

            И про архетипы или «шаблоны» проектов я писал выше. Плюс они хороши для быстрого quickstart'а и «пощупывания» новой технологии, но в проекте ты же не будешь с ними каждый день работать, особенно со сложными, которые не под либу генерируют проект, а под целый слоеный стэк сразу. Отсюда быстрее скопировать какой-то старый проект и почистить его, а не вспоминать как работать с архетипами или вот Lazybones.

            Но это я тему в статье обсуждаю, никак не гоню на саму статью. Я всегда рад новым качественным обзорам, их здесь мало. Так что, спасибо, Барух, продолжай :)
              +2
              На мой взгляд, ты (как, похоже, и другие тоже, может я просто плохо объяснил) не совсем правильно видишь кто и зачем создает шаблоны проектов. Я вижу сценарии для двух ролей:
              • Разработчики фреймворков (и соратники): Написал theshade jmh. Для работы с этим фреймворком нужна определенная структура проекта. Для облегчения работы с ним, был создан архетайп (ну, и я, как соратник, сделаю шаблон Lazybones)
              • Configuration Manager-ы в кровавых энтерпразах: В его энтерпразе каждые несколько дней где-нибудь в мире начинают новый проект. К ним всем есть определенные требования: какие-то обязательные зависимости, какие-то плагины, какие-то фреймворки. Написать один раз и поддерживать шаблон — единственный способ это всё менеджить

              Как--то так…
                +1
                JMH прикручивается к Градлу в пару строк в конфиге без всяких архетипов. И структура проекта не отличается от стандартной явовской. Или я что-то не понимаю?
                  +1
                  Ну фиг его знает, зачем они архетип написали. Но раз кому-то он нужен, то и шаблон Lazybones пригодится.
                  • НЛО прилетело и опубликовало эту надпись здесь
                    +1
                    Сделаю шаблон lazybones

                    Это как пример, или и правда сделал? :)

                    Весьма годная статья! #groovyнедооцененвэйтостране
                      0
                      Еще не сделал, но сделаю (или нет. но наверное да.).
              0
              Слишком сложно выходит, если проекты делаются не часто, то каждый раз надо будет ходить по всем граблям. Если нет особых требований, тогда уже наверное стоит использовать гредл, явовский плагин и мавеновский репозиторий.
                +1
                Вы точно мою статью комментируете? :-) Где вы грабли увидели то?
                И это всё вообще не имеет отношения к системам сборки. Этот пост не по у Мавен и не про Грейдл. Мавен я упомянул лишь как пример системы, в которой уже есть решение, подобное Lazybones. А шаблоны можно создавать любые, для любых проектов, безотносительно к системе сборки.
                  0
                  Первая строка же прямо так и говорит: я не люблю мавен.
                  Почему грабли? Нужно поставить гредл, настроить, поставить гвм, слазить в его репозиторий, покрутить лейзибонс, подпихнуть груви скрипт или изучать готовые шаблоны. Со временем что-нибудь подправят в любом из этих инструментов(или ошибется человек в настройке), туториал потеряет актуальность, а девелопер будет наступать на грабли. Если вы создаете новый проект раз в неделю — ок, а если раз в полгода, то игра не стоит свеч. Как-то так.
                    +2
                    Первая строка же прямо так и говорит: я не люблю мавен.

                    Это был так называемый «троллинг». Моя любовь/нелюбовь к Мавену не имеет никакого отношения к теме. Более того, я привожу пример Мавена, потому что его все знают, и многие (почему-то) любят.
                    Почему грабли? Нужно поставить гредл, настроить, поставить гвм, слазить в его репозиторий, покрутить лейзибонс, подпихнуть груви скрипт или изучать готовые шаблоны. Со временем что-нибудь подправят в любом из этих инструментов(или ошибется человек в настройке), туториал потеряет актуальность, а девелопер будет наступать на грабли.

                    Грейдл даже ставить не нужно, не говоря уж про настройку (у меня разве написано, что нужно?) gvm ставить не нужно (там написано: «или-или»). Ваше соображение, что если что-то поменяется, или кто-то ошибется, то наступят грабли — оно конечно верно, но верно в общем случае, и не имеет отношения к данной статье. В статье как раз описаны ровно ноль граблей. Все работает as expected.
                    Если вы создаете новый проект раз в неделю — ок, а если раз в полгода, то игра не стоит свеч. Как-то так.

                    Это, безусловно, верное замечание. Не всем нужно создавать шаблоны, и даже не всем нужно создавать проекты с использованием шаблонов. Выше я описал два сценария, когда вы захотите создать свой шаблон. Именно для них и пригодится моя статья.
                0
                Есть ли плагин для gradle? Или это бессмыслено? Просто не очень хочется юзать 2 разные консольные команды.
                  +1
                  Для list, info и create? Я о таком не слышал, и очень сомневаюсь, что его нужно делать. Ведь идея в том, что тебе не нужен Грейдл, чтобы создать проект из шаблона. Вполне может быть, что полученный проект вообще не будет использовать Гредйл. Так зачем его ставить?
                  +1
                  А под Видой Lazybones ни как не запустить? Но только без Cygwin, мы его не любим.
                    0
                    Ну а за что его любить-то?

                    Там был баг, который я пофиксил, и мой pull-request смерджен.
                    Соберите из head-a, и все будет работать :)

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

                  Самое читаемое