12

Disclaimer: I have solved this problem and am documenting the solution for the world to know.

How do I create and install a *-sources.jar containing "delomboked" source code in maven?

By default, The maven-source-plugin creates a sources jar without delomboking the source files, which causes projects that depend on the library binaries to complain about mismatching source files.

coderatchet
  • 8,120
  • 17
  • 69
  • 125
  • If you project uses lombok you have to have it as a dependency. But why are you trying to delombok the resulting `-source` package? I don't understand the idea or the problem you have? – khmarbaise Sep 17 '18 at 07:15
  • 6
    @khmarbaise The issue arises when you try and debug code that uses the library you created with lombok, The runtime has the compiled (and thus delomboked) class files, but the sources jar would have the original lombok annotated source files. Your IDE would complain that the `.java` source and `.class` files don't match up which makes it difficult to debug. Personally I like to place breakpoints in the source code and let the IDE match it up with the compiled code, but this cannot be done easily when the compiled lombok code does not yet exist in the source java files. – coderatchet Sep 18 '18 at 04:22

4 Answers4

11

TL;DR (explained beneath)

Add the following plugins configuration to your plugins configuration in the project.build element of your pom.xml

<project>
    ...
<build>
<plugins>
    ...
<plugin>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok-maven-plugin</artifactId>
    <version>1.18.0.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>
    </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>
                <groupId>${project.groupId}</groupId>
                <artifactId>${project.artifactId}</artifactId>
                <version>${project.version}</version>
                <classifier>sources</classifier>
                <generatePom>true</generatePom>
                <pomFile>${project.basedir}/pom.xml</pomFile>
            </configuration>
        </execution>
    </executions>
</plugin>
</plugins>
</build>
</project>

Explanation

lombok-maven-plugin will enable you to delombok the source code (${project.basedir}/src/main/java) and place it in the target directory (${project.build.directory}/delombok). Usually this will place the code in the ${project.build.directory}/generated-sources/delombok folder, but because Intellij automatically considers this additional source-code, duplicate code errors will occur when developing your library, in order to stop this, just specify a non-default target directory (in this case just outside of the generated-sources dir).

maven-resources-plugin is necessary in order to also copy resources from the standard ${project.basedir}/src/main/resources directory. If there are any other non-standard resource directories in your project, you should configure them in the resources section for this plugin.

maven-antrun-plugin is used instead of the maven-source-plugin because you cannot specify a custom source directory in the latter. The jar task points to our custom "generated-sources" and produces the standard-named sources jar.

maven-install-plugin install-file goal is used because you cannot attach jars using the install goal. We can hack a solution by manually installing a file using the install-file goal with a classifier of sources.

I hope this helps others who are on struggle street like I was with this problem.

Joseph K. Strauss
  • 4,683
  • 1
  • 23
  • 40
coderatchet
  • 8,120
  • 17
  • 69
  • 125
10

The following solution is based on the one offered above but improves it by using the build-helper plugin to attach the generated delomboked source jar instead of using install-file. This has the benefit that the normal maven install and deploy phases correctly handle the generated file just as they would if the sources plugin had been used.

<project>
...
<build>
    <plugins>
        ...
        <plugin>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-maven-plugin</artifactId>
            <version>1.18.12.0</version>
            <executions>
                <execution>
                    <id>delombok-sources</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>delombok</goal>
                    </goals>
                    <configuration>
                        <sourceDirectory>src/main/java</sourceDirectory>
                        <outputDirectory>${project.build.directory}/delombok</outputDirectory>
                        <addOutputDirectory>false</addOutputDirectory>
                        <encoding>UTF-8</encoding>
                    </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.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.1.0</version>
            <executions>
                <execution>
                    <id>attach-delomboked-sources-jar</id>
                    <phase>package</phase>
                    <goals>
                        <goal>attach-artifact</goal>
                    </goals>
                    <configuration>
                        <artifacts>
                            <artifact>
                                <file>${project.build.directory}/${project.build.finalName}-sources.jar</file>
                                <type>jar</type>
                                <classifier>sources</classifier>
                            </artifact>
                        </artifacts>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
</project>
xap
  • 101
  • 1
  • 5
  • Hi! Welcome to SO! Remember not to use answers in order to say thanks or provide information that's not directly related to answering the questions! https://stackoverflow.com/help/how-to-answer – César Landesa Mar 23 '20 at 15:56
  • 2
    @CésarLandesa I have edited the post to make it more clear that it is an improved solution directly related to the original question. If your downvote is not due to a technical disagreement please could you remove it? Otherwise you are incorrectly indicating to others looking for solutions to this problem that there is something wrong with my answer. – xap Mar 24 '20 at 17:11
4

Would like to point out that a profile can also be used (to get around the build source directory being un-customizable). The solution is described at https://sudonull.com/post/1197-Lombok-sourcesjar-and-convenient-debug

Add the following to the pom.xml properties:

<origSourceDir>${project.basedir}/src/main/java</origSourceDir>
<sourceDir>${origSourceDir}</sourceDir>
<delombokedSourceDir>${project.build.directory}/delombok</delombokedSourceDir>
</properties>

Profile and build section changes:

<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>1.18.20.0</version>
            <executions>
                <execution>
                    <id>delombok</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>delombok</goal>
                    </goals>
                    <configuration>
                        <addOutputDirectory>false</addOutputDirectory>
                        <sourceDirectory>${origSourceDir}</sourceDirectory>
                        <outputDirectory>${delombokedSourceDir}</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </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>

Execute with mvn clean install -Pbuild

This should solve the "Library source does not match the bytecode for class" error in IntelliJ and allow seamless debugging in most cases.

Ref: "Delombok plugin + profile in maven"

awgtek
  • 1,483
  • 16
  • 28
  • 1
    Thanks for your solution. I added an Ant task right after the `maven-source-plugin` in order to delete the delomboked sources as Intellij IDEA adds this directory as a source folder automatically by default: `org.apache.maven.pluginsmaven-antrun-pluginverifyrun` – mrpiggi Mar 28 '22 at 21:07
  • thanks @mrpiggi. Also needed to add failonerror="false" to for multi-module where target directory didn't exist. – sdoxsee Jun 30 '22 at 14:18
  • @awgtek, I'd recommend putting the lombok-maven-plugin in the profile itself so that you're not generating delombok sources even when you're not using the profile. – sdoxsee Jun 30 '22 at 14:39
0

Both of the answers are flawed for multi-module projects or pure pom projects, because there are no sources, so you will have to create an empty directory, and it'll produce an empty .jar.

There is a simple (but a bit more complex) way to achieve functionality you want: make your own Maven Plugin.

Sounds overly complicated, but we can re-use maven-sources-plugin and with MOJO's inheritance update necessary parts:

import java.util.List;
import java.util.stream.Collectors;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.source.SourceJarNoForkMojo;
import org.apache.maven.project.MavenProject;

/**
 * This goal bundles all the sources into a jar archive, but uses delomboked sources.
 */
@Mojo(name = "jar-no-fork", defaultPhase = LifecyclePhase.VERIFY, threadSafe = true)
public class EnhancedSourceJarNoForkMojo extends SourceJarNoForkMojo {

    @Parameter(property = "<some-prefix>.useDelombokSources", defaultValue = "true")
    protected boolean useDelombokSources;

    @Parameter(property = "<some-prefix>.delombokSourcesLocation", defaultValue = "delombok")
    protected String delombokSourcesLocation;

    @Override
    protected List<String> getSources(MavenProject p) {
        // if user doesn't want delomboked sources, use default algorithm
        List<String> sources = super.getSources(p);
        if (!useDelombokSources) {
            return sources;
        }

        // typically, sources' list will contain: [src/main/java, target/generated_sources].
        // replace src/main/java if it's present with delombok-generated sources
        String target = p.getBuild().getDirectory();
        return super.getSources(p)
                .stream()
                .map(s -> s.endsWith("java") ? String.format("%s/%s", target, delombokSourcesLocation) : s)
                .collect(Collectors.toList());
    }

}

Gist with pom.xml routine is available here.