0

I am trying to learn how to use gradle to make compiling and packaging my java programs easier. I already have experience programming in java and usually I just use eclipse to manage this, or, I just compile my programs manually (using javac in a terminal). However, I am getting to a point where I want to use libraries that seem to be easiest to use & maintain with gradle.

To understand more of how gradle works, I went to the gradle site where they have tutorials for making a simple gradle setup for use when writing a java application (this is the one I found). However, this tutorial didn't really explain how any of this was supposed to work, and it seemed to assume that you were following some specific system for the layout of your project. It also included some testing program or something to test your program when you build it. And in addition to these things, it never really explained how running the gradle build function worked or where (and what) the file was that held the instructions for what it was doing when you built the program.

So essentially, I am asking if someone can explain the steps to take to make a simple gradle environment that simply compiles a set of .java files and either puts them in an executable jar or just leaves them as .class files in a separate bin folder or something. It would also be helpful if you explained what each part was doing and how I can make changes to add more stuff (like basic dependencies, maybe adding .exe wrappers around the .jar, etc.)

Lukas Körfer
  • 13,515
  • 7
  • 46
  • 62
smcbot
  • 15
  • 5

1 Answers1

3

Let's start with a simple question: What basic steps are required to build a Java project? Well, as long as your project is not using some fancy preprocessing or code generation, the first step will probably be the compilation of .java files to .class files. Those .class files (and resources, if provided) may then be packed into a .jar file. It may not seem obvious to you right now, but in general, you also should run (unit) tests when your project is built. Depending on your project, there may be many more possible steps (e.g. publishing, documentation, quality control).

In Gradle, those steps are called tasks. A task can basically do anything that helps you build your software. This often means that a task takes some input files and transforms them to some output files. As an example, a compilation task transforms .java files to .class files. Another task can transform .class files to a .jar file (lets call this task jar task). Since the .class files need to be created to put them into a .jar file, the jar task depends on the compilation task. You can express this relation using Gradle, so every time the jar task runs, Gradle will ensure that the compilation task has run beforehand. Tasks do not need to do something, they might just be used to express such relations (such a task is called a lifecycle task). You may create tasks of a specific task type to reuse functionality, for a example two different tasks may be used to compile production code and test code, but both internally use a compiler and just operate on a different set of input files.

You can create tasks manually and I definitively encourage you to create some tasks to understand how their relations and their execution works, but quite often you don't need to create tasks in your build scripts, because all necessary tasks are created by plugins. Gradle plugins are really mighty, as they can basically do everything you could do manually in your build script and there is a plugin for almost everything you might want to do in your build process.

[...] I am asking if someone can explain the steps to take to make a simple gradle environment that simply compiles a set of .java files [...]

The easiest way to compile a Java project using Gradle is to create a file build.gradle with the following content:

plugins {
    id 'java'
}

That's it! Gradle is ready to build your project (just run gradle build). How is this possible? Well, the Java plugin creates all the necessary tasks to compile, test and pack your project and configures them to follow the common conventions. Take a look at the documentation of the Java plugin, it even includes a nice image that shows all the tasks and their dependencies. As shown by the image, the build task depends on all other tasks. This task is executed when you call gradle build. You can also call gradle jar or gradle assemble and Gradle will only run the tasks that are required to build the .jar file.

[...] it seemed to assume that you were following some specific system for the layout of your project.

Yes, Gradle follows an approach called convention over configuration. This means that there is a (somehow common or useful) convention for everything that otherwise would have to be configured manually.

A simple example for this approach is the location where source and resource files are expected. As you can see, this location is not configured in the build script above. However, there is a convention (established by Maven) that .java source files should go into src/main/java for production code and src/test/java for test code. Of course, those paths may be changed, but in most projects you should simply stick to the conventions.

It would also be helpful if you explained what each part was doing and how I can make changes to add more stuff (like basic dependencies [...])

Let's simply take a look at the build file in your tutorial:

plugins {
    id 'application' 
}

repositories {
    jcenter() 
}

dependencies {
    testImplementation 'junit:junit:4.13' 
    implementation 'com.google.guava:guava:29.0-jre' 
}

application {
    mainClass = 'demo.App' 
}

The first block is similar to the example above, but instead of the plugin with the identifier java the plugin with the identifier application is applied. However, this won't change much as the Application plugin internally applies the Java plugin, too.

Now let's take a look at the blocks repositories and dependencies. The dependencies block can be used to register dependencies. You may add dependencies on local .jar files, on other Gradle projects (in multi-project builds) or on external modules. The word external refers to remote repositories that provide libraries that you may use in your project. The lines inside the dependencies block each denote a specific dependency by defining the dependency scope and a module identifier that consists of a group identifier, an artifact identifier and a version (using : as a separator). The dependency scope (also called configuration in Gradle) basically defines where and how a dependency may be used. As an example, the first dependency can only be used in test code (due to the testImplementation scope). Now Gradle knows what library is required to build the project, but it does not know where to get that library. Here the repositories block comes to the rescue, because it can be used to define where Gradle should look for external module dependencies. Most of the time you will mainly use jcenter(), mavenCentral() or google(), however it is also possible to add repositories accessible under custom URLs.

The last part applies a configuration that is necessary, because no useful convention can be applied. Gradle simply does not know which class in your project should be used as the main class of your application, so you must define it manually.

Now, thanks to the Application plugin, you may not only build your project using gradle build, but also run your application from Gradle using gradle run, as the task run is one of the tasks created on top of the tasks created by the Java plugin.

Lukas Körfer
  • 13,515
  • 7
  • 46
  • 62
  • Thanks so much for this general rundown, this is exactly what I had been looking for and it explained most of my questions. Thanks again! – smcbot Nov 25 '20 at 04:24