багатомодульна Java-проект з Gradle. Крок за кроком

    Дуже багато статей про Gradle написано. І зі свого боку хотілося б додати в скарбничку таку покрокову інструкцію, прочитання якої, я сподіваюся, дозволить тим, хто погано знайомий з Gradle, "розкуштувати" і продовжити самостійно вивчати цей інструмент.
 
Дана стаття не буде докладно описувати такі теми, як плагіни gradle (plugin), завдання (task), залежності (dependencies), автоматичне тестування та інші принади цього збирача проектів. По-перше, кожна тема заслуговує окремої статті або навіть серії статей, а по-друге, на ці теми вже є статті на Хабре, наприклад: Gradle: Tasks Are Code , Gradle: Better Way To Build . А ще на офіційному сайті Gradle є чудово написаний Gradle User Guide . Я ж cфокусірую увагу на безпосередньому вирішенні поставленого завдання, і всі супутні теми будуть описані в рамках цієї самої задачі.
Спочатку визначимося з метою, що ж ми хочемо отримати на виході? А мета вказана в заголовку статті. Ми хочемо отримати проект з декількома модулями, який збирається за допомогою Gradle. І так, приступимо.
 
 
 
Крок 1. Установка gradle
 Прімеченіе: Якщо вихотіте просто "пограти" з gradle, скачавши файли для статті, або вам дісталися чужі исходники з чарівним файлом gradlew (gradlew.bat) в корені проекту, то встановлювати gradle не обов'язково.
 
Gradle можна поставити, скачавши останню версію зі сторінки завантажень Gradle або скориставшись менеджером пакетів у вашій улюбленій ОС (прим. Я ставив на Mac OS через brew і на Debian через apt-get зі стандартних джерел)
 
Результат першого кроку:
 
$ gradle -version

------------------------------------------------------------
Gradle 1.11
------------------------------------------------------------

Build time:   2014-02-11 11:34:39 UTC
Build number: none
Revision:     a831fa866d46cbee94e61a09af15f9dd95987421

Groovy:       1.8.6
Ant:          Apache Ant(TM) version 1.9.2 compiled on July 8 2013
Ivy:          2.2.0
JVM:          1.8.0_05 (Oracle Corporation 25.5-b02)
OS:           Mac OS X 10.9.3 x86_64

 
 
Крок 2. Порожній проект, плагіни (plugin), обгортка (wrapper)
Створимо папку проекту і в її корені зробимо файл
build.gradle
з наступним вмістом:
 
 {project_path} / build.gralde
 
apply plugin: “java”
apply plugin: “application”

task wrapper(type: Wrapper) {
    gradleVersion = '1.12'
}

Давайте, розглянемо докладніше, що ми написали у файлі. Тут використовується динамічний мову Groovy. Використання повноцінного мови програмування в gradle дає більшу свободу в порівнянні зі збирачами пакетів, що використовують декларативні мови.
У цьому файлі ми підключаємо плагіни
java
і
application
. Плагін
java
містить в собі такі корисні завдання, як jar — зібрати jar архів, compileJava — скомпілювати вихідні коди та ін Детальніше про модуль можна почитати тут . Плагін
application
містить в собі завдання: run — запуск програми; installApp — установка програми на комп'ютер, ця задача створює виконувані файли для * nix і для windows (bat файл); distZip — збирає додаток в zip архів, поміщаючи туди все jar файли, а також специфічні для операційної системи скрипти. Детальніше про плагіні в документації .
Тепер зупинимося детальніше на завданні
wrapper
. Ця дуже корисна завдання, напевно, саме геніальне рішення, покликане полегшити життя програмістам. Виконавши
$ gradle wrapper
, отримаємо наступний результат:
 
 
$ gradle wrapper
:wrapper

BUILD SUCCESSFUL

Total time: 7.991 secs

 
$ ls -a
.          ..          .gradle          build.gradle     gradle          gradlew          gradlew.bat

Ми бачимо, що скрипт створив нам виконувані файли gradlew для * nix, gradlew.bat для Windows, а також папки gradle і. Gradle. Приховану папку. Gradle можна не включати в репозиторій, там містяться бібліотеки залежностей. Все основне лежить в gradle і в самому файлі gradlew. Тепер ми сміливо може віддавати наш проект будь-якій людині, що має jdk потрібної версії, і він самостійно зможе скомпілювати, зібрати, встановити проект, використовуючи
./gradlew
. Зауважте, що моя версія gradle (див. результат команди
$ gradle -version
вище) відрізняється від тієї, яку я вказав у файлі build.gradle, але це не страшно, оскільки після запуску завдання wrapper, ми отримаємо необхідну версію gradle.
 
 
$ ./gradlew -version

------------------------------------------------------------
Gradle 1.12
------------------------------------------------------------

Build time:   2014-04-29 09:24:31 UTC
Build number: none
Revision:     a831fa866d46cbee94e61a09af15f9dd95987421

Groovy:       1.8.6
Ant:          Apache Ant(TM) version 1.9.3 compiled on December 23 2013
Ivy:          2.2.0
JVM:          1.8.0_05 (Oracle Corporation 25.5-b02)
OS:           Mac OS X 10.9.3 x86_64

Тепер замість
gradle
можна сміливо використовувати
gradlew
. До речі, виконання команди
$ ./gradlew
без параметрів створить папку
.gralde
і завантажить туди всі залежні бібліотеки (про залежності нижче). Але виконання цієї команди не обов'язково, так як при будь-якому запуску gradle (gradlew), перевірятимуться залежності і викачуватися відсутні файли. Тому, отримавши проект, в якому лежать файли gradlew, можна відразу запускати потрібну задачу, список яких можна отримати по команді
./gradlew tasks

 
Підсумки другого кроку (висновок скорочений):
 
$ ./gradlew tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Application tasks
-----------------
distTar - Bundles the project as a JVM application with libs and OS specific scripts.
distZip - Bundles the project as a JVM application with libs and OS specific scripts.
installApp - Installs the project as a JVM application along with libs and OS specific scripts.
run - Runs this project as a JVM application

...

Other tasks
-----------
wrapper

...

To see all tasks and more detail, run with --all.

BUILD SUCCESSFUL

Total time: 7.808 secs

 
 
Крок 3. Заповнюємо прогалини
На даному етапі ми вже можемо виконувати декілька завдань
gradle
. Ми можемо навіть зібрати jar файл, але нічого крім порожнього маніфесту там не буде. Настав час написати код. Gradle використовує за замовчуванням таку ж структуру каталогів, що і Maven , а саме
 
src
    -main
        -java
        -resources
    -test
        -java
        -resources

 
main/java
— це java-файли нашої програми,
main/resources
— це інші файли (*. properties, *. xml, *. img та інші). У
test
знаходяться файли необхідні для тестування.
Оскільки тестування в цій статті розглядатися не буде, обійдемося створенням папки
src/main
з усіма вкладеними і приступимо до створення нашого застосування. А додаток — це Hello World, в якому будемо використовувати бібліотеку Log4j . Якраз і розберемося, як в gradle працюють залежності. Внесемо зміни в файл
build.gradle
, створимо файл
com/example/Main.java
з головним класом програми у папці
src/main/java
, а також файл з настройками Log4j
src/main/resources/log4j.xml
. І файл
gradle.properties
(не обов'язково, подробиці нижче)
 
 {project_path} / build.gradle
 
apply plugin: "java"
apply plugin: "application"

mainClassName = "com.example.Main"

sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7

repositories {
	mavenCentral()
}

dependencies {
	compile "log4j:log4j:1.2.17"
}

jar {
	manifest.attributes("Main-Class": mainClassName);
}

task wrapper(type: Wrapper) {
	gradleVersion = "1.12"
}

 {project_path} / gradle.properties
 
org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home/

 {project_path} / src / main / java / com / example / Main.java
 
package com.example;

import org.apache.log4j.Logger;

public class Main {
	private static final Logger LOG = Logger.getLogger(Main.class);

	public static void main(String[] args) {
		LOG.info("Application started");
		System.out.println("I'm the main project");
		LOG.info("Application finished");
	}
}

 {project_path} / src/main/resources/log4j.xml
 
<?xml version="1.0" encoding="UTF-8" ?> 
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> 
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> 
<appender name="console" class="org.apache.log4j.ConsoleAppender"> 
	<param name="Target" value="System.out"/> 
	<layout class="org.apache.log4j.PatternLayout"> 
		<param name="ConversionPattern" value="%p %c: %m%n"/> 
	</layout> 
</appender>     

<root> 
	<priority value ="debug" /> 
	<appender-ref ref="console" /> 
</root> 
</log4j:configuration>

Розглянемо зміни у файлі
build.gradle
. Ми додали змінну
mainClassName
. Вона вказує головний клас нашого застосування і використовується плагіном
application
в задачі
run
. Саме цей клас буде запущений. Також ми додали змінні
sourceCompatibility
і
targetCompatibility
, присвоївши їм значення
JavaVersion.VERSION_1_7
. Це змінні з плагіна
java
, показують, яка версія jdk нам потрібна при складанні. Наступний блок —
repositories
. У цьому блоці ми підключаємо репозиторій Maven . Gradle прекрасно з ним "дружить". Блок
dependencies
містить залежності нашої програми. Тонкощі настройки дивимося в документації . Тут ми вказуємо, що для задачі
compile
необхідна наявність log4j. У прикладі вказано скорочений синтаксис. Можна написати розгорнутий варіант і виглядати він буде так:
 
complie group: 'log4j', name: 'log4j', version: '1.2.17'

Для порівняння аналогічний блок в maven:
 
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

Також можна настроювати залежності від файлів
compile files('libs/a.jar', 'libs/b.jar')
і від підпроектів
compile project(':library_project')
.
Останнє додавання в
build.gradle
— це блок
jar
. Він також належить до плагіну
java
. Містить в собі додаткову інформацію для складання jar-файлу. У даному випадку ми додаємо в маніфест головний клас, скориставшись оголошеною вище змінної
mainClassName
.
Далі необов'язковий файл
gradle.properties
. Опис цього файлу розкидано по всій докментаціі, трохи знаходиться тут і тут . У даному випадку ми фактично перевизначають змінну
JAVA_HOME
. Це актуально, коли у вас кілька версій jdk, як у моєму випадку, ви могли звернути увагу на початку статті,
$ gradle -version
показує, що моя версія
JVM: 1.8.0_05 (Oracle Corporation 25.5-b02)
.
Я думаю, докладно зупинятися на файлах
src/main/java/Main.java
і
src/main/resources/log4j.xml
не має сенсу, так як все гранично просто. Відправляємо два повідомлення в Logger, повідомлення «I'm the main project» виводимо в консоль. У файлі налаштувань log4j написано, що наш logger буде виводити повідомлення також в консоль.
 
Підсумки третього кроку:
 
$ ./gradlew run
:compileJava
Download http://repo1.maven.org/maven2/log4j/log4j/1.2.17/log4j-1.2.17.jar
:processResources
:classes
:run
INFO com.example.Main: Application started
I'm the main project
INFO com.example.Main: Application finished

BUILD SUCCESSFUL

Total time: 14.627 secs

Видно, що скачується відсутня бібліотека, і продемонстровано її використання.
 
 
Крок 4. Досягнення мети
У нас вже є проект, який працює, збирається і запускається через gradle. Залишилося доробити зовсім небагато: реалізувати багатомодульна, заявлену в заголовку статті, або multi-project , якщо користуватися термінологією gradle. Створимо дві директорії в корені проекту:
main_project
і
library_project
. Тепер перемістимо папку
src
і файл
build.gradle
у щойно створену директорію
main_project
, і створимо в корені новий файл
settings.gradle
з таким вмістом (про цей файл докладніше тут ):
 
 {project_path} / settings.gradle
 
rootProject.name = 'Gradle_Multiproject'

include 'main_project'

У цьому файлі ми говоримо, як називається наш проект і які папки підключати (фактично самостійні gradle проекти). На даному етапі нам потрібна одна папка
main_project
. Після таких змін ми можемо виконати
$ ./gradlew run
або із зазначенням конкретного підпроекту
$ ./gradlew main_project:run
, і отримаємо той же результат, що і в кінці кроку 3. Тобто працюючий проект. Також можемо виконувати всі інші команди jar, build, installApp і так далі. Gradle, якщо не вказувати конкретного підпроекту, буде запускати завдання у всіх підключених підпроекту, у яких ця задача є (наприклад, якщо плагін application підключений тільки до одного підпроекту, у нас це буде main_project, команда
$ ./gradlew run
запустить run тільки цього підпроекту)
Тепер створимо код в нашому
library_project
. Створюємо
build.gradle
і
src/main/java/com/example/library/Simple.java

 
 {project_path} / library_project / build.gradle
 
apply plugin: "java"

sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7

 
 {project_path} / library_project / src / main / java / com / example / library / Simple.java
 
package com.example.library;

public class Simple {
	private int value;

	public int getValue() {
		return value;
	}

	public void setValue(int value) {
		this.value = value;
	}
} 

 
 
build.gradle
для цього підпроекту набагато простіше. Використовуємо плагін java і виставляємо змінні з версією JDK. У рамках даної статті цього достатньо. Тепер ми хочемо, щоб gradle дізнався про подпроeкте
library_project
, опишемо це у файлі
settings.gradle
:
 
 {project_path} / settings.gradle
 
rootProject.name = 'Gradle_Multiproject'

include 'main_project', 'library_project'

Тепер ми може зібрати jar файл, що містить нашу бібліотеку, командою
$ ./gradlew library_project:jar
.
 
$ ./gradlew library_project:jar
:library_project:compileJava
:library_project:processResources UP-TO-DATE
:library_project:classes
:library_project:jar

BUILD SUCCESSFUL

Total time: 10.061 secs

Отриманий файл можна знайти за адресою:
{project_path}/library_project/build/libs/library_project.jar
.
А тепер давайте додамо використання класу
Simple
в
main_project
. Для цього потрібно в файл
{project_path}/main_project/build.gradle
додати рядок
compile project(":library_project")
в блок
dependencies
, яка повідомляє, що для виконання завдання compile в цьому модулі потрібен проект
library_project
.
 
 {project_path} / main_project / build.gradle (блок
dependencies
)
 
dependencies {
	compile "log4j:log4j:1.2.17"
	compile project(":library_project")
}

 
 {project_path} / main_project / src / main / java / com / example / Main.java
 
package com.example;

import org.apache.log4j.Logger;
import com.example.library.Simple;

public class Main {
	private static final Logger LOG = Logger.getLogger(Main.class);

	public static void main(String[] args) {
		LOG.info("Application started");
		System.out.println("I'm the main project");
		Simple simple = new Simple();
		simple.setValue(10);
		System.out.println("Value from Simple: " + simple.getValue());
		LOG.info("Application finished");
	}
}

Можна перевіряти.
 
Підсумок четвертого кроку:
 
$ ./gradlew run
:library_project:compileJava UP-TO-DATE
:library_project:processResources UP-TO-DATE
:library_project:classes UP-TO-DATE
:library_project:jar UP-TO-DATE
:main_project:compileJava
:main_project:processResources UP-TO-DATE
:main_project:classes
:main_project:run
INFO com.example.Main: Application started
I'm the main project
Value from Simple: 10
INFO com.example.Main: Application finished

BUILD SUCCESSFUL

Total time: 11.022 secs

 
 
Крок 5 (заключний). Прибираємо сміття
Основна мета досягнута, але на даному етапі могли виникнути цілком закономірні питання про дублювання інформації в build файлах, більш глибокої налаштуванні gradle, а також про те, що вивчати далі. Для самостійного вивчення, я раджу ознайомитися з вмістом посилань в кінці статті. А поки, давайте приведемо в порядок наші build файли, створивши
build.gradle
докорінно проекту і змінивши вміст інших build файлів
 
 {project_path} / build.gradle
 
apply plugin: "idea"
apply plugin: "eclipse"

subprojects {
    apply plugin: "java"

    tasks.withType(JavaCompile) {
        sourceCompatibility = JavaVersion.VERSION_1_7
        targetCompatibility = JavaVersion.VERSION_1_7
    }

    repositories {
        mavenCentral()
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = "1.12"
}

 {project_path} / main_project / build.gradle
 
apply plugin: "application"

version = '1.0'

mainClassName = "com.example.Main"

dependencies {
	compile "log4j:log4j:1.2.17"
	compile project(":library_project")
}

jar {
	manifest.attributes("Main-Class": mainClassName);
}

 {project_path} / build.gradle
 
version = "1.1_beta"

У кореневому
build.gradle
ми будемо зберігати те, що відноситься до всіх проектів (насправді, можна зберігати взагалі всі налаштування, але я віддаю перевагу розділяти великі файли) і те, що не потрібно в підпроекту, наприклад, wrapper нам потрібен тільки один, в корені.
У блок
subprojects
ми поміщаємо налаштування підпроектів, а саме: підключаємо плагін java — він потрібен усім; виставляємо версію jdk; підключаємо maven-репозиторій. Також в цьому файлі ми підключаємо плагіни idea і eclipse . Ці плагіни містять завдання для генерації файлів проектів для відповідних IDE. І сюди ж переносимо задачу wrapper. Вона потрібна тільки докорінно, щоб створити загальні для всіх файли gradlew.
У підпроекту ми прибрали все зайве і додали змінну
version
. Значення цієї змінної буде додаватися до jar файлів, наприклад, замість library_project.jar тепер буде library_project-1.1.beta.jar.
Крім блоку
subprojects
, можна використовувати
allprojects
або
project(':project_name')
. Детальніше тут .
 
На цьому я закінчу. Сподіваюся, ця стаття викликала інтерес у людей, не знайомих з Gradle, і спонукала до більш докладного вивчення і подальшого використання у своїх проектах цього інструменту.
 
Спасибі за увагу.
 
 
Корисні посилання
 Ісходникі проекту, створеного в статті, на bitbucket (zip архів )
 Gradle
 Gradle User Guide
 Apache Logging Services
 Apache Maven
 Groovy Language
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.