Builds (Gradle)
For building our application, we are going to use Gradle, a modern build system that provides more functionality than make and is more suitable for complex projects. Gradle is popular in the Kotlin and Java ecosystems, and is the Google-endorsed build tool for Android projects.
Gradle is always setup in the context of a project: a specific directory structure, and a set of configuration files that define how your source code will be built. You create the Gradle project, and then your source code (and other assets) are added to that directory structure.
Setup
Gradle should be setup as the build system when you create your project in IntelliJ or Android Studio. Projects can be created with a variety of configurations, but for this course, you should choose Kotlin as your programming language, Gradle for your build system, and Kotlin for your DSL language.
The result of this process is a new project with a directory structure and configuration files that are ready to use with Gradle. Note that the project will be specific to the type of project that you chose e.g., Android, Desktop, Web Service.
The section below will describe the different styles of projects in more detail.
Wrapper
At the top-level of your project’s directory structure will be a script named gradlew (or gradlew.bat for Windows users). This is the Gradle wrapper: a script that you can use to run Gradle tasks without having to install Gradle on your machine. You can run it and pass it the same command-line arguments that you would normally pass to Gradle.
$ ./gradlew help
Starting a Gradle Daemon (subsequent builds will be faster)
> Task :help
Welcome to Gradle 7.5.1.
To run a build, run gradlew <task> ...
To see a list of available tasks, run gradlew tasks
To see more detail about a task, run gradlew help --task <task>
To see a list of command-line options, run gradlew --help
For troubleshooting, visit https://help.gradle.orgGradle Tasks
To run Gradle tasks from the command line, use the Gradle wrapper with the appropriate the task name. We can run ./gradlew tasks to see a list of tasks that are supported in your project. Commonly used tasks include:
./gradlew helpwill provide online help../gradlew taskswill list all of the tasks that are available../gradlew cleanwill remove temporary build files../gradlew buildwill build your project../gradlew runwill usually run your project (depending on the platform).
IntelliJ IDEA and Android Studio also allow you to run Gradle tasks directly within the IDE, as long as the Gradle plugin is installed.
Build Configuration
When you create a new project with Gradle, it will create a directory structure for you. This directory structure is opinionated: Gradle requires a specific directory structure to work correctly, so you should work within the structure that it gives you.
The sections below describe the different types of projects that you can create.
Single-Target Desktop
The default Gradle project is meant to build a console or desktop application. All code resides in a single module e.g., app in the example below.
To create this style of project, launch IntelliJ IDEA.
- From the Splash screen in IntelliJ IDEA, select
New Project, andKotlinas the project type. Nameis the name of the folder that will contain your source code. e.g., “DesktopApp”, andLocationis the location of your Git working copy.- Click
Createto proceed. If successful, IntelliJ IDEA will open into the main window to a starting project.
Your project should have this structure.
Explanation
app: the top-level code module.build.gradle.kts: the main Gradle configuration file, specific to this module.src/main: source code locationsrc/test: corresponding unit tests
gradle: the Gradle configuration fileslibs.version.toml: the “version catalog” which contains dependency info (see below)wrapper/gradle-wrapper.jar: bootstrap Gradle downloaderwrapper/gradle-wrapper.properties: information on Gradle version, download location
gradle.properties: optional environment variablesgradlew: Gradle wrapper (see above)gradlew.bat: Gradle wrapper (see above)settings.gradle.kts: top-level configuration file
Single-Target Android
If you have the Android plugin installed in IntelliJ, you will have the option to create a dedicated Android project.
You normally create an Android project in the IntelliJ New Project Wizard, and using one of the Android templates. I typically use the Empty Activity.
From the Splash screen in Android Studio:
- Select
New Project. - Select
Empty ActivityandNext.
A second screen will prompt you for project parameters. Fill in the following:
Name: Your application namePackage name: A unique package name.Save location: Your working copy location.Minimum SDK: API 26 or later.Build configuration language: Kotlin DSL.
Click Finish and your project should be created. Your directory structure will be the same as the Desktop example, with a couple of differences:
- You will have
TestandAndroidTestfor test source sets, instead of a single folder. - You will have a
Manifestfile in the root of your project which contains Android specific configuration information. - Your application will launch from a
MainActivity.ktfile instead ofMain.kt.
Multi-Project (App+Lib)
What do you do if you want to build a more complex project e.g., a combination of desktop, Android, web service and so on? In Gradle, this is known as a multi-project build, and it allows you to build multiple projects from a single top-level project.
This structure is useful when you have multiple projects that depend on each other, or when you want to share code between projects.
There is a specific structure that you need to follow to create a multi-project build. In the example below, we have subprojects application, models and server which represent different projects.
You would typically create this project by using the IntelliJ New Project Wizard to create a starting project and then manually adding the others. Creating this structure isn’t directly supported by the IDE.
.
├── application
│ ├── build.gradle.kts
│ └── src
├── gradle
│ └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
├── models
│ ├── build.gradle.kts
│ └── src
├── server
│ ├── build.gradle.kts
│ └── src
└── settings.gradle.ktsNotice that the top-level modules have been named application, models, server. Each of these represents a different type of project, with it’s own build.gradle.kts file that defines how that particular module should be built. e.g.,
applicationis a console application, so itsbuild.gradle.ktswill contain a plugin forapplication. If you build that sub-project, you will get that style of application produced.modelsis a shared library, meaning that it contains classes that are imported by both theapplicationandserverprojects. e.g., data classes.serveris a server, which runs standalone on a web server. It has specific server-like dependencies e.g., Ktor. If you build this project, you get a JAR file that can be installed on a web server.
To create a multi-project build, you need to create subprojects for each project that you want to manage. Each subproject should have its own build.gradle.kts file, and a src directory containing the source code for that project. The top-level settings.gradle.kts describes common dependencies and the overall project structure.
Kotlin Multiplatform (KMP)
This project-type is suitable for targetting two or more targets with your application e.g., Desktop/JVM + Android. If you don’t mind the folder structure, it can also be used for any single project as well!
From the Splash screen in IntelliJ IDEA:
- Select
New Project. - Choose
Kotlin Multiplatformfrom the list of project types.
Provide the following:
Nameis the name of the folder that will contain your source code. e.g., “DesktopApp”.Locationis the location of your working copy (from Step 1 above).Groupis a reverse-DNS name, used for the top-level package name.Artifactis the single-word name of the project.JDKshould point to your JDK installation (see toolchain).
Select the platforms that you wish to target, and enable Share UI (which will default to using Compose for all platforms).
Click Create to proceed. If successful, IntelliJ IDEA will open into the main window to an empty project.
You should be able to expand the folders to an individual target under the composeApp folder, and click the Run button in the toolbar to execute it.
FAQ
How do I specify the version of Gradle?
The Gradle project configuration (gradle/gradle-wrapper.properties) lists the version of Gradle to be used for your project.
To change the version of Gradle being used, update the gradle-wrapper.properties file, and change the distributionURL line to the correct version e.g., Gradle 8.0.2 below.
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/distsThe gradle-wrapper.properties file should be stored in Git, as part of your build configuration scripts.
How can I tell Gradle what version of the JDK to use?
Gradle will use the JDK in the class path on your system to execute tasks. If you are using IntelliJ IDEA, you can also specify the version to use in the IDE settings: Settings > Build Tools > Gradle.
Often you want to tell Gradle to compile your code with a specific JDK version. You can specify this in your project configuration files. For example, here’s how you specify JDK 21.
// add this to your build.gradle.kts
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}// add this to your settings.gradle.kts so Gradle can download the JDK
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0")
}How do I create an installer for my program?
For a Compose desktop application, there is a set of Gradle packaging tasks: Gradle > application > Tasks > compose desktop > package. You probably want to packageDistributionForCurrentOS which will build a regular installer for the OS that you’re currently using.
For Android, it’s acceptable to build an APK file (Build > Generate APK) instead of a full installer. This file can be drag-dropped onto a running AVD to run the program.
There are also third-party installation tools if you want something more sophisticated. e.g. Conveyor.
How can I fix an installer that fails?
Here are some common errors to watch out for (all related to the build.gradle.kts configuration file):
- You need to specify that you want to include all modules in the installer.
nativeDistributions {
includeAllModules = true
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "cs346-project"
packageVersion = "1.0.0"
}- The version property defaults to “1.0-SNAPSHOT”. You MUST change this to the format “1.0.0” - see Semantic Versioning. You should probably be incrementing this with each software release/demo.
group = "com.example"
version = "1.0.0"- It often takes some time for libraries to be updated to the “newest” JDK release. Best-practice is to use a JDK that has been stable for some time to avoid incompatibilty issues (see required software). Make sure to set the JDK in the project settings (
File>Project Structure>Project) and the Gradle project settings (IntelliJ>Settings>Build>Gradle)
If your installer fails when you execute it, try checking some of the other related tasks in the Gradle Menu in IntelliJ. e.g. compose desktop > runDistributable. The Gradle tasks in this section represent the subtasks that the installer builds and if you run them in the IDE, you will get more detailed feedback than you would from running the installer manually.
Final Word
It’s staggering how much software is available through package repositories…