Skip to content
Maven plugin for migration from Apache Maven to Google Bazel
Java
Branch: master
Clone or download

Latest commit

Fetching latest commit…
Cannot retrieve the latest commit at this time.

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src
.gitignore
LICENSE
README.md
pom.xml

README.md

Bazelize Maven Plugin

This Apache Maven plugin prepares scripts required for the Google Bazel build tool.

Maven interprets the Project Object Model configurations, contained in pom.xml files, which are essential for a Maven build.

It is an XML file that contains information about the project and configuration details used by Maven to build the project.

Bazel uses Skylark, syntactically a subset of both Python 2 and Python 3, which is optimized for configuration management.

Skylark is designed to be small, simple, and thread-safe. Although it is inspired from Python, it is not a general-purpose language and most Python features are not included.

Migration from Maven to Bazel means basically generation of Skylark WORKSPACE and BUILD scripts from a set of pom.xml configuration files.

The main culprit for the migration from Maven to Bazel is the slowness of the Maven builds. With the current emphasis on DevOps and CI, this slowness can be considered as highly unsatisfactory.

Although equipped with an impressive feature set, Google Bazel faces the usual unfamiliarity issues. For Java developers, after so many years of Maven experience, Bazel looks like a time-consuming challenge from Maven's declarative (what to do) to Bazel's imperative (how to do) mindset.

 

Alternatives

Bazel itself provides a systematic approach to migrate from Maven to Bazel. The required WORKSPACE script can be generated with Migration tooling. The BUILD scripts require manual intervention.

The migrator-maven-plugin by Zalán Meggyesi may be a good starting point for Java developers.

The curated list Awesome Bazel by Jingwen holds up-to-date pointers about Bazel.

 

Apache Maven

The plugin is built with the following Maven version:

$ mvn --version
Apache Maven 3.5.3 (3383c37e1f9e9b3bc3df5050c29c8aff9f295297; 2018-02-24T22:49:05+03:00)
Maven home: /home/me/dev/tools/apache-maven-3.5.3
Java version: 1.8.0_171, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-8-oracle/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.10.0-38-generic", arch: "amd64", family: "unix"

Due to its popularity, Maven has good documentation and Internet presence. To see the command line help:

$ mvn --help

Effective POM

A POM combines with other settings and profiles, and creates an effective pom.xml. The so-called Effective POM gives details about the current configuration.

$ mvn help:effective-pom
$ mvn help:effective-pom -Doutput=pom-effective.xml

Inspection

Maven has many built-in goals for inspection of the current project. Listed dependencies and plugins can be checked easily for updates, too.

$ mvn dependency:list
$ mvn dependency:resolve
$ mvn dependency:tree
$ mvn versions:display-dependency-updates
$ mvn versions:display-plugin-updates

Maven Coordinates

Semantic Versioning is an important concept to follow. Maven coordinates uniquely identify a project, dependency, or plugin defined in a POM with groupId:artifactId:version properties. As a project grows, its version increases, too. Instead of changing versions with ad hoc find-replace, Maven provides goals with the Versions Maven Plugin.

# Enter the new version
$ mvn versions:set

# Set immediately
$ mvn versions:set -DnewVersion=0.3.3

# Revert back
$ mvn versions:revert

# Remove the 'pom.xml.versionsBackup' files.
$ mvn versions:commit

Build

Typically a Maven project is built with clean and install.

$ mvn clean install

Tests are called automatically, but this step can be skipped with maven.test.skip property.

Apache Maven Surefire test framework contains Maven Failsafe Plugin for unit tests and Maven Surefire Plugin for integration tests. Failsafe can be configured with the skipTests property. Surefire plugin introduces the skipITs property. In contrast to Maven's own maven.test.skip property, both plugins compile the tests, but do not run them.

$ mvn clean install -Dmaven.test.skip

# skip unit tests with Failsafe
$ mvn clean install -DskipTests

# skip integration tests with Surefire
$ mvn clean install -DskipITs

Reports

The Apache Maven Site Plugin generates a Web site for the plugin, which includes the standard Javadoc pages by default.

If the Apache Maven Plugin is repeated in the element of the pom.xml, then extra plugin documentation pages with details about each goal are added. The documentation is generated by default in ${project.build.directory}/site which is the target/site directory.

$ mvn site

The Apache Maven Javadoc Plugin can be explicitly configured to run during a build phase, of course. The plugin can be called by itself, too.

$ mvn javadoc:javadoc
$ mvn javadoc:test-javadoc

The Apache Maven JXR Plugin generates line-numbered HTML pages for each source file.

The Apache Maven Documentation Checker Plugin rates the configuration for a better output. However the check fails quite easily.

$ mvn docck:check

Sources

The Apache Maven Source Plugin creates JAR archives of the source files of the current project in the target directory. If configured, source:jar goal is called during the package/install phases automatically.

$ mvn source:jar

In general, bundling Test sources and Javadoc pages is best left to their plugin's respective goals.

$ mvn source:test-jar
$ mvn site:jar
$ mvn javadoc:jar
$ mvn javadoc:test-jar

 

Google Bazel

For years Google was using an internal automation tool called Blaze. In March 2015 a subset of this tool is released as the open-source project Bazel. Google Bazel aims to build and test software of any size, quickly and reliably. The project's motto is:

{Fast, Correct} - Choose two

Java is only one of the many languages Bazel supports.

Building a Java project with Bazel requires one WORKSPACE file to identify project's root directory and one or many BUILD files containing build instructions. The WORKSPACE and BUILD files can even be empty, but their mere existence may fulfill the Bazel requirements.

$ bazel version
Build label: 0.14.1
Build target: bazel-out/k8-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Fri Jun 8 12:17:35 2018 (1528460255)
Build timestamp: 1528460255
Build timestamp as int: 1528460255

As expected, Bazel has extensive documentation. Bazel's target pattern syntax determines the way rules are referred to.

All target patterns starting with '//' are resolved relative to the current workspace.

To see the command line help:

$ bazel help

Caveat

One major caveat is Bazel's lack of support for OSGi bundles. Built-in support for Google Protocol Buffers is now OK for Java.

Unfortunately OSGi bundles are required for the Apache Karaf runtime environment, and can be easily generated on Maven with Maven Bundle Plugin.

Missing features can be added by writing extensions:

Bazel extensions are files ending in .bzl. Use the load statement to import a symbol from an extension.

 

Goals of the Bazelize Maven Plugin

The Bazelize Maven Plugin aims to generate all of the required WORKSPACE and BUILD scripts by processing pom.xml configuration files with four successive goals. A further four goals are added for convenience.

$ mvn clean install
# for example:
$ cd ../project-to-migrate
$ mvn kupusoglu.orhan:bazelize-maven-plugin:GOAL -Dproperty1=value1 -Dproperty2=value2

The first four goals are expected to be called in succession. A single goal is a too big code with no granularity for each step, and calling other goals with @Execute annotation works only just once.

GOAL ORDER DESCRIPTION
module 1 Traverses a project's source files, typically src/main/java directories, and saves one tmp-bzl-module.json file for each corresponding pom.xml.
meta 2 Parses tmp-bzl-module.json files, each containing a module's meta data, and consolidates this data into a single tmp-bzl-meta.json file.
build 3 Generates a BUILD file corresonding to a pom.xml consisting of Bazel java_library rules. Serializes dependency and server data to tmp-bzl-dependency.json and tmp-bzl-server.json files respectively. Finds resources and adds to the rule.
workspace 4 Generates a WORKSPACE file to download all dependencies referred in BUILD files with Bazel maven_server and maven_jar rules.
test - Appends to BUILD files a Bazel test rule for each Java Test class. Finds resources and adds to the rule.
binary - Appends to the root BUILD file a Bazel binary rule, which refers to all other Java libraries. Requires the main class for the MANIFEST.MF file, of course: -DmainClass=com.mycompany.app.App
clean - Cleans all temporary files. With -Dexpunge cleans WORKSPACE and BUILD files, too.
help - Displays help.

 

Strict Dependencies

The test rules generated by the plugin depend upon the java_library rule contained in the same BUILD file. Therefore as explained in the documentation for options, to prevent build and test errors the option --strict_java_deps=off is necessary except for simple classes.

This option controls whether javac checks for missing direct dependencies. Java targets must explicitly declare all directly used targets as dependencies. This flag instructs javac to determine the jars actually used for type checking each java file, and warn/error if they are not the output of a direct dependency of the current target.

 

Maven Plugin Prefix Resolution

The four goals must be called like this:

$ mvn kupusoglu.orhan:bazelize-maven-plugin:module
$ mvn kupusoglu.orhan:bazelize-maven-plugin:meta
$ mvn kupusoglu.orhan:bazelize-maven-plugin:build
$ mvn kupusoglu.orhan:bazelize-maven-plugin:workspace

Clearly this a verbose process, which can be shortened with Maven plugin prefix resolution. Since the plugin follows the Maven convention of ${prefix}-maven-plugin names, its groupID can be declared in the Maven settings file. The Maven settings file is located at ${user.home}/.m2/settings.xml.

<settings>
...
    <pluginGroups>
        <pluginGroup>kupusoglu.orhan</pluginGroup>
    </pluginGroups>
...
</settings>

With this simplification the goals can be called succinctly:

$ mvn bazelize:module
$ mvn bazelize:meta
$ mvn bazelize:build
$ mvn bazelize:workspace

 

Maven Lifecycle

The Maven lifecycle extension uses sessions, but found to be of limited use. Still it can be seen in action by declaring the plugin in the target project's pom.xml:

<project>
...
    <build>
        <extensions>
            <extension>
                <groupId>kupusoglu.orhan</groupId>
                <artifactId>bazelize-maven-plugin</artifactId>
                <version>0.3.3</version>
            </extension>
        </extensions>
    </build>
...
</project>

With this addition, calling the first goal and the third goal is enough:

$ mvn bazelize:module
$ mvn bazelize:build

Another benefit is sharing context between succeeding executions. For example, with the following executions, backup suffixes for the temporary module and meta JSON files, and for the BUILD and WORKSPACE files will be identical, respectively.

$ mvn bazelize:module -Dbackup=true
$ mvn bazelize:build -Dbackup=true

 

Sample Migration

After installing the Maven-Bazelize plugin, a sample project is needed.

With the Maven Archetype Plugin Maven can generate a sample project, which just prints, not surprisingly, Hello World.

$ cd ..
$ mvn archetype:generate -DgroupId=com.mycompany.app \
                         -DartifactId=my-app \
                         -DarchetypeArtifactId=maven-archetype-quickstart \
                         -DinteractiveMode=false
$ cd my-app
$ mvn package
$ java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
Hello World!

The Bazelize Maven Plugin must be called to work on this Maven project:

$ mvn bazelize:module
$ mvn bazelize:meta
$ mvn bazelize:build
$ mvn bazelize:workspace

Now a WORKSPACE and BUILD script is generated, so building with Bazel is now possible.

With the other goals test and binary rules can be added, and clean without -Dexpunge removes the temporary JSON files:

$ mvn bazelize:help
$ mvn bazelize:test
$ mvn bazelize:binary -DmainClass=com.mycompany.app.App
$ mvn bazelize:clean

Check the WORKSPACE script:

$ cat WORKSPACE
maven_jar(
    name = "junit_junit_3_8_1",
    artifact = "junit:junit:3.8.1",
)

Check the BUILD script:

$ cat BUILD
java_library(
    name = "com_mycompany_app_my_app_1_0_SNAPSHOT",
    visibility = ["//visibility:public"],
    srcs = glob(["src/main/java/com/mycompany/app/*.java"]),
    resources = [
    ],
    deps = [
        "@junit_junit_3_8_1//jar",
    ],
)

java_test(
    name = "com_mycompany_app_AppTest",
    size = "small",
    test_class = "com.mycompany.app.AppTest",
    srcs = ["src/test/java/com/mycompany/app/AppTest.java"],
    resources = [
    ],
    deps = [
        ":com_mycompany_app_my_app_1_0_SNAPSHOT",
    ],
)

java_binary(
    name = "com_mycompany_app_App",
    main_class = "com.mycompany.app.App",
    runtime_deps = [
        ":com_mycompany_app_my_app_1_0_SNAPSHOT",
    ],
)

Let's test with Bazel:

$ bazel build ...
Starting local Bazel server and connecting to it...
.........
INFO: Analysed 3 targets (17 packages loaded).
INFO: Found 3 targets...
INFO: Elapsed time: 9.332s, Critical Path: 2.62s
INFO: 7 processes: 2 linux-sandbox, 2 local, 3 worker.
INFO: Build completed successfully, 16 total actions

# to call all tests: bazel test ...
# to capture the test output: --test_output all

$ bazel test com_mycompany_app_AppTest
INFO: Analysed target //:com_mycompany_app_AppTest (0 packages loaded).
INFO: Found 1 test target...
Target //:com_mycompany_app_AppTest up-to-date:
  bazel-bin/com_mycompany_app_AppTest.jar
  bazel-bin/com_mycompany_app_AppTest
INFO: Elapsed time: 0.513s, Critical Path: 0.28s
INFO: 1 process, linux-sandbox.
INFO: Build completed successfully, 2 total actions
//:com_mycompany_app_AppTest                                             PASSED in 0.2s

Executed 1 out of 1 test: 1 test passes.

$ bazel run com_mycompany_app_App
INFO: Analysed target //:com_mycompany_app_App (0 packages loaded).
INFO: Found 1 target...
Target //:com_mycompany_app_App up-to-date:
  bazel-bin/com_mycompany_app_App.jar
  bazel-bin/com_mycompany_app_App
INFO: Elapsed time: 0.272s, Critical Path: 0.00s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action

INFO: Running command line: bazel-bin/com_mycompany_app_App
Hello World!

$ bazel build libcom_mycompany_app_my_app_1_0_SNAPSHOT-src.jar
INFO: Analysed target //:libcom_mycompany_app_my_app_1_0_SNAPSHOT-src.jar (0 packages loaded).
INFO: Found 1 target...
Target //:libcom_mycompany_app_my_app_1_0_SNAPSHOT-src.jar up-to-date:
  bazel-bin/libcom_mycompany_app_my_app_1_0_SNAPSHOT-src.jar
INFO: Elapsed time: 0.309s, Critical Path: 0.06s
INFO: 1 process, linux-sandbox.
INFO: Build completed successfully, 3 total actions

$ bazel build com_mycompany_app_App_deploy.jar
INFO: Analysed target //:com_mycompany_app_App_deploy.jar (0 packages loaded).
INFO: Found 1 target...
Target //:com_mycompany_app_App_deploy.jar up-to-date:
  bazel-bin/com_mycompany_app_App_deploy.jar
INFO: Elapsed time: 0.297s, Critical Path: 0.07s
INFO: 1 process, linux-sandbox.
INFO: Build completed successfully, 4 total actions

$ bazel build com_mycompany_app_AppTest-src.jar
INFO: Analysed target //:com_mycompany_app_AppTest-src.jar (0 packages loaded).
INFO: Found 1 target...
Target //:com_mycompany_app_AppTest-src.jar up-to-date:
  bazel-bin/com_mycompany_app_AppTest-src.jar
INFO: Elapsed time: 0.309s, Critical Path: 0.05s
INFO: 1 process, linux-sandbox.
INFO: Build completed successfully, 3 total actions

$ bazel build com_mycompany_app_AppTest_deploy.jar
INFO: Analysed target //:com_mycompany_app_AppTest_deploy.jar (0 packages loaded).
INFO: Found 1 target...
Target //:com_mycompany_app_AppTest_deploy.jar up-to-date:
  bazel-bin/com_mycompany_app_AppTest_deploy.jar
INFO: Elapsed time: 0.306s, Critical Path: 0.06s
INFO: 1 process, linux-sandbox.
INFO: Build completed successfully, 3 total actions

# after review
$ bazel clean --expunge
INFO: Starting clean (this may take a while). Consider using --async if the clean takes more than several minutes.

Bazel can create a JAR file containing the sources collected from the transitive closure of the target:

$ bazel build com_mycompany_app_App_deploy-src.jar
$ bazel build com_mycompany_app_AppTest_deploy-src.jar

Bazel can profile a build for issues. An HTML page, here myprofile.out.html, can be generated to review the build in minute detail:

$ bazel build --profile=bazelize.out ...
$ bazel analyze-profile --html bazelize.out

Script

Using heredoc a simple shell script to call the goals can easily be prepared at target project's root.

For an example, please refer to the Quicksort repository.

$ cat << 'EOF' > bazelize.sh
#!/bin/bash

MAIN_CLASS=com.mycompany.app.App
MAVEN_PLUGIN=kupusoglu.orhan:bazelize-maven-plugin
MIGRATE=$1

bazel clean --expunge
rm -f bazelize.out bazelize.out.html

if [[ $MIGRATE == "-g" ]]
then
    mvn ${MAVEN_PLUGIN}:clean -Dexpunge
    mvn ${MAVEN_PLUGIN}:module
    mvn ${MAVEN_PLUGIN}:meta
    mvn ${MAVEN_PLUGIN}:build
    mvn ${MAVEN_PLUGIN}:workspace
    mvn ${MAVEN_PLUGIN}:clean
    mvn ${MAVEN_PLUGIN}:test

    if [[ ! -z $MAIN_CLASS ]]
    then
        mvn ${MAVEN_PLUGIN}:binary -DmainClass=${MAIN_CLASS}
    fi

    msg="migration completed"
else
    msg="bazel is ready"
fi

printf "\n"
printf "%0.s=" {1..80}
printf "\n"
printf "== ${msg}\n"
printf "%0.s=" {1..80}
printf "\n\n"

bazel build ... --strict_java_deps=off --profile=bazelize.out

bazel analyze-profile --html bazelize.out

bazel test ... --strict_java_deps=off --test_output all

if [[ ! -z $MAIN_CLASS ]]
then
    MAIN_BAZEL=${MAIN_CLASS//./_}
    bazel run $MAIN_BAZEL
fi
EOF

$ chmod +x bazelize.sh

# to generate the Bazel scripts and build & run with Bazel
$ ./bazelize.sh -g

# to only build & run with Bazel
$ ./bazelize.sh

 

Parameters

Parameters for each goal are supplied with -Dname=value, for example:

$ mvn kupusoglu.orhan:bazelize-maven-plugin:test -DsrcTest=src/test/java

The backup and suffix parameters are useful for debugging the plugin.

Detailed plugin documentation can be generated with Maven Site Plugin's mvn site goal, please check the HTML pages at target/site/index.html. For example: Project Reports > Plugin Documentation > bazelize:test

goal: clean

Parameter Default Value Description
expunge false delete all generated files including BUILD and WORKSPACE files

goal: module

Parameter Default Value Description
backup false if true back up the BUILD and tmp-bzl-meta.json files - to be used by LifeCycle
suffix "" if empty set current timestamp as suffix - to be used by LifeCycle
whiteListPattern "src/" pattern for directories to include
blackListPattern "/test|/integration-test|/target" pattern for directories to exclude

goal: meta

Parameter Default Value Description
backup false if true back up the tmp-bzl-meta.json files
suffix "" if empty set current timestamp as suffix

goal: build

Parameter Default Value Description
settingsFile "../settings.xml" path of the settings file relative to local repo
backup false if true back up the BUILD files
suffix "" if empty set current timestamp as suffix
blackListPattern "^jdk_tools" add dependency to the black list to be ignored
defaultServer "central" default remote repository
addScope true set scope of the Maven dependency
addHash false add hash of the Maven dependency
addServer false add remote server of the Maven dependency
resMain "src/main/resources" path of the resource files

goal: workspace

Parameter Default Value Description
backup false if true back up the WORKSPACE file
suffix "" if empty set current timestamp as suffix
workspaceName "" if empty no workspace() line is added

goal: test

Parameter Default Value Description
backup false if true back up the BUILD files
suffix "" if empty set current timestamp as suffix
srcTest "src/test/java" path of the test source files
resTest "src/test/resources" path of the test resource files

goal: binary

Parameter Default Value Description
backup false if true back up the BUILD file
suffix "" if empty set current timestamp as suffix
binName "" if empty use name of the main class as rule's name
mainClass name of the main class, required

 

Next Steps

Unlike a Hello World application, a large Java Maven project like ONOS SDN Controller will require some tweaks.

Parameters

The module goal can add or remove source file locations with the whiteListPattern and blackListPattern parameters respectively:

$ mvn bazelize:module -DwhiteListPattern=src/ -DblackListPattern=/test|/integration-test|/target

The build goal can eliminate unwanted dependencies with the blackListPattern parameter:

$ mvn bazelize:build -DblackListPattern="^jdk_tools|^com_sun_tools"

The workspace goal may add name to the WORKSPACE:

$ mvn bazelize:workspace -DworkspaceName=my-app
$ head -n 1 WORKSPACE
workspace(name = "my-app")

Files

Pre-arranged text files containing Bazel rules can simply be prepended or appended to WORKSPACE and specific BUILD scripts.

Another option is the bzl-build-dependency.json files. The module goal reads these files to add or remove source files, which are located in unusual directories. The build goal reads these files to add or remove dependencies to configure Bazel for correct builds .

$ cat bzl-build-dependency.json
{
    "srcWhiteList": ["src/main/java/org/project/white/net"],
    "srcBlackList": ["src/main/java/org/project/black/net"],
    "depBlackList": ["^jdk_tools", "^com_sun_tools"],
    "addDep": [":exp_rule"],
    "removeDep": ["@io_netty_netty_3_10_5_Final//jar",
                  "@io_netty_netty_all_4_0_31_Final//jar"]
}
FILE DESCRIPTION
bzl‐build-prepend‐txt Prepend Bazel rules to a BUILD file
bzl‐build‐append.txt Append Bazel rules to a BUILD file
bzl‐build‐dependency.json Inject or remove dependencies/sources from a BUILD file
bzl-workspace-prepend.txt Prepend Bazel rules to a WORKSPACE file
bzl-workspace-append.txt Append Bazel rules to a WORKSPACE file

 

Source Lines of Code

SLOC of the project can be counted by the Source Lines of Code Maven Plugin.

$ mvn kupusoglu.orhan:sloc-maven-plugin:sloc
[INFO] Scanning for projects...
[INFO] Inspecting build with total of 1 modules...
[INFO] Installing Nexus Staging features:
[INFO]   ... total of 1 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin
[INFO]
[INFO] ---------------< kupusoglu.orhan:bazelize-maven-plugin >----------------
[INFO] Building Maven-to-Bazel Migration Plugin 0.3.3
[INFO] ----------------------------[ maven-plugin ]----------------------------
[INFO]
[INFO] --- sloc-maven-plugin:0.1.4:sloc (default-cli) @ bazelize-maven-plugin ---
[INFO] SLOC - directory: /home/orhanku/ME/DEV/OK/bazelize-maven-plugin/src
+------------------+----------------------+----------+----------+----------+----------+----------+----------+
| Package Name     | File Name            | Type     | Blank    | JavaDoc  | Comment  | Code     | Total    |
+------------------+----------------------+----------+----------+----------+----------+----------+----------+
| goal             | GoalBinary.java      | src      |       14 |       26 |        0 |       52 |       92 |
| goal             | GoalBuild.java       | src      |       77 |       50 |       10 |      282 |      419 |
| goal             | GoalClean.java       | src      |        9 |       16 |        0 |       35 |       60 |
| goal             | GoalMeta.java        | src      |       11 |       20 |        0 |       39 |       70 |
| goal             | GoalModule.java      | src      |       33 |       42 |        1 |      121 |      197 |
| goal             | GoalTest.java        | src      |       13 |       28 |        0 |       48 |       89 |
| goal             | GoalWorkspace.java   | src      |       12 |       24 |        0 |       41 |       77 |
| goal             | LifeCycle.java       | src      |       26 |        5 |        2 |      106 |      139 |
+------------------+----------------------+----------+----------+----------+----------+----------+----------+
| model            | Common.java          | src      |      107 |       12 |        9 |      508 |      636 |
| model            | CommonTest.java      | test     |       40 |        0 |        0 |      150 |      190 |
| model            | CreateBinary.java    | src      |       22 |        7 |        2 |      109 |      140 |
| model            | CreateTest.java      | src      |       26 |        7 |        2 |      112 |      147 |
| model            | CreateWorkspace.java | src      |       45 |        7 |        0 |      169 |      221 |
| model            | FindMeta.java        | src      |       31 |        6 |        0 |      111 |      148 |
| model            | MavenDependency.java | src      |       34 |       22 |        0 |      145 |      201 |
| model            | MavenMeta.java       | src      |       41 |        3 |        0 |      158 |      202 |
| model            | MavenServer.java     | src      |       23 |       17 |        0 |       71 |      111 |
| model            | SaveBinary.java      | src      |       12 |        3 |        0 |       43 |       58 |
| model            | SaveMeta.java        | src      |       12 |        3 |        0 |       34 |       49 |
| model            | SaveTest.java        | src      |       12 |        3 |        0 |       46 |       61 |
| model            | SaveWorkspace.java   | src      |       12 |        3 |        0 |       42 |       57 |
| model            | SourceMeta.java      | src      |       28 |       14 |        0 |      140 |      182 |
+------------------+----------------------+----------+----------+----------+----------+----------+----------+
| 2 package(s)     | 22 file(s)           | java     |      640 |      318 |       26 |     2562 |     3546 |
+------------------+----------------------+----------+----------+----------+----------+----------+----------+

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.992 s
[INFO] Finished at: 2018-12-05T10:06:48+03:00
[INFO] ------------------------------------------------------------------------
You can’t perform that action at this time.