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.