В нашей команде мы очень любим Lombok. Он позволяет писать меньше кода и меньше его рефакторить, что идеально подходит для ленивых разработчиков. Но если помимо артефакта проекта вы публикуете так же и исходники с документацией, то можете столкнуться с проблемой — код исходников не будет совпадать с байткодом. О том, как мы решали эту проблему и с какими трудностями столкнулись в процессе, я и расскажу в этом посте.
Кстати, если вы пишете на Java и по какой-то причине всё ещё не используете Lombok в своём проекте, то я рекомендую познакомиться со статьями на Хабре (раз и два). Уверен, вам понравится!
Проект, над которым мы работаем, состоит из нескольких модулей. Часть из них (назовём их условно backend) при выпуске релиза пакуется в архив (поставку), загружается в репозиторий и впоследствии деплоится на серверы приложений. Другая часть — т.н. клиентский модуль — публикуется в репозиторий в виде набора артефактов, содержащего в т.ч. sources.jar и javadoc.jar. Lombok мы используем во всех частях, а собирается всё это Maven'ом.
Некоторое время назад один из потребителей нашего сервиса обратился с проблемой — он пытался дебажить наш модуль, но не мог этого сделать, т.к. в sources.jar отсутствовали методы (и даже классы), в которых он хотел бы поставить breakpoint. Мы в нашей команде считаем, что попытка самостоятельно выявить и решить проблему, вместо того, чтобы бездумно заводить дефект — поступок достойного мужа, который нужно поощрять! :-) Поэтому было принято решение привести sources.jar в соответствие с байткодом.
Давайте представим себе, что у нас есть простое приложение, состоящее из двух классов:
И собирается наше приложение с помощью Maven'а:
Если скомпилировать этот проект (
Довольно сильно отличается от того, что попадёт в наш sources.jar, не правда ли? ;) Как видите, если бы вы подключили исходники для дебага SomePojo и захотели поставить breakpoint, например, в конструкторе, то вы бы столкнулись с проблемой — breakpoint ставить некуда, а класса SomePojoBuilder там вообще нет.
Как это часто бывает — у этой проблемы есть несколько способов решения. Давайте рассмотрим каждый из них.
Когда мы столкнулись с этой проблемой впервые — речь шла о модуле, в котором была всего пара классов, использующих Lombok. Отказываться от него, конечно, не хотелось, поэтому я сразу подумал о том, чтоб делать delombok. Поисследовав этот вопрос, я нашёл несколько странных решений с использованием Lombok-плагина для Maven'а — lombok-maven-plugin. В одном из них предлагали, например, держать исходники, в которых используется Lombok, в отдельной директории, для которой будет выполняться delombok, и сгенерированные исходники будут попадать в generated-sources, откуда уже будет компилироваться и попадать в sources.jar. Вариант это, наверное, рабочий но в этом случае в IDE не будет работать подсветка синтаксиса в оригинальных исходниках, т.к. каталог с ними не будет считаться директорией с исходниками. Такой вариант меня не устраивал, и, поскольку цена отказа от Lombok'а в этом модуле была невелика, было принято решение не тратить на это время, отключить Lombok и просто сгенерировать нужные методы через IDE.
В целом, мне кажется, что такой вариант имеет право на жизнь, но только если классов, использующих Lombok действительно мало и они редко меняются.
Спустя некоторое время пришлось вернуться к этой проблеме снова, когда речь шла уже о модуле, в котором Lombok использовался гораздо более интенсивно. Вернувшись снова к изучению этой проблемы, я наткнулся на вопрос на stackoverflow, где предлагалось выполнять для исходников delombok, а затем с помощью задачи в Ant генерировать sources.jar.
Тут нужно сделать отступление о том, почему генерировать sources.jar нужно именно с помощью Ant'а, а не с помощью Source-плагина (maven-source-plugin). Дело в том, что для этого плагина нельзя сконфигурировать директорию с исходниками. Он всегда будет использовать содержимое свойства
Итак, в случае с нашим примером, pom.xml станет выглядеть так:
Как видите, конфигурация очень сильно разрослась, и в ней есть далеко не только
Кроме того, я обнаружил, что при выполнении delombok'а по умолчанию Lombok добавляет в шапку сгенерированных файлов комментарий. При этом формат генерирумеых файлов управляется не с помощью опций в файлеслишком ленивый для этого ж программист, поэтому я нашёл их в исходниках на гитхабе.
Но ни объём конфигурации, ни её особенности не идут ни в какое сравнение с главным недостатком этого способа. Он не решает проблему. Байткод компилируется из одних исходников, а в sources.jar попадают другие. И несмотря на то, что delombok выполняется тем же самым Lombok'ом, между байткодом и сгенерированными исходниками всё равно будут отличия, т.е. для дебага они по прежнему непригодны. Мягко говоря, я расстроился, когда понял это.
Так что же делать? У меня был sources.jar с «правильными» исходниками, но они всё равно отличались от байткода. В принципе проблему могла бы решить компиляция из исходников, сгенерированных в результате delombok'а. Но проблема в том, что maven-compiler-plugin'у нельзя указать путь до исходников. Он всегда использует исходники, указанные в
Можно использовать профили! Создать профиль, который бы использовался только при сборке проекта и в котором бы подменялось значение
К счастью, для этой проблемы есть обходной путь. Можно объявить свойство, которое будет подставляться в тег
В этом случае конфигурация проекта будет выглядеть так:
Работает это следующим образом. В свойство
В результате мы получим байткод, скомпилированный из тех же исходников, которые попадут в sources.jar, т.е. дебаг наконец-то будет работать. При этом нам не нужно конфигурировать установку и деплой артефакта с исходниками, поскольку мы используем для генерации артефакта плагин
А ещё можно в
Думаю, если вы уже знакомы с Gradle, то не стоит объяснять все его преимущества перед Maven. Если же вы ещё не знакомы с ним, то на хабре для этого есть целый Хаб. Отличный повод заглянуть в него! :)
Вообще, когда я подумал об использовании Gradle, то я ожидал, что сделать в нём то, что мне нужно, будет гораздо проще, поскольку я знал, что уж в нём-то я без проблем смогу указать из чего собирать sources.jar и что компилировать в байткод. Проблема поджидала меня там, где я ожидал меньше всего — для Gradle нет работающего delombok плагина.
Есть этот плагин, но похоже, что в нём нельзя указать опции для форматирования delomboked-исходников, что мне не подходило.
Есть ещё один плагин, он генерирует текстовый файл из переданных ему опций, а потом передаёт его в качестве аргумента lombok.jar. Мне не удалось заставить его складывать сгенерированные исходники в нужный каталог, похоже это связано с тем, что путь в текстовом файле с аргументами не берётся в кавычки и не экранируется должным образом. Возможно позже я сделаю пулл реквест автору плагина с предложением исправления.
В итоге я решил пойти другим путём и просто описал задачу с вызовом Ant для выполнения delombok'а, в Lombok'е как раз есть Ant task для этого, и выглядит это вполне неплохо:
По результату этот вариант эквивалентен предыдущему.
Довольно тривиальная, по сути, задача в итоге оказалась чередой попыток найти обходные пути вокруг странных решений авторов Maven. Как по мне — скрипт Gradle, на фоне получившихся конфигов Maven'а, выглядит гораздо более очевидно и логично. Впрочем, может быть мне просто не удалось найти решение лучше? Расскажите в комментариях, решали ли вы похожую задачу, и если решали, то каким образом.
Спасибо за чтение!
Исходники
Кстати, если вы пишете на Java и по какой-то причине всё ещё не используете Lombok в своём проекте, то я рекомендую познакомиться со статьями на Хабре (раз и два). Уверен, вам понравится!
Проблема
Проект, над которым мы работаем, состоит из нескольких модулей. Часть из них (назовём их условно backend) при выпуске релиза пакуется в архив (поставку), загружается в репозиторий и впоследствии деплоится на серверы приложений. Другая часть — т.н. клиентский модуль — публикуется в репозиторий в виде набора артефактов, содержащего в т.ч. sources.jar и javadoc.jar. Lombok мы используем во всех частях, а собирается всё это Maven'ом.
Некоторое время назад один из потребителей нашего сервиса обратился с проблемой — он пытался дебажить наш модуль, но не мог этого сделать, т.к. в sources.jar отсутствовали методы (и даже классы), в которых он хотел бы поставить breakpoint. Мы в нашей команде считаем, что попытка самостоятельно выявить и решить проблему, вместо того, чтобы бездумно заводить дефект — поступок достойного мужа, который нужно поощрять! :-) Поэтому было принято решение привести sources.jar в соответствие с байткодом.
Пример
Давайте представим себе, что у нас есть простое приложение, состоящее из двух классов:
SomePojo.java
package com.github.monosoul.lombok.sourcesjar;
import lombok.Builder;
import lombok.Value;
@Value
@Builder(toBuilder = true)
class SomePojo {
/**
* Some string field
*/
String someStringField;
/**
* Another string field
*/
String anotherStringField;
}
Main.java
package com.github.monosoul.lombok.sourcesjar;
import lombok.val;
public final class Main {
public static void main(String[] args) {
if (args.length != 2) {
throw new IllegalArgumentException("Wrong arguments!");
}
val pojo = SomePojo.builder()
.someStringField(args[0])
.anotherStringField(args[1])
.build();
System.out.println(pojo);
}
}
И собирается наше приложение с помощью Maven'а:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>lombok-sourcesjar</artifactId>
<groupId>com.github.monosoul</groupId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<archive>
<manifest>
<mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Если скомпилировать этот проект (
mvn compile
), а затем сдекомпилировать получившийся байткод, то класс SomePojo будет выглядеть так:SomePojo.class
package com.github.monosoul.lombok.sourcesjar;
final class SomePojo {
private final String someStringField;
private final String anotherStringField;
SomePojo(String someStringField, String anotherStringField) {
this.someStringField = someStringField;
this.anotherStringField = anotherStringField;
}
public static SomePojo.SomePojoBuilder builder() {
return new SomePojo.SomePojoBuilder();
}
public SomePojo.SomePojoBuilder toBuilder() {
return (new SomePojo.SomePojoBuilder()).someStringField(this.someStringField).anotherStringField(this.anotherStringField);
}
public String getSomeStringField() {
return this.someStringField;
}
public String getAnotherStringField() {
return this.anotherStringField;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof SomePojo)) {
return false;
} else {
SomePojo other = (SomePojo)o;
Object this$someStringField = this.getSomeStringField();
Object other$someStringField = other.getSomeStringField();
if (this$someStringField == null) {
if (other$someStringField != null) {
return false;
}
} else if (!this$someStringField.equals(other$someStringField)) {
return false;
}
Object this$anotherStringField = this.getAnotherStringField();
Object other$anotherStringField = other.getAnotherStringField();
if (this$anotherStringField == null) {
if (other$anotherStringField != null) {
return false;
}
} else if (!this$anotherStringField.equals(other$anotherStringField)) {
return false;
}
return true;
}
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $someStringField = this.getSomeStringField();
int result = result * 59 + ($someStringField == null ? 43 : $someStringField.hashCode());
Object $anotherStringField = this.getAnotherStringField();
result = result * 59 + ($anotherStringField == null ? 43 : $anotherStringField.hashCode());
return result;
}
public String toString() {
return "SomePojo(someStringField=" + this.getSomeStringField() + ", anotherStringField=" + this.getAnotherStringField() + ")";
}
public static class SomePojoBuilder {
private String someStringField;
private String anotherStringField;
SomePojoBuilder() {
}
public SomePojo.SomePojoBuilder someStringField(String someStringField) {
this.someStringField = someStringField;
return this;
}
public SomePojo.SomePojoBuilder anotherStringField(String anotherStringField) {
this.anotherStringField = anotherStringField;
return this;
}
public SomePojo build() {
return new SomePojo(this.someStringField, this.anotherStringField);
}
public String toString() {
return "SomePojo.SomePojoBuilder(someStringField=" + this.someStringField + ", anotherStringField=" + this.anotherStringField + ")";
}
}
}
Довольно сильно отличается от того, что попадёт в наш sources.jar, не правда ли? ;) Как видите, если бы вы подключили исходники для дебага SomePojo и захотели поставить breakpoint, например, в конструкторе, то вы бы столкнулись с проблемой — breakpoint ставить некуда, а класса SomePojoBuilder там вообще нет.
Что с этим делать?
Как это часто бывает — у этой проблемы есть несколько способов решения. Давайте рассмотрим каждый из них.
Не использовать Lombok
Когда мы столкнулись с этой проблемой впервые — речь шла о модуле, в котором была всего пара классов, использующих Lombok. Отказываться от него, конечно, не хотелось, поэтому я сразу подумал о том, чтоб делать delombok. Поисследовав этот вопрос, я нашёл несколько странных решений с использованием Lombok-плагина для Maven'а — lombok-maven-plugin. В одном из них предлагали, например, держать исходники, в которых используется Lombok, в отдельной директории, для которой будет выполняться delombok, и сгенерированные исходники будут попадать в generated-sources, откуда уже будет компилироваться и попадать в sources.jar. Вариант это, наверное, рабочий но в этом случае в IDE не будет работать подсветка синтаксиса в оригинальных исходниках, т.к. каталог с ними не будет считаться директорией с исходниками. Такой вариант меня не устраивал, и, поскольку цена отказа от Lombok'а в этом модуле была невелика, было принято решение не тратить на это время, отключить Lombok и просто сгенерировать нужные методы через IDE.
В целом, мне кажется, что такой вариант имеет право на жизнь, но только если классов, использующих Lombok действительно мало и они редко меняются.
Delombok плагин + сборка sources.jar с помощью Ant
Спустя некоторое время пришлось вернуться к этой проблеме снова, когда речь шла уже о модуле, в котором Lombok использовался гораздо более интенсивно. Вернувшись снова к изучению этой проблемы, я наткнулся на вопрос на stackoverflow, где предлагалось выполнять для исходников delombok, а затем с помощью задачи в Ant генерировать sources.jar.
Тут нужно сделать отступление о том, почему генерировать sources.jar нужно именно с помощью Ant'а, а не с помощью Source-плагина (maven-source-plugin). Дело в том, что для этого плагина нельзя сконфигурировать директорию с исходниками. Он всегда будет использовать содержимое свойства
sourceDirectory
проекта.Итак, в случае с нашим примером, pom.xml станет выглядеть так:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>lombok-sourcesjar</artifactId>
<groupId>com.github.monosoul</groupId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<lombok.version>1.18.2</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<archive>
<manifest>
<mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>${lombok.version}.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectory>src/main/java</sourceDirectory>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
<addOutputDirectory>false</addOutputDirectory>
<encoding>UTF-8</encoding>
<formatPreferences>
<generateDelombokComment>skip</generateDelombokComment>
</formatPreferences>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-to-lombok-build</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>generate-delomboked-sources-jar</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<jar destfile="${project.build.directory}/${project.build.finalName}-sources.jar"
basedir="${project.build.directory}/delombok"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
<executions>
<execution>
<id>install-source-jar</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>install</phase>
<configuration>
<file>${project.build.directory}/${project.build.finalName}-sources.jar</file>
<classifier>sources</classifier>
<generatePom>true</generatePom>
<pomFile>${project.basedir}/pom.xml</pomFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M1</version>
<executions>
<execution>
<id>deploy-source-jar</id>
<goals>
<goal>deploy-file</goal>
</goals>
<phase>deploy</phase>
<configuration>
<file>${project.build.directory}/${project.build.finalName}-sources.jar</file>
<classifier>sources</classifier>
<generatePom>true</generatePom>
<pomFile>${project.basedir}/pom.xml</pomFile>
<repositoryId>someRepoId</repositoryId>
<url>some://repo.url</url>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Как видите, конфигурация очень сильно разрослась, и в ней есть далеко не только
lombok-maven-plugin
и maven-antrun-plugin
. Почему так произошло? Дело в том, что поскольку sources.jar мы теперь собираем Ant'ом, то Maven ничего об этом артефакте не знает. И нам нужно явно указать ему, как этот артефакт устанавливать, как его деплоить и как паковать в него ресурсы.Кроме того, я обнаружил, что при выполнении delombok'а по умолчанию Lombok добавляет в шапку сгенерированных файлов комментарий. При этом формат генерирумеых файлов управляется не с помощью опций в файле
lombok.config
, а с помощью опций плагина. Список этих опций оказалось непросто найти. Можно было, конечно, вызвать jar-ник Lombok'а с ключами delombok
и --help
, но я Но ни объём конфигурации, ни её особенности не идут ни в какое сравнение с главным недостатком этого способа. Он не решает проблему. Байткод компилируется из одних исходников, а в sources.jar попадают другие. И несмотря на то, что delombok выполняется тем же самым Lombok'ом, между байткодом и сгенерированными исходниками всё равно будут отличия, т.е. для дебага они по прежнему непригодны. Мягко говоря, я расстроился, когда понял это.
Delombok плагин + профиль в maven
Так что же делать? У меня был sources.jar с «правильными» исходниками, но они всё равно отличались от байткода. В принципе проблему могла бы решить компиляция из исходников, сгенерированных в результате delombok'а. Но проблема в том, что maven-compiler-plugin'у нельзя указать путь до исходников. Он всегда использует исходники, указанные в
sourceDirectory
проекта, как и maven-source-plugin
. Можно было бы указать там каталог, в который генерируются delomboked исходники, но в этом случае при импорте проекта в IDE, каталог с реальными исходниками не будет считаться таковым и для файлов в нём не будет работать подсветка синтаксиса и другие фичи. Такой вариант меня тоже не устраивал.Можно использовать профили! Создать профиль, который бы использовался только при сборке проекта и в котором бы подменялось значение
sourceDirectory
! Но есть нюанс. Тег sourceDirectory
можно объявить только внутри тега build
в корне проекта.К счастью, для этой проблемы есть обходной путь. Можно объявить свойство, которое будет подставляться в тег
sourceDirectory
, а в профиле менять значение этого свойства!В этом случае конфигурация проекта будет выглядеть так:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>lombok-sourcesjar</artifactId>
<groupId>com.github.monosoul</groupId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<lombok.version>1.18.2</lombok.version>
<origSourceDir>${project.basedir}/src/main/java</origSourceDir>
<sourceDir>${origSourceDir}</sourceDir>
<delombokedSourceDir>${project.build.directory}/delombok</delombokedSourceDir>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>build</id>
<properties>
<sourceDir>${delombokedSourceDir}</sourceDir>
</properties>
</profile>
</profiles>
<build>
<sourceDirectory>${sourceDir}</sourceDirectory>
<plugins>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>${lombok.version}.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectory>${origSourceDir}</sourceDirectory>
<outputDirectory>${delombokedSourceDir}</outputDirectory>
<addOutputDirectory>false</addOutputDirectory>
<encoding>UTF-8</encoding>
<formatPreferences>
<generateDelombokComment>skip</generateDelombokComment>
<javaLangAsFQN>skip</javaLangAsFQN>
</formatPreferences>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<archive>
<manifest>
<mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Работает это следующим образом. В свойство
origSourceDir
мы подставляем путь до каталога с оригинальными исходниками, а в свойство sourceDir
по умолчанию подставляем значение из origSourceDir
. В свойстве delombokedSourceDir
мы указываем путь до исходников, сгенерированных delombok'ом. Таким образом, при импорте проекта в IDE используется каталог из origSourceDir
, а при сборке проекта, если указать профиль build
, будет использован каталог delombokedSourceDir
.В результате мы получим байткод, скомпилированный из тех же исходников, которые попадут в sources.jar, т.е. дебаг наконец-то будет работать. При этом нам не нужно конфигурировать установку и деплой артефакта с исходниками, поскольку мы используем для генерации артефакта плагин
maven-source-plugin
. Правда, магия с переменными может запутать незнакомого с нюансами Maven'а человека.А ещё можно в
lombok.config
добавить опцию lombok.addJavaxGeneratedAnnotation = true
, тогда в сгенерированных исходниках над сгенерированным кодом будет стоять аннотация @javax.annotation.Generated("lombok")
, что поможет избежать вопросов типа «Почему ваш код выглядит так странно?!». :)Использовать Gradle
Думаю, если вы уже знакомы с Gradle, то не стоит объяснять все его преимущества перед Maven. Если же вы ещё не знакомы с ним, то на хабре для этого есть целый Хаб. Отличный повод заглянуть в него! :)
Вообще, когда я подумал об использовании Gradle, то я ожидал, что сделать в нём то, что мне нужно, будет гораздо проще, поскольку я знал, что уж в нём-то я без проблем смогу указать из чего собирать sources.jar и что компилировать в байткод. Проблема поджидала меня там, где я ожидал меньше всего — для Gradle нет работающего delombok плагина.
Есть этот плагин, но похоже, что в нём нельзя указать опции для форматирования delomboked-исходников, что мне не подходило.
Есть ещё один плагин, он генерирует текстовый файл из переданных ему опций, а потом передаёт его в качестве аргумента lombok.jar. Мне не удалось заставить его складывать сгенерированные исходники в нужный каталог, похоже это связано с тем, что путь в текстовом файле с аргументами не берётся в кавычки и не экранируется должным образом. Возможно позже я сделаю пулл реквест автору плагина с предложением исправления.
В итоге я решил пойти другим путём и просто описал задачу с вызовом Ant для выполнения delombok'а, в Lombok'е как раз есть Ant task для этого, и выглядит это вполне неплохо:
build.gradle.kts
group = "com.github.monosoul"
version = "1.0.0"
plugins {
java
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
val lombokDependency = "org.projectlombok:lombok:1.18.2"
annotationProcessor(lombokDependency)
compileOnly(lombokDependency)
}
repositories {
mavenCentral()
}
tasks {
"jar"(Jar::class) {
manifest {
attributes(
Pair("Main-Class", "com.github.monosoul.lombok.sourcesjar.Main")
)
}
}
val delombok by creating {
group = "lombok"
val delombokTarget by extra { File(buildDir, "delomboked") }
doLast({
ant.withGroovyBuilder {
"taskdef"(
"name" to "delombok",
"classname" to "lombok.delombok.ant.Tasks\$Delombok",
"classpath" to sourceSets.main.get().compileClasspath.asPath)
"mkdir"("dir" to delombokTarget)
"delombok"(
"verbose" to false,
"encoding" to "UTF-8",
"to" to delombokTarget,
"from" to sourceSets.main.get().java.srcDirs.first().absolutePath
) {
"format"("value" to "generateDelombokComment:skip")
"format"("value" to "generated:generate")
"format"("value" to "javaLangAsFQN:skip")
}
}
})
}
register<Jar>("sourcesJar") {
dependsOn(delombok)
val delombokTarget: File by delombok.extra
from(delombokTarget)
archiveClassifier.set("sources")
}
withType(JavaCompile::class) {
dependsOn(delombok)
val delombokTarget: File by delombok.extra
source = fileTree(delombokTarget)
}
}
По результату этот вариант эквивалентен предыдущему.
Выводы
Довольно тривиальная, по сути, задача в итоге оказалась чередой попыток найти обходные пути вокруг странных решений авторов Maven. Как по мне — скрипт Gradle, на фоне получившихся конфигов Maven'а, выглядит гораздо более очевидно и логично. Впрочем, может быть мне просто не удалось найти решение лучше? Расскажите в комментариях, решали ли вы похожую задачу, и если решали, то каким образом.
Спасибо за чтение!
Исходники
Комментарии (7)
MaximusWork
05.02.2019 13:00+1Думаю, если вы уже знакомы с Gradle, то не стоит объяснять все его преимущества перед Maven.
И все же! В чем преимущества?
sshikov
05.02.2019 20:07Очень своевременная статья. Думаю теперь, после прочтения я точно знаю, что в нашем проекте этого не будет.
Maccimo
06.02.2019 02:30Сами создали проблему, сами её решили, виртуозно исполнив танец на костылях.
Некоторое время назад один из потребителей нашего сервиса обратился с проблемой — он пытался дебажить наш модуль, но не мог этого сделать, т.к. в sources.jar отсутствовали методы (и даже классы), в которых он хотел бы поставить breakpoint.
Что помешало убрать бесполезный
sources.jar
из проекта и невозбранно ставить какие душе угодно точки останова в декомпилированном коде?
Qwertovsky
Предполагаю, что на написание статьи ушло больше времени, чем нужно было на генерацию конструкторов и сеттерев/геттеров в IDE.
monosoul Автор
Не без этого. :) Но лично мне Lombok нравится тем, что он позволяет фокусировать внимание только на том коде, который делает что-то важное. Это удобно тем, что если по задаче был рефакторинг, например, то diff, который нужно ревьювить будет меньше. Долго запрягаем, но быстро ездим.
distrik
После пары лет работы с ломбоком у меня выработалось к нему сильное привыкание. Недавно, буквально, хныкал, когда добавлял в легаси-класс несколько полей, а потом добавлял их же в билдер, а потом корректировал имена полей, а потом корректировал их в билдере, а потом нужно было изменить тип, и следом изменить его в билдере.
Конечно все эти мелкие операции заняли у меня меньше времени, чем заняло бы написание статьи, но и удовольствия это мне не принесло. Хочется тратить свое время на что-то более творческое.