📜 ⬆️ ⬇️

From bike to maven

It so happened that, until recently, I collected all projects written by me in Java, ahem, for NetBeans. And this arrangement of things suited me perfectly: after assembling the entire project, everything neatly folded into the dist directory with all the tied up libraries, it remained to load user documentation there, the necessary native libraries (for example, from Firebird) and go, i.e. all archived. Once I did it manually, then with a bicycle, and then with Maven. Under the cut there is a story about how I came to the maven camp and what came of it.

Interesting things started to happen when I wanted to wrap a java jar-nickname in an exe-file, somehow look nicer at it than on a bat-nickname (if only you can screw the icon :)), and then also the documentation itself copied, and then everything was collected in the archive, and then the version number so that it was present in the name of the archive, and then ... and then ... in general, it started. I ended up with writing a bike, adding the builder prefix to the name of the project as the name of my bike and writing a little code that does all the work for me (except for the fact that the project builds with NetBeans). And the version number, horror of horrors, was generally pulled out of a string constant somewhere in the source code. Understandably, my instrument did not pretend to universality, and the above-described Wishlist promised to repeat further for other projects.

And so I met Maven. I casually read about him on one Russian-speaking resource, I realized that he would solve my tasks, but he did not fully understand why he used it, what the bike didn’t do? I looked through the forums on the Internet, the benefit there were found the same non-understanding like me, to which they explained, explained, sort of convinced. The study began, difficult, given my knowledge of English, and all the documentation is in that language. Very helpful articles from Habr: Apache Maven - bases and Maven - automation of the assembly of the project . The last straw to make the right decision was the memory of how you had to re-link the libraries for a Java project inclined from the Mercurial repository. Well at least there were a few of them, and the developer of previous projects was sitting opposite, you could ask :).

So, there was a goal in front of our eyes: everything had to be done beautifully so that everything was going for me :) And laziness, as they say, is the engine of progress. As a result, the following tasks were formed:
- build the entire project as in NetBeans (with all libraries);
- automation of numbering assemblies without having to go into the code;
- wrapping jar in exe-file;
- synchronization with the Mercurial-repository (in general, I wanted to create assemblies based on the commit number);
Fortunately, NetBeans Maven support comes out of the box;
- adding user documentation and other necessary files (for example, .properties files outside the jar-archive).
')
What is included in the concept of “like in NetBeans”: copy all the resources (images, .properties-files), pull up all the related libraries, write the necessary information into the manifest. As a standard, maven collects only .class files and nothing extra, and the manifest is scanty to the jar-archive.

In this article I will not talk about the phases of the project life cycle. You can see here Introduction to the Build Lifecycle in the articles above.

So, all of us (hurray, already and I will add :)) we know that all the information on the project build is stored in the pom.xml file. There both dependencies (project artifacts and plug-ins), and the configuration of these same plug-ins, what and how to do, where to put, what other manipulations to do. Each plugin is configured in its <plugin /> tag, indicating, of course, its version, name, and life cycle phase. All configurations were placed in a common <plugins /> tag inside the <build /> tag, which is responsible for building the entire project.

We will understand the points.

Build a project like in NetBeans


What is included here? Yeah, it’s already decided: copy all the resources from the src directory (the sources are there), copy all dependencies to the final directory (target), add information to the manifest.

All dependencies were copied by the maven-dependency-plugin plugin, below is its configuration:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <configuration> <outputDirectory>${project.build.directory}/lib/</outputDirectory> <overWriteReleases>false</overWriteReleases> <overWriteSnapshots>false</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> </configuration> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> </execution> </executions> </plugin> 

Please note that they all add up to the lib directory (as in NetBeans :)). At the same time, we point out that we overwrite libraries with newer versions, do not overwrite current versions, and do not overwrite dependencies without a final version (snapshot). It also indicates on which phase this operation takes place: package. In principle, it makes no difference when guests arrive.

Information was added to the manifest as follows:
 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <classpathLayoutType>simple</classpathLayoutType> <mainClass>com.khmb.block_v2.Block_v2App</mainClass> </manifest> <manifestEntries> <Version>${buildNumber}</Version> </manifestEntries> </archive> </configuration> </plugin> 

The <classpathPrefix /> tag specifies that the libraries are pulled from the lib directory. The <classpathLayoutType /> tag with a value of simple tells the collector that jar-nicks should be dropped into one heap. There is also a repository value, then the libraries will be added up as in the maven repository, i.e. with all subdirectories with the names of packages, versions, library names.
It is worth noting the variable $ {buildNumber}, it is described below.
In addition to dependencies, the manifest indicates the class from which the program will be launched.
Description of all the parameters of the plugin used is here: Maven JAR plugin , and there are many interesting things.

Further, it was required to dump all resources (pictures and .properties-files) into the directory with compiled .class-files before building into a jar-archive.
 <plugin> <artifactId>maven-resources-plugin</artifactId> <version>2.5</version> <executions> <execution> <id>copy-resources</id> <phase>validate</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${project.build.outputDirectory}/com/khmb/${project.name}</outputDirectory> <resources> <resource> <directory>${project.build.sourceDirectory}/com/khmb/${project.name}</directory> <filtering>true</filtering> <includes> <include>**/*.properties</include> </includes> </resource> <resource> <directory>${project.build.sourceDirectory}/com/khmb/${project.name}</directory> <includes> <include>**/*.png</include> </includes> </resource> </resources> </configuration> </execution> </executions> </plugin> 

No tricky copies, just ask maven to put everything exactly as it was. But there is one feature: if png-files (and the project uses pictures only in this format) are copied just like that, then the .prioperties files are copied filtering, i.e. the plugin will look inside them and if anything needs to be replaced with maven variables, replace. This is indicated by the <filtering /> tag parameter - true. That is why resources are distributed in different <resource /> tags - it is pointless to filter images.

The compilation of the project is governed by the following plugin:
 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>${jdkVersion}</source> <target>${jdkVersion}</target> </configuration> </plugin> 

About $ {jdkVersion} variable a bit later.

Automating the numbering of assemblies without having to go into the code


And what is there such inside these files properties? Everything is very simple, in one of them lies the version number of the application, which stretches in runtime and is displayed in a window with information about this application (the so-called about).
 Application.version = ${buildNumber} 

And where did this build-namber come from? He was born to the plugin buildnumber-maven-plugin. The role of the plugin is to generate the version number, absolutely any. I decided to include the version number of my program (or rather the artifact), plus the build date:
 <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>buildnumber-maven-plugin</artifactId> <version>1.0</version> <configuration> <format>{0}-{1,date,yyyyMMdd}</format> <items> <item>${project.version}</item> <item>timestamp</item> </items> <doCheck>true</doCheck> <doUpdate>true</doUpdate> </configuration> <executions> <execution> <phase>validate</phase> <goals> <goal>create</goal> </goals> </execution> </executions> </plugin> 

The version number is made up of several parts (the <format /> tag), each of which is enclosed in curly brackets and is formed according to the description from the Java MessageFormat . Each part has a <item /> tag indicating which value should be substituted. By the way, you can insert the text “buildNumber” into it, only every time the build will generate the number of this build itself (the value is stored in the buildNumber.properties file) in the root directory of the project. I refused, because the number can be very large due to constant checks of the performance of its program, how many times it has to be launched.

Wrapping a jar in an exe file


At the time of my bike, I came across a good launch4j program, which not only wrapped the jar in exe, it also allowed me to add an icon to the application (and without it, the exe-schnick was very similar to some kind of virus or old good dos-program ), information about the author, version and so on, indicate which version of jre should be used, where to refer to it (after all, you can carry a portable version with you), but in general there is a lot more that can be done there. All settings are stored in the xml-file, its description lies on the program's website. With a bicycle, I formed this xml file and passed its path as a parameter called launch4j.exe. At the output, I got an exe-file that was attached to the dependencies as well as its jar-colleague (i.e., must be in the same place, refer to the same dependencies, unless any particular parameters are specified, of course). What was my happiness that this useful program exists in the form of a plug-in to maven. The configuration of the plug-in almost completely corresponds to its older brother-program, with the exception of some features that can be peeped here. By the way, you can collect binaries not only for Windows OS, but also for Linux. Below is my config.
 <plugin> <groupId>com.akathist.maven.plugins.launch4j</groupId> <artifactId>launch4j-maven-plugin</artifactId> <executions> <execution> <id>l4j-clui</id> <phase>package</phase> <goals> <goal>launch4j</goal> </goals> <configuration> <headerType>gui</headerType> <outfile>target/${exeFileName}.exe</outfile> <jar>target/${project.artifactId}-${project.version}.jar</jar> <errTitle>${product.title}</errTitle> <icon>favicon.ico</icon> <classPath> <mainClass>com.khmb.block_v2.Block_v2App</mainClass> <addDependencies>true</addDependencies> <preCp>anything</preCp> </classPath> <jre> <minVersion>${jdkVersion}.0</minVersion> </jre> <versionInfo> <fileVersion>${project.version}.0</fileVersion> <txtFileVersion>${project.version}</txtFileVersion> <fileDescription>       </fileDescription> <copyright>Copyright © 2011 ${product.company}</copyright> <productVersion>${project.version}.0</productVersion> <txtProductVersion>${project.version}</txtProductVersion> <companyName>${product.company}</companyName> <productName>${product.title}</productName> <internalName>${exeFileName}</internalName> <originalFilename>${exeFileName}.exe</originalFilename> </versionInfo> </configuration> </execution> </executions> </plugin> 

I indicated which version of jre should be used (corresponding to the $ {jdkVersion} variable), the input file, the output file, which icon to tie, and the information that can be looked at when viewing information about the exe file. Why is the jre version number written like this: $ {jdkVersion} .0? Everything is very simple, the launch4j plugin requires a version number in the xxx [_x] format, and in another place in the pom.xml file (in the compilation plugin configuration) you need to specify the version number in xx format. But don’t you follow identical parameters separately? Therefore, the common part was moved to the variable (see at the end).

By the way, in order for the plugin to be pulled from the maven repository on the Internet (there is no one in the central repository of this plugin) you need to specify one more:
 <repositories> <repository> <id>akathist-repository</id> <name>Akathist Repository</name> <url>http://www.9stmaryrd.com/maven</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>akathist-repository</id> <name>Akathist Repository</name> <url>http://www.9stmaryrd.com/maven</url> </pluginRepository> </pluginRepositories> 

The <repositories /> and <pluginRepositories /> tags are inside the project root tag, i.e. along with the tag description of dependencies and tag description of the assembly.

Synchronization with Mercurial repository


Initially there was an idea for the plugin to take the number of the last commit of the active branch and insert it as the build number of the project. But it didn't work out for me how to do it. I would be very grateful to those who tell.
In general, the essence of working with the repository was to ensure that before assembling the entire project, it receives the latest changes from its repository. Here the maven-scm-plugin plugin helped me. In addition, full support for Mercurial was announced, as opposed to, say, Git. Well, great, because I'm used to the mercury repository :). And the configuration is as follows:
 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-scm-plugin</artifactId> <version>1.5</version> <configuration> <!-- <username>username</username> <password>password</password> //--> <connectiontype>developerConnection</connectiontype> </configuration> <executions> <execution> <phase>validate</phase> <goals> <goal>update</goal> </goals> </execution> </executions> </plugin> 

The login and password parameters are commented out for the reason that I use a local repository without the need for authorization. Yes, by the way, outside the <build /> tag the parameters are indicated where our repository is located:
 <scm> <connection>scm:hg:file:///${project.basedir}</connection> <developerConnection>scm:hg:file:///${project.basedir}</developerConnection> <url>file:///${project.basedir}</url> </scm> 

How, what and where to twist, described again in the official documentation: SCM Implementation: Mercurial . A request is made to the repository, the latest changes are accepted, and the latest version of the project is recorded (within the framework of the active branch, of course).

The finish


It remains to draw the final line, include the user documentation in the project, pack everything into the archive and rejoice. This is the responsibility of the maven-assembly-plugin plugin.

 <plugin> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <id>assembly</id> <phase>package</phase> <goals> <goal>attached</goal> </goals> <configuration> <descriptors> <descriptor>assembly.xml</descriptor> </descriptors> </configuration> </execution> </executions> </plugin> 

The plugin documentation strongly recommends the use of the attached. The whole configuration is stored in a separate file assembly.xml. Here are its contents:
 <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> <id>bin</id> <formats> <format>zip</format> </formats> <fileSets> <fileSet> <directory>${project.basedir}</directory> <outputDirectory>/</outputDirectory> <includes> <include>data.ini</include> <include>ReleaseNotes.txt</include> </includes> </fileSet> <fileSet> <directory>${project.basedir}</directory> <outputDirectory>/docs</outputDirectory> <includes> <include>User's guide.pdf</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}</directory> <outputDirectory>/</outputDirectory> <includes> <include>${exeFileName}.exe</include> <include>${project.artifactId}-${project.version}.jar</include> <include>lib/**</include> </includes> </fileSet> </fileSets> </assembly> 

We indicate that we need to attach from which directories (do not forget about the exe-file and the tied libraries), and in the list of formats we specify only zip, the rest are not needed in my case in principle. Everything, on an output we receive archive with all necessary inside (the main thing to forget nothing :)).

Variables


All configurations are simply infested with these variables. Well, tell.
- $ {project.basedir} - stores the path to the project root directory on maven;
- $ {project.build.directory} - usually corresponds to the subdirectory of the project target on maven;
- $ {project.build.outputDirectory} - corresponds to the directory inside the target where the compiled .class files are added. Usually it has the name classes, and it is from its contents that the final jar-archive is collected.
- $ {project.name} - the name of our artifact, taken from the <artifactId /> tag
- $ {project.version} - version of the artifact, the value of the tag <version />

The remaining variables were defined personally, in the <properties /> tag inside the root <project /> tag of the pom.xml file. Here are its contents:

 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <exeFileName>block</exeFileName> <product.title> </product.title> <product.company>   :)</product.company> <product.year>2011</product.year> <jdkVersion>1.6</jdkVersion> </properties> 

I note that the name of the exe-file is specified without an extension, because In the launch4j plugin configuration, sometimes you need to specify the full name, and sometimes without the extension.

Conclusion


Use maven! :) spending time studying it will save time later.
So my project was in the local maven repository, but you can configure its publication in a remote repository (for example, in the organization’s network). When you clone a project from the Mercurial repository, all dependencies will pull up automatically - very convenient. And develop the project further, at least in NetBeans, at least in Eclipse, at least in IntelliJ IDEA - as you like more.

PS: all dependencies are pulled from the Internet, then they are added to the local maven repository, and then they are taken from there. What was my "happiness" that at work the configuration of the maven proxy was incompatible with NTLM authentication, so many dependencies had to be downloaded manually and put into the necessary subdirectories.

Source: https://habr.com/ru/post/131164/


All Articles