Maven-way to build Go projects

First, a little background. In the early 2010s, I made a small converter utility for BIN files of the BK-0010 emulator to WAV files. The utility was written in Python with the goal of maximum portability, it worked without problems and I forgot about it for a while. But in 2016, the user appeared, who had no idea about Python and how to install it. He wanted a simple executable monolith that would "just work." His request seemed logical to me, and I decided to rework the utility as a set of binary executable files for the main platforms.







image







Python and Java did not give such an opportunity (unless of course there was a desire to inflate the utility by many tens of megabytes). Potentially, the solution could be made in C / C ++, but with such a targeted coverage of the platforms, difficulties with cross-compilation would go beyond the time allotted for the task (and I had to support cross-build for Windows, Linux and MacOS in 64 and 32 bit options). So I drew attention to the language Go, which is gaining popularity, which by that time had already become quite mature and the only one who, without "dancing with a tambourine," provides all the required cross-compilation right out of the box (!) .







One inconvenience that annoyed me a lot about Go (especially as a Java developer) was the poor thoughtfulness of organizing the structure of Go projects and the need to constantly adjust the environment. Maybe the Go developers initially planned for some specific use or there was a focus on containers, but I got used to the “tasty” one and since my main tool in Java is Maven , I decided to make a plug-in that makes calls to utilities and GoSDK commands with the automatic generation of the necessary ones environment variables.







It was not interesting to make a simple plugin that would call the go utility, and I decided to go from the step - installing GoSDK on the host platform. Immediately after the start, the presence of GoSDK is checked in its configuration directory (I chose the default path ~/.mvnGoLang



) and in the absence of the required version, the GoSDK archive is automatically downloaded and unpacked from the official site . This allowed us to make portable Go projects without worrying about whether the tool of the required version is preinstalled and configured. Of course, with many settings, I foresaw the possibility of using a pre-installed version and added settings for all the steps in the process, since for many issues such as disabling SSL certificate verification or generally making HTTP requests outside (such as for the Keycloak project) are critical .







The next step, I wrapped in the Maven-goals (goals) all the go utility commands provided at that time. Since there was a chance that another new command would appear with the new version of GoSDK, a custom



task was added allowing the user to determine the desired command on their own. By default, within maven phases, tasks are executed in the following order:









Any of the basic steps can be disabled by translating the task into a nonexistent phase:







 <execution> <id>default-fix</id> <phase>none</phase> </execution>
      
      





In the internal implementation, the tasks were divided into “working with dependencies” and “not requiring dependency resolution”, after the appearance of support for the module mode, most migrated to “working with dependencies”.







The structure of the Go-maven project, I tried to bring closer to the standard folder structure accepted for Go (i.e. /src



and /bin



in the root of the project). But since Maven is imprisoned for Java, it was not directly possible to “break” its approach to organizing the project structure and make this step invisible to the user, so the basic configuration of the plugin looks a little unusual even to many familiar with maven:







 <build> ```${basedir}/src</sourceDirectory> <directory>${basedir}/bin</directory> <plugins> <plugin> <groupId>com.igormaznitsa</groupId> <artifactId>mvn-golang-wrapper</artifactId> <version>2.3.3</version> <extensions>true</extensions> <configuration> <goVersion>1.12.9</goVersion> </configuration> </plugin> </plugins> </build>
      
      





WARNING! For some reason, the HabR may incorrectly display the <sourceDirectory>${basedir}/src</sourceDirectory>



section when formatting XML

As you can see, you have to directly determine the source folder /src



and the folder with the result /bin



. On the one hand, this is a minus, on the other hand, the possibility of changing their location.







The whole minimalistic pom.xml for a single-module project, a la Hello world, is as follows:







 <?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> <groupId>com.igormaznitsa</groupId> <artifactId>mvn-golang-helloworld</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>mvn-golang</packaging> <build> ```${basedir}/src</sourceDirectory> <directory>${basedir}/bin</directory> <plugins> <plugin> <groupId>com.igormaznitsa</groupId> <artifactId>mvn-golang-wrapper</artifactId> <version>2.3.3</version> <extensions>true</extensions> <configuration> <goVersion>1.12.9</goVersion> </configuration> </plugin> </plugins> </build> </project>
      
      





Please note that the packaging of the project is designated as mvn-golang . This configuration is enough to build the executable file and put it in the resulting bin folder on the basis of the source texts in the src folder. Go build cache will also be created in the /bin



(as /bin/.goBuildCache



by default) and will be erased with clean with this temporary folder.







In the install phase, the internal mvninstall



task is mvninstall



, which simply packs the entire project in a zip archive and places it in the maven repository as a generated artifact. Initially, I just stored these artifacts, but from version 2.3.2 a mechanism was added to support them as standard maven dependencies and it became possible to develop projects with "sharing" common code through the maven repository. It is clear that putting the generated binary results into the repository as artifacts is a bad idea due to cross-platform requirements and therefore the contents of the /bin



folder are not packaged in the artifact.







Connecting another project with packaging mvn-golang



as a maven dependency looks something like this:







 <dependency> <groupId>com.igormaznitsa</groupId> <artifactId>mvn-go-test-mix-terminal</artifactId> <version>1.0.0-SNAPSHOT</version> <type>mvn-golang</type> </dependency>
      
      





With version 2.3.3, support was added for working with the Go module mechanism, but by default it is not activated (for backward compatibility) and is enabled using the configuration parameter:







 <moduleMode>true</moduleMode>
      
      





When the mechanism of Go-modules was still at the experimental stage, I added support for working with dependency versions through direct calls to CVS utilities, and a test example was made . But now I think that such a mechanism is no longer of great interest and it is more profitable to use standard dependencies through modules. Moreover, the plugin is able to preprocess go.mod



files at the time of building the project, replacing the paths to local folders if work is ongoing within the framework of a multi-module project.







Since Maven provides the ability to template using archetypes, I made two archetypes:









An example of working with the archetype of a single-module project can be seen in the animation below

image .







You can also just clone the "Hello World" project and play around:







 git clone https://github.com/raydac/mvn-golang-example.git
      
      





image







Often a reasonable question arises - why is all this necessary and what bonuses does Maven use in the process of building a Go-project? I will try to answer it point by point:







  1. Maven is a very mature, cross-platform project-building environment, supported by almost all CI platforms, for example Jenkins, using Go2XUnit you can convert test results to the format displayed by reporting plugins.
  2. Maven is cross-platform and supported by all operating systems, being included in all repositories. The presence of flexibly activated profiles makes it easy to customize the process for various platforms.
  3. Maven has a HUGE amount of plugins , which makes it easy to get a synergistic effect, say by combining Go and GWT , connecting ANTLR to the project , generating Go based on Protobuf descriptors and even adding preprocessing . All this can be organized manually through batch files, but if there are official supported plugins, then it is more profitable to use them.
  4. The project becomes easily portable between machines and platforms, and switching the GoSDK version is done by changing one line.
  5. Ease of organizing multi-module projects, with the separation of source code through the Maven repository.
  6. The tool is familiar and familiar to Java developers, which facilitates their adaptation when switching to development on Golang or when developing multilingual Go + Java solutions.
  7. There is the possibility of pseudo-development on Go in environments that support Maven, even if they do not have any support for this platform, for example, in the NetBeans IDE .


A major drawback of the solution, in my opinion, is one here - a limited number of developers familiar with Maven among the Golang community. For those who switched to Golang with C / C ++, it is clear that it is difficult to find something closer and dearer to make, just as no one has canceled the “native” Go-build systems. I noticed that for some reason, many developers do not like to mix platforms.







So, I briefly showed one of the ways to use Maven when developing Go projects using the mvn-golang-wrapper plugin. The plugin project is designed as an OSS project and posted on GitHub . If someone is interested and will use in their projects, then do not hesitate to ask questions and "report bugs." I tried to make a set of examples for different occasions (on which the plugin is being tested), but I can’t cover everything.







Test cases in the plugin project use the dev version, so if you want to build them locally after cloning the project, you first need to build the dev version of the plugin using the command in the project root directory:







 mvn install
      
      





after which, you can go into any mvn-golang-examples



subproject and build it with







 mvn clean install
      
      





you can also start building all the examples from the root of the project, using







 mvn clean install -Pexamples
      
      





The plugin supports multi-threaded assembly of projects and it can be accelerated using the corresponding argument, breaking for example into 6 threads







 mvn clean install -Pexamples -T6
      
      





During development, the project has accumulated a decent amount of "bells and whistles", which I decided not to cover in this short article. Information about the parameters with small configuration examples can be found in the mind map of this plugin (the source file in the SciaReto format is here ):







image








All Articles